X-Git-Url: https://git.lukelau.me/?p=opengl.git;a=blobdiff_plain;f=blendshapes.cpp;fp=blendshapes.cpp;h=d4d03236d6577c12840914bcd1c990923f0db17e;hp=8611f6289fb7cd83b024071bc9f88a0d9e45feee;hb=9886c33eb8cd31f28234585718410cd51aef2a4c;hpb=d2a4cfcd292f5bc422a025d43855e5f4f21fb161 diff --git a/blendshapes.cpp b/blendshapes.cpp index 8611f62..d4d0323 100644 --- a/blendshapes.cpp +++ b/blendshapes.cpp @@ -1,13 +1,18 @@ -#include -#include #include "blendshapes.hpp" #include "util.hpp" +#include +#include +#include +#include +#include -void createBlendshapes(std::vector blendshapes, std::string neutral, Program p, Blendshapes *res) { +void createBlendshapes(std::string dir, std::vector 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 blendshapes, std::string neutral const aiMesh *neutralMesh = scene->mMeshes[0]; res->neutral = std::vector(neutralMesh->mNumVertices); - res->deltas = std::vector>(blendshapes.size()); + + res->blendshapes = std::vector(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 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 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 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 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 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(); +}