diff options
author | Johannes Schickel | 2010-10-13 03:57:44 +0000 |
---|---|---|
committer | Johannes Schickel | 2010-10-13 03:57:44 +0000 |
commit | 75e8452b6e6a2bf4fb2f588aa00b428a60d873b5 (patch) | |
tree | f29541d55309487a94bd1d38e8b53bb3dde9aec6 /engines/sword25/math | |
parent | 48ee83b88957dab86bc763e9ef21a70179fa8679 (diff) | |
parent | e9f50882ea5b6beeefa994040be9d3bab6a1f107 (diff) | |
download | scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.gz scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.bz2 scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.zip |
OPENGL: Merged from trunk, from rev 52105 to 53396.
This includes an rather hacky attempt to merge all the recent gp2x backend
changes into the branch. I suppose the gp2x backend and probably all new
backends, i.e. gph, dingux etc., might not compile anymore.
Since I have no way of testing those it would be nice if porters could look
into getting those up to speed in this branch.
svn-id: r53399
Diffstat (limited to 'engines/sword25/math')
-rw-r--r-- | engines/sword25/math/geometry.cpp | 53 | ||||
-rw-r--r-- | engines/sword25/math/geometry.h | 56 | ||||
-rw-r--r-- | engines/sword25/math/geometry_script.cpp | 496 | ||||
-rw-r--r-- | engines/sword25/math/line.h | 198 | ||||
-rw-r--r-- | engines/sword25/math/polygon.cpp | 491 | ||||
-rw-r--r-- | engines/sword25/math/polygon.h | 262 | ||||
-rw-r--r-- | engines/sword25/math/region.cpp | 354 | ||||
-rw-r--r-- | engines/sword25/math/region.h | 241 | ||||
-rw-r--r-- | engines/sword25/math/regionregistry.cpp | 106 | ||||
-rw-r--r-- | engines/sword25/math/regionregistry.h | 66 | ||||
-rw-r--r-- | engines/sword25/math/vertex.cpp | 93 | ||||
-rw-r--r-- | engines/sword25/math/vertex.h | 180 | ||||
-rw-r--r-- | engines/sword25/math/walkregion.cpp | 390 | ||||
-rw-r--r-- | engines/sword25/math/walkregion.h | 113 |
14 files changed, 3099 insertions, 0 deletions
diff --git a/engines/sword25/math/geometry.cpp b/engines/sword25/math/geometry.cpp new file mode 100644 index 0000000000..caa10160e9 --- /dev/null +++ b/engines/sword25/math/geometry.cpp @@ -0,0 +1,53 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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/geometry.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "GEOMETRY" + +Geometry::Geometry(Kernel *pKernel) : Service(pKernel) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + + +Service *Geometry_CreateObject(Kernel *pKernel) { + return new Geometry(pKernel); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/geometry.h b/engines/sword25/math/geometry.h new file mode 100644 index 0000000000..78aa30696e --- /dev/null +++ b/engines/sword25/math/geometry.h @@ -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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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); + virtual ~Geometry() {} + +private: + bool registerScriptBindings(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/geometry_script.cpp b/engines/sword25/math/geometry_script.cpp new file mode 100644 index 0000000000..dac766927b --- /dev/null +++ b/engines/sword25/math/geometry_script.cpp @@ -0,0 +1,496 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#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" + +// How luaL_checkudata, only without that no error is generated. +static void *my_checkudata(lua_State *L, int ud, const char *tname) { + int top = lua_gettop(L); + + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + // lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + LuaBindhelper::getMetatable(L, tname); + /* does it have the correct mt? */ + if (lua_rawequal(L, -1, -2)) { + lua_settop(L, top); + return p; + } + } + } + + lua_settop(L, top); + return NULL; +} + +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 + BS_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)); + } + BS_ASSERT((int)vertices.size() == vertexCount); + +#ifdef DEBUG + BS_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 { + BS_ASSERT(false); + } + + BS_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::getInstance().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::getInstance().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); + } + BS_ASSERT((int)holes.size() == polygonCount - 1); + + RegionRegistry::getInstance().resolveHandle(regionHandle)->init(polygon, &holes); + } + } + break; + + default: + luaL_error(L, "Illegal region definition."); + return 0; + } + +#ifdef DEBUG + BS_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); + BS_ASSERT(regionHandle); + + newUintUserData(L, regionHandle); + // luaL_getmetatable(L, className); + LuaBindhelper::getMetatable(L, className); + BS_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; + if ((regionHandlePtr = reinterpret_cast<uint *>(my_checkudata(L, 1, REGION_CLASS_NAME))) != 0 || + (regionHandlePtr = reinterpret_cast<uint *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) { + return RegionRegistry::getInstance().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); + BS_ASSERT(pR); + + lua_pushbooleancpp(L, pR->isValid()); + return 1; +} + +static int r_getX(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + lua_pushnumber(L, pR->getPosX()); + return 1; +} + +static int r_getY(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + lua_pushnumber(L, pR->getPosY()); + return 1; +} + +static int r_getPos(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + Vertex::vertexToLuaVertex(L, pR->getPosition()); + return 1; +} + +static int r_isPointInRegion(lua_State *L) { + Region *pR = checkRegion(L); + BS_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); + BS_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); + BS_ASSERT(pR); + + pR->setPosX(static_cast<int>(luaL_checknumber(L, 2))); + + return 0; +} + +static int r_setY(lua_State *L) { + Region *pR = checkRegion(L); + BS_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 = static_cast<GraphicEngine *>(Kernel::GetInstance()->GetService("gfx")); + BS_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 ®ion, 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); + BS_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); + BS_ASSERT(RPtr); + + Vertex::vertexToLuaVertex(L, RPtr->getCentroid()); + + return 1; +} + +static int r_delete(lua_State *L) { + Region *pR = checkRegion(L); + BS_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 *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) { + return reinterpret_cast<WalkRegion *>(RegionRegistry::getInstance().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); + BS_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(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = static_cast<ScriptEngine *>(pKernel->GetService("script")); + BS_ASSERT(pScript); + lua_State *L = static_cast< lua_State *>(pScript->getScriptObject()); + BS_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 diff --git a/engines/sword25/math/line.h b/engines/sword25/math/line.h new file mode 100644 index 0000000000..d57fce68f7 --- /dev/null +++ b/engines/sword25/math/line.h @@ -0,0 +1,198 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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 diff --git a/engines/sword25/math/polygon.cpp b/engines/sword25/math/polygon.cpp new file mode 100644 index 0000000000..700c375e8c --- /dev/null +++ b/engines/sword25/math/polygon.cpp @@ -0,0 +1,491 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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 <math.h> + +#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) : 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 initialising 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(); + _isConvex = computeIsConvex(); + _centroid = computeCentroid(); + + return true; +} + +// Review the order of the vertices +// --------------------------------- + +bool Polygon::isCW() const { + return _isCW; +} + +bool Polygon::isCCW() 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 arrranged 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; +} + +// Testing for Convex / Concave +// ------------------------ + +bool Polygon::isConvex() const { + return _isConvex; +} + +bool Polygon::isConcave() const { + return !isConvex(); +} + +bool Polygon::computeIsConvex() const { + // Polygons with three or less vertices can only be convex + if (vertexCount <= 3) return true; + + // All angles in the polygon computed will have the same direction sign if the polygon is convex + int flag = 0; + for (int i = 0; i < vertexCount; i++) { + // Determine the next two vertecies to check + int j = (i + 1) % vertexCount; + int k = (i + 2) % vertexCount; + + // Calculate the cross product of the three vertecies + int cross = crossProduct(vertices[i], vertices[j], vertices[k]); + + // The lower two bits of the flag represent the following: + // 0: negative angle occurred + // 1: positive angle occurred + + // The sign of the current angle is recorded in Flag + if (cross < 0) + flag |= 1; + else if (cross > 0) + flag |= 2; + + // If flag is 3, there are both positive and negative angles; so the polygon is concave + if (flag == 3) + return false; + } + + // Polygon is convex + return true; +} + +// Make a determine vertex order +// ----------------------------- + +void Polygon::ensureCWOrder() { + if (!isCW()) + reverseVertexOrder(); +} + +void Polygon::ensureCCWOrder() { + if (!isCCW()) + 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); +} + +// Scalar Product +// -------------- + +int Polygon::dotProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const { + return (v1.x - v2.x) * (v3.x - v2.x) + + (v1.y - v2.y) * (v3.x - v2.y); +} + +// 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(vertices[i].x); + writer.write(vertices[i].y); + } + + return true; +} + +bool Polygon::unpersist(InputPersistenceBlock &reader) { + int storedvertexCount; + reader.read(storedvertexCount); + + Common::Array<Vertex> storedvertices; + for (int i = 0; i < storedvertexCount; ++i) { + int 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 diff --git a/engines/sword25/math/polygon.h b/engines/sword25/math/polygon.h new file mode 100644 index 0000000000..3bd243f666 --- /dev/null +++ b/engines/sword25/math/polygon.h @@ -0,0 +1,262 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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 + */ + virtual ~Polygon(); + + /** + * Initialises 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-initialise 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 initialised. + */ + 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 the polygon is convex. + * @return Returns true if the polygon is convex. Returns false if the polygon is concave. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isConvex() const; + + /** + * Checks whether the polygon is concave. + * @return Returns true if the polygon is concave. Returns false if the polygon is convex. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isConcave() 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. + int vertexCount; + /// COntains the Vertecies of the polygon + Vertex *vertices; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + bool _isCW; + bool _isConvex; + 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; + + /** + * Determines whether the polygon is convex or concave. + * @return Returns true if the polygon is convex, otherwise false. + */ + bool computeIsConvex() 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; + + /** + * Computes the scalar product of two vectors spanning three vertecies + * + * The vectors are spanned by V2->V1 and V2->V3 + * + * @param V1 The first Vertex + * @param V2 The second Vertex + * @param V3 The third Vertex + * @return Returns the dot product of the three Vertecies. + * @todo This method would be better as a method of the BS_Vertex class + */ + int dotProduct(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 diff --git a/engines/sword25/math/region.cpp b/engines/sword25/math/region.cpp new file mode 100644 index 0000000000..454f90a8ba --- /dev/null +++ b/engines/sword25/math/region.cpp @@ -0,0 +1,354 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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" + +#define BS_LOG_PREFIX "REGION" + +namespace Sword25 { + +Region::Region() : _valid(false), _type(RT_REGION) { + RegionRegistry::getInstance().registerObject(this); +} + +Region::Region(InputPersistenceBlock &reader, uint handle) : _valid(false), _type(RT_REGION) { + RegionRegistry::getInstance().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: + BS_ASSERT(true); + } + + return RegionRegistry::getInstance().resolvePtr(regionPtr); +} + +uint Region::create(InputPersistenceBlock &reader, uint handle) { + // Read type + uint 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 { + BS_ASSERT(false); + } + + return RegionRegistry::getInstance().resolvePtr(regionPtr); +} + +Region::~Region() { + RegionRegistry::getInstance().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(); + } + } + + + // Initialise 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]; + + BS_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 vacinity + // 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].distance2(point); + { + for (int i = 1; i < polygon.vertexCount; i++) { + int curDistance2 = polygon.vertices[i].distance2(point); + if (curDistance2 < shortestVertexDistance2) { + closestVertex = polygon.vertices[i]; + shortestVertexDistance2 = curDistance2; + } + } + } + + BS_LOG_WARNINGLN("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 = sqrtf(vector2X * vector2X + vector2Y * vector2Y); + vector2X /= vector2Length; + vector2Y /= vector2Length; + float distance = sqrtf(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 { + BS_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<uint>(_type)); + writer.write(_valid); + writer.write(_position.x); + writer.write(_position.y); + + writer.write(_polygons.size()); + Common::Array<Polygon>::iterator It = _polygons.begin(); + while (It != _polygons.end()) { + Result &= It->persist(writer); + ++It; + } + + writer.write(_boundingBox.left); + writer.write(_boundingBox.top); + writer.write(_boundingBox.right); + writer.write(_boundingBox.bottom); + + return Result; +} + +bool Region::unpersist(InputPersistenceBlock &reader) { + reader.read(_valid); + reader.read(_position.x); + reader.read(_position.y); + + _polygons.clear(); + uint 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 diff --git a/engines/sword25/math/region.h b/engines/sword25/math/region.h new file mode 100644 index 0000000000..fac9f98bb6 --- /dev/null +++ b/engines/sword25/math/region.h @@ -0,0 +1,241 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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); + + virtual ~Region(); + + /** + * Initialises 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 initialised, 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 + // + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +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 { + BS_ASSERT(i < _polygons.size() - 1); + return _polygons[i + 1]; +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/regionregistry.cpp b/engines/sword25/math/regionregistry.cpp new file mode 100644 index 0000000000..1509ea9e5e --- /dev/null +++ b/engines/sword25/math/regionregistry.cpp @@ -0,0 +1,106 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "REGIONREGISTRY" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/math/regionregistry.h" +#include "sword25/math/region.h" + +namespace Sword25 { + +Common::SharedPtr<RegionRegistry> RegionRegistry::_instancePtr; + +void RegionRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void RegionRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +bool RegionRegistry::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // write out the next handle + writer.write(_nextHandle); + + // Number of regions to write + writer.write(_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 + uint regionCount; + reader.read(regionCount); + + // Restore all the BS_Regions objects + for (uint i = 0; i < regionCount; ++i) { + // Handle read + uint handle; + reader.read(handle); + + // BS_Region restore + result &= Region::create(reader, handle) != 0; + } + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/regionregistry.h b/engines/sword25/math/regionregistry.h new file mode 100644 index 0000000000..bbe2fb8370 --- /dev/null +++ b/engines/sword25/math/regionregistry.h @@ -0,0 +1,66 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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/ptr.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: + static RegionRegistry &getInstance() { + if (!_instancePtr.get()) _instancePtr = Common::SharedPtr<RegionRegistry>(new RegionRegistry()); + return *_instancePtr.get(); + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; + + static Common::SharedPtr<RegionRegistry> _instancePtr; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/vertex.cpp b/engines/sword25/math/vertex.cpp new file mode 100644 index 0000000000..4997da09d3 --- /dev/null +++ b/engines/sword25/math/vertex.cpp @@ -0,0 +1,93 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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" + +namespace Lua { + +extern "C" +{ +#include "sword25/util/lua/lua.h" +#include "sword25/util/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 + BS_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 diff --git a/engines/sword25/math/vertex.h b/engines/sword25/math/vertex.h new file mode 100644 index 0000000000..87e4694d48 --- /dev/null +++ b/engines/sword25/math/vertex.h @@ -0,0 +1,180 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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 <math.h> +#include "sword25/kernel/common.h" + +namespace Lua { + +// Forward declarations +struct lua_State; + +} + +using namespace Lua; + +namespace Sword25 { + +/** + * Defines a 2-D Vertex + */ +class Vertex { +public: + Vertex() : x(0), y(0) {}; + Vertex(int x_, int y_) { + this->x = x_; + this->y = y_; + } + + int x; + int y; + + /** + * Compares two Vertecies. + */ + inline bool operator==(const Vertex &rhs) const { + if (x == rhs.x && y == rhs.y) return true; + return false; + } + /** + * Compares two Vertecies. + */ + inline bool operator!=(const Vertex &rhs) const { + if (x != rhs.x || y != rhs.y) return true; + return false; + } + /** + * Adds a vertex to vertex + */ + inline void operator+=(const Vertex &delta) { + x += delta.x; + y += delta.y; + } + + /** + * Subtracts a vertex from a vertex + */ + inline void operator-=(const Vertex &delta) { + x -= delta.x; + y -= delta.y; + } + + /** + * Adds two vertecies + */ + inline Vertex operator+(const Vertex &delta) const { + return Vertex(x + delta.x, y + delta.y); + } + + /** + * Subtracts two vertecies + */ + inline Vertex operator-(const Vertex &delta) const { + return Vertex(x - delta.x, y - delta.y); + } + + /** + * 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, this method should be used because + * it is faster than Distance() + */ + inline int distance2(const Vertex &vertex) const { + return (x - vertex.x) * (x - vertex.x) + (y - vertex.y) * (y - vertex.y); + } + + /** + * 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, Distance2() should be used, since it is faster. + */ + inline int distance(const Vertex &vertex) const { + return (int)(sqrtf(static_cast<float>(distance2(vertex))) + 0.5); + } + + /** + * Calculates the cross product of the vertex with another vertex. Here the Vertecies will be + * interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the cross product of this vertex and the passed vertex. + */ + inline int computeCrossProduct(const Vertex &vertex) const { + return x * vertex.y - vertex.x * y; + } + + /** + * Returns the dot product of this vertex with another vertex. Here the Vertecies are interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the dot product of this vertex and the passed vertex. + */ + inline int computeDotProduct(const Vertex &vertex) const { + return x * vertex.x + y * vertex.y; + } + + /** + * Calculates the angle between this vertex and another vertex. Here the Vertecies are interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the angle between this vertex and the passed vertex in radians. + */ + inline float computeAngle(const Vertex &vertex) const { + return atan2f(static_cast<float>(computeCrossProduct(vertex)), static_cast<float>(computeDotProduct(vertex))); + } + + /** + * Calculates the length of the vector + */ + inline float computeLength() const { + return sqrtf(static_cast<float>(x * x + y * y)); + } + + static Vertex &luaVertexToVertex(lua_State *L, int StackIndex, Vertex &vertex); + static void vertexToLuaVertex(lua_State *L, const Vertex &vertex); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/walkregion.cpp b/engines/sword25/math/walkregion.cpp new file mode 100644 index 0000000000..7cdd8c64c5 --- /dev/null +++ b/engines/sword25/math/walkregion.cpp @@ -0,0 +1,390 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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" + +#define BS_LOG_PREFIX "WALKREGION" + +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) { + BS_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() : cost(Infinity), chosen(false) {}; + ConstIter parentIter; + int cost; + bool chosen; +}; + +static void initDijkstraNodes(DijkstraNode::Container &dijkstraNodes, const Region ®ion, + const Vertex &start, const Common::Array<Vertex> &nodes) { + // Allocate sufficient space in the array + dijkstraNodes.resize(nodes.size()); + + // Initialise 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); + } + BS_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 ®ion) { + if (region.isLineOfSight(curNodePos, endPointPos)) { + int totalCost = (*curNodeIter).cost + curNodePos.distance(endPointPos); + if (totalCost < endPoint.cost) { + endPoint.parentIter = curNodeIter; + endPoint.cost = totalCost; + } + } +} + +bool WalkRegion::findPath(const Vertex &start, const Vertex &end, BS_Path &path) const { + // This is an implementation of Dijkstra's algorithm + + // Initialise 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()) { + BS_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() { + // Initialise 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)) { + BS_LOG_ERRORLN("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)) { + BS_LOG_ERRORLN("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(_nodes.size()); + Common::Array<Vertex>::const_iterator it = _nodes.begin(); + while (it != _nodes.end()) { + writer.write(it->x); + writer.write(it->y); + ++it; + } + + // Persist the visibility matrix + writer.write(_visibilityMatrix.size()); + Common::Array< Common::Array<int> >::const_iterator rowIter = _visibilityMatrix.begin(); + while (rowIter != _visibilityMatrix.end()) { + writer.write(rowIter->size()); + Common::Array<int>::const_iterator colIter = rowIter->begin(); + while (colIter != rowIter->end()) { + writer.write(*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 + uint 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 + uint rowCount; + reader.read(rowCount); + _visibilityMatrix.clear(); + _visibilityMatrix.resize(rowCount); + Common::Array< Common::Array<int> >::iterator rowIter = _visibilityMatrix.begin(); + while (rowIter != _visibilityMatrix.end()) { + uint colCount; + reader.read(colCount); + rowIter->resize(colCount); + Common::Array<int>::iterator colIter = rowIter->begin(); + while (colIter != rowIter->end()) { + reader.read(*colIter); + ++colIter; + } + + ++rowIter; + } + + return result && reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/walkregion.h b/engines/sword25/math/walkregion.h new file mode 100644 index 0000000000..e8bf40becc --- /dev/null +++ b/engines/sword25/math/walkregion.h @@ -0,0 +1,113 @@ +/* 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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * 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: + virtual ~WalkRegion(); + + virtual bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = 0); + + /** + * 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); + + virtual void setPos(int x, int y); + + const Common::Array<Vertex> &getNodes() const { + return _nodes; + } + const Common::Array< Common::Array<int> > &getVisibilityMatrix() const { + return _visibilityMatrix; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +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 |