This commit is contained in:
Howard Stearns 2015-07-29 09:12:44 -07:00
commit 68aec1fd36
7 changed files with 98 additions and 107 deletions

View file

@ -968,12 +968,8 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL); Avatar::setSkeletonModelURL(skeletonModelURL);
render::ScenePointer scene = Application::getInstance()->getMain3DScene(); render::ScenePointer scene = Application::getInstance()->getMain3DScene();
_billboardValid = false; _billboardValid = false;
_skeletonModel.setVisibleInScene(true, scene);
if (_useFullAvatar) { _headBoneSet.clear();
_skeletonModel.setVisibleInScene(_prevShouldDrawHead, scene);
} else {
_skeletonModel.setVisibleInScene(true, scene);
}
} }
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
@ -1183,17 +1179,45 @@ void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene
} }
} }
void MyAvatar::initHeadBones() {
int neckJointIndex = -1;
if (_skeletonModel.getGeometry()) {
neckJointIndex = _skeletonModel.getGeometry()->getFBXGeometry().neckJointIndex;
}
if (neckJointIndex == -1) {
return;
}
_headBoneSet.clear();
std::queue<int> q;
q.push(neckJointIndex);
_headBoneSet.insert(neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < _skeletonModel.getJointStateCount(); i++) {
if (jointIndex == _skeletonModel.getParentJointIndex(i)) {
_headBoneSet.insert(i);
q.push(i);
}
}
q.pop();
}
}
void MyAvatar::preRender(RenderArgs* renderArgs) { void MyAvatar::preRender(RenderArgs* renderArgs) {
render::ScenePointer scene = Application::getInstance()->getMain3DScene(); render::ScenePointer scene = Application::getInstance()->getMain3DScene();
const bool shouldDrawHead = shouldRenderHead(renderArgs); const bool shouldDrawHead = shouldRenderHead(renderArgs);
_skeletonModel.initWhenReady(scene); if (_skeletonModel.initWhenReady(scene)) {
initHeadBones();
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
}
if (shouldDrawHead != _prevShouldDrawHead) { if (shouldDrawHead != _prevShouldDrawHead) {
if (_useFullAvatar) { if (_useFullAvatar) {
_skeletonModel.setVisibleInScene(true, scene); _skeletonModel.setCauterizeBones(!shouldDrawHead);
_rig->setFirstPerson(!shouldDrawHead);
} else { } else {
getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene); getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene);
} }

View file

@ -273,7 +273,8 @@ private:
void updatePosition(float deltaTime); void updatePosition(float deltaTime);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void maybeUpdateBillboard(); void maybeUpdateBillboard();
void initHeadBones();
// Avatar Preferences // Avatar Preferences
bool _useFullAvatar = false; bool _useFullAvatar = false;
QUrl _fullAvatarURLFromPreferences; QUrl _fullAvatarURLFromPreferences;
@ -286,6 +287,7 @@ private:
RigPointer _rig; RigPointer _rig;
bool _prevShouldDrawHead; bool _prevShouldDrawHead;
std::unordered_set<int> _headBoneSet;
}; };
#endif // hifi_MyAvatar_h #endif // hifi_MyAvatar_h

View file

