TWP: Add path finding

This commit is contained in:
scemino 2024-01-01 10:19:20 +01:00 committed by Eugene Sandulenko
parent e88c12424c
commit 4e67d24e04
19 changed files with 5791 additions and 47 deletions

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.5)
project(clipper CXX)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
set(CLIPPER_SRC clipper.cpp)
add_library(clipper STATIC ${CLIPPER_SRC})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,403 @@
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.4.2 *
* Date : 27 February 2017 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2017 *
* *
* License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt *
* *
* Attributions: *
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
* "A generic solution to polygon clipping" *
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
* http://portal.acm.org/citation.cfm?id=129906 *
* *
* Computer graphics and geometric modeling: implementation and algorithms *
* By Max K. Agoston *
* Springer; 1 edition (January 4, 2005) *
* http://books.google.com/books?q=vatti+clipping+agoston *
* *
* See also: *
* "Polygon Offsetting by Computing Winding Numbers" *
* Paper no. DETC2005-85513 pp. 565-575 *
* ASME 2005 International Design Engineering Technical Conferences *
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
* September 24-28, 2005 , Long Beach, California, USA *
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
* *
*******************************************************************************/
#ifndef clipper_hpp
#define clipper_hpp
#define CLIPPER_VERSION "6.4.2"
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
//improve performance but coordinate values are limited to the range +/- 46340
//#define use_int32
//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
//#define use_xyz
//use_lines: Enables line clipping. Adds a very minor cost to performance.
#define use_lines
//use_deprecated: Enables temporary support for the obsolete functions
//#define use_deprecated
#include <vector>
#include <list>
#include <set>
#include <stdexcept>
#include <cstring>
#include <cstdlib>
#include <ostream>
#include <functional>
#include <queue>
namespace ClipperLib {
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
enum PolyType { ptSubject, ptClip };
//By far the most widely used winding rules for polygon filling are
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
//see http://glprogramming.com/red/chapter11.html
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
#ifdef use_int32
typedef int cInt;
static cInt const loRange = 0x7FFF;
static cInt const hiRange = 0x7FFF;
#else
typedef signed long long cInt;
static cInt const loRange = 0x3FFFFFFF;
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
typedef signed long long long64; //used by Int128 class
typedef unsigned long long ulong64;
#endif
struct IntPoint {
cInt X;
cInt Y;
#ifdef use_xyz
cInt Z;
IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
#else
IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y) {};
#endif
friend inline bool operator==(const IntPoint &a, const IntPoint &b) {
return a.X == b.X && a.Y == b.Y;
}
friend inline bool operator!=(const IntPoint &a, const IntPoint &b) {
return a.X != b.X || a.Y != b.Y;
}
};
//------------------------------------------------------------------------------
typedef std::vector<IntPoint> Path;
typedef std::vector<Path> Paths;
inline Path &operator<<(Path &poly, const IntPoint &p) {
poly.push_back(p);
return poly;
}
inline Paths &operator<<(Paths &polys, const Path &p) {
polys.push_back(p);
return polys;
}
std::ostream &operator<<(std::ostream &s, const IntPoint &p);
std::ostream &operator<<(std::ostream &s, const Path &p);
std::ostream &operator<<(std::ostream &s, const Paths &p);
struct DoublePoint {
double X;
double Y;
DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
DoublePoint(IntPoint ip) : X((double) ip.X), Y((double) ip.Y) {}
};
//------------------------------------------------------------------------------
#ifdef use_xyz
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
#endif
enum InitOptions { ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4 };
enum JoinType { jtSquare, jtRound, jtMiter };
enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound };
class PolyNode;
typedef std::vector<PolyNode *> PolyNodes;
class PolyNode {
public:
PolyNode();
virtual ~PolyNode() {};
Path Contour;
PolyNodes Childs;
PolyNode *Parent;
PolyNode *GetNext() const;
bool IsHole() const;
bool IsOpen() const;
int ChildCount() const;
private:
//PolyNode& operator =(PolyNode& other);
unsigned Index; //node index in Parent.Childs
bool m_IsOpen;
JoinType m_jointype;
EndType m_endtype;
PolyNode *GetNextSiblingUp() const;
void AddChild(PolyNode &child);
friend class Clipper; //to access Index
friend class ClipperOffset;
};
class PolyTree : public PolyNode {
public:
~PolyTree() { Clear(); };
PolyNode *GetFirst() const;
void Clear();
int Total() const;
private:
//PolyTree& operator =(PolyTree& other);
PolyNodes AllNodes;
friend class Clipper; //to access AllNodes
};
bool Orientation(const Path &poly);
double Area(const Path &poly);
int PointInPolygon(const IntPoint &pt, const Path &path);
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415);
void CleanPolygon(Path &poly, double distance = 1.415);
void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance = 1.415);
void CleanPolygons(Paths &polys, double distance = 1.415);
void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, bool pathIsClosed);
void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, bool pathIsClosed);
void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution);
void PolyTreeToPaths(const PolyTree &polytree, Paths &paths);
void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths);
void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths);
void ReversePath(Path &p);
void ReversePaths(Paths &p);
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
//enums that are used internally ...
enum EdgeSide { esLeft = 1, esRight = 2 };
//forward declarations (for stuff used internally) ...
struct TEdge;
struct IntersectNode;
struct LocalMinimum;
struct OutPt;
struct OutRec;
struct Join;
typedef std::vector<OutRec *> PolyOutList;
typedef std::vector<TEdge *> EdgeList;
typedef std::vector<Join *> JoinList;
typedef std::vector<IntersectNode *> IntersectList;
//------------------------------------------------------------------------------
//ClipperBase is the ancestor to the Clipper class. It should not be
//instantiated directly. This class simply abstracts the conversion of sets of
//polygon coordinates into edge objects that are stored in a LocalMinima list.
class ClipperBase {
public:
ClipperBase();
virtual ~ClipperBase();
virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
virtual void Clear();
IntRect GetBounds();
bool PreserveCollinear() { return m_PreserveCollinear; };
void PreserveCollinear(bool value) { m_PreserveCollinear = value; };
protected:
void DisposeLocalMinimaList();
TEdge *AddBoundsToLML(TEdge *e, bool IsClosed);
virtual void Reset();
TEdge *ProcessBound(TEdge *E, bool IsClockwise);
void InsertScanbeam(const cInt Y);
bool PopScanbeam(cInt &Y);
bool LocalMinimaPending();
bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
OutRec *CreateOutRec();
void DisposeAllOutRecs();
void DisposeOutRec(PolyOutList::size_type index);
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
void DeleteFromAEL(TEdge *e);
void UpdateEdgeIntoAEL(TEdge *&e);
typedef std::vector<LocalMinimum> MinimaList;
MinimaList::iterator m_CurrentLM;
MinimaList m_MinimaList;
bool m_UseFullRange;
EdgeList m_edges;
bool m_PreserveCollinear;
bool m_HasOpenPaths;
PolyOutList m_PolyOuts;
TEdge *m_ActiveEdges;
typedef std::priority_queue<cInt> ScanbeamList;
ScanbeamList m_Scanbeam;
};
//------------------------------------------------------------------------------
class Clipper : public virtual ClipperBase {
public:
Clipper(int initOptions = 0);
bool Execute(ClipType clipType,
Paths &solution,
PolyFillType fillType = pftEvenOdd);
bool Execute(ClipType clipType,
Paths &solution,
PolyFillType subjFillType,
PolyFillType clipFillType);
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType fillType = pftEvenOdd);
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType subjFillType,
PolyFillType clipFillType);
bool ReverseSolution() { return m_ReverseOutput; };
void ReverseSolution(bool value) { m_ReverseOutput = value; };
bool StrictlySimple() { return m_StrictSimple; };
void StrictlySimple(bool value) { m_StrictSimple = value; };
//set the callback function for z value filling on intersections (otherwise Z is 0)
#ifdef use_xyz
void ZFillFunction(ZFillCallback zFillFunc);
#endif
protected:
virtual bool ExecuteInternal();
private:
JoinList m_Joins;
JoinList m_GhostJoins;
IntersectList m_IntersectList;
ClipType m_ClipType;
typedef std::list<cInt> MaximaList;
MaximaList m_Maxima;
TEdge *m_SortedEdges;
bool m_ExecuteLocked;
PolyFillType m_ClipFillType;
PolyFillType m_SubjFillType;
bool m_ReverseOutput;
bool m_UsingPolyTree;
bool m_StrictSimple;
#ifdef use_xyz
ZFillCallback m_ZFill; //custom callback
#endif
void SetWindingCount(TEdge &edge);
bool IsEvenOddFillType(const TEdge &edge) const;
bool IsEvenOddAltFillType(const TEdge &edge) const;
void InsertLocalMinimaIntoAEL(const cInt botY);
void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge);
void AddEdgeToSEL(TEdge *edge);
bool PopEdgeFromSEL(TEdge *&edge);
void CopyAELToSEL();
void DeleteFromSEL(TEdge *e);
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
bool IsContributing(const TEdge &edge) const;
bool IsTopHorz(const cInt XPos);
void DoMaxima(TEdge *e);
void ProcessHorizontals();
void ProcessHorizontal(TEdge *horzEdge);
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutRec *GetOutRec(int idx);
void AppendPolygon(TEdge *e1, TEdge *e2);
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
OutPt *AddOutPt(TEdge *e, const IntPoint &pt);
OutPt *GetLastOutPt(TEdge *e);
bool ProcessIntersections(const cInt topY);
void BuildIntersectList(const cInt topY);
void ProcessIntersectList();
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
void BuildResult(Paths &polys);
void BuildResult2(PolyTree &polytree);
void SetHoleState(TEdge *e, OutRec *outrec);
void DisposeIntersectNodes();
bool FixupIntersectionOrder();
void FixupOutPolygon(OutRec &outrec);
void FixupOutPolyline(OutRec &outrec);
bool IsHole(TEdge *e);
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
void FixHoleLinkage(OutRec &outrec);
void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
void ClearJoins();
void ClearGhostJoins();
void AddGhostJoin(OutPt *op, const IntPoint offPt);
bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2);
void JoinCommonEdges();
void DoSimplePolygons();
void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec);
void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec);
void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec);
#ifdef use_xyz
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
#endif
};
//------------------------------------------------------------------------------
class ClipperOffset {
public:
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
~ClipperOffset();
void AddPath(const Path &path, JoinType joinType, EndType endType);
void AddPaths(const Paths &paths, JoinType joinType, EndType endType);
void Execute(Paths &solution, double delta);
void Execute(PolyTree &solution, double delta);
void Clear();
double MiterLimit;
double ArcTolerance;
private:
Paths m_destPolys;
Path m_srcPoly;
Path m_destPoly;
std::vector<DoublePoint> m_normals;
double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad;
IntPoint m_lowest;
PolyNode m_polyNodes;
void FixOrientations();
void DoOffset(double delta);
void OffsetPoint(int j, int &k, JoinType jointype);
void DoSquare(int j, int k);
void DoMiter(int j, int k, double r);
void DoRound(int j, int k);
};
//------------------------------------------------------------------------------
class clipperException : public std::exception {
public:
clipperException(const char *description) : m_descr(description) {}
virtual ~clipperException() throw() {}
virtual const char *what() const throw() { return m_descr.c_str(); }
private:
std::string m_descr;
};
//------------------------------------------------------------------------------
} //ClipperLib namespace
#endif //clipper_hpp

