diff --git a/engines/stark/movement/followpath.cpp b/engines/stark/movement/followpath.cpp new file mode 100644 index 00000000000..c383605a3b8 --- /dev/null +++ b/engines/stark/movement/followpath.cpp @@ -0,0 +1,130 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "engines/stark/movement/followpath.h" + +#include "engines/stark/services/global.h" +#include "engines/stark/services/services.h" + +#include "engines/stark/resources/anim.h" +#include "engines/stark/resources/floor.h" +#include "engines/stark/resources/item.h" + +namespace Stark { + +FollowPath::FollowPath(Resources::ItemVisual *item) : + Movement(item), + _path(nullptr), + _speed(0.0), + _position(0.0), + _previouslyEnabled(true) { +} + +FollowPath::~FollowPath() { +} + +void FollowPath::start() { + Movement::start(); + + changeItemAnim(); + + _previouslyEnabled = _item->isEnabled(); + _item->setEnabled(true); +} + +void FollowPath::stop() { + Movement::stop(); + + _item->setEnabled(_previouslyEnabled); +} + +void FollowPath::onGameLoop() { + // Compute the new position on the path + _position += _speed * StarkGlobal->getMillisecondsPerGameloop(); + + // Find the current path edge, and position on the path edge + uint currentEdge = 0; + float positionInEdge = _position; + for (uint i = 0; i < _path->getEdgeCount(); i++) { + float edgeLength = _path->getWeightedEdgeLength(i); + if (positionInEdge < edgeLength) { + break; // Found the current path edge + } + + positionInEdge -= edgeLength; + currentEdge++; + } + + // Check if we went beyond the path's end + if (currentEdge >= _path->getEdgeCount()) { + stop(); + return; + } + + // Get the new position for the item + Math::Vector3d newPosition = _path->getWeightedPositionInEdge(currentEdge, positionInEdge); + + // Update the item's properties in the scene + if (is3D()) { + Resources::FloorPositionedItem *item3D = Resources::Object::cast(_item); + Resources::Floor *floor = StarkGlobal->getCurrent()->getFloor(); + + int32 floorFaceIndex = floor->findFaceContainingPoint(newPosition); + if (floorFaceIndex >= 0) { + item3D->setFloorFaceIndex(floorFaceIndex); + } else { + item3D->overrideSortKey(_path->getSortKey()); + } + + item3D->setPosition3D(newPosition); + + Math::Vector3d direction = _path->getEdgeDirection(currentEdge); + item3D->setDirection(computeAngleBetweenVectorsXYPlane(direction, Math::Vector3d(1.0, 0.0, 0.0))); + } else { + Common::Point position2D = Common::Point(newPosition.x(), newPosition.y()); + _item->setPosition2D(position2D); + } + + changeItemAnim(); +} + +void FollowPath::changeItemAnim() { + if (_ended) { + _item->setAnimKind(Resources::Anim::kActorUsageIdle); + } else { + _item->setAnimKind(Resources::Anim::kActorUsageWalk); + } +} + +void FollowPath::setPath(Resources::Path *path) { + _path = path; +} + +void FollowPath::setSpeed(float speed) { + _speed = speed; +} + +bool FollowPath::is3D() const { + return _path->getSubType() == Resources::Path::kPath3D; +} + +} // End of namespace Stark \ No newline at end of file diff --git a/engines/stark/movement/followpath.h b/engines/stark/movement/followpath.h new file mode 100644 index 00000000000..cf615bb02ce --- /dev/null +++ b/engines/stark/movement/followpath.h @@ -0,0 +1,65 @@ +/* ResidualVM - A 3D game interpreter + * + * ResidualVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef STARK_MOVEMENT_FOLLOW_PATH_H +#define STARK_MOVEMENT_FOLLOW_PATH_H + +#include +#include "engines/stark/movement/movement.h" + +namespace Stark { + +/** + * Make an item follow pre-computed path + * + * Works for 2D and 3D items, with respectively 2D and 3D paths + */ +class FollowPath : public Movement { +public: + FollowPath(Resources::ItemVisual *item); + virtual ~FollowPath(); + + // Movement API + void start() override; + void onGameLoop() override; + void stop() override; + + /** Set the path to follow */ + void setPath(Stark::Resources::Path *path); + + /** Set the movement speed on the path */ + void setSpeed(float speed); + +private: + void changeItemAnim(); + bool is3D() const; + + Resources::Path *_path; + float _speed; + + float _position; + bool _previouslyEnabled; +}; + +} // End of namespace Stark + +#endif // STARK_MOVEMENT_FOLLOW_PATH_H diff --git a/engines/stark/resources/command.cpp b/engines/stark/resources/command.cpp index 34a77e8a818..6196e189b15 100644 --- a/engines/stark/resources/command.cpp +++ b/engines/stark/resources/command.cpp @@ -26,6 +26,7 @@ #include "engines/stark/formats/xrc.h" +#include "engines/stark/movement/followpath.h" #include "engines/stark/movement/turn.h" #include "engines/stark/movement/walk.h" @@ -44,6 +45,7 @@ #include "engines/stark/resources/layer.h" #include "engines/stark/resources/light.h" #include "engines/stark/resources/location.h" +#include "engines/stark/resources/path.h" #include "engines/stark/resources/pattable.h" #include "engines/stark/resources/script.h" #include "engines/stark/resources/scroll.h" @@ -108,6 +110,9 @@ Command *Command::execute(uint32 callMode, Script *script) { return opItem3DPlaceOn(_arguments[1].referenceValue, _arguments[2].referenceValue); case kItem3DWalkTo: return opItem3DWalkTo(script, _arguments[1].referenceValue, _arguments[2].referenceValue, _arguments[3].intValue); + case kItem3DFollowPath: + case kItem2DFollowPath: + return opItemFollowPath(script, _arguments[1].referenceValue, _arguments[2].referenceValue, _arguments[3].intValue, _arguments[4].intValue); case kItemLookAt: return opItemLookAt(script, _arguments[1].referenceValue, _arguments[2].referenceValue, _arguments[3].intValue, _arguments[4].intValue); case kItemEnable: @@ -430,6 +435,26 @@ Command *Command::opItem3DWalkTo(Script *script, const ResourceReference &itemRe } } +Command *Command::opItemFollowPath(Script *script, ResourceReference itemRef, ResourceReference pathRef, uint32 speed, uint32 suspend) { + ItemVisual *item = itemRef.resolve(); + Path *path = pathRef.resolve(); + + FollowPath *follow = new FollowPath(item); + follow->setPath(path); + follow->setSpeed(speed / 100.0); + follow->start(); + + item->setMovement(follow); + + if (suspend) { + script->suspend(item); + item->setMovementSuspendedScript(script); + return this; // Stay on the same command while suspended + } else { + return nextCommand(); + } +} + Command *Command::opItemLookAt(Script *script, const ResourceReference &itemRef, const ResourceReference &objRef, bool suspend, int32 unknown) { FloorPositionedItem *item = itemRef.resolve(); Math::Vector3d currentPosition = item->getPosition3D(); diff --git a/engines/stark/resources/command.h b/engines/stark/resources/command.h index aba23a9e929..2607d7e7071 100644 --- a/engines/stark/resources/command.h +++ b/engines/stark/resources/command.h @@ -83,9 +83,10 @@ public: kItem3DPlaceOn = 81, kItem3DWalkTo = 82, - + kItem3DFollowPath = 83, kItemLookAt = 84, + kItem2DFollowPath = 86, kItemEnable = 87, kItemSetActivity = 88, kItemSelectInInventory = 89, @@ -205,6 +206,7 @@ protected: Command *opInventoryOpen(bool open); Command *opItem3DPlaceOn(const ResourceReference &itemRef, const ResourceReference &targetRef); Command *opItem3DWalkTo(Script *script, const ResourceReference &itemRef, const ResourceReference &targetRef, bool suspend); + Command *opItemFollowPath(Script *script, ResourceReference itemRef, ResourceReference pathRef, uint32 speed, uint32 suspend); Command *opItemLookAt(Script *script, const ResourceReference &itemRef, const ResourceReference &objRef, bool suspend, int32 unknown); Command *opItemEnable(const ResourceReference &itemRef, int32 enable); Command *opItemSetActivity(const ResourceReference &itemRef, int32 unknown1, int32 unknown2); diff --git a/engines/stark/resources/item.cpp b/engines/stark/resources/item.cpp index e8e841b2659..2c75a081cb8 100644 --- a/engines/stark/resources/item.cpp +++ b/engines/stark/resources/item.cpp @@ -89,7 +89,7 @@ void Item::onGameLoop() { if (_enabled && _movement) { _movement->onGameLoop(); - if (_movement->hasEnded()) { + if (_movement && _movement->hasEnded()) { setMovement(nullptr); } } @@ -333,6 +333,10 @@ void ItemVisual::resetActionAnim() { } } +void ItemVisual::setPosition2D(const Common::Point &position) { + warning("ItemVisual::setPosition2D is not implemented for this item type: %d (%s)", _subType, _name.c_str()); +} + ItemTemplate::~ItemTemplate() { } @@ -594,7 +598,9 @@ FloorPositionedItem::~FloorPositionedItem() { FloorPositionedItem::FloorPositionedItem(Object *parent, byte subType, uint16 index, const Common::String &name) : ItemVisual(parent, subType, index, name), _direction3D(0.0), - _floorFaceIndex(-1) { + _floorFaceIndex(-1), + _sortKeyOverride(false), + _sortKeyOverridenValue(0.0) { } Math::Vector3d FloorPositionedItem::getPosition3D() const { @@ -611,6 +617,7 @@ int32 FloorPositionedItem::getFloorFaceIndex() const { void FloorPositionedItem::setFloorFaceIndex(int32 faceIndex) { _floorFaceIndex = faceIndex; + _sortKeyOverride = false; } void FloorPositionedItem::placeOnBookmark(Bookmark *target) { @@ -654,7 +661,16 @@ void FloorPositionedItem::setDirection(const Math::Angle &direction) { _direction3D = direction.getDegrees(0.0); } +void FloorPositionedItem::overrideSortKey(float sortKey) { + _sortKeyOverride = true; + _sortKeyOverridenValue = sortKey; +} + float FloorPositionedItem::getSortKey() const { + if (_sortKeyOverride) { + return _sortKeyOverridenValue; + } + Floor *floor = StarkGlobal->getCurrent()->getFloor(); if (_floorFaceIndex == -1) { @@ -705,6 +721,10 @@ Gfx::RenderEntry *FloorPositionedImageItem::getRenderEntry(const Common::Point & return _renderEntry; } +void FloorPositionedImageItem::setPosition2D(const Common::Point &position) { + _position = position; +} + void FloorPositionedImageItem::printData() { FloorPositionedItem::printData(); @@ -744,6 +764,10 @@ Gfx::RenderEntry *ImageItem::getRenderEntry(const Common::Point &positionOffset) return _renderEntry; } +void ImageItem::setPosition2D(const Common::Point &position) { + _position = position; +} + void ImageItem::printData() { ItemVisual::printData(); diff --git a/engines/stark/resources/item.h b/engines/stark/resources/item.h index e8d0ea7988b..ede934fc448 100644 --- a/engines/stark/resources/item.h +++ b/engines/stark/resources/item.h @@ -151,6 +151,13 @@ public: ItemVisual *getSceneInstance() override; void setAnimHierarchy(AnimHierarchy *animHierarchy) override; + /** + * Change the item's 2D position. + * + * Only applies to 2D items + */ + virtual void setPosition2D(const Common::Point &position); + /** Get the hotspot index for an item relative position */ int getHotspotIndexForPoint(const Common::Point &point); @@ -345,10 +352,20 @@ public: /** Obtain the sort value for the item, used to compute the draw order */ float getSortKey() const; + /** + * Don't rely on the floor face to compute the sort key, use the provided value instead. + * + * This can be used to handle cases where the item is not over the floor. + */ + void overrideSortKey(float sortKey); + protected: int32 _floorFaceIndex; Math::Vector3d _position3D; float _direction3D; + + bool _sortKeyOverride; + float _sortKeyOverridenValue; }; /** @@ -367,6 +384,9 @@ public: // Item API Gfx::RenderEntry *getRenderEntry(const Common::Point &positionOffset) override; + // ItemVisual API + void setPosition2D(const Common::Point &position) override; + protected: void printData() override; @@ -438,6 +458,9 @@ public: // Item API Gfx::RenderEntry *getRenderEntry(const Common::Point &positionOffset) override; + // ItemVisual API + void setPosition2D(const Common::Point &position) override; + protected: void printData() override; diff --git a/engines/stark/resources/path.cpp b/engines/stark/resources/path.cpp index 34b7c2e27a1..fbf3b783d7b 100644 --- a/engines/stark/resources/path.cpp +++ b/engines/stark/resources/path.cpp @@ -54,6 +54,46 @@ void Path::printData() { debug("field_30: %d", _field_30); } +float Path::getEdgeLength(uint edgeIndex) const { + Math::Vector3d edgeStart = getVertexPosition(edgeIndex); + Math::Vector3d edgeEnd = getVertexPosition(edgeIndex + 1); + + return edgeStart.getDistanceTo(edgeEnd); +} + +float Path::getWeightedEdgeLength(uint edgeIndex) const { + float length = getEdgeLength(edgeIndex); + float startWeight = getVertexWeight(edgeIndex); + float endWeight = getVertexWeight(edgeIndex + 1); + + return 2000.0 * length / (startWeight + endWeight); +} + +Math::Vector3d Path::getWeightedPositionInEdge(uint edgeIndex, float positionInEdge) { + float edgeLength = getEdgeLength(edgeIndex); + float weightedEdgeLength = getWeightedEdgeLength(edgeIndex); + + float startWeight = getVertexWeight(edgeIndex); + float endWeight = getVertexWeight(edgeIndex + 1); + + float weightedEdgePosition = ((endWeight - startWeight) / (2 * weightedEdgeLength) * positionInEdge + startWeight) * 0.001 + * positionInEdge / edgeLength; + + Math::Vector3d edgeStart = getVertexPosition(edgeIndex); + Math::Vector3d edgeEnd = getVertexPosition(edgeIndex + 1); + + return edgeEnd * weightedEdgePosition + edgeStart * (1.0 - weightedEdgePosition); +} + +float Path::getSortKey() const { + return 0; +} + +Math::Vector3d Path::getEdgeDirection(uint edgeIndex) const { + return Math::Vector3d(); +} + + Path2D::Path2D(Object *parent, byte subType, uint16 index, const Common::String &name) : Path(parent, subType, index, name) { } @@ -61,13 +101,13 @@ Path2D::Path2D(Object *parent, byte subType, uint16 index, const Common::String void Path2D::readData(Formats::XRCReadStream *stream) { Path::readData(stream); - uint32 stepCount = stream->readUint32LE(); - for (uint i = 0; i < stepCount; i++) { - Step step; - step.weight = stream->readFloat(); - step.position = stream->readPoint(); + uint32 vertexCount = stream->readUint32LE(); + for (uint i = 0; i < vertexCount; i++) { + Vertex vertex; + vertex.weight = stream->readFloat(); + vertex.position = stream->readPoint(); - _steps.push_back(step); + _vertices.push_back(vertex); } stream->readUint32LE(); // Unused in the original @@ -76,15 +116,28 @@ void Path2D::readData(Formats::XRCReadStream *stream) { void Path2D::printData() { Path::printData(); - for (uint i = 0; i < _steps.size(); i++) { - debug("step[%d]: (x %d, y %d), weight: %f", i, - _steps[i].position.x, _steps[i].position.y, _steps[i].weight); + for (uint i = 0; i < _vertices.size(); i++) { + debug("vertex[%d]: (x %d, y %d), weight: %f", i, + _vertices[i].position.x, _vertices[i].position.y, _vertices[i].weight); } } Path2D::~Path2D() { } +uint Path2D::getEdgeCount() const { + return _vertices.size() - 1; +} + +Math::Vector3d Path2D::getVertexPosition(uint vertexIndex) const { + Common::Point point = _vertices[vertexIndex].position; + return Math::Vector3d(point.x, point.y, 0.0); +} + +float Path2D::getVertexWeight(uint vertexIndex) const { + return _vertices[vertexIndex].weight; +} + Path3D::Path3D(Object *parent, byte subType, uint16 index, const Common::String &name) : Path(parent, subType, index, name), _sortKey(0) { @@ -93,13 +146,13 @@ Path3D::Path3D(Object *parent, byte subType, uint16 index, const Common::String void Path3D::readData(Formats::XRCReadStream *stream) { Path::readData(stream); - uint32 stepCount = stream->readUint32LE(); - for (uint i = 0; i < stepCount; i++) { - Step step; - step.weight = stream->readFloat(); - step.position = stream->readVector3(); + uint32 vertexCount = stream->readUint32LE(); + for (uint i = 0; i < vertexCount; i++) { + Vertex vertex; + vertex.weight = stream->readFloat(); + vertex.position = stream->readVector3(); - _steps.push_back(step); + _vertices.push_back(vertex); } _sortKey = stream->readFloat(); @@ -108,9 +161,9 @@ void Path3D::readData(Formats::XRCReadStream *stream) { void Path3D::printData() { Path::printData(); - for (uint i = 0; i < _steps.size(); i++) { - debug("step[%d]: (x %f, y %f, z %f), weight: %f", i, - _steps[i].position.x(), _steps[i].position.y(), _steps[i].position.z(), _steps[i].weight); + for (uint i = 0; i < _vertices.size(); i++) { + debug("vertex[%d]: (x %f, y %f, z %f), weight: %f", i, + _vertices[i].position.x(), _vertices[i].position.y(), _vertices[i].position.z(), _vertices[i].weight); } debug("sortKey: %f", _sortKey); @@ -119,5 +172,27 @@ void Path3D::printData() { Path3D::~Path3D() { } +uint Path3D::getEdgeCount() const { + return _vertices.size() - 1; +} + +Math::Vector3d Path3D::getVertexPosition(uint vertexIndex) const { + return _vertices[vertexIndex].position; +} + +float Path3D::getVertexWeight(uint vertexIndex) const { + return _vertices[vertexIndex].weight; +} + +float Path3D::getSortKey() const { + return _sortKey; +} + +Math::Vector3d Path3D::getEdgeDirection(uint edgeIndex) const { + Math::Vector3d direction = getVertexPosition(edgeIndex) - getVertexPosition(edgeIndex + 1); + direction.normalize(); + return direction; +} + } // End of namespace Resources } // End of namespace Stark diff --git a/engines/stark/resources/path.h b/engines/stark/resources/path.h index 37debdc9ba4..98767b0f860 100644 --- a/engines/stark/resources/path.h +++ b/engines/stark/resources/path.h @@ -37,6 +37,12 @@ class XRCReadStream; namespace Resources { +/** + * A path can be followed by an item in a location + * + * Path are made of a list of vertices. Two consecutive vertices delimit an edge. + * Each vertex has a weight. A higher weight means a higher movement speed. + */ class Path : public Object { public: static const Type::ResourceType TYPE = Type::kPath; @@ -55,18 +61,44 @@ public: // Resource API virtual void readData(Formats::XRCReadStream *stream) override; + /** Get the edge count in the path */ + virtual uint getEdgeCount() const = 0; + + /** + * Get a unit vector pointing in the direction of an edge + * + * Only valid for 3D paths + */ + virtual Math::Vector3d getEdgeDirection(uint edgeIndex) const; + + /** Get the sort key to be used by the item following the path */ + virtual float getSortKey() const; + + /** Get an edge's length */ + float getWeightedEdgeLength(uint edgeIndex) const; + + /** Get the scene position from a position in an edge */ + Math::Vector3d getWeightedPositionInEdge(uint edgeIndex, float positionInEdge); + protected: void printData() override; + float getEdgeLength(uint edgeIndex) const; + virtual float getVertexWeight(uint vertexIndex) const = 0; + virtual Math::Vector3d getVertexPosition(uint vertexIndex) const = 0; uint32 _field_30; + }; +/** + * A 2D path for 2D items + */ class Path2D : public Path { public: Path2D(Object *parent, byte subType, uint16 index, const Common::String &name); virtual ~Path2D(); - struct Step { + struct Vertex { float weight; Common::Point position; }; @@ -74,19 +106,29 @@ public: // Resource API virtual void readData(Formats::XRCReadStream *stream) override; + // Path API + uint getEdgeCount() const override; + +protected: + float getVertexWeight(uint vertexIndex) const override; + Math::Vector3d getVertexPosition(uint vertexIndex) const override; + private: // Resource API - void printData(); + void printData() override; - Common::Array _steps; + Common::Array _vertices; }; +/** + * A 3D path for 3D items + */ class Path3D : public Path { public: Path3D(Object *parent, byte subType, uint16 index, const Common::String &name); virtual ~Path3D(); - struct Step { + struct Vertex { float weight; Math::Vector3d position; }; @@ -94,11 +136,20 @@ public: // Resource API virtual void readData(Formats::XRCReadStream *stream) override; + // Path API + uint getEdgeCount() const override; + float getSortKey() const override; + Math::Vector3d getEdgeDirection(uint edgeIndex) const override; + +protected: + float getVertexWeight(uint vertexIndex) const override; + Math::Vector3d getVertexPosition(uint vertexIndex) const override; + private: // Resource API - void printData(); + void printData() override; - Common::Array _steps; + Common::Array _vertices; float _sortKey; };