mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
Merge pull request #593 from Akz-/joints
GRIM: Animate all three of the head joints
This commit is contained in:
+186
-139
@@ -27,25 +27,166 @@
|
||||
|
||||
namespace Grim {
|
||||
|
||||
Head::Joint::Joint() : _node(NULL), _pitch(0.f), _yaw(0.f), _roll(0.f) {
|
||||
}
|
||||
|
||||
void Head::Joint::init(ModelNode *node) {
|
||||
_node = node;
|
||||
}
|
||||
|
||||
void Head::Joint::orientTowards(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix,
|
||||
float maxPitch, float maxYaw, float maxRoll, float constrain) {
|
||||
float step = g_grim->getPerSecond(rate);
|
||||
float yawStep = step;
|
||||
float pitchStep = step / 3.0f;
|
||||
float rollStep = step / 3.0f;
|
||||
|
||||
// Make sure we have up-to-date world transform matrices computed for the joint nodes of this character.
|
||||
_node->_needsUpdate = true;
|
||||
ModelNode *p = _node;
|
||||
while (p->_parent) {
|
||||
p = p->_parent;
|
||||
p->_needsUpdate = true;
|
||||
}
|
||||
p->setMatrix(matrix);
|
||||
p->update();
|
||||
|
||||
Math::Vector3d modelFront; // the modeling convention for the forward direction.
|
||||
Math::Vector3d modelUp; // the modeling convention for the upward direction.
|
||||
Math::Vector3d frontDir; // Character front facing direction vector in world space (global scene coordinate space)
|
||||
|
||||
// the character head coordinate frame is: +Y forward, +Z up, +X right.
|
||||
frontDir = Math::Vector3d(_node->_matrix(0,1), _node->_matrix(1,1), _node->_matrix(2,1)); // Look straight ahead. (+Y)
|
||||
modelFront = Math::Vector3d(0,1,0);
|
||||
modelUp = Math::Vector3d(0,0,1);
|
||||
|
||||
// v is the world space direction vector this character should be looking towards.
|
||||
Math::Vector3d targetDir = point - _node->_pivotMatrix.getPosition();
|
||||
if (!entering)
|
||||
targetDir = frontDir;
|
||||
if (targetDir.isZero())
|
||||
return;
|
||||
|
||||
targetDir.normalize();
|
||||
|
||||
// The vector v is in world space, so generate the world space lookat matrix for the desired head facing
|
||||
// orientation.
|
||||
Math::Matrix4 lookAtTM;
|
||||
lookAtTM.setToIdentity();
|
||||
const Math::Vector3d worldUp(0,0,1); // The Residual scene convention: +Z is world space up.
|
||||
if (Math::Vector3d::dotProduct(targetDir, worldUp) >= 0.98f) // Avoid singularity if trying to look straight up.
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, -frontDir); // Instead of orienting head towards scene up, orient head towards character "back",
|
||||
// i.e. when you look straight up, your head up vector tilts/arches to point straight backwards.
|
||||
else if (Math::Vector3d::dotProduct(targetDir, worldUp) <= -0.98f) // Avoid singularity if trying to look straight down.
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, frontDir); // Instead of orienting head towards scene down, orient head towards character "front",
|
||||
// i.e. when you look straight down, your head up vector tilts/arches to point straight forwards.
|
||||
else
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, worldUp);
|
||||
// The above specifies the world space orientation of this bone, but we need to output
|
||||
// the orientation in parent space (as yaw/pitch/roll).
|
||||
|
||||
// Get the coordinate frame in which we need to produce the character head yaw/pitch/roll values.
|
||||
Math::Matrix4 parentWorldTM;
|
||||
if (_node->_parent)
|
||||
parentWorldTM = _node->_parent->_matrix;
|
||||
|
||||
// While we could compute the desired lookat direction directly in the above coordinate frame,
|
||||
// it is preferrable to compute the lookat direction with respect to the head orientation in
|
||||
// the keyframe animation. This is because the LUA scripts specify the maximum head yaw, pitch and
|
||||
// roll values with respect to those keyframe animations. If the lookat was simply computed
|
||||
// directly in the space of the parent, we couldn't apply the head maxYaw/Pitch/Roll constraints
|
||||
// properly. So, compute the coordinate frame of this bone in the keyframe animation.
|
||||
Math::Matrix4 animFrame = _node->_localMatrix;
|
||||
parentWorldTM = parentWorldTM * animFrame;
|
||||
parentWorldTM.invertAffineOrthonormal();
|
||||
|
||||
// Convert lookAtTM orientation from world space to parent-with-keyframe-animation space.
|
||||
lookAtTM = parentWorldTM * lookAtTM;
|
||||
|
||||
// Decompose to yaw-pitch-roll (+Z, +X, +Y).
|
||||
// In this space, Yaw is +Z. Pitch is +X. Roll is +Y.
|
||||
Math::Angle y, pt, r;
|
||||
lookAtTM.getPitchYawRoll(&pt, &y, &r);
|
||||
|
||||
y = y * constrain;
|
||||
pt = pt * constrain;
|
||||
r = r * constrain;
|
||||
|
||||
// Constrain the maximum head movement, as desired by the game LUA scripts.
|
||||
y.clampDegrees(maxYaw);
|
||||
pt.clampDegrees(maxPitch);
|
||||
r.clampDegrees(maxRoll);
|
||||
|
||||
// Also limit yaw, pitch and roll to make at most a movement as large as the given max step size during this frame.
|
||||
// This will produce a slow head-turning animation instead of immediately snapping to the
|
||||
// target lookat orientation.
|
||||
if (y - _yaw > yawStep)
|
||||
y = _yaw + yawStep;
|
||||
if (_yaw - y > yawStep)
|
||||
y = _yaw - yawStep;
|
||||
|
||||
if (pt - _pitch > pitchStep)
|
||||
pt = _pitch + pitchStep;
|
||||
if (_pitch - pt > pitchStep)
|
||||
pt = _pitch - pitchStep;
|
||||
|
||||
if (r - _roll > rollStep)
|
||||
r = _roll + rollStep;
|
||||
if (_roll - r > rollStep)
|
||||
r = _roll - rollStep;
|
||||
|
||||
// Remember how far we animated the head this frame, and we'll continue from here the next frame.
|
||||
_pitch = pt;
|
||||
_yaw = y;
|
||||
_roll = r;
|
||||
|
||||
// Assemble ypr back to a matrix.
|
||||
// This matrix is the head orientation with respect to parent-with-keyframe-animation space.
|
||||
lookAtTM.buildFromPitchYawRoll(pt, y, r);
|
||||
|
||||
// What follows is a hack: Since translateObject(ModelNode *node, bool reset) in this file,
|
||||
// and GfxOpenGL/GfxTinyGL::drawHierachyNode concatenate transforms incorrectly, by summing up
|
||||
// euler angles, do a hack here where we do the proper transform here already, and *subtract off*
|
||||
// the YPR scalars from the animYPR scalars to cancel out the values that those pieces of code
|
||||
// will later accumulate. After those pieces of code have been fixed, the following lines can
|
||||
// be deleted, and this function can simply output the contents of pt, y and r variables above.
|
||||
lookAtTM = animFrame * lookAtTM;
|
||||
|
||||
lookAtTM.getPitchYawRoll(&pt, &y, &r);
|
||||
_node->_animYaw = y - _node->_yaw;
|
||||
_node->_animPitch = pt - _node->_pitch;
|
||||
_node->_animRoll = r - _node->_roll;
|
||||
}
|
||||
|
||||
void Head::Joint::saveState(SaveGame *state) const {
|
||||
state->writeFloat(_pitch.getDegrees());
|
||||
state->writeFloat(_yaw.getDegrees());
|
||||
state->writeFloat(_roll.getDegrees());
|
||||
}
|
||||
|
||||
void Head::Joint::restoreState(SaveGame *state) {
|
||||
_pitch = state->readFloat();
|
||||
_yaw = state->readFloat();
|
||||
_roll = state->readFloat();
|
||||
}
|
||||
|
||||
Head::Head() :
|
||||
_maxPitch(0),
|
||||
_joint1(-1), _joint2(-1), _joint3(-1),
|
||||
_joint1Node(NULL), _joint2Node(NULL), _joint3Node(NULL),
|
||||
_headYaw(0), _headPitch(0) {
|
||||
_joint1Node(-1), _joint2Node(-1), _joint3Node(-1) {
|
||||
|
||||
}
|
||||
|
||||
void Head::setJoints(int joint1, int joint2, int joint3) {
|
||||
_joint1 = joint1;
|
||||
_joint2 = joint2;
|
||||
_joint3 = joint3;
|
||||
_joint1Node = joint1;
|
||||
_joint2Node = joint2;
|
||||
_joint3Node = joint3;
|
||||
}
|
||||
|
||||
void Head::loadJoints(ModelNode *nodes) {
|
||||
if (_joint1 >= 0 && _joint2 >= 0 && _joint3 >= 0 && nodes) {
|
||||
_joint1Node = nodes + _joint1;
|
||||
_joint2Node = nodes + _joint2;
|
||||
_joint3Node = nodes + _joint3;
|
||||
if (_joint1Node >= 0 && _joint2Node >= 0 && _joint3Node >= 0 && nodes) {
|
||||
_joint1.init(nodes + _joint1Node);
|
||||
_joint2.init(nodes + _joint2Node);
|
||||
_joint3.init(nodes + _joint3Node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,82 +197,7 @@ void Head::setMaxAngles(float maxPitch, float maxYaw, float maxRoll) {
|
||||
}
|
||||
|
||||
void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) {
|
||||
if (_joint1Node) {
|
||||
float step = g_grim->getPerSecond(rate);
|
||||
float yawStep = step;
|
||||
float pitchStep = step / 3.f;
|
||||
float rollStep = step / 3.f;
|
||||
|
||||
// Make sure we have up-to-date world transform matrices computed for every bone node of this character.
|
||||
ModelNode *p = _joint3Node;
|
||||
while (p->_parent) {
|
||||
p = p->_parent;
|
||||
}
|
||||
p->setMatrix(matrix);
|
||||
p->update();
|
||||
|
||||
Math::Vector3d modelFront; // the modeling convention for the forward direction.
|
||||
Math::Vector3d modelUp; // the modeling convention for the upward direction.
|
||||
Math::Vector3d frontDir; // Character front facing direction vector in world space (global scene coordinate space)
|
||||
|
||||
// the character head coordinate frame is: +Y forward, +Z up, +X right.
|
||||
frontDir = Math::Vector3d(_joint3Node->_matrix(0,1), _joint3Node->_matrix(1,1), _joint3Node->_matrix(2,1)); // Look straight ahead. (+Y)
|
||||
modelFront = Math::Vector3d(0,1,0);
|
||||
modelUp = Math::Vector3d(0,0,1);
|
||||
|
||||
// v is the world space direction vector this character should be looking towards.
|
||||
Math::Vector3d targetDir = point - _joint3Node->_pivotMatrix.getPosition();
|
||||
if (!entering)
|
||||
targetDir = frontDir;
|
||||
if (targetDir.isZero())
|
||||
return;
|
||||
|
||||
targetDir.normalize();
|
||||
|
||||
// The vector v is in world space, so generate the world space lookat matrix for the desired head facing
|
||||
// orientation.
|
||||
Math::Matrix4 lookAtTM;
|
||||
lookAtTM.setToIdentity();
|
||||
const Math::Vector3d worldUp(0,0,1); // The Residual scene convention: +Z is world space up.
|
||||
if (Math::Vector3d::dotProduct(targetDir, worldUp) >= 0.98f) // Avoid singularity if trying to look straight up.
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, -frontDir); // Instead of orienting head towards scene up, orient head towards character "back",
|
||||
// i.e. when you look straight up, your head up vector tilts/arches to point straight backwards.
|
||||
else if (Math::Vector3d::dotProduct(targetDir, worldUp) <= -0.98f) // Avoid singularity if trying to look straight down.
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, frontDir); // Instead of orienting head towards scene down, orient head towards character "front",
|
||||
// i.e. when you look straight down, your head up vector tilts/arches to point straight forwards.
|
||||
else
|
||||
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, worldUp);
|
||||
// The above specifies the world space orientation of this bone, but we need to output
|
||||
// the orientation in parent space (as yaw/pitch/roll).
|
||||
|
||||
// Get the coordinate frame in which we need to produce the character head yaw/pitch/roll values.
|
||||
Math::Matrix4 parentWorldTM;
|
||||
if (_joint3Node->_parent)
|
||||
parentWorldTM = _joint3Node->_parent->_matrix;
|
||||
|
||||
// While we could compute the desired lookat direction directly in the above coordinate frame,
|
||||
// it is preferrable to compute the lookat direction with respect to the head orientation in
|
||||
// the keyframe animation. This is because the LUA scripts specify the maximum head yaw, pitch and
|
||||
// roll values with respect to those keyframe animations. If the lookat was simply computed
|
||||
// directly in the space of the parent, we couldn't apply the head maxYaw/Pitch/Roll constraints
|
||||
// properly. So, compute the coordinate frame of this bone in the keyframe animation.
|
||||
Math::Matrix4 animFrame;
|
||||
animFrame.buildFromPitchYawRoll(_joint3Node->_pitch, _joint3Node->_yaw, _joint3Node->_roll);
|
||||
animFrame.setPosition(Math::Vector3d(0, 0 ,0));
|
||||
parentWorldTM = parentWorldTM * animFrame;
|
||||
parentWorldTM.invertAffineOrthonormal();
|
||||
|
||||
// Convert lookAtTM orientation from world space to parent-with-keyframe-animation space.
|
||||
lookAtTM = parentWorldTM * lookAtTM;
|
||||
|
||||
// Decompose to yaw-pitch-roll (+Z, +X, +Y).
|
||||
// In this space, Yaw is +Z. Pitch is +X. Roll is +Y.
|
||||
Math::Angle y, pt, r;
|
||||
lookAtTM.getPitchYawRoll(&pt, &y, &r);
|
||||
|
||||
// Constrain the maximum head movement, as desired by the game LUA scripts.
|
||||
y.clampDegrees(_maxYaw);
|
||||
pt.clampDegrees(_maxPitch);
|
||||
if (_joint1Node != -1) {
|
||||
// NOTE: By default, the _head.maxRoll for Manny's head is constrained to 165 degrees, which
|
||||
// comes in from the orignal Lua data scripts. (also, maxYaw == 80, maxPitch == 28).
|
||||
// The very small maxPitch angle, and a very large maxRoll angle causes problems when Manny
|
||||
@@ -143,73 +209,54 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const
|
||||
// right above the stairs, and Manny looks dead up.
|
||||
// B) Year 3, when Manny and Meche are imprisoned in the vault. Walk inside the room where Meche
|
||||
// is in, to look straight up to the sprinklers.
|
||||
r.clampDegrees(30);
|
||||
// r.clampDegrees(_head.maxRoll); // For original, use this.
|
||||
|
||||
// Also limit yaw, pitch and roll to make at most a movement as large as the given max step size during this frame.
|
||||
// This will produce a slow head-turning animation instead of immediately snapping to the
|
||||
// target lookat orientation.
|
||||
if (y - _headYaw > yawStep)
|
||||
y = _headYaw + yawStep;
|
||||
if (_headYaw - y > yawStep)
|
||||
y = _headYaw - yawStep;
|
||||
|
||||
if (pt - _headPitch > pitchStep)
|
||||
pt = _headPitch + pitchStep;
|
||||
if (_headPitch - pt > pitchStep)
|
||||
pt = _headPitch - pitchStep;
|
||||
|
||||
if (r - _headRoll > rollStep)
|
||||
r = _headRoll + rollStep;
|
||||
if (_headRoll - r > rollStep)
|
||||
r = _headRoll - rollStep;
|
||||
|
||||
// Remember how far we animated the head this frame, and we'll continue from here the next frame.
|
||||
_headPitch = pt;
|
||||
_headYaw = y;
|
||||
_headRoll = r;
|
||||
|
||||
// Assemble ypr back to a matrix.
|
||||
// This matrix is the head orientation with respect to parent-with-keyframe-animation space.
|
||||
lookAtTM.buildFromPitchYawRoll(pt, y, r);
|
||||
|
||||
// What follows is a hack: Since translateObject(ModelNode *node, bool reset) in this file,
|
||||
// and GfxOpenGL/GfxTinyGL::drawHierachyNode concatenate transforms incorrectly, by summing up
|
||||
// euler angles, do a hack here where we do the proper transform here already, and *subtract off*
|
||||
// the YPR scalars from the animYPR scalars to cancel out the values that those pieces of code
|
||||
// will later accumulate. After those pieces of code have been fixed, the following lines can
|
||||
// be deleted, and this function can simply output the contents of pt, y and r variables above.
|
||||
lookAtTM = animFrame * lookAtTM;
|
||||
|
||||
lookAtTM.getPitchYawRoll(&pt, &y, &r);
|
||||
_joint3Node->_animYaw = y - _joint3Node->_yaw;
|
||||
_joint3Node->_animPitch = pt - _joint3Node->_pitch;
|
||||
_joint3Node->_animRoll = r - _joint3Node->_roll;
|
||||
|
||||
|
||||
if (_joint1Node == _joint2Node && _joint1Node == _joint3Node) {
|
||||
// Most characters only have one head joint instead of three, so we can orient the head
|
||||
// with a single call.
|
||||
_joint3.orientTowards(entering, point, rate, matrix, _maxPitch, _maxYaw, 30.f, 1.0f);
|
||||
} else {
|
||||
// For characters like Manny, we'll have to orient each of the three head joints.
|
||||
_joint1.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 0.333f);
|
||||
_joint2.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 0.666f);
|
||||
_joint3.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 1.000f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Head::saveState(SaveGame *state) const {
|
||||
state->writeLESint32(_joint1);
|
||||
state->writeLESint32(_joint2);
|
||||
state->writeLESint32(_joint3);
|
||||
state->writeLESint32(_joint1Node);
|
||||
state->writeLESint32(_joint2Node);
|
||||
state->writeLESint32(_joint3Node);
|
||||
state->writeFloat(_maxPitch);
|
||||
state->writeFloat(_maxYaw);
|
||||
state->writeFloat(_maxRoll);
|
||||
state->writeFloat(_headPitch.getDegrees());
|
||||
state->writeFloat(_headYaw.getDegrees());
|
||||
|
||||
// TODO: Remove on next save format change.
|
||||
state->writeFloat(0.0f);
|
||||
state->writeFloat(0.0f);
|
||||
|
||||
// TODO: Uncomment on next save format change.
|
||||
//_joint1.saveState(state);
|
||||
//_joint2.saveState(state);
|
||||
//_joint3.saveState(state);
|
||||
}
|
||||
|
||||
void Head::restoreState(SaveGame *state) {
|
||||
_joint1 = state->readLESint32();
|
||||
_joint2 = state->readLESint32();
|
||||
_joint3 = state->readLESint32();
|
||||
_joint1Node = state->readLESint32();
|
||||
_joint2Node = state->readLESint32();
|
||||
_joint3Node = state->readLESint32();
|
||||
_maxPitch = state->readFloat();
|
||||
_maxYaw = state->readFloat();
|
||||
_maxRoll = state->readFloat();
|
||||
|
||||
_headPitch = state->readFloat();
|
||||
_headYaw = state->readFloat();
|
||||
// TODO: Remove on next save format change.
|
||||
state->readFloat();
|
||||
state->readFloat();
|
||||
|
||||
// TODO: Uncomment on next save format change.
|
||||
//_joint1.restoreState(state);
|
||||
//_joint2.restoreState(state);
|
||||
//_joint3.restoreState(state);
|
||||
}
|
||||
|
||||
} // end of namespace Grim
|
||||
|
||||
+28
-10
@@ -32,6 +32,26 @@ class SaveGame;
|
||||
|
||||
class Head {
|
||||
public:
|
||||
class Joint {
|
||||
public:
|
||||
Joint();
|
||||
|
||||
void init(ModelNode *node);
|
||||
|
||||
void orientTowards(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix,
|
||||
float maxPitch, float maxYaw, float maxRoll, float constrain);
|
||||
|
||||
void saveState(SaveGame *state) const;
|
||||
void restoreState(SaveGame *state);
|
||||
|
||||
private:
|
||||
ModelNode *_node;
|
||||
|
||||
Math::Angle _pitch;
|
||||
Math::Angle _yaw;
|
||||
Math::Angle _roll;
|
||||
};
|
||||
|
||||
Head();
|
||||
|
||||
void setJoints(int joint1, int joint2, int joint3);
|
||||
@@ -44,24 +64,22 @@ public:
|
||||
void restoreState(SaveGame *state);
|
||||
|
||||
private:
|
||||
int _joint1;
|
||||
int _joint2;
|
||||
int _joint3;
|
||||
int _joint1Node;
|
||||
int _joint2Node;
|
||||
int _joint3Node;
|
||||
float _maxRoll;
|
||||
float _maxPitch;
|
||||
float _maxYaw;
|
||||
|
||||
ModelNode *_rootNode;
|
||||
|
||||
// Specifies the three head joint bones of this character.
|
||||
// These joint bones are animated by the moveHead function to make
|
||||
// the characters face different directions.
|
||||
// Note that for some characters, these variables may all be equal.
|
||||
ModelNode *_joint1Node;
|
||||
ModelNode *_joint2Node;
|
||||
ModelNode *_joint3Node;
|
||||
|
||||
Math::Angle _headPitch;
|
||||
Math::Angle _headYaw;
|
||||
Math::Angle _headRoll;
|
||||
Joint _joint1;
|
||||
Joint _joint2;
|
||||
Joint _joint3;
|
||||
};
|
||||
|
||||
} // end of namespace Grim
|
||||
|
||||
@@ -646,9 +646,11 @@ void Lua_V1::GetActorNodeLocation() {
|
||||
ModelNode *allNodes = actor->getCurrentCostume()->getModelNodes();
|
||||
ModelNode *node = allNodes + nodeId;
|
||||
|
||||
node->_needsUpdate = true;
|
||||
ModelNode *root = node;
|
||||
while (root->_parent) {
|
||||
root = root->_parent;
|
||||
root->_needsUpdate = true;
|
||||
}
|
||||
|
||||
Math::Matrix4 matrix;
|
||||
|
||||
@@ -715,7 +715,7 @@ void ModelNode::update() {
|
||||
if (!_initialized)
|
||||
return;
|
||||
|
||||
if (_hierVisible) {
|
||||
if (_hierVisible && _needsUpdate) {
|
||||
Math::Vector3d animPos = _pos + _animPos;
|
||||
Math::Angle animPitch = _pitch + _animPitch;
|
||||
Math::Angle animYaw = _yaw + _animYaw;
|
||||
@@ -737,6 +737,8 @@ void ModelNode::update() {
|
||||
_child->setMatrix(_matrix);
|
||||
_child->update();
|
||||
}
|
||||
|
||||
_needsUpdate = false;
|
||||
}
|
||||
|
||||
if (_sibling) {
|
||||
|
||||
@@ -143,7 +143,7 @@ public:
|
||||
|
||||
class ModelNode {
|
||||
public:
|
||||
ModelNode() : _initialized(false) { }
|
||||
ModelNode() : _initialized(false), _needsUpdate(true) { }
|
||||
~ModelNode();
|
||||
void loadBinary(Common::SeekableReadStream *data, ModelNode *hierNodes, const Model::Geoset *g);
|
||||
void draw() const;
|
||||
@@ -181,6 +181,7 @@ public:
|
||||
Math::Angle _animPitch, _animYaw, _animRoll;
|
||||
bool _meshVisible, _hierVisible;
|
||||
bool _initialized;
|
||||
bool _needsUpdate;
|
||||
Math::Matrix4 _matrix;
|
||||
Math::Matrix4 _localMatrix;
|
||||
Math::Matrix4 _pivotMatrix;
|
||||
|
||||
Reference in New Issue
Block a user