1/ Data from the simulation are loaded, for this there are 2 objects - cuticleLoader and MuscleLoader … for cuticle the cuticleLoader, position file and membrane file has to be set. Position file is taken from OW_DATA_VERSION data dir and membrane file is taken from OW_BINDPOSE_DATA_VERSION data dir.
g_core->openwormWrapper.cuticleLoader.ReadWormIteration(g_core->openwormWrapper.mesh, timestep); Import::CalculatePerVertexNormals(g_core->openwormWrapper.mesh);
2/ All the cuticles in all the timesteps are loaded in a loop and exported into collada files (in order to be loaded by assimp, so SDF can be calculated).
g_core->openwormWrapper.exporter.Export( gcore->openwormWrapper.mesh, config.projectDir + "Models\OpenWorm\Export\export"+OW_DATAVERSION+"\collada\wormCollada" + g_core->exportWrapper.PadNumber( g_core->openwormWrapper.timeStep, OW_PADDING) + ".dae");
3/ Next, wrapper is called from the script. It is loaded from the script in a loop, because there is a memory leak in SDF library. In can be run in 8 threads using 8 powershell scripts. By running this wrapper, skeletons and transformations between skeletons and a bindpose are calculated. The output are quaternions, matrices and axis angles. They are rotations only encoding in the matrices.
g_core->openwormWrapper.skeletonExtractionSDF_calculateSDF(&(g_engine->oModel), &(g_core->lbseWrapper.oLBSExtractor)); g_core->openwormWrapper.skeletonExtractionSDF_extractSkeleton(g_core->defaultSkeletonRoot, &(g_core->lbseWrapper.oLBSExtractor), &(g_core->sdfWrapper.oSDFExtractor));
4) Postprocessing of the rotations. There are three steps in the postprocessing process.
a - interpolation is applied, SLERP is used for this (this is performed to get rid of the shivering of the worm caused by floating inaccuracy) SM::SmoothQuaternions(source, 130); // 100-150 are good smoothing neighbourhoods
b - SDF is calculated, muscle stretching and gravitation force is applied and composed into the matrices (muscle are stretching the cuticle, therefore the volume has to be restored)
c - rotations are perform in the skeleton chain, the translation of the skeleton root is ignored (in each node, there is transformation for local node only and by multiplying of transformation matrices in skeleton chain we get global transformation matrix for each node, that can be directly used for skinning in shaders) g_core->openwormWrapper.performSDFBasedTransformationsInSkeletonTree(&g_core->openwormWrapper.bindPoseSkeletonNode, &source[i], &matrices, sdf_loaded, 9.81, glm::vec3(0, 0, 1)); // b and c are both called in this method
d - the skeleton is anchored in some skeleton node (for each timestep, the translation vector for anchored skeleton node is calculated - translation between bindpose position and the skinned one. Then this translation vector is added to each transformation matrix, do the skinning is applied and final skinned vertex is translated at the end by the vector) g_core->openwormWrapper.anchorSkeletonDuringSkinning(&g_core->openwormWrapper.bindPoseSkeletonNode, &matrices, 15); // last parameter is the index of the node
All projects are in this repository's Visual Studio Solution file.
1) GDSW_lib [Geodesic Distance Skinning Weights]
Main functions:
void FloydWarshall(MeshGraph * pMesh, Array2D<float>& M)
2) LBSE_lib [Laplacian-Based Skeleton Extraction]
Main functions:
void computeSkeleton(t3DModel *pModel,
int sourcePointID,
SN::SkeletonNode * skeleton,
int * ite,
bool &recreateOperator,
float modelMaxDim)
3) PCT_lib [Point Cloud Triangulation]
Main functions:
void computeLocalTriangulationFromPoints(int index, int numOfPoints,
float * points, int &numOfIndices,
int ** indices,
std::vector<std::set<int>> globalNeighbourhoods,
float * nor, bool visualization = false)
void computeGlobalTriangulationFromPoints(int numOfPoints,
float * points,
int &numOfIndices,
int ** indices,
float ** normals,
bool visualization = false)
4) SDF_lib [Shape Diameter Function]
D_SCL_SECURE_NO_WARNINGS;_NLOG;_NMMGR;NOMINMAX;
When a process wants to extract a skeleton from an input mesh, the process has to call the "computeSkeleton"
method from LBSE_lib
. Next, for extraction of skinning weights, the GDSW_lib
has to be used.
int timeStep = OW_START_WORM_ITERATION, timeIncrease = 1;
//load skeleton
string file = config.projectDir + "Models\\OpenWorm\\Export\\export_" + OW_DATA_VERSION + "\\animation_" + std::to_string(OW_SKINNING_NUM_BONES) + "\\skeleton_" + std::to_string(OW_SKINNING_NUM_BONES) + "S_"+ g_core->exportWrapper.PadNumber(OW_BINDPOSE_TIMESTEP, 4) + ".skl";
ifstream ifs(file);
assert(ifs.good());
boost::archive::xml_iarchive ia(ifs);
ia >> BOOST_SERIALIZATION_NVP(g_core->openwormWrapper.bindPoseSkeletonNode);
assignFathersForSkeletonTree(&g_core->openwormWrapper.bindPoseSkeletonNode);
//load sdf
string sdf_file_31_0 = config.projectDir + "Models\\OpenWorm\\Export\\export_" + OW_DATA_VERSION + "\\animation_"+ std::to_string(OW_SKINNING_NUM_BONES)+"\\SDF__31S_00000.sdf";
vector<float> sdf_loaded;
g_core->openwormWrapper.loadSDFFromFile(sdf_file_31_0, sdf_loaded);
//load quaternions
vector<vector<glm::quat> > source;
source.reserve(OW_MAX_WORM_ITERATIONS);
while (timeStep < OW_MAX_WORM_ITERATIONS) {
//load skinning data
string quaternionFile = config.projectDir + "Models\\OpenWorm\\Export\\export_" + OW_DATA_VERSION + "\\animation_"+ std::to_string(OW_SKINNING_NUM_BONES)+"_bindpose"+ std::to_string(OW_BINDPOSE_TIMESTEP) +"\\quaternion_" + std::to_string(OW_SKINNING_NUM_BONES) + "S_" + g_core->exportWrapper.PadNumber(timeStep, 4) + ".qua";
ifstream isq(quaternionFile);
assert(isq.good());
source.push_back(vector<glm::quat>());
g_core->openwormWrapper.ImportQuaternion(source.back(), isq);
timeStep += timeIncrease;
}
//postprocess quaternions, smoth them with SLERP radius
SM::SmoothQuaternions(source, 130);
//output quaternions
timeStep = OW_START_WORM_ITERATION;
for (int i = 0; i < source.size(); i++) {
//save quaternions to file
string quaternionFile = config.projectDir + "Models\\OpenWorm\\Export\\export_" + OW_DATA_VERSION + "\\animation_"+ std::to_string(OW_SKINNING_NUM_BONES)+"_bindpose"+ std::to_string(OW_BINDPOSE_TIMESTEP) +"\\postprocessed\\quaternion_post_" + std::to_string(OW_SKINNING_NUM_BONES) + "S_" + g_core->exportWrapper.PadNumber(timeStep, 4) + ".qua";
ofstream outputQ(quaternionFile);
for (int q = 0; q < source[i].size(); q++) {
glm::quat quat = source[i][q];
outputQ << quat.w << " " << quat.x << " " << quat.y << " " << quat.z << std::endl;
}
//save matrix to file
string matrixFile = config.projectDir + "Models\\OpenWorm\\Export\\export_" + OW_DATA_VERSION + "\\animation_"+ std::to_string(OW_SKINNING_NUM_BONES)+"_bindpose"+ std::to_string(OW_BINDPOSE_TIMESTEP) +"\\anchored2\\matrix_anchored_" + std::to_string(OW_SKINNING_NUM_BONES) + "S_" + g_core->exportWrapper.PadNumber(timeStep, 4) + ".mat";
ofstream outputM(matrixFile);
vector<glm::mat4> matrices;
g_core->openwormWrapper.performSDFBasedTransformationsInSkeletonTree(&g_core->openwormWrapper.bindPoseSkeletonNode, &source[i], &matrices, sdf_loaded, 9.81, glm::vec3(0, 0, 1));
// anchor middle node of the skeleton, translate all the other nodes
g_core->openwormWrapper.anchorSkeletonDuringSkinning(&g_core->openwormWrapper.bindPoseSkeletonNode, &matrices, 22);
for (int m = 0; m < matrices.size(); m++) {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
outputM << matrices[m][j][k] << " ";
}
outputM << std::endl;
}
outputM << std::endl;
}
timeStep += timeIncrease;
}