diff --git a/engines/grim/costume.cpp b/engines/grim/costume.cpp index 3f16bda5bf9..488d0fd4aaf 100644 --- a/engines/grim/costume.cpp +++ b/engines/grim/costume.cpp @@ -463,7 +463,7 @@ void Costume::animate() { } void Costume::moveHead(bool entering, const Math::Vector3d &lookAt) { - _head->lookAt(entering, lookAt, _lookAtRate, _matrix); + _head->lookAt(entering, lookAt, _lookAtRate, _matrix, _fname); } void Costume::setHead(int joint1, int joint2, int joint3, float maxRoll, float maxPitch, float maxYaw) { diff --git a/engines/grim/costume/head.cpp b/engines/grim/costume/head.cpp index ce325fd43e5..91b53ed909c 100644 --- a/engines/grim/costume/head.cpp +++ b/engines/grim/costume/head.cpp @@ -194,12 +194,13 @@ void invertAffineOrthonormal(Math::Matrix4 &m) { m = m2; } -void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) { +void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix, const Common::String &fname) { 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) { @@ -209,9 +210,25 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const p->update(); bool yFront = true; // If true, the character head coordinate frame is: +Y forward, +Z up, +X right. - if (this->_fname.compareTo("gunar_meshes.cos") == 0 || this->_fname.compareTo("slisko_meshes.cos") == 0) + if (fname.compareTo("gunar_meshes.cos") == 0 || fname.compareTo("slisko_meshes.cos") == 0) yFront = false; // For these characters, the head coordinate frame is: -Z front, -X up, +Y right. + Math::Vector3d localFront; // Character front direction vector in local space. + Math::Vector3d localUp; // Character up direction vector in local space. + Math::Vector3d frontDir; // Character front facing direction vector in world space (global scene coordinate space) + if (yFront) + { + frontDir = Math::Vector3d(_joint3Node->_matrix(0,1), _joint3Node->_matrix(1,1), _joint3Node->_matrix(2,1)); // Look straight ahead. (+Y) + localFront = Math::Vector3d(0,1,0); + localUp = Math::Vector3d(0,0,1); + } + else + { + frontDir = Math::Vector3d(-_joint3Node->_matrix(0,2), -_joint3Node->_matrix(1,2), -_joint3Node->_matrix(2,2)); // Look straight ahead. (-Z) + localFront = Math::Vector3d(0,0,-1); + localUp = Math::Vector3d(-1,0,0); + } + // yFront == true for about every character in the game. // yFront == false for those two revolutionistas in Blue Casket. Perhaps the artist wanted to be // a revolutionist as well ;) OR, there is a bug in the way how transform hierarchies @@ -219,12 +236,8 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const // the animation mechanism) // v is the world space direction vector this character should be looking towards. Math::Vector3d v = point - _joint3Node->_pivotMatrix.getPosition(); - if (!lookingMode) { - if (yFront) - v = Math::Vector3d(_joint3Node->_matrix(0,1), _joint3Node->_matrix(1,1), _joint3Node->_matrix(2,1)); // Look straight ahead. (+Y) - else - v = Math::Vector3d(-_joint3Node->_matrix(0,2), -_joint3Node->_matrix(1,2), -_joint3Node->_matrix(2,2)); // Look straight ahead. (-Z) - } + if (!entering) + v = frontDir; if (v.isZero()) return; @@ -233,10 +246,12 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const // The vector v is in world space, so generate the world space lookat matrix for the desired head facing // orientation. Math::Matrix4 lookAtTM; - if (yFront) - lookAtTM = lookAtMatrix(Math::Vector3d(0,1,0), v, Math::Vector3d(0,0,1), Math::Vector3d(0,0,1)); + const Math::Vector3d worldUp(0,0,1); // The Residual scene convention: +Z is world space up. + if (fabs(Math::Vector3d::dotProduct(v, Math::Vector3d(0,0,1))) >= 0.98f) // Avoid singularity if trying to look straight up. + lookAtTM = lookAtMatrix(localFront, v, localUp, -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 - lookAtTM = lookAtMatrix(Math::Vector3d(0,0,-1), v, Math::Vector3d(-1,0,0), Math::Vector3d(0,0,1)); + lookAtTM = lookAtMatrix(localFront, v, localUp, 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). @@ -266,9 +281,21 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const // Constrain the maximum head movement, as desired by the game LUA scripts. y = clampMagnitude(y, _maxYaw); pt = clampMagnitude(pt, _maxPitch); - r = clampMagnitude(r, _maxRoll); + // 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 + // is trying to look straight up to an object, in which case the euler roll angles vary + // wildly compared to the pitch angles, which get clamped to a much smaller interval. Therefore, + // restrict the maximum roll angle to a smaller value than 165 degrees to avoid this behavior. + // If you want to change this, good places to test are: + // A) Year 1, outside the Department of Death, run/walk up & down the stairs, there's a sign + // 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 = clampMagnitude(r, 30); + // r = clampMagnitude(r, _head.maxRoll); // For original, use this. - // Also limit yaw and pitch to make at most a movement as large as yawStep/pitchStep during this frame. + // 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) @@ -281,9 +308,15 @@ void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const 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. diff --git a/engines/grim/costume/head.h b/engines/grim/costume/head.h index a6ff6006b93..7575f87c216 100644 --- a/engines/grim/costume/head.h +++ b/engines/grim/costume/head.h @@ -38,7 +38,7 @@ public: void loadJoints(ModelNode *nodes); void setMaxAngles(float maxPitch, float maxYaw, float maxRoll); - void lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix); + void lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix, const Common::String &fname); void saveState(SaveGame *state) const; void restoreState(SaveGame *state); @@ -61,6 +61,7 @@ private: Math::Angle _headPitch; Math::Angle _headYaw; + Math::Angle _headRoll; }; } // end of namespace Grim