View File

@ -250,6 +250,11 @@ void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
drawPrimitives(GL_LINE_STRIP, vertices, count, trsf);
}
void Gfx::drawLinesLoop(Vertex *vertices, int count, Math::Matrix4 trsf) {
noTexture();
drawPrimitives(GL_LINE_LOOP, vertices, count, trsf);
}
void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture *texture) {
if (v_size > 0) {
_texture = texture ? texture : &gEmptyTexture;

View File

@ -162,6 +162,7 @@ public:
void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
void drawLines(Vertex *vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
void drawLinesLoop(Vertex *vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
void draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf = Math::Matrix4(), Texture *texture = NULL);
void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
void drawSprite(Common::Rect textRect, Texture &texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);

504
engines/twp/graph.cpp Normal file
View File

@ -0,0 +1,504 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "twp/graph.h"
#include "twp/util.h"
#define EPSILON 1e-9
namespace Twp {
struct Segment {
Segment(Math::Vector2d s, Math::Vector2d t);
void normalize();
float distance(Math::Vector2d p);
Math::Vector2d start, to;
float left, right, top, bottom;
float a, b, c;
};
Segment::Segment(Math::Vector2d s, Math::Vector2d t) {
start = s;
to = t;
left = MIN(s.getX(), t.getX());
right = MAX(s.getX(), t.getX());
top = MIN(s.getY(), t.getY());
bottom = MAX(s.getY(), t.getY());
a = s.getY() - t.getY();
b = t.getX() - s.getX();
c = -a * s.getX() - b * s.getY();
normalize();
}
void Segment::normalize() {
float z = sqrt(a * a + b * b);
if (abs(z) > EPSILON) {
a /= z;
b /= z;
c /= z;
}
}
float Segment::distance(Math::Vector2d p) {
return a * p.getX() + b * p.getY() + c;
}
IndexedPriorityQueue::IndexedPriorityQueue(Common::Array<float> &keys)
: _keys(keys) {
}
void IndexedPriorityQueue::insert(int index) {
_data.push_back(index);
reorderUp();
}
int IndexedPriorityQueue::pop() {
int r = _data[0];
_data[0] = _data[_data.size() - 1];
_data.pop_back();
reorderDown();
return r;
}
void IndexedPriorityQueue::reorderUp() {
if (_data.empty())
return;
size_t a = _data.size() - 1;
while (a > 0) {
if (_keys[_data[a]] >= _keys[_data[a - 1]])
return;
int tmp = _data[a];
_data[a] = _data[a - 1];
_data[a - 1] = tmp;
a--;
}
}
void IndexedPriorityQueue::reorderDown() {
if (_data.empty())
return;
for (int a = 0; a < static_cast<int>(_data.size() - 1); a++) {
if (_keys[_data[a]] <= _keys[_data[a + 1]])
return;
int tmp = _data[a];
_data[a] = _data[a + 1];
_data[a + 1] = tmp;
}
}
bool IndexedPriorityQueue::isEmpty() {
return _data.empty();
}
Graph::Graph() {}
Graph::Graph(const Graph &graph) {
_nodes = graph._nodes;
_concaveVertices = graph._concaveVertices;
for (int i = 0; i < graph._edges.size(); i++) {
const Common::Array<GraphEdge> &e = graph._edges[i];
Common::Array<GraphEdge> sEdges;
for (int j = 0; j < e.size(); j++) {
const GraphEdge &se = e[j];
sEdges.push_back(GraphEdge(se.start, se.to, se.cost));
}
_edges.push_back(sEdges);
}
}
GraphEdge::GraphEdge(int s, int t, float c)
: start(s), to(t), cost(c) {
}
void Graph::addNode(Math::Vector2d node) {
_nodes.push_back(node);
_edges.push_back(Common::Array<GraphEdge>());
}
AStar::AStar(Graph *graph)
: _fCost(graph->_nodes.size()), _gCost(graph->_nodes.size()), _spt(graph->_nodes.size()), _sf(graph->_nodes.size()) {
_graph = graph;
}
// TODO this really should have some simd optimization
// matrix multiplication is based on this
static float dot(Math::Vector2d u, Math::Vector2d v) {
float result = 0.f;
result += u.getX() * v.getX();
result += u.getY() * v.getY();
return result;
}
static float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
void AStar::search(int source, int target) {
IndexedPriorityQueue pq(_fCost);
pq.insert(source);
while (!pq.isEmpty()) {
int NCN = pq.pop();
_spt[NCN] = _sf[NCN];
if (NCN != target) {
// for (edge in _graph->edges[NCN]) {
for (int i = 0; i < _graph->_edges[NCN].size(); i++) {
GraphEdge &edge = _graph->_edges[NCN][i];
float Hcost = length(_graph->_nodes[edge.to] - _graph->_nodes[target]);
float Gcost = _gCost[NCN] + edge.cost;
if (!_sf[edge.to]) {
_fCost[edge.to] = Gcost + Hcost;
_gCost[edge.to] = Gcost;
pq.insert(edge.to);
_sf[edge.to] = &edge;
} else if (Gcost < _gCost[edge.to] && !_spt[edge.to]) {
_fCost[edge.to] = Gcost + Hcost;
_gCost[edge.to] = Gcost;
pq.reorderUp();
_sf[edge.to] = &edge;
}
}
}
}
}
void Graph::addEdge(GraphEdge e) {
if (!edge(e.start, e.to)) {
_edges[e.start].push_back(e);
}
if (!edge(e.to, e.start)) {
GraphEdge e2(e.to, e.start, e.cost);
_edges[e.to].push_back(e);
}
}
GraphEdge *Graph::edge(int start, int to) {
Common::Array<GraphEdge> &edges = _edges[start];
for (int i = 0; i < edges.size(); i++) {
GraphEdge *e = &edges[i];
if (e->to == to)
return e;
}
return nullptr;
}
Common::Array<int> reverse(const Common::Array<int> &arr) {
Common::Array<int> result(arr.size());
for (int i = 0; i < arr.size(); i++) {
result[arr.size() - 1 - i] = arr[i];
}
return result;
}
Common::Array<int> Graph::getPath(int source, int target) {
Common::Array<int> result;
AStar astar(this);
if (target >= 0) {
astar.search(source, target);
int nd = target;
result.push_back(nd);
while ((nd != source) && (astar._spt[nd] != nullptr)) {
nd = astar._spt[nd]->start;
result.push_back(nd);
}
return reverse(result);
}
return result;
}
void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
_walkboxes = walkboxes;
}
// Indicates whether or not the specified position is inside this walkbox.
static bool inside(const Walkbox &self, Math::Vector2d position, bool toleranceOnOutside = true) {
bool result = false;
Math::Vector2d point = position;
const float epsilon = 1.0f;
// Must have 3 or more edges
const Common::Array<Math::Vector2d> &polygon = self.getPoints();
if (polygon.size() < 3)
return false;
Math::Vector2d oldPoint(polygon[polygon.size() - 1]);
float oldSqDist = distanceSquared(oldPoint, point);
for (int i = 0; i < polygon.size(); i++) {
Math::Vector2d newPoint = polygon[i];
float newSqDist = distanceSquared(newPoint, point);
if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
return toleranceOnOutside;
Math::Vector2d left;
Math::Vector2d right;
if (newPoint.getX() > oldPoint.getX()) {
left = oldPoint;
right = newPoint;
} else {
left = newPoint;
right = oldPoint;
}
if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX()) < (right.getY() - left.getY()) * (point.getX() - left.getX())))
result = !result;
oldPoint = newPoint;
oldSqDist = newSqDist;
}
return result;
}
Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
int vi1 = -1;
int vi2 = -1;
float minDist = 100000.0f;
const Common::Array<Math::Vector2d> &polygon = getPoints();
for (int i = 0; i < polygon.size(); i++) {
float dist = distanceToSegment(p3, polygon[i], polygon[(i + 1) % polygon.size()]);
if (dist < minDist) {
minDist = dist;
vi1 = i;
vi2 = (i + 1) % polygon.size();
}
}
Math::Vector2d p1 = polygon[vi1];
Math::Vector2d p2 = polygon[vi2];
float x1 = p1.getX();
float y1 = p1.getY();
float x2 = p2.getX();
float y2 = p2.getY();
float x3 = p3.getX();
float y3 = p3.getY();
float u = (((x3 - x1) * (x2 - x1)) + ((y3 - y1) * (y2 - y1))) / (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
float xu = x1 + u * (x2 - x1);
float yu = y1 + u * (y2 - y1);
if (u < 0)
return Math::Vector2d(x1, y1);
if (u > 1)
return Math::Vector2d(x2, y2);
return Math::Vector2d(xu, yu);
}
static bool less(Math::Vector2d p1, Math::Vector2d p2) {
return ((p1.getX() < p2.getX() - EPSILON) || (abs(p1.getX() - p2.getX()) < EPSILON) && (p1.getY() < p2.getY() - EPSILON));
}
static float det(float a, float b, float c, float d) {
return a * d - b * c;
}
static bool betw(float l, float r, float x) {
return (MIN(l, r) <= x + EPSILON) && (x <= MAX(l, r) + EPSILON);
}
static bool intersect_1d(float a, float b, float c, float d) {
float a2 = a;
float b2 = b;
float c2 = c;
float d2 = d;
if (a2 > b2)
SWAP(a2, b2);
if (c2 > d2)
SWAP(c2, d2);
return MAX(a2, c2) <= MIN(b2, d2) + EPSILON;
}
static bool lineSegmentsCross(Math::Vector2d a1, Math::Vector2d b1, Math::Vector2d c1, Math::Vector2d d1) {
Math::Vector2d a = a1;
Math::Vector2d b = b1;
Math::Vector2d c = c1;
Math::Vector2d d = d1;
if ((!intersect_1d(a.getX(), b.getX(), c.getX(), d.getX())) || (!intersect_1d(a.getY(), b.getY(), c.getY(), d.getY())))
return false;
Segment m(a, b);
Segment n(c, d);
float zn = det(m.a, m.b, n.a, n.b);
if (abs(zn) < EPSILON) {
if ((abs(m.distance(c)) > EPSILON) || (abs(n.distance(a)) > EPSILON))
return false;
if (less(b, a))
SWAP(a, b);
if (less(d, c))
SWAP(c, d);
return true;
}
float lx = -det(m.c, m.b, n.c, n.b) / zn;
float ly = -det(m.a, m.c, n.a, n.c) / zn;
return betw(a.getX(), b.getX(), lx) && betw(a.getY(), b.getY(), ly) && betw(c.getX(), d.getX(), lx) && betw(c.getY(), d.getY(), ly);
}
bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
const float epsilon = 0.5f;
// Not in LOS if any of the ends is outside the polygon
if (!_walkboxes[0].contains(start) || !_walkboxes[0].contains(to))
return false;
// In LOS if it's the same start and end location
if (length(start - to) < epsilon)
return true;
// Not in LOS if any edge is intersected by the start-end line segment
for (int i = 0; i < _walkboxes.size(); i++) {
Walkbox &walkbox = _walkboxes[i];
const Common::Array<Math::Vector2d> &polygon = walkbox.getPoints();
int size = polygon.size();
for (int j = 0; j < size; j++) {
Math::Vector2d v1 = polygon[j];
Math::Vector2d v2 = polygon[(j + 1) % size];
if (!lineSegmentsCross(start, to, v1, v2))
continue;
// In some cases a 'snapped' endpoint is just a little over the line due to rounding errors. So a 0.5 margin is used to tackle those cases.
if ((distanceToSegment(start, v1, v2) > epsilon) && (distanceToSegment(to, v1, v2) > epsilon))
return false;
}
}
// Finally the middle point in the segment determines if in LOS or not
Math::Vector2d v2 = (start + to) / 2.0f;
bool result = _walkboxes[0].contains(v2);
for (int i = 1; i < _walkboxes.size(); i++) {
if (_walkboxes[i].contains(v2, false))
result = false;
}
return result;
}
static int minIndex(const Common::Array<float> values) {
float min = values[0];
int index = 0;
for (int i = 1; i < values.size(); i++) {
if (values[i] < min) {
index = i;
min = values[i];
}
}
return index;
}
Graph *PathFinder::createGraph() {
Graph *result = new Graph();
for (int i = 0; i < _walkboxes.size(); i++) {
Walkbox &walkbox = _walkboxes[i];
if (walkbox.getPoints().size() > 2) {
bool visible = walkbox.isVisible();
for (int j = 0; j < walkbox.getPoints().size(); j++) {
if (walkbox.concave(j) == visible) {
Math::Vector2d vertex = walkbox.getPoints()[j];
result->_concaveVertices.push_back(vertex);
result->addNode(vertex);
}
}
}
}
for (int i = 0; i < result->_concaveVertices.size(); i++) {
for (int j = 0; j < result->_concaveVertices.size(); j++) {
Math::Vector2d c1(result->_concaveVertices[i]);
Math::Vector2d c2(result->_concaveVertices[j]);
if (inLineOfSight(c1, c2)) {
float d = distance(c1, c2);
result->addEdge(GraphEdge(i, j, d));
}
}
}
return result;
}
Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Math::Vector2d to) {
Common::Array<Math::Vector2d> result;
if (_walkboxes.size() > 0) {
// find the walkbox where the actor is and put it first
for (int i = 0; i < _walkboxes.size(); i++) {
const Walkbox &wb = _walkboxes[i];
if (inside(wb, start) && (i != 0)) {
SWAP(_walkboxes[0], _walkboxes[i]);
break;
}
}
// if no walkbox has been found => find the nearest walkbox
if (!inside(_walkboxes[0], start)) {
Common::Array<float> dists(_walkboxes.size());
for (int i = 0; i < _walkboxes.size(); i++) {
Walkbox wb = _walkboxes[i];
dists[i] = distance(wb.getClosestPointOnEdge(start), start);
}
int index = minIndex(dists);
if (index != 0)
SWAP(_walkboxes[0], _walkboxes[index]);
}
if (!_graph)
_graph = createGraph();
// create new node on start position
Graph *walkgraph = new Graph(*_graph);
int startNodeIndex = walkgraph->_nodes.size();
// if destination is not inside current walkable area, then get the closest point
const Walkbox &wb = _walkboxes[0];
if (wb.isVisible() && !wb.contains(to))
to = wb.getClosestPointOnEdge(to);
walkgraph->addNode(start);
for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
Math::Vector2d c = walkgraph->_concaveVertices[i];
if (inLineOfSight(start, c))
walkgraph->addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
}
// create new node on end position
int endNodeIndex = walkgraph->_nodes.size();
walkgraph->addNode(to);
for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
Math::Vector2d c = walkgraph->_concaveVertices[i];
if (inLineOfSight(to, c))
walkgraph->addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
}
if (inLineOfSight(start, to))
walkgraph->addEdge(GraphEdge(startNodeIndex, endNodeIndex, distance(start, to)));
Common::Array<int> indices = walkgraph->getPath(startNodeIndex, endNodeIndex);
for (int i = 0; i < indices.size(); i++) {
int index = indices[i];
result.push_back(walkgraph->_nodes[index]);
}
}
return result;
}
} // namespace Twp

