+ aiVector3D lerpPos;
+ if (yIndex == 0) {
+ lerpPos = anim->mPositionKeys[0].mValue;
+ } else if (yIndex == -1) {
+ lerpPos = anim->mPositionKeys[anim->mNumPositionKeys - 1].mValue;
+ } else {
+ auto X = anim->mPositionKeys[yIndex - 1];
+ auto Y = anim->mPositionKeys[yIndex];
+
+ lerpPos = (X.mValue * (float)(Y.mTime - tick) + Y.mValue * (float)(tick - X.mTime)) / (float)(Y.mTime - X.mTime);
+ }
+ aiMatrix4x4 result;
+ aiMatrix4x4::Translation(lerpPos, result);
+ return aiMatrixToMat4(result);
+}
+
+glm::mat4 lerpRotation(const aiNodeAnim *anim, const float tick) {
+ int yIndex = -1;
+ for (int i = 0; i < anim->mNumRotationKeys; i++) {
+ aiQuatKey vk = anim->mRotationKeys[i];
+ if (vk.mTime > tick) {
+ yIndex = i;
+ break;
+ }
+ }
+
+ aiQuaternion result;
+ if (yIndex < 1) {
+ result = anim->mRotationKeys[0].mValue;
+ } else if (yIndex == -1) {
+ result = anim->mRotationKeys[anim->mNumRotationKeys - 1].mValue;
+ } else {
+
+ auto X = anim->mRotationKeys[yIndex - 1];
+ auto Y = anim->mRotationKeys[yIndex];
+
+ float mix = (tick - X.mTime) / (Y.mTime - X.mTime);
+
+ aiQuaternion::Interpolate(result, X.mValue, Y.mValue, mix);
+
+ }
+ return aiMatrixToMat4(aiMatrix4x4(result.GetMatrix()));
+}
+
+glm::mat4 lerpScaling(const aiNodeAnim *anim, const float tick) {
+ int yIndex = -1;
+ for (int i = 0; i < anim->mNumScalingKeys; i++) {
+ aiVectorKey vk = anim->mScalingKeys[i];
+ if (vk.mTime > tick) {
+ yIndex = i;
+ break;
+ }
+ }
+
+ aiVector3D lerpPos;
+ if (yIndex < 1) {
+ lerpPos = anim->mScalingKeys[0].mValue;
+ } else {
+ auto X = anim->mScalingKeys[yIndex - 1];
+ auto Y = anim->mScalingKeys[yIndex];
+
+ lerpPos = (X.mValue * (float)(Y.mTime - tick) + Y.mValue * (float)(tick - X.mTime)) / (float)(Y.mTime - X.mTime);
+ }
+ aiMatrix4x4 result;
+ aiMatrix4x4::Scaling(lerpPos, result);
+ return aiMatrixToMat4(result);
+}
+
+glm::mat4 Model::Node::totalTrans(const glm::mat4 parentTrans, const float tick) const {
+ glm::mat4 aiTrans = aiMatrixToMat4(ai.mTransformation);
+ if (animMap->count(std::string(ai.mName.C_Str()))) {
+ for (const Animation anim: animMap->at(std::string(ai.mName.C_Str()))) {
+ // animations are *absolute*
+ // they replace aiNode.mTransformation!!
+ aiTrans = glm::mat4(1);
+ float t = fmod(tick, anim.duration);
+ for (const aiNodeAnim *nodeAnim: anim.nodeAnims) {
+ aiTrans *= lerpPosition(nodeAnim, t);
+ aiTrans *= lerpRotation(nodeAnim, t);
+ aiTrans *= lerpScaling(nodeAnim, t);
+ }
+ }
+ }
+
+ glm::mat4 m = parentTrans * aiTrans * transform;
+ return m;
+}
+
+void Model::Node::draw( const std::vector<Mesh> &meshes,
+ const std::vector<Material> &materials,
+ const Skybox skybox,
+ const float tick,
+ const BoneTransforms &boneTransforms,
+ glm::mat4 parentTrans = glm::mat4(1)) const {
+
+ GLuint modelLoc = glGetUniformLocation(progId, "model");
+ glm::mat4 m = totalTrans(parentTrans, tick);
+
+#ifdef DEBUG_NODES
+ drawDebugNode(m);
+#endif
+
+ for (unsigned int i: meshIndices) {
+ const Mesh &mesh = meshes[i];
+ glBindVertexArray(mesh.vao);
+
+ // bones
+ std::vector<glm::mat4> idBones(17, glm::mat4(1.f));
+ glUniformMatrix4fv(glGetUniformLocation(progId, "bones"), 17, GL_FALSE, glm::value_ptr(idBones[0]));
+
+ // bonemap: map from bone nodes to bone ids and aiBones
+ for (auto pair: mesh.boneMap) {
+
+ std::string boneName = pair.first;
+
+ unsigned int boneId = pair.second.first;
+ aiBone *bone = pair.second.second;
+ // This is actually an inverse-bind matrix
+ // i.e. transforms bone space -> mesh space
+ // so no need to inverse again!
+ // https://github.com/assimp/assimp/pull/1803/files
+ glm::mat4 boneOffset = aiMatrixToMat4(bone->mOffsetMatrix);
+
+ if (!boneTransforms.count(boneName)) abort();
+ glm::mat4 boneTrans = boneTransforms.at(boneName);
+
+ boneTrans = boneTrans * boneOffset;
+
+ std::string boneLocStr = "bones[" + std::to_string(boneId) + "]";
+ GLuint boneLoc = glGetUniformLocation(progId, boneLocStr.c_str());
+ glUniformMatrix4fv(boneLoc, 1, GL_FALSE, glm::value_ptr(boneTrans));
+ }
+
+ Material material = materials[mesh.materialIndex];
+ material.bind();
+
+ glUniform1i(glGetUniformLocation(progId, "irradianceMap"), 4);
+ glActiveTexture(GL_TEXTURE4);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, skybox.getIrradianceMap());
+
+ glUniform1i(glGetUniformLocation(progId, "prefilterMap"), 5);
+ glActiveTexture(GL_TEXTURE5);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, skybox.getPrefilterMap());
+
+ glUniform1i(glGetUniformLocation(progId, "brdfMap"), 6);
+ glActiveTexture(GL_TEXTURE6);
+ glBindTexture(GL_TEXTURE_2D, skybox.getBRDFMap());
+
+ glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(m));
+
+ glDrawElements(GL_TRIANGLES, mesh.numIndices, GL_UNSIGNED_INT, 0);
+ }
+ for (Node *child: children) child->draw(meshes, materials, skybox, tick, boneTransforms, m);
+}
+
+void printMatrix4x4(aiMatrix4x4 m) {
+ fprintf(stderr, "%f, %f, %f, %f\n", m.a1, m.a2, m.a3, m.a4);
+ fprintf(stderr, "%f, %f, %f, %f\n", m.b1, m.b2, m.b3, m.b4);
+ fprintf(stderr, "%f, %f, %f, %f\n", m.c1, m.c2, m.c3, m.c4);
+ fprintf(stderr, "%f, %f, %f, %f\n", m.d1, m.d2, m.d3, m.d4);
+}
+
+void printHierarchy(aiNode *n, int indent = 0) {
+ for (int i = 0; i < indent; i++)
+ fprintf(stderr, "\t");
+ fprintf(stderr,"%s\n", n->mName.C_Str());
+ printMatrix4x4(n->mTransformation);
+ for (int i = 0; i < n->mNumChildren; i++)
+ printHierarchy(n->mChildren[i], indent + 1);
+}
+
+Model::Model(const aiScene *scene, Program p): program(p) {
+ glUseProgram(p.progId);