STARK: Add a movement type allowing an item to follow a path

This commit is contained in:
Bastien Bouclet 2015-09-23 21:21:53 +02:00
parent c733a5ab59
commit b048c4ae86
8 changed files with 422 additions and 27 deletions

View File

@ -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<Resources::FloorPositionedItem>(_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

View File

@ -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 <engines/stark/resources/path.h>
#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

View File

@ -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<ItemVisual>();
Path *path = pathRef.resolve<Path>();
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<FloorPositionedItem>();
Math::Vector3d currentPosition = item->getPosition3D();

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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<Step> _steps;
Common::Array<Vertex> _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<Step> _steps;
Common::Array<Vertex> _vertices;
float _sortKey;
};