mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-07 10:21:31 +00:00
TWP: Add path finding
This commit is contained in:
parent
e88c12424c
commit
4e67d24e04
8
engines/twp/clipper/CMakeLists.txt
Normal file
8
engines/twp/clipper/CMakeLists.txt
Normal 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})
|
4311
engines/twp/clipper/clipper.cpp
Normal file
4311
engines/twp/clipper/clipper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
403
engines/twp/clipper/clipper.hpp
Normal file
403
engines/twp/clipper/clipper.hpp
Normal 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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
504
engines/twp/graph.cpp
Normal 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
131
engines/twp/graph.h
Normal 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
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
175
engines/twp/walkboxnode.cpp
Normal 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
57
engines/twp/walkboxnode.h
Normal 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
|
Loading…
Reference in New Issue
Block a user