scummvm/engines/tinsel/polygons.cpp
Max Horn 1dbf8d73d5 TINSEL: Mark all (?) global vars with a FIXME comment
Use of global vars is what prevents RTL from working in Tinsel (and
probably in other engines). More specifically, the fact that many
global vars are not explicitly inited when the engine is (re)launched.

svn-id: r54262
2010-11-16 09:53:55 +00:00

2371 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 --------------------
// FIXME: Avoid non-const global vars
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