Initial commit
This commit is contained in:
56
engines/sword25/math/geometry.h
Normal file
56
engines/sword25/math/geometry.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_GEOMETRY_H
|
||||
#define SWORD25_GEOMETRY_H
|
||||
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/kernel/service.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
class Kernel;
|
||||
|
||||
class Geometry : public Service {
|
||||
public:
|
||||
Geometry(Kernel *pKernel) : Service(pKernel) {
|
||||
if (!registerScriptBindings())
|
||||
error("Script bindings could not be registered.");
|
||||
}
|
||||
|
||||
~Geometry() override {}
|
||||
|
||||
private:
|
||||
bool registerScriptBindings();
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
467
engines/sword25/math/geometry_script.cpp
Normal file
467
engines/sword25/math/geometry_script.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/array.h"
|
||||
#include "sword25/gfx/graphicengine.h"
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/kernel/kernel.h"
|
||||
#include "sword25/script/script.h"
|
||||
#include "sword25/script/luabindhelper.h"
|
||||
|
||||
#include "sword25/math/geometry.h"
|
||||
#include "sword25/math/region.h"
|
||||
#include "sword25/math/regionregistry.h"
|
||||
#include "sword25/math/walkregion.h"
|
||||
#include "sword25/math/vertex.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
// These strings are defined as #defines to enable compile-time string composition
|
||||
#define REGION_CLASS_NAME "Geo.Region"
|
||||
#define WALKREGION_CLASS_NAME "Geo.WalkRegion"
|
||||
|
||||
static void newUintUserData(lua_State *L, uint value) {
|
||||
void *userData = lua_newuserdata(L, sizeof(value));
|
||||
memcpy(userData, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static bool isValidPolygonDefinition(lua_State *L) {
|
||||
#ifdef DEBUG
|
||||
int __startStackDepth = lua_gettop(L);
|
||||
#endif
|
||||
|
||||
// Ensure that we actually consider a table
|
||||
if (!lua_istable(L, -1)) {
|
||||
luaL_error(L, "Invalid polygon definition. Unexpected type, \"table\" needed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int tableSize = luaL_getn(L, -1);
|
||||
|
||||
// Make sure that there are at least three Vertecies
|
||||
if (tableSize < 6) {
|
||||
luaL_error(L, "Invalid polygon definition. At least three vertecies needed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that the number of table elements is divisible by two.
|
||||
// Since any two elements is a vertex, an odd number of elements is not allowed
|
||||
if ((tableSize % 2) != 0) {
|
||||
luaL_error(L, "Invalid polygon definition. Even number of table elements needed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that all elements in the table are of type Number
|
||||
for (int i = 1; i <= tableSize; i++) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (!lua_isnumber(L, -1)) {
|
||||
luaL_error(L, "Invalid polygon definition. All table elements have to be numbers.");
|
||||
return false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
assert(__startStackDepth == lua_gettop(L));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tablePolygonToPolygon(lua_State *L, Polygon &polygon) {
|
||||
#ifdef DEBUG
|
||||
int __startStackDepth = lua_gettop(L);
|
||||
#endif
|
||||
|
||||
// Ensure that a valid polygon definition is on the stack.
|
||||
// It is not necessary to catch the return value, since all errors are reported on luaL_error
|
||||
// End script.
|
||||
isValidPolygonDefinition(L);
|
||||
|
||||
int vertexCount = luaL_getn(L, -1) / 2;
|
||||
|
||||
// Memory is reserved for Vertecies
|
||||
Common::Array<Vertex> vertices;
|
||||
vertices.reserve(vertexCount);
|
||||
|
||||
// Create Vertecies
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
// X Value
|
||||
lua_rawgeti(L, -1, (i * 2) + 1);
|
||||
int X = static_cast<int>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Y Value
|
||||
lua_rawgeti(L, -1, (i * 2) + 2);
|
||||
int Y = static_cast<int>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Vertex
|
||||
vertices.push_back(Vertex(X, Y));
|
||||
}
|
||||
assert((int)vertices.size() == vertexCount);
|
||||
|
||||
#ifdef DEBUG
|
||||
assert(__startStackDepth == lua_gettop(L));
|
||||
#endif
|
||||
|
||||
// Create polygon
|
||||
polygon.init(vertexCount, &vertices[0]);
|
||||
}
|
||||
|
||||
static uint tableRegionToRegion(lua_State *L, const char *className) {
|
||||
#ifdef DEBUG
|
||||
int __startStackDepth = lua_gettop(L);
|
||||
#endif
|
||||
|
||||
// You can define a region in Lua in two ways:
|
||||
// 1. A table that defines a polygon (polgon = table with numbers, which define
|
||||
// two consecutive numbers per vertex)
|
||||
// 2. A table containing more polygon definitions
|
||||
// Then the first polygon is the contour of the region, and the following are holes
|
||||
// defined in the first polygon.
|
||||
|
||||
// It may be passed only one parameter, and this must be a table
|
||||
if (lua_gettop(L) != 1 || !lua_istable(L, -1)) {
|
||||
luaL_error(L, "First and only parameter has to be of type \"table\".");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint regionHandle = 0;
|
||||
if (!strcmp(className, REGION_CLASS_NAME)) {
|
||||
regionHandle = Region::create(Region::RT_REGION);
|
||||
} else if (!strcmp(className, WALKREGION_CLASS_NAME)) {
|
||||
regionHandle = WalkRegion::create(Region::RT_WALKREGION);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(regionHandle);
|
||||
|
||||
// If the first element of the parameter is a number, then case 1 is accepted
|
||||
// If the first element of the parameter is a table, then case 2 is accepted
|
||||
// If the first element of the parameter has a different type, there is an error
|
||||
lua_rawgeti(L, -1, 1);
|
||||
int firstElementType = lua_type(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
switch (firstElementType) {
|
||||
case LUA_TNUMBER: {
|
||||
Polygon polygon;
|
||||
tablePolygonToPolygon(L, polygon);
|
||||
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon);
|
||||
}
|
||||
break;
|
||||
|
||||
case LUA_TTABLE: {
|
||||
lua_rawgeti(L, -1, 1);
|
||||
Polygon polygon;
|
||||
tablePolygonToPolygon(L, polygon);
|
||||
lua_pop(L, 1);
|
||||
|
||||
int polygonCount = luaL_getn(L, -1);
|
||||
if (polygonCount == 1)
|
||||
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon);
|
||||
else {
|
||||
Common::Array<Polygon> holes;
|
||||
holes.reserve(polygonCount - 1);
|
||||
|
||||
for (int i = 2; i <= polygonCount; i++) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
holes.resize(holes.size() + 1);
|
||||
tablePolygonToPolygon(L, holes.back());
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
assert((int)holes.size() == polygonCount - 1);
|
||||
|
||||
RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon, &holes);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
luaL_error(L, "Illegal region definition.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
assert(__startStackDepth == lua_gettop(L));
|
||||
#endif
|
||||
|
||||
return regionHandle;
|
||||
}
|
||||
|
||||
static void newUserdataRegion(lua_State *L, const char *className) {
|
||||
// Region due to the Lua code to create
|
||||
// Any errors that occur will be intercepted to the luaL_error
|
||||
uint regionHandle = tableRegionToRegion(L, className);
|
||||
assert(regionHandle);
|
||||
|
||||
newUintUserData(L, regionHandle);
|
||||
// luaL_getmetatable(L, className);
|
||||
LuaBindhelper::getMetatable(L, className);
|
||||
assert(!lua_isnil(L, -1));
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
static int newRegion(lua_State *L) {
|
||||
newUserdataRegion(L, REGION_CLASS_NAME);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int newWalkRegion(lua_State *L) {
|
||||
newUserdataRegion(L, WALKREGION_CLASS_NAME);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *GEO_LIBRARY_NAME = "Geo";
|
||||
|
||||
static const luaL_reg GEO_FUNCTIONS[] = {
|
||||
{"NewRegion", newRegion},
|
||||
{"NewWalkRegion", newWalkRegion},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static Region *checkRegion(lua_State *L) {
|
||||
// The first parameter must be of type 'userdata', and the Metatable class Geo.Region or Geo.WalkRegion
|
||||
uint *regionHandlePtr = reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, REGION_CLASS_NAME));
|
||||
if (regionHandlePtr != 0 ||
|
||||
(regionHandlePtr = reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
|
||||
return RegionRegistry::instance().resolveHandle(*regionHandlePtr);
|
||||
} else {
|
||||
luaL_argcheck(L, 0, 1, "'" REGION_CLASS_NAME "' expected");
|
||||
}
|
||||
|
||||
// Compilation fix. Execution never reaches this point
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int r_isValid(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
lua_pushbooleancpp(L, pR->isValid());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_getX(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
lua_pushnumber(L, pR->getPosX());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_getY(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
lua_pushnumber(L, pR->getPosY());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_getPos(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
Vertex::vertexToLuaVertex(L, pR->getPosition());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_isPointInRegion(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
Vertex vertex;
|
||||
Vertex::luaVertexToVertex(L, 2, vertex);
|
||||
lua_pushbooleancpp(L, pR->isPointInRegion(vertex));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_setPos(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
Vertex vertex;
|
||||
Vertex::luaVertexToVertex(L, 2, vertex);
|
||||
pR->setPos(vertex.x, vertex.y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int r_setX(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
pR->setPosX(static_cast<int>(luaL_checknumber(L, 2)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int r_setY(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
|
||||
pR->setPosY(static_cast<int>(luaL_checknumber(L, 2)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void drawPolygon(const Polygon &polygon, uint color, const Vertex &offset) {
|
||||
GraphicEngine *pGE = Kernel::getInstance()->getGfx();
|
||||
assert(pGE);
|
||||
|
||||
for (int i = 0; i < polygon.vertexCount - 1; i++)
|
||||
pGE->drawDebugLine(polygon.vertices[i] + offset, polygon.vertices[i + 1] + offset, color);
|
||||
|
||||
pGE->drawDebugLine(polygon.vertices[polygon.vertexCount - 1] + offset, polygon.vertices[0] + offset, color);
|
||||
}
|
||||
|
||||
static void drawRegion(const Region ®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);
|
||||
assert(pR);
|
||||
|
||||
switch (lua_gettop(L)) {
|
||||
case 3: {
|
||||
Vertex offset;
|
||||
Vertex::luaVertexToVertex(L, 3, offset);
|
||||
drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), offset);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), Vertex(0, 0));
|
||||
break;
|
||||
|
||||
default:
|
||||
drawRegion(*pR, BS_RGB(255, 255, 255), Vertex(0, 0));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int r_getCentroid(lua_State *L) {
|
||||
Region *RPtr = checkRegion(L);
|
||||
assert(RPtr);
|
||||
|
||||
Vertex::vertexToLuaVertex(L, RPtr->getCentroid());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int r_delete(lua_State *L) {
|
||||
Region *pR = checkRegion(L);
|
||||
assert(pR);
|
||||
delete pR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_reg REGION_METHODS[] = {
|
||||
{"SetPos", r_setPos},
|
||||
{"SetX", r_setX},
|
||||
{"SetY", r_setY},
|
||||
{"GetPos", r_getPos},
|
||||
{"IsPointInRegion", r_isPointInRegion},
|
||||
{"GetX", r_getX},
|
||||
{"GetY", r_getY},
|
||||
{"IsValid", r_isValid},
|
||||
{"Draw", r_draw},
|
||||
{"GetCentroid", r_getCentroid},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static WalkRegion *checkWalkRegion(lua_State *L) {
|
||||
// The first parameter must be of type 'userdate', and the Metatable class Geo.WalkRegion
|
||||
uint regionHandle;
|
||||
if ((regionHandle = *reinterpret_cast<uint *>(LuaBindhelper::my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
|
||||
return reinterpret_cast<WalkRegion *>(RegionRegistry::instance().resolveHandle(regionHandle));
|
||||
} else {
|
||||
luaL_argcheck(L, 0, 1, "'" WALKREGION_CLASS_NAME "' expected");
|
||||
}
|
||||
|
||||
// Compilation fix. Execution never reaches this point
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wr_getPath(lua_State *L) {
|
||||
WalkRegion *pWR = checkWalkRegion(L);
|
||||
assert(pWR);
|
||||
|
||||
Vertex start;
|
||||
Vertex::luaVertexToVertex(L, 2, start);
|
||||
Vertex end;
|
||||
Vertex::luaVertexToVertex(L, 3, end);
|
||||
BS_Path path;
|
||||
if (pWR->queryPath(start, end, path)) {
|
||||
lua_newtable(L);
|
||||
BS_Path::const_iterator it = path.begin();
|
||||
for (; it != path.end(); it++) {
|
||||
lua_pushnumber(L, (it - path.begin()) + 1);
|
||||
Vertex::vertexToLuaVertex(L, *it);
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
} else
|
||||
lua_pushnil(L);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_reg WALKREGION_METHODS[] = {
|
||||
{"GetPath", wr_getPath},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
bool Geometry::registerScriptBindings() {
|
||||
Kernel *pKernel = Kernel::getInstance();
|
||||
assert(pKernel);
|
||||
ScriptEngine *pScript = pKernel->getScript();
|
||||
assert(pScript);
|
||||
lua_State *L = static_cast< lua_State *>(pScript->getScriptObject());
|
||||
assert(L);
|
||||
|
||||
if (!LuaBindhelper::addMethodsToClass(L, REGION_CLASS_NAME, REGION_METHODS)) return false;
|
||||
if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, REGION_METHODS)) return false;
|
||||
if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, WALKREGION_METHODS)) return false;
|
||||
|
||||
if (!LuaBindhelper::setClassGCHandler(L, REGION_CLASS_NAME, r_delete)) return false;
|
||||
if (!LuaBindhelper::setClassGCHandler(L, WALKREGION_CLASS_NAME, r_delete)) return false;
|
||||
|
||||
if (!LuaBindhelper::addFunctionsToLib(L, GEO_LIBRARY_NAME, GEO_FUNCTIONS)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
194
engines/sword25/math/line.h
Normal file
194
engines/sword25/math/line.h
Normal file
@@ -0,0 +1,194 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
BS_Line
|
||||
-------
|
||||
This class contains only static methods, which have to do with straight line
|
||||
segments. There is no real straight line segment class. Calculations will be
|
||||
used with polygons, and it is important the process of starting and selecting
|
||||
endpoints of lines is dynamic. This would prhobit a polygon from a set
|
||||
being formed by fixed line segments
|
||||
|
||||
Autor: Malte Thiesen
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_LINE_H
|
||||
#define SWORD25_LINE_H
|
||||
|
||||
#include "sword25/kernel/common.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
class Line {
|
||||
public:
|
||||
/**
|
||||
* Determines whether a piont is left of a line
|
||||
* @param a The start point of a line
|
||||
* @param b The end point of a line
|
||||
* @param c The test point
|
||||
* @return Returns true if the point is to the left of the line.
|
||||
* If the point is to the right of the line or on the line, false is returned.
|
||||
*/
|
||||
static bool isVertexLeft(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return triangleArea2(a, b, c) > 0;
|
||||
}
|
||||
|
||||
static bool isVertexLeftOn(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return triangleArea2(a, b, c) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a piont is right of a line
|
||||
* @param a The start point of a line
|
||||
* @param b The end point of a line
|
||||
* @param c The test point
|
||||
* @return Returns true if the point is to the right of the line.
|
||||
* If the point is to the right of the line or on the line, false is returned.
|
||||
*/
|
||||
static bool isVertexRight(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return triangleArea2(a, b, c) < 0;
|
||||
}
|
||||
|
||||
static bool isVertexRightOn(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return triangleArea2(a, b, c) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a piont is on a line
|
||||
* @param a The start point of a line
|
||||
* @param b The end point of a line
|
||||
* @param c The test point
|
||||
* @return Returns true if the point is on the line, false otherwise.
|
||||
*/
|
||||
static bool isVertexOn(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return triangleArea2(a, b, c) == 0;
|
||||
}
|
||||
|
||||
enum VERTEX_CLASSIFICATION {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
ON
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines where a point is relative to a line.
|
||||
* @param a The start point of a line
|
||||
* @param b The end point of a line
|
||||
* @param c The test point
|
||||
* @return LEFT is returned if the point is to the left of the line.
|
||||
* RIGHT is returned if the point is to the right of the line.
|
||||
* ON is returned if the point is on the line.
|
||||
*/
|
||||
static VERTEX_CLASSIFICATION classifyVertexToLine(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
int area = triangleArea2(a, b, c);
|
||||
if (area > 0) return LEFT;
|
||||
if (area < 0) return RIGHT;
|
||||
return ON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two lines intersect
|
||||
* @param a The start point of the first line
|
||||
* @param b The end point of the first line
|
||||
* @param c The start point of the second line
|
||||
* @param d The end point of the second line
|
||||
* @remark In cases where a line only touches the other, false is returned (improper intersection)
|
||||
*/
|
||||
static bool doesIntersectProperly(const Vertex &a, const Vertex &b, const Vertex &c, const Vertex &d) {
|
||||
VERTEX_CLASSIFICATION class1 = classifyVertexToLine(a, b, c);
|
||||
VERTEX_CLASSIFICATION class2 = classifyVertexToLine(a, b, d);
|
||||
VERTEX_CLASSIFICATION class3 = classifyVertexToLine(c, d, a);
|
||||
VERTEX_CLASSIFICATION class4 = classifyVertexToLine(c, d, b);
|
||||
|
||||
if (class1 == ON || class2 == ON || class3 == ON || class4 == ON) return false;
|
||||
|
||||
return ((class1 == LEFT) ^(class2 == LEFT)) && ((class3 == LEFT) ^(class4 == LEFT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is on a line segment
|
||||
* @param a The start point of a line
|
||||
* @param b The end point of a line
|
||||
* @param c The test point
|
||||
*/
|
||||
static bool isOnLine(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
// The items must all be Collinear, otherwise don't bothering testing the point
|
||||
if (triangleArea2(a, b, c) != 0) return false;
|
||||
|
||||
// If the line segment is not vertical, check on the x-axis, otherwise the y-axis
|
||||
if (a.x != b.x) {
|
||||
return ((a.x <= c.x) &&
|
||||
(c.x <= b.x)) ||
|
||||
((a.x >= c.x) &&
|
||||
(c.x >= b.x));
|
||||
} else {
|
||||
return ((a.y <= c.y) &&
|
||||
(c.y <= b.y)) ||
|
||||
((a.y >= c.y) &&
|
||||
(c.y >= b.y));
|
||||
}
|
||||
}
|
||||
|
||||
static bool isOnLineStrict(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
// The items must all be Collinear, otherwise don't bothering testing the point
|
||||
if (triangleArea2(a, b, c) != 0) return false;
|
||||
|
||||
// If the line segment is not vertical, check on the x-axis, otherwise the y-axis
|
||||
if (a.x != b.x) {
|
||||
return ((a.x < c.x) &&
|
||||
(c.x < b.x)) ||
|
||||
((a.x > c.x) &&
|
||||
(c.x > b.x));
|
||||
} else {
|
||||
return ((a.y < c.y) &&
|
||||
(c.y < b.y)) ||
|
||||
((a.y > c.y) &&
|
||||
(c.y > b.y));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return double the size of the triangle defined by the three passed points.
|
||||
*
|
||||
* The result is positive if the points are arrange counterclockwise,
|
||||
* and negative if they are arranged counter-clockwise.
|
||||
*/
|
||||
static int triangleArea2(const Vertex &a, const Vertex &b, const Vertex &c) {
|
||||
return a.x * b.y - a.y * b.x +
|
||||
a.y * c.x - a.x * c.y +
|
||||
b.x * c.y - c.x * b.y;
|
||||
}
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
419
engines/sword25/math/polygon.cpp
Normal file
419
engines/sword25/math/polygon.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sword25/kernel/outputpersistenceblock.h"
|
||||
#include "sword25/kernel/inputpersistenceblock.h"
|
||||
|
||||
#include "sword25/math/polygon.h"
|
||||
#include "sword25/math/line.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
Polygon::Polygon() : vertexCount(0), vertices(NULL) {
|
||||
}
|
||||
|
||||
Polygon::Polygon(int vertexCount_, const Vertex *vertices_) : vertexCount(0), vertices(NULL) {
|
||||
init(vertexCount_, vertices_);
|
||||
}
|
||||
|
||||
Polygon::Polygon(const Polygon &other) : Persistable(other), vertexCount(0), vertices(NULL) {
|
||||
init(other.vertexCount, other.vertices);
|
||||
}
|
||||
|
||||
Polygon::Polygon(InputPersistenceBlock &Reader) : vertexCount(0), vertices(NULL) {
|
||||
unpersist(Reader);
|
||||
}
|
||||
|
||||
Polygon::~Polygon() {
|
||||
delete[] vertices;
|
||||
}
|
||||
|
||||
bool Polygon::init(int vertexCount_, const Vertex *vertices_) {
|
||||
// Rember the old obstate to restore it if an error occurs whilst initializing it with the new data
|
||||
int oldvertexCount = this->vertexCount;
|
||||
Vertex *oldvertices = this->vertices;
|
||||
|
||||
this->vertexCount = vertexCount_;
|
||||
this->vertices = new Vertex[vertexCount_ + 1];
|
||||
memcpy(this->vertices, vertices_, sizeof(Vertex) * vertexCount_);
|
||||
// TODO:
|
||||
// Duplicate and remove redundant vertecies (Superflous = 3 co-linear verts)
|
||||
// _WeedRepeatedvertices();
|
||||
// The first vertex is repeated at the end of the vertex array; this simplifies
|
||||
// some algorithms, running through the edges and thus can save the overflow control.
|
||||
this->vertices[vertexCount_] = this->vertices[0];
|
||||
|
||||
// If the polygon is self-intersecting, the object state is restore, and an error signalled
|
||||
if (checkForSelfIntersection()) {
|
||||
delete[] this->vertices;
|
||||
this->vertices = oldvertices;
|
||||
this->vertexCount = oldvertexCount;
|
||||
|
||||
// BS_LOG_ERROR("POLYGON: Tried to create a self-intersecting polygon.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release old vertex list
|
||||
delete[] oldvertices;
|
||||
|
||||
// Calculate properties of the polygon
|
||||
_isCW = computeIsCW();
|
||||
_centroid = computeCentroid();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Review the order of the vertices
|
||||
// ---------------------------------
|
||||
|
||||
bool Polygon::isCW() const {
|
||||
return _isCW;
|
||||
}
|
||||
bool Polygon::computeIsCW() const {
|
||||
if (vertexCount) {
|
||||
// Find the vertex on extreme bottom right
|
||||
int v2Index = findLRVertexIndex();
|
||||
|
||||
// Find the vertex before and after it
|
||||
int v1Index = (v2Index + (vertexCount - 1)) % vertexCount;
|
||||
int v3Index = (v2Index + 1) % vertexCount;
|
||||
|
||||
// Cross product form
|
||||
// If the cross product of the vertex lying fartherest bottom left is positive,
|
||||
// the vertecies arranged in a clockwise order. Otherwise counter-clockwise
|
||||
if (crossProduct(vertices[v1Index], vertices[v2Index], vertices[v3Index]) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Polygon::findLRVertexIndex() const {
|
||||
if (vertexCount) {
|
||||
int curIndex = 0;
|
||||
int maxX = vertices[0].x;
|
||||
int maxY = vertices[0].y;
|
||||
|
||||
for (int i = 1; i < vertexCount; i++) {
|
||||
if (vertices[i].y > maxY ||
|
||||
(vertices[i].y == maxY && vertices[i].x > maxX)) {
|
||||
maxX = vertices[i].x;
|
||||
maxY = vertices[i].y;
|
||||
curIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return curIndex;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
// Make a determine vertex order
|
||||
// -----------------------------
|
||||
|
||||
void Polygon::ensureCWOrder() {
|
||||
if (!isCW())
|
||||
reverseVertexOrder();
|
||||
}
|
||||
// Reverse the order of vertecies
|
||||
// ------------------------------
|
||||
|
||||
void Polygon::reverseVertexOrder() {
|
||||
// vertices are exchanged in pairs, until the list has been completely reversed
|
||||
for (int i = 0; i < vertexCount / 2; i++) {
|
||||
Vertex tempVertex = vertices[i];
|
||||
vertices[i] = vertices[vertexCount - i - 1];
|
||||
vertices[vertexCount - i - 1] = tempVertex;
|
||||
}
|
||||
|
||||
// Vertexordnung neu berechnen.
|
||||
_isCW = computeIsCW();
|
||||
}
|
||||
|
||||
// Cross Product
|
||||
// -------------
|
||||
|
||||
int Polygon::crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const {
|
||||
return (v2.x - v1.x) * (v3.y - v2.y) -
|
||||
(v2.y - v1.y) * (v3.x - v2.x);
|
||||
}
|
||||
// Check for self-intersections
|
||||
// ----------------------------
|
||||
|
||||
bool Polygon::checkForSelfIntersection() const {
|
||||
// TODO: Finish this
|
||||
/*
|
||||
float AngleSum = 0.0f;
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
int j = (i + 1) % vertexCount;
|
||||
int k = (i + 2) % vertexCount;
|
||||
|
||||
float Dot = DotProduct(vertices[i], vertices[j], vertices[k]);
|
||||
|
||||
// Skalarproduct normalisieren
|
||||
float Length1 = sqrt((vertices[i].x - vertices[j].x) * (vertices[i].x - vertices[j].x) +
|
||||
(vertices[i].y - vertices[j].y) * (vertices[i].y - vertices[j].y));
|
||||
float Length2 = sqrt((vertices[k].x - vertices[j].x) * (vertices[k].x - vertices[j].x) +
|
||||
(vertices[k].y - vertices[j].y) * (vertices[k].y - vertices[j].y));
|
||||
float Norm = Length1 * Length2;
|
||||
|
||||
if (Norm > 0.0f) {
|
||||
Dot /= Norm;
|
||||
AngleSum += acos(Dot);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move
|
||||
// ----
|
||||
|
||||
void Polygon::operator+=(const Vertex &delta) {
|
||||
// Move all vertecies
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
vertices[i] += delta;
|
||||
|
||||
// Shift the focus
|
||||
_centroid += delta;
|
||||
}
|
||||
|
||||
// Line of Sight
|
||||
// -------------
|
||||
|
||||
bool Polygon::isLineInterior(const Vertex &a, const Vertex &b) const {
|
||||
// Both points have to be in the polygon
|
||||
if (!isPointInPolygon(a, true) || !isPointInPolygon(b, true))
|
||||
return false;
|
||||
|
||||
// If the points are identical, the line is trivially within the polygon
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
// Test whether the line intersects a line segment strictly (proper intersection)
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
int j = (i + 1) % vertexCount;
|
||||
const Vertex &vs = vertices[i];
|
||||
const Vertex &ve = vertices[j];
|
||||
|
||||
// If the line intersects a line segment strictly (proper cross section) the line is not in the polygon
|
||||
if (Line::doesIntersectProperly(a, b, vs, ve))
|
||||
return false;
|
||||
|
||||
// If one of the two line items is on the edge and the other is to the right of the edge,
|
||||
// then the line is not completely within the polygon
|
||||
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexRight(vs, ve, b))
|
||||
return false;
|
||||
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexRight(vs, ve, a))
|
||||
return false;
|
||||
|
||||
// If one of the two line items is on a vertex, the line traces into the polygon
|
||||
if ((a == vs) && !isLineInCone(i, b, true))
|
||||
return false;
|
||||
if ((b == vs) && !isLineInCone(i, a, true))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Polygon::isLineExterior(const Vertex &a, const Vertex &b) const {
|
||||
// Neither of the two points must be strictly in the polygon (on the edge is allowed)
|
||||
if (isPointInPolygon(a, false) || isPointInPolygon(b, false))
|
||||
return false;
|
||||
|
||||
// If the points are identical, the line is trivially outside of the polygon
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
// Test whether the line intersects a line segment strictly (proper intersection)
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
int j = (i + 1) % vertexCount;
|
||||
const Vertex &vs = vertices[i];
|
||||
const Vertex &ve = vertices[j];
|
||||
|
||||
// If the line intersects a line segment strictly (proper intersection), then
|
||||
// the line is partially inside the polygon
|
||||
if (Line::doesIntersectProperly(a, b, vs, ve))
|
||||
return false;
|
||||
|
||||
// If one of the two line items is on the edge and the other is to the right of the edge,
|
||||
// the line is not completely outside the polygon
|
||||
if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexLeft(vs, ve, b))
|
||||
return false;
|
||||
if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexLeft(vs, ve, a))
|
||||
return false;
|
||||
|
||||
// If one of the lwo line items is on a vertex, the line must not run into the polygon
|
||||
if ((a == vs) && isLineInCone(i, b, false))
|
||||
return false;
|
||||
if ((b == vs) && isLineInCone(i, a, false))
|
||||
return false;
|
||||
|
||||
// If the vertex with start and end point is collinear, (a vs) and (b, vs) is not in the polygon
|
||||
if (Line::isOnLine(a, b, vs)) {
|
||||
if (isLineInCone(i, a, false))
|
||||
return false;
|
||||
if (isLineInCone(i, b, false))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Polygon::isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const {
|
||||
const Vertex &startVertex = vertices[startVertexIndex];
|
||||
const Vertex &nextVertex = vertices[(startVertexIndex + 1) % vertexCount];
|
||||
const Vertex &prevVertex = vertices[(startVertexIndex + vertexCount - 1) % vertexCount];
|
||||
|
||||
if (Line::isVertexLeftOn(prevVertex, startVertex, nextVertex)) {
|
||||
if (includeEdges)
|
||||
return Line::isVertexLeftOn(endVertex, startVertex, nextVertex) &&
|
||||
Line::isVertexLeftOn(startVertex, endVertex, prevVertex);
|
||||
else
|
||||
return Line::isVertexLeft(endVertex, startVertex, nextVertex) &&
|
||||
Line::isVertexLeft(startVertex, endVertex, prevVertex);
|
||||
} else {
|
||||
if (includeEdges)
|
||||
return !(Line::isVertexLeft(endVertex, startVertex, prevVertex) &&
|
||||
Line::isVertexLeft(startVertex, endVertex, nextVertex));
|
||||
else
|
||||
return !(Line::isVertexLeftOn(endVertex, startVertex, prevVertex) &&
|
||||
Line::isVertexLeftOn(startVertex, endVertex, nextVertex));
|
||||
}
|
||||
}
|
||||
|
||||
// Point-Polygon Tests
|
||||
// -------------------
|
||||
|
||||
bool Polygon::isPointInPolygon(int x, int y, bool borderBelongsToPolygon) const {
|
||||
return isPointInPolygon(Vertex(x, y), borderBelongsToPolygon);
|
||||
}
|
||||
|
||||
bool Polygon::isPointInPolygon(const Vertex &point, bool edgesBelongToPolygon) const {
|
||||
int rcross = 0; // Number of right-side overlaps
|
||||
int lcross = 0; // Number of left-side overlaps
|
||||
|
||||
// Each edge is checked whether it cuts the outgoing stream from the point
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
const Vertex &edgeStart = vertices[i];
|
||||
const Vertex &edgeEnd = vertices[(i + 1) % vertexCount];
|
||||
|
||||
// A vertex is a point? Then it lies on one edge of the polygon
|
||||
if (point == edgeStart)
|
||||
return edgesBelongToPolygon;
|
||||
|
||||
if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
|
||||
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
|
||||
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
|
||||
if ((term1 > 0) == (term2 >= 0))
|
||||
rcross++;
|
||||
}
|
||||
|
||||
if ((edgeStart.y < point.y) != (edgeEnd.y < point.y)) {
|
||||
int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
|
||||
int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
|
||||
if ((term1 < 0) == (term2 <= 0))
|
||||
lcross++;
|
||||
}
|
||||
}
|
||||
|
||||
// The point is on an adge, if the number of left and right intersections have the same even numbers
|
||||
if ((rcross % 2) != (lcross % 2))
|
||||
return edgesBelongToPolygon;
|
||||
|
||||
// The point is strictly inside the polygon if and only if the number of overlaps is odd
|
||||
if ((rcross % 2) == 1)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Polygon::persist(OutputPersistenceBlock &writer) {
|
||||
writer.write(vertexCount);
|
||||
for (int i = 0; i < vertexCount; ++i) {
|
||||
writer.write((int32)vertices[i].x);
|
||||
writer.write((int32)vertices[i].y);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Polygon::unpersist(InputPersistenceBlock &reader) {
|
||||
int32 storedvertexCount;
|
||||
reader.read(storedvertexCount);
|
||||
|
||||
Common::Array<Vertex> storedvertices;
|
||||
for (int i = 0; i < storedvertexCount; ++i) {
|
||||
int32 x, y;
|
||||
reader.read(x);
|
||||
reader.read(y);
|
||||
storedvertices.push_back(Vertex(x, y));
|
||||
}
|
||||
|
||||
init(storedvertexCount, &storedvertices[0]);
|
||||
|
||||
return reader.isGood();
|
||||
}
|
||||
|
||||
// Main Focus
|
||||
// ----------
|
||||
|
||||
Vertex Polygon::getCentroid() const {
|
||||
return _centroid;
|
||||
}
|
||||
|
||||
Vertex Polygon::computeCentroid() const {
|
||||
// Area of the polygon is calculated
|
||||
int doubleArea = 0;
|
||||
for (int i = 0; i < vertexCount; ++i) {
|
||||
doubleArea += vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
|
||||
}
|
||||
|
||||
// Avoid division by zero in the next step
|
||||
if (doubleArea == 0)
|
||||
return Vertex();
|
||||
|
||||
// Calculate centroid
|
||||
Vertex centroid;
|
||||
for (int i = 0; i < vertexCount; ++i) {
|
||||
int area = vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y;
|
||||
centroid.x += (vertices[i].x + vertices[i + 1].x) * area;
|
||||
centroid.y += (vertices[i].y + vertices[i + 1].y) * area;
|
||||
}
|
||||
centroid.x /= 3 * doubleArea;
|
||||
centroid.y /= 3 * doubleArea;
|
||||
|
||||
return centroid;
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
229
engines/sword25/math/polygon.h
Normal file
229
engines/sword25/math/polygon.h
Normal file
@@ -0,0 +1,229 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_POLYGON_H
|
||||
#define SWORD25_POLYGON_H
|
||||
|
||||
// Includes
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/kernel/persistable.h"
|
||||
#include "sword25/math/vertex.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
class Vertex;
|
||||
|
||||
/**
|
||||
@brief Eine Polygonklasse.
|
||||
*/
|
||||
class Polygon : public Persistable {
|
||||
public:
|
||||
/**
|
||||
* Creates an object of type #BS_Polygon, containing 0 Vertecies.
|
||||
*
|
||||
* With the method Init(), Vertices can be added in later
|
||||
*/
|
||||
Polygon();
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
*/
|
||||
Polygon(const Polygon &other);
|
||||
|
||||
/**
|
||||
* Creates a polygon using persisted data
|
||||
*/
|
||||
Polygon(InputPersistenceBlock &reader);
|
||||
|
||||
/**
|
||||
* Creaes an object of type #BS_Polygon, and assigns Vertices to it
|
||||
* @param VertexCount The number of vertices being passed
|
||||
* @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon.
|
||||
* @remark The Vertecies that define a polygon must not have any self-intersections.
|
||||
* If the polygon does have self-intersections, then an empty polygon object is created.
|
||||
*/
|
||||
Polygon(int vertexCount_, const Vertex *vertices_);
|
||||
|
||||
/**
|
||||
* Deletes the BS_Polygon object
|
||||
*/
|
||||
~Polygon() override;
|
||||
|
||||
/**
|
||||
* Initializes the BS_Polygon with a list of Vertecies.
|
||||
*
|
||||
* The Vertices need to define a polygon must not have self-intersections.
|
||||
* If a polygon already has verticies, this will re-initialize it with the new list.
|
||||
*
|
||||
* @param VertexCount The number of vertices being passed
|
||||
* @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon.
|
||||
* @return Returns false if the Vertecies have self-intersections. In this case,
|
||||
* the object is not initialized.
|
||||
*/
|
||||
bool init(int vertexCount_, const Vertex *vertices_);
|
||||
|
||||
//
|
||||
// ** Exploratory methods **
|
||||
//
|
||||
|
||||
/**
|
||||
* Checks whether the Vertecies of the polygon are arranged in a clockwise direction.
|
||||
* @return Returns true if the Vertecies of the polygon are arranged clockwise or co-planar.
|
||||
* Returns false if the Vertecies of the polygon are arrange counter-clockwise.
|
||||
* @remark This method only returns a meaningful result if the polygon has at least three Vertecies.
|
||||
*/
|
||||
bool isCW() const;
|
||||
|
||||
/**
|
||||
* Checks whether the Vertices of the polygon are arranged in a counter-clockwise direction.
|
||||
* @return Returns true if the Vertecies of the polygon are arranged counter-clockwise.
|
||||
* Returns false if the Vertecies of the polygon are arranged clockwise or co-planar.
|
||||
* @remark This method only returns a meaningful result if the polygon has at least three Vertecies.
|
||||
*/
|
||||
bool isCCW() const;
|
||||
|
||||
/**
|
||||
* Checks whether a point is inside the polygon
|
||||
* @param Vertex A Vertex with the co-ordinates of the point to be tested.
|
||||
* @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered
|
||||
* @return Returns true if the point is inside the polygon, false if it is outside.
|
||||
*/
|
||||
bool isPointInPolygon(const Vertex &vertex, bool borderBelongsToPolygon = true) const;
|
||||
|
||||
/**
|
||||
* Checks whether a point is inside the polygon
|
||||
* @param X The X position of the point
|
||||
* @param Y The Y position of the point
|
||||
* @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered
|
||||
* @return Returns true if the point is inside the polygon, false if it is outside.
|
||||
*/
|
||||
bool isPointInPolygon(int x, int y, bool borderBelongsToPolygon = true) const;
|
||||
|
||||
/**
|
||||
* Returns the focus/centroid of the polygon
|
||||
*/
|
||||
Vertex getCentroid() const;
|
||||
|
||||
// Edge belongs to the polygon
|
||||
// Polygon must be CW
|
||||
bool isLineInterior(const Vertex &a, const Vertex &b) const;
|
||||
// Edge does not belong to the polygon
|
||||
// Polygon must be CW
|
||||
bool isLineExterior(const Vertex &a, const Vertex &b) const;
|
||||
|
||||
//
|
||||
// Manipulation methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Ensures that the Vertecies of the polygon are arranged in a clockwise direction
|
||||
*/
|
||||
void ensureCWOrder();
|
||||
|
||||
/**
|
||||
* Ensures that the Vertecies of the polygon are arranged in a counter-clockwise direction
|
||||
*/
|
||||
void ensureCCWOrder();
|
||||
|
||||
/**
|
||||
* Reverses the Vertecies order.
|
||||
*/
|
||||
void reverseVertexOrder();
|
||||
|
||||
/**
|
||||
* Moves the polygon.
|
||||
* @param Delta The vertex around the polygon to be moved.
|
||||
*/
|
||||
void operator+=(const Vertex &delta);
|
||||
|
||||
//
|
||||
//------------------
|
||||
//
|
||||
|
||||
/// Specifies the number of Vertecies in the Vertecies array.
|
||||
int32 vertexCount;
|
||||
/// COntains the Vertecies of the polygon
|
||||
Vertex *vertices;
|
||||
|
||||
bool persist(OutputPersistenceBlock &writer) override;
|
||||
bool unpersist(InputPersistenceBlock &reader) override;
|
||||
|
||||
Polygon &operator=(const Polygon &p) {
|
||||
init(p.vertexCount, p.vertices);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _isCW;
|
||||
Vertex _centroid;
|
||||
|
||||
/**
|
||||
* Computes the centroid of the polygon.
|
||||
*/
|
||||
Vertex computeCentroid() const;
|
||||
|
||||
/**
|
||||
* Determines how the Vertecies of the polygon are arranged.
|
||||
* @return Returns true if the Vertecies are arranged in a clockwise
|
||||
* direction, otherwise false.
|
||||
*/
|
||||
bool computeIsCW() const;
|
||||
|
||||
/**
|
||||
* Calculates the cross product of three Vertecies
|
||||
* @param V1 The first Vertex
|
||||
* @param V2 The second Vertex
|
||||
* @param V3 The third Vertex
|
||||
* @return Returns the cross-product of the three vertecies
|
||||
* @todo This method would be better as a method of the BS_Vertex class
|
||||
*/
|
||||
int crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const;
|
||||
|
||||
/**
|
||||
* Checks whether the polygon is self-intersecting
|
||||
* @return Returns true if the polygon is self-intersecting.
|
||||
* Returns false if the polygon is not self-intersecting.
|
||||
*/
|
||||
bool checkForSelfIntersection() const;
|
||||
|
||||
/**
|
||||
* Find the vertex of the polygon that is located below the right-most point,
|
||||
* and returns it's index in the vertex array.
|
||||
* @return Returns the index of the vertex at the bottom-right of the polygon.
|
||||
* Returns -1 if the vertex list is empty.
|
||||
*/
|
||||
int findLRVertexIndex() const;
|
||||
|
||||
bool isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const;
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
348
engines/sword25/math/region.cpp
Normal file
348
engines/sword25/math/region.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sword25/kernel/inputpersistenceblock.h"
|
||||
#include "sword25/kernel/outputpersistenceblock.h"
|
||||
|
||||
#include "sword25/math/region.h"
|
||||
#include "sword25/math/walkregion.h"
|
||||
#include "sword25/math/regionregistry.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
Region::Region() : _valid(false), _type(RT_REGION) {
|
||||
RegionRegistry::instance().registerObject(this);
|
||||
}
|
||||
|
||||
Region::Region(InputPersistenceBlock &reader, uint handle) : _valid(false), _type(RT_REGION) {
|
||||
RegionRegistry::instance().registerObject(this, handle);
|
||||
unpersist(reader);
|
||||
}
|
||||
|
||||
uint Region::create(REGION_TYPE type) {
|
||||
Region *regionPtr = NULL;
|
||||
switch (type) {
|
||||
case RT_REGION:
|
||||
regionPtr = new Region();
|
||||
break;
|
||||
|
||||
case RT_WALKREGION:
|
||||
regionPtr = new WalkRegion();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(true);
|
||||
}
|
||||
|
||||
return RegionRegistry::instance().resolvePtr(regionPtr);
|
||||
}
|
||||
|
||||
uint Region::create(InputPersistenceBlock &reader, uint handle) {
|
||||
// Read type
|
||||
uint32 type;
|
||||
reader.read(type);
|
||||
|
||||
// Depending on the type, create a new BS_Region or BS_WalkRegion object
|
||||
Region *regionPtr = NULL;
|
||||
if (type == RT_REGION) {
|
||||
regionPtr = new Region(reader, handle);
|
||||
} else if (type == RT_WALKREGION) {
|
||||
regionPtr = new WalkRegion(reader, handle);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return RegionRegistry::instance().resolvePtr(regionPtr);
|
||||
}
|
||||
|
||||
Region::~Region() {
|
||||
RegionRegistry::instance().deregisterObject(this);
|
||||
}
|
||||
|
||||
bool Region::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) {
|
||||
// Reset object state
|
||||
_valid = false;
|
||||
_position = Vertex(0, 0);
|
||||
_polygons.clear();
|
||||
|
||||
// Reserve sufficient space for countour and holes in the polygon list
|
||||
if (pHoles)
|
||||
_polygons.reserve(1 + pHoles->size());
|
||||
else
|
||||
_polygons.reserve(1);
|
||||
|
||||
// The first polygon will be the contour
|
||||
_polygons.push_back(Polygon());
|
||||
_polygons[0].init(contour.vertexCount, contour.vertices);
|
||||
// Make sure that the Vertecies in the Contour are arranged in a clockwise direction
|
||||
_polygons[0].ensureCWOrder();
|
||||
|
||||
// Place the hole polygons in the following positions
|
||||
if (pHoles) {
|
||||
for (uint i = 0; i < pHoles->size(); ++i) {
|
||||
_polygons.push_back(Polygon());
|
||||
_polygons[i + 1].init((*pHoles)[i].vertexCount, (*pHoles)[i].vertices);
|
||||
_polygons[i + 1].ensureCWOrder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize bounding box
|
||||
updateBoundingBox();
|
||||
|
||||
_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Region::updateBoundingBox() {
|
||||
if (_polygons[0].vertexCount) {
|
||||
int minX = _polygons[0].vertices[0].x;
|
||||
int maxX = _polygons[0].vertices[0].x;
|
||||
int minY = _polygons[0].vertices[0].y;
|
||||
int maxY = _polygons[0].vertices[0].y;
|
||||
|
||||
for (int i = 1; i < _polygons[0].vertexCount; i++) {
|
||||
if (_polygons[0].vertices[i].x < minX) minX = _polygons[0].vertices[i].x;
|
||||
else if (_polygons[0].vertices[i].x > maxX) maxX = _polygons[0].vertices[i].x;
|
||||
if (_polygons[0].vertices[i].y < minY) minY = _polygons[0].vertices[i].y;
|
||||
else if (_polygons[0].vertices[i].y > maxY) maxY = _polygons[0].vertices[i].y;
|
||||
}
|
||||
|
||||
_boundingBox = Common::Rect(minX, minY, maxX + 1, maxY + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Position Changes
|
||||
void Region::setPos(int x, int y) {
|
||||
// Calculate the difference between the old and new position
|
||||
Vertex delta(x - _position.x, y - _position.y);
|
||||
|
||||
// Save the new position
|
||||
_position = Vertex(x, y);
|
||||
|
||||
// Move all the vertecies
|
||||
for (uint i = 0; i < _polygons.size(); ++i) {
|
||||
_polygons[i] += delta;
|
||||
}
|
||||
|
||||
// Update the bounding box
|
||||
updateBoundingBox();
|
||||
}
|
||||
|
||||
void Region::setPosX(int x) {
|
||||
setPos(x, _position.y);
|
||||
}
|
||||
|
||||
void Region::setPosY(int y) {
|
||||
setPos(_position.x, y);
|
||||
}
|
||||
|
||||
// Point-Region Tests
|
||||
bool Region::isPointInRegion(int x, int y) const {
|
||||
// Test whether the point is in the bounding box
|
||||
if (_boundingBox.contains(x, y)) {
|
||||
// Test whether the point is in the contour
|
||||
if (_polygons[0].isPointInPolygon(x, y, true)) {
|
||||
// Test whether the point is in a hole
|
||||
for (uint i = 1; i < _polygons.size(); i++) {
|
||||
if (_polygons[i].isPointInPolygon(x, y, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Region::isPointInRegion(const Vertex &vertex) const {
|
||||
return isPointInRegion(vertex.x, vertex.y);
|
||||
}
|
||||
|
||||
Vertex Region::findClosestRegionPoint(const Vertex &point) const {
|
||||
// Determine whether the point is inside a hole. If that is the case, the closest
|
||||
// point on the edge of the hole is determined
|
||||
int polygonIdx = 0;
|
||||
{
|
||||
for (uint i = 1; i < _polygons.size(); ++i) {
|
||||
if (_polygons[i].isPointInPolygon(point)) {
|
||||
polygonIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Polygon &polygon = _polygons[polygonIdx];
|
||||
|
||||
assert(polygon.vertexCount > 1);
|
||||
|
||||
// For each line of the polygon, calculate the point that is cloest to the given point
|
||||
// The point of this set with the smallest distance to the given point is the result.
|
||||
Vertex closestVertex = findClosestPointOnLine(polygon.vertices[0], polygon.vertices[1], point);
|
||||
int closestVertexDistance2 = closestVertex.distance(point);
|
||||
for (int i = 1; i < polygon.vertexCount; ++i) {
|
||||
int j = (i + 1) % polygon.vertexCount;
|
||||
|
||||
Vertex curVertex = findClosestPointOnLine(polygon.vertices[i], polygon.vertices[j], point);
|
||||
if (curVertex.distance(point) < closestVertexDistance2) {
|
||||
closestVertex = curVertex;
|
||||
closestVertexDistance2 = curVertex.distance(point);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether the point is really within the region. This must not be so, as a result of rounding
|
||||
// errors can occur at the edge of polygons
|
||||
if (isPointInRegion(closestVertex))
|
||||
return closestVertex;
|
||||
else {
|
||||
// Try to construct a point within the region - 8 points are tested in the immediate vicinity
|
||||
// of the point
|
||||
if (isPointInRegion(closestVertex + Vertex(-2, -2)))
|
||||
return closestVertex + Vertex(-2, -2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(0, -2)))
|
||||
return closestVertex + Vertex(0, -2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(2, -2)))
|
||||
return closestVertex + Vertex(2, -2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
|
||||
return closestVertex + Vertex(-2, 0);
|
||||
else if (isPointInRegion(closestVertex + Vertex(0, 2)))
|
||||
return closestVertex + Vertex(0, 2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(-2, 2)))
|
||||
return closestVertex + Vertex(-2, 2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(-2, 0)))
|
||||
return closestVertex + Vertex(2, 2);
|
||||
else if (isPointInRegion(closestVertex + Vertex(2, 2)))
|
||||
return closestVertex + Vertex(2, 2);
|
||||
|
||||
// If no point could be found that way that lies within the region, find the next point
|
||||
closestVertex = polygon.vertices[0];
|
||||
int shortestVertexDistance2 = polygon.vertices[0].sqrDist(point);
|
||||
{
|
||||
for (int i = 1; i < polygon.vertexCount; i++) {
|
||||
int curDistance2 = polygon.vertices[i].sqrDist(point);
|
||||
if (curDistance2 < shortestVertexDistance2) {
|
||||
closestVertex = polygon.vertices[i];
|
||||
shortestVertexDistance2 = curDistance2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warning("Clostest vertex forced because edgepoint was outside region.");
|
||||
return closestVertex;
|
||||
}
|
||||
}
|
||||
|
||||
Vertex Region::findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const {
|
||||
float vector1X = static_cast<float>(point.x - lineStart.x);
|
||||
float vector1Y = static_cast<float>(point.y - lineStart.y);
|
||||
float vector2X = static_cast<float>(lineEnd.x - lineStart.x);
|
||||
float vector2Y = static_cast<float>(lineEnd.y - lineStart.y);
|
||||
float vector2Length = sqrt(vector2X * vector2X + vector2Y * vector2Y);
|
||||
vector2X /= vector2Length;
|
||||
vector2Y /= vector2Length;
|
||||
float distance = sqrt(static_cast<float>((lineStart.x - lineEnd.x) * (lineStart.x - lineEnd.x) +
|
||||
(lineStart.y - lineEnd.y) * (lineStart.y - lineEnd.y)));
|
||||
float dot = vector1X * vector2X + vector1Y * vector2Y;
|
||||
|
||||
if (dot <= 0)
|
||||
return lineStart;
|
||||
if (dot >= distance)
|
||||
return lineEnd;
|
||||
|
||||
Vertex vector3(static_cast<int>(vector2X * dot + 0.5f), static_cast<int>(vector2Y * dot + 0.5f));
|
||||
return lineStart + vector3;
|
||||
}
|
||||
|
||||
// Line of Sight
|
||||
bool Region::isLineOfSight(const Vertex &a, const Vertex &b) const {
|
||||
assert(_polygons.size());
|
||||
|
||||
// The line must be within the contour polygon, and outside of any hole polygons
|
||||
Common::Array<Polygon>::const_iterator iter = _polygons.begin();
|
||||
if (!(*iter).isLineInterior(a, b)) return false;
|
||||
for (iter++; iter != _polygons.end(); iter++)
|
||||
if (!(*iter).isLineExterior(a, b)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Persistence
|
||||
bool Region::persist(OutputPersistenceBlock &writer) {
|
||||
bool Result = true;
|
||||
|
||||
writer.write(static_cast<uint32>(_type));
|
||||
writer.write(_valid);
|
||||
writer.write((int32)_position.x);
|
||||
writer.write((int32)_position.y);
|
||||
|
||||
writer.write((uint32)_polygons.size());
|
||||
Common::Array<Polygon>::iterator It = _polygons.begin();
|
||||
while (It != _polygons.end()) {
|
||||
Result &= It->persist(writer);
|
||||
++It;
|
||||
}
|
||||
|
||||
writer.write((int32)_boundingBox.left);
|
||||
writer.write((int32)_boundingBox.top);
|
||||
writer.write((int32)_boundingBox.right);
|
||||
writer.write((int32)_boundingBox.bottom);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool Region::unpersist(InputPersistenceBlock &reader) {
|
||||
reader.read(_valid);
|
||||
reader.read(_position.x);
|
||||
reader.read(_position.y);
|
||||
|
||||
_polygons.clear();
|
||||
uint32 PolygonCount;
|
||||
reader.read(PolygonCount);
|
||||
for (uint i = 0; i < PolygonCount; ++i) {
|
||||
_polygons.push_back(Polygon(reader));
|
||||
}
|
||||
|
||||
reader.read(_boundingBox.left);
|
||||
reader.read(_boundingBox.top);
|
||||
reader.read(_boundingBox.right);
|
||||
reader.read(_boundingBox.bottom);
|
||||
|
||||
return reader.isGood();
|
||||
}
|
||||
|
||||
Vertex Region::getCentroid() const {
|
||||
if (_polygons.size() > 0)
|
||||
return _polygons[0].getCentroid();
|
||||
return
|
||||
Vertex();
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
237
engines/sword25/math/region.h
Normal file
237
engines/sword25/math/region.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_REGION_H
|
||||
#define SWORD25_REGION_H
|
||||
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/kernel/persistable.h"
|
||||
#include "sword25/math/vertex.h"
|
||||
#include "sword25/math/polygon.h"
|
||||
#include "common/rect.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
/**
|
||||
* This class is the base class of all regions.
|
||||
*
|
||||
* The IsValid() method can be queried to see whether the object is in a valid state.
|
||||
* If this is not the case, the method Init() is the only method that may be invoked.
|
||||
* This class guarantees that the Vertecies outline of the hole, and the polygons are
|
||||
* arranged in a clockwise direction, so that the polygon working algorithms will
|
||||
* work properly.
|
||||
*/
|
||||
class Region : public Persistable {
|
||||
protected:
|
||||
/**
|
||||
* Creates a new BS_Region object
|
||||
*
|
||||
* After creation the object is invaild (IsValid() return false), but a call can
|
||||
* be made later on to Init() to set up the region into a valid state.
|
||||
*/
|
||||
Region();
|
||||
|
||||
Region(InputPersistenceBlock &reader, uint handle);
|
||||
|
||||
public:
|
||||
enum REGION_TYPE {
|
||||
RT_REGION,
|
||||
RT_WALKREGION
|
||||
};
|
||||
|
||||
static uint create(REGION_TYPE type);
|
||||
static uint create(InputPersistenceBlock &reader, uint handle = 0);
|
||||
|
||||
~Region() override;
|
||||
|
||||
/**
|
||||
* Initializes a BS_Region object
|
||||
* @param Contour A polygon indicating the outline of the region
|
||||
* @param pHoles A pointer to an array of polygons representing the hole state in the region.
|
||||
* If the region has no holes, it must be passed as NULL. The default value is NULL.
|
||||
* @return Returns true if the initialisation was successful, otherwise false.
|
||||
* @remark If the region was already initialized, the old state will be deleted.
|
||||
*/
|
||||
virtual bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = NULL);
|
||||
|
||||
//
|
||||
// Exploratory Methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Specifies whether the object is in a valid state
|
||||
* @return Returns true if the object is in a valid state, otherwise false.
|
||||
* @remark Invalid objects can be made valid by calling Init with a valid state.
|
||||
*/
|
||||
bool isValid() const {
|
||||
return _valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the region
|
||||
*/
|
||||
const Vertex &getPosition() const {
|
||||
return _position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X position of the region
|
||||
*/
|
||||
int getPosX() const {
|
||||
return _position.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Y position of the region
|
||||
*/
|
||||
int getPosY() const {
|
||||
return _position.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a point is inside the region
|
||||
* @param Vertex A verex with the co-ordinates of the test point
|
||||
* @return Returns true if the point is within the region, otherwise false.
|
||||
*/
|
||||
bool isPointInRegion(const Vertex &vertex) const;
|
||||
|
||||
/**
|
||||
* Indicates whether a point is inside the region
|
||||
* @param X The X position
|
||||
* @param Y The Y position
|
||||
* @return Returns true if the point is within the region, otherwise false.
|
||||
*/
|
||||
bool isPointInRegion(int x, int y) const;
|
||||
|
||||
/**
|
||||
* Returns the countour of the region
|
||||
*/
|
||||
const Polygon &getContour() const {
|
||||
return _polygons[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of polygons in the hole region
|
||||
*/
|
||||
int getHoleCount() const {
|
||||
return static_cast<int>(_polygons.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific hole polygon in the region
|
||||
* @param i The number of the hole to return.
|
||||
* The index must be between 0 and GetHoleCount() - 1.
|
||||
* @return Returns the desired hole polygon
|
||||
*/
|
||||
inline const Polygon &getHole(uint i) const;
|
||||
|
||||
/**
|
||||
* For a point outside the region, finds the closest point inside the region
|
||||
* @param Point The point that is outside the region
|
||||
* @return Returns the point within the region which is closest
|
||||
* @remark This method does not always work with pixel accuracy.
|
||||
* One should not therefore rely on the fact that there is really no point in
|
||||
* the region which is closer to the given point.
|
||||
*/
|
||||
Vertex findClosestRegionPoint(const Vertex &point) const;
|
||||
|
||||
/**
|
||||
* Returns the centroid for the region
|
||||
*/
|
||||
Vertex getCentroid() const;
|
||||
|
||||
bool isLineOfSight(const Vertex &a, const Vertex &b) const;
|
||||
|
||||
//
|
||||
// Manipulation Methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets the position of the region
|
||||
* @param X The new X psoition of the region
|
||||
* @param Y The new Y psoition of the region
|
||||
*/
|
||||
virtual void setPos(int x, int y);
|
||||
|
||||
/**
|
||||
* Sets the X position of the region
|
||||
* @param X The new X position of the region
|
||||
*/
|
||||
void setPosX(int x);
|
||||
|
||||
/**
|
||||
* Sets the Y position of the region
|
||||
* @param Y The new Y position of the region
|
||||
*/
|
||||
void setPosY(int y);
|
||||
|
||||
//
|
||||
// Manipulation Methods
|
||||
//
|
||||
|
||||
bool persist(OutputPersistenceBlock &writer) override;
|
||||
bool unpersist(InputPersistenceBlock &reader) override;
|
||||
|
||||
protected:
|
||||
/// This specifies the type of object
|
||||
REGION_TYPE _type;
|
||||
/// This variable indicates whether the current object state is valid
|
||||
bool _valid;
|
||||
/// This vertex is the position of the region
|
||||
Vertex _position;
|
||||
/// This array contains all the polygons that define the region. The first element of
|
||||
// the array is the contour, all others are the holes
|
||||
Common::Array<Polygon> _polygons;
|
||||
/// The bounding box for the region
|
||||
Common::Rect _boundingBox;
|
||||
|
||||
/**
|
||||
* Updates the bounding box of the region.
|
||||
*/
|
||||
void updateBoundingBox();
|
||||
|
||||
/**
|
||||
* Find the point on a line which is closest to another point
|
||||
* @param LineStart The start of the line
|
||||
* @param LineEnd The end of the line
|
||||
* @param Point The point to be compared against
|
||||
* @return Returns the point on the line which is cloest to the passed point.
|
||||
*/
|
||||
Vertex findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const;
|
||||
};
|
||||
|
||||
inline const Polygon &Region::getHole(uint i) const {
|
||||
assert(i < _polygons.size() - 1);
|
||||
return _polygons[i + 1];
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
94
engines/sword25/math/regionregistry.cpp
Normal file
94
engines/sword25/math/regionregistry.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sword25/kernel/outputpersistenceblock.h"
|
||||
#include "sword25/kernel/inputpersistenceblock.h"
|
||||
#include "sword25/math/regionregistry.h"
|
||||
#include "sword25/math/region.h"
|
||||
|
||||
namespace Common {
|
||||
DECLARE_SINGLETON(Sword25::RegionRegistry);
|
||||
}
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
bool RegionRegistry::persist(OutputPersistenceBlock &writer) {
|
||||
bool result = true;
|
||||
|
||||
// write out the next handle
|
||||
writer.write(_nextHandle);
|
||||
|
||||
// Number of regions to write
|
||||
writer.write((uint32)_handle2PtrMap.size());
|
||||
|
||||
// Persist all the BS_Regions
|
||||
HANDLE2PTR_MAP::const_iterator iter = _handle2PtrMap.begin();
|
||||
while (iter != _handle2PtrMap.end()) {
|
||||
// Handle persistence
|
||||
writer.write(iter->_key);
|
||||
|
||||
// Persist object
|
||||
result &= iter->_value->persist(writer);
|
||||
|
||||
++iter;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RegionRegistry::unpersist(InputPersistenceBlock &reader) {
|
||||
bool result = true;
|
||||
|
||||
// read in the next handle
|
||||
reader.read(_nextHandle);
|
||||
|
||||
// Destroy all existing BS_Regions
|
||||
//FIXME: This doesn't seem right - the value is being deleted but not the actual hash node itself?
|
||||
while (!_handle2PtrMap.empty())
|
||||
delete _handle2PtrMap.begin()->_value;
|
||||
|
||||
// read in the number of BS_Regions
|
||||
uint32 regionCount;
|
||||
reader.read(regionCount);
|
||||
|
||||
// Restore all the BS_Regions objects
|
||||
for (uint i = 0; i < regionCount; ++i) {
|
||||
// Handle read
|
||||
uint32 handle;
|
||||
reader.read(handle);
|
||||
|
||||
// BS_Region restore
|
||||
result &= Region::create(reader, handle) != 0;
|
||||
}
|
||||
|
||||
return reader.isGood() && result;
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
55
engines/sword25/math/regionregistry.h
Normal file
55
engines/sword25/math/regionregistry.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_REGIONREGISTRY_H
|
||||
#define SWORD25_REGIONREGISTRY_H
|
||||
|
||||
#include "common/singleton.h"
|
||||
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/kernel/persistable.h"
|
||||
#include "sword25/kernel/objectregistry.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
class Region;
|
||||
|
||||
class RegionRegistry :
|
||||
public ObjectRegistry<Region>,
|
||||
public Persistable,
|
||||
public Common::Singleton<RegionRegistry> {
|
||||
public:
|
||||
bool persist(OutputPersistenceBlock &writer) override;
|
||||
bool unpersist(InputPersistenceBlock &reader) override;
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
82
engines/sword25/math/vertex.cpp
Normal file
82
engines/sword25/math/vertex.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sword25/math/vertex.h"
|
||||
|
||||
#include "common/lua/lua.h"
|
||||
#include "common/lua/lauxlib.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
Vertex &Vertex::luaVertexToVertex(lua_State *L, int stackIndex, Vertex &vertex) {
|
||||
#ifdef DEBUG
|
||||
int __startStackDepth = lua_gettop(L);
|
||||
#endif
|
||||
|
||||
// Ensure that we actually consider a table
|
||||
luaL_checktype(L, stackIndex, LUA_TTABLE);
|
||||
|
||||
// Read X Component
|
||||
lua_pushstring(L, "X");
|
||||
lua_gettable(L, stackIndex);
|
||||
if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the X component has to be a number");
|
||||
vertex.x = static_cast<int>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Read Y Component
|
||||
lua_pushstring(L, "Y");
|
||||
lua_gettable(L, stackIndex);
|
||||
if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the Y component has to be a number");
|
||||
vertex.y = static_cast<int>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
#ifdef DEBUG
|
||||
assert(__startStackDepth == lua_gettop(L));
|
||||
#endif
|
||||
|
||||
return vertex;
|
||||
}
|
||||
|
||||
void Vertex::vertexToLuaVertex(lua_State *L, const Vertex &vertex) {
|
||||
// Create New Table
|
||||
lua_newtable(L);
|
||||
|
||||
// X value is written to table
|
||||
lua_pushstring(L, "X");
|
||||
lua_pushnumber(L, vertex.x);
|
||||
lua_settable(L, -3);
|
||||
|
||||
// Y value is written to table
|
||||
lua_pushstring(L, "Y");
|
||||
lua_pushnumber(L, vertex.y);
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
74
engines/sword25/math/vertex.h
Normal file
74
engines/sword25/math/vertex.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
BS_Vertex
|
||||
---------
|
||||
|
||||
Autor: Malte Thiesen
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_VERTEX_H
|
||||
#define SWORD25_VERTEX_H
|
||||
|
||||
// Includes
|
||||
#include "common/rect.h"
|
||||
#include "sword25/kernel/common.h"
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
/**
|
||||
* Defines a 2-D Vertex
|
||||
*/
|
||||
class Vertex : public Common::Point {
|
||||
public:
|
||||
Vertex() : Point() {}
|
||||
Vertex(int x_, int y_) : Point(x_, y_) {}
|
||||
Vertex(Point p) : Point(p) {}
|
||||
|
||||
/**
|
||||
* Calculates the square of the distance between two Vertecies.
|
||||
* @param Vertex The vertex for which the distance is to be calculated
|
||||
* @return Returns the square of the distance between itself and the passed vertex
|
||||
* @remark If only distances should be compared, sqrDist() should be used, since it is faster.
|
||||
*/
|
||||
inline int distance(const Vertex &vertex) const {
|
||||
return (int)(sqrt(static_cast<float>(sqrDist(vertex))) + 0.5);
|
||||
}
|
||||
|
||||
static Vertex &luaVertexToVertex(lua_State *L, int StackIndex, Vertex &vertex);
|
||||
static void vertexToLuaVertex(lua_State *L, const Vertex &vertex);
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
397
engines/sword25/math/walkregion.cpp
Normal file
397
engines/sword25/math/walkregion.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sword25/kernel/kernel.h"
|
||||
#include "sword25/kernel/inputpersistenceblock.h"
|
||||
#include "sword25/kernel/outputpersistenceblock.h"
|
||||
#include "sword25/math/walkregion.h"
|
||||
#include "sword25/math/line.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
static const int Infinity = 0x7fffffff;
|
||||
|
||||
WalkRegion::WalkRegion() {
|
||||
_type = RT_WALKREGION;
|
||||
}
|
||||
|
||||
WalkRegion::WalkRegion(InputPersistenceBlock &reader, uint handle) :
|
||||
Region(reader, handle) {
|
||||
_type = RT_WALKREGION;
|
||||
unpersist(reader);
|
||||
}
|
||||
|
||||
WalkRegion::~WalkRegion() {
|
||||
}
|
||||
|
||||
bool WalkRegion::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) {
|
||||
// Default initialisation of the region
|
||||
if (!Region::init(contour, pHoles)) return false;
|
||||
|
||||
// Prepare structures for pathfinding
|
||||
initNodeVector();
|
||||
computeVisibilityMatrix();
|
||||
|
||||
// Signal success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalkRegion::queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path) {
|
||||
assert(path.empty());
|
||||
|
||||
// If the start and finish are identical, no path can be found trivially
|
||||
if (startPoint == endPoint)
|
||||
return true;
|
||||
|
||||
// Ensure that the start and finish are valid and find new start points if either
|
||||
// are outside the polygon
|
||||
if (!checkAndPrepareStartAndEnd(startPoint, endPoint)) return false;
|
||||
|
||||
// If between the start and point a line of sight exists, then it can be returned.
|
||||
if (isLineOfSight(startPoint, endPoint)) {
|
||||
path.push_back(startPoint);
|
||||
path.push_back(endPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
return findPath(startPoint, endPoint, path);
|
||||
}
|
||||
|
||||
struct DijkstraNode {
|
||||
typedef Common::Array<DijkstraNode> Container;
|
||||
typedef Container::iterator Iter;
|
||||
typedef Container::const_iterator ConstIter;
|
||||
|
||||
DijkstraNode() : parentIter(), cost(Infinity), chosen(false) {}
|
||||
ConstIter parentIter;
|
||||
int cost;
|
||||
bool chosen;
|
||||
};
|
||||
|
||||
static void initDijkstraNodes(DijkstraNode::Container &dijkstraNodes, const Region ®ion,
|
||||
const Vertex &start, const Common::Array<Vertex> &nodes) {
|
||||
// Allocate sufficient space in the array
|
||||
dijkstraNodes.resize(nodes.size());
|
||||
|
||||
// Initialize all the nodes which are visible from the starting node
|
||||
DijkstraNode::Iter dijkstraIter = dijkstraNodes.begin();
|
||||
for (Common::Array<Vertex>::const_iterator nodesIter = nodes.begin();
|
||||
nodesIter != nodes.end(); nodesIter++, dijkstraIter++) {
|
||||
(*dijkstraIter).parentIter = dijkstraNodes.end();
|
||||
if (region.isLineOfSight(*nodesIter, start))(*dijkstraIter).cost = (*nodesIter).distance(start);
|
||||
}
|
||||
assert(dijkstraIter == dijkstraNodes.end());
|
||||
}
|
||||
|
||||
static DijkstraNode::Iter chooseClosestNode(DijkstraNode::Container &nodes) {
|
||||
DijkstraNode::Iter closestNodeInter = nodes.end();
|
||||
int minCost = Infinity;
|
||||
|
||||
for (DijkstraNode::Iter iter = nodes.begin(); iter != nodes.end(); iter++) {
|
||||
if (!(*iter).chosen && (*iter).cost < minCost) {
|
||||
minCost = (*iter).cost;
|
||||
closestNodeInter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
return closestNodeInter;
|
||||
}
|
||||
|
||||
static void relaxNodes(DijkstraNode::Container &nodes,
|
||||
const Common::Array< Common::Array<int> > &visibilityMatrix,
|
||||
const DijkstraNode::ConstIter &curNodeIter) {
|
||||
// All the successors of the current node that have not been chosen will be
|
||||
// inserted into the boundary node list, and the cost will be updated if
|
||||
// a shorter path has been found to them.
|
||||
|
||||
int curNodeIndex = curNodeIter - nodes.begin();
|
||||
for (uint i = 0; i < nodes.size(); i++) {
|
||||
int cost = visibilityMatrix[curNodeIndex][i];
|
||||
if (!nodes[i].chosen && cost != Infinity) {
|
||||
int totalCost = (*curNodeIter).cost + cost;
|
||||
if (totalCost < nodes[i].cost) {
|
||||
nodes[i].parentIter = curNodeIter;
|
||||
nodes[i].cost = totalCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void relaxEndPoint(const Vertex &curNodePos,
|
||||
const DijkstraNode::ConstIter &curNodeIter,
|
||||
const Vertex &endPointPos,
|
||||
DijkstraNode &endPoint,
|
||||
const Region ®ion) {
|
||||
if (region.isLineOfSight(curNodePos, endPointPos)) {
|
||||
int totalCost = (*curNodeIter).cost + curNodePos.distance(endPointPos);
|
||||
if (totalCost < endPoint.cost) {
|
||||
endPoint.parentIter = curNodeIter;
|
||||
endPoint.cost = totalCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void reverseArray(Common::Array<T> &arr) {
|
||||
const uint size = arr.size();
|
||||
if (size < 2)
|
||||
return;
|
||||
|
||||
for (uint i = 0; i <= (size / 2 - 1); ++i) {
|
||||
SWAP(arr[i], arr[size - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool WalkRegion::findPath(const Vertex &start, const Vertex &end, BS_Path &path) const {
|
||||
// This is an implementation of Dijkstra's algorithm
|
||||
|
||||
// Initialize edge node list
|
||||
DijkstraNode::Container dijkstraNodes;
|
||||
initDijkstraNodes(dijkstraNodes, *this, start, _nodes);
|
||||
|
||||
// The end point is treated separately, since it does not exist in the visibility graph
|
||||
DijkstraNode endPoint;
|
||||
|
||||
// Since a node is selected each round from the node list, and can never be selected again
|
||||
// after that, the maximum number of loop iterations is limited by the number of nodes
|
||||
for (uint i = 0; i < _nodes.size(); i++) {
|
||||
// Determine the nearest edge node in the node list
|
||||
DijkstraNode::Iter nodeInter = chooseClosestNode(dijkstraNodes);
|
||||
|
||||
// If no free nodes are absent from the edge node list, there is no path from start
|
||||
// to end node. This case should never occur, since the number of loop passes is
|
||||
// limited, but etter safe than sorry
|
||||
if (nodeInter == dijkstraNodes.end())
|
||||
return false;
|
||||
|
||||
// If the destination point is closer than the point cost, scan can stop
|
||||
(*nodeInter).chosen = true;
|
||||
if (endPoint.cost <= (*nodeInter).cost) {
|
||||
// Insert the end point in the list
|
||||
path.push_back(end);
|
||||
|
||||
// The list is done in reverse order and inserted into the path
|
||||
DijkstraNode::ConstIter curNode = endPoint.parentIter;
|
||||
while (curNode != dijkstraNodes.end()) {
|
||||
assert((*curNode).chosen);
|
||||
path.push_back(_nodes[curNode - dijkstraNodes.begin()]);
|
||||
curNode = (*curNode).parentIter;
|
||||
}
|
||||
|
||||
// The starting point is inserted into the path
|
||||
path.push_back(start);
|
||||
|
||||
// The nodes of the path must be untwisted, as they were extracted in reverse order.
|
||||
// This step could be saved if the path from end to the beginning was desired
|
||||
reverseArray<Vertex>(path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Relaxation step for nodes of the graph, and perform the end nodes
|
||||
relaxNodes(dijkstraNodes, _visibilityMatrix, nodeInter);
|
||||
relaxEndPoint(_nodes[nodeInter - dijkstraNodes.begin()], nodeInter, end, endPoint, *this);
|
||||
}
|
||||
|
||||
// If the loop has been completely run through, all the nodes have been chosen, and still
|
||||
// no path was found. There is therefore no path available
|
||||
return false;
|
||||
}
|
||||
|
||||
void WalkRegion::initNodeVector() {
|
||||
// Empty the Node list
|
||||
_nodes.clear();
|
||||
|
||||
// Determine the number of nodes
|
||||
int nodeCount = 0;
|
||||
{
|
||||
for (uint i = 0; i < _polygons.size(); i++)
|
||||
nodeCount += _polygons[i].vertexCount;
|
||||
}
|
||||
|
||||
// Knoten-Vector füllen
|
||||
_nodes.reserve(nodeCount);
|
||||
{
|
||||
for (uint j = 0; j < _polygons.size(); j++)
|
||||
for (int i = 0; i < _polygons[j].vertexCount; i++)
|
||||
_nodes.push_back(_polygons[j].vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void WalkRegion::computeVisibilityMatrix() {
|
||||
// Initialize visibility matrix
|
||||
_visibilityMatrix = Common::Array< Common::Array <int> >();
|
||||
for (uint idx = 0; idx < _nodes.size(); ++idx) {
|
||||
Common::Array<int> arr;
|
||||
for (uint idx2 = 0; idx2 < _nodes.size(); ++idx2)
|
||||
arr.push_back(Infinity);
|
||||
|
||||
_visibilityMatrix.push_back(arr);
|
||||
}
|
||||
|
||||
// Calculate visibility been vertecies
|
||||
for (uint j = 0; j < _nodes.size(); ++j) {
|
||||
for (uint i = j; i < _nodes.size(); ++i) {
|
||||
if (isLineOfSight(_nodes[i], _nodes[j])) {
|
||||
// There is a line of sight, so save the distance between the two
|
||||
int distance = _nodes[i].distance(_nodes[j]);
|
||||
_visibilityMatrix[i][j] = distance;
|
||||
_visibilityMatrix[j][i] = distance;
|
||||
} else {
|
||||
// There is no line of sight, so save Infinity as the distance
|
||||
_visibilityMatrix[i][j] = Infinity;
|
||||
_visibilityMatrix[j][i] = Infinity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WalkRegion::checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const {
|
||||
if (!isPointInRegion(start)) {
|
||||
Vertex newStart = findClosestRegionPoint(start);
|
||||
|
||||
// Check to make sure the point is really in the region. If not, stop with an error
|
||||
if (!isPointInRegion(newStart)) {
|
||||
error("Constructed startpoint ((%d,%d) from (%d,%d)) is not inside the region.",
|
||||
newStart.x, newStart.y,
|
||||
start.x, start.y);
|
||||
return false;
|
||||
}
|
||||
|
||||
start = newStart;
|
||||
}
|
||||
|
||||
// If the destination is outside the region, a point is determined that is within the region,
|
||||
// and that is used as an endpoint instead
|
||||
if (!isPointInRegion(end)) {
|
||||
Vertex newEnd = findClosestRegionPoint(end);
|
||||
|
||||
// Make sure that the determined point is really within the region
|
||||
if (!isPointInRegion(newEnd)) {
|
||||
error("Constructed endpoint ((%d,%d) from (%d,%d)) is not inside the region.",
|
||||
newEnd.x, newEnd.y,
|
||||
end.x, end.y);
|
||||
return false;
|
||||
}
|
||||
|
||||
end = newEnd;
|
||||
}
|
||||
|
||||
// Signal success
|
||||
return true;
|
||||
}
|
||||
|
||||
void WalkRegion::setPos(int x, int y) {
|
||||
// Calculate the difference between old and new position
|
||||
Vertex Delta(x - _position.x, y - _position.y);
|
||||
|
||||
// Move all the nodes
|
||||
for (uint i = 0; i < _nodes.size(); i++)
|
||||
_nodes[i] += Delta;
|
||||
|
||||
// Move regions
|
||||
Region::setPos(x, y);
|
||||
}
|
||||
|
||||
bool WalkRegion::persist(OutputPersistenceBlock &writer) {
|
||||
bool result = true;
|
||||
|
||||
// Persist the parent region
|
||||
result &= Region::persist(writer);
|
||||
|
||||
// Persist the nodes
|
||||
writer.write((uint32)_nodes.size());
|
||||
Common::Array<Vertex>::const_iterator it = _nodes.begin();
|
||||
while (it != _nodes.end()) {
|
||||
writer.write((int32)it->x);
|
||||
writer.write((int32)it->y);
|
||||
++it;
|
||||
}
|
||||
|
||||
// Persist the visibility matrix
|
||||
writer.write((uint32)_visibilityMatrix.size());
|
||||
Common::Array< Common::Array<int> >::const_iterator rowIter = _visibilityMatrix.begin();
|
||||
while (rowIter != _visibilityMatrix.end()) {
|
||||
writer.write((uint32)rowIter->size());
|
||||
Common::Array<int>::const_iterator colIter = rowIter->begin();
|
||||
while (colIter != rowIter->end()) {
|
||||
writer.write((int32)*colIter);
|
||||
++colIter;
|
||||
}
|
||||
|
||||
++rowIter;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WalkRegion::unpersist(InputPersistenceBlock &reader) {
|
||||
bool result = true;
|
||||
|
||||
// The parent object was already loaded in the constructor of BS_Region, so at
|
||||
// this point only the additional data from BS_WalkRegion needs to be loaded
|
||||
|
||||
// Node load
|
||||
uint32 nodeCount;
|
||||
reader.read(nodeCount);
|
||||
_nodes.clear();
|
||||
_nodes.resize(nodeCount);
|
||||
Common::Array<Vertex>::iterator it = _nodes.begin();
|
||||
while (it != _nodes.end()) {
|
||||
reader.read(it->x);
|
||||
reader.read(it->y);
|
||||
++it;
|
||||
}
|
||||
|
||||
// Visibility matrix load
|
||||
uint32 rowCount;
|
||||
reader.read(rowCount);
|
||||
_visibilityMatrix.clear();
|
||||
_visibilityMatrix.resize(rowCount);
|
||||
Common::Array< Common::Array<int> >::iterator rowIter = _visibilityMatrix.begin();
|
||||
while (rowIter != _visibilityMatrix.end()) {
|
||||
uint32 colCount;
|
||||
reader.read(colCount);
|
||||
rowIter->resize(colCount);
|
||||
Common::Array<int>::iterator colIter = rowIter->begin();
|
||||
while (colIter != rowIter->end()) {
|
||||
int32 t;
|
||||
reader.read(t);
|
||||
*colIter = t;
|
||||
++colIter;
|
||||
}
|
||||
|
||||
++rowIter;
|
||||
}
|
||||
|
||||
return result && reader.isGood();
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
109
engines/sword25/math/walkregion.h
Normal file
109
engines/sword25/math/walkregion.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on Broken Sword 2.5 engine
|
||||
*
|
||||
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
|
||||
*
|
||||
* Licensed under GNU GPL v2
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SWORD25_WALKREGION_H
|
||||
#define SWORD25_WALKREGION_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "sword25/kernel/common.h"
|
||||
#include "sword25/math/region.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
typedef Common::Array<Vertex> BS_Path;
|
||||
|
||||
/**
|
||||
* This class represents the region in which the main character can move
|
||||
*/
|
||||
class WalkRegion : public Region {
|
||||
friend class Region;
|
||||
|
||||
protected:
|
||||
WalkRegion();
|
||||
WalkRegion(InputPersistenceBlock &Reader, uint handle);
|
||||
|
||||
public:
|
||||
~WalkRegion() override;
|
||||
|
||||
bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = 0) override;
|
||||
|
||||
/**
|
||||
* Get the shortest path between two points in the region
|
||||
*
|
||||
* This method requires that the starting point lies within the region. The end point
|
||||
* may lie outside the region. Int his case, the end is chosen as the cloest point to it
|
||||
* that lies within the region.
|
||||
*
|
||||
* @param X1 X Co-ordinate of the start point
|
||||
* @param Y1 Y Co-ordinate of the start point
|
||||
* @param X2 X Co-ordinate of the end point
|
||||
* @param Y2 Y Co-ordinate of the end point
|
||||
* @param Path An empty BS_Path that will be set to the resulting path
|
||||
* @return Returns false if the result is invalid, otherwise returns true.
|
||||
*/
|
||||
bool queryPath(int x1, int y1, int x2, int y2, BS_Path &path) {
|
||||
return queryPath(Vertex(x1, y1), Vertex(x2, y2), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shortest path between two points in the region.
|
||||
*
|
||||
* @param StartPoint The start point
|
||||
* @param EndPoint The end point
|
||||
* @param Path An empty BS_Path that will be set to the resulting path
|
||||
* @return Returns false if the result is invalid, otherwise returns true.
|
||||
*/
|
||||
bool queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path);
|
||||
|
||||
void setPos(int x, int y) override;
|
||||
|
||||
const Common::Array<Vertex> &getNodes() const {
|
||||
return _nodes;
|
||||
}
|
||||
const Common::Array< Common::Array<int> > &getVisibilityMatrix() const {
|
||||
return _visibilityMatrix;
|
||||
}
|
||||
|
||||
bool persist(OutputPersistenceBlock &writer) override;
|
||||
bool unpersist(InputPersistenceBlock &reader) override;
|
||||
|
||||
private:
|
||||
Common::Array<Vertex> _nodes;
|
||||
Common::Array< Common::Array<int> > _visibilityMatrix;
|
||||
|
||||
void initNodeVector();
|
||||
void computeVisibilityMatrix();
|
||||
bool checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const;
|
||||
bool findPath(const Vertex &start, const Vertex &end, BS_Path &path) const;
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user