Rework UI architecture and blendshape animation
[opengl.git] / blendshapes.cpp
index 8611f6289fb7cd83b024071bc9f88a0d9e45feee..d4d03236d6577c12840914bcd1c990923f0db17e 100644 (file)
@@ -1,13 +1,18 @@
-#include <assimp/Importer.hpp>
-#include <assimp/postprocess.h>
 #include "blendshapes.hpp"
 #include "util.hpp"
+#include <assimp/Importer.hpp>
+#include <assimp/postprocess.h>
+#include <dirent.h>
+#include <eigen3/Eigen/Dense>
+#include <istream>
 
-void createBlendshapes(std::vector<std::string> blendshapes, std::string neutral, Program p, Blendshapes *res) {
+void createBlendshapes(std::string dir, std::vector<std::string> blends,
+                                          std::string neutral, Program p, BlendshapeModel *res) {
 
        Assimp::Importer importer;
-       unsigned int ppFlags = aiProcess_Triangulate | aiProcess_CalcTangentSpace | aiProcess_GenNormals;
-       importer.ReadFile(neutral, ppFlags);
+       unsigned int ppFlags = aiProcess_Triangulate | aiProcess_CalcTangentSpace |
+                                                  aiProcess_GenNormals;
+       importer.ReadFile(dir + "/" + neutral, ppFlags);
        const aiScene *scene = importer.GetOrphanedScene();
 
        res->model = new Model(scene, p);
@@ -16,40 +21,136 @@ void createBlendshapes(std::vector<std::string> blendshapes, std::string neutral
 
        const aiMesh *neutralMesh = scene->mMeshes[0];
        res->neutral = std::vector<glm::vec3>(neutralMesh->mNumVertices);
-       res->deltas = std::vector<std::vector<glm::vec3>>(blendshapes.size());
+
+       res->blendshapes = std::vector<Blendshape>(blends.size());
+
        for (int i = 0; i < neutralMesh->mNumVertices; i++)
                res->neutral[i] = aiVector3DToVec3(neutralMesh->mVertices[i]);
 
-       for (int i = 0; i < blendshapes.size(); i++) {
-               auto fp = blendshapes[i];
+       for (int i = 0; i < blends.size(); i++) {
+               auto fp = dir + "/" + blends[i];
                const aiScene *blendshape = importer.ReadFile(fp, ppFlags);
                if (blendshape->mNumMeshes != 1) {
-                       std::cerr << "Too many or too little meshes for the blendshape " << fp << std::endl;
+                       std::cerr << "Too many or too little meshes for the blendshape "
+                                         << fp << std::endl;
                        abort();
                }
                aiMesh *mesh = blendshape->mMeshes[0];
                std::vector<glm::vec3> meshDeltas(mesh->mNumVertices);
                assert(mesh->mNumVertices == neutralMesh->mNumVertices);
                for (int j = 0; j < mesh->mNumVertices; j++) {
-                       glm::vec3 d = aiVector3DToVec3(mesh->mVertices[j]) - aiVector3DToVec3(neutralMesh->mVertices[j]);
+                       glm::vec3 d = aiVector3DToVec3(mesh->mVertices[j]) -
+                                                 aiVector3DToVec3(neutralMesh->mVertices[j]);
                        meshDeltas[j] = d;
                }
-               res->deltas[i] = meshDeltas;
+               res->blendshapes[i] = {blends[i], meshDeltas, 0};
+       }
 }
 
+void loadBlendshapes(std::string dir, Program p, BlendshapeModel *bsModel) {
+       std::vector<std::string> names;
+       DIR *blendDir = opendir(dir.c_str());
+       while (dirent *e = readdir(blendDir)) {
+               if (e->d_type & DT_DIR)
+                       continue;
+               const std::string name(e->d_name);
+               const std::string ext = ".obj";
+               if (name.compare(name.length() - ext.length(), ext.length(), ext) != 0)
+                       continue;
+               if (name == "neutral.obj")
+                       continue;
+               names.push_back(name);
        }
+       closedir(blendDir);
 
-void interpolateBlendshapes(Blendshapes *b, std::vector<float> weights) {
-       assert(weights.size() == b->deltas.size());
+       // alphabetically sort blendshapes
+       std::sort(names.begin(), names.end());
+
+       // load animation
+       std::ifstream animFile(dir + "/animation.txt");
+       if (animFile.good()) {
+               std::vector<float> weights(names.size());
+               int i = 0;
+               while (animFile >> weights[i]) {
+                       i++;
+                       if (i >= names.size()) {
+                               i = 0;
+                               bsModel->animation.push_back(weights);
+                       }
+               }
+               assert(i == 0); // should not end on an arbitrary number
+       }
+
+       createBlendshapes(dir, names, "neutral.obj", p, bsModel);
+}
+
+void interpolateBlendshapes(BlendshapeModel *b) {
        const Model::Mesh mesh = b->model->meshes[0];
 
        for (int i = 0; i < mesh.ai.mNumVertices; i++) {
                glm::vec3 pos = b->neutral[i];
-               for (int j = 0; j < b->deltas.size(); j++) {
-                       pos += b->deltas[j][i] * weights[j];
+               for (auto &blendshape : b->blendshapes) {
+                       pos += blendshape.deltas[i] * blendshape.weight;
                }
+
                mesh.ai.mVertices[i] = vec3ToaiVector3D(pos);
        }
 
        mesh.updatePosBuffer();
 }
+
+void solveWeights(BlendshapeModel *b, std::map<VertIdx, glm::vec3> manips) {
+
+       int numBlends = b->blendshapes.size();
+       int numManips = manips.size();
+
+       Eigen::VectorXf manipOffset(3 * numManips); // 3 * numManips x 1
+       {
+               int i = 0;
+               for (auto &m : manips) {
+                       auto origPos = b->neutral[m.first.second];
+                       auto manipOffsetV = m.second - origPos;
+                       for (int j = 0; j < 3; j++)
+                               manipOffset(i * 3 + j) = manipOffsetV[j];
+                       i++;
+               }
+       }
+
+       Eigen::MatrixXf Bbar(3 * numManips, numBlends); // 3 * numManips x numBlends
+       for (int i = 0; i < numBlends; i++) {
+               int j = 0;
+               for (auto &m : manips) {
+                       for (int k = 0; k < 3; k++)
+                               Bbar(j * 3 + k, i) =
+                                       b->blendshapes[i].deltas[m.first.second][k];
+                       j++;
+               }
+       }
+
+       Eigen::VectorXf prevWeights(numBlends); // numBlends x 1
+       for (int i = 0; i < numBlends; i++)
+               prevWeights(i) = b->blendshapes[i].weight;
+
+       float alpha = .1, mu = 1;
+
+       Eigen::MatrixXf LHS(3 * numManips + numBlends * 2, numBlends);
+       LHS << Bbar, alpha * Eigen::MatrixXf::Identity(numBlends, numBlends),
+               mu * Eigen::MatrixXf::Identity(numBlends, numBlends);
+
+       Eigen::VectorXf RHS(3 * numManips + numBlends * 2);
+       RHS << manipOffset, alpha * prevWeights, Eigen::VectorXf::Zero(numBlends);
+
+       Eigen::VectorXf w =
+               LHS.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeFullV).solve(RHS);
+
+       for (int i = 0; i < numBlends; i++)
+               b->blendshapes[i].weight = w[i];
+}
+
+void stepBlendshapeAnim(BlendshapeModel *b) {
+       for (int i = 0; i < b->blendshapes.size(); i++) {
+               b->blendshapes[i].weight = b->animation[b->curFrame][i];
+       }
+       b->curFrame++;
+       b->curFrame %= b->animation.size();
+}