scummvm/engines/tinsel/polygons.cpp
2009-11-02 21:54:57 +00:00

2369 lines
58 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$
*/
#include "tinsel/actors.h"
#include "tinsel/font.h"
#include "tinsel/handle.h"
#include "tinsel/pcode.h"
#include "tinsel/pid.h"
#include "tinsel/polygons.h"
#include "tinsel/rince.h"
#include "tinsel/sched.h"
#include "common/serializer.h"
#include "tinsel/tinsel.h"
#include "tinsel/token.h"
#include "common/util.h"
namespace Tinsel {
//----------------- LOCAL DEFINES --------------------
/** different types of polygon */
enum POLY_TYPE {
POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT,
POLY_EXIT, POLY_TAG
};
// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4
// so reduced this back to 6 (from 12) for now.
#define MAXADJ 6 // Max number of known adjacent paths
struct POLYGON {
PTYPE polyType; // Polygon type
int subtype; // refer type in REFER polygons
// NODE/NORMAL in PATH polygons
int pIndex; // Index into compiled polygon data
/*
* Data duplicated from compiled polygon data
*/
short cx[4]; // Corners (clockwise direction)
short cy[4];
int polyID;
/* For TAG polygons only */
int tagFlags;
SCNHANDLE hOverrideTag;
/* For TAG and EXIT (and EFFECT in future?) polygons only */
TSTATE tagState;
PSTATE pointState;
/* For Path polygons only */
bool tried;
/*
* Internal derived data for speed and conveniance
* set up by FiddlyBit()
*/
short ptop; //
short pbottom; // Enclosing external rectangle
short pleft; //
short pright; //
short ltop[4]; //
short lbottom[4]; // Rectangles enclosing each side
short lleft[4]; //
short lright[4]; //
int a[4]; // y1-y2 }
int b[4]; // x2-x1 } See IsInPolygon()
long c[4]; // y1x2 - x1y2 }
/*
* Internal derived data for speed and conveniance
* set up by PseudoCentre()
*/
int pcentrex; // Pseudo-centre
int pcentrey; //
/**
* List of adjacent polygons. For Path polygons only.
* set up by SetPathAdjacencies()
*/
POLYGON *adjpaths[MAXADJ];
};
typedef POLYGON *PPOLYGON;
#define MAXONROUTE 40
#include "common/pack-start.h" // START STRUCT PACKING
/** lineinfo struct - one per (node-1) in a node path */
struct LINEINFO {
int32 a;
int32 b;
int32 c;
int32 a2; ///< a squared
int32 b2; ///< b squared
int32 a2pb2; ///< a squared + b squared
int32 ra2pb2; ///< root(a squared + b squared)
int32 ab;
int32 ac;
int32 bc;
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
/**
* POLY structure class. This is implemented as a class, because the structure
* of POLY's changed between TINSEL v1 and v2.
*
* FIXME: Right now, we always read *all* data in a polygon, even if only a single
* field is needed. This is rather inefficient.
*/
class Poly {
private:
const byte * const _pStart;
const byte *_pData;
int _recordSize;
void nextPoly();
public:
Poly(const byte *pSrc);
Poly(const byte *pSrc, int startIndex);
void operator++();
void setIndex(int index);
POLY_TYPE getType() const { return (POLY_TYPE)FROM_LE_32(type); }
int getNodecount() const { return (int)FROM_LE_32(nodecount); }
int getNodeX(int i) const { return (int)FROM_LE_32(nlistx[i]); }
int getNodeY(int i) const { return (int)FROM_LE_32(nlisty[i]); }
// get Inter-node line structure
const LINEINFO *getLineinfo(int i) const { return ((const LINEINFO *)(_pStart + (int)FROM_LE_32(plinelist))) + i; }
protected:
POLY_TYPE type; ///< type of polygon
public:
int32 x[4], y[4]; // Polygon definition
uint32 xoff, yoff; // DW2 - polygon offset
int32 tagx, tagy; // } For tagged polygons
SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT
int32 nodex, nodey; // EXIT, TAG, REFER
SCNHANDLE hFilm; ///< film reel handle for EXIT, TAG
int32 reftype; ///< Type of REFER
int32 id; // } EXIT and TAG
int32 scale1, scale2; // }
int32 level1, level2; // DW2 fields
int32 bright1, bright2; // DW2 fields
int32 reel; // } PATH and NPATH
int32 zFactor; // }
protected:
int32 nodecount; ///<The number of nodes in this polygon
int32 pnodelistx, pnodelisty; ///<offset in chunk to this array if present
int32 plinelist;
const int32 *nlistx;
const int32 *nlisty;
public:
SCNHANDLE hScript; ///< handle of code segment for polygon events
};
Poly::Poly(const byte *pSrc) : _pStart(pSrc) {
_pData = pSrc;
nextPoly();
_recordSize = _pData - pSrc;
}
Poly::Poly(const byte *pSrc, int startIndex) : _pStart(pSrc) {
_pData = pSrc;
nextPoly();
_recordSize = _pData - pSrc;
setIndex(startIndex);
}
void Poly::operator++() {
nextPoly();
}
void Poly::setIndex(int index) {
_pData = _pStart + index * _recordSize;
nextPoly();
}
static uint32 nextLong(const byte *&p) {
uint32 result = READ_UINT32(p);
p += 4;
return result;
}
void Poly::nextPoly() {
// Note: For now we perform no endian conversion of the data. We could change that
// at some point, and remove all endian conversions from the code that uses POLY's
const byte *pRecord = _pData;
int typeVal = nextLong(_pData);
if ((FROM_LE_32(typeVal) == 5) && TinselV2)
typeVal = TO_LE_32(6);
type = (POLY_TYPE)typeVal;
for (int i = 0; i < 4; ++i)
x[i] = nextLong(_pData);
for (int i = 0; i < 4; ++i)
y[i] = nextLong(_pData);
if (TinselV2) {
xoff = nextLong(_pData);
yoff = nextLong(_pData);
id = nextLong(_pData);
reftype = nextLong(_pData);
}
tagx = nextLong(_pData);
tagy = nextLong(_pData);
hTagtext = nextLong(_pData);
nodex = nextLong(_pData);
nodey = nextLong(_pData);
hFilm = nextLong(_pData);
if (!TinselV2) {
reftype = nextLong(_pData);
id = nextLong(_pData);
}
scale1 = nextLong(_pData);
scale2 = nextLong(_pData);
if (TinselV2) {
level1 = nextLong(_pData);
level2 = nextLong(_pData);
bright1 = nextLong(_pData);
bright2 = nextLong(_pData);
}
reel = nextLong(_pData);
zFactor = nextLong(_pData);
nodecount = nextLong(_pData);
pnodelistx = nextLong(_pData);
pnodelisty = nextLong(_pData);
plinelist = nextLong(_pData);
nlistx = (const int32 *)(_pStart + (int)FROM_LE_32(pnodelistx));
nlisty = (const int32 *)(_pStart + (int)FROM_LE_32(pnodelisty));
if (TinselV0)
// Skip to the last 4 bytes of the record for the hScript value
_pData = pRecord + 0x62C;
hScript = nextLong(_pData);
}
//----------------- LOCAL GLOBAL DATA --------------------
static int MaxPolys = MAX_POLY;
static POLYGON *Polys[MAX_POLY+1];
static POLYGON *Polygons = 0;
static SCNHANDLE pHandle = 0; // } Set at start of each scene
static int noofPolys = 0; // }
static POLYGON extraBlock; // Used for dynamic blocking
static int pathsOnRoute = 0;
static const POLYGON *RoutePaths[MAXONROUTE];
static POLYGON *RouteEnd = 0;
#ifdef DEBUG
int highestYet = 0;
#endif
// dead/alive, offsets
static POLY_VOLATILE volatileStuff[MAX_POLY];
//----------------- LOCAL MACROS --------------------
// The str parameter is no longer used
#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY);
#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys);
//-------------------- METHODS ----------------------
static HPOLYGON PolygonIndex(const POLYGON *pp) {
for (int i = 0; i <= MAX_POLY; ++i) {
if (Polys[i] == pp)
return i;
}
error("PolygonIndex(): polygon not found");
}
/**
* Returns TRUE if the point is within the polygon supplied.
*
* Firstly, the point must be within the smallest imaginary rectangle
* which encloses the polygon.
*
* Then, from each corner of the polygon, if the point is within an
* imaginary rectangle enclosing the clockwise-going side from that
* corner, the gradient of a line from the corner to the point must be
* less than (or more negative than) the gradient of that side:
*
* If the corners' coordinates are designated (x1, y1) and (x2, y2), and
* the point in question's (xt, yt), then:
* gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt)
* (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1)
* (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1)
* xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1
* xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2
*
* If the point passed one of the four 'side tests', and failed none,
* then it must be within the polygon. If the point was not tested, it
* may be within the internal rectangle not covered by the above tests.
*
* Most polygons contain an internal rectangle which does not fall into
* any of the above side-related tests. Such a rectangle will always
* have two polygon corners above it and two corners to the left of it.
*/
bool IsInPolygon(int xt, int yt, HPOLYGON hp) {
const POLYGON *pp;
int i;
bool BeenTested = false;
int pl = 0, pa = 0;
CHECK_HP_OR(hp, "Out of range polygon handle (1)");
pp = Polys[hp];
assert(pp != NULL); // Testing whether in a NULL polygon
// Shift cursor for relative polygons
if (TinselV2) {
xt -= volatileStuff[hp].xoff;
yt -= volatileStuff[hp].yoff;
}
/* Is point within the external rectangle? */
if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom)
return false;
// For each corner/side
for (i = 0; i < 4; i++) {
// If within this side's 'testable' area
// i.e. within the width of the line in y direction of end of line
// or within the height of the line in x direction of end of line
if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i])))
|| (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) {
if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i])
return false;
else
BeenTested = true;
}
}
if (BeenTested) {
// New dodgy code 29/12/94
if (pp->polyType == BLOCK) {
// For each corner/side
for (i = 0; i < 4; i++) {
// Pretend the corners of blocking polys are not in the poly.
if (xt == pp->cx[i] && yt == pp->cy[i])
return false;
}
}
return true;
} else {
// Is point within the internal rectangle?
for (i = 0; i < 4; i++) {
if (pp->cx[i] < xt)
pl++;
if (pp->cy[i] < yt)
pa++;
}
if (pa == 2 && pl == 2)
return true;
else
return false;
}
}
/**
* Finds a polygon of the specified type containing the supplied point.
*/
HPOLYGON InPolygon(int xt, int yt, PTYPE type) {
for (int j = 0; j <= MAX_POLY; j++) {
if (Polys[j] && Polys[j]->polyType == type) {
if (IsInPolygon(xt, yt, j))
return j;
}
}
return NOPOLY;
}
/**
* Given a blocking polygon, current co-ordinates of an actor, and the
* co-ordinates of where the actor is heading, works out which corner of
* the blocking polygon to head around.
*/
void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) {
const POLYGON *pp;
int i;
int xd, yd; // distance per axis
int ThisD, SmallestD = 1000;
int D1, D2;
int NearestToHere = 1000, NearestToTarget;
unsigned At = 10; // Corner already at
int bcx[4], bcy[4]; // Bogus corners
CHECK_HP_OR(hp, "Out of range polygon handle (2)");
pp = Polys[hp];
// Work out a point outside each corner
for (i = 0; i < 4; i++) {
int next, prev;
// X-direction
next = pp->cx[i] - pp->cx[(i+1)%4];
prev = pp->cx[i] - pp->cx[(i+3)%4];
if (next <= 0 && prev <= 0)
bcx[i] = pp->cx[i] - 4; // Both points to the right
else if (next >= 0 && prev >= 0)
bcx[i] = pp->cx[i] + 4; // Both points to the left
else
bcx[i] = pp->cx[i];
// Y-direction
next = pp->cy[i] - pp->cy[(i+1)%4];
prev = pp->cy[i] - pp->cy[(i+3)%4];
if (next <= 0 && prev <= 0)
bcy[i] = pp->cy[i] - 4; // Both points below
else if (next >= 0 && prev >= 0)
bcy[i] = pp->cy[i] + 4; // Both points above
else
bcy[i] = pp->cy[i];
}
// Find nearest corner to where we are,
// but not the one we're stood at.
for (i = 0; i < 4; i++) { // For 4 corners
// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]);
ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]);
if (ThisD < SmallestD) {
// Ignore this corner if it's not in a path
if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY ||
InPolygon(bcx[i], bcy[i], PATH) == NOPOLY)
continue;
// Are we stood at this corner?
if (ThisD > 4) {
// No - it's the nearest we've found yet.
NearestToHere = i;
SmallestD = ThisD;
} else {
// Stood at/next to this corner
At = i;
}
}
}
// If we're not already at a corner, go to the nearest corner
if (At == 10) {
// Not stood at a corner
// assert(NearestToHere != 1000); // At blocking corner, not found near corner!
// Better to give up than to assert fail!
if (NearestToHere == 1000) {
// Send it to where it is now
// i.e. leave x and y alone
} else {
*x = bcx[NearestToHere];
*y = bcy[NearestToHere];
}
} else {
// Already at a corner. Go to an adjacent corner.
// First, find out which adjacent corner is nearest the target.
xd = ABS(tarx - pp->cx[(At + 1) % 4]);
yd = ABS(tary - pp->cy[(At + 1) % 4]);
D1 = xd + yd;
xd = ABS(tarx - pp->cx[(At + 3) % 4]);
yd = ABS(tary - pp->cy[(At + 3) % 4]);
D2 = xd + yd;
NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4;
if (NearestToTarget == NearestToHere) {
*x = bcx[NearestToHere];
*y = bcy[NearestToHere];
} else {
// Need to decide whether it's better to go to the nearest to
// here and then on to the target, or to the nearest to the
// target and on from there.
xd = ABS(pp->cx[At] - pp->cx[NearestToHere]);
D1 = xd;
xd = ABS(pp->cx[NearestToHere] - tarx);
D1 += xd;
yd = ABS(pp->cy[At] - pp->cy[NearestToHere]);
D1 += yd;
yd = ABS(pp->cy[NearestToHere] - tary);
D1 += yd;
xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]);
D2 = xd;
xd = ABS(pp->cx[NearestToTarget] - tarx);
D2 += xd;
yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]);
D2 += yd;
yd = ABS(pp->cy[NearestToTarget] - tary);
D2 += yd;
if (D2 > D1) {
*x = bcx[NearestToHere];
*y = bcy[NearestToHere];
} else {
*x = bcx[NearestToTarget];
*y = bcy[NearestToTarget];
}
}
}
}
/**
* Try do drop a perpendicular to each inter-node line from the point
* and remember the shortest (if any).
* Find which node is nearest to the point.
* The shortest of these gives the best point in the node path.
*/
void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) {
const POLYGON *pp;
int dropD; // length of perpendicular (i.e. distance of point from line)
int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line
int d1, d2; // distance from perpendicular intersect to line's end nodes
int shortestD = 10000; // Shortest distance found
int nearestL = -1; // Nearest line
int nearestN; // Nearest Node
int h = *x; // For readability/conveniance
int k = *y; // - why aren't these #defines?
CHECK_HP(hp, "Out of range polygon handle (3)");
pp = Polys[hp];
// Pointer to polygon data
Poly ptp(LockMem(pHandle), pp->pIndex); // This polygon
// Look for fit of perpendicular to lines between nodes
for (int i = 0; i < ptp.getNodecount() - 1; i++) {
const LINEINFO *line = ptp.getLineinfo(i);
const int32 a = (int)FROM_LE_32(line->a);
const int32 b = (int)FROM_LE_32(line->b);
const int32 c = (int)FROM_LE_32(line->c);
#if 1
// TODO: If the comments of the LINEINFO struct are correct, then it contains mostly
// duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices
// we support, calculating a product of two ints is not an issue.
// So we can just load & endian convert a,b,c, then replace stuff like
// (int)FROM_LE_32(line->ab)
// by simply a*b, which makes it easier to understand what the code does, too.
// Just in case there is some bugged data, I leave this code here for verifying it.
// Let's leave it in for some time.
//
// One bad thing: We use sqrt to compute a square root. Might not be a good idea,
// speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later.
int32 a2 = (int)FROM_LE_32(line->a2); ///< a squared
int32 b2 = (int)FROM_LE_32(line->b2); ///< b squared
int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); ///< a squared + b squared
int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); ///< root(a squared + b squared)
int32 ab = (int)FROM_LE_32(line->ab);
int32 ac = (int)FROM_LE_32(line->ac);
int32 bc = (int)FROM_LE_32(line->bc);
assert(a*a == a2);
assert(b*b == b2);
assert(a*b == ab);
assert(a*c == ac);
assert(b*c == bc);
assert(a2pb2 == a*a + b*b);
assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b));
#endif
if (a == 0 && b == 0)
continue; // Line is just a point!
// X position of dropped perpendicular intersection with line
dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
// X distances from intersection to end nodes
d1 = dropX - ptp.getNodeX(i);
d2 = dropX - ptp.getNodeX(i+1);
// if both -ve or both +ve, no fit
if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
continue;
//#if 0
// Y position of sidweays perpendicular intersection with line
dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
// Y distances from intersection to end nodes
d1 = dropY - ptp.getNodeY(i);
d2 = dropY - ptp.getNodeY(i+1);
// if both -ve or both +ve, no fit
if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
continue;
//#endif
dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b);
dropD = ABS(dropD);
if (dropD < shortestD) {
shortestD = dropD;
nearestL = i;
}
}
// Distance to nearest node
nearestN = NearestNodeWithin(hp, h, k);
dropD = ABS(h - ptp.getNodeX(nearestN)) + ABS(k - ptp.getNodeY(nearestN));
// Go to a node or a point on a line
if (dropD < shortestD) {
// A node is nearest
*x = ptp.getNodeX(nearestN);
*y = ptp.getNodeY(nearestN);
*pline = nearestN;
} else {
assert(nearestL != -1);
// A point on a line is nearest
const LINEINFO *line = ptp.getLineinfo(nearestL);
const int32 a = (int)FROM_LE_32(line->a);
const int32 b = (int)FROM_LE_32(line->b);
const int32 c = (int)FROM_LE_32(line->c);
dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
*x = dropX;
*y = dropY;
*pline = nearestL;
}
assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!)
}
/**
* Returns TRUE if two paths are asdjacent.
*/
bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) {
const POLYGON *pp1, *pp2;
CHECK_HP(hPath1, "Out of range polygon handle (4)");
CHECK_HP(hPath2, "Out of range polygon handle (500)");
if (hPath1 == hPath2)
return true;
pp1 = Polys[hPath1];
pp2 = Polys[hPath2];
for (int j = 0; j < MAXADJ; j++)
if (pp1->adjpaths[j] == pp2)
return true;
return false;
}
static const POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) {
POLYGON *x;
// For each path adjacent to this one
for (int j = 0; j < MAXADJ; j++) {
x = current->adjpaths[j]; // call the adj. path x
if (x == whereto) {
RoutePaths[pathsOnRoute++] = x;
return x; // Got there!
}
if (x == NULL)
break; // no more adj. paths to look at
if (x->tried)
continue; // don't double back
if (x == last)
continue; // don't double back
x->tried = true;
if (TryPath(current, whereto, x) != NULL) {
RoutePaths[pathsOnRoute++] = x;
assert(pathsOnRoute < MAXONROUTE);
return x; // Got there in this direction
} else
x->tried = false;
}
return NULL;
}
/**
* Sort out the first path to head to for the imminent leg of a walk.
*/
static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) {
// TODO: Fingolfin says: This code currently uses DFS (depth first search),
// in the TryPath function, to compute a path between 'from' and 'to'.
// However, a BFS (breadth first search) might yield more natural results,
// at least in cases where there are multiple possible paths.
// There is a small risk of regressions caused by such a change, though.
//
// Also, the overhead of computing a DFS again and again could be avoided
// by computing a path matrix (like we do in the SCUMM engine).
int i;
CHECK_HP(from, "Out of range polygon handle (501a)");
CHECK_HP(to, "Out of range polygon handle (501b)");
if (IsAdjacentPath(from, to))
return to;
for (i = 0; i < MAX_POLY; i++) { // For each polygon..
POLYGON *p = Polys[i];
if (p && p->polyType == PATH) //...if it's a path
p->tried = false;
}
Polys[from]->tried = true;
pathsOnRoute = 0;
const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]);
if (TinselV2 && !p)
return NOPOLY;
assert(p != NULL); // Trying to find route between unconnected paths
// Don't go a roundabout way to an adjacent path.
for (i = 0; i < pathsOnRoute; i++) {
CHECK_HP(PolygonIndex(RoutePaths[i]), "Out of range polygon handle (502)");
if (IsAdjacentPath(from, PolygonIndex(RoutePaths[i])))
return PolygonIndex(RoutePaths[i]);
}
return PolygonIndex(p);
}
/**
* Indirect method of calling PathOnTheWay().
* Used to be implemented using coroutines, to put the burden of
* recursion onto the main stack. Since our "fake" coroutines use the
* same stack for everything anyway, we can do without the coroutines.
*/
HPOLYGON GetPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) {
CHECK_HP(hFrom, "Out of range polygon handle (6)");
CHECK_HP(hTo, "Out of range polygon handle (7)");
// Reuse already computed result
if (RouteEnd == Polys[hTo]) {
for (int i = 0; i < pathsOnRoute; i++) {
CHECK_HP(PolygonIndex(RoutePaths[i]), "Out of range polygon handle (503)");
if (IsAdjacentPath(hFrom, PolygonIndex(RoutePaths[i]))) {
return PolygonIndex(RoutePaths[i]);
}
}
}
RouteEnd = Polys[hTo];
return PathOnTheWay(hFrom, hTo);
}
/**
* Given a node path, work out which end node is nearest the given point.
*/
int NearestEndNode(HPOLYGON hPath, int x, int y) {
const POLYGON *pp;
int d1, d2;
CHECK_HP(hPath, "Out of range polygon handle (8)");
pp = Polys[hPath];
Poly ptp(LockMem(pHandle), pp->pIndex); // This polygon
const int nodecount = ptp.getNodecount() - 1;
d1 = ABS(x - ptp.getNodeX(0)) + ABS(y - ptp.getNodeY(0));
d2 = ABS(x - ptp.getNodeX(nodecount)) + ABS(y - ptp.getNodeY(nodecount));
return (d2 > d1) ? 0 : nodecount;
}
/**
* Given a start path and a destination path, find which pair of end
* nodes is nearest together.
* Return which node in the start path is part of the closest pair.
*/
int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) {
const POLYGON *pSpath, *pDpath;
int dist, NearDist;
int NearNode;
CHECK_HP(hSpath, "Out of range polygon handle (9)");
CHECK_HP(hDpath, "Out of range polygon handle (10)");
pSpath = Polys[hSpath];
pDpath = Polys[hDpath];
uint8 *pps = LockMem(pHandle); // All polygons
Poly ps(pps, pSpath->pIndex); // Start polygon
Poly pd(pps, pDpath->pIndex); // Dest polygon
// 'top' nodes in each path
const int ns = ps.getNodecount() - 1;
const int nd = pd.getNodecount() - 1;
// start[0] to dest[0]
NearDist = ABS(ps.getNodeX(0) - pd.getNodeX(0)) + ABS(ps.getNodeY(0) - pd.getNodeY(0));
NearNode = 0;
// start[0] to dest[top]
dist = ABS(ps.getNodeX(0) - pd.getNodeX(nd)) + ABS(ps.getNodeY(0) - pd.getNodeY(nd));
if (dist < NearDist)
NearDist = dist;
// start[top] to dest[0]
dist = ABS(ps.getNodeX(ns) - pd.getNodeX(0)) + ABS(ps.getNodeY(ns) - pd.getNodeY(0));
if (dist < NearDist) {
NearDist = dist;
NearNode = ns;
}
// start[top] to dest[top]
dist = ABS(ps.getNodeX(ns) - pd.getNodeX(nd)) + ABS(ps.getNodeY(ns) - pd.getNodeY(nd));
if (dist < NearDist) {
NearNode = ns;
}
return NearNode;
}
/**
* Given a follow nodes path and a co-ordinate, finds which node in the
* path is nearest to the co-ordinate.
*/
int NearestNodeWithin(HPOLYGON hNpath, int x, int y) {
int ThisDistance, SmallestDistance = 1000;
int NearestYet = 0; // Number of nearest node
CHECK_HP(hNpath, "Out of range polygon handle (11)");
Poly ptp(LockMem(pHandle), Polys[hNpath]->pIndex); // This polygon
const int numNodes = ptp.getNodecount(); // Number of nodes in this follow nodes path
for (int i = 0; i < numNodes; i++) {
ThisDistance = ABS(x - ptp.getNodeX(i)) + ABS(y - ptp.getNodeY(i));
if (ThisDistance < SmallestDistance) {
NearestYet = i;
SmallestDistance = ThisDistance;
}
}
return NearestYet;
}
/**
* Given a point and start and destination paths, find the nearest
* corner (if any) of the start path which is within the destination
* path. If there is no such corner, find the nearest corner of the
* destination path which falls within the source path.
*/
void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) {
const POLYGON *psp, *pdp;
int j;
int ncorn = 0; // nearest corner
HPOLYGON hNpath = NOPOLY; // path containing nearest corner
int ThisD, SmallestD = 1000;
CHECK_HP(hStartPoly, "Out of range polygon handle (12)");
CHECK_HP(hDestPoly, "Out of range polygon handle (13)");
psp = Polys[hStartPoly];
pdp = Polys[hDestPoly];
// Nearest corner of start path in destination path.
for (j = 0; j < 4; j++) {
if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) {
ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]);
if (ThisD < SmallestD) {
hNpath = hStartPoly;
ncorn = j;
// Try to ignore it if virtually stood on it
if (ThisD > 4)
SmallestD = ThisD;
}
}
}
if (SmallestD == 1000) {
// Nearest corner of destination path in start path.
for (j = 0; j < 4; j++) {
if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) {
ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]);
if (ThisD < SmallestD) {
hNpath = hDestPoly;
ncorn = j;
// Try to ignore it if virtually stood on it
if (ThisD > 4)
SmallestD = ThisD;
}
}
}
}
if (hNpath != NOPOLY) {
*x = Polys[hNpath]->cx[ncorn];
*y = Polys[hNpath]->cy[ncorn];
} else
error("NearestCorner() failure");
}
bool IsPolyCorner(HPOLYGON hPath, int x, int y) {
CHECK_HP(hPath, "Out of range polygon handle (37)");
for (int i = 0; i < 4; i++) {
if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y)
return true;
}
return false;
}
/**
* Given a path polygon and a Y co-ordinate, return a scale value.
*/
int GetScale(HPOLYGON hPath, int y) {
int zones; // Number of different scales
int zlen; // Depth of each scale zone
int scale;
int top;
// To try and fix some unknown potential bug
if (hPath == NOPOLY)
return SCALE_LARGE;
CHECK_HP(hPath, "Out of range polygon handle (14)");
Poly ptp(LockMem(pHandle), Polys[hPath]->pIndex);
// Path is of a constant scale?
if (FROM_LE_32(ptp.scale2) == 0)
return FROM_LE_32(ptp.scale1);
assert(FROM_LE_32(ptp.scale1) >= FROM_LE_32(ptp.scale2));
zones = FROM_LE_32(ptp.scale1) - FROM_LE_32(ptp.scale2) + 1;
zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones;
scale = FROM_LE_32(ptp.scale1);
top = Polys[hPath]->ptop;
do {
top += zlen;
if (y < top)
return scale;
} while (--scale);
return FROM_LE_32(ptp.scale2);
}
/**
* Given a path polygon and a Y co-ordinate, return a brightness value.
*/
int GetBrightness(HPOLYGON hPath, int y) {
int zones; // Number of different brightnesses
int zlen; // Depth of each brightness zone
int brightness;
int top;
// To try and fix some unknown potential bug
if (hPath == NOPOLY)
return 10;
CHECK_HP(hPath, "Out of range polygon handle (38)");
Poly ptp(LockMem(pHandle), Polys[hPath]->pIndex);
// Path is of a constant brightness?
if (FROM_LE_32(ptp.bright1) == FROM_LE_32(ptp.bright2))
return FROM_LE_32(ptp.bright1);
assert(FROM_LE_32(ptp.bright1) >= FROM_LE_32(ptp.bright2));
zones = FROM_LE_32(ptp.bright1) - FROM_LE_32(ptp.bright2) + 1;
zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones;
brightness = FROM_LE_32(ptp.bright1);
top = Polys[hPath]->ptop;
do {
top += zlen;
if (y < top)
return brightness;
} while (--brightness);
return FROM_LE_32(ptp.bright2);
}
/**
* Give the co-ordinates of a node in a node path.
*/
void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) {
CHECK_HP(hNpath, "Out of range polygon handle (15)");
assert(Polys[hNpath] != NULL && Polys[hNpath]->polyType == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path!
Poly ptp(LockMem(pHandle), Polys[hNpath]->pIndex); // This polygon
// Might have just walked to the node from above.
if (node == ptp.getNodecount())
node -= 1;
*px = ptp.getNodeX(node);
*py = ptp.getNodeY(node);
}
/**
* Get compiled tag text handle and tag co-ordinates of a tag polygon.
*/
void GetTagTag(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) {
CHECK_HP(hp, "Out of range polygon handle (16)");
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
*tagx = (int)FROM_LE_32(ptp.tagx) + (TinselV2 ? volatileStuff[hp].xoff : 0);
*tagy = (int)FROM_LE_32(ptp.tagy) + (TinselV2 ? volatileStuff[hp].yoff : 0);
*hTagText = FROM_LE_32(ptp.hTagtext);
}
/**
* Get polygon's film reel handle.
*/
SCNHANDLE GetPolyFilm(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (17)");
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
return FROM_LE_32(ptp.hFilm);
}
/**
* Get handle to polygon's glitter code.
*/
SCNHANDLE GetPolyScript(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (19)");
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
return FROM_LE_32(ptp.hScript);
}
REEL GetPolyReelType(HPOLYGON hp) {
// To try and fix some unknown potential bug (toyshop entrance)
if (hp == NOPOLY)
return REEL_ALL;
CHECK_HP(hp, "Out of range polygon handle (20)");
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
return (REEL)FROM_LE_32(ptp.reel);
}
int32 GetPolyZfactor(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (21)");
assert(Polys[hp] != NULL);
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
return (int)FROM_LE_32(ptp.zFactor);
}
int numNodes(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (22)");
assert(Polys[hp] != NULL);
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
return ptp.getNodecount();
}
// *************************************************************************
//
// Code concerned with killing and reviving TAG and EXIT polygons.
// And code to enable this information to be saved and restored.
//
// *************************************************************************
struct TAGSTATE {
int tid;
bool enabled;
};
#define MAX_SCENES 256
#define MAX_TAGS 2048
#define MAX_EXITS 512
static struct {
SCNHANDLE sid;
int nooftags;
int offset;
} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES];
static TAGSTATE TagStates[MAX_TAGS];
static TAGSTATE ExitStates[MAX_EXITS];
static int nextfreeT = 0, numScenesT = 0;
static int nextfreeE = 0, numScenesE = 0;
static int currentTScene = 0;
static int currentEScene = 0;
bool deadPolys[MAX_POLY]; // Currently just for dead blocks
void RebootDeadTags() {
nextfreeT = numScenesT = 0;
nextfreeE = numScenesE = 0;
memset(SceneTags, 0, sizeof(SceneTags));
memset(SceneExits, 0, sizeof(SceneExits));
memset(TagStates, 0, sizeof(TagStates));
memset(ExitStates, 0, sizeof(ExitStates));
memset(deadPolys, 0, sizeof(deadPolys));
}
/**
* (Un)serialize the dead tag and exit data for save/restore game.
*/
void syncPolyInfo(Common::Serializer &s) {
int i;
for (i = 0; i < MAX_SCENES; i++) {
s.syncAsUint32LE(SceneTags[i].sid);
s.syncAsSint32LE(SceneTags[i].nooftags);
s.syncAsSint32LE(SceneTags[i].offset);
}
for (i = 0; i < MAX_SCENES; i++) {
s.syncAsUint32LE(SceneExits[i].sid);
s.syncAsSint32LE(SceneExits[i].nooftags);
s.syncAsSint32LE(SceneExits[i].offset);
}
for (i = 0; i < MAX_TAGS; i++) {
s.syncAsUint32LE(TagStates[i].tid);
s.syncAsSint32LE(TagStates[i].enabled);
}
for (i = 0; i < MAX_EXITS; i++) {
s.syncAsUint32LE(ExitStates[i].tid);
s.syncAsSint32LE(ExitStates[i].enabled);
}
s.syncAsSint32LE(nextfreeT);
s.syncAsSint32LE(numScenesT);
s.syncAsSint32LE(nextfreeE);
s.syncAsSint32LE(numScenesE);
}
/**
* This is all totally different to the way the rest of the way polygon
* data is stored and restored, more specifically, different to how dead
* tags and exits are handled, because of the piecemeal design-by-just-
* thought-of-this approach employed.
*/
void SaveDeadPolys(bool *sdp) {
assert(!TinselV2);
memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool));
}
void RestoreDeadPolys(bool *sdp) {
assert(!TinselV2);
memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool));
}
void SavePolygonStuff(POLY_VOLATILE *sps) {
assert(TinselV2);
memcpy(sps, volatileStuff, MAX_POLY*sizeof(POLY_VOLATILE));
}
void RestorePolygonStuff(POLY_VOLATILE *sps) {
assert(TinselV2);
memcpy(volatileStuff, sps, MAX_POLY*sizeof(POLY_VOLATILE));
}
/**
* Scan for a given polygon
*/
static HPOLYGON FindPolygon(PTYPE type, int id) {
for (int i = 0; i <= MAX_POLY; i++) {
if (Polys[i] && Polys[i]->polyType == type && Polys[i]->polyID == id) {
// Found it
return i;
}
}
// Not found
return NOPOLY;
}
HPOLYGON FirstPathPoly() {
for (int i = 0; i < noofPolys; i++) {
if (Polys[i]->polyType == PATH)
return i;
}
error("FirstPathPoly() - no PATH polygons");
return NOPOLY; // for compilers that don't support NORETURN
}
HPOLYGON GetPolyHandle(int i) {
assert(i >= 0 && i <= MAX_POLY);
return (Polys[i] != NULL) ? i : NOPOLY;
}
// **************************************************************************
//
// Code called to initialise or wrap up a scene:
//
// **************************************************************************
/**
* Called at the start of a scene, when all polygons have been
* initialised, to work out which paths are adjacent to which.
*/
static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) {
const POLYGON *pp1, *pp2;
int i, j;
int retval = 0;
CHECK_HP(hp1, "Out of range polygon handle (23)");
CHECK_HP(hp2, "Out of range polygon handle (24)");
pp1 = Polys[hp1];
pp2 = Polys[hp2];
// Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1)
for (i = 0; i < 4; i++) {
if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2))
retval++;
if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1))
retval++;
}
// Common corners only count once
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j])
retval--;
}
}
return retval;
}
/**
* Returns true if the two paths are on the same level
*/
static bool MatchingLevels(PPOLYGON p1, PPOLYGON p2) {
byte *pps = LockMem(pHandle); // All polygons
Poly pp1(pps, p1->pIndex); // This polygon 1
Poly pp2(pps, p2->pIndex); // This polygon 2
assert((int32)FROM_LE_32(pp1.level1) <= (int32)FROM_LE_32(pp1.level2));
assert((int32)FROM_LE_32(pp2.level1) <= (int32)FROM_LE_32(pp2.level2));
for (int pl = (int32)FROM_LE_32(pp1.level1); pl <= (int32)FROM_LE_32(pp1.level2); pl++) {
if (pl >= (int32)FROM_LE_32(pp2.level1) && pl <= (int32)FROM_LE_32(pp2.level2))
return true;
}
return false;
}
static void SetPathAdjacencies() {
POLYGON *p1, *p2; // Polygon pointers
int i1, i2;
// Reset them all
for (i1 = 0; i1 < noofPolys; i1++)
memset(Polys[i1]->adjpaths, 0, MAXADJ * sizeof(PPOLYGON));
// For each polygon..
for (i1 = 0; i1 < MAX_POLY-1; i1++) {
// Get polygon, but only carry on if it's a path
p1 = Polys[i1];
if (!p1 || p1->polyType != PATH)
continue;
// For each subsequent polygon..
for (i2 = i1 + 1; i2 < MAX_POLY; i2++) {
// Get polygon, but only carry on if it's a path
p2 = Polys[i2];
if (!p2 || p2->polyType != PATH)
continue;
// Must be on the same level
if (TinselV2 && !MatchingLevels(p1, p2))
continue;
int j = DistinctCorners(i1, i2);
if (j >= 2) {
// Paths are adjacent
for (j = 0; j < MAXADJ; j++)
if (p1->adjpaths[j] == NULL) {
p1->adjpaths[j] = p2;
break;
}
#ifdef DEBUG
if (j > highestYet)
highestYet = j;
#endif
assert(j < MAXADJ); // Number of adjacent paths limit
for (j = 0; j < MAXADJ; j++) {
if (p2->adjpaths[j] == NULL) {
p2->adjpaths[j] = p1;
break;
}
}
#ifdef DEBUG
if (j > highestYet)
highestYet = j;
#endif
assert(j < MAXADJ); // Number of adjacent paths limit
}
}
}
}
/**
* Ensure NPATH nodes are not inside another PATH/NPATH polygon.
* Only bother with end nodes for now.
*/
#ifdef DEBUG
void CheckNPathIntegrity() {
uint8 *pps; // Compiled polygon data
const POLYGON *rp; // Run-time polygon structure
HPOLYGON hp;
int i, j; // Loop counters
int n; // Last node in current path
pps = LockMem(pHandle); // All polygons
for (i = 0; i < MAX_POLY; i++) { // For each polygon..
rp = Polys[i];
if (rp && rp->polyType == PATH && rp->subtype == NODE) { //...if it's a node path
// Get compiled polygon structure
const Poly cp(pps, rp->pIndex); // This polygon
n = cp.getNodecount() - 1; // Last node
assert(n >= 1); // Node paths must have at least 2 nodes
hp = PolygonIndex(rp);
for (j = 0; j <= n; j++) {
if (!IsInPolygon(cp.getNodeX(j), cp.getNodeY(j), hp)) {
sprintf(TextBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))",
cp.getNodeX(j), cp.getNodeY(j), rp->cx[0], rp->cy[0]);
error(TextBufferAddr());
}
}
// Check end nodes are not in adjacent path
for (j = 0; j < MAXADJ; j++) { // For each adjacent path
if (rp->adjpaths[j] == NULL)
break;
if (IsInPolygon(cp.getNodeX(0), cp.getNodeY(0), PolygonIndex(rp->adjpaths[j]))) {
sprintf(TextBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
cp.getNodeX(0), cp.getNodeY(0), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
error(TextBufferAddr());
}
if (IsInPolygon(cp.getNodeX(n), cp.getNodeY(n), PolygonIndex(rp->adjpaths[j]))) {
sprintf(TextBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
cp.getNodeX(n), cp.getNodeY(n), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
error(TextBufferAddr());
}
}
}
}
}
#endif
/**
* Called at the start of a scene, nobbles TAG polygons which should be dead.
*/
static void SetExBlocks() {
for (int i = 0; i < MAX_POLY; i++) {
if (deadPolys[i]) {
if (Polys[i] && Polys[i]->polyType == BLOCK)
Polys[i]->polyType = EX_BLOCK;
#ifdef DEBUG
else
error("Impossible message");
#endif
}
}
}
/**
* Called at the start of a scene, nobbles TAG polygons which should be dead.
*/
static void SetExTags(SCNHANDLE ph) {
int i, j;
TAGSTATE *pts;
for (i = 0; i < numScenesT; i++) {
if (SceneTags[i].sid == ph) {
currentTScene = i;
pts = &TagStates[SceneTags[i].offset];
for (j = 0; j < SceneTags[i].nooftags; j++, pts++) {
if (!pts->enabled)
DisableTag(nullContext, pts->tid);
}
return;
}
}
i = numScenesT++;
currentTScene = i;
assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit
SceneTags[i].sid = ph;
SceneTags[i].offset = nextfreeT;
SceneTags[i].nooftags = 0;
for (j = 0; j < MAX_POLY; j++) {
if (Polys[j] && Polys[j]->polyType == TAG) {
TagStates[nextfreeT].tid = Polys[j]->polyID;
TagStates[nextfreeT].enabled = true;
nextfreeT++;
assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit
SceneTags[i].nooftags++;
}
}
}
/**
* Called at the start of a scene, nobbles EXIT polygons which should be dead.
*/
static void SetExExits(SCNHANDLE ph) {
TAGSTATE *pts;
int i, j;
for (i = 0; i < numScenesE; i++) {
if (SceneExits[i].sid == ph) {
currentEScene = i;
pts = &ExitStates[SceneExits[i].offset];
for (j = 0; j < SceneExits[i].nooftags; j++, pts++) {
if (!pts->enabled)
DisableExit(pts->tid);
}
return;
}
}
i = numScenesE++;
currentEScene = i;
assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit
SceneExits[i].sid = ph;
SceneExits[i].offset = nextfreeE;
SceneExits[i].nooftags = 0;
for (j = 0; j < MAX_POLY; j++) {
if (Polys[j] && Polys[j]->polyType == EXIT) {
ExitStates[nextfreeE].tid = Polys[j]->polyID;
ExitStates[nextfreeE].enabled = true;
nextfreeE++;
assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit
SceneExits[i].nooftags++;
}
}
}
/**
* Works out some fixed numbers for a polygon.
*/
static void FiddlyBit(POLYGON *p) {
int t1, t2; // General purpose temp. variables
// Enclosing external rectangle
t1 = MAX(p->cx[0], p->cx[1]);
t2 = MAX(p->cx[2], p->cx[3]);
p->pright = MAX(t1, t2);
t1 = MIN(p->cx[0], p->cx[1]);
t2 = MIN(p->cx[2], p->cx[3]);
p->pleft = MIN(t1, t2);
t1 = MAX(p->cy[0], p->cy[1]);
t2 = MAX(p->cy[2], p->cy[3]);
p->pbottom = MAX(t1, t2);
t1 = MIN(p->cy[0], p->cy[1]);
t2 = MIN(p->cy[2], p->cy[3]);
p->ptop = MIN(t1, t2);
// Rectangles enclosing each side and each side's magic numbers
for (t1 = 0; t1 < 4; t1++) {
p->lright[t1] = MAX(p->cx[t1], p->cx[(t1+1)%4]);
p->lleft[t1] = MIN(p->cx[t1], p->cx[(t1+1)%4]);
p->ltop[t1] = MIN(p->cy[t1], p->cy[(t1+1)%4]);
p->lbottom[t1] = MAX(p->cy[t1], p->cy[(t1+1)%4]);
p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4];
p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1];
p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4];
}
}
/**
* Allocate a POLYGON structure and reset it to default values
*/
static PPOLYGON GetPolyEntry() {
int i; // Loop counter
PPOLYGON p;
for (i = 0; i < MaxPolys; i++) {
if (!Polys[i]) {
p = Polys[i] = &Polygons[i];
// What the hell, just clear it all out - it's safer
memset(p, 0, sizeof(POLYGON));
return p;
}
}
error("Exceeded MaxPolys");
}
/**
* Variation of GetPolyEntry from Tinsel 1 that splits up getting a new
* polygon structure from initialising it
*/
static PPOLYGON CommonInits(PTYPE polyType, int pno, const Poly &ptp, bool bRestart) {
int i;
HPOLYGON hp;
PPOLYGON p = GetPolyEntry(); // Obtain a slot
p->polyType = polyType; // Polygon type
p->pIndex = pno;
for (i = 0; i < 4; i++) { // Polygon definition
p->cx[i] = (short)FROM_LE_32(ptp.x[i]);
p->cy[i] = (short)FROM_LE_32(ptp.y[i]);
}
if (!bRestart) {
hp = PolygonIndex(p);
volatileStuff[hp].xoff = (short)FROM_LE_32(ptp.xoff);
volatileStuff[hp].yoff = (short)FROM_LE_32(ptp.yoff);
}
p->polyID = FROM_LE_32(ptp.id); // Identifier
FiddlyBit(p);
return p;
}
/**
* Calculate a point approximating to the centre of a polygon.
* Not very sophisticated.
*/
static void PseudoCentre(POLYGON *p) {
p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4;
p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4;
if (!IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))) {
int i, top = 0, bot = 0;
for (i = p->ptop; i <= p->pbottom; i++) {
if (IsInPolygon(p->pcentrex, i, PolygonIndex(p))) {
top = i;
break;
}
}
for (i = p->pbottom; i >= p->ptop; i--) {
if (IsInPolygon(p->pcentrex, i, PolygonIndex(p))) {
bot = i;
break;
}
}
p->pcentrex = (top+bot)/2;
}
#ifdef DEBUG
// assert(IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))); // Pseudo-centre is not in path
if (!IsInPolygon(p->pcentrex, p->pcentrey, PolygonIndex(p))) {
sprintf(TextBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?",
p->cx[0], p->cy[0]);
error(TextBufferAddr());
}
#endif
}
/**
* Initialise an EXIT polygon.
*/
static void InitExit(const Poly &ptp, int pno, bool bRestart) {
CommonInits(EXIT, pno, ptp, bRestart);
}
/**
* Initialise a PATH or NPATH polygon.
*/
static void InitPath(const Poly &ptp, bool NodePath, int pno, bool bRestart) {
PPOLYGON p = CommonInits(PATH, pno, ptp, bRestart);
p->subtype = NodePath ? NODE : NORMAL;
PseudoCentre(p);
}
/**
* Initialise a BLOCKING polygon.
*/
static void InitBlock(const Poly &ptp, int pno, bool bRestart) {
CommonInits(BLOCK, pno, ptp, bRestart);
}
/**
* Initialise an extra BLOCKING polygon related to a moving actor.
* The width of the polygon depends on the width of the actor which is
* trying to walk through the actor you first thought of.
* This is for dynamic blocking.
*/
HPOLYGON InitExtraBlock(PMOVER ca, PMOVER ta) {
int caX, caY; // Calling actor co-ords
int taX, taY; // Test actor co-ords
int left, right;
GetMoverPosition(ca, &caX, &caY); // Calling actor co-ords
GetMoverPosition(ta, &taX, &taY); // Test actor co-ords
left = GetMoverLeft(ta) - (GetMoverRight(ca) - caX);
right = GetMoverRight(ta) + (caX - GetMoverLeft(ca));
memset(&extraBlock, 0, sizeof(extraBlock));
// The 3s on the y co-ordinates used to be 10s
extraBlock.cx[0] = (short)(left - 2);
extraBlock.cy[0] = (short)(taY - 3);
extraBlock.cx[1] = (short)(right + 2);
extraBlock.cy[1] = (short)(taY - 3);
extraBlock.cx[2] = (short)(right + 2);
extraBlock.cy[2] = (short)(taY + 3);
extraBlock.cx[3] = (short)(left - 2);
extraBlock.cy[3] = (short)(taY + 3);
FiddlyBit(&extraBlock); // Is this necessary?
Polys[MAX_POLY] = &extraBlock;
return MAX_POLY;
}
/**
* Initialise an EFFECT polygon.
*/
static void InitEffect(const Poly &ptp, int pno, bool bRestart) {
CommonInits(EFFECT, pno, ptp, bRestart);
}
/**
* Initialise a REFER polygon.
*/
static void InitRefer(const Poly &ptp, int pno, bool bRestart) {
PPOLYGON p = CommonInits(REFER, pno, ptp, bRestart);
p->subtype = FROM_LE_32(ptp.reftype); // Refer type
}
/**
* Initialise a TAG polygon.
*/
static void InitTag(const Poly &ptp, int pno, bool bRestart) {
CommonInits(TAG, pno, ptp, bRestart);
}
/**
* Called at the restart of a scene, nobbles polygons which are dead.
*/
static void KillDeadPolygons() {
int i;
for (i = 0; i < MAX_POLY; i++) {
if (volatileStuff[i].bDead) {
assert(Polys[i]);
switch (Polys[i]->polyType) {
case BLOCK:
Polys[i]->polyType = EX_BLOCK;
break;
case EFFECT:
Polys[i]->polyType = EX_EFFECT;
break;
case REFER:
Polys[i]->polyType = EX_REFER;
break;
case PATH:
Polys[i]->polyType = EX_PATH;
break;
case TAG:
Polys[i]->polyType = EX_TAG;
break;
default:
error("Impossible message");
}
}
}
}
/**
* Called at the start of a scene to initialise the polys in that scene.
*/
void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) {
pHandle = ph;
noofPolys = numPoly;
if (Polygons == NULL) {
// first time - allocate memory for process list
Polygons = (POLYGON *)calloc(MaxPolys, sizeof(POLYGON));
// make sure memory allocated
if (Polygons == NULL) {
error("Cannot allocate memory for polygon data");
}
}
if (numPoly == 0)
return;
for (int i = 0; i < noofPolys; i++) {
if (Polys[i]) {
Polys[i]->pointState = PS_NOT_POINTING;
Polys[i] = NULL;
}
}
memset(RoutePaths, 0, sizeof(RoutePaths));
if (!bRestart) {
if (TinselV2)
memset(volatileStuff, 0, sizeof(volatileStuff));
else
memset(deadPolys, 0, sizeof(deadPolys));
}
if (numPoly > 0) {
Poly ptp(LockMem(ph));
for (int i = 0; i < numPoly; ++i, ++ptp) {
switch (ptp.getType()) {
case POLY_PATH:
InitPath(ptp, false, i, bRestart);
break;
case POLY_NPATH:
InitPath(ptp, true, i, bRestart);
break;
case POLY_BLOCK:
InitBlock(ptp, i, bRestart);
break;
case POLY_REFER:
InitRefer(ptp, i, bRestart);
break;
case POLY_EFFECT:
InitEffect(ptp, i, bRestart);
break;
case POLY_EXIT:
InitExit(ptp, i, bRestart);
break;
case POLY_TAG:
InitTag(ptp, i, bRestart);
break;
default:
error("Unknown polygon type");
}
}
}
if (!TinselV2) {
SetPathAdjacencies(); // Paths need to know the facts
#ifdef DEBUG
CheckNPathIntegrity();
#endif
SetExTags(ph); // Some tags may have been killed
SetExExits(ph); // Some exits may have been killed
if (bRestart)
SetExBlocks(); // Some blocks may have been killed
} else {
if (bRestart) {
// Some may have been killed if this is a restore
KillDeadPolygons();
} else {
for (int i = numPoly - 1; i >= 0; i--) {
if (Polys[i]->polyType == TAG) {
PolygonEvent(nullContext, i, STARTUP, 0, false, 0);
}
}
}
// Paths need to know the facts
SetPathAdjacencies();
}
}
/**
* Called at the end of a scene to ditch all polygons.
*/
void DropPolygons() {
pathsOnRoute = 0;
memset(RoutePaths, 0, sizeof(RoutePaths));
RouteEnd = NULL;
for (int i = 0; i < noofPolys; i++) {
if (Polys[i]) {
Polys[i]->pointState = PS_NOT_POINTING;
Polys[i] = NULL;
}
}
noofPolys = 0;
free(Polygons);
Polygons = NULL;
}
PTYPE PolyType(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (25)");
return Polys[hp]->polyType;
}
int PolySubtype(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (26)");
return Polys[hp]->subtype;
}
int PolyCentreX(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (27)");
return Polys[hp]->pcentrex;
}
int PolyCentreY(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (28)");
return Polys[hp]->pcentrey;
}
int PolyCornerX(HPOLYGON hp, int n) {
CHECK_HP(hp, "Out of range polygon handle (29)");
return Polys[hp]->cx[n];
}
int PolyCornerY(HPOLYGON hp, int n) {
CHECK_HP(hp, "Out of range polygon handle (30)");
return Polys[hp]->cy[n];
}
PSTATE PolyPointState(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (31)");
return Polys[hp]->pointState;
}
TSTATE PolyTagState(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (32)");
return Polys[hp]->tagState;
}
void SetPolyPointState(HPOLYGON hp, PSTATE ps) {
CHECK_HP(hp, "Out of range polygon handle (34)");
Polys[hp]->pointState = ps;
}
void SetPolyTagState(HPOLYGON hp, TSTATE ts) {
CHECK_HP(hp, "Out of range polygon handle (35)");
Polys[hp]->tagState = ts;
}
void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) {
CHECK_HP(hp, "Out of range polygon handle (36)");
Polys[hp]->hOverrideTag = th;
}
void MaxPolygons(int numPolys) {
assert(numPolys <= MAX_POLY);
MaxPolys = numPolys;
}
/**
* Get polygon's associated node.
* The one for WalkTag(), StandTag() etc.
*/
void GetPolyNode(HPOLYGON hp, int *pNodeX, int *pNodeY) {
CHECK_HP(hp, "GetPolyNode(): Out of range polygon handle");
Poly ptp(LockMem(pHandle), Polys[hp]->pIndex);
// WORKAROUND: Invalid node adjustment for DW2 Cartwheel scene refer polygon
if (TinselV2 && (pHandle == 0x74191900) && (hp == 8)) {
*pNodeX = 480;
*pNodeY = 408;
} else {
*pNodeX = FROM_LE_32(ptp.nodex);
*pNodeY = FROM_LE_32(ptp.nodey);
}
if (TinselV2) {
*pNodeX += volatileStuff[hp].xoff;
*pNodeY += volatileStuff[hp].yoff;
}
}
void SetPolyPointedTo(HPOLYGON hp, bool bPointedTo) {
CHECK_HP(hp, "Out of range polygon handle (34)");
if (bPointedTo)
Polys[hp]->tagFlags |= POINTING;
else
Polys[hp]->tagFlags &= ~POINTING;
}
bool PolyIsPointedTo(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (31)");
if (TinselV2)
return (Polys[hp]->tagFlags & POINTING);
return PolyPointState(hp) == PS_POINTING;
}
void SetPolyTagWanted(HPOLYGON hp, bool bTagWanted, bool bCursor, SCNHANDLE hOverrideTag) {
CHECK_HP(hp, "Out of range polygon handle (35)");
if (bTagWanted) {
Polys[hp]->tagFlags |= TAGWANTED;
Polys[hp]->hOverrideTag = hOverrideTag;
} else {
Polys[hp]->tagFlags &= ~TAGWANTED;
Polys[hp]->hOverrideTag = 0;
}
if (bCursor)
Polys[hp]->tagFlags |= FOLLOWCURSOR;
else
Polys[hp]->tagFlags &= ~FOLLOWCURSOR;
}
bool PolyTagIsWanted(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (32)");
return (Polys[hp]->tagFlags & TAGWANTED);
}
bool PolyTagFollowsCursor(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (36)");
return (Polys[hp]->tagFlags & FOLLOWCURSOR);
}
SCNHANDLE GetPolyTagHandle(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (33)");
return Polys[hp]->hOverrideTag;
}
bool IsTagPolygon(int tagno) {
return (FindPolygon(TAG, tagno) != NOPOLY || FindPolygon(EX_TAG, tagno) != NOPOLY);
}
int GetTagPolyId(HPOLYGON hp) {
CHECK_HP(hp, "Out of range polygon handle (GetTagPolyId()");
assert(Polys[hp]->polyType == TAG || Polys[hp]->polyType == EX_TAG);
return Polys[hp]->polyID;
}
void GetPolyMidBottom( HPOLYGON hp, int *pX, int *pY) {
CHECK_HP(hp, "Out of range polygon handle (GetPolyMidBottom()");
*pY = Polys[hp]->pbottom + volatileStuff[hp].yoff;
*pX = (Polys[hp]->pleft + Polys[hp]->pright)/2 + volatileStuff[hp].xoff;
}
int PathCount() {
int i, count;
for (i = 0, count = 0; i < noofPolys; i++) {
if (Polys[i]->polyType == PATH)
count++;
}
return count;
}
/**
* Convert a BLOCK to an EX_BLOCK poly
*/
void DisableBlock(int block) {
int i = FindPolygon(BLOCK, block);
if (i != NOPOLY) {
Polys[i]->polyType = EX_BLOCK;
volatileStuff[i].bDead = true;
}
}
/**
* Convert an EX_BLOCK to a BLOCK poly
*/
void EnableBlock(int block) {
int i = FindPolygon(EX_BLOCK, block);
if (i != NOPOLY) {
Polys[i]->polyType = BLOCK;
volatileStuff[i].bDead = false;
}
}
/**
* Convert an EFFECT to an EX_EFFECT poly
*/
void DisableEffect(int effect) {
int i = FindPolygon(EFFECT, effect);
if (i != NOPOLY) {
Polys[i]->polyType = EX_EFFECT;
volatileStuff[i].bDead = true;
}
}
/**
* Convert an EX_EFFECT to an EFFECT poly
*/
void EnableEffect(int effect) {
int i = FindPolygon(EX_EFFECT, effect);
if (i != NOPOLY) {
Polys[i]->polyType = EFFECT;
volatileStuff[i].bDead = false;
}
}
/**
* Convert a PATH to an EX_PATH poly
*/
void DisablePath(int path) {
int i = FindPolygon(PATH, path);
if (i != NOPOLY) {
Polys[i]->polyType = EX_PATH;
volatileStuff[i].bDead = true;
// Paths need to know the new facts
SetPathAdjacencies();
}
}
/**
* Convert a PATH to an EX_PATH poly
*/
void EnablePath(int path) {
int i = FindPolygon(EX_PATH, path);
if (i != NOPOLY) {
Polys[i]->polyType = PATH;
volatileStuff[i].bDead = false;
// Paths need to know the new facts
SetPathAdjacencies();
}
}
/**
* Convert a REFER to an EX_REFER poly
*/
void DisableRefer(int refer) {
int i = FindPolygon(REFER, refer);
if (i != NOPOLY) {
Polys[i]->polyType = EX_REFER;
volatileStuff[i].bDead = true;
}
}
/**
* Convert a REFER to an EX_REFER poly
*/
void EnableRefer(int refer) {
int i = FindPolygon(EX_REFER, refer);
if (i != NOPOLY) {
Polys[i]->polyType = REFER;
volatileStuff[i].bDead = false;
}
}
/**
* Convert an EX_TAG to a TAG poly.
*/
void EnableTag(CORO_PARAM, int tag) {
CORO_BEGIN_CONTEXT;
int i;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
if ((_ctx->i = FindPolygon(EX_TAG, tag)) != NOPOLY) {
Polys[_ctx->i]->polyType = TAG;
volatileStuff[_ctx->i].bDead = false;
if (TinselV2)
CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, SHOWEVENT, 0, true, 0));
} else if ((_ctx->i = FindPolygon(TAG, tag)) != NOPOLY) {
if (TinselV2)
CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, SHOWEVENT, 0, true, 0));
}
if (!TinselV2) {
TAGSTATE *pts = &TagStates[SceneTags[currentTScene].offset];
for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
if (pts->tid == tag) {
pts->enabled = true;
break;
}
}
}
CORO_END_CODE;
}
/**
* Convert an EX_EXIT to a EXIT poly.
*/
void EnableExit(int exitno) {
for (int i = 0; i < MAX_POLY; i++) {
if (Polys[i] && Polys[i]->polyType == EX_EXIT && Polys[i]->polyID == exitno) {
Polys[i]->polyType = EXIT;
}
}
TAGSTATE *pts;
pts = &ExitStates[SceneExits[currentEScene].offset];
for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
if (pts->tid == exitno) {
pts->enabled = true;
break;
}
}
}
/**
* Move a polygon relative to current offset.
*/
void MovePolygon(PTYPE ptype, int id, int x, int y) {
int i = FindPolygon(ptype, id);
// If not found, try its dead equivalent
if (i == NOPOLY) {
switch (ptype) {
case TAG:
ptype = EX_TAG;
break;
default:
break;
}
i = FindPolygon(ptype, id);
}
if (i != NOPOLY) {
volatileStuff[i].xoff += (short)x;
volatileStuff[i].yoff += (short)y;
}
}
/**
* Move a polygon relative to absolute offset.
*/
void MovePolygonTo(PTYPE ptype, int id, int x, int y) {
int i = FindPolygon(ptype, id);
// If not found, try its dead equivalent
if (i == NOPOLY) {
switch (ptype) {
case TAG:
ptype = EX_TAG;
break;
default:
break;
}
i = FindPolygon(ptype, id);
}
if (i != NOPOLY) {
volatileStuff[i].xoff = (short)x;
volatileStuff[i].yoff = (short)y;
}
}
/**
* Convert tag number to polygon handle.
*/
HPOLYGON GetTagHandle(int tagno) {
int i = FindPolygon(TAG, tagno);
if (i == NOPOLY)
i = FindPolygon(EX_TAG, tagno);
assert(i != NOPOLY);
return GetPolyHandle(i);
}
/**
* Convert a TAG to an EX_TAG poly.
*/
void DisableTag(CORO_PARAM, int tag) {
CORO_BEGIN_CONTEXT;
int i;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
if ((_ctx->i = FindPolygon(TAG, tag)) != NOPOLY) {
Polys[_ctx->i]->polyType = EX_TAG;
Polys[_ctx->i]->tagFlags = 0;
Polys[_ctx->i]->tagState = TAG_OFF;
Polys[_ctx->i]->pointState = PS_NOT_POINTING;
volatileStuff[_ctx->i].bDead = true;
if (TinselV2)
CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, HIDEEVENT, 0, true, 0));
} else if ((_ctx->i = FindPolygon(EX_TAG, tag)) != NOPOLY) {
if (TinselV2)
CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->i, HIDEEVENT, 0, true, 0));
}
if (!TinselV2) {
TAGSTATE *pts = &TagStates[SceneTags[currentTScene].offset];
for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
if (pts->tid == tag) {
pts->enabled = false;
break;
}
}
}
CORO_END_CODE;
}
/**
* Convert a EXIT to an EX_EXIT poly.
*/
void DisableExit(int exitno) {
TAGSTATE *pts;
for (int i = 0; i < MAX_POLY; i++) {
if (Polys[i] && Polys[i]->polyType == EXIT && Polys[i]->polyID == exitno) {
Polys[i]->polyType = EX_EXIT;
Polys[i]->tagState = TAG_OFF;
Polys[i]->pointState = PS_NOT_POINTING;
}
}
pts = &ExitStates[SceneExits[currentEScene].offset];
for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
if (pts->tid == exitno) {
pts->enabled = false;
break;
}
}
}
} // End of namespace Tinsel