diff options
Diffstat (limited to 'engines/tinsel/polygons.cpp')
| -rw-r--r-- | engines/tinsel/polygons.cpp | 1862 |
1 files changed, 1862 insertions, 0 deletions
diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp new file mode 100644 index 0000000000..d73e290277 --- /dev/null +++ b/engines/tinsel/polygons.cpp @@ -0,0 +1,1862 @@ +/* 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/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/serializer.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 and EXIT (and EFFECT in future?) polygons only */ + TSTATE tagState; + PSTATE pointState; + SCNHANDLE oTagHandle; // Override tag. + + /* 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]; + +}; + + +#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; + +/** polygon struct - one per polygon */ +struct POLY { + int32 type; //!< type of polygon + int32 x[4], y[4]; // Polygon definition + + 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 reel; // } PATH and NPATH + int32 zFactor; // } + + //The arrays now stored externally + int32 nodecount; //!<The number of nodes in this polygon + int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present + int32 plinelist; + + SCNHANDLE hScript; //!< handle of code segment for polygon events +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +//----------------- 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 + + + +//----------------- 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); + +static HPOLYGON PolyIndex(const POLYGON *pp) { + for (int j = 0; j <= MAX_POLY; j++) { + if (Polys[j] == pp) + return j; + } + + error("PolyIndex(): polygon not found"); + return NOPOLY; +} + +/** + * 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 + + /* 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 == BLOCKING) { + // 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; + + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Compiled polygon data + 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 + int32 *nlistx, *nlisty; + + 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? + LINEINFO *llist; // Inter-node line structure + + CHECK_HP(hp, "Out of range polygon handle (3)"); + pp = Polys[hp]; + + // Pointer to polygon data + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist)); + + // Look for fit of perpendicular to lines between nodes + for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) { + const int32 a = (int)FROM_LE_32(llist[i].a); + const int32 b = (int)FROM_LE_32(llist[i].b); + const int32 c = (int)FROM_LE_32(llist[i].c); + +#if 1 + if (true) { + //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b); + + // 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, calculatin 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. + + LINEINFO *line = &llist[i]; + 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 - (int)FROM_LE_32(nlistx[i]); + d2 = dropX - (int)FROM_LE_32(nlistx[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 - (int)FROM_LE_32(nlisty[i]); + d2 = dropY - (int)FROM_LE_32(nlisty[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 - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN])); + + // Go to a node or a point on a line + if (dropD < shortestD) { + // A node is nearest + *x = (int)FROM_LE_32(nlistx[nearestN]); + *y = (int)FROM_LE_32(nlisty[nearestN]); + *pline = nearestN; + } else { + assert(nearestL != -1); + + // A point on a line is nearest + const int32 a = (int)FROM_LE_32(llist[nearestL].a); + const int32 b = (int)FROM_LE_32(llist[nearestL].b); + const int32 c = (int)FROM_LE_32(llist[nearestL].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]); + + 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(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)"); + if (IsAdjacentPath(from, PolyIndex(RoutePaths[i]))) + return PolyIndex(RoutePaths[i]); + } + return PolyIndex(p); +} + +/** + * Indirect method of calling PathOnTheWay(), to put the burden of + * recursion onto the main stack. + */ +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(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)"); + if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) { + return PolyIndex(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; + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hPath, "Out of range polygon handle (8)"); + pp = Polys[hPath]; + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + const int nodecount = (int)FROM_LE_32(ptp->nodecount); + + d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0])); + d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1])); + + return (d2 > d1) ? 0 : nodecount - 1; +} + + +/** + * 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 ns, nd; // 'top' nodes in each path + int dist, NearDist; + int NearNode; + uint8 *pps; // Compiled polygon data + const POLY *ps, *pd; // Pointer to compiled polygon data + int32 *snlistx, *snlisty; + int32 *dnlistx, *dnlisty; + + CHECK_HP(hSpath, "Out of range polygon handle (9)"); + CHECK_HP(hDpath, "Out of range polygon handle (10)"); + pSpath = Polys[hSpath]; + pDpath = Polys[hDpath]; + + pps = LockMem(pHandle); // All polygons + ps = (const POLY *)pps + pSpath->pIndex; // Start polygon + pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon + + ns = (int)FROM_LE_32(ps->nodecount) - 1; + nd = (int)FROM_LE_32(pd->nodecount) - 1; + + snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx)); + snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty)); + dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx)); + dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty)); + + // start[0] to dest[0] + NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0])); + NearNode = 0; + + // start[0] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd])); + if (dist < NearDist) + NearDist = dist; + + // start[top] to dest[0] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0])); + if (dist < NearDist) { + NearDist = dist; + NearNode = ns; + } + + // start[top] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[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 NumNodes; // Number of nodes in this follow nodes path + int NearestYet = 0; // Number of nearest node + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hNpath, "Out of range polygon handle (11)"); + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + NumNodes = (int)FROM_LE_32(ptp->nodecount); + + for (int i = 0; i < NumNodes; i++) { + ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[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) { + const POLY *ptp; // Pointer to compiled polygon data + 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)"); + + ptp = (const POLY *)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); +} + +/** + * Give the co-ordinates of a node in a node path. + */ +void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) { + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + 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! + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + // Might have just walked to the node from above. + if (node == (int)FROM_LE_32(ptp->nodecount)) + node -= 1; + + *px = (int)FROM_LE_32(nlistx[node]); + *py = (int)FROM_LE_32(nlisty[node]); +} + +/** + * Get tag text handle and tag co-ordinates of a polygon. + */ + +void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (16)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *tagx = (int)FROM_LE_32(pp->tagx); + *tagy = (int)FROM_LE_32(pp->tagy); + *hTagText = FROM_LE_32(pp->hTagtext); +} + +/** + * Get polygon's film reel handle. + */ + +SCNHANDLE getPolyFilm(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (17)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hFilm); +} + +/** + * Get polygon's associated node. + */ + +void getPolyNode(HPOLYGON hp, int *px, int *py) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (18)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *px = (int)FROM_LE_32(pp->nodex); + *py = (int)FROM_LE_32(pp->nodey); +} + +/** + * Get handle to polygon's glitter code. + */ + +SCNHANDLE getPolyScript(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (19)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hScript); +} + +REEL getPolyReelType(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + // 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)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (REEL)FROM_LE_32(pp->reel); +} + +int32 getPolyZfactor(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (21)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->zFactor); +} + +int numNodes(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (22)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->nodecount); +} + +// ************************************************************************* +// +// 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(void) { + 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(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) { + memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool)); +} + +void RestoreDeadPolys(bool *sdp) { + memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool)); +} + +/** + * Convert a BLOCKING to an EX_BLOCK poly. + */ +void DisableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) { + Polys[i]->polytype = EX_BLOCK; + deadPolys[i] = true; + } + } +} + +/** + * Convert an EX_BLOCK to a BLOCKING poly. + */ +void EnableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) { + Polys[i]->polytype = BLOCKING; + deadPolys[i] = false; + } + } +} + +/** + * Convert an EX_TAG to a TAG poly. + */ +void EnableTag(int tagno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = TAG; + } + } + + TAGSTATE *pts; + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = true; + break; + } + } +} + +/** + * 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; + } + } +} + +/** + * Convert a TAG to an EX_TAG poly. + */ +void DisableTag(int tagno) { + TAGSTATE *pts; + + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = EX_TAG; + Polys[i]->tagState = TAG_OFF; + Polys[i]->pointState = NOT_POINTING; + } + } + + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = false; + break; + } + } +} + +/** + * 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 = 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; + } + } +} + +HPOLYGON FirstPathPoly(void) { + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]->polytype == PATH) + return i; + } + error("FirstPathPoly() - no PATH polygons!"); + return NOPOLY; +} + +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; +} + +static void SetPathAdjacencies() { + POLYGON *p1, *p2; // Polygon pointers + + // For each polygon.. + for (int 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 (int 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; + + 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; + const POLY *cp; // Compiled polygon structure + int i, j; // Loop counters + int n; // Last node in current path + int32 *nlistx, *nlisty; + + 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 + cp = (const POLY *)pps + rp->pIndex; // This polygon + nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty)); + + n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node + assert(n >= 1); // Node paths must have at least 2 nodes + + hp = PolyIndex(rp); + for (j = 0; j <= n; j++) { + if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) { + sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]); + error(tBufferAddr()); + } + } + + // 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((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + } + } + } +} +#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 == BLOCKING) + 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) { + TAGSTATE *pts; + int i, j; + + 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(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]; + } +} + +/** + * 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, PolyIndex(p))) { + int i, top = 0, bot = 0; + + for (i = p->ptop; i <= p->pbottom; i++) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + top = i; + break; + } + } + for (i = p->pbottom; i >= p->ptop; i--) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + bot = i; + break; + } + } + p->pcentrex = (top+bot)/2; + } +#ifdef DEBUG + // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { + sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?", + p->cx[0], p->cy[0]); + error(tBufferAddr()); + } +#endif +} + +/** + * Allocate a POLYGON structure. + */ +static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) { + for (int i = 0; i < MaxPolys; i++) { + if (!Polys[i]) { + POLYGON *p = Polys[i] = &Polygons[i]; + memset(p, 0, sizeof(POLYGON)); + + p->polytype = type; // Polygon type + p->pIndex = pno; + p->tagState = TAG_OFF; + p->pointState = NOT_POINTING; + p->polyID = FROM_LE_32(pp->id); // Identifier + + for (int j = 0; j < 4; j++) { // Polygon definition + p->cx[j] = (short)FROM_LE_32(pp->x[j]); + p->cy[j] = (short)FROM_LE_32(pp->y[j]); + } + + return p; + } + } + + error("Exceeded MaxPolys"); + return NULL; +} + +/** + * Initialise an EXIT polygon. + */ +static void InitExit(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(EXIT, pp, pno)); +} + +/** + * Initialise a PATH or NPATH polygon. + */ +static void InitPath(const POLY *pp, bool NodePath, int pno) { + POLYGON *p; + + p = GetPolyEntry(PATH, pp, pno); // Obtain a slot + + if (NodePath) { + p->subtype = NODE; + } else { + p->subtype = NORMAL; + } + + // Clear out ajacent path pointers + memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *)); + + FiddlyBit(p); + PseudoCentre(p); +} + + +/** + * Initialise a BLOCKING polygon. + */ +static void InitBlock(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(BLOCKING, pp, pno)); +} + +/** + * 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(PMACTOR ca, PMACTOR ta) { + int caX, caY; // Calling actor co-ords + int taX, taY; // Test actor co-ords + int left, right; + + GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords + GetMActorPosition(ta, &taX, &taY); // Test actor co-ords + + left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX); + right = GetMActorRight(ta) + (caX - GetMActorLeft(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 *pp, int pno) { + FiddlyBit(GetPolyEntry(EFFECT, pp, pno)); +} + + +/** + * Initialise a REFER polygon. + */ +static void InitRefer(const POLY *pp, int pno) { + POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot + + p->subtype = FROM_LE_32(pp->reftype); // Refer type + + FiddlyBit(p); +} + + +/** + * Initialise a TAG polygon. + */ +static void InitTag(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(TAG, pp, pno)); +} + + +/** + * Called at the start of a scene to initialise the polys in that scene. + */ +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) { + const POLY *pp; // Pointer to compiled data polygon structure + + 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"); + } + } + + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]) { + Polys[i]->pointState = NOT_POINTING; + Polys[i] = NULL; + } + } + + memset(RoutePaths, 0, sizeof(RoutePaths)); + + if (!bRestart) + memset(deadPolys, 0, sizeof(deadPolys)); + + pp = (const POLY *)LockMem(ph); + for (int i = 0; i < numPoly; i++, pp++) { + switch (FROM_LE_32(pp->type)) { + case POLY_PATH: + InitPath(pp, false, i); + break; + + case POLY_NPATH: + InitPath(pp, true, i); + break; + + case POLY_BLOCK: + InitBlock(pp, i); + break; + + case POLY_REFER: + InitRefer(pp, i); + break; + + case POLY_EFFECT: + InitEffect(pp, i); + break; + + case POLY_EXIT: + InitExit(pp, i); + break; + + case POLY_TAG: + InitTag(pp, i); + break; + + default: + error("Unknown polygon type"); + } + } + 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 +} + +/** + * 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 = 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; +} + +SCNHANDLE PolyTagHandle(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (33)"); + + return Polys[hp]->oTagHandle; +} + +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]->oTagHandle = th; +} + +void MaxPolygons(int numPolys) { + assert(numPolys <= MAX_POLY); + + MaxPolys = numPolys; +} + +} // end of namespace Tinsel |
