From 354d7f6366d11628030bcfde9186fc6e1c819f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20=C5=A0palek?= Date: Sun, 8 Nov 2009 03:16:22 +0000 Subject: [PATCH] Debugged smooth walking except for 1 bug. Adjusting to the edge is done such that it respects slight sideways movements of the dragon. Fixed rounding issues in the whole game. Improved debug messages. Made sure that the dragon does not turn like crazy around when clicking on the same pixel: the final point is always the clicked one although the middle points made by shifted to make the animations smooth, and preserve the dragons direction if he has not walked. There is a bug with running turning animations as they seem to disappear for 1 frame and have incorrect Z coordinate. Will investigate it next. svn-id: r45742 --- engines/draci/animation.cpp | 4 +- engines/draci/draci.h | 5 ++ engines/draci/game.cpp | 8 +-- engines/draci/script.cpp | 4 +- engines/draci/sprite.cpp | 8 +-- engines/draci/walking.cpp | 131 +++++++++++++++++++++--------------- engines/draci/walking.h | 10 +-- 7 files changed, 100 insertions(+), 70 deletions(-) diff --git a/engines/draci/animation.cpp b/engines/draci/animation.cpp index ffdfa6c1e2d..9b7694377f2 100644 --- a/engines/draci/animation.cpp +++ b/engines/draci/animation.cpp @@ -59,8 +59,8 @@ void Animation::setRelative(int relx, int rely) { Displacement Animation::getCurrentFrameDisplacement() const { Displacement dis = _displacement; - dis.relX += (int) (dis.extraScaleX * _shift.x); - dis.relY += (int) (dis.extraScaleY * _shift.y); + dis.relX += scummvm_lround(dis.extraScaleX * _shift.x); + dis.relY += scummvm_lround(dis.extraScaleY * _shift.y); return dis; } diff --git a/engines/draci/draci.h b/engines/draci/draci.h index 1707fc29adb..68e56bb2d72 100644 --- a/engines/draci/draci.h +++ b/engines/draci/draci.h @@ -26,6 +26,8 @@ #ifndef DRACI_H #define DRACI_H +#include + #include "common/system.h" #include "engines/engine.h" #include "engines/advancedDetector.h" @@ -109,6 +111,9 @@ enum { kDraciWalkingDebugLevel = 1 << 6 }; +// Macro to simulate lround() for non-C99 compilers +static inline long scummvm_lround(double val) { return (long)floor(val + 0.5); } + } // End of namespace Draci #endif // DRACI_H diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp index f18b2fda32a..23a037e5e75 100644 --- a/engines/draci/game.cpp +++ b/engines/draci/game.cpp @@ -1482,8 +1482,8 @@ void Game::positionAnimAsHero(Animation *anim) { // click but sprites are drawn from their top-left corner so we subtract // the current height of the dragon's sprite Common::Point p = _hero; - p.x -= (int)(scale * frame->getWidth() / 2); - p.y -= (int)(scale * frame->getHeight()); + p.x -= scummvm_lround(scale * frame->getWidth() / 2); + p.y -= scummvm_lround(scale * frame->getHeight()); // Since _persons[] is used for placing talking text, we use the non-adjusted x value // so the text remains centered over the dragon. @@ -1511,8 +1511,8 @@ void Game::positionHeroAsAnim(Animation *anim) { // elsewhere). // TODO: what about rounding errors? Drawable *frame = anim->getCurrentFrame(); - _hero.x += (int) (anim->getScaleX() * frame->getWidth() / 2); - _hero.y += (int) (anim->getScaleY() * frame->getHeight()); + _hero.x += scummvm_lround(anim->getScaleX() * frame->getWidth() / 2); + _hero.y += scummvm_lround(anim->getScaleY() * frame->getHeight()); } void Game::pushNewRoom() { diff --git a/engines/draci/script.cpp b/engines/draci/script.cpp index 7d9dd4126fe..340942a7e21 100644 --- a/engines/draci/script.cpp +++ b/engines/draci/script.cpp @@ -666,11 +666,13 @@ void Script::stayOn(Common::Queue ¶ms) { // Jumps into the given position regardless of the walking map. Common::Point heroPos(_vm->_game->findNearestWalkable(x, y)); Common::Point mousePos(_vm->_mouse->getPosX(), _vm->_mouse->getPosY()); + const GameObject *dragon = _vm->_game->getObject(kDragonObject); + Movement startingDirection = static_cast (_vm->_game->playingObjectAnimation(dragon)); _vm->_game->stopWalking(); _vm->_game->setHeroPosition(heroPos); _vm->_game->playHeroAnimation(WalkingState::animationForSightDirection( - dir, heroPos, mousePos, WalkingPath())); + dir, heroPos, mousePos, WalkingPath(), startingDirection)); } void Script::walkOn(Common::Queue ¶ms) { diff --git a/engines/draci/sprite.cpp b/engines/draci/sprite.cpp index 32c4044e4ec..64374e70e3e 100644 --- a/engines/draci/sprite.cpp +++ b/engines/draci/sprite.cpp @@ -114,10 +114,6 @@ Sprite::~Sprite() { } } -// Macro to simulate lround() for non-C99 compilers -// TODO: get rid of it -static inline long scummvm_lround(double val) { return (long)floor(val + 0.5); } - int Sprite::getPixel(int x, int y, const Displacement &displacement) const { Common::Rect rect = getRect(displacement); @@ -262,8 +258,8 @@ void Sprite::draw(Surface *surface, bool markDirty, int relX, int relY) const { Common::Rect Sprite::getRect(const Displacement &displacement) const { return Common::Rect(_x + displacement.relX, _y + displacement.relY, - _x + displacement.relX + (int) (_scaledWidth * displacement.extraScaleX), - _y + displacement.relY + (int) (_scaledHeight * displacement.extraScaleY)); + _x + displacement.relX + scummvm_lround(_scaledWidth * displacement.extraScaleX), + _y + displacement.relY + scummvm_lround(_scaledHeight * displacement.extraScaleY)); } Text::Text(const Common::String &str, const Font *font, byte fontColour, diff --git a/engines/draci/walking.cpp b/engines/draci/walking.cpp index 51b77cb049b..bda91b79cc6 100644 --- a/engines/draci/walking.cpp +++ b/engines/draci/walking.cpp @@ -443,7 +443,7 @@ void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2 // they are different pixels. _path.push_back(p2); } - debugC(2, kDraciWalkingDebugLevel, "Starting walking [%d,%d] -> [%d,%d] in %d segments", + debugC(2, kDraciWalkingDebugLevel, "Starting walking [%d,%d] -> [%d,%d] with %d vertices", p1.x, p1.y, p2.x, p2.y, _path.size()); // The first and last point are available with pixel accurracy. @@ -456,6 +456,10 @@ void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2 _path[i].y *= delta.y; } + // Remember the initial dragon's direction. + const GameObject *dragon = _vm->_game->getObject(kDragonObject); + _startingDirection = static_cast (_vm->_game->playingObjectAnimation(dragon)); + // Going to start with the first segment. _segment = _lastAnimPhase = -1; turnForTheNextSegment(); @@ -491,22 +495,6 @@ bool WalkingState::continueWalking() { const GameObject *dragon = _vm->_game->getObject(kDragonObject); const Movement movement = static_cast (_vm->_game->playingObjectAnimation(dragon)); - // If the current animation is a turning animation, wait a bit more. - // When this animation has finished, heroAnimationFinished() callback - // will be called, which starts a new scheduled one, so the code never - // gets here if it hasn't finished yet. - if (isTurningMovement(movement)) { - return true; - } - - // If the current segment is the last one, we have reached the - // destination and are already facing in the right direction ===> - // return false. - if (_segment >= (int) (_path.size() - 1)) { - _path.clear(); - return false; - } - // Read the dragon's animation's current phase. Determine if it has // changed from the last time. If not, wait until it has. const int animID = dragon->_anim[movement]; @@ -518,55 +506,79 @@ bool WalkingState::continueWalking() { return true; } + // If the current animation is a turning animation, wait a bit more. + // When this animation has finished, heroAnimationFinished() callback + // will be called, which starts a new scheduled one, so the code never + // gets here if it hasn't finished yet. + if (isTurningMovement(movement)) { + debugC(3, kDraciWalkingDebugLevel, "Continuing turning for edge %d with phase %d", _segment+1, animPhase); + _lastAnimPhase = animPhase; + return true; + } + + // If the current segment is the last one, we have reached the + // destination and are already facing in the right direction ===> + // return false. + if (_segment >= (int) (_path.size() - 1)) { + _path.clear(); + return false; + } + // We are walking in the middle of an edge. The animation phase has // just changed. - // Read the position of the hero from the animation object, and project + // Read the position of the hero from the animation object, and adjust // it to the current edge. + const Common::Point prevHero = _vm->_game->getHeroPosition(); _vm->_game->positionHeroAsAnim(anim); - const Common::Point &hero = _vm->_game->getHeroPosition(); - Common::Point newHero = hero; - const bool reachedEnd = alignHeroToEdge(_path[_segment], _path[_segment+1], &newHero); + const Common::Point curHero = _vm->_game->getHeroPosition(); + Common::Point adjustedHero = curHero; + const bool reachedEnd = alignHeroToEdge(_path[_segment], _path[_segment+1], prevHero, &adjustedHero); + if (reachedEnd && _segment >= (int) (_path.size() - 2)) { + // We don't want the dragon to jump around if we repeatedly + // click on the same pixel. Let him always end where desired. + debugC(2, kDraciWalkingDebugLevel, "Adjusting position to the final node"); + adjustedHero = _path[_segment+1]; + } - debugC(3, kDraciWalkingDebugLevel, "Continuing walking in segment %d: phase %d and position [%d,%d] projected to [%d,%d]", - _segment, animPhase, hero.x, hero.y, newHero.x, newHero.y); + debugC(3, kDraciWalkingDebugLevel, "Continuing walking on edge %d: phase %d and position+=[%d,%d]->[%d,%d] adjusted to [%d,%d]", + _segment, animPhase, curHero.x - prevHero.x, curHero.y - prevHero.y, curHero.x, curHero.y, adjustedHero.x, adjustedHero.y); - // Update the hero position to the projected one. The animation number + // Update the hero position to the adjusted one. The animation number // is not changing, so this will just move the sprite and return the // current frame number. - _vm->_game->setHeroPosition(newHero); + _vm->_game->setHeroPosition(adjustedHero); _lastAnimPhase = _vm->_game->playHeroAnimation(movement); // If the hero has reached the end of the edge, start transition to the // next phase. This will increment _segment, either immediately (if no // transition is needed) or in the callback (after the transition is - // done). The position is equal to the end-point of the edge thanks to - // alignHeroToEdge(). + // done). If the hero has arrived at a slightly different point due to + // animated sprites, adjust the path so that the animation can smoothly + // continue. if (reachedEnd) { + if (adjustedHero != _path[_segment+1]) { + debugC(2, kDraciWalkingDebugLevel, "Adjusting node %d of the path [%d,%d]->[%d,%d]", + _segment+1, _path[_segment+1].x, _path[_segment+1].y, adjustedHero.x, adjustedHero.y); + _path[_segment+1] = adjustedHero; + } turnForTheNextSegment(); } return true; } -bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, Common::Point *hero) { - const Common::Point heroDiff(hero->x - p1.x, hero->y - p1.y); +bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, const Common::Point &prevHero, Common::Point *hero) { + const Movement movement = animationForDirection(p1, p2); const Common::Point p2Diff(p2.x - p1.x, p2.y - p1.y); - const int scalarProduct = p2Diff.x * heroDiff.x + p2Diff.y * heroDiff.y; - const int p2DiffSqNorm = p2Diff.x * p2Diff.x + p2Diff.y * p2Diff.y; - double fraction = ((double) scalarProduct) / p2DiffSqNorm; - bool reachedEnd = false; - if (fraction >= 1.0) { - fraction = 1.0; - reachedEnd = true; + bool reachedEnd; + if (movement == kMoveLeft || movement == kMoveRight) { + reachedEnd = movement == kMoveLeft ? hero->x <= p2.x : hero->x >= p2.x; + hero->y += hero->x * p2Diff.y / p2Diff.x - prevHero.x * p2Diff.y / p2Diff.x; + } else { + reachedEnd = movement == kMoveUp ? hero->y <= p2.y : hero->y >= p2.y; + hero->x += hero->y * p2Diff.x / p2Diff.y - prevHero.y * p2Diff.x / p2Diff.y; } - hero->x = p1.x + (int) (fraction * p2Diff.x + 0.5); - hero->y = p1.y + (int) (fraction * p2Diff.y + 0.5); - // TODO: unfortunately, the left-right walking animation jumps up and - // down by two pixels instead of going exactly horizontally, which - // means that the projection algorithm screws the vertical "shaking" as - // well as rounds horizontal positions improperly. Fix it by better - // rounding or different projection. return reachedEnd; } @@ -576,7 +588,7 @@ void WalkingState::turnForTheNextSegment() { const Movement wantAnim = directionForNextPhase(); Movement transition = transitionBetweenAnimations(currentAnim, wantAnim); - debugC(2, kDraciWalkingDebugLevel, "Turning for segment %d", _segment+1); + debugC(2, kDraciWalkingDebugLevel, "Turning for edge %d", _segment+1); if (transition == kMoveUndefined) { // Start the next segment right away as if the turning has just finished. @@ -585,13 +597,12 @@ void WalkingState::turnForTheNextSegment() { // Otherwise start the transition and wait until the Animation // class calls heroAnimationFinished() as a callback. assert(isTurningMovement(transition)); - _vm->_game->playHeroAnimation(transition); + _lastAnimPhase = _vm->_game->playHeroAnimation(transition); const int animID = dragon->_anim[transition]; Animation *anim = _vm->_anims->getAnimation(animID); anim->registerCallback(&Animation::tellWalkingState); - debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d", transition); - _lastAnimPhase = -1; + debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d with phase %d", transition, _lastAnimPhase); } } @@ -609,12 +620,12 @@ void WalkingState::heroAnimationFinished() { Movement nextAnim = directionForNextPhase(); _lastAnimPhase = _vm->_game->playHeroAnimation(nextAnim); - debugC(2, kDraciWalkingDebugLevel, "Turned for segment %d, starting animation %d", _segment+1, nextAnim); + debugC(2, kDraciWalkingDebugLevel, "Turned for edge %d, starting animation %d with phase %d", _segment+1, nextAnim, _lastAnimPhase); if (++_segment < (int) (_path.size() - 1)) { // We are on an edge: track where the hero is on this edge. int length = WalkingMap::pointsBetween(_path[_segment], _path[_segment+1]); - debugC(2, kDraciWalkingDebugLevel, "Next segment %d has length %d", _segment, length); + debugC(2, kDraciWalkingDebugLevel, "Next edge %d has length %d", _segment, length); } else { // Otherwise we are done. continueWalking() will return false next time. debugC(2, kDraciWalkingDebugLevel, "We have walked the whole path"); @@ -635,7 +646,7 @@ Movement WalkingState::animationForDirection(const Common::Point &here, const Co Movement WalkingState::directionForNextPhase() const { if (_segment >= (int) (_path.size() - 2)) { - return animationForSightDirection(_dir, _path[_path.size()-1], _mouse, _path); + return animationForSightDirection(_dir, _path[_path.size()-1], _mouse, _path, _startingDirection); } else { return animationForDirection(_path[_segment+1], _path[_segment+2]); } @@ -722,21 +733,35 @@ Movement WalkingState::transitionBetweenAnimations(Movement previous, Movement n } } -Movement WalkingState::animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path) { +Movement WalkingState::animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path, Movement startingDirection) { switch (dir) { case kDirectionMouse: - return mouse.x < hero.x ? kStopLeft : kStopRight; + if (mouse.x < hero.x) { + return kStopLeft; + } else if (mouse.x > hero.x) { + return kStopRight; + } else { + goto defaultCase; + } case kDirectionLeft: return kStopLeft; case kDirectionRight: return kStopRight; default: { +defaultCase: // Find the last horizontal direction on the path. int i = path.size() - 1; while (i >= 0 && path[i].x == hero.x) { --i; } - return (i >= 0 && path[i].x < hero.x) ? kStopRight : kStopLeft; + if (i >= 0) { + return path[i].x < hero.x ? kStopRight : kStopLeft; + } else { + // Avoid changing the direction when no walking has + // been done. Preserve the original direction. + return (startingDirection == kMoveLeft || startingDirection == kStopLeft || startingDirection == kSpeakLeft) + ? kStopLeft : kStopRight; + } } } } diff --git a/engines/draci/walking.h b/engines/draci/walking.h index e2d86a5653b..b178c2164b0 100644 --- a/engines/draci/walking.h +++ b/engines/draci/walking.h @@ -132,7 +132,7 @@ public: // direction. The direction can be smart and in that case this // function needs to know the whole last path, the current position of // the hero, or the mouse position. - static Movement animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path); + static Movement animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path, Movement startingDirection); private: DraciEngine *_vm; @@ -140,6 +140,7 @@ private: WalkingPath _path; Common::Point _mouse; SightDirection _dir; + Movement _startingDirection; int _segment; int _lastAnimPhase; @@ -168,9 +169,10 @@ private: return m >= kFirstTurning && m <= kLastTurning; } - // Projects hero to the given edge, but not behind p2. Returns true - // when hero has reached at least p2. - static bool alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, Common::Point *hero); + // Projects hero to the given edge. Returns true when hero has reached + // at least p2. prevHero is passed so that we can compute how much to + // adjust in the other-than-walking direction. + static bool alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, const Common::Point &prevHero, Common::Point *hero); }; } // End of namespace Draci