#include <memory>
// TnzExt includes
#include "ext/plasticskeleton.h"
#include "ext/plasticskeletondeformation.h"
// STD includes
#include <limits>
#include <map>
#include <algorithm>
// Boost includes
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
// Qt includes
#include <QMutex>
#include <QMutexLocker>
#include "ext/plasticdeformerstorage.h"
//***********************************************************************************************
// Storage multi-index map definition
//***********************************************************************************************
namespace
{
typedef PlasticDeformerDataGroup DataGroup;
//----------------------------------------------------------------------------------
typedef std::pair<const SkD *, int> DeformedSkeleton;
//----------------------------------------------------------------------------------
struct Key {
const TMeshImage *m_mi;
DeformedSkeleton m_ds;
std::shared_ptr<DataGroup> m_dataGroup;
public:
Key(const TMeshImage *mi, const SkD *sd, int skelId)
: m_mi(mi), m_ds(sd, skelId), m_dataGroup() {}
bool operator<(const Key &other) const
{
return (m_mi < other.m_mi) || ((!(other.m_mi < m_mi)) && (m_ds < other.m_ds));
}
};
//----------------------------------------------------------------------------------
using namespace boost::multi_index;
typedef boost::multi_index_container<Key, indexed_by<
ordered_unique<identity<Key>>,
ordered_non_unique<
tag<TMeshImage>, member<Key, const TMeshImage *, &Key::m_mi>>,
ordered_non_unique<
tag<DeformedSkeleton>, member<Key, DeformedSkeleton, &Key::m_ds>>
>> DeformersSet;
typedef DeformersSet::nth_index<0>::type DeformersByKey;
typedef DeformersSet::index<TMeshImage>::type DeformersByMeshImage;
typedef DeformersSet::index<DeformedSkeleton>::type DeformersByDeformedSkeleton;
} // namespace
//***********************************************************************************************
// Initialization stage functions
//***********************************************************************************************
namespace
{
void initializeSO(PlasticDeformerData &data, const TTextureMeshP &mesh)
{
data.m_so = new double[mesh->facesCount()];
}
//----------------------------------------------------------------------------------
void initializeDeformerData(PlasticDeformerData &data, const TTextureMeshP &mesh)
{
initializeSO(data, mesh); // Allocates SO data
// Also, allocate suitable input-output arrays for the deformation
data.m_output = new double[2 * mesh->verticesCount()];
}
//----------------------------------------------------------------------------------
void initializeDeformersData(DataGroup *group, const TMeshImage *meshImage)
{
group->m_datas = new PlasticDeformerData[meshImage->meshes().size()];
// Push a PlasticDeformer for each mesh in the image
const std::vector<TTextureMeshP> &meshes = meshImage->meshes();
int fTotal = 0; // Also count total # of faces
int m, mCount = meshes.size();
for (m = 0; m != mCount; ++m) {
fTotal += meshes[m]->facesCount();
initializeDeformerData(group->m_datas[m], meshes[m]);
}
// Initialize the vector of sorted faces
std::vector<std::pair<int, int>> &sortedFaces = group->m_sortedFaces;
sortedFaces.reserve(fTotal);
for (m = 0; m != mCount; ++m) {
const TTextureMesh &mesh = *meshes[m];
int f, fCount = mesh.facesCount();
for (f = 0; f != fCount; ++f)
sortedFaces.push_back(std::make_pair(f, m));
}
}
} // namespace
//***********************************************************************************************
// Handle processing functions
//***********************************************************************************************
namespace
{
void transformHandles(std::vector<PlasticHandle> &handles, const TAffine &aff)
{
// Transforms handles through deformAff AND applies mi's dpi scale inverse
std::vector<PlasticHandle>::size_type h, hCount = handles.size();
for (h = 0; h != hCount; ++h)
handles[h].m_pos = aff * handles[h].m_pos;
}
//----------------------------------------------------------------------------------
void transformHandles(std::vector<TPointD> &handles, const TAffine &aff)
{
// Transforms handles through deformAff AND applies mi's dpi scale inverse
std::vector<PlasticHandle>::size_type h, hCount = handles.size();
for (h = 0; h != hCount; ++h)
handles[h] = aff * handles[h];
}
//----------------------------------------------------------------------------------
void processHandles(DataGroup *group, double frame, const TMeshImage *meshImage,
const SkD *sd, int skelId, const TAffine &deformationAffine)
{
assert(sd);
const PlasticSkeletonP &skeleton = sd->skeleton(skelId);
if (!skeleton || skeleton->verticesCount() == 0) {
group->m_handles.clear();
group->m_dstHandles.clear();
group->m_compiled |= PlasticDeformerStorage::HANDLES;
group->m_upToDate |= PlasticDeformerStorage::HANDLES;
return;
}
int mCount = meshImage->meshes().size();
if (!(group->m_upToDate & PlasticDeformerStorage::HANDLES)) {
// Compile handles if necessary
if (!(group->m_compiled & PlasticDeformerStorage::HANDLES)) {
// Build and transform handles
group->m_handles = skeleton->verticesToHandles();
::transformHandles(group->m_handles, deformationAffine);
// Prepare a vector for handles' face hints
for (int m = 0; m != mCount; ++m)
group->m_datas[m].m_faceHints.resize(group->m_handles.size(), -1);
group->m_compiled |= PlasticDeformerStorage::HANDLES;
}
// Then, build destination handles
PlasticSkeleton deformedSkeleton; // NOTE: Could this be moved to the group as well?
sd->storeDeformedSkeleton(skelId, frame, deformedSkeleton);
// Copy deformed skeleton data into input deformation parameters
group->m_dstHandles = std::vector<TPointD>(deformedSkeleton.vertices().begin(), deformedSkeleton.vertices().end());
::transformHandles(group->m_dstHandles, deformationAffine);
group->m_upToDate |= PlasticDeformerStorage::HANDLES;
}
}
} // namespace
//***********************************************************************************************
// Stacking Order processing functions
//***********************************************************************************************
namespace
{
bool updateHandlesSO(DataGroup *group, const SkD *sd, int skelId, double frame)
{
assert(sd);
const PlasticSkeletonP &skeleton = sd->skeleton(skelId);
if (!skeleton || skeleton->verticesCount() == 0) {
group->m_soMin = group->m_soMax = 0.0;
return false;
}
// Copy SO values to data's handles
// Return whether values changed with respect to previous ones
bool changed = false;
assert(group->m_handles.size() == skeleton->verticesCount());
int h, hCount = group->m_handles.size();
{
tcg::list<PlasticSkeletonVertex>::iterator vt = skeleton->vertices().begin();
for (h = 0; h != hCount; ++h, ++vt) {
const SkVD *vd = sd->vertexDeformation(vt->name());
if (!vd)
continue;
double so = vd->m_params[SkVD::SO]->getValue(frame);
PlasticHandle &handle = group->m_handles[h];
if (handle.m_so != so) {
group->m_handles[h].m_so = so;
changed = true;
}
}
}
if (changed) {
// Rebuild SO minmax
group->m_soMax = -(group->m_soMin = (std::numeric_limits<double>::max)());
for (h = 0; h != hCount; ++h) {
const double &so = group->m_handles[h].m_so;
group->m_soMin = tmin(group->m_soMin, so);
group->m_soMax = tmax(group->m_soMax, so);
}
}
return changed;
}
//----------------------------------------------------------------------------------
void interpolateSO(DataGroup *group, const TMeshImage *meshImage)
{
int m, mCount = meshImage->meshes().size();
if (group->m_handles.size() == 0) {
// No handles case, fill in with 0s
for (m = 0; m != mCount; ++m) {
const TTextureMesh &mesh = *meshImage->meshes()[m];
PlasticDeformerData &data = group->m_datas[m];
std::fill(data.m_so, data.m_so + mesh.facesCount(), 0.0);
}
return;
}
// Apply handles' SO values to each mesh
for (m = 0; m != mCount; ++m) {
const TTextureMesh &mesh = *meshImage->meshes()[m];
PlasticDeformerData &data = group->m_datas[m];
// Interpolate so values
double *verticesSO = new double[mesh.verticesCount()];
::buildSO(verticesSO, mesh, group->m_handles, &data.m_faceHints.front());
// Make the mean of each face's vertex values and store that
int f, fCount = mesh.facesCount();
for (f = 0; f != fCount; ++f) {
int v0, v1, v2;
mesh.faceVertices(f, v0, v1, v2);
data.m_so[f] = (verticesSO[v0] + verticesSO[v1] + verticesSO[v2]) / 3.0;
}
delete[] verticesSO;
}
}
//----------------------------------------------------------------------------------
struct FaceLess {
const PlasticDeformerDataGroup *m_group;
public:
FaceLess(const PlasticDeformerDataGroup *group) : m_group(group) {}
bool operator()(const std::pair<int, int> &a, const std::pair<int, int> &b)
{
return (m_group->m_datas[a.second].m_so[a.first] < m_group->m_datas[b.second].m_so[b.first]);
}
};
// Must be invoked after updateSO
void updateSortedFaces(PlasticDeformerDataGroup *group)
{
FaceLess comp(group);
std::sort(group->m_sortedFaces.begin(), group->m_sortedFaces.end(), comp);
}
//----------------------------------------------------------------------------------
void processSO(DataGroup *group, double frame, const TMeshImage *meshImage,
const SkD *sd, int skelId, const TAffine &deformationAffine)
{
// SO re-interpolate values along the mesh if either:
// 1. Recompilation was requested (ie some vertex may have been added/removed)
// 2. OR the value of one of the handle has changed
bool interpolate = !(group->m_compiled & PlasticDeformerStorage::SO);
if (!(group->m_upToDate & PlasticDeformerStorage::SO)) // implied by (interpolate == true)
{
interpolate = updateHandlesSO(group, sd, skelId, frame) || interpolate; // Order is IMPORTANT
if (interpolate) {
interpolateSO(group, meshImage);
updateSortedFaces(group);
}
group->m_compiled |= PlasticDeformerStorage::SO;
group->m_upToDate |= PlasticDeformerStorage::SO;
}
}
} // namespace
//***********************************************************************************************
// Mesh Deform processing functions
//***********************************************************************************************
namespace
{
void processMesh(DataGroup *group, double frame, const TMeshImage *meshImage,
const SkD *sd, int skelId, const TAffine &deformationAffine)
{
if (!(group->m_upToDate & PlasticDeformerStorage::MESH)) {
int m, mCount = meshImage->meshes().size();
if (!(group->m_compiled & PlasticDeformerStorage::MESH)) {
for (m = 0; m != mCount; ++m) {
const TTextureMeshP &mesh = meshImage->meshes()[m];
PlasticDeformerData &data = group->m_datas[m];
data.m_deformer.initialize(mesh);
data.m_deformer.compile(group->m_handles, data.m_faceHints.empty() ? 0 : &data.m_faceHints.front());
data.m_deformer.releaseInitializedData();
}
group->m_compiled |= PlasticDeformerStorage::MESH;
}
const TPointD *dstHandlePos = group->m_dstHandles.empty() ? 0 : &group->m_dstHandles.front();
for (m = 0; m != mCount; ++m) {
PlasticDeformerData &data = group->m_datas[m];
data.m_deformer.deform(dstHandlePos, data.m_output);
}
group->m_upToDate |= PlasticDeformerStorage::MESH;
}
}
} // namespace
//***********************************************************************************************
// PlasticDeformerData implementation
//***********************************************************************************************
PlasticDeformerData::PlasticDeformerData()
: m_so(), m_output()
{
}
//----------------------------------------------------------------------------------
PlasticDeformerData::~PlasticDeformerData()
{
delete[] m_so;
delete[] m_output;
}
//***********************************************************************************************
// PlasticDeformerDataGroup implementation
//***********************************************************************************************
PlasticDeformerDataGroup::PlasticDeformerDataGroup()
: m_datas(), m_compiled(PlasticDeformerStorage::NONE), m_upToDate(PlasticDeformerStorage::NONE), m_outputFrame((std::numeric_limits<double>::max)()), m_soMin(), m_soMax()
{
}
//----------------------------------------------------------------------------------
PlasticDeformerDataGroup::~PlasticDeformerDataGroup()
{
delete[] m_datas;
}
//***********************************************************************************************
// PlasticDeformerStorage::Imp definition
//***********************************************************************************************
class PlasticDeformerStorage::Imp
{
public:
QMutex m_mutex; //!< Access mutex - needed for thread-safety
DeformersSet m_deformers; //!< Set of deformers, ordered by mesh image, deformation, and affine.
public:
Imp() : m_mutex(QMutex::Recursive) {}
};
//***********************************************************************************************
// PlasticDeformerStorage implementation
//***********************************************************************************************
PlasticDeformerStorage::PlasticDeformerStorage()
: m_imp(new Imp)
{
}
//----------------------------------------------------------------------------------
PlasticDeformerStorage::~PlasticDeformerStorage()
{
delete m_imp;
}
//----------------------------------------------------------------------------------
PlasticDeformerStorage *PlasticDeformerStorage::instance()
{
static PlasticDeformerStorage theInstance;
return &theInstance;
}
//----------------------------------------------------------------------------------
PlasticDeformerDataGroup *PlasticDeformerStorage::deformerData(
const TMeshImage *meshImage, const PlasticSkeletonDeformation *deformation, int skelId)
{
QMutexLocker locker(&m_imp->m_mutex);
// Search for the corresponding deformation in the storage
Key key(meshImage, deformation, skelId);
DeformersByKey::iterator dt = m_imp->m_deformers.find(key);
if (dt == m_imp->m_deformers.end()) {
// No deformer was found. Allocate it.
key.m_dataGroup = std::make_shared<PlasticDeformerDataGroup>();
initializeDeformersData(key.m_dataGroup.get(), meshImage);
dt = m_imp->m_deformers.insert(key).first;
}
return dt->m_dataGroup.get();
}
//----------------------------------------------------------------------------------
const PlasticDeformerDataGroup *PlasticDeformerStorage::process(
double frame, const TMeshImage *meshImage,
const PlasticSkeletonDeformation *deformation, int skelId,
const TAffine &skeletonAffine, DataType dataType)
{
QMutexLocker locker(&m_imp->m_mutex);
PlasticDeformerDataGroup *group = deformerData(meshImage, deformation, skelId);
// On-the-fly checks for data invalidation
if (group->m_skeletonAffine != skeletonAffine) {
group->m_upToDate = NONE;
group->m_compiled = NONE;
group->m_skeletonAffine = skeletonAffine;
}
if (group->m_outputFrame != frame) {
group->m_upToDate = NONE;
group->m_outputFrame = frame;
}
bool doMesh = (dataType & MESH);
bool doSO = (dataType & SO) || doMesh;
bool doHandles = (bool)dataType;
// Process data
if (doHandles)
processHandles(group, frame, meshImage, deformation, skelId, skeletonAffine);
if (doSO)
processSO(group, frame, meshImage, deformation, skelId, skeletonAffine);
if (doMesh)
processMesh(group, frame, meshImage, deformation, skelId, skeletonAffine);
return group;
}
//----------------------------------------------------------------------------------
const PlasticDeformerDataGroup *PlasticDeformerStorage::processOnce(
double frame,
const TMeshImage *meshImage,
const PlasticSkeletonDeformation *deformation, int skelId,
const TAffine &skeletonAffine,
DataType dataType)
{
PlasticDeformerDataGroup *group = new PlasticDeformerDataGroup;
initializeDeformersData(group, meshImage);
bool doMesh = (dataType & MESH);
bool doSO = (dataType & SO) || doMesh;
bool doHandles = (bool)dataType;
// Process data
if (doHandles)
processHandles(group, frame, meshImage, deformation, skelId, skeletonAffine);
if (doSO)
processSO(group, frame, meshImage, deformation, skelId, skeletonAffine);
if (doMesh)
processMesh(group, frame, meshImage, deformation, skelId, skeletonAffine);
return group;
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::invalidateMeshImage(
const TMeshImage *meshImage, int recompiledData)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformersByMeshImage &deformers = m_imp->m_deformers.get<TMeshImage>();
DeformersByMeshImage::iterator dBegin(deformers.lower_bound(meshImage));
if (dBegin == deformers.end())
return;
DeformersByMeshImage::iterator dt, dEnd(deformers.upper_bound(meshImage));
for (dt = dBegin; dt != dEnd; ++dt) {
dt->m_dataGroup->m_outputFrame = (std::numeric_limits<double>::max)(); // Schedule for redeformation
if (recompiledData)
dt->m_dataGroup->m_compiled &= ~recompiledData; // Schedule for recompilation, too
}
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::invalidateSkeleton(
const PlasticSkeletonDeformation *deformation, int skelId, int recompiledData)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformedSkeleton ds(deformation, skelId);
DeformersByDeformedSkeleton &deformers = m_imp->m_deformers.get<DeformedSkeleton>();
DeformersByDeformedSkeleton::iterator dBegin(deformers.lower_bound(ds));
if (dBegin == deformers.end())
return;
DeformersByDeformedSkeleton::iterator dt, dEnd(deformers.upper_bound(ds));
for (dt = dBegin; dt != dEnd; ++dt) {
dt->m_dataGroup->m_outputFrame = (std::numeric_limits<double>::max)(); // Schedule for redeformation
if (recompiledData)
dt->m_dataGroup->m_compiled &= ~recompiledData; // Schedule for recompilation, too
}
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::invalidateDeformation(
const PlasticSkeletonDeformation *deformation, int recompiledData)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformersByDeformedSkeleton &deformers = m_imp->m_deformers.get<DeformedSkeleton>();
DeformedSkeleton dsBegin(deformation, -(std::numeric_limits<int>::max)()),
dsEnd(deformation, (std::numeric_limits<int>::max)());
DeformersByDeformedSkeleton::iterator dBegin(deformers.lower_bound(dsBegin));
DeformersByDeformedSkeleton::iterator dEnd(deformers.upper_bound(dsEnd));
if (dBegin == dEnd)
return;
for (DeformersByDeformedSkeleton::iterator dt = dBegin; dt != dEnd; ++dt) {
dt->m_dataGroup->m_outputFrame = (std::numeric_limits<double>::max)(); // Schedule for redeformation
if (recompiledData)
dt->m_dataGroup->m_compiled &= ~recompiledData; // Schedule for recompilation, too
}
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::releaseMeshData(const TMeshImage *meshImage)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformersByMeshImage &deformers = m_imp->m_deformers.get<TMeshImage>();
DeformersByMeshImage::iterator dBegin(deformers.lower_bound(meshImage));
if (dBegin == deformers.end())
return;
deformers.erase(dBegin, deformers.upper_bound(meshImage));
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::releaseSkeletonData(const SkD *deformation, int skelId)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformedSkeleton ds(deformation, skelId);
DeformersByDeformedSkeleton &deformers = m_imp->m_deformers.get<DeformedSkeleton>();
DeformersByDeformedSkeleton::iterator dBegin(deformers.lower_bound(ds));
if (dBegin == deformers.end())
return;
deformers.erase(dBegin, deformers.upper_bound(ds));
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::releaseDeformationData(const SkD *deformation)
{
QMutexLocker locker(&m_imp->m_mutex);
DeformersByDeformedSkeleton &deformers = m_imp->m_deformers.get<DeformedSkeleton>();
DeformedSkeleton dsBegin(deformation, -(std::numeric_limits<int>::max)()),
dsEnd(deformation, (std::numeric_limits<int>::max)());
DeformersByDeformedSkeleton::iterator dBegin(deformers.lower_bound(dsBegin));
DeformersByDeformedSkeleton::iterator dEnd(deformers.upper_bound(dsEnd));
if (dBegin == dEnd)
return;
deformers.erase(dBegin, dEnd);
}
//----------------------------------------------------------------------------------
void PlasticDeformerStorage::clear()
{
QMutexLocker locker(&m_imp->m_mutex);
m_imp->m_deformers.clear();
}