scummvm/engines/sword25/math/geometry_script.cpp
2010-10-12 22:24:58 +00:00

562 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
// -----------------------------------------------------------------------------
// Includes
// -----------------------------------------------------------------------------
#include "common/array.h"
#include "sword25/gfx/graphicengine.h"
#include "sword25/kernel/common.h"
#include "sword25/kernel/kernel.h"
#include "sword25/script/script.h"
#include "sword25/script/luabindhelper.h"
#include "sword25/math/geometry.h"
#include "sword25/math/region.h"
#include "sword25/math/regionregistry.h"
#include "sword25/math/walkregion.h"
#include "sword25/math/vertex.h"
// -----------------------------------------------------------------------------
namespace Sword25 {
// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------
// These strings are defined as #defines to enable compile-time string composition
#define REGION_CLASS_NAME "Geo.Region"
#define WALKREGION_CLASS_NAME "Geo.WalkRegion"
// -----------------------------------------------------------------------------
// How luaL_checkudata, only without that no error is generated.
static void *my_checkudata(lua_State *L, int ud, const char *tname) {
int top = lua_gettop(L);
void * p = lua_touserdata(L, ud);
if (p != NULL) /* value is a userdata? */
{
if (lua_getmetatable(L, ud)) /* does it have a metatable? */
{
// lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */
BS_LuaBindhelper::GetMetatable(L, tname);
/* does it have the correct mt? */
if (lua_rawequal(L, -1, -2)) {
lua_settop(L, top);
return p;
}
}
}
lua_settop(L, top);
return NULL;
}
// -----------------------------------------------------------------------------
static void NewUintUserData(lua_State *L, unsigned int 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 += 1) {
lua_rawgeti(L, -1, i);
if (!lua_isnumber(L, -1)) {
luaL_error(L, "Invalid polygon definition. All table elements have to be numbers.");
return false;
}
lua_pop(L, 1);
}
#ifdef DEBUG
BS_ASSERT(__startStackDepth == lua_gettop(L));
#endif
return true;
}
// -----------------------------------------------------------------------------
static void TablePolygonToPolygon(lua_State *L, BS_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<BS_Vertex> Vertecies;
Vertecies.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
Vertecies.push_back(BS_Vertex(X, Y));
}
BS_ASSERT((int)Vertecies.size() == VertexCount);
#ifdef DEBUG
BS_ASSERT(__startStackDepth == lua_gettop(L));
#endif
// Create polygon
Polygon.Init(VertexCount, &Vertecies[0]);
}
// -----------------------------------------------------------------------------
static unsigned int 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;
}
unsigned int RegionHandle = 0;
if (!strcmp(ClassName, REGION_CLASS_NAME)) {
RegionHandle = BS_Region::Create(BS_Region::RT_REGION);
} else if (!strcmp(ClassName, WALKREGION_CLASS_NAME)) {
RegionHandle = BS_WalkRegion::Create(BS_Region::RT_WALKREGION);
} else {
BS_ASSERT(false);
}
BS_ASSERT(RegionHandle);
// If the first element of the parameter is a number, then case 1 is accepted
// If the first element of the parameter is a table, then case 2 is accepted
// If the first element of the parameter has a different type, there is an error
lua_rawgeti(L, -1, 1);
int FirstElementType = lua_type(L, -1);
lua_pop(L, 1);
switch(FirstElementType) {
case LUA_TNUMBER: {
BS_Polygon Polygon;
TablePolygonToPolygon(L, Polygon);
BS_RegionRegistry::GetInstance().ResolveHandle(RegionHandle)->Init(Polygon);
}
break;
case LUA_TTABLE: {
lua_rawgeti(L, -1, 1);
BS_Polygon Polygon;
TablePolygonToPolygon(L, Polygon);
lua_pop(L, 1);
int PolygonCount = luaL_getn(L, -1);
if (PolygonCount == 1)
BS_RegionRegistry::GetInstance().ResolveHandle(RegionHandle)->Init(Polygon);
else {
Common::Array<BS_Polygon> Holes;
Holes.reserve(PolygonCount - 1);
for (int i = 2; i <= PolygonCount; i++) {
lua_rawgeti(L, -1, i);
Holes.resize(Holes.size() + 1);
TablePolygonToPolygon(L, Holes.back());
lua_pop(L, 1);
}
BS_ASSERT((int)Holes.size() == PolygonCount - 1);
BS_RegionRegistry::GetInstance().ResolveHandle(RegionHandle)->Init(Polygon, &Holes);
}
}
break;
default:
luaL_error(L, "Illegal region definition.");
return 0;
}
#ifdef DEBUG
BS_ASSERT(__startStackDepth == lua_gettop(L));
#endif
return RegionHandle;
}
// -----------------------------------------------------------------------------
static void NewUserdataRegion(lua_State *L, const char *ClassName)
{
// Region due to the Lua code to create
// Any errors that occur will be intercepted to the luaL_error
unsigned int RegionHandle = TableRegionToRegion(L, ClassName);
BS_ASSERT(RegionHandle);
NewUintUserData(L, RegionHandle);
// luaL_getmetatable(L, ClassName);
BS_LuaBindhelper::GetMetatable(L, ClassName);
BS_ASSERT(!lua_isnil(L, -1));
lua_setmetatable(L, -2);
}
// -----------------------------------------------------------------------------
static int NewRegion(lua_State *L) {
NewUserdataRegion(L, REGION_CLASS_NAME);
return 1;
}
// -----------------------------------------------------------------------------
static int NewWalkRegion(lua_State *L) {
NewUserdataRegion(L, WALKREGION_CLASS_NAME);
return 1;
}
// -----------------------------------------------------------------------------
static const char *GEO_LIBRARY_NAME = "Geo";
static const luaL_reg GEO_FUNCTIONS[] = {
"NewRegion", NewRegion,
"NewWalkRegion", NewWalkRegion,
0, 0,
};
// -----------------------------------------------------------------------------
static BS_Region * CheckRegion(lua_State *L) {
// The first parameter must be of type 'userdata', and the Metatable class Geo.Region or Geo.WalkRegion
unsigned int *RegionHandlePtr;
if ((RegionHandlePtr = reinterpret_cast<unsigned int *>(my_checkudata(L, 1, REGION_CLASS_NAME))) != 0 ||
(RegionHandlePtr = reinterpret_cast<unsigned int *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
return BS_RegionRegistry::GetInstance().ResolveHandle(*RegionHandlePtr);
} else {
luaL_argcheck(L, 0, 1, "'" REGION_CLASS_NAME "' expected");
}
// Compilation fix. Execution never reaches this point
return 0;
}
// -----------------------------------------------------------------------------
static int R_IsValid(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
lua_pushbooleancpp(L, pR->IsValid());
return 1;
}
// -----------------------------------------------------------------------------
static int R_GetX(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
lua_pushnumber(L, pR->GetPosX());
return 1;
}
// -----------------------------------------------------------------------------
static int R_GetY(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
lua_pushnumber(L, pR->GetPosY());
return 1;
}
// -----------------------------------------------------------------------------
static int R_GetPos(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
BS_Vertex::VertexToLuaVertex(L, pR->GetPosition());
return 1;
}
// -----------------------------------------------------------------------------
static int R_IsPointInRegion(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
BS_Vertex Vertex;
BS_Vertex::LuaVertexToVertex(L, 2, Vertex);
lua_pushbooleancpp(L, pR->IsPointInRegion(Vertex));
return 1;
}
// -----------------------------------------------------------------------------
static int R_SetPos(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
BS_Vertex Vertex;
BS_Vertex::LuaVertexToVertex(L, 2, Vertex);
pR->SetPos(Vertex.X, Vertex.Y);
return 0;
}
// -----------------------------------------------------------------------------
static int R_SetX(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
pR->SetPosX(static_cast<int>(luaL_checknumber(L, 2)));
return 0;
}
// -----------------------------------------------------------------------------
static int R_SetY(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
pR->SetPosY(static_cast<int>(luaL_checknumber(L, 2)));
return 0;
}
// -----------------------------------------------------------------------------
static void DrawPolygon(const BS_Polygon &Polygon, unsigned int Color, const BS_Vertex &Offset) {
BS_GraphicEngine *pGE = static_cast<BS_GraphicEngine *>(BS_Kernel::GetInstance()->GetService("gfx"));
BS_ASSERT(pGE);
for (int i = 0; i < Polygon.VertexCount - 1; i++)
pGE->DrawDebugLine(Polygon.Vertecies[i] + Offset, Polygon.Vertecies[i + 1] + Offset, Color);
pGE->DrawDebugLine(Polygon.Vertecies[Polygon.VertexCount - 1] + Offset, Polygon.Vertecies[0] + Offset, Color);
}
// -----------------------------------------------------------------------------
static void DrawRegion(const BS_Region &Region, unsigned int Color, const BS_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) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
switch (lua_gettop(L)) {
case 3: {
BS_Vertex Offset;
BS_Vertex::LuaVertexToVertex(L, 3, Offset);
DrawRegion(*pR, BS_GraphicEngine::LuaColorToARGBColor(L, 2), Offset);
}
break;
case 2:
DrawRegion(*pR, BS_GraphicEngine::LuaColorToARGBColor(L, 2), BS_Vertex(0, 0));
break;
default:
DrawRegion(*pR, BS_RGB(255, 255, 255), BS_Vertex(0, 0));
}
return 0;
}
// -----------------------------------------------------------------------------
static int R_GetCentroid(lua_State *L) {
BS_Region * RPtr = CheckRegion(L);
BS_ASSERT(RPtr);
BS_Vertex::VertexToLuaVertex(L, RPtr->GetCentroid());
return 1;
}
// -----------------------------------------------------------------------------
static int R_Delete(lua_State *L) {
BS_Region * pR = CheckRegion(L);
BS_ASSERT(pR);
delete pR;
return 0;
}
// -----------------------------------------------------------------------------
static const luaL_reg REGION_METHODS[] = {
"SetPos", R_SetPos,
"SetX", R_SetX,
"SetY", R_SetY,
"GetPos", R_GetPos,
"IsPointInRegion", R_IsPointInRegion,
"GetX", R_GetX,
"GetY", R_GetY,
"IsValid", R_IsValid,
"Draw", R_Draw,
"GetCentroid", R_GetCentroid,
0, 0,
};
// -----------------------------------------------------------------------------
static BS_WalkRegion *CheckWalkRegion(lua_State *L) {
// The first parameter must be of type 'userdate', and the Metatable class Geo.WalkRegion
unsigned int RegionHandle;
if ((RegionHandle = *reinterpret_cast<unsigned int *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) {
return reinterpret_cast<BS_WalkRegion *>(BS_RegionRegistry::GetInstance().ResolveHandle(RegionHandle));
} else {
luaL_argcheck(L, 0, 1, "'" WALKREGION_CLASS_NAME "' expected");
}
// Compilation fix. Execution never reaches this point
return 0;
}
// -----------------------------------------------------------------------------
static int WR_GetPath(lua_State *L) {
BS_WalkRegion *pWR = CheckWalkRegion(L);
BS_ASSERT(pWR);
BS_Vertex Start;
BS_Vertex::LuaVertexToVertex(L, 2, Start);
BS_Vertex End;
BS_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);
BS_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 BS_Geometry::_RegisterScriptBindings() {
BS_Kernel * pKernel = BS_Kernel::GetInstance();
BS_ASSERT(pKernel);
BS_ScriptEngine * pScript = static_cast<BS_ScriptEngine *>(pKernel->GetService("script"));
BS_ASSERT(pScript);
lua_State *L = static_cast< lua_State *>(pScript->GetScriptObject());
BS_ASSERT(L);
if (!BS_LuaBindhelper::AddMethodsToClass(L, REGION_CLASS_NAME, REGION_METHODS)) return false;
if (!BS_LuaBindhelper::AddMethodsToClass(L, WALKREGION_CLASS_NAME, REGION_METHODS)) return false;
if (!BS_LuaBindhelper::AddMethodsToClass(L, WALKREGION_CLASS_NAME, WALKREGION_METHODS)) return false;
if (!BS_LuaBindhelper::SetClassGCHandler(L, REGION_CLASS_NAME, R_Delete)) return false;
if (!BS_LuaBindhelper::SetClassGCHandler(L, WALKREGION_CLASS_NAME, R_Delete)) return false;
if (!BS_LuaBindhelper::AddFunctionsToLib(L, GEO_LIBRARY_NAME, GEO_FUNCTIONS)) return false;
return true;
}
} // End of namespace Sword25