mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-27 12:16:59 +00:00
51933629d1
svn-id: r45616
2369 lines
58 KiB
C++
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
|