@ -51,7 +51,7 @@ SkeletonModel::~SkeletonModel() {
void SkeletonModel::initJointStates(QVector<JointState> states) { void SkeletonModel::initJointStates(QVector<JointState> states) {
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_boundingRadius = _rig->initJointStates(states, parentTransform, geometry.neckJointIndex); _boundingRadius = _rig->initJointStates(states, parentTransform);
// Determine the default eye position for avatar scale = 1.0 // Determine the default eye position for avatar scale = 1.0
int headJointIndex = _geometry->getFBXGeometry().headJointIndex; int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
@ -175,14 +175,6 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} }
} }
// if (_isFirstPerson) {
// cauterizeHead();
// updateClusterMatrices();
// }
if (_rig->getJointsAreDirty()) {
updateClusterMatrices();
}
} }
void SkeletonModel::renderIKConstraints(gpu::Batch& batch) { void SkeletonModel::renderIKConstraints(gpu::Batch& batch) {

View file

@ -124,9 +124,8 @@ void Rig::deleteAnimations() {
_animationHandles.clear(); _animationHandles.clear();
} }
float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform, int neckJointIndex) { float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform) {
_jointStates = states; _jointStates = states;
_neckJointIndex = neckJointIndex;
initJointTransforms(parentTransform); initJointTransforms(parentTransform);
int numStates = _jointStates.size(); int numStates = _jointStates.size();
@ -142,8 +141,6 @@ float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform
_jointStates[i].slaveVisibleTransform(); _jointStates[i].slaveVisibleTransform();
} }
initHeadBones();
return radius; return radius;
} }
@ -195,8 +192,7 @@ JointState Rig::getJointState(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return JointState(); return JointState();
} }
// return _jointStates[jointIndex]; return _jointStates[jointIndex];
return maybeCauterizeHead(jointIndex);
} }
bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { bool Rig::getJointStateRotation(int index, glm::quat& rotation) const {
@ -270,8 +266,7 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
return false; return false;
} }
// position is in world-frame // position is in world-frame
// position = translation + rotation * _jointStates[jointIndex].getPosition(); position = translation + rotation * _jointStates[jointIndex].getPosition();
position = translation + rotation * maybeCauterizeHead(jointIndex).getPosition();
return true; return true;
} }
@ -280,7 +275,7 @@ bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const {
return false; return false;
} }
// position is in model-frame // position is in model-frame
position = extractTranslation(maybeCauterizeHead(jointIndex).getTransform()); position = extractTranslation(_jointStates[jointIndex].getTransform());
return true; return true;
} }
@ -288,7 +283,7 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false; return false;
} }
result = rotation * maybeCauterizeHead(jointIndex).getRotation(); result = rotation * _jointStates[jointIndex].getRotation();
return true; return true;
} }
@ -296,7 +291,7 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false; return false;
} }
rotation = maybeCauterizeHead(jointIndex).getRotation(); rotation = _jointStates[jointIndex].getRotation();
return true; return true;
} }
@ -304,7 +299,7 @@ bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm:
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false; return false;
} }
result = rotation * maybeCauterizeHead(jointIndex).getRotation(); result = rotation * _jointStates[jointIndex].getRotation();
return true; return true;
} }
@ -315,7 +310,7 @@ bool Rig::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& positio
return false; return false;
} }
// position is in world-frame // position is in world-frame
position = translation + rotation * maybeCauterizeHead(jointIndex).getVisiblePosition(); position = translation + rotation * _jointStates[jointIndex].getVisiblePosition();
return true; return true;
} }
@ -323,7 +318,7 @@ bool Rig::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& result,
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false; return false;
} }
result = rotation * maybeCauterizeHead(jointIndex).getVisibleRotation(); result = rotation * _jointStates[jointIndex].getVisibleRotation();
return true; return true;
} }
@ -331,14 +326,14 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return glm::mat4(); return glm::mat4();
} }
return maybeCauterizeHead(jointIndex).getTransform(); return _jointStates[jointIndex].getTransform();
} }
glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const { glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return glm::mat4(); return glm::mat4();
} }
return maybeCauterizeHead(jointIndex).getVisibleTransform(); return _jointStates[jointIndex].getVisibleTransform();
} }
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) { void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
@ -624,7 +619,7 @@ glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) {
if (jointIndex == -1 || _jointStates.isEmpty()) { if (jointIndex == -1 || _jointStates.isEmpty()) {
return glm::vec3(); return glm::vec3();
} }
return maybeCauterizeHead(jointIndex).getDefaultTranslationInConstrainedFrame(); return _jointStates[jointIndex].getDefaultTranslationInConstrainedFrame();
} }
glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, bool constrain) { glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, bool constrain) {
@ -669,53 +664,5 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
if (jointIndex == -1 || _jointStates.isEmpty()) { if (jointIndex == -1 || _jointStates.isEmpty()) {
return glm::quat(); return glm::quat();
} }
return maybeCauterizeHead(jointIndex).getDefaultRotationInParentFrame(); return _jointStates[jointIndex].getDefaultRotationInParentFrame();
}
void Rig::initHeadBones() {
if (_neckJointIndex == -1) {
return;
}
_headBones.clear();
std::queue<int> q;
q.push(_neckJointIndex);
_headBones.push_back(_neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& fbxJoint = _jointStates[i].getFBXJoint();
if (jointIndex == fbxJoint.parentIndex) {
_headBones.push_back(i);
q.push(i);
}
}
q.pop();
}
}
JointState Rig::maybeCauterizeHead(int jointIndex) const {
// if (_headBones.contains(jointIndex)) {
// XXX fix this... make _headBones a hash? add a flag to JointState?
if (_neckJointIndex != -1 &&
_isFirstPerson &&
std::find(_headBones.begin(), _headBones.end(), jointIndex) != _headBones.end()) {
glm::vec4 trans = _jointStates[jointIndex].getTransform()[3];
glm::vec4 zero(0, 0, 0, 0);
glm::mat4 newXform(zero, zero, zero, trans);
JointState jointStateCopy = _jointStates[jointIndex];
jointStateCopy.setTransform(newXform);
jointStateCopy.setVisibleTransform(newXform);
return jointStateCopy;
} else {
return _jointStates[jointIndex];
}
}
void Rig::setFirstPerson(bool isFirstPerson) {
if (_isFirstPerson != isFirstPerson) {
_isFirstPerson = isFirstPerson;
_jointsAreDirty = true;
}
} }

