text: #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 "core/maf.h"
  #include "shaders.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;
  }
  