131
engines/twp/graph.h Normal file
View File

@ -0,0 +1,131 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWP_GRAPH_H
#define TWP_GRAPH_H
#include "common/array.h"
#include "math/vector2d.h"
namespace Twp {
class IndexedPriorityQueue {
public:
explicit IndexedPriorityQueue(Common::Array<float> &keys);
void insert(int index);
int pop();
void reorderUp();
void reorderDown();
bool isEmpty();
private:
Common::Array<float> &_keys;
Common::Array<int> _data;
};
// An edge is a part of a walkable area, it is used by a Graph.
// See also:
// - PathFinder
// - Graph
struct GraphEdge {
GraphEdge(int start, int to, float cost);
int start; // Index of the node in the graph representing the start of the edge.
int to; // Index of the node in the graph representing the end of the edge.
float cost; // Cost of the edge in the graph.
};
// A graph helps to find a path between two points.
// This class has been ported from http://www.groebelsloot.com/2016/03/13/pathfinding-part-2/
// and modified
class Graph {
public:
Graph();
Graph(const Graph &graph);
void addNode(Math::Vector2d node);
void addEdge(GraphEdge edge);
// Gets the edge from 'from' index to 'to' index.
GraphEdge *edge(int start, int to);
Common::Array<int> getPath(int source, int target);
Common::Array<Math::Vector2d> _nodes;
Common::Array<Common::Array<GraphEdge> > _edges;
Common::Array<Math::Vector2d> _concaveVertices;
};
class AStar {
public:
AStar(Graph *graph);
void search(int source, int target);
Graph *_graph = nullptr;
Common::Array<GraphEdge *> _spt; // The Shortest Path Tree
Common::Array<float> _gCost; // This array will store the G cost of each node
Common::Array<float> _fCost; // This array will store the F cost of each node
Common::Array<GraphEdge*> _sf; // The Search Frontier
};
// Represents an area where an actor can or cannot walk
class Walkbox {
public:
Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
// Indicates whether or not the specified position is inside this walkbox.
bool contains(Math::Vector2d position, bool toleranceOnOutside = true) const;
bool concave(int vertex) const;
void setVisible(bool visible) { _visible = visible; }
bool isVisible() const { return _visible; }
const Common::Array<Math::Vector2d>& getPoints() const { return _polygon; }
Math::Vector2d getClosestPointOnEdge(Math::Vector2d p3) const;
public:
Common::String _name;
private:
Common::Array<Math::Vector2d> _polygon;
bool _visible;
};
// A PathFinder is used to find a walkable path within one or several walkboxes.
class PathFinder {
public:
void setWalkboxes(const Common::Array<Walkbox> &walkboxes);
Common::Array<Math::Vector2d> calculatePath(Math::Vector2d start, Math::Vector2d to);
void setDirty(bool dirty) { _isDirty = dirty; }
bool isDirty() const { return _isDirty; }
const Graph* getGraph() const { return _graph; }
private:
Graph *createGraph();
bool inLineOfSight(Math::Vector2d start, Math::Vector2d to);
private:
Common::Array<Walkbox> _walkboxes;
Graph *_graph = nullptr;
bool _isDirty = true;
};
} // namespace Twp
#endif

View File

@ -21,8 +21,11 @@ SQUIRREL_OBJS = \
squirrel/sqstdrex.o \
squirrel/sqstdaux.o
CLIPPER_OBJS = clipper/clipper.o
MODULE_OBJS = \
$(SQUIRREL_OBJS) \
$(CLIPPER_OBJS) \
twp.o \
console.o \
metaengine.o \
@ -58,6 +61,8 @@ MODULE_OBJS = \
hud.o \
lip.o \
callback.o \
graph.o \
walkboxnode.o \
# This module can be built as a plugin
ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)

View File

@ -208,6 +208,8 @@ public:
WalkTo(Object *obj, Math::Vector2d dest, int facing = 0);
virtual void disable() override;
const Common::Array<Math::Vector2d>& getPath() const { return _path; }
private:
void actorArrived();
virtual void update(float elapsed) override;

View File

@ -19,6 +19,8 @@
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "twp/twp.h"
#include "twp/room.h"
#include "twp/ggpack.h"
@ -28,6 +30,7 @@
#include "twp/ids.h"
#include "twp/object.h"
#include "twp/util.h"
#include "twp/clipper/clipper.hpp"
#include "common/algorithm.h"
namespace Twp {
@ -94,6 +97,50 @@ static Scaling parseScaling(const Common::JSONArray &jScalings) {
return result;
}
static ClipperLib::Path toPolygon(const Walkbox &walkbox) {
ClipperLib::Path path;
const Common::Array<Math::Vector2d> &points = walkbox.getPoints();
for (int i = 0; i < points.size(); i++) {
path.push_back(ClipperLib::IntPoint(points[i].getX(), points[i].getY()));
}
return path;
}
static Walkbox toWalkbox(const ClipperLib::Path &path) {
Common::Array<Math::Vector2d> pts;
for (int i = 0; i < path.size(); i++) {
const ClipperLib::IntPoint &pt = path[i];
pts.push_back(Math::Vector2d(pt.X, pt.Y));
}
return Walkbox(pts, ClipperLib::Orientation(path));
}
static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
Common::Array<Walkbox> result;
if (walkboxes.size() > 0) {
ClipperLib::Paths subjects, clips;
for (int i = 0; i < walkboxes.size(); i++) {
const Walkbox &wb = walkboxes[i];
if (wb.isVisible()) {
subjects.push_back(toPolygon(wb));
} else {
clips.push_back(toPolygon(wb));
}
}
ClipperLib::Paths solutions;
ClipperLib::Clipper c;
c.AddPaths(subjects, ClipperLib::ptSubject, true);
c.AddPaths(clips, ClipperLib::ptClip, true);
c.Execute(ClipperLib::ClipType::ctUnion, solutions, ClipperLib::pftEvenOdd);
for (int i = 0; i < solutions.size(); i++) {
result.push_back(toWalkbox(solutions[i]));
}
}
return result;
}
Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
setId(_table, newRoomId());
_name = name;
@ -321,6 +368,8 @@ void Room::load(Common::SeekableReadStream &s) {
_scaling = _scalings[0];
}
_mergedPolygon = merge(_walkboxes);
delete value;
}
@ -409,8 +458,28 @@ void Room::update(float elapsed) {
}
}
void Room::walkboxHidden(const Common::String &name, bool hidden) {
for (int i = 0; i < _walkboxes.size(); i++) {
Walkbox &wb = _walkboxes[i];
if (wb._name == name) {
wb.setVisible(!hidden);
// 1 walkbox has change so update merged polygon
_mergedPolygon = merge(_walkboxes);
_pathFinder.setDirty(true);
return;
}
}
}
Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
return {frm, to};
if (_mergedPolygon.size() > 0) {
if (_pathFinder.isDirty()) {
_pathFinder.setWalkboxes(_mergedPolygon);
_pathFinder.setDirty(false);
}
return _pathFinder.calculatePath(frm, to);
}
return {};
}
Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
@ -429,6 +498,56 @@ Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
: _polygon(polygon), _visible(visible) {
}
bool Walkbox::concave(int vertex) const {
Math::Vector2d current = _polygon[vertex];
Math::Vector2d next = _polygon[(vertex + 1) % _polygon.size()];
Math::Vector2d previous = _polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
Math::Vector2d left(current.getX() - previous.getX(), current.getY() - previous.getY());
Math::Vector2d right(next.getX() - current.getX(), next.getY() - current.getY());
float cross = (left.getX() * right.getY()) - (left.getY() * right.getX());
return _visible ? cross < 0 : cross >= 0;
}
bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
Math::Vector2d point = position;
const float epsilon = 1.0f;
bool result = false;
// Must have 3 or more edges
if (_polygon.size() < 3)
return false;
Math::Vector2d oldPoint(_polygon[_polygon.size() - 1]);
float oldSqDist = distanceSquared(oldPoint, point);
for (int i = 0; i < _polygon.size(); i++) {
Math::Vector2d newPoint = _polygon[i];
float newSqDist = distanceSquared(newPoint, point);
if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
return toleranceOnOutside;
Math::Vector2d left;
Math::Vector2d right;
if (newPoint.getX() > oldPoint.getX()) {
left = oldPoint;
right = newPoint;
} else {
left = newPoint;
right = oldPoint;
}
if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX())) < ((right.getY() - left.getY()) * (point.getX() - left.getX())))
result = !result;
oldPoint = newPoint;
oldSqDist = newSqDist;
}
return result;
}
float Scaling::getScaling(float yPos) {
if (values.size() == 0)
return 1.0f;

View File

@ -30,6 +30,7 @@
#include "twp/font.h"
#include "twp/motor.h"
#include "twp/scenegraph.h"
#include "twp/graph.h"
#define FULLSCREENCLOSEUP 1
#define FULLSCREENROOM 2
@ -61,19 +62,6 @@ public:
Node *_node = nullptr;
};
// Represents an area where an actor can or cannot walk
class Walkbox {
public:
Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
public:
Common::String _name;
private:
Common::Array<Math::Vector2d> _polygon;
bool _visible;
};
struct ScalingValue {
float scale;
int y;
@ -105,6 +93,7 @@ struct Lights {
Color _ambientLight; // Ambient light color
};
class PathFinder;
class Scene;
class Room {
public:
@ -129,6 +118,7 @@ public:
void setOverlay(Color color);
Color getOverlay() const;
void walkboxHidden(const Common::String &name, bool hidden);
Common::Array<Math::Vector2d> calculatePath(Math::Vector2d frm, Math::Vector2d to);
public:
@ -139,12 +129,12 @@ public:
int _height = 0; // Height of the room (what else ?)
Common::Array<Layer *> _layers; // Parallax layers of a room
Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
Common::Array<Walkbox> _mergedPolygon;
Common::Array<Scaling> _scalings; // Defines the scaling of the actor in the room
Scaling _scaling; // Defines the scaling of the actor in the room
HSQOBJECT _table; // Squirrel table representing this room
bool _entering = false; // Indicates whether or not an actor is entering this room
Lights _lights; // Lights of the room
Common::Array<Walkbox> _mergedPolygon;
Common::Array<Object *> _triggers; // Triggers currently enabled in the room
bool _pseudo = false;
Common::Array<Object *> _objects;
@ -152,6 +142,7 @@ public:
OverlayNode _overlayNode; // Represents an overlay
RoomEffect _effect;
Motor* _overlayTo = nullptr;
PathFinder _pathFinder;
};
} // namespace Twp

View File

@ -322,24 +322,24 @@ static SQInteger roomLayer(HSQUIRRELVM v) {
// }
static SQInteger roomOverlayColor(HSQUIRRELVM v) {
int startColor;
SQInteger numArgs = sq_gettop(v);
if (SQ_FAILED(sqget(v, 2, startColor)))
return sq_throwerror(v, "failed to get startColor");
Room* room = g_engine->_room;
if (room->_overlayTo)
room->_overlayTo->disable();
room->setOverlay(Color::fromRgba(startColor));
if (numArgs == 4) {
int endColor;
if (SQ_FAILED(sqget(v, 3, endColor)))
return sq_throwerror(v, "failed to get endColor");
float duration;
if (SQ_FAILED(sqget(v, 4, duration)))
return sq_throwerror(v, "failed to get duration");
debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
}
return 0;
SQInteger numArgs = sq_gettop(v);
if (SQ_FAILED(sqget(v, 2, startColor)))
return sq_throwerror(v, "failed to get startColor");
Room *room = g_engine->_room;
if (room->_overlayTo)
room->_overlayTo->disable();
room->setOverlay(Color::fromRgba(startColor));
if (numArgs == 4) {
int endColor;
if (SQ_FAILED(sqget(v, 3, endColor)))
return sq_throwerror(v, "failed to get endColor");
float duration;
if (SQ_FAILED(sqget(v, 4, duration)))
return sq_throwerror(v, "failed to get duration");
debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
}
return 0;
}
static SQInteger roomRotateTo(HSQUIRRELVM v) {
@ -355,8 +355,17 @@ static SQInteger roomSize(HSQUIRRELVM v) {
return 1;
}
// Sets walkbox to be hidden (YES) or not (NO).
// If the walkbox is hidden, the actors cannot walk to any point within that area anymore, nor to any walkbox that's connected to it on the other side from the actor.
// Often used on small walkboxes below a gate or door to keep the actor from crossing that boundary if the gate/door is closed.
static SQInteger walkboxHidden(HSQUIRRELVM v) {
warning("TODO: walkboxHidden not implemented");
Common::String walkbox;
if (SQ_FAILED(sqget(v, 2, walkbox)))
return sq_throwerror(v, "failed to get object or walkbox");
int hidden = 0;
if (SQ_FAILED(sqget(v, 3, hidden)))
return sq_throwerror(v, "failed to get object or hidden");
g_engine->_room->walkboxHidden(walkbox, hidden != 0);
return 0;
}

View File

@ -410,7 +410,8 @@ Scene::Scene() : Node("Scene") {
}
Scene::~Scene() {}
InputState::InputState() : Node("InputState") {}
InputState::InputState() : Node("InputState") {
}
InputState::~InputState() {}
@ -422,7 +423,7 @@ void InputState::drawCore(Math::Matrix4 trsf) {
// cursorName = "hotspot_" & self.cursorName
const SpriteSheetFrame &sf = gameSheet->frameTable["cursor"];
Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
trsf.translate(pos);
trsf.translate(pos * 2.f);
scale(trsf, Math::Vector2d(2.f, 2.f));
g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
}

View File

@ -57,6 +57,8 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
g_engine = this;
sq_resetobject(&_defaultObj);
_screenScene.setName("Screen");
_scene.addChild(&_walkboxNode);
_screenScene.addChild(&_pathNode);
_screenScene.addChild(&_inputState);
_screenScene.addChild(&_sentence);
_screenScene.addChild(&_dialog);
@ -73,7 +75,6 @@ static Math::Vector2d winToScreen(Math::Vector2d pos) {
Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
Math::Vector2d screenSize = _room->getScreenSize();
pos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx.cameraPos()) / screenSize;
}
@ -341,7 +342,7 @@ void TwpEngine::update(float elapsed) {
// } else {
// walkFast(false);
// }
if (_cursor.isLeftDown() || _cursor.isRightDown())
if (_cursor.leftDown || _cursor.rightDown)
clickedAt(scrPos);
}
} else {
@ -351,11 +352,11 @@ void TwpEngine::update(float elapsed) {
Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->_name);
_sentence.setText(cText);
// TODO: _inputState.setCursorShape(CursorShape::Normal);
if (_cursor.isLeftDown())
if (_cursor.leftDown)
clickedAt(scrPos);
}
if (_cursor.isLeftDown() || _cursor.isRightDown())
if (_cursor.leftDown || _cursor.rightDown)
clickedAt(_cursor.pos);
}
@ -544,11 +545,11 @@ void TwpEngine::draw() {
_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
// draw UI
_gfx.cameraPos(camPos);
_screenScene.draw();
g_system->updateScreen();
_gfx.cameraPos(camPos);
}
Common::Error TwpEngine::run() {
@ -1116,12 +1117,12 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
} else {
bool handled = false;
if (sqrawexists(noun2->_table, verbFuncName)) {
debug("call {verbFuncName} on {noun2.key}");
debug("call %s on %s", verbFuncName.c_str(), noun2->_key.c_str());
sqcallfunc(handled, noun2->_table, verbFuncName.c_str(), noun1->_table);
}
// verbGive is called on object only for non selectable actors
if (!handled && !selectable(noun2) && sqrawexists(noun1->_table, verbFuncName)) {
debug("call {verbFuncName} on {noun1.key}");
debug("call %s on %s", verbFuncName.c_str(), noun1->_key.c_str());
sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
handled = true;
}
@ -1138,7 +1139,7 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
if (!noun2) {
if (sqrawexists(noun1->_table, verbFuncName)) {
int count = sqparamCount(getVm(), noun1->_table, verbFuncName);
debug("call {noun1.key}.{verbFuncName}");
debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
if (count == 1) {
sqcall(noun1->_table, verbFuncName.c_str());
} else {
@ -1147,17 +1148,17 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
sqcall(noun1->_table, VERBDEFAULT);
} else {
debug("call defaultObject.{verbFuncName}");
debug("call defaultObject.%s", verbFuncName.c_str());
sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, actor->_table);
}
} else {
if (sqrawexists(noun1->_table, verbFuncName)) {
debug("call {noun1.key}.{verbFuncName}");
debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
sqcall(noun1->_table, VERBDEFAULT);
} else {
debug("call defaultObject.{verbFuncName}");
debug("call defaultObject.%s", verbFuncName.c_str());
sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, noun2->_table);
}
}

View File

@ -44,6 +44,7 @@
#include "twp/dialog.h"
#include "twp/hud.h"
#include "twp/callback.h"
#include "twp/walkboxnode.h"
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
@ -210,6 +211,8 @@ private:
ShaderParams _shaderParams;
unique_ptr<FadeShader> _fadeShader;
SentenceNode _sentence;
WalkboxNode _walkboxNode;
PathNode _pathNode;
};
extern TwpEngine *g_engine;

View File

@ -134,6 +134,22 @@ float distanceSquared(Math::Vector2d p1, Math::Vector2d p2) {
return dx * dx + dy * dy;
}
float distanceToSegmentSquared(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
float l2 = distanceSquared(v, w);
if (l2 == 0)
return distanceSquared(p, v);
float t = ((p.getX() - v.getX()) * (w.getX() - v.getX()) + (p.getY() - v.getY()) * (w.getY() - v.getY())) / l2;
if (t < 0)
return distanceSquared(p, v);
if (t > 1)
return distanceSquared(p, w);
return distanceSquared(p, Math::Vector2d(v.getX() + t * (w.getX() - v.getX()), v.getY() + t * (w.getY() - v.getY())));
}
float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
return sqrt(distanceToSegmentSquared(p, v, w));
}
float distance(Math::Vector2d p1, Math::Vector2d p2) {
return sqrt(distanceSquared(p1, p2));
}

View File

@ -50,6 +50,8 @@ Common::Rect parseRect(const Common::String &s);
void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
float distance(Math::Vector2d p1, Math::Vector2d p2);
float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
template<typename T>
int find(Common::Array<T>& array, const T& o) {

175
engines/twp/walkboxnode.cpp Normal file
View File

@ -0,0 +1,175 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "twp/twp.h"
#include "twp/walkboxnode.h"
namespace Twp {
WalkboxNode::WalkboxNode() : Node("Walkbox") {
_zOrder = -1000;
_mode = WalkboxMode::Merged;
}
void WalkboxNode::drawCore(Math::Matrix4 trsf) {
if (g_engine->_room) {
switch (_mode) {
case WalkboxMode::All: {
Math::Matrix4 transf;
// cancel camera pos
Math::Vector2d pos = g_engine->getGfx().cameraPos();
transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
Walkbox &wb = g_engine->_room->_walkboxes[i];
if (wb.isVisible()) {
Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
Common::Array<Vertex> vertices;
for (int j = 0; j < wb.getPoints().size(); j++) {
Math::Vector2d p = wb.getPoints()[j];
Vertex v;
v.pos = p;
v.color = color;
vertices.push_back(v);
}
g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
}
}
} break;
case WalkboxMode::Merged: {
Math::Matrix4 transf;
Math::Vector2d pos = g_engine->getGfx().cameraPos();
// cancel camera pos
transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
Walkbox &wb = g_engine->_room->_mergedPolygon[i];
Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
Common::Array<Vertex> vertices;
for (int j = 0; j < wb.getPoints().size(); j++) {
Math::Vector2d p = wb.getPoints()[j];
Vertex v;
v.pos = p;
v.color = color;
vertices.push_back(v);
}
g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
}
} break;
default:
break;
}
}
}
PathNode::PathNode() : Node("Path") {
_zOrder = -1000;
}
Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
Walkbox &wb = g_engine->_room->_mergedPolygon[i];
if (!wb.isVisible() && wb.contains(pos)) {
return wb.getClosestPointOnEdge(pos);
}
}
// for wb in gEngine.room.mergedPolygon:
// if wb.visible and not wb.contains(pos):
// return wb.getClosestPointOnEdge(pos)
return pos;
}
void PathNode::drawCore(Math::Matrix4 trsf) {
Color yellow(1.f, 1.f, 0.f);
Object *actor = g_engine->_actor;
// draw actor path
if (actor && actor->getWalkTo()) {
WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
const Common::Array<Math::Vector2d> &path = walkTo->getPath();
if (path.size() > 0) {
Common::Array<Vertex> vertices;
Vertex v;
v.pos = g_engine->roomToScreen(actor->_node->getPos());
v.color = yellow;
vertices.push_back(v);
for (int i = 0; i < path.size(); i++) {
Math::Vector2d p = g_engine->roomToScreen(path[i]);
v.pos = p;
v.color = yellow;
vertices.push_back(v);
Math::Matrix4 t;
p -= Math::Vector2d(2.f, 2.f);
t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
}
g_engine->getGfx().drawLines(&vertices[0], vertices.size());
}
}
// draw graph nodes
const Graph *graph = g_engine->_room->_pathFinder.getGraph();
if (graph) {
for (int i = 0; i < graph->_concaveVertices.size(); i++) {
Math::Vector2d v = graph->_concaveVertices[i];
Math::Matrix4 t;
Math::Vector2d p = g_engine->roomToScreen(v) - Math::Vector2d(2.f, 2.f);
t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow);
}
// for (int i = 0; i < graph->_edges.size(); i++) {
// const Common::Array<GraphEdge> &edges = graph->_edges[i];
// for (int j = 0; j < edges.size(); j++) {
// const GraphEdge &edge = edges[j];
// Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
// Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
// Vertex vertices[] = {Vertex{.pos = p1, .color = Color()}, Vertex{.pos = p2, .color = Color()}};
// g_engine->getGfx().drawLines(&vertices[0], 2);
// }
// }
}
// draw path from actor to mouse position
if (actor) {
Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getPos()) - Math::Vector2d(2.f, 2.f);
Math::Matrix4 t;
t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
Math::Vector2d scrPos = g_engine->_cursor.pos;
Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
Math::Vector2d p = fixPos(roomPos);
t = Math::Matrix4();
pos = g_engine->roomToScreen(p) - Math::Vector2d(4.f, 4.f);
t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
Common::Array<Math::Vector2d> path = g_engine->_room->calculatePath(fixPos(actor->_node->getPos()), p);
Common::Array<Vertex> vertices;
for (int i = 0; i < path.size(); i++) {
Vertex v;
v.pos = g_engine->roomToScreen(path[i]);;
v.color = yellow;
vertices.push_back(v);
}
g_engine->getGfx().drawLines(&vertices[0], vertices.size());
}
}
} // namespace Twp

57
engines/twp/walkboxnode.h Normal file
View File

@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWP_WALKBOXMODE_H
#define TWP_WALKBOXMODE_H
#include "twp/scenegraph.h"
namespace Twp {
enum class WalkboxMode {
None,
Merged,
All
};
class WalkboxNode : public Node {
public:
WalkboxNode();
private:
virtual void drawCore(Math::Matrix4 trsf) override;
private:
WalkboxMode _mode;
};
class PathNode : public Node {
public:
PathNode();
private:
Math::Vector2d fixPos(Math::Vector2d pos);
virtual void drawCore(Math::Matrix4 trsf) override;
};
} // namespace Twp
#endif