-#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);
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();
+}