View file

@ -75,7 +75,7 @@ public:
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform, int neckJointIndex); float initJointStates(QVector<JointState> states, glm::mat4 parentTransform);
bool jointStatesEmpty() { return _jointStates.isEmpty(); }; bool jointStatesEmpty() { return _jointStates.isEmpty(); };
int getJointStateCount() const { return _jointStates.size(); } int getJointStateCount() const { return _jointStates.size(); }
int indexOfJoint(const QString& jointName) ; int indexOfJoint(const QString& jointName) ;
@ -131,10 +131,6 @@ public:
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0; virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
virtual void updateFaceJointState(int index, glm::mat4 parentTransform) = 0; virtual void updateFaceJointState(int index, glm::mat4 parentTransform) = 0;
virtual void setFirstPerson(bool isFirstPerson);
virtual bool getIsFirstPerson() const { return _isFirstPerson; }
bool getJointsAreDirty() { return _jointsAreDirty; }
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; } void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
protected: protected:
@ -143,13 +139,6 @@ public:
QList<AnimationHandlePointer> _animationHandles; QList<AnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations; QList<AnimationHandlePointer> _runningAnimations;
JointState maybeCauterizeHead(int jointIndex) const;
void initHeadBones();
bool _isFirstPerson = false;
std::vector<int> _headBones;
bool _jointsAreDirty = false;
int _neckJointIndex = -1;
bool _enableRig; bool _enableRig;
bool _isWalking; bool _isWalking;
bool _isTurning; bool _isTurning;

View file

