lovr/src/modules/graphics/model.c

535 lines
19 KiB
C

#include "graphics/model.h"
#include "graphics/buffer.h"
#include "graphics/graphics.h"
#include "graphics/material.h"
#include "graphics/mesh.h"
#include "graphics/texture.h"
#include "resources/shaders.h"
#include "core/maf.h"
#include <stdlib.h>
#include <float.h>
#include <math.h>
typedef struct {
float properties[3][4];
} NodeTransform;
struct Model {
uint32_t ref;
struct ModelData* data;
struct Buffer** buffers;
struct Mesh** meshes;
struct Texture** textures;
struct Material** materials;
float* vertices;
uint32_t* indices;
uint32_t vertexCount;
uint32_t indexCount;
NodeTransform* localTransforms;
float* globalTransforms;
bool transformsDirty;
};
static void updateGlobalTransform(Model* model, uint32_t nodeIndex, mat4 parent) {
mat4 global = model->globalTransforms + 16 * nodeIndex;
NodeTransform* local = &model->localTransforms[nodeIndex];
vec3 T = local->properties[PROP_TRANSLATION];
quat R = local->properties[PROP_ROTATION];
vec3 S = local->properties[PROP_SCALE];
mat4_init(global, parent);
mat4_translate(global, T[0], T[1], T[2]);
mat4_rotateQuat(global, R);
mat4_scale(global, S[0], S[1], S[2]);
ModelNode* node = &model->data->nodes[nodeIndex];
for (uint32_t i = 0; i < node->childCount; i++) {
updateGlobalTransform(model, node->children[i], global);
}
}
static void renderNode(Model* model, uint32_t nodeIndex, uint32_t instances) {
ModelNode* node = &model->data->nodes[nodeIndex];
mat4 globalTransform = model->globalTransforms + 16 * nodeIndex;
float poseMatrix[16 * MAX_BONES];
float* pose = NULL;
if (node->skin != ~0u) {
ModelSkin* skin = &model->data->skins[node->skin];
pose = poseMatrix;
for (uint32_t j = 0; j < skin->jointCount; j++) {
mat4 globalJointTransform = model->globalTransforms + 16 * skin->joints[j];
mat4 inverseBindMatrix = skin->inverseBindMatrices + 16 * j;
mat4 jointPose = pose + 16 * j;
mat4_set(jointPose, globalTransform);
mat4_invert(jointPose);
mat4_mul(jointPose, globalJointTransform);
mat4_mul(jointPose, inverseBindMatrix);
}
}
for (uint32_t i = 0; i < node->primitiveCount; i++) {
lovrGraphicsDrawMesh(model->meshes[node->primitiveIndex + i], globalTransform, instances, pose);
}
for (uint32_t i = 0; i < node->childCount; i++) {
renderNode(model, node->children[i], instances);
}
}
Model* lovrModelCreate(ModelData* data) {
Model* model = calloc(1, sizeof(Model));
lovrAssert(model, "Out of memory");
model->ref = 1;
model->data = data;
lovrRetain(data);
// Materials
if (data->materialCount > 0) {
model->materials = malloc(data->materialCount * sizeof(Material*));
if (data->imageCount > 0) {
model->textures = calloc(data->imageCount, sizeof(Texture*));
}
for (uint32_t i = 0; i < data->materialCount; i++) {
Material* material = lovrMaterialCreate();
for (uint32_t j = 0; j < MAX_MATERIAL_SCALARS; j++) {
lovrMaterialSetScalar(material, j, data->materials[i].scalars[j]);
}
for (uint32_t j = 0; j < MAX_MATERIAL_COLORS; j++) {
lovrMaterialSetColor(material, j, data->materials[i].colors[j]);
}
for (uint32_t j = 0; j < MAX_MATERIAL_TEXTURES; j++) {
uint32_t index = data->materials[i].images[j];
if (index != ~0u) {
if (!model->textures[index]) {
Image* image = data->images[index];
bool srgb = j == TEXTURE_DIFFUSE || j == TEXTURE_EMISSIVE;
model->textures[index] = lovrTextureCreate(TEXTURE_2D, &image, 1, srgb, true, 0);
lovrTextureSetFilter(model->textures[index], data->materials[i].filters[j]);
lovrTextureSetWrap(model->textures[index], data->materials[i].wraps[j]);
}
lovrMaterialSetTexture(material, j, model->textures[index]);
}
}
model->materials[i] = material;
}
}
// Geometry
if (data->primitiveCount > 0) {
if (data->bufferCount > 0) {
model->buffers = calloc(data->bufferCount, sizeof(Buffer*));
}
model->meshes = calloc(data->primitiveCount, sizeof(Mesh*));
for (uint32_t i = 0; i < data->primitiveCount; i++) {
ModelPrimitive* primitive = &data->primitives[i];
uint32_t vertexCount = primitive->attributes[ATTR_POSITION] ? primitive->attributes[ATTR_POSITION]->count : 0;
model->meshes[i] = lovrMeshCreate(primitive->mode, NULL, vertexCount);
if (primitive->material != ~0u) {
lovrMeshSetMaterial(model->meshes[i], model->materials[primitive->material]);
}
bool setDrawRange = false;
for (uint32_t j = 0; j < MAX_DEFAULT_ATTRIBUTES; j++) {
if (primitive->attributes[j]) {
ModelAttribute* attribute = primitive->attributes[j];
if (!model->buffers[attribute->buffer]) {
ModelBuffer* buffer = &data->buffers[attribute->buffer];
model->buffers[attribute->buffer] = lovrBufferCreate(buffer->size, buffer->data, BUFFER_VERTEX, USAGE_STATIC, false);
}
lovrMeshAttachAttribute(model->meshes[i], lovrShaderAttributeNames[j], &(MeshAttribute) {
.buffer = model->buffers[attribute->buffer],
.offset = attribute->offset,
.stride = data->buffers[attribute->buffer].stride,
.type = attribute->type,
.components = attribute->components,
.normalized = attribute->normalized
});
if (!setDrawRange && !primitive->indices) {
lovrMeshSetDrawRange(model->meshes[i], 0, attribute->count);
setDrawRange = true;
}
}
}
lovrMeshAttachAttribute(model->meshes[i], "lovrDrawID", &(MeshAttribute) {
.buffer = lovrGraphicsGetIdentityBuffer(),
.type = U8,
.components = 1,
.divisor = 1
});
if (primitive->indices) {
ModelAttribute* attribute = primitive->indices;
if (!model->buffers[attribute->buffer]) {
ModelBuffer* buffer = &data->buffers[attribute->buffer];
model->buffers[attribute->buffer] = lovrBufferCreate(buffer->size, buffer->data, BUFFER_INDEX, USAGE_STATIC, false);
}
size_t indexSize = attribute->type == U16 ? 2 : 4;
lovrMeshSetIndexBuffer(model->meshes[i], model->buffers[attribute->buffer], attribute->count, indexSize, attribute->offset);
lovrMeshSetDrawRange(model->meshes[i], 0, attribute->count);
}
}
}
// Ensure skin bone count doesn't exceed the maximum supported limit
for (uint32_t i = 0; i < data->skinCount; i++) {
uint32_t jointCount = data->skins[i].jointCount;
lovrAssert(jointCount < MAX_BONES, "ModelData skin '%d' has too many joints (%d, max is %d)", i, jointCount, MAX_BONES);
}
model->localTransforms = malloc(sizeof(NodeTransform) * data->nodeCount);
model->globalTransforms = malloc(16 * sizeof(float) * data->nodeCount);
lovrModelResetPose(model);
return model;
}
void lovrModelDestroy(void* ref) {
Model* model = ref;
if (model->buffers) {
for (uint32_t i = 0; i < model->data->bufferCount; i++) {
lovrRelease(model->buffers[i], lovrBufferDestroy);
}
free(model->buffers);
}
if (model->meshes) {
for (uint32_t i = 0; i < model->data->primitiveCount; i++) {
lovrRelease(model->meshes[i], lovrMeshDestroy);
}
free(model->meshes);
}
if (model->textures) {
for (uint32_t i = 0; i < model->data->imageCount; i++) {
lovrRelease(model->textures[i], lovrTextureDestroy);
}
free(model->textures);
}
if (model->materials) {
for (uint32_t i = 0; i < model->data->materialCount; i++) {
lovrRelease(model->materials[i], lovrMaterialDestroy);
}
free(model->materials);
}
lovrRelease(model->data, lovrModelDataDestroy);
free(model->globalTransforms);
free(model->localTransforms);
free(model);
}
ModelData* lovrModelGetModelData(Model* model) {
return model->data;
}
void lovrModelDraw(Model* model, mat4 transform, uint32_t instances) {
if (model->transformsDirty) {
updateGlobalTransform(model, model->data->rootNode, (float[]) MAT4_IDENTITY);
model->transformsDirty = false;
}
lovrGraphicsPush();
lovrGraphicsMatrixTransform(transform);
renderNode(model, model->data->rootNode, instances);
lovrGraphicsPop();
}
void lovrModelAnimate(Model* model, uint32_t animationIndex, float time, float alpha) {
if (alpha <= 0.f) {
return;
}
lovrAssert(animationIndex < model->data->animationCount, "Invalid animation index '%d' (Model only has %d animations)", animationIndex, model->data->animationCount);
ModelAnimation* animation = &model->data->animations[animationIndex];
time = fmodf(time, animation->duration);
for (uint32_t i = 0; i < animation->channelCount; i++) {
ModelAnimationChannel* channel = &animation->channels[i];
uint32_t nodeIndex = channel->nodeIndex;
NodeTransform* transform = &model->localTransforms[nodeIndex];
uint32_t keyframe = 0;
while (keyframe < channel->keyframeCount && channel->times[keyframe] < time) {
keyframe++;
}
float property[4];
bool rotate = channel->property == PROP_ROTATION;
size_t n = 3 + rotate;
float* (*lerp)(float* a, float* b, float t) = rotate ? quat_slerp : vec3_lerp;
if (keyframe == 0 || keyframe >= channel->keyframeCount) {
size_t index = MIN(keyframe, channel->keyframeCount - 1);
// For cubic interpolation, each keyframe has 3 parts, and the actual data is in the middle (*3, +1)
if (channel->smoothing == SMOOTH_CUBIC) {
index = 3 * index + 1;
}
memcpy(property, channel->data + index * n, n * sizeof(float));
} else {
float t1 = channel->times[keyframe - 1];
float t2 = channel->times[keyframe];
float z = (time - t1) / (t2 - t1);
switch (channel->smoothing) {
case SMOOTH_STEP:
memcpy(property, channel->data + (z >= .5f ? keyframe : keyframe - 1) * n, n * sizeof(float));
break;
case SMOOTH_LINEAR:
memcpy(property, channel->data + (keyframe - 1) * n, n * sizeof(float));
lerp(property, channel->data + keyframe * n, z);
break;
case SMOOTH_CUBIC: {
size_t stride = 3 * n;
float* p0 = channel->data + (keyframe - 1) * stride + 1 * n;
float* m0 = channel->data + (keyframe - 1) * stride + 2 * n;
float* p1 = channel->data + (keyframe - 0) * stride + 1 * n;
float* m1 = channel->data + (keyframe - 0) * stride + 0 * n;
float dt = t2 - t1;
float z2 = z * z;
float z3 = z2 * z;
float a = 2.f * z3 - 3.f * z2 + 1.f;
float b = 2.f * z3 - 3.f * z2 + 1.f;
float c = (-2.f * z3 + 3.f * z2);
float d = (z3 * -z2) * dt;
for (size_t j = 0; j < n; j++) {
property[j] = a * p0[j] + b * m0[j] + c * p1[j] + d * m1[j];
}
break;
}
default:
break;
}
}
if (alpha >= 1.f) {
memcpy(transform->properties[channel->property], property, n * sizeof(float));
} else {
lerp(transform->properties[channel->property], property, alpha);
}
}
model->transformsDirty = true;
}
void lovrModelGetNodePose(Model* model, uint32_t nodeIndex, float position[4], float rotation[4], CoordinateSpace space) {
lovrAssert(nodeIndex < model->data->nodeCount, "Invalid node index '%d' (Model only has %d nodes)", nodeIndex, model->data->nodeCount);
if (space == SPACE_LOCAL) {
vec3_init(position, model->localTransforms[nodeIndex].properties[PROP_TRANSLATION]);
quat_init(rotation, model->localTransforms[nodeIndex].properties[PROP_ROTATION]);
} else {
if (model->transformsDirty) {
updateGlobalTransform(model, model->data->rootNode, (float[]) MAT4_IDENTITY);
model->transformsDirty = false;
}
mat4_getPosition(model->globalTransforms + 16 * nodeIndex, position);
mat4_getOrientation(model->globalTransforms + 16 * nodeIndex, rotation);
}
}
void lovrModelPose(Model* model, uint32_t nodeIndex, float position[4], float rotation[4], float alpha) {
if (alpha <= 0.f) {
return;
}
lovrAssert(nodeIndex < model->data->nodeCount, "Invalid node index '%d' (Model only has %d node)", nodeIndex + 1, model->data->nodeCount, model->data->nodeCount == 1 ? "" : "s");
NodeTransform* transform = &model->localTransforms[nodeIndex];
if (alpha >= 1.f) {
vec3_init(transform->properties[PROP_TRANSLATION], position);
quat_init(transform->properties[PROP_ROTATION], rotation);
} else {
vec3_lerp(transform->properties[PROP_TRANSLATION], position, alpha);
quat_slerp(transform->properties[PROP_ROTATION], rotation, alpha);
}
model->transformsDirty = true;
}
void lovrModelResetPose(Model* model) {
for (uint32_t i = 0; i < model->data->nodeCount; i++) {
if (model->data->nodes[i].matrix) {
mat4_getPosition(model->data->nodes[i].transform.matrix, model->localTransforms[i].properties[PROP_TRANSLATION]);
mat4_getOrientation(model->data->nodes[i].transform.matrix, model->localTransforms[i].properties[PROP_ROTATION]);
mat4_getScale(model->data->nodes[i].transform.matrix, model->localTransforms[i].properties[PROP_SCALE]);
} else {
vec3_init(model->localTransforms[i].properties[PROP_TRANSLATION], model->data->nodes[i].transform.properties.translation);
quat_init(model->localTransforms[i].properties[PROP_ROTATION], model->data->nodes[i].transform.properties.rotation);
vec3_init(model->localTransforms[i].properties[PROP_SCALE], model->data->nodes[i].transform.properties.scale);
}
}
model->transformsDirty = true;
}
Material* lovrModelGetMaterial(Model* model, uint32_t material) {
lovrAssert(material < model->data->materialCount, "Invalid material index '%d' (Model only has %d material%s)", material + 1, model->data->materialCount, model->data->materialCount == 1 ? "" : "s");
return model->materials[material];
}
static void applyAABB(Model* model, uint32_t nodeIndex, float aabb[6]) {
ModelNode* node = &model->data->nodes[nodeIndex];
for (uint32_t i = 0; i < node->primitiveCount; i++) {
ModelAttribute* position = model->data->primitives[node->primitiveIndex + i].attributes[ATTR_POSITION];
if (position && position->hasMin && position->hasMax) {
mat4 m = model->globalTransforms + 16 * nodeIndex;
float xa[3] = { position->min[0] * m[0], position->min[0] * m[1], position->min[0] * m[2] };
float xb[3] = { position->max[0] * m[0], position->max[0] * m[1], position->max[0] * m[2] };
float ya[3] = { position->min[1] * m[4], position->min[1] * m[5], position->min[1] * m[6] };
float yb[3] = { position->max[1] * m[4], position->max[1] * m[5], position->max[1] * m[6] };
float za[3] = { position->min[2] * m[8], position->min[2] * m[9], position->min[2] * m[10] };
float zb[3] = { position->max[2] * m[8], position->max[2] * m[9], position->max[2] * m[10] };
float min[3] = {
MIN(xa[0], xb[0]) + MIN(ya[0], yb[0]) + MIN(za[0], zb[0]) + m[12],
MIN(xa[1], xb[1]) + MIN(ya[1], yb[1]) + MIN(za[1], zb[1]) + m[13],
MIN(xa[2], xb[2]) + MIN(ya[2], yb[2]) + MIN(za[2], zb[2]) + m[14]
};
float max[3] = {
MAX(xa[0], xb[0]) + MAX(ya[0], yb[0]) + MAX(za[0], zb[0]) + m[12],
MAX(xa[1], xb[1]) + MAX(ya[1], yb[1]) + MAX(za[1], zb[1]) + m[13],
MAX(xa[2], xb[2]) + MAX(ya[2], yb[2]) + MAX(za[2], zb[2]) + m[14]
};
aabb[0] = MIN(aabb[0], min[0]);
aabb[1] = MAX(aabb[1], max[0]);
aabb[2] = MIN(aabb[2], min[1]);
aabb[3] = MAX(aabb[3], max[1]);
aabb[4] = MIN(aabb[4], min[2]);
aabb[5] = MAX(aabb[5], max[2]);
}
}
for (uint32_t i = 0; i < node->childCount; i++) {
applyAABB(model, node->children[i], aabb);
}
}
void lovrModelGetAABB(Model* model, float aabb[6]) {
if (model->transformsDirty) {
updateGlobalTransform(model, model->data->rootNode, (float[]) MAT4_IDENTITY);
model->transformsDirty = false;
}
aabb[0] = aabb[2] = aabb[4] = FLT_MAX;
aabb[1] = aabb[3] = aabb[5] = -FLT_MAX;
applyAABB(model, model->data->rootNode, aabb);
}
static void countVertices(Model* model, uint32_t nodeIndex, uint32_t* vertexCount, uint32_t* indexCount) {
ModelNode* node = &model->data->nodes[nodeIndex];
for (uint32_t i = 0; i < node->primitiveCount; i++) {
ModelPrimitive* primitive = &model->data->primitives[node->primitiveIndex + i];
ModelAttribute* positions = primitive->attributes[ATTR_POSITION];
ModelAttribute* indices = primitive->indices;
uint32_t count = positions ? positions->count : 0;
*vertexCount += count;
*indexCount += indices ? indices->count : count;
}
for (uint32_t i = 0; i < node->childCount; i++) {
countVertices(model, node->children[i], vertexCount, indexCount);
}
}
static void collectVertices(Model* model, uint32_t nodeIndex, float** vertices, uint32_t** indices, uint32_t* baseIndex) {
ModelNode* node = &model->data->nodes[nodeIndex];
mat4 transform = model->globalTransforms + 16 * nodeIndex;
for (uint32_t i = 0; i < node->primitiveCount; i++) {
ModelPrimitive* primitive = &model->data->primitives[node->primitiveIndex + i];
ModelAttribute* positions = primitive->attributes[ATTR_POSITION];
if (!positions) continue;
ModelBuffer* buffer = &model->data->buffers[positions->buffer];
char* data = (char*) buffer->data + positions->offset;
size_t stride = buffer->stride == 0 ? 3 * sizeof(float) : buffer->stride;
for (uint32_t j = 0; j < positions->count; j++) {
float v[4];
memcpy(v, data, 3 * sizeof(float));
mat4_transform(transform, v);
memcpy(*vertices, v, 3 * sizeof(float));
*vertices += 3;
data += stride;
}
ModelAttribute* index = primitive->indices;
if (index) {
AttributeType type = index->type;
lovrAssert(type == U16 || type == U32, "Unreachable");
buffer = &model->data->buffers[index->buffer];
data = (char*) buffer->data + index->offset;
size_t stride = buffer->stride == 0 ? (type == U16 ? 2 : 4) : buffer->stride;
for (uint32_t j = 0; j < index->count; j++) {
**indices = (type == U16 ? ((uint32_t) *(uint16_t*) data) : *(uint32_t*) data) + *baseIndex;
*indices += 1;
data += stride;
}
} else {
for (uint32_t j = 0; j < positions->count; j++) {
**indices = j + *baseIndex;
*indices += 1;
}
}
*baseIndex += positions->count;
}
for (uint32_t i = 0; i < node->childCount; i++) {
collectVertices(model, node->children[i], vertices, indices, baseIndex);
}
}
void lovrModelGetTriangles(Model* model, float** vertices, uint32_t* vertexCount, uint32_t** indices, uint32_t* indexCount) {
if (model->transformsDirty) {
updateGlobalTransform(model, model->data->rootNode, (float[]) MAT4_IDENTITY);
model->transformsDirty = false;
}
if (!model->vertices) {
countVertices(model, model->data->rootNode, &model->vertexCount, &model->indexCount);
model->vertices = malloc(model->vertexCount * 3 * sizeof(float));
model->indices = malloc(model->indexCount * sizeof(uint32_t));
lovrAssert(model->vertices && model->indices, "Out of memory");
}
*vertices = model->vertices;
*indices = model->indices;
uint32_t baseIndex = 0;
collectVertices(model, model->data->rootNode, vertices, indices, &baseIndex);
*vertexCount = model->vertexCount;
*indexCount = model->indexCount;
*vertices = model->vertices;
*indices = model->indices;
}