/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 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. Warzone 2100 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 Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * RayCast.c * * raycasting routine that gives intersection points with map tiles * */ #include #include #include "lib/framework/frame.h" #include "objects.h" #include "map.h" #include "raycast.h" #include "display3d.h" //#ifdef ALEXM #include "effects.h" //#endif // accuracy for the raycast lookup tables #define RAY_ACC 12 #define RAY_ACCMUL (1<= 3*NUM_RAYS/4)) { rayDX[i] = -rayDX[i]; } if(val == 0) { val = (FRACT)1; // Horrible hack to avoid divide by zero. } rayDY[i] = (SDWORD)(TILE_UNITS * RAY_ACCMUL / val); if (i >= NUM_RAYS/2) { rayDY[i] = -rayDY[i]; } // These are used to calculate the initial intersection rayFPTan[i] = MAKEINT(FRACTmul(val, MAKEFRACT(RAY_ACCMUL))); rayFPInvTan[i] = MAKEINT(FRACTdiv(MAKEFRACT(RAY_ACCMUL), val)); // Set up the trig tables for calculating the offset distances val = (float)sin(angle); if(val == 0) { val = (FRACT)1; } rayFPInvSin[i] = MAKEINT(FRACTdiv(MAKEFRACT(RAY_ACCMUL), val)); if (i >= NUM_RAYS/2) { rayVDist[i] = MAKEINT(FRACTdiv(MAKEFRACT(-TILE_UNITS), val)); } else { rayVDist[i] = MAKEINT(FRACTdiv(MAKEFRACT(TILE_UNITS), val)); } val = (float)cos(angle); if(val == 0) { val = (FRACT)1; } rayFPInvCos[i] = MAKEINT(FRACTdiv(MAKEFRACT(RAY_ACCMUL), val)); if (i < NUM_RAYS/4 || i > 3*NUM_RAYS/4) { rayHDist[i] = MAKEINT(FRACTdiv(MAKEFRACT(TILE_UNITS), val)); } else { rayHDist[i] = MAKEINT(FRACTdiv(MAKEFRACT(-TILE_UNITS), val)); } angle += RAY_ANGLE; } return TRUE; } //void rayC(UDWORD x, UDWORD y, UDWORD ray, UDWORD length, RAY_CALLBACK callback); // ////#ifndef PSX // //void rayCast(UDWORD x, UDWORD y, UDWORD ray, UDWORD length, RAY_CALLBACK callback) //{ // rayC(x, y, ray, length, callback); //} //#else // //void rayCast(UDWORD x, UDWORD y, UDWORD ray, UDWORD length, RAY_CALLBACK callback) //{ // static UDWORD Tx; // static UDWORD Ty; // static UDWORD Tray; // static UDWORD Tlength; // static RAY_CALLBACK Tcallback; // // Tx = x; // Ty = y; // Tray = ray; // Tlength = length; // Tcallback = callback; // // Stack in the DCache. // SetSpDCache(); // rayC(Tx, Ty, Tray, Tlength, Tcallback); // SetSpNormal(); //} // //#endif /* cast a ray from x,y (world coords) at angle ray (0-360) * The ray angle starts at zero along the positive y axis and * increases towards -ve X. * * Sorry about the wacky angle set up but that was what I thought * warzone used, but turned out not to be after I wrote it. */ void rayCast(UDWORD x, UDWORD y, UDWORD ray, UDWORD length, RAY_CALLBACK callback) { SDWORD hdInc=0, vdInc=0; // increases in x and y distance per intersection SDWORD hDist, vDist; // distance to current horizontal and vertical intersectionse RAY_POINT sVert = { 0, 0 }, sHoriz = { 0, 0 }; SDWORD vdx=0, hdy=0; // vertical x increment, horiz y inc #if RAY_CLIP == 0 SDWORD newLen, clipLen; // ray length after clipping #endif // Clipping is done with the position offset by TILE_UNITS/4 to account // for the rounding errors when the intersection length is calculated. // Bit of a hack but I'm pretty sure it doesn't let through anything // that should be clippped. #if RAY_CLIP == 0 // Initial clip length is just the length of the ray clipLen = (SDWORD)length; #endif // initialise the horizontal intersection calculations // and clip to the top and bottom of the map // (no horizontal intersection for a horizontal ray) if (ray != NUM_RAYS/4 && ray != 3*NUM_RAYS/4) { if (ray < NUM_RAYS/4 || ray > 3*NUM_RAYS/4) { // intersection sHoriz.y = (y & ~TILE_MASK) + TILE_UNITS; hdy = TILE_UNITS; #if RAY_CLIP == 0 // clipping newLen = (((mapHeight << TILE_SHIFT) - ((SDWORD)y + TILE_UNITS/4)) * rayFPInvCos[ray]) >> RAY_ACC; if (newLen < clipLen) { clipLen = newLen; } #endif } else { // intersection sHoriz.y = (y & ~TILE_MASK) - 1; hdy = -TILE_UNITS; #if RAY_CLIP == 0 // clipping newLen = ((TILE_UNITS/4 - (SDWORD)y) * rayFPInvCos[ray]) >> RAY_ACC; if (newLen < clipLen) { clipLen = newLen; } #endif } // Horizontal x is kept in fixed point form until passed to the callback // to avoid rounding errors // Horizontal y is in integer form all the time sHoriz.x = (x << RAY_ACC) + (((SDWORD)y-sHoriz.y) * rayFPTan[ray]); // Set up the distance calculations hDist = ((sHoriz.y - (SDWORD)y) * rayFPInvCos[ray]) >> RAY_ACC; hdInc = rayHDist[ray]; } else { // ensure no horizontal intersections are calculated hDist = length; } // initialise the vertical intersection calculations // and clip to the left and right of the map // (no vertical intersection for a vertical ray) if (ray != 0 && ray != NUM_RAYS/2) { if (ray >= NUM_RAYS/2) { // intersection sVert.x = (x & ~TILE_MASK) + TILE_UNITS; vdx = TILE_UNITS; #if RAY_CLIP == 0 // clipping newLen = ((((SDWORD)x + TILE_UNITS/4) - (mapWidth << TILE_SHIFT)) * rayFPInvSin[ray]) >> RAY_ACC; if (newLen < clipLen) { clipLen = newLen; } #endif } else { // intersection sVert.x = (x & ~TILE_MASK) - 1; vdx = -TILE_UNITS; #if RAY_CLIP == 0 // clipping newLen = (((SDWORD)x - TILE_UNITS/4) * rayFPInvSin[ray]) >> RAY_ACC; if (newLen < clipLen) { clipLen = newLen; } #endif } // Vertical y is kept in fixed point form until passed to the callback // to avoid rounding errors // Vertical x is in integer form all the time sVert.y = (y << RAY_ACC) + ((SDWORD)x-sVert.x) * rayFPInvTan[ray]; // Set up the distance calculations vDist = (((SDWORD)x - sVert.x) * rayFPInvSin[ray]) >> RAY_ACC; vdInc = rayVDist[ray]; } else { // ensure no vertical intersections are calculated vDist = length; } ASSERT( hDist != 0 && vDist != 0, "rayCast: zero distance" ); ASSERT( (hDist == (SDWORD)length || hdInc > 0) && (vDist == (SDWORD)length || vdInc > 0), "rayCast: negative (or 0) distance increment" ); #if RAY_CLIP == 0 while(hDist < clipLen || vDist < clipLen) { // choose the next closest intersection if (hDist < vDist) { // pass through the current intersection, converting x from fixed point if (!callback( sHoriz.x >> RAY_ACC,sHoriz.y, hDist)) { // callback doesn't want any more points so return return; } // update for the next intersection sHoriz.x += rayDX[ray]; sHoriz.y += hdy; hDist += hdInc; } else { // pass through the current intersection, converting y from fixed point if (!callback( sVert.x,sVert.y >> RAY_ACC, vDist)) { // callback doesn't want any more points so return return; } // update for the next intersection sVert.x += vdx; sVert.y += rayDY[ray]; vDist += vdInc; } ASSERT( hDist != 0 && vDist != 0, "rayCast: zero distance" ); } #elif RAY_CLIP == 1 while(hDist < (SDWORD)length || vDist < (SDWORD)length) { // choose the next closest intersection if (hDist < vDist) { // clip to the edge of the map if (sHoriz.x < 0 || (sHoriz.x >> RAY_ACC) >= (SDWORD)(mapWidth << TILE_SHIFT) || sHoriz.y < 0 || sHoriz.y >= (SDWORD)(mapHeight << TILE_SHIFT)) { return; } // pass through the current intersection, converting x from fixed point if (!callback( sHoriz.x >> RAY_ACC,sHoriz.y, hDist)) { // callback doesn't want any more points so return return; } // update for the next intersection sHoriz.x += rayDX[ray]; sHoriz.y += hdy; hDist += hdInc; } else { // clip to the edge of the map if (sVert.x < 0 || sVert.x >= (SDWORD)(mapWidth << TILE_SHIFT) || sVert.y < 0 || (sVert.y >> RAY_ACC) >= (SDWORD)(mapHeight << TILE_SHIFT)) { return; } // pass through the current intersection, converting y from fixed point if (!callback( sVert.x,sVert.y >> RAY_ACC, vDist)) { // callback doesn't want any more points so return return; } // update for the next intersection sVert.x += vdx; sVert.y += rayDY[ray]; vDist += vdInc; } ASSERT( hDist != 0 && vDist != 0, "rayCast: zero distance" ); } #endif } // Calculate the angle to cast a ray between two points UDWORD rayPointsToAngle(SDWORD x1,SDWORD y1, SDWORD x2,SDWORD y2) { SDWORD xdiff, ydiff; SDWORD angle; xdiff = x2 - x1; ydiff = y1 - y2; angle = (SDWORD)((NUM_RAYS/2) * atan2(xdiff, ydiff) / PI); angle += NUM_RAYS/2; angle = angle % NUM_RAYS; ASSERT( angle >= 0 && angle < NUM_RAYS, "rayPointsToAngle: angle out of range" ); return (UDWORD)angle; } /* Distance of a point from a line. * NOTE: This is not 100% accurate - it approximates to get the square root * * This is based on Graphics Gems II setion 1.3 */ SDWORD rayPointDist(SDWORD x1,SDWORD y1, SDWORD x2,SDWORD y2, SDWORD px,SDWORD py) { SDWORD a, lxd,lyd, dist; lxd = x2 - x1; lyd = y2 - y1; a = (py - y1)*lxd - (px - x1)*lyd; if (a < 0) { a = -a; } if (lxd < 0) { lxd = -lxd; } if (lyd < 0) { lyd = -lyd; } if (lxd < lyd) { dist = a / (lxd + lyd - lxd/2); } else { dist = a / (lxd + lyd - lyd/2); } return dist; } //----------------------------------------------------------------------------------- /* Gets the maximum terrain height along a certain direction to the edge of the grid from wherever you specify, as well as the distance away */ // typedef BOOL (*RAY_CALLBACK)(SDWORD x, SDWORD y, SDWORD dist); //void rayCast(UDWORD x, UDWORD y, UDWORD ray, UDWORD length, RAY_CALLBACK callback) //#define TEST_RAY /* Nasty global vars - put into a structure? */ //----------------------------------------------------------------------------------- SDWORD gHeight; FRACT gPitch; UDWORD gStartTileX; UDWORD gStartTileY; SDWORD gHighestHeight,gHOrigHeight; SDWORD gHMinDist; FRACT gHPitch; //----------------------------------------------------------------------------------- UDWORD getTileTallObj(UDWORD x, UDWORD y) { UDWORD i, j; UDWORD TallObj = 0; x = x >> TILE_SHIFT; y = y >> TILE_SHIFT; for (j=y; j < y+2; j++) { for (i=x; i < x+2; i++) { TallObj |= TILE_HAS_TALLSTRUCTURE(mapTile(i,j)); } } return TallObj; } //----------------------------------------------------------------------------------- static BOOL getTileHighestCallback(SDWORD x, SDWORD y, SDWORD dist) { SDWORD heightDif; UDWORD height; //iVector pos; if(clipXY(x,y)) { height = map_Height(x,y); if( (height > gHighestHeight) AND (dist >= gHMinDist) ) { heightDif = height - gHOrigHeight; gHPitch = RAD_TO_DEG(atan2(MAKEFRACT(heightDif), MAKEFRACT(6*TILE_UNITS)));//MAKEFRACT(dist-(TILE_UNITS*3)))); gHighestHeight = height; } // pos.x = x; // pos.y = height; // pos.z = y; // addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SMALL,FALSE,NULL,0); } else { return(FALSE); } return(TRUE); } //----------------------------------------------------------------------------------- /* Will return false when we've hit the edge of the grid */ static BOOL getTileHeightCallback(SDWORD x, SDWORD y, SDWORD dist) { SDWORD height,heightDif; FRACT newPitch; BOOL HasTallStructure = FALSE; #ifdef TEST_RAY iVector pos; #endif /* Are we still on the grid? */ if(clipXY(x,y)) { HasTallStructure = TILE_HAS_TALLSTRUCTURE(mapTile(x>>TILE_SHIFT,y>>TILE_SHIFT)); if( (dist>TILE_UNITS) || HasTallStructure) { // Only do it the current tile is > TILE_UNITS away from the starting tile. Or.. // there is a tall structure on the current tile and the current tile is not the starting tile. // if( (dist>TILE_UNITS) || // ( (HasTallStructure = TILE_HAS_TALLSTRUCTURE(mapTile(x>>TILE_SHIFT,y>>TILE_SHIFT))) && // ((x >> TILE_SHIFT != gStartTileX) || (y >> TILE_SHIFT != gStartTileY)) ) ) { /* Get height at this intersection point */ height = map_Height(x,y); if(HasTallStructure) { height += 300; //TALLOBJECT_ADJUST; } if(height<=gHeight) { heightDif = 0; } else { heightDif = height-gHeight; } /* Work out the angle to this point from start point */ newPitch = RAD_TO_DEG(atan2(MAKEFRACT(heightDif),MAKEFRACT(dist))); /* Is this the steepest we've found? */ if(newPitch>gPitch) { /* Yes, then keep a record of it */ gPitch = newPitch; } //--- #ifdef TEST_RAY pos.x = x; pos.y = height; pos.z = y; addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SMALL,FALSE,NULL,0); #endif // if(height > gMaxRayHeight) // { // gMaxRayHeight = height; // gRayDist = dist; // return(TRUE); // } } } else { /* We've hit edge of grid - so exit!! */ return(FALSE); } /* Not at edge yet - so exit */ return(TRUE); } void getBestPitchToEdgeOfGrid(UDWORD x, UDWORD y, UDWORD direction, SDWORD *pitch) { /* Set global var to clear */ gPitch = MAKEFRACT(0); gHeight = map_Height(x,y); gStartTileX = x >> TILE_SHIFT; gStartTileY = y >> TILE_SHIFT; //#ifdef TEST_RAY //DBPRINTF(("%d\n",direction); //#endif rayCast(x,y, direction%360,5430,getTileHeightCallback); *pitch = MAKEINT(gPitch); } //----------------------------------------------------------------------------------- void getPitchToHighestPoint( UDWORD x, UDWORD y, UDWORD direction, UDWORD thresholdDistance, SDWORD *pitch) { gHPitch = MAKEFRACT(0); gHOrigHeight = map_Height(x,y); gHighestHeight = map_Height(x,y); gHMinDist = thresholdDistance; rayCast(x,y,direction%360,3000,getTileHighestCallback); *pitch = MAKEINT(gHPitch); } //-----------------------------------------------------------------------------------