Merge pull request #593 from Akz-/joints

GRIM: Animate all three of the head joints
This commit is contained in:
Paweł Kołodziejski
2012-04-08 08:49:25 -07:00
5 changed files with 221 additions and 151 deletions
+186 -139
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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;
+3 -1
View File
@@ -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) {
+2 -1
View File
@@ -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;