/* 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. * * Additional copyright for this file: * Copyright (C) 1999-2000 Revolution Software Ltd. * This code is based on source code created by Revolution Software, * used with permission. * * 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 3 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, see . * */ #include "engines/icb/icb.h" #include "engines/icb/p4.h" #include "engines/icb/common/px_common.h" #include "engines/icb/common/px_floor_map.h" #include "engines/icb/common/px_linkeddatafile.h" #include "engines/icb/common/px_route_barriers.h" #include "engines/icb/floors.h" #include "engines/icb/debug.h" #include "engines/icb/prim_route_builder.h" #include "engines/icb/remora.h" #include "engines/icb/global_objects.h" #include "common/system.h" #include "math/utils.h" namespace ICB { uint32 pen; uint32 timer; void _prim_route_builder::Reset_barrier_list() { total_points = 0; } void _prim_route_builder::Add_barrier(RouteBarrier *new_barrier) { barrier_list[total_points].x = new_barrier->m_x1; barrier_list[total_points++].z = new_barrier->m_z1; barrier_list[total_points].x = new_barrier->m_x2; barrier_list[total_points++].z = new_barrier->m_z2; if (!ExtrapolateLine(&barrier_list[total_points - 2], &barrier_list[total_points - 1], &barrier_list[total_points - 2], &barrier_list[total_points - 1], extrap_size)) Fatal_error("extrapolate line failed on line %3.2f %3.2f %3.2f %3.2f", new_barrier->m_x1, new_barrier->m_z1, new_barrier->m_x2, new_barrier->m_z2); assert(total_points < MAX_barriers); } void _prim_route_builder::Give_barrier_list(_route_description *route) { // this may seem daft, but now we're giving the barriers back - for NETHACK diagnostics, not the logic // this won't be called in final .exe if (!total_points) { route->number_of_diag_bars = 0; return; } // Turn off the new's for PSX under certain conditions // i.e. a CD 2MB build // set passed number of points route->number_of_diag_bars = total_points; if (route->diag_bars) Message_box("multiple calls to Give_barrier_list"); route->diag_bars = new _point[total_points]; // move memcpy((unsigned char *)route->diag_bars, (unsigned char *)barrier_list, ((total_points) * sizeof(_point))); } void _prim_route_builder::Give_route(_route_description *route) { // copy the route to pass_to Zdebug("give route %d points", final_points + 1); for (uint32 j = 0; j < final_points + 1; j++) Zdebug(" %3.1f %3.1f", final_route[j].x, final_route[j].z); if (!final_points) Fatal_error("_prim_route_builder::Give_route no route to give!"); // do a check for length exceeding MAX_final_route as this isn't really done anyway - it will have already scribbled of course but hey we're hanging on in there if (final_points + 1 >= MAX_final_route) Fatal_error("route too big"); // pass number of points route->total_points = final_points + 1; // routes start at point 1 as route creator gives our start position as point 0 route->current_position = 1; memcpy((unsigned char *)route->prim_route, (unsigned char *)final_route, ((final_points + 1) * sizeof(_point))); final_points = 0; // effectively clear the route } _route_stat _prim_route_builder::Calc_route(PXreal startx, PXreal startz, PXreal endx, PXreal endz) { // total_points is the number of dots derives from lines - i.e lines*2 // we then add 2 to this to get our total number of points // true_points == lines*2 +2 where lines*2 means a point at either end of each barrier +2 for start and end points // uses 'points' as waypoints plus points[0] is start and points[total_points+1] is end point // used 'lines' as list of barriers uint32 j, l, thisp, lastp, go; uint32 to; // Used for computing how to index into the bit based arrays uint32 jplace, jremainder; Zdebug("-*-calc route-*-"); if (total_points + 2 >= MAX_barriers) { Fatal_error("too many barriers %d MAX %d", total_points + 2, MAX_barriers); } // insert start and end positions barrier_list[total_points].x = startx; barrier_list[total_points++].z = startz; barrier_list[total_points].x = endx; barrier_list[total_points++].z = endz; // reset the hit tables for (go = 0; go < total_points; go++) { // goes for total number of points excluding target #if defined(PRIM_BYTE_ARRAYS) && PRIM_BYTE_ARRAYS for (l = 0; l < total_points; l++) { hits[go][l] = 0; gohits[go][l] = 0; } #endif for (l = 0; l < (total_points + 7) / 8; l++) { hitsBits[go][l] = 0; gohitsBits[go][l] = 0; } } // create the exclusion table // exclude = new uint32 [total_points]; memset((unsigned char *)exclude, 0, (total_points) * sizeof(uint8)); // zero the table // create the temporary exclusion table // temp_exclude = new uint32 [total_points]; memset((unsigned char *)temp_exclude, 0, (total_points) * sizeof(uint8)); // zero the table // firsly prime the system by seeing what gets to the target to = total_points - 1; // last point defined is target // test all points against target 'to' for (j = 0; j < total_points - 1; j++) { //-1 means don't test the last point against itself // ok, point 'j' can be tested to see if can reach point 'to' // test against all our lines for (l = 0; l < total_points - 2; l += 2) { // don't test point J against the line it is derived from if (l != (j & 0xfffffffe)) { if (Get_intersect(/*firing line from*/ barrier_list[j].x, barrier_list[j].z, /*firing line to*/ barrier_list[to].x, barrier_list[to].z, /*barrier*/ barrier_list[l].x, barrier_list[l].z, barrier_list[l + 1].x, barrier_list[l + 1].z)) { break; // intersection found j does not get to to } } } // if it gets there then mark in if (l == total_points - 2) { if (j == (total_points - 2)) { // target got there? Zdebug("straight line HIT!"); final_points = 1; // end point is an off the end value (final_points+1)=dest // insert start point final_route[0].x = startx; final_route[0].z = startz; // insert end point final_route[1].x = endx; final_route[1].z = endz; return (__PRIM_ROUTE_OK); } #if defined(PRIM_BYTE_ARRAYS) && PRIM_BYTE_ARRAYS hits[to][j] = 1; gohits[0][j] = 1; // what hit in go 0 #endif exclude[j] = 1; // no int32er check this point // Set the bit arrays to 1 jplace = j >> 3; jremainder = j & 0x7; hitsBits[to][jplace] |= (1 << jremainder); gohitsBits[0][jplace] |= (1 << jremainder); } } // test every thing that hit in go[last-go][-] against everything not excluded so far for (go = 1; go < total_points - 1; go++) { // goes for total number of points excluding the target // read along finding the points that connected up last go for (lastp = 0; lastp < total_points - 2; lastp++) { // never the start point or the end point uint32 flag; jplace = lastp >> 3; jremainder = lastp & 0x7; flag = gohitsBits[go - 1][jplace] & (1 << jremainder); #if defined(PRIM_BYTE_ARRAYS) && PRIM_BYTE_ARRAYS if ((flag != 0) != (gohits[go - 1][lastp] != 0)) { Fatal_error("bits i:%d j:%d p:%d r:%d flag:%d gohits:%d", go - 1, lastp, jplace, jremainder, flag, gohits[go - 1][lastp]); } #endif if (flag != 0) { // find points that connected last go // now check all non excluded points against lastp - lastp being the point that connected last go for (thisp = 0; thisp < total_points - 1; thisp++) // all points to that point last go if (!exclude[thisp]) { // not excluded points // test point thisp to point lastp // test against all our lines for (l = 0; l < total_points - 2; l += 2) { // don't test point J against the line it is derived from if ((l != (thisp & 0xfffffffe)) && (l != (lastp & 0xfffffffe))) { if (Get_intersect(/*firing line*/ barrier_list[thisp].x, barrier_list[thisp].z, barrier_list[lastp].x, barrier_list[lastp].z, /*barrier*/ barrier_list[l].x, barrier_list[l].z, barrier_list[l + 1].x, barrier_list[l + 1].z)) { break; // intersection found thisp does not get to lastp } } } // if it gets there then mark in if (l == total_points - 2) { #if defined(PRIM_BYTE_ARRAYS) && PRIM_BYTE_ARRAYS hits[lastp][thisp] = 1; // hits[point-hit-last-go] [point-that's-just-connected-this-go] gohits[go][thisp] = 1; // this point hit this go #endif // Set the bit arrays to 1 jplace = thisp >> 3; jremainder = thisp & 0x7; hitsBits[lastp][jplace] |= (1 << jremainder); gohitsBits[go][jplace] |= (1 << jremainder); if (thisp == total_points - 2) { // the start point has connected so abandon the search for hits to lastp break; // quit the FOR(thisp loop } temp_exclude[thisp] = 1; // never exclude point 0 // allow thisp to be tested against other points this cycle } } } } // update exclude list for (j = 0; j < total_points; j++) { if (temp_exclude[j]) { exclude[j] = 1; // update main exclude list temp_exclude[j] = 0; // reset for next cycle } } } // now work out what different routes we found - if any final_points = 0; // number of points in route final_len = REAL_MAX; // primer value - any route will be shorter than this timer = g_system->getMillis(); pen = 15; Find_connects(total_points - 2, REAL_ZERO, 0); Tdebug("trout.txt", "\n\n\n\n\n\n\n\n\n\n"); timer = g_system->getMillis() - timer; // paste in the destination final_route[final_points].x = endx; final_route[final_points].z = endz; if (!final_points) return (__PRIM_ROUTE_FAIL); return (__PRIM_ROUTE_OK); } // line length == PXsqrt ( (x-x2)*(x-x2) + (y-y2)*(y-y2) ) void _prim_route_builder::Find_connects(uint32 point, PXreal cur_len, uint32 level) { // what does 'point' get to? // hurray, reentrant code // when the target room is met we see if its the shortest route yet found - if so copy temp route to final in case its 'the one' uint32 j, k; PXreal branch_len; PXreal sub1; PXreal sub2; uint32 flag; // Used for computing how to index into the bit based arrays uint32 jplace, jremainder; temp_route[level] = (uint8)point; Tdebug("trout.txt", "l %d", level); for (j = 0; j < total_points; j++) { // test the bit array jplace = point >> 3; jremainder = point & 0x7; flag = hitsBits[j][jplace] & (1 << jremainder); #if defined(PRIM_BYTE_ARRAYS) && PRIM_BYTE_ARRAYS if ((flag != 0) != (hits[j][point] != 0)) { Fatal_error("bits i:%d j:%d p:%d r:%d flag:%d hits:%d", j, point, jplace, jremainder, flag, hits[j][point]); } #endif if (flag != 0) { // we say - what points does point get to? sub1 = (PXreal)barrier_list[j].x - barrier_list[point].x; sub2 = (PXreal)barrier_list[j].z - barrier_list[point].z; // pythagoras no less // Don't need m_fabs as sqrt is always +ve branch_len = (PXfloat)PXsqrt((sub1 * sub1) + (sub2 * sub2)); Tdebug("trout.txt", "branch %.1f", branch_len); if (j != total_points - 1) { // not end point then keep going Find_connects(j, cur_len + branch_len, level + 1); } else { pen += 20; Tdebug("trout.txt", "hit the target"); if ((branch_len + cur_len) < final_len) { // point we got to is final point, so Tdebug("trout.txt", "%d shorter than %d", (int32)(branch_len + cur_len), (int32)final_len); final_len = branch_len + cur_len; final_points = level + 1; for (k = 0; k < level + 1; k++) { final_route[k].x = barrier_list[temp_route[k]].x; final_route[k].z = barrier_list[temp_route[k]].z; } } else Tdebug("trout.txt", "%d is int32er then %d", (int32)(branch_len + cur_len), (int32)final_len); } } } } uint32 _prim_route_builder::ExtrapolateLine(_point *pSrc0, _point *pSrc1, _point *pDst0, _point *pDst1, int32 d) { // FUNCTION RETURNS FALSE IF IT CANNOT WORK OUT THE NEW POINTS! // pSrc0, pSrc1 -> Source points! // pDst0, pDst1 -> Destination points! _fpoint p0, p1; int32 swapped = 0; // Make sure points are going from left to right! if (pSrc0->x < pSrc1->x) { p0.x = (PXdouble)(pSrc0->x); p0.z = (PXdouble)(pSrc0->z); p1.x = (PXdouble)(pSrc1->x); p1.z = (PXdouble)(pSrc1->z); } else { p1.x = (PXdouble)(pSrc0->x); p1.z = (PXdouble)(pSrc0->z); p0.x = (PXdouble)(pSrc1->x); p0.z = (PXdouble)(pSrc1->z); swapped = 1; } // Jake - for readability and to make sure we don't rely on the optimiser _fpoint delta; delta.x = (p1.x - p0.x); delta.z = (p1.z - p0.z); PXdouble val = delta.x * delta.x + delta.z * delta.z; // This is only trying to test for a zero length line if (val <= 0.0) return 0; PXdouble l = sqrt(val); PXdouble dx = (d * delta.x) / l; PXdouble dz = (d * delta.z) / l; if (swapped) { pDst0->x = (PXreal)(p1.x + dx); pDst0->z = (PXreal)(p1.z + dz); pDst1->x = (PXreal)(p0.x - dx); pDst1->z = (PXreal)(p0.z - dz); } else { pDst0->x = (PXreal)(p0.x - dx); pDst0->z = (PXreal)(p0.z - dz); pDst1->x = (PXreal)(p1.x + dx); pDst1->z = (PXreal)(p1.z + dz); } return 1; } PXfloat _prim_route_builder::m_fabs(PXfloat val) { if (val < FLOAT_ZERO) return FLOAT_ZERO - val; else return val; } int32 _prim_route_builder::Get_intersect(PXreal x0, PXreal y0, PXreal x1, PXreal y1, PXreal x2, PXreal y2, PXreal x3, PXreal y3) { // S2.1(19Mar96tw) PXfloat fAX, fBX, fCX, fAY, fBY, fCY; PXfloat fX1Low, fX1High, fY1Low, fY1High; PXfloat fD, fE, fF; // PXfloat fNum, fOffset; // Work out some commonly used terms. fAX = x1 - x0; fBX = x2 - x3; // X bounding box test. if (fAX < FLOAT_ZERO) { fX1Low = x1; fX1High = x0; } else { fX1High = x1; fX1Low = x0; } if (fBX > FLOAT_ZERO) { if ((fX1High < x3) || (x2 < fX1Low)) return 0; } else { if ((fX1High < x2) || (x3 < fX1Low)) return 0; } // More common terms. fAY = y1 - y0; fBY = y2 - y3; // Y bounding box test. if (fAY < FLOAT_ZERO) { fY1Low = y1; fY1High = y0; } else { fY1High = y1; fY1Low = y0; } if (fBY > FLOAT_ZERO) { if ((fY1High < y3) || (y2 < fY1Low)) return 0; } else { if ((fY1High < y2) || (y3 < fY1Low)) return 0; } // Couldn't dismiss the lines on their bounding rectangles, so do a proper intersection. fCX = x0 - x2; fCY = y0 - y2; fD = (fBY * fCX) - (fBX * fCY); fF = (fAY * fBX) - (fAX * fBY); if (fF > FLOAT_ZERO) { if ((fD < FLOAT_ZERO) || (fD > fF)) return 0; } else { if ((fD > FLOAT_ZERO) || (fD < fF)) return 0; } fE = (fAX * fCY) - (fAY * fCX); if (fF > FLOAT_ZERO) { if ((fE < FLOAT_ZERO) || (fE > fF)) return 0; } else { if ((fE > FLOAT_ZERO) || (fE < fF)) return 0; } // At this point, we can say that the lines do intersect as int32 as they are not // colinear (colinear is indicated by fF == 0.0). if (fabs(fF - FLOAT_ZERO) < (FLT_MIN * 10.0f)) return 0; return 1; } bool8 _prim_route_builder::LineIntersectsRect(DXrect oRect, int32 nX1, int32 nY1, int32 nX2, int32 nY2) const { if (g_icb->getGameType() == GType_ICB) { return (g_oRemora->CohenSutherland(oRect, nX1, nY1, nX2, nY2, FALSE8)); } else { return (/*g_oMap*/g_oRemora->CohenSutherland(oRect, nX1, nY1, nX2, nY2, FALSE8)); } } } // End of namespace ICB