@ -67,6 +67,7 @@ Model::Model(RigPointer rig, QObject* parent) :
_snapModelToRegistrationPoint(false), _snapModelToRegistrationPoint(false),
_snappedToRegistrationPoint(false), _snappedToRegistrationPoint(false),
_showTrueJointTransforms(true), _showTrueJointTransforms(true),
_cauterizeBones(false),
_lodDistance(0.0f), _lodDistance(0.0f),
_pupilDilation(0.0f), _pupilDilation(0.0f),
_url("http://invalid.com"), _url("http://invalid.com"),
@ -452,6 +453,7 @@ bool Model::updateGeometry() {
foreach (const FBXMesh& mesh, fbxGeometry.meshes) { foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
MeshState state; MeshState state;
state.clusterMatrices.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size());
state.cauterizedClusterMatrices.resize(mesh.clusters.size());
_meshStates.append(state); _meshStates.append(state);
auto buffer = std::make_shared<gpu::Buffer>(); auto buffer = std::make_shared<gpu::Buffer>();
@ -472,7 +474,7 @@ bool Model::updateGeometry() {
void Model::initJointStates(QVector<JointState> states) { void Model::initJointStates(QVector<JointState> states) {
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_boundingRadius = _rig->initJointStates(states, parentTransform, geometry.neckJointIndex); _boundingRadius = _rig->initJointStates(states, parentTransform);
} }
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
@ -1337,6 +1339,12 @@ void Model::simulateInternal(float deltaTime) {
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
updateRig(deltaTime, parentTransform); updateRig(deltaTime, parentTransform);
glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
glm::mat4 modelToWorld = glm::mat4_cast(_rotation); glm::mat4 modelToWorld = glm::mat4_cast(_rotation);
for (int i = 0; i < _meshStates.size(); i++) { for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i]; MeshState& state = _meshStates[i];
@ -1344,14 +1352,30 @@ void Model::simulateInternal(float deltaTime) {
if (_showTrueJointTransforms) { if (_showTrueJointTransforms) {
for (int j = 0; j < mesh.clusters.size(); j++) { for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j); const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = auto jointMatrix =_rig->getJointTransform(cluster.jointIndex);
modelToWorld * _rig->getJointTransform(cluster.jointIndex) * cluster.inverseBindMatrix; state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
state.cauterizedClusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
}
} }
} else { } else {
for (int j = 0; j < mesh.clusters.size(); j++) { for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j); const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = auto jointMatrix = _rig->getJointVisibleTransform(cluster.jointIndex);
modelToWorld * _rig->getJointVisibleTransform(cluster.jointIndex) * cluster.inverseBindMatrix; state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
state.cauterizedClusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
}
} }
} }
} }
@ -1611,8 +1635,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
} }
if (isSkinned) { if (isSkinned) {
batch._glUniformMatrix4fv(locations->clusterMatrices, state.clusterMatrices.size(), false, const float* bones = (const float*)state.clusterMatrices.constData();
(const float*)state.clusterMatrices.constData()); if (_cauterizeBones) {
bones = (const float*)state.cauterizedClusterMatrices.constData();
}
batch._glUniformMatrix4fv(locations->clusterMatrices, state.clusterMatrices.size(), false, bones);
_transforms[0] = Transform(); _transforms[0] = Transform();
_transforms[0].preTranslate(_translation); _transforms[0].preTranslate(_translation);
} else { } else {

View file

@ -18,6 +18,7 @@
#include <QMutex> #include <QMutex>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <functional> #include <functional>
#include <AABox.h> #include <AABox.h>
@ -172,6 +173,9 @@ public:
/// \return true if joint exists /// \return true if joint exists
bool getJointRotation(int jointIndex, glm::quat& rotation) const; bool getJointRotation(int jointIndex, glm::quat& rotation) const;
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority); void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
/// Returns the extents of the model in its bind pose. /// Returns the extents of the model in its bind pose.
@ -187,6 +191,12 @@ public:
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to
void setCauterizeBones(bool flag) { _cauterizeBones = flag; }
bool getCauterizeBones() const { return _cauterizeBones; }
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
protected: protected:
void setPupilDilation(float dilation) { _pupilDilation = dilation; } void setPupilDilation(float dilation) { _pupilDilation = dilation; }
@ -218,9 +228,6 @@ protected:
/// Clear the joint states /// Clear the joint states
void clearJointState(int index); void clearJointState(int index);
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
/// Returns the index of the last free ancestor of the indexed joint, or -1 if not found. /// Returns the index of the last free ancestor of the indexed joint, or -1 if not found.
int getLastFreeJointIndex(int jointIndex) const; int getLastFreeJointIndex(int jointIndex) const;
@ -255,9 +262,12 @@ protected:
class MeshState { class MeshState {
public: public:
QVector<glm::mat4> clusterMatrices; QVector<glm::mat4> clusterMatrices;
QVector<glm::mat4> cauterizedClusterMatrices;
}; };
QVector<MeshState> _meshStates; QVector<MeshState> _meshStates;
std::unordered_set<int> _cauterizeBoneSet;
bool _cauterizeBones;
// returns 'true' if needs fullUpdate after geometry change // returns 'true' if needs fullUpdate after geometry change
bool updateGeometry(); bool updateGeometry();