/* 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 LINEINFO *getLineinfo(int i) const { return ((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; //!= 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; uint8 *pps; // 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 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 pps = LockMem(pHandle); // All polygons Poly ptp(pps, pp->pIndex); // This polygon // Look for fit of perpendicular to lines between nodes for (int i = 0; i < ptp.getNodecount() - 1; i++) { 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 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; uint8 *pps; // Compiled polygon data CHECK_HP(hPath, "Out of range polygon handle (8)"); pp = Polys[hPath]; pps = LockMem(pHandle); // All polygons Poly ptp(pps, 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; uint8 *pps; // Compiled polygon data 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 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 uint8 *pps; // Compiled polygon data CHECK_HP(hNpath, "Out of range polygon handle (11)"); pps = LockMem(pHandle); // All polygons Poly ptp(pps, 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 pp(LockMem(pHandle), Polys[hPath]->pIndex); // Path is of a constant brightness? if (FROM_LE_32(pp.bright1) == FROM_LE_32(pp.bright2)) return FROM_LE_32(pp.bright1); assert(FROM_LE_32(pp.bright1) >= FROM_LE_32(pp.bright2)); zones = FROM_LE_32(pp.bright1) - FROM_LE_32(pp.bright2) + 1; zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones; brightness = FROM_LE_32(pp.bright1); top = Polys[hPath]->ptop; do { top += zlen; if (y < top) return brightness; } while (--brightness); return FROM_LE_32(pp.bright2); } /** * 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 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 Poly ptp(pps, 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 pp(LockMem(pHandle), Polys[hp]->pIndex); *tagx = (int)FROM_LE_32(pp.tagx) + (TinselV2 ? volatileStuff[hp].xoff : 0); *tagy = (int)FROM_LE_32(pp.tagy) + (TinselV2 ? volatileStuff[hp].yoff : 0); *hTagText = FROM_LE_32(pp.hTagtext); } /** * Get polygon's film reel handle. */ SCNHANDLE GetPolyFilm(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (17)"); Poly pp(LockMem(pHandle), Polys[hp]->pIndex); return FROM_LE_32(pp.hFilm); } /** * Get handle to polygon's glitter code. */ SCNHANDLE GetPolyScript(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (19)"); Poly pp(LockMem(pHandle), Polys[hp]->pIndex); return FROM_LE_32(pp.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 pp(LockMem(pHandle), Polys[hp]->pIndex); return (REEL)FROM_LE_32(pp.reel); } int32 GetPolyZfactor(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (21)"); assert(Polys[hp] != NULL); Poly pp(LockMem(pHandle), Polys[hp]->pIndex); return (int)FROM_LE_32(pp.zFactor); } int numNodes(HPOLYGON hp) { CHECK_HP(hp, "Out of range polygon handle (22)"); assert(Polys[hp] != NULL); Poly pp(LockMem(pHandle), Polys[hp]->pIndex); return pp.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(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(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(void) { 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; // Compiled polygon data 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 &pp, 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(pp.x[i]); p->cy[i] = (short)FROM_LE_32(pp.y[i]); } if (!bRestart) { hp = PolygonIndex(p); volatileStuff[hp].xoff = (short)FROM_LE_32(pp.xoff); volatileStuff[hp].yoff = (short)FROM_LE_32(pp.yoff); } p->polyID = FROM_LE_32(pp.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 &pp, int pno, bool bRestart) { CommonInits(EXIT, pno, pp, bRestart); } /** * Initialise a PATH or NPATH polygon. */ static void InitPath(const Poly &pp, bool NodePath, int pno, bool bRestart) { PPOLYGON p = CommonInits(PATH, pno, pp, bRestart); p->subtype = NodePath ? NODE : NORMAL; PseudoCentre(p); } /** * Initialise a BLOCKING polygon. */ static void InitBlock(const Poly &pp, int pno, bool bRestart) { CommonInits(BLOCK, pno, pp, 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 &pp, int pno, bool bRestart) { CommonInits(EFFECT, pno, pp, bRestart); } /** * Initialise a REFER polygon. */ static void InitRefer(const Poly &pp, int pno, bool bRestart) { PPOLYGON p = CommonInits(REFER, pno, pp, bRestart); p->subtype = FROM_LE_32(pp.reftype); // Refer type } /** * Initialise a TAG polygon. */ static void InitTag(const Poly &pp, int pno, bool bRestart) { CommonInits(TAG, pno, pp, bRestart); } /** * Called at the restart of a scene, nobbles polygons which are dead. */ static void KillDeadPolygons(void) { 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 pp(LockMem(ph)); for (int i = 0; i < numPoly; ++i, ++pp) { switch (pp.getType()) { case POLY_PATH: InitPath(pp, false, i, bRestart); break; case POLY_NPATH: InitPath(pp, true, i, bRestart); break; case POLY_BLOCK: InitBlock(pp, i, bRestart); break; case POLY_REFER: InitRefer(pp, i, bRestart); break; case POLY_EFFECT: InitEffect(pp, i, bRestart); break; case POLY_EXIT: InitExit(pp, i, bRestart); break; case POLY_TAG: InitTag(pp, 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 pp(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(pp.nodex); *pNodeY = FROM_LE_32(pp.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(void) { 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