From 5ecd00ec36c1192b8ff65ff087491466d10467d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joni=20V=C3=A4h=C3=A4m=C3=A4ki?= Date: Sat, 7 Apr 2012 21:06:11 +0300 Subject: [PATCH 1/4] GRIM: Animate all three of the head joints instead of just one. --- engines/grim/costume/head.cpp | 313 +++++++++++++++++++--------------- engines/grim/costume/head.h | 38 +++-- 2 files changed, 201 insertions(+), 150 deletions(-) diff --git a/engines/grim/costume/head.cpp b/engines/grim/costume/head.cpp index cd34006f1e4..55b60f515a8 100644 --- a/engines/grim/costume/head.cpp +++ b/engines/grim/costume/head.cpp @@ -27,25 +27,164 @@ 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 every bone node of this character. + ModelNode *p = _node; + 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(_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 +195,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 +207,44 @@ 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()); + + _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(); + _joint1.restoreState(state); + _joint2.restoreState(state); + _joint3.restoreState(state); } } // end of namespace Grim diff --git a/engines/grim/costume/head.h b/engines/grim/costume/head.h index 52c87ba9b20..85fbb8c3387 100644 --- a/engines/grim/costume/head.h +++ b/engines/grim/costume/head.h @@ -31,7 +31,27 @@ class ModelNode; class SaveGame; class Head { -public: + 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,9 +64,9 @@ public: void restoreState(SaveGame *state); private: - int _joint1; - int _joint2; - int _joint3; + int _joint1Node; + int _joint2Node; + int _joint3Node; float _maxRoll; float _maxPitch; float _maxYaw; @@ -55,13 +75,9 @@ private: // 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 From 8530edd589eecf4e3e212bc8f99bca1bb6aeb947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joni=20V=C3=A4h=C3=A4m=C3=A4ki?= Date: Sat, 7 Apr 2012 23:33:30 +0300 Subject: [PATCH 2/4] GRIM: Fix indentation. --- engines/grim/costume/head.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/grim/costume/head.h b/engines/grim/costume/head.h index 85fbb8c3387..8e802f8e52a 100644 --- a/engines/grim/costume/head.h +++ b/engines/grim/costume/head.h @@ -31,7 +31,7 @@ class ModelNode; class SaveGame; class Head { - public: +public: class Joint { public: Joint(); From 4fb4e0c32085130721e2ffe490e3dfe32a7da9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joni=20V=C3=A4h=C3=A4m=C3=A4ki?= Date: Sun, 8 Apr 2012 12:54:42 +0300 Subject: [PATCH 3/4] GRIM: Only compute the world transform matrices for the joint node and its parents, not for every node of the character. --- engines/grim/costume/head.cpp | 4 +++- engines/grim/costume/head.h | 2 ++ engines/grim/lua_v1_actor.cpp | 2 ++ engines/grim/model.cpp | 4 +++- engines/grim/model.h | 3 ++- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/engines/grim/costume/head.cpp b/engines/grim/costume/head.cpp index 55b60f515a8..b830862a08f 100644 --- a/engines/grim/costume/head.cpp +++ b/engines/grim/costume/head.cpp @@ -41,10 +41,12 @@ void Head::Joint::orientTowards(bool entering, const Math::Vector3d &point, floa float pitchStep = step / 3.0f; float rollStep = step / 3.0f; - // Make sure we have up-to-date world transform matrices computed for every bone node of this character. + // 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(); diff --git a/engines/grim/costume/head.h b/engines/grim/costume/head.h index 8e802f8e52a..f298e74b17e 100644 --- a/engines/grim/costume/head.h +++ b/engines/grim/costume/head.h @@ -71,6 +71,8 @@ private: 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. diff --git a/engines/grim/lua_v1_actor.cpp b/engines/grim/lua_v1_actor.cpp index c65610d439e..e159a3d9e67 100644 --- a/engines/grim/lua_v1_actor.cpp +++ b/engines/grim/lua_v1_actor.cpp @@ -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; diff --git a/engines/grim/model.cpp b/engines/grim/model.cpp index 8a9a04a5064..cafea47dff1 100644 --- a/engines/grim/model.cpp +++ b/engines/grim/model.cpp @@ -709,7 +709,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; @@ -731,6 +731,8 @@ void ModelNode::update() { _child->setMatrix(_matrix); _child->update(); } + + _needsUpdate = false; } if (_sibling) { diff --git a/engines/grim/model.h b/engines/grim/model.h index f57693ee555..bc7f3f9e6b7 100644 --- a/engines/grim/model.h +++ b/engines/grim/model.h @@ -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; From 10af894edff510fa7bccb5cf3645e1829d451ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joni=20V=C3=A4h=C3=A4m=C3=A4ki?= Date: Sun, 8 Apr 2012 18:40:52 +0300 Subject: [PATCH 4/4] GRIM: Commented out the save changes for now. --- engines/grim/costume/head.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/engines/grim/costume/head.cpp b/engines/grim/costume/head.cpp index b830862a08f..c2f422bdb4e 100644 --- a/engines/grim/costume/head.cpp +++ b/engines/grim/costume/head.cpp @@ -231,9 +231,14 @@ void Head::saveState(SaveGame *state) const { state->writeFloat(_maxYaw); state->writeFloat(_maxRoll); - _joint1.saveState(state); - _joint2.saveState(state); - _joint3.saveState(state); + // 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) { @@ -244,9 +249,14 @@ void Head::restoreState(SaveGame *state) { _maxYaw = state->readFloat(); _maxRoll = state->readFloat(); - _joint1.restoreState(state); - _joint2.restoreState(state); - _joint3.restoreState(state); + // 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