Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#ifndef SWORD25_GEOMETRY_H
#define SWORD25_GEOMETRY_H
#include "sword25/kernel/common.h"
#include "sword25/kernel/service.h"
namespace Sword25 {
class Kernel;
class Geometry : public Service {
public:
Geometry(Kernel *pKernel) : Service(pKernel) {
if (!registerScriptBindings())
error("Script bindings could not be registered.");
}
~Geometry() override {}
private:
bool registerScriptBindings();
};
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,467 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "common/array.h"
#include "sword25/gfx/graphicengine.h"
#include "sword25/kernel/common.h"
#include "sword25/kernel/kernel.h"
#include "sword25/script/script.h"
#include "sword25/script/luabindhelper.h"
#include "sword25/math/geometry.h"
#include "sword25/math/region.h"
#include "sword25/math/regionregistry.h"
#include "sword25/math/walkregion.h"
#include "sword25/math/vertex.h"
namespace Sword25 {
// These strings are defined as #defines to enable compile-time string composition
#define REGION_CLASS_NAME "Geo.Region"
#define WALKREGION_CLASS_NAME "Geo.WalkRegion"
static void newUintUserData(lua_State *L, uint value) {
void *userData = lua_newuserdata(L, sizeof(value));
memcpy(userData, &value, sizeof(value));
}
static bool isValidPolygonDefinition(lua_State *L) {
#ifdef DEBUG
int __startStackDepth = lua_gettop(L);
#endif
// Ensure that we actually consider a table
if (!lua_istable(L, -1)) {
luaL_error(L, "Invalid polygon definition. Unexpected type, \"table\" needed.");
return false;
}
int tableSize = luaL_getn(L, -1);
// Make sure that there are at least three Vertecies
if (tableSize < 6) {
luaL_error(L, "Invalid polygon definition. At least three vertecies needed.");
return false;
}
// Make sure that the number of table elements is divisible by two.
// Since any two elements is a vertex, an odd number of elements is not allowed
if ((tableSize % 2) != 0) {
luaL_error(L, "Invalid polygon definition. Even number of table elements needed.");
return false;
}
// Ensure that all elements in the table are of type Number
for (int i = 1; i <= tableSize; i++) {
lua_rawgeti(L, -1, i);
if (!lua_isnumber(L, -1)) {
luaL_error(L, "Invalid polygon definition. All table elements have to be numbers.");
return false;
}
lua_pop(L, 1);
}
#ifdef DEBUG
assert(__startStackDepth == lua_gettop(L));
#endif
return true;
}
static void tablePolygonToPolygon(lua_State *L, Polygon &polygon) {
#ifdef DEBUG
int __startStackDepth = lua_gettop(L);
#endif
// Ensure that a valid polygon definition is on the stack.
// It is not necessary to catch the return value, since all errors are reported on luaL_error
// End script.
isValidPolygonDefinition(L);
int vertexCount = luaL_getn(L, -1) / 2;
// Memory is reserved for Vertecies
Common::Array<Vertex> vertices;
vertices.reserve(vertexCount);
// Create Vertecies
for (int i = 0; i < vertexCount; i++) {
// X Value
lua_rawgeti(L, -1, (i * 2) + 1);
int X = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
// Y Value
lua_rawgeti(L, -1, (i * 2) + 2);
int Y = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
// Vertex
vertices.push_back(Vertex(X, Y));
}
assert((int)vertices.size() == vertexCount);
#ifdef DEBUG
assert(__startStackDepth == lua_gettop(L));
#endif
// Create polygon
polygon.init(vertexCount, &vertices[0]);
}
static uint tableRegionToRegion(lua_State *L, const char *className) {
#ifdef DEBUG
int __startStackDepth = lua_gettop(L);
#endif
// You can define a region in Lua in two ways:
// 1. A table that defines a polygon (polgon = table with numbers, which define
// two consecutive numbers per vertex)
// 2. A table containing more polygon definitions
// Then the first polygon is the contour of the region, and the following are holes
// defined in the first polygon.
// It may be passed only one parameter, and this must be a table
if (lua_gettop(L) != 1 || !lua_istable(L, -1)) {
luaL_error(L, "First and only parameter has to be of type \"table\".");
return 0;
}
uint regionHandle = 0;
if (!strcmp(className, REGION_CLASS_NAME)) {
regionHandle = Region::create(Region::RT_REGION);
} else if (!strcmp(className, WALKREGION_CLASS_NAME)) {
regionHandle = WalkRegion::create(Region::RT_WALKREGION);
} else {
assert(false);
}
assert(regionHandle);
// If the first element of the parameter is a number, then case 1 is accepted
// If the first element of the parameter is a table, then case 2 is accepted
// If the first element of the parameter has a different type, there is an error
lua_rawgeti(L, -1, 1);
int firstElementType = lua_type(L, -1);
lua_pop(L, 1);
switch (firstElementType) {
case LUA_TNUMBER: {
Polygon polygon;
tablePolygonToPolygon(L, polygon);
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon);
}
break;
case LUA_TTABLE: {
lua_rawgeti(L, -1, 1);
Polygon polygon;
tablePolygonToPolygon(L, polygon);
lua_pop(L, 1);
int polygonCount = luaL_getn(L, -1);
if (polygonCount == 1)
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon);
else {
Common::Array<Polygon> holes;
holes.reserve(polygonCount - 1);
for (int i = 2; i <= polygonCount; i++) {
lua_rawgeti(L, -1, i);
holes.resize(holes.size() + 1);
tablePolygonToPolygon(L, holes.back());
lua_pop(L, 1);
}
assert((int)holes.size() == polygonCount - 1);
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon, &holes);
}
}
break;
default:
luaL_error(L, "Illegal region definition.");
return 0;
}
#ifdef DEBUG
assert(__startStackDepth == lua_gettop(L));
#endif
return regionHandle;
}
static void newUserdataRegion(lua_State *L, const char *className) {
// Region due to the Lua code to create
// Any errors that occur will be intercepted to the luaL_error
uint regionHandle = tableRegionToRegion(L, className);
assert(regionHandle);
newUintUserData(L, regionHandle);
// luaL_getmetatable(L, className);
LuaBindhelper::getMetatable(L, className);
assert(!lua_isnil(L, -1));
lua_setmetatable(L, -2);
}
static int newRegion(lua_State *L) {
newUserdataRegion(L, REGION_CLASS_NAME);
return 1;
}
static int newWalkRegion(lua_State *L) {
newUserdataRegion(L, WALKREGION_CLASS_NAME);
return 1;
}
static const char *GEO_LIBRARY_NAME = "Geo";
static const luaL_reg GEO_FUNCTIONS[] = {
{"NewRegion", newRegion},
{"NewWalkRegion", newWalkRegion},
{0, 0}
};
static Region *checkRegion(lua_State *L) {
// The first parameter must be of type 'userdata', and the Metatable class Geo.Region or Geo.WalkRegion
uint *regionHandlePtr = reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, REGION_CLASS_NAME));
if (regionHandlePtr != 0 ||
(regionHandlePtr = reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
return RegionRegistry::instance().resolveHandle(*regionHandlePtr);
} else {
luaL_argcheck(L, 0, 1, "'" REGION_CLASS_NAME "' expected");
}
// Compilation fix. Execution never reaches this point
return 0;
}
static int r_isValid(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
lua_pushbooleancpp(L, pR->isValid());
return 1;
}
static int r_getX(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
lua_pushnumber(L, pR->getPosX());
return 1;
}
static int r_getY(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
lua_pushnumber(L, pR->getPosY());
return 1;
}
static int r_getPos(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
Vertex::vertexToLuaVertex(L, pR->getPosition());
return 1;
}
static int r_isPointInRegion(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
Vertex vertex;
Vertex::luaVertexToVertex(L, 2, vertex);
lua_pushbooleancpp(L, pR->isPointInRegion(vertex));
return 1;
}
static int r_setPos(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
Vertex vertex;
Vertex::luaVertexToVertex(L, 2, vertex);
pR->setPos(vertex.x, vertex.y);
return 0;
}
static int r_setX(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
pR->setPosX(static_cast<int>(luaL_checknumber(L, 2)));
return 0;
}
static int r_setY(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
pR->setPosY(static_cast<int>(luaL_checknumber(L, 2)));
return 0;
}
static void drawPolygon(const Polygon &polygon, uint color, const Vertex &offset) {
GraphicEngine *pGE = Kernel::getInstance()->getGfx();
assert(pGE);
for (int i = 0; i < polygon.vertexCount - 1; i++)
pGE->drawDebugLine(polygon.vertices[i] + offset, polygon.vertices[i + 1] + offset, color);
pGE->drawDebugLine(polygon.vertices[polygon.vertexCount - 1] + offset, polygon.vertices[0] + offset, color);
}
static void drawRegion(const Region &region, uint color, const Vertex &offset) {
drawPolygon(region.getContour(), color, offset);
for (int i = 0; i < region.getHoleCount(); i++)
drawPolygon(region.getHole(i), color, offset);
}
static int r_draw(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
switch (lua_gettop(L)) {
case 3: {
Vertex offset;
Vertex::luaVertexToVertex(L, 3, offset);
drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), offset);
}
break;
case 2:
drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), Vertex(0, 0));
break;
default:
drawRegion(*pR, BS_RGB(255, 255, 255), Vertex(0, 0));
}
return 0;
}
static int r_getCentroid(lua_State *L) {
Region *RPtr = checkRegion(L);
assert(RPtr);
Vertex::vertexToLuaVertex(L, RPtr->getCentroid());
return 1;
}
static int r_delete(lua_State *L) {
Region *pR = checkRegion(L);
assert(pR);
delete pR;
return 0;
}
static const luaL_reg REGION_METHODS[] = {
{"SetPos", r_setPos},
{"SetX", r_setX},
{"SetY", r_setY},
{"GetPos", r_getPos},
{"IsPointInRegion", r_isPointInRegion},
{"GetX", r_getX},
{"GetY", r_getY},
{"IsValid", r_isValid},
{"Draw", r_draw},
{"GetCentroid", r_getCentroid},
{0, 0}
};
static WalkRegion *checkWalkRegion(lua_State *L) {
// The first parameter must be of type 'userdate', and the Metatable class Geo.WalkRegion
uint regionHandle;
if ((regionHandle = *reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
return reinterpret_cast<WalkRegion *>(RegionRegistry::instance().resolveHandle(regionHandle));
} else {
luaL_argcheck(L, 0, 1, "'" WALKREGION_CLASS_NAME "' expected");
}
// Compilation fix. Execution never reaches this point
return 0;
}
static int wr_getPath(lua_State *L) {
WalkRegion *pWR = checkWalkRegion(L);
assert(pWR);
Vertex start;
Vertex::luaVertexToVertex(L, 2, start);
Vertex end;
Vertex::luaVertexToVertex(L, 3, end);
BS_Path path;
if (pWR->queryPath(start, end, path)) {
lua_newtable(L);
BS_Path::const_iterator it = path.begin();
for (; it != path.end(); it++) {
lua_pushnumber(L, (it - path.begin()) + 1);
Vertex::vertexToLuaVertex(L, *it);
lua_settable(L, -3);
}
} else
lua_pushnil(L);
return 1;
}
static const luaL_reg WALKREGION_METHODS[] = {
{"GetPath", wr_getPath},
{0, 0}
};
bool Geometry::registerScriptBindings() {
Kernel *pKernel = Kernel::getInstance();
assert(pKernel);
ScriptEngine *pScript = pKernel->getScript();
assert(pScript);
lua_State *L = static_cast< lua_State *>(pScript->getScriptObject());
assert(L);
if (!LuaBindhelper::addMethodsToClass(L, REGION_CLASS_NAME, REGION_METHODS)) return false;
if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, REGION_METHODS)) return false;
if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, WALKREGION_METHODS)) return false;
if (!LuaBindhelper::setClassGCHandler(L, REGION_CLASS_NAME, r_delete)) return false;
if (!LuaBindhelper::setClassGCHandler(L, WALKREGION_CLASS_NAME, r_delete)) return false;
if (!LuaBindhelper::addFunctionsToLib(L, GEO_LIBRARY_NAME, GEO_FUNCTIONS)) return false;
return true;
}
} // End of namespace Sword25

194
engines/sword25/math/line.h Normal file
View File

@@ -0,0 +1,194 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
/*
BS_Line
-------
This class contains only static methods, which have to do with straight line
segments. There is no real straight line segment class. Calculations will be
used with polygons, and it is important the process of starting and selecting
endpoints of lines is dynamic. This would prhobit a polygon from a set
being formed by fixed line segments
Autor: Malte Thiesen
*/
#ifndef SWORD25_LINE_H
#define SWORD25_LINE_H
#include "sword25/kernel/common.h"
namespace Sword25 {
class Line {
public:
/**
* Determines whether a piont is left of a line
* @param a The start point of a line
* @param b The end point of a line
* @param c The test point
* @return Returns true if the point is to the left of the line.
* If the point is to the right of the line or on the line, false is returned.
*/
static bool isVertexLeft(const Vertex &a, const Vertex &b, const Vertex &c) {
return triangleArea2(a, b, c) > 0;
}
static bool isVertexLeftOn(const Vertex &a, const Vertex &b, const Vertex &c) {
return triangleArea2(a, b, c) >= 0;
}
/**
* Determines whether a piont is right of a line
* @param a The start point of a line
* @param b The end point of a line
* @param c The test point
* @return Returns true if the point is to the right of the line.
* If the point is to the right of the line or on the line, false is returned.
*/
static bool isVertexRight(const Vertex &a, const Vertex &b, const Vertex &c) {
return triangleArea2(a, b, c) < 0;
}
static bool isVertexRightOn(const Vertex &a, const Vertex &b, const Vertex &c) {
return triangleArea2(a, b, c) <= 0;
}
/**
* Determines whether a piont is on a line
* @param a The start point of a line
* @param b The end point of a line
* @param c The test point
* @return Returns true if the point is on the line, false otherwise.
*/
static bool isVertexOn(const Vertex &a, const Vertex &b, const Vertex &c) {
return triangleArea2(a, b, c) == 0;
}
enum VERTEX_CLASSIFICATION {
LEFT,
RIGHT,
ON
};
/**
* Determines where a point is relative to a line.
* @param a The start point of a line
* @param b The end point of a line
* @param c The test point
* @return LEFT is returned if the point is to the left of the line.
* RIGHT is returned if the point is to the right of the line.
* ON is returned if the point is on the line.
*/
static VERTEX_CLASSIFICATION classifyVertexToLine(const Vertex &a, const Vertex &b, const Vertex &c) {
int area = triangleArea2(a, b, c);
if (area > 0) return LEFT;
if (area < 0) return RIGHT;
return ON;
}
/**
* Determines whether two lines intersect
* @param a The start point of the first line
* @param b The end point of the first line
* @param c The start point of the second line
* @param d The end point of the second line
* @remark In cases where a line only touches the other, false is returned (improper intersection)
*/
static bool doesIntersectProperly(const Vertex &a, const Vertex &b, const Vertex &c, const Vertex &d) {
VERTEX_CLASSIFICATION class1 = classifyVertexToLine(a, b, c);
VERTEX_CLASSIFICATION class2 = classifyVertexToLine(a, b, d);
VERTEX_CLASSIFICATION class3 = classifyVertexToLine(c, d, a);
VERTEX_CLASSIFICATION class4 = classifyVertexToLine(c, d, b);
if (class1 == ON || class2 == ON || class3 == ON || class4 == ON) return false;
return ((class1 == LEFT) ^(class2 == LEFT)) && ((class3 == LEFT) ^(class4 == LEFT));
}
/**
* Determines whether a point is on a line segment
* @param a The start point of a line
* @param b The end point of a line
* @param c The test point
*/
static bool isOnLine(const Vertex &a, const Vertex &b, const Vertex &c) {
// The items must all be Collinear, otherwise don't bothering testing the point
if (triangleArea2(a, b, c) != 0) return false;
// If the line segment is not vertical, check on the x-axis, otherwise the y-axis
if (a.x != b.x) {
return ((a.x <= c.x) &&
(c.x <= b.x)) ||
((a.x >= c.x) &&
(c.x >= b.x));
} else {
return ((a.y <= c.y) &&
(c.y <= b.y)) ||
((a.y >= c.y) &&
(c.y >= b.y));
}
}
static bool isOnLineStrict(const Vertex &a, const Vertex &b, const Vertex &c) {
// The items must all be Collinear, otherwise don't bothering testing the point
if (triangleArea2(a, b, c) != 0) return false;
// If the line segment is not vertical, check on the x-axis, otherwise the y-axis
if (a.x != b.x) {
return ((a.x < c.x) &&
(c.x < b.x)) ||
((a.x > c.x) &&
(c.x > b.x));
} else {
return ((a.y < c.y) &&
(c.y < b.y)) ||
((a.y > c.y) &&
(c.y > b.y));
}
}
private:
/**
* Return double the size of the triangle defined by the three passed points.
*
* The result is positive if the points are arrange counterclockwise,
* and negative if they are arranged counter-clockwise.
*/
static int triangleArea2(const Vertex &a, const Vertex &b, const Vertex &c) {
return a.x * b.y - a.y * b.x +
a.y * c.x - a.x * c.y +
b.x * c.y - c.x * b.y;
}
};
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,419 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/kernel/outputpersistenceblock.h"
#include "sword25/kernel/inputpersistenceblock.h"
#include "sword25/math/polygon.h"
#include "sword25/math/line.h"
namespace Sword25 {
Polygon::Polygon() : vertexCount(0), vertices(NULL) {
}
Polygon::Polygon(int vertexCount_, const Vertex *vertices_) : vertexCount(0), vertices(NULL) {
init(vertexCount_, vertices_);
}
Polygon::Polygon(const Polygon &other) : Persistable(other), vertexCount(0), vertices(NULL) {
init(other.vertexCount, other.vertices);
}
Polygon::Polygon(InputPersistenceBlock &Reader) : vertexCount(0), vertices(NULL) {
unpersist(Reader);
}
Polygon::~Polygon() {
delete[] vertices;
}
bool Polygon::init(int vertexCount_, const Vertex *vertices_) {
// Rember the old obstate to restore it if an error occurs whilst initializing it with the new data
int oldvertexCount = this->vertexCount;
Vertex *oldvertices = this->vertices;
this->vertexCount = vertexCount_;
this->vertices = new Vertex[vertexCount_ + 1];
memcpy(this->vertices, vertices_, sizeof(Vertex) * vertexCount_);
// TODO:
// Duplicate and remove redundant vertecies (Superflous = 3 co-linear verts)
// _WeedRepeatedvertices();
// The first vertex is repeated at the end of the vertex array; this simplifies
// some algorithms, running through the edges and thus can save the overflow control.
this->vertices[vertexCount_] = this->vertices[0];
// If the polygon is self-intersecting, the object state is restore, and an error signalled
if (checkForSelfIntersection()) {
delete[] this->vertices;
this->vertices = oldvertices;
this->vertexCount = oldvertexCount;
// BS_LOG_ERROR("POLYGON: Tried to create a self-intersecting polygon.\n");
return false;
}
// Release old vertex list
delete[] oldvertices;
// Calculate properties of the polygon
_isCW = computeIsCW();
_centroid = computeCentroid();
return true;
}
// Review the order of the vertices
// ---------------------------------
bool Polygon::isCW() const {
return _isCW;
}
bool Polygon::computeIsCW() const {
if (vertexCount) {
// Find the vertex on extreme bottom right
int v2Index = findLRVertexIndex();
// Find the vertex before and after it
int v1Index = (v2Index + (vertexCount - 1)) % vertexCount;
int v3Index = (v2Index + 1) % vertexCount;
// Cross product form
// If the cross product of the vertex lying fartherest bottom left is positive,
// the vertecies arranged in a clockwise order. Otherwise counter-clockwise
if (crossProduct(vertices[v1Index], vertices[v2Index], vertices[v3Index]) >= 0)
return true;
}
return false;
}
int Polygon::findLRVertexIndex() const {
if (vertexCount) {
int curIndex = 0;
int maxX = vertices[0].x;
int maxY = vertices[0].y;
for (int i = 1; i < vertexCount; i++) {
if (vertices[i].y > maxY ||
(vertices[i].y == maxY && vertices[i].x > maxX)) {
maxX = vertices[i].x;
maxY = vertices[i].y;
curIndex = i;
}
}
return curIndex;
}
return -1;
}
// Make a determine vertex order
// -----------------------------
void Polygon::ensureCWOrder() {
if (!isCW())
reverseVertexOrder();
}
// Reverse the order of vertecies
// ------------------------------
void Polygon::reverseVertexOrder() {
// vertices are exchanged in pairs, until the list has been completely reversed
for (int i = 0; i < vertexCount / 2; i++) {
Vertex tempVertex = vertices[i];
vertices[i] = vertices[vertexCount - i - 1];
vertices[vertexCount - i - 1] = tempVertex;
}
// Vertexordnung neu berechnen.
_isCW = computeIsCW();
}
// Cross Product
// -------------
int Polygon::crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const {
return (v2.x - v1.x) * (v3.y - v2.y) -
(v2.y - v1.y) * (v3.x - v2.x);
}
// Check for self-intersections
// ----------------------------
bool Polygon::checkForSelfIntersection() const {
// TODO: Finish this
/*
float AngleSum = 0.0f;
for (int i = 0; i < vertexCount; i++) {
int j = (i + 1) % vertexCount;
int k = (i + 2) % vertexCount;
float Dot = DotProduct(vertices[i], vertices[j], vertices[k]);
// Skalarproduct normalisieren
float Length1 = sqrt((vertices[i].x - vertices[j].x) * (vertices[i].x - vertices[j].x) +
(vertices[i].y - vertices[j].y) * (vertices[i].y - vertices[j].y));
float Length2 = sqrt((vertices[k].x - vertices[j].x) * (vertices[k].x - vertices[j].x) +
(vertices[k].y - vertices[j].y) * (vertices[k].y - vertices[j].y));
float Norm = Length1 * Length2;
if (Norm > 0.0f) {
Dot /= Norm;
AngleSum += acos(Dot);
}
}
*/
return false;
}
// Move
// ----
void Polygon::operator+=(const Vertex &delta) {
// Move all vertecies
for (int i = 0; i < vertexCount; i++)
vertices[i] += delta;
// Shift the focus
_centroid += delta;
}
// Line of Sight
// -------------
bool Polygon::isLineInterior(const Vertex &a, const Vertex &b) const {
// Both points have to be in the polygon
if (!isPointInPolygon(a, true) || !isPointInPolygon(b, true))
return false;
// If the points are identical, the line is trivially within the polygon
if (a == b)
return true;
// Test whether the line intersects a line segment strictly (proper intersection)
for (int i = 0; i < vertexCount; i++) {
int j = (i + 1) % vertexCount;
const Vertex &vs = vertices[i];
const Vertex &ve = vertices[j];
// If the line intersects a line segment strictly (proper cross section) the line is not in the polygon
if (Line::doesIntersectProperly(a, b, vs, ve))
return false;
// If one of the two line items is on the edge and the other is to the right of the edge,
// then the line is not completely within the polygon
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexRight(vs, ve, b))
return false;
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexRight(vs, ve, a))
return false;
// If one of the two line items is on a vertex, the line traces into the polygon
if ((a == vs) && !isLineInCone(i, b, true))
return false;
if ((b == vs) && !isLineInCone(i, a, true))
return false;
}
return true;
}
bool Polygon::isLineExterior(const Vertex &a, const Vertex &b) const {
// Neither of the two points must be strictly in the polygon (on the edge is allowed)
if (isPointInPolygon(a, false) || isPointInPolygon(b, false))
return false;
// If the points are identical, the line is trivially outside of the polygon
if (a == b)
return true;
// Test whether the line intersects a line segment strictly (proper intersection)
for (int i = 0; i < vertexCount; i++) {
int j = (i + 1) % vertexCount;
const Vertex &vs = vertices[i];
const Vertex &ve = vertices[j];
// If the line intersects a line segment strictly (proper intersection), then
// the line is partially inside the polygon
if (Line::doesIntersectProperly(a, b, vs, ve))
return false;
// If one of the two line items is on the edge and the other is to the right of the edge,
// the line is not completely outside the polygon
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexLeft(vs, ve, b))
return false;
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexLeft(vs, ve, a))
return false;
// If one of the lwo line items is on a vertex, the line must not run into the polygon
if ((a == vs) && isLineInCone(i, b, false))
return false;
if ((b == vs) && isLineInCone(i, a, false))
return false;
// If the vertex with start and end point is collinear, (a vs) and (b, vs) is not in the polygon
if (Line::isOnLine(a, b, vs)) {
if (isLineInCone(i, a, false))
return false;
if (isLineInCone(i, b, false))
return false;
}
}
return true;
}
bool Polygon::isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const {
const Vertex &startVertex = vertices[startVertexIndex];
const Vertex &nextVertex = vertices[(startVertexIndex + 1) % vertexCount];
const Vertex &prevVertex = vertices[(startVertexIndex + vertexCount - 1) % vertexCount];
if (Line::isVertexLeftOn(prevVertex, startVertex, nextVertex)) {
if (includeEdges)
return Line::isVertexLeftOn(endVertex, startVertex, nextVertex) &&
Line::isVertexLeftOn(startVertex, endVertex, prevVertex);
else
return Line::isVertexLeft(endVertex, startVertex, nextVertex) &&
Line::isVertexLeft(startVertex, endVertex, prevVertex);
} else {
if (includeEdges)
return !(Line::isVertexLeft(endVertex, startVertex, prevVertex) &&
Line::isVertexLeft(startVertex, endVertex, nextVertex));
else
return !(Line::isVertexLeftOn(endVertex, startVertex, prevVertex) &&
Line::isVertexLeftOn(startVertex, endVertex, nextVertex));
}
}
// Point-Polygon Tests
// -------------------
bool Polygon::isPointInPolygon(int x, int y, bool borderBelongsToPolygon) const {
return isPointInPolygon(Vertex(x, y), borderBelongsToPolygon);
}
bool Polygon::isPointInPolygon(const Vertex &point, bool edgesBelongToPolygon) const {
int rcross = 0; // Number of right-side overlaps
int lcross = 0; // Number of left-side overlaps
// Each edge is checked whether it cuts the outgoing stream from the point
for (int i = 0; i < vertexCount; i++) {
const Vertex &edgeStart = vertices[i];
const Vertex &edgeEnd = vertices[(i + 1) % vertexCount];
// A vertex is a point? Then it lies on one edge of the polygon
if (point == edgeStart)
return edgesBelongToPolygon;
if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
if ((term1 > 0) == (term2 >= 0))
rcross++;
}
if ((edgeStart.y < point.y) != (edgeEnd.y < point.y)) {
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
if ((term1 < 0) == (term2 <= 0))
lcross++;
}
}
// The point is on an adge, if the number of left and right intersections have the same even numbers
if ((rcross % 2) != (lcross % 2))
return edgesBelongToPolygon;
// The point is strictly inside the polygon if and only if the number of overlaps is odd
if ((rcross % 2) == 1)
return true;
else
return false;
}
bool Polygon::persist(OutputPersistenceBlock &writer) {
writer.write(vertexCount);
for (int i = 0; i < vertexCount; ++i) {
writer.write((int32)vertices[i].x);
writer.write((int32)vertices[i].y);
}
return true;
}
bool Polygon::unpersist(InputPersistenceBlock &reader) {
int32 storedvertexCount;
reader.read(storedvertexCount);
Common::Array<Vertex> storedvertices;
for (int i = 0; i < storedvertexCount; ++i) {
int32 x, y;
reader.read(x);
reader.read(y);
storedvertices.push_back(Vertex(x, y));
}
init(storedvertexCount, &storedvertices[0]);
return reader.isGood();
}
// Main Focus
// ----------
Vertex Polygon::getCentroid() const {
return _centroid;
}
Vertex Polygon::computeCentroid() const {
// Area of the polygon is calculated
int doubleArea = 0;
for (int i = 0; i < vertexCount; ++i) {
doubleArea += vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
}
// Avoid division by zero in the next step
if (doubleArea == 0)
return Vertex();
// Calculate centroid
Vertex centroid;
for (int i = 0; i < vertexCount; ++i) {
int area = vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
centroid.x += (vertices[i].x + vertices[i + 1].x) * area;
centroid.y += (vertices[i].y + vertices[i + 1].y) * area;
}
centroid.x /= 3 * doubleArea;
centroid.y /= 3 * doubleArea;
return centroid;
}
} // End of namespace Sword25

View File

@@ -0,0 +1,229 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#ifndef SWORD25_POLYGON_H
#define SWORD25_POLYGON_H
// Includes
#include "sword25/kernel/common.h"
#include "sword25/kernel/persistable.h"
#include "sword25/math/vertex.h"
namespace Sword25 {
class Vertex;
/**
@brief Eine Polygonklasse.
*/
class Polygon : public Persistable {
public:
/**
* Creates an object of type #BS_Polygon, containing 0 Vertecies.
*
* With the method Init(), Vertices can be added in later
*/
Polygon();
/**
* Copy constructor
*/
Polygon(const Polygon &other);
/**
* Creates a polygon using persisted data
*/
Polygon(InputPersistenceBlock &reader);
/**
* Creaes an object of type #BS_Polygon, and assigns Vertices to it
* @param VertexCount The number of vertices being passed
* @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon.
* @remark The Vertecies that define a polygon must not have any self-intersections.
* If the polygon does have self-intersections, then an empty polygon object is created.
*/
Polygon(int vertexCount_, const Vertex *vertices_);
/**
* Deletes the BS_Polygon object
*/
~Polygon() override;
/**
* Initializes the BS_Polygon with a list of Vertecies.
*
* The Vertices need to define a polygon must not have self-intersections.
* If a polygon already has verticies, this will re-initialize it with the new list.
*
* @param VertexCount The number of vertices being passed
* @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon.
* @return Returns false if the Vertecies have self-intersections. In this case,
* the object is not initialized.
*/
bool init(int vertexCount_, const Vertex *vertices_);
//
// ** Exploratory methods **
//
/**
* Checks whether the Vertecies of the polygon are arranged in a clockwise direction.
* @return Returns true if the Vertecies of the polygon are arranged clockwise or co-planar.
* Returns false if the Vertecies of the polygon are arrange counter-clockwise.
* @remark This method only returns a meaningful result if the polygon has at least three Vertecies.
*/
bool isCW() const;
/**
* Checks whether the Vertices of the polygon are arranged in a counter-clockwise direction.
* @return Returns true if the Vertecies of the polygon are arranged counter-clockwise.
* Returns false if the Vertecies of the polygon are arranged clockwise or co-planar.
* @remark This method only returns a meaningful result if the polygon has at least three Vertecies.
*/
bool isCCW() const;
/**
* Checks whether a point is inside the polygon
* @param Vertex A Vertex with the co-ordinates of the point to be tested.
* @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered
* @return Returns true if the point is inside the polygon, false if it is outside.
*/
bool isPointInPolygon(const Vertex &vertex, bool borderBelongsToPolygon = true) const;
/**
* Checks whether a point is inside the polygon
* @param X The X position of the point
* @param Y The Y position of the point
* @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered
* @return Returns true if the point is inside the polygon, false if it is outside.
*/
bool isPointInPolygon(int x, int y, bool borderBelongsToPolygon = true) const;
/**
* Returns the focus/centroid of the polygon
*/
Vertex getCentroid() const;
// Edge belongs to the polygon
// Polygon must be CW
bool isLineInterior(const Vertex &a, const Vertex &b) const;
// Edge does not belong to the polygon
// Polygon must be CW
bool isLineExterior(const Vertex &a, const Vertex &b) const;
//
// Manipulation methods
//
/**
* Ensures that the Vertecies of the polygon are arranged in a clockwise direction
*/
void ensureCWOrder();
/**
* Ensures that the Vertecies of the polygon are arranged in a counter-clockwise direction
*/
void ensureCCWOrder();
/**
* Reverses the Vertecies order.
*/
void reverseVertexOrder();
/**
* Moves the polygon.
* @param Delta The vertex around the polygon to be moved.
*/
void operator+=(const Vertex &delta);
//
//------------------
//
/// Specifies the number of Vertecies in the Vertecies array.
int32 vertexCount;
/// COntains the Vertecies of the polygon
Vertex *vertices;
bool persist(OutputPersistenceBlock &writer) override;
bool unpersist(InputPersistenceBlock &reader) override;
Polygon &operator=(const Polygon &p) {
init(p.vertexCount, p.vertices);
return *this;
}
private:
bool _isCW;
Vertex _centroid;
/**
* Computes the centroid of the polygon.
*/
Vertex computeCentroid() const;
/**
* Determines how the Vertecies of the polygon are arranged.
* @return Returns true if the Vertecies are arranged in a clockwise
* direction, otherwise false.
*/
bool computeIsCW() const;
/**
* Calculates the cross product of three Vertecies
* @param V1 The first Vertex
* @param V2 The second Vertex
* @param V3 The third Vertex
* @return Returns the cross-product of the three vertecies
* @todo This method would be better as a method of the BS_Vertex class
*/
int crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const;
/**
* Checks whether the polygon is self-intersecting
* @return Returns true if the polygon is self-intersecting.
* Returns false if the polygon is not self-intersecting.
*/
bool checkForSelfIntersection() const;
/**
* Find the vertex of the polygon that is located below the right-most point,
* and returns it's index in the vertex array.
* @return Returns the index of the vertex at the bottom-right of the polygon.
* Returns -1 if the vertex list is empty.
*/
int findLRVertexIndex() const;
bool isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const;
};
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,348 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/kernel/inputpersistenceblock.h"
#include "sword25/kernel/outputpersistenceblock.h"
#include "sword25/math/region.h"
#include "sword25/math/walkregion.h"
#include "sword25/math/regionregistry.h"
namespace Sword25 {
Region::Region() : _valid(false), _type(RT_REGION) {
RegionRegistry::instance().registerObject(this);
}
Region::Region(InputPersistenceBlock &reader, uint handle) : _valid(false), _type(RT_REGION) {
RegionRegistry::instance().registerObject(this, handle);
unpersist(reader);
}
uint Region::create(REGION_TYPE type) {
Region *regionPtr = NULL;
switch (type) {
case RT_REGION:
regionPtr = new Region();
break;
case RT_WALKREGION:
regionPtr = new WalkRegion();
break;
default:
assert(true);
}
return RegionRegistry::instance().resolvePtr(regionPtr);
}
uint Region::create(InputPersistenceBlock &reader, uint handle) {
// Read type
uint32 type;
reader.read(type);
// Depending on the type, create a new BS_Region or BS_WalkRegion object
Region *regionPtr = NULL;
if (type == RT_REGION) {
regionPtr = new Region(reader, handle);
} else if (type == RT_WALKREGION) {
regionPtr = new WalkRegion(reader, handle);
} else {
assert(false);
}
return RegionRegistry::instance().resolvePtr(regionPtr);
}
Region::~Region() {
RegionRegistry::instance().deregisterObject(this);
}
bool Region::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) {
// Reset object state
_valid = false;
_position = Vertex(0, 0);
_polygons.clear();
// Reserve sufficient space for countour and holes in the polygon list
if (pHoles)
_polygons.reserve(1 + pHoles->size());
else
_polygons.reserve(1);
// The first polygon will be the contour
_polygons.push_back(Polygon());
_polygons[0].init(contour.vertexCount, contour.vertices);
// Make sure that the Vertecies in the Contour are arranged in a clockwise direction
_polygons[0].ensureCWOrder();
// Place the hole polygons in the following positions
if (pHoles) {
for (uint i = 0; i < pHoles->size(); ++i) {
_polygons.push_back(Polygon());
_polygons[i + 1].init((*pHoles)[i].vertexCount, (*pHoles)[i].vertices);
_polygons[i + 1].ensureCWOrder();
}
}
// Initialize bounding box
updateBoundingBox();
_valid = true;
return true;
}
void Region::updateBoundingBox() {
if (_polygons[0].vertexCount) {
int minX = _polygons[0].vertices[0].x;
int maxX = _polygons[0].vertices[0].x;
int minY = _polygons[0].vertices[0].y;
int maxY = _polygons[0].vertices[0].y;
for (int i = 1; i < _polygons[0].vertexCount; i++) {
if (_polygons[0].vertices[i].x < minX) minX = _polygons[0].vertices[i].x;
else if (_polygons[0].vertices[i].x > maxX) maxX = _polygons[0].vertices[i].x;
if (_polygons[0].vertices[i].y < minY) minY = _polygons[0].vertices[i].y;
else if (_polygons[0].vertices[i].y > maxY) maxY = _polygons[0].vertices[i].y;
}
_boundingBox = Common::Rect(minX, minY, maxX + 1, maxY + 1);
}
}
// Position Changes
void Region::setPos(int x, int y) {
// Calculate the difference between the old and new position
Vertex delta(x - _position.x, y - _position.y);
// Save the new position
_position = Vertex(x, y);
// Move all the vertecies
for (uint i = 0; i < _polygons.size(); ++i) {
_polygons[i] += delta;
}
// Update the bounding box
updateBoundingBox();
}
void Region::setPosX(int x) {
setPos(x, _position.y);
}
void Region::setPosY(int y) {
setPos(_position.x, y);
}
// Point-Region Tests
bool Region::isPointInRegion(int x, int y) const {
// Test whether the point is in the bounding box
if (_boundingBox.contains(x, y)) {
// Test whether the point is in the contour
if (_polygons[0].isPointInPolygon(x, y, true)) {
// Test whether the point is in a hole
for (uint i = 1; i < _polygons.size(); i++) {
if (_polygons[i].isPointInPolygon(x, y, false))
return false;
}
return true;
}
}
return false;
}
bool Region::isPointInRegion(const Vertex &vertex) const {
return isPointInRegion(vertex.x, vertex.y);
}
Vertex Region::findClosestRegionPoint(const Vertex &point) const {
// Determine whether the point is inside a hole. If that is the case, the closest
// point on the edge of the hole is determined
int polygonIdx = 0;
{
for (uint i = 1; i < _polygons.size(); ++i) {
if (_polygons[i].isPointInPolygon(point)) {
polygonIdx = i;
break;
}
}
}
const Polygon &polygon = _polygons[polygonIdx];
assert(polygon.vertexCount > 1);
// For each line of the polygon, calculate the point that is cloest to the given point
// The point of this set with the smallest distance to the given point is the result.
Vertex closestVertex = findClosestPointOnLine(polygon.vertices[0], polygon.vertices[1], point);
int closestVertexDistance2 = closestVertex.distance(point);
for (int i = 1; i < polygon.vertexCount; ++i) {
int j = (i + 1) % polygon.vertexCount;
Vertex curVertex = findClosestPointOnLine(polygon.vertices[i], polygon.vertices[j], point);
if (curVertex.distance(point) < closestVertexDistance2) {
closestVertex = curVertex;
closestVertexDistance2 = curVertex.distance(point);
}
}
// Determine whether the point is really within the region. This must not be so, as a result of rounding
// errors can occur at the edge of polygons
if (isPointInRegion(closestVertex))
return closestVertex;
else {
// Try to construct a point within the region - 8 points are tested in the immediate vicinity
// of the point
if (isPointInRegion(closestVertex + Vertex(-2, -2)))
return closestVertex + Vertex(-2, -2);
else if (isPointInRegion(closestVertex + Vertex(0, -2)))
return closestVertex + Vertex(0, -2);
else if (isPointInRegion(closestVertex + Vertex(2, -2)))
return closestVertex + Vertex(2, -2);
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
return closestVertex + Vertex(-2, 0);
else if (isPointInRegion(closestVertex + Vertex(0, 2)))
return closestVertex + Vertex(0, 2);
else if (isPointInRegion(closestVertex + Vertex(-2, 2)))
return closestVertex + Vertex(-2, 2);
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
return closestVertex + Vertex(2, 2);
else if (isPointInRegion(closestVertex + Vertex(2, 2)))
return closestVertex + Vertex(2, 2);
// If no point could be found that way that lies within the region, find the next point
closestVertex = polygon.vertices[0];
int shortestVertexDistance2 = polygon.vertices[0].sqrDist(point);
{
for (int i = 1; i < polygon.vertexCount; i++) {
int curDistance2 = polygon.vertices[i].sqrDist(point);
if (curDistance2 < shortestVertexDistance2) {
closestVertex = polygon.vertices[i];
shortestVertexDistance2 = curDistance2;
}
}
}
warning("Clostest vertex forced because edgepoint was outside region.");
return closestVertex;
}
}
Vertex Region::findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const {
float vector1X = static_cast<float>(point.x - lineStart.x);
float vector1Y = static_cast<float>(point.y - lineStart.y);
float vector2X = static_cast<float>(lineEnd.x - lineStart.x);
float vector2Y = static_cast<float>(lineEnd.y - lineStart.y);
float vector2Length = sqrt(vector2X * vector2X + vector2Y * vector2Y);
vector2X /= vector2Length;
vector2Y /= vector2Length;
float distance = sqrt(static_cast<float>((lineStart.x - lineEnd.x) * (lineStart.x - lineEnd.x) +
(lineStart.y - lineEnd.y) * (lineStart.y - lineEnd.y)));
float dot = vector1X * vector2X + vector1Y * vector2Y;
if (dot <= 0)
return lineStart;
if (dot >= distance)
return lineEnd;
Vertex vector3(static_cast<int>(vector2X * dot + 0.5f), static_cast<int>(vector2Y * dot + 0.5f));
return lineStart + vector3;
}
// Line of Sight
bool Region::isLineOfSight(const Vertex &a, const Vertex &b) const {
assert(_polygons.size());
// The line must be within the contour polygon, and outside of any hole polygons
Common::Array<Polygon>::const_iterator iter = _polygons.begin();
if (!(*iter).isLineInterior(a, b)) return false;
for (iter++; iter != _polygons.end(); iter++)
if (!(*iter).isLineExterior(a, b)) return false;
return true;
}
// Persistence
bool Region::persist(OutputPersistenceBlock &writer) {
bool Result = true;
writer.write(static_cast<uint32>(_type));
writer.write(_valid);
writer.write((int32)_position.x);
writer.write((int32)_position.y);
writer.write((uint32)_polygons.size());
Common::Array<Polygon>::iterator It = _polygons.begin();
while (It != _polygons.end()) {
Result &= It->persist(writer);
++It;
}
writer.write((int32)_boundingBox.left);
writer.write((int32)_boundingBox.top);
writer.write((int32)_boundingBox.right);
writer.write((int32)_boundingBox.bottom);
return Result;
}
bool Region::unpersist(InputPersistenceBlock &reader) {
reader.read(_valid);
reader.read(_position.x);
reader.read(_position.y);
_polygons.clear();
uint32 PolygonCount;
reader.read(PolygonCount);
for (uint i = 0; i < PolygonCount; ++i) {
_polygons.push_back(Polygon(reader));
}
reader.read(_boundingBox.left);
reader.read(_boundingBox.top);
reader.read(_boundingBox.right);
reader.read(_boundingBox.bottom);
return reader.isGood();
}
Vertex Region::getCentroid() const {
if (_polygons.size() > 0)
return _polygons[0].getCentroid();
return
Vertex();
}
} // End of namespace Sword25

View File

@@ -0,0 +1,237 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#ifndef SWORD25_REGION_H
#define SWORD25_REGION_H
#include "sword25/kernel/common.h"
#include "sword25/kernel/persistable.h"
#include "sword25/math/vertex.h"
#include "sword25/math/polygon.h"
#include "common/rect.h"
namespace Sword25 {
/**
* This class is the base class of all regions.
*
* The IsValid() method can be queried to see whether the object is in a valid state.
* If this is not the case, the method Init() is the only method that may be invoked.
* This class guarantees that the Vertecies outline of the hole, and the polygons are
* arranged in a clockwise direction, so that the polygon working algorithms will
* work properly.
*/
class Region : public Persistable {
protected:
/**
* Creates a new BS_Region object
*
* After creation the object is invaild (IsValid() return false), but a call can
* be made later on to Init() to set up the region into a valid state.
*/
Region();
Region(InputPersistenceBlock &reader, uint handle);
public:
enum REGION_TYPE {
RT_REGION,
RT_WALKREGION
};
static uint create(REGION_TYPE type);
static uint create(InputPersistenceBlock &reader, uint handle = 0);
~Region() override;
/**
* Initializes a BS_Region object
* @param Contour A polygon indicating the outline of the region
* @param pHoles A pointer to an array of polygons representing the hole state in the region.
* If the region has no holes, it must be passed as NULL. The default value is NULL.
* @return Returns true if the initialisation was successful, otherwise false.
* @remark If the region was already initialized, the old state will be deleted.
*/
virtual bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = NULL);
//
// Exploratory Methods
//
/**
* Specifies whether the object is in a valid state
* @return Returns true if the object is in a valid state, otherwise false.
* @remark Invalid objects can be made valid by calling Init with a valid state.
*/
bool isValid() const {
return _valid;
}
/**
* Returns the position of the region
*/
const Vertex &getPosition() const {
return _position;
}
/**
* Returns the X position of the region
*/
int getPosX() const {
return _position.x;
}
/**
* Returns the Y position of the region
*/
int getPosY() const {
return _position.y;
}
/**
* Indicates whether a point is inside the region
* @param Vertex A verex with the co-ordinates of the test point
* @return Returns true if the point is within the region, otherwise false.
*/
bool isPointInRegion(const Vertex &vertex) const;
/**
* Indicates whether a point is inside the region
* @param X The X position
* @param Y The Y position
* @return Returns true if the point is within the region, otherwise false.
*/
bool isPointInRegion(int x, int y) const;
/**
* Returns the countour of the region
*/
const Polygon &getContour() const {
return _polygons[0];
}
/**
* Returns the number of polygons in the hole region
*/
int getHoleCount() const {
return static_cast<int>(_polygons.size() - 1);
}
/**
* Returns a specific hole polygon in the region
* @param i The number of the hole to return.
* The index must be between 0 and GetHoleCount() - 1.
* @return Returns the desired hole polygon
*/
inline const Polygon &getHole(uint i) const;
/**
* For a point outside the region, finds the closest point inside the region
* @param Point The point that is outside the region
* @return Returns the point within the region which is closest
* @remark This method does not always work with pixel accuracy.
* One should not therefore rely on the fact that there is really no point in
* the region which is closer to the given point.
*/
Vertex findClosestRegionPoint(const Vertex &point) const;
/**
* Returns the centroid for the region
*/
Vertex getCentroid() const;
bool isLineOfSight(const Vertex &a, const Vertex &b) const;
//
// Manipulation Methods
//
/**
* Sets the position of the region
* @param X The new X psoition of the region
* @param Y The new Y psoition of the region
*/
virtual void setPos(int x, int y);
/**
* Sets the X position of the region
* @param X The new X position of the region
*/
void setPosX(int x);
/**
* Sets the Y position of the region
* @param Y The new Y position of the region
*/
void setPosY(int y);
//
// Manipulation Methods
//
bool persist(OutputPersistenceBlock &writer) override;
bool unpersist(InputPersistenceBlock &reader) override;
protected:
/// This specifies the type of object
REGION_TYPE _type;
/// This variable indicates whether the current object state is valid
bool _valid;
/// This vertex is the position of the region
Vertex _position;
/// This array contains all the polygons that define the region. The first element of
// the array is the contour, all others are the holes
Common::Array<Polygon> _polygons;
/// The bounding box for the region
Common::Rect _boundingBox;
/**
* Updates the bounding box of the region.
*/
void updateBoundingBox();
/**
* Find the point on a line which is closest to another point
* @param LineStart The start of the line
* @param LineEnd The end of the line
* @param Point The point to be compared against
* @return Returns the point on the line which is cloest to the passed point.
*/
Vertex findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const;
};
inline const Polygon &Region::getHole(uint i) const {
assert(i < _polygons.size() - 1);
return _polygons[i + 1];
}
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,94 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/kernel/outputpersistenceblock.h"
#include "sword25/kernel/inputpersistenceblock.h"
#include "sword25/math/regionregistry.h"
#include "sword25/math/region.h"
namespace Common {
DECLARE_SINGLETON(Sword25::RegionRegistry);
}
namespace Sword25 {
bool RegionRegistry::persist(OutputPersistenceBlock &writer) {
bool result = true;
// write out the next handle
writer.write(_nextHandle);
// Number of regions to write
writer.write((uint32)_handle2PtrMap.size());
// Persist all the BS_Regions
HANDLE2PTR_MAP::const_iterator iter = _handle2PtrMap.begin();
while (iter != _handle2PtrMap.end()) {
// Handle persistence
writer.write(iter->_key);
// Persist object
result &= iter->_value->persist(writer);
++iter;
}
return result;
}
bool RegionRegistry::unpersist(InputPersistenceBlock &reader) {
bool result = true;
// read in the next handle
reader.read(_nextHandle);
// Destroy all existing BS_Regions
//FIXME: This doesn't seem right - the value is being deleted but not the actual hash node itself?
while (!_handle2PtrMap.empty())
delete _handle2PtrMap.begin()->_value;
// read in the number of BS_Regions
uint32 regionCount;
reader.read(regionCount);
// Restore all the BS_Regions objects
for (uint i = 0; i < regionCount; ++i) {
// Handle read
uint32 handle;
reader.read(handle);
// BS_Region restore
result &= Region::create(reader, handle) != 0;
}
return reader.isGood() && result;
}
} // End of namespace Sword25

View File

@@ -0,0 +1,55 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#ifndef SWORD25_REGIONREGISTRY_H
#define SWORD25_REGIONREGISTRY_H
#include "common/singleton.h"
#include "sword25/kernel/common.h"
#include "sword25/kernel/persistable.h"
#include "sword25/kernel/objectregistry.h"
namespace Sword25 {
class Region;
class RegionRegistry :
public ObjectRegistry<Region>,
public Persistable,
public Common::Singleton<RegionRegistry> {
public:
bool persist(OutputPersistenceBlock &writer) override;
bool unpersist(InputPersistenceBlock &reader) override;
};
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,82 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/math/vertex.h"
#include "common/lua/lua.h"
#include "common/lua/lauxlib.h"
namespace Sword25 {
Vertex &Vertex::luaVertexToVertex(lua_State *L, int stackIndex, Vertex &vertex) {
#ifdef DEBUG
int __startStackDepth = lua_gettop(L);
#endif
// Ensure that we actually consider a table
luaL_checktype(L, stackIndex, LUA_TTABLE);
// Read X Component
lua_pushstring(L, "X");
lua_gettable(L, stackIndex);
if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the X component has to be a number");
vertex.x = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
// Read Y Component
lua_pushstring(L, "Y");
lua_gettable(L, stackIndex);
if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the Y component has to be a number");
vertex.y = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
#ifdef DEBUG
assert(__startStackDepth == lua_gettop(L));
#endif
return vertex;
}
void Vertex::vertexToLuaVertex(lua_State *L, const Vertex &vertex) {
// Create New Table
lua_newtable(L);
// X value is written to table
lua_pushstring(L, "X");
lua_pushnumber(L, vertex.x);
lua_settable(L, -3);
// Y value is written to table
lua_pushstring(L, "Y");
lua_pushnumber(L, vertex.y);
lua_settable(L, -3);
}
} // End of namespace Sword25

View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
/*
BS_Vertex
---------
Autor: Malte Thiesen
*/
#ifndef SWORD25_VERTEX_H
#define SWORD25_VERTEX_H
// Includes
#include "common/rect.h"
#include "sword25/kernel/common.h"
struct lua_State;
namespace Sword25 {
/**
* Defines a 2-D Vertex
*/
class Vertex : public Common::Point {
public:
Vertex() : Point() {}
Vertex(int x_, int y_) : Point(x_, y_) {}
Vertex(Point p) : Point(p) {}
/**
* Calculates the square of the distance between two Vertecies.
* @param Vertex The vertex for which the distance is to be calculated
* @return Returns the square of the distance between itself and the passed vertex
* @remark If only distances should be compared, sqrDist() should be used, since it is faster.
*/
inline int distance(const Vertex &vertex) const {
return (int)(sqrt(static_cast<float>(sqrDist(vertex))) + 0.5);
}
static Vertex &luaVertexToVertex(lua_State *L, int StackIndex, Vertex &vertex);
static void vertexToLuaVertex(lua_State *L, const Vertex &vertex);
};
} // End of namespace Sword25
#endif

View File

@@ -0,0 +1,397 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/kernel/kernel.h"
#include "sword25/kernel/inputpersistenceblock.h"
#include "sword25/kernel/outputpersistenceblock.h"
#include "sword25/math/walkregion.h"
#include "sword25/math/line.h"
namespace Sword25 {
static const int Infinity = 0x7fffffff;
WalkRegion::WalkRegion() {
_type = RT_WALKREGION;
}
WalkRegion::WalkRegion(InputPersistenceBlock &reader, uint handle) :
Region(reader, handle) {
_type = RT_WALKREGION;
unpersist(reader);
}
WalkRegion::~WalkRegion() {
}
bool WalkRegion::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) {
// Default initialisation of the region
if (!Region::init(contour, pHoles)) return false;
// Prepare structures for pathfinding
initNodeVector();
computeVisibilityMatrix();
// Signal success
return true;
}
bool WalkRegion::queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path) {
assert(path.empty());
// If the start and finish are identical, no path can be found trivially
if (startPoint == endPoint)
return true;
// Ensure that the start and finish are valid and find new start points if either
// are outside the polygon
if (!checkAndPrepareStartAndEnd(startPoint, endPoint)) return false;
// If between the start and point a line of sight exists, then it can be returned.
if (isLineOfSight(startPoint, endPoint)) {
path.push_back(startPoint);
path.push_back(endPoint);
return true;
}
return findPath(startPoint, endPoint, path);
}
struct DijkstraNode {
typedef Common::Array<DijkstraNode> Container;
typedef Container::iterator Iter;
typedef Container::const_iterator ConstIter;
DijkstraNode() : parentIter(), cost(Infinity), chosen(false) {}
ConstIter parentIter;
int cost;
bool chosen;
};
static void initDijkstraNodes(DijkstraNode::Container &dijkstraNodes, const Region &region,
const Vertex &start, const Common::Array<Vertex> &nodes) {
// Allocate sufficient space in the array
dijkstraNodes.resize(nodes.size());
// Initialize all the nodes which are visible from the starting node
DijkstraNode::Iter dijkstraIter = dijkstraNodes.begin();
for (Common::Array<Vertex>::const_iterator nodesIter = nodes.begin();
nodesIter != nodes.end(); nodesIter++, dijkstraIter++) {
(*dijkstraIter).parentIter = dijkstraNodes.end();
if (region.isLineOfSight(*nodesIter, start))(*dijkstraIter).cost = (*nodesIter).distance(start);
}
assert(dijkstraIter == dijkstraNodes.end());
}
static DijkstraNode::Iter chooseClosestNode(DijkstraNode::Container &nodes) {
DijkstraNode::Iter closestNodeInter = nodes.end();
int minCost = Infinity;
for (DijkstraNode::Iter iter = nodes.begin(); iter != nodes.end(); iter++) {
if (!(*iter).chosen && (*iter).cost < minCost) {
minCost = (*iter).cost;
closestNodeInter = iter;
}
}
return closestNodeInter;
}
static void relaxNodes(DijkstraNode::Container &nodes,
const Common::Array< Common::Array<int> > &visibilityMatrix,
const DijkstraNode::ConstIter &curNodeIter) {
// All the successors of the current node that have not been chosen will be
// inserted into the boundary node list, and the cost will be updated if
// a shorter path has been found to them.
int curNodeIndex = curNodeIter - nodes.begin();
for (uint i = 0; i < nodes.size(); i++) {
int cost = visibilityMatrix[curNodeIndex][i];
if (!nodes[i].chosen && cost != Infinity) {
int totalCost = (*curNodeIter).cost + cost;
if (totalCost < nodes[i].cost) {
nodes[i].parentIter = curNodeIter;
nodes[i].cost = totalCost;
}
}
}
}
static void relaxEndPoint(const Vertex &curNodePos,
const DijkstraNode::ConstIter &curNodeIter,
const Vertex &endPointPos,
DijkstraNode &endPoint,
const Region &region) {
if (region.isLineOfSight(curNodePos, endPointPos)) {
int totalCost = (*curNodeIter).cost + curNodePos.distance(endPointPos);
if (totalCost < endPoint.cost) {
endPoint.parentIter = curNodeIter;
endPoint.cost = totalCost;
}
}
}
template<class T>
void reverseArray(Common::Array<T> &arr) {
const uint size = arr.size();
if (size < 2)
return;
for (uint i = 0; i <= (size / 2 - 1); ++i) {
SWAP(arr[i], arr[size - i - 1]);
}
}
bool WalkRegion::findPath(const Vertex &start, const Vertex &end, BS_Path &path) const {
// This is an implementation of Dijkstra's algorithm
// Initialize edge node list
DijkstraNode::Container dijkstraNodes;
initDijkstraNodes(dijkstraNodes, *this, start, _nodes);
// The end point is treated separately, since it does not exist in the visibility graph
DijkstraNode endPoint;
// Since a node is selected each round from the node list, and can never be selected again
// after that, the maximum number of loop iterations is limited by the number of nodes
for (uint i = 0; i < _nodes.size(); i++) {
// Determine the nearest edge node in the node list
DijkstraNode::Iter nodeInter = chooseClosestNode(dijkstraNodes);
// If no free nodes are absent from the edge node list, there is no path from start
// to end node. This case should never occur, since the number of loop passes is
// limited, but etter safe than sorry
if (nodeInter == dijkstraNodes.end())
return false;
// If the destination point is closer than the point cost, scan can stop
(*nodeInter).chosen = true;
if (endPoint.cost <= (*nodeInter).cost) {
// Insert the end point in the list
path.push_back(end);
// The list is done in reverse order and inserted into the path
DijkstraNode::ConstIter curNode = endPoint.parentIter;
while (curNode != dijkstraNodes.end()) {
assert((*curNode).chosen);
path.push_back(_nodes[curNode - dijkstraNodes.begin()]);
curNode = (*curNode).parentIter;
}
// The starting point is inserted into the path
path.push_back(start);
// The nodes of the path must be untwisted, as they were extracted in reverse order.
// This step could be saved if the path from end to the beginning was desired
reverseArray<Vertex>(path);
return true;
}
// Relaxation step for nodes of the graph, and perform the end nodes
relaxNodes(dijkstraNodes, _visibilityMatrix, nodeInter);
relaxEndPoint(_nodes[nodeInter - dijkstraNodes.begin()], nodeInter, end, endPoint, *this);
}
// If the loop has been completely run through, all the nodes have been chosen, and still
// no path was found. There is therefore no path available
return false;
}
void WalkRegion::initNodeVector() {
// Empty the Node list
_nodes.clear();
// Determine the number of nodes
int nodeCount = 0;
{
for (uint i = 0; i < _polygons.size(); i++)
nodeCount += _polygons[i].vertexCount;
}
// Knoten-Vector füllen
_nodes.reserve(nodeCount);
{
for (uint j = 0; j < _polygons.size(); j++)
for (int i = 0; i < _polygons[j].vertexCount; i++)
_nodes.push_back(_polygons[j].vertices[i]);
}
}
void WalkRegion::computeVisibilityMatrix() {
// Initialize visibility matrix
_visibilityMatrix = Common::Array< Common::Array <int> >();
for (uint idx = 0; idx < _nodes.size(); ++idx) {
Common::Array<int> arr;
for (uint idx2 = 0; idx2 < _nodes.size(); ++idx2)
arr.push_back(Infinity);
_visibilityMatrix.push_back(arr);
}
// Calculate visibility been vertecies
for (uint j = 0; j < _nodes.size(); ++j) {
for (uint i = j; i < _nodes.size(); ++i) {
if (isLineOfSight(_nodes[i], _nodes[j])) {
// There is a line of sight, so save the distance between the two
int distance = _nodes[i].distance(_nodes[j]);
_visibilityMatrix[i][j] = distance;
_visibilityMatrix[j][i] = distance;
} else {
// There is no line of sight, so save Infinity as the distance
_visibilityMatrix[i][j] = Infinity;
_visibilityMatrix[j][i] = Infinity;
}
}
}
}
bool WalkRegion::checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const {
if (!isPointInRegion(start)) {
Vertex newStart = findClosestRegionPoint(start);
// Check to make sure the point is really in the region. If not, stop with an error
if (!isPointInRegion(newStart)) {
error("Constructed startpoint ((%d,%d) from (%d,%d)) is not inside the region.",
newStart.x, newStart.y,
start.x, start.y);
return false;
}
start = newStart;
}
// If the destination is outside the region, a point is determined that is within the region,
// and that is used as an endpoint instead
if (!isPointInRegion(end)) {
Vertex newEnd = findClosestRegionPoint(end);
// Make sure that the determined point is really within the region
if (!isPointInRegion(newEnd)) {
error("Constructed endpoint ((%d,%d) from (%d,%d)) is not inside the region.",
newEnd.x, newEnd.y,
end.x, end.y);
return false;
}
end = newEnd;
}
// Signal success
return true;
}
void WalkRegion::setPos(int x, int y) {
// Calculate the difference between old and new position
Vertex Delta(x - _position.x, y - _position.y);
// Move all the nodes
for (uint i = 0; i < _nodes.size(); i++)
_nodes[i] += Delta;
// Move regions
Region::setPos(x, y);
}
bool WalkRegion::persist(OutputPersistenceBlock &writer) {
bool result = true;
// Persist the parent region
result &= Region::persist(writer);
// Persist the nodes
writer.write((uint32)_nodes.size());
Common::Array<Vertex>::const_iterator it = _nodes.begin();
while (it != _nodes.end()) {
writer.write((int32)it->x);
writer.write((int32)it->y);
++it;
}
// Persist the visibility matrix
writer.write((uint32)_visibilityMatrix.size());
Common::Array< Common::Array<int> >::const_iterator rowIter = _visibilityMatrix.begin();
while (rowIter != _visibilityMatrix.end()) {
writer.write((uint32)rowIter->size());
Common::Array<int>::const_iterator colIter = rowIter->begin();
while (colIter != rowIter->end()) {
writer.write((int32)*colIter);
++colIter;
}
++rowIter;
}
return result;
}
bool WalkRegion::unpersist(InputPersistenceBlock &reader) {
bool result = true;
// The parent object was already loaded in the constructor of BS_Region, so at
// this point only the additional data from BS_WalkRegion needs to be loaded
// Node load
uint32 nodeCount;
reader.read(nodeCount);
_nodes.clear();
_nodes.resize(nodeCount);
Common::Array<Vertex>::iterator it = _nodes.begin();
while (it != _nodes.end()) {
reader.read(it->x);
reader.read(it->y);
++it;
}
// Visibility matrix load
uint32 rowCount;
reader.read(rowCount);
_visibilityMatrix.clear();
_visibilityMatrix.resize(rowCount);
Common::Array< Common::Array<int> >::iterator rowIter = _visibilityMatrix.begin();
while (rowIter != _visibilityMatrix.end()) {
uint32 colCount;
reader.read(colCount);
rowIter->resize(colCount);
Common::Array<int>::iterator colIter = rowIter->begin();
while (colIter != rowIter->end()) {
int32 t;
reader.read(t);
*colIter = t;
++colIter;
}
++rowIter;
}
return result && reader.isGood();
}
} // End of namespace Sword25

View File

@@ -0,0 +1,109 @@
/* 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/>.
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#ifndef SWORD25_WALKREGION_H
#define SWORD25_WALKREGION_H
#include "common/array.h"
#include "sword25/kernel/common.h"
#include "sword25/math/region.h"
namespace Sword25 {
typedef Common::Array<Vertex> BS_Path;
/**
* This class represents the region in which the main character can move
*/
class WalkRegion : public Region {
friend class Region;
protected:
WalkRegion();
WalkRegion(InputPersistenceBlock &Reader, uint handle);
public:
~WalkRegion() override;
bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = 0) override;
/**
* Get the shortest path between two points in the region
*
* This method requires that the starting point lies within the region. The end point
* may lie outside the region. Int his case, the end is chosen as the cloest point to it
* that lies within the region.
*
* @param X1 X Co-ordinate of the start point
* @param Y1 Y Co-ordinate of the start point
* @param X2 X Co-ordinate of the end point
* @param Y2 Y Co-ordinate of the end point
* @param Path An empty BS_Path that will be set to the resulting path
* @return Returns false if the result is invalid, otherwise returns true.
*/
bool queryPath(int x1, int y1, int x2, int y2, BS_Path &path) {
return queryPath(Vertex(x1, y1), Vertex(x2, y2), path);
}
/**
* Get the shortest path between two points in the region.
*
* @param StartPoint The start point
* @param EndPoint The end point
* @param Path An empty BS_Path that will be set to the resulting path
* @return Returns false if the result is invalid, otherwise returns true.
*/
bool queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path);
void setPos(int x, int y) override;
const Common::Array<Vertex> &getNodes() const {
return _nodes;
}
const Common::Array< Common::Array<int> > &getVisibilityMatrix() const {
return _visibilityMatrix;
}
bool persist(OutputPersistenceBlock &writer) override;
bool unpersist(InputPersistenceBlock &reader) override;
private:
Common::Array<Vertex> _nodes;
Common::Array< Common::Array<int> > _visibilityMatrix;
void initNodeVector();
void computeVisibilityMatrix();
bool checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const;
bool findPath(const Vertex &start, const Vertex &end, BS_Path &path) const;
};
} // End of namespace Sword25
#endif