sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

surface_collision.c (25358B)


      1 #include <PR/ultratypes.h>
      2 
      3 #include "sm64.h"
      4 #include "game/debug.h"
      5 #include "game/level_update.h"
      6 #include "game/mario.h"
      7 #include "game/object_list_processor.h"
      8 #include "surface_collision.h"
      9 #include "surface_load.h"
     10 
     11 /**************************************************
     12  *                      WALLS                     *
     13  **************************************************/
     14 
     15 /**
     16  * Iterate through the list of walls until all walls are checked and
     17  * have given their wall push.
     18  */
     19 static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
     20                                           struct WallCollisionData *data) {
     21     register struct Surface *surf;
     22     register f32 offset;
     23     register f32 radius = data->radius;
     24     register f32 x = data->x;
     25     register f32 y = data->y + data->offsetY;
     26     register f32 z = data->z;
     27     register f32 px, pz;
     28     register f32 w1, w2, w3;
     29     register f32 y1, y2, y3;
     30     s32 numCols = 0;
     31 
     32     // Max collision radius = 200
     33     if (radius > 200.0f) {
     34         radius = 200.0f;
     35     }
     36 
     37     // Stay in this loop until out of walls.
     38     while (surfaceNode != NULL) {
     39         surf = surfaceNode->surface;
     40         surfaceNode = surfaceNode->next;
     41 
     42         // Exclude a large number of walls immediately to optimize.
     43         if (y < surf->lowerY || y > surf->upperY) {
     44             continue;
     45         }
     46 
     47         offset = surf->normal.x * x + surf->normal.y * y + surf->normal.z * z + surf->originOffset;
     48 
     49         if (offset < -radius || offset > radius) {
     50             continue;
     51         }
     52 
     53         px = x;
     54         pz = z;
     55 
     56         //! (Quantum Tunneling) Due to issues with the vertices walls choose and
     57         //  the fact they are floating point, certain floating point positions
     58         //  along the seam of two walls may collide with neither wall or both walls.
     59         if (surf->flags & SURFACE_FLAG_X_PROJECTION) {
     60             w1 = -surf->vertex1[2];            w2 = -surf->vertex2[2];            w3 = -surf->vertex3[2];
     61             y1 = surf->vertex1[1];            y2 = surf->vertex2[1];            y3 = surf->vertex3[1];
     62 
     63             if (surf->normal.x > 0.0f) {
     64                 if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) > 0.0f) {
     65                     continue;
     66                 }
     67                 if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) > 0.0f) {
     68                     continue;
     69                 }
     70                 if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) > 0.0f) {
     71                     continue;
     72                 }
     73             } else {
     74                 if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) < 0.0f) {
     75                     continue;
     76                 }
     77                 if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) < 0.0f) {
     78                     continue;
     79                 }
     80                 if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) < 0.0f) {
     81                     continue;
     82                 }
     83             }
     84         } else {
     85             w1 = surf->vertex1[0];            w2 = surf->vertex2[0];            w3 = surf->vertex3[0];
     86             y1 = surf->vertex1[1];            y2 = surf->vertex2[1];            y3 = surf->vertex3[1];
     87 
     88             if (surf->normal.z > 0.0f) {
     89                 if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) > 0.0f) {
     90                     continue;
     91                 }
     92                 if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) > 0.0f) {
     93                     continue;
     94                 }
     95                 if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) > 0.0f) {
     96                     continue;
     97                 }
     98             } else {
     99                 if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) < 0.0f) {
    100                     continue;
    101                 }
    102                 if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) < 0.0f) {
    103                     continue;
    104                 }
    105                 if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) < 0.0f) {
    106                     continue;
    107                 }
    108             }
    109         }
    110 
    111         // Determine if checking for the camera or not.
    112         if (gCheckingSurfaceCollisionsForCamera) {
    113             if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
    114                 continue;
    115             }
    116         } else {
    117             // Ignore camera only surfaces.
    118             if (surf->type == SURFACE_CAMERA_BOUNDARY) {
    119                 continue;
    120             }
    121 
    122             // If an object can pass through a vanish cap wall, pass through.
    123             if (surf->type == SURFACE_VANISH_CAP_WALLS) {
    124                 // If an object can pass through a vanish cap wall, pass through.
    125                 if (gCurrentObject != NULL
    126                     && (gCurrentObject->activeFlags & ACTIVE_FLAG_MOVE_THROUGH_GRATE)) {
    127                     continue;
    128                 }
    129 
    130                 // If Mario has a vanish cap, pass through the vanish cap wall.
    131                 if (gCurrentObject != NULL && gCurrentObject == gMarioObject
    132                     && (gMarioState->flags & MARIO_VANISH_CAP)) {
    133                     continue;
    134                 }
    135             }
    136         }
    137 
    138         //! (Wall Overlaps) Because this doesn't update the x and z local variables,
    139         //  multiple walls can push mario more than is required.
    140         data->x += surf->normal.x * (radius - offset);
    141         data->z += surf->normal.z * (radius - offset);
    142 
    143         //! (Unreferenced Walls) Since this only returns the first four walls,
    144         //  this can lead to wall interaction being missed. Typically unreferenced walls
    145         //  come from only using one wall, however.
    146         if (data->numWalls < 4) {
    147             data->walls[data->numWalls++] = surf;
    148         }
    149 
    150         numCols++;
    151     }
    152 
    153     return numCols;
    154 }
    155 
    156 /**
    157  * Formats the position and wall search for find_wall_collisions.
    158  */
    159 s32 f32_find_wall_collision(f32 *xPtr, f32 *yPtr, f32 *zPtr, f32 offsetY, f32 radius) {
    160     struct WallCollisionData collision;
    161     s32 numCollisions = 0;
    162 
    163     collision.offsetY = offsetY;
    164     collision.radius = radius;
    165 
    166     collision.x = *xPtr;
    167     collision.y = *yPtr;
    168     collision.z = *zPtr;
    169 
    170     collision.numWalls = 0;
    171 
    172     numCollisions = find_wall_collisions(&collision);
    173 
    174     *xPtr = collision.x;
    175     *yPtr = collision.y;
    176     *zPtr = collision.z;
    177 
    178     return numCollisions;
    179 }
    180 
    181 /**
    182  * Find wall collisions and receive their push.
    183  */
    184 s32 find_wall_collisions(struct WallCollisionData *colData) {
    185     struct SurfaceNode *node;
    186     s16 cellX, cellZ;
    187     s32 numCollisions = 0;
    188     TerrainData x = colData->x;
    189     TerrainData z = colData->z;
    190 
    191     colData->numWalls = 0;
    192 
    193     if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
    194         return numCollisions;
    195     }
    196     if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
    197         return numCollisions;
    198     }
    199 
    200     // World (level) consists of a 16x16 grid. Find where the collision is on
    201     // the grid (round toward -inf)
    202     cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    203     cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    204 
    205     // Check for surfaces belonging to objects.
    206     node = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next;
    207     numCollisions += find_wall_collisions_from_list(node, colData);
    208 
    209     // Check for surfaces that are a part of level geometry.
    210     node = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next;
    211     numCollisions += find_wall_collisions_from_list(node, colData);
    212 
    213     // Increment the debug tracker.
    214     gNumCalls.wall++;
    215 
    216     return numCollisions;
    217 }
    218 
    219 /**************************************************
    220  *                     CEILINGS                   *
    221  **************************************************/
    222 
    223 /**
    224  * Iterate through the list of ceilings and find the first ceiling over a given point.
    225  */
    226 static struct Surface *find_ceil_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) {
    227     register struct Surface *surf;
    228     register s32 x1, z1, x2, z2, x3, z3;
    229     struct Surface *ceil = NULL;
    230 
    231     ceil = NULL;
    232 
    233     // Stay in this loop until out of ceilings.
    234     while (surfaceNode != NULL) {
    235         surf = surfaceNode->surface;
    236         surfaceNode = surfaceNode->next;
    237 
    238         x1 = surf->vertex1[0];
    239         z1 = surf->vertex1[2];
    240         z2 = surf->vertex2[2];
    241         x2 = surf->vertex2[0];
    242 
    243         // Checking if point is in bounds of the triangle laterally.
    244         if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) > 0) {
    245             continue;
    246         }
    247 
    248         // Slight optimization by checking these later.
    249         x3 = surf->vertex3[0];
    250         z3 = surf->vertex3[2];
    251         if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) > 0) {
    252             continue;
    253         }
    254         if ((z3 - z) * (x1 - x3) - (x3 - x) * (z1 - z3) > 0) {
    255             continue;
    256         }
    257 
    258         // Determine if checking for the camera or not.
    259         if (gCheckingSurfaceCollisionsForCamera != 0) {
    260             if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
    261                 continue;
    262             }
    263         }
    264         // Ignore camera only surfaces.
    265         else if (surf->type == SURFACE_CAMERA_BOUNDARY) {
    266             continue;
    267         }
    268 
    269         {
    270             f32 nx = surf->normal.x;
    271             f32 ny = surf->normal.y;
    272             f32 nz = surf->normal.z;
    273             f32 oo = surf->originOffset;
    274             f32 height;
    275 
    276             // If a wall, ignore it. Likely a remnant, should never occur.
    277             if (ny == 0.0f) {
    278                 continue;
    279             }
    280 
    281             // Find the ceil height at the specific point.
    282             height = -(x * nx + nz * z + oo) / ny;
    283 
    284             // Checks for ceiling interaction with a 78 unit buffer.
    285             //! (Exposed Ceilings) Because any point above a ceiling counts
    286             //  as interacting with a ceiling, ceilings far below can cause
    287             // "invisible walls" that are really just exposed ceilings.
    288             if (y - (height - -78.0f) > 0.0f) {
    289                 continue;
    290             }
    291 
    292             *pheight = height;
    293             ceil = surf;
    294             break;
    295         }
    296     }
    297 
    298     //! (Surface Cucking) Since only the first ceil is returned and not the lowest,
    299     //  lower ceilings can be "cucked" by higher ceilings.
    300     return ceil;
    301 }
    302 
    303 /**
    304  * Find the lowest ceiling above a given position and return the height.
    305  */
    306 f32 find_ceil(f32 posX, f32 posY, f32 posZ, struct Surface **pceil) {
    307     s16 cellZ, cellX;
    308 
    309     struct Surface *ceil, *dynamicCeil;
    310     struct SurfaceNode *surfaceList;
    311 
    312     f32 height = CELL_HEIGHT_LIMIT;
    313     f32 dynamicHeight = CELL_HEIGHT_LIMIT;
    314 
    315     //! (Parallel Universes) Because position is casted to an s16, reaching higher
    316     //  float locations can return ceilings despite them not existing there.
    317     //  (Dynamic ceilings will unload due to the range.)
    318     TerrainData x = (TerrainData) posX;
    319     TerrainData y = (TerrainData) posY;
    320     TerrainData z = (TerrainData) posZ;
    321 
    322     *pceil = NULL;
    323 
    324     if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
    325         return height;
    326     }
    327     if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
    328         return height;
    329     }
    330 
    331     // Each level is split into cells to limit load, find the appropriate cell.
    332     cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    333     cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    334 
    335     // Check for surfaces belonging to objects.
    336     surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next;
    337     dynamicCeil = find_ceil_from_list(surfaceList, x, y, z, &dynamicHeight);
    338 
    339     // Check for surfaces that are a part of level geometry.
    340     surfaceList = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next;
    341     ceil = find_ceil_from_list(surfaceList, x, y, z, &height);
    342 
    343     if (dynamicHeight < height) {
    344         ceil = dynamicCeil;
    345         height = dynamicHeight;
    346     }
    347 
    348     *pceil = ceil;
    349 
    350     // Increment the debug tracker.
    351     gNumCalls.ceil++;
    352 
    353     return height;
    354 }
    355 
    356 /**************************************************
    357  *                     FLOORS                     *
    358  **************************************************/
    359 
    360 /**
    361  * Find the height of the highest floor below an object.
    362  */
    363 f32 unused_obj_find_floor_height(struct Object *obj) {
    364     struct Surface *floor;
    365     f32 floorHeight = find_floor(obj->oPosX, obj->oPosY, obj->oPosZ, &floor);
    366     return floorHeight;
    367 }
    368 
    369 /**
    370  * Basically a local variable that passes through floor geo info.
    371  */
    372 struct FloorGeometry sFloorGeo;
    373 
    374 UNUSED static u8 unused8038BE50[0x40];
    375 
    376 /**
    377  * Return the floor height underneath (xPos, yPos, zPos) and populate `floorGeo`
    378  * with data about the floor's normal vector and origin offset. Also update
    379  * sFloorGeo.
    380  */
    381 f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometry **floorGeo) {
    382     struct Surface *floor;
    383     f32 floorHeight = find_floor(xPos, yPos, zPos, &floor);
    384 
    385     *floorGeo = NULL;
    386 
    387     if (floor != NULL) {
    388         sFloorGeo.normalX = floor->normal.x;
    389         sFloorGeo.normalY = floor->normal.y;
    390         sFloorGeo.normalZ = floor->normal.z;
    391         sFloorGeo.originOffset = floor->originOffset;
    392 
    393         *floorGeo = &sFloorGeo;
    394     }
    395     return floorHeight;
    396 }
    397 
    398 /**
    399  * Iterate through the list of floors and find the first floor under a given point.
    400  */
    401 static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) {
    402     register struct Surface *surf;
    403     register s32 x1, z1, x2, z2, x3, z3;
    404     f32 nx, ny, nz;
    405     f32 oo;
    406     f32 height;
    407     struct Surface *floor = NULL;
    408 
    409     // Iterate through the list of floors until there are no more floors.
    410     while (surfaceNode != NULL) {
    411         surf = surfaceNode->surface;
    412         surfaceNode = surfaceNode->next;
    413 
    414         x1 = surf->vertex1[0];
    415         z1 = surf->vertex1[2];
    416         x2 = surf->vertex2[0];
    417         z2 = surf->vertex2[2];
    418 
    419         // Check that the point is within the triangle bounds.
    420         if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) {
    421             continue;
    422         }
    423 
    424         // To slightly save on computation time, set this later.
    425         x3 = surf->vertex3[0];
    426         z3 = surf->vertex3[2];
    427 
    428         if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) {
    429             continue;
    430         }
    431         if ((z3 - z) * (x1 - x3) - (x3 - x) * (z1 - z3) < 0) {
    432             continue;
    433         }
    434 
    435         // Determine if we are checking for the camera or not.
    436         if (gCheckingSurfaceCollisionsForCamera != 0) {
    437             if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
    438                 continue;
    439             }
    440         }
    441         // If we are not checking for the camera, ignore camera only floors.
    442         else if (surf->type == SURFACE_CAMERA_BOUNDARY) {
    443             continue;
    444         }
    445 
    446         nx = surf->normal.x;
    447         ny = surf->normal.y;
    448         nz = surf->normal.z;
    449         oo = surf->originOffset;
    450 
    451         // If a wall, ignore it. Likely a remnant, should never occur.
    452         if (ny == 0.0f) {
    453             continue;
    454         }
    455 
    456         // Find the height of the floor at a given location.
    457         height = -(x * nx + nz * z + oo) / ny;
    458         // Checks for floor interaction with a 78 unit buffer.
    459         if (y - (height + -78.0f) < 0.0f) {
    460             continue;
    461         }
    462 
    463         *pheight = height;
    464         floor = surf;
    465         break;
    466     }
    467 
    468     //! (Surface Cucking) Since only the first floor is returned and not the highest,
    469     //  higher floors can be "cucked" by lower floors.
    470     return floor;
    471 }
    472 
    473 /**
    474  * Find the height of the highest floor below a point.
    475  */
    476 f32 find_floor_height(f32 x, f32 y, f32 z) {
    477     struct Surface *floor;
    478 
    479     f32 floorHeight = find_floor(x, y, z, &floor);
    480 
    481     return floorHeight;
    482 }
    483 
    484 /**
    485  * Find the highest dynamic floor under a given position. Perhaps originally static
    486  * and dynamic floors were checked separately.
    487  */
    488 f32 unused_find_dynamic_floor(f32 xPos, f32 yPos, f32 zPos, struct Surface **pfloor) {
    489     struct SurfaceNode *surfaceList;
    490     struct Surface *floor;
    491     f32 floorHeight = FLOOR_LOWER_LIMIT;
    492 
    493     // Would normally cause PUs, but dynamic floors unload at that range.
    494     TerrainData x = (TerrainData) xPos;
    495     TerrainData y = (TerrainData) yPos;
    496     TerrainData z = (TerrainData) zPos;
    497 
    498     // Each level is split into cells to limit load, find the appropriate cell.
    499     s16 cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    500     s16 cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    501 
    502     surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
    503     floor = find_floor_from_list(surfaceList, x, y, z, &floorHeight);
    504 
    505     *pfloor = floor;
    506 
    507     return floorHeight;
    508 }
    509 
    510 /**
    511  * Find the highest floor under a given position and return the height.
    512  */
    513 f32 find_floor(f32 xPos, f32 yPos, f32 zPos, struct Surface **pfloor) {
    514     s16 cellZ, cellX;
    515 
    516     struct Surface *floor, *dynamicFloor;
    517     struct SurfaceNode *surfaceList;
    518 
    519     f32 height = FLOOR_LOWER_LIMIT;
    520     f32 dynamicHeight = FLOOR_LOWER_LIMIT;
    521 
    522     //! (Parallel Universes) Because position is casted to an s16, reaching higher
    523     //  float locations can return floors despite them not existing there.
    524     //  (Dynamic floors will unload due to the range.)
    525     TerrainData x = (TerrainData) xPos;
    526     TerrainData y = (TerrainData) yPos;
    527     TerrainData z = (TerrainData) zPos;
    528 
    529     *pfloor = NULL;
    530 
    531     if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
    532         return height;
    533     }
    534     if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
    535         return height;
    536     }
    537 
    538     // Each level is split into cells to limit load, find the appropriate cell.
    539     cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    540     cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & NUM_CELLS_INDEX;
    541 
    542     // Check for surfaces belonging to objects.
    543     surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
    544     dynamicFloor = find_floor_from_list(surfaceList, x, y, z, &dynamicHeight);
    545 
    546     // Check for surfaces that are a part of level geometry.
    547     surfaceList = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
    548     floor = find_floor_from_list(surfaceList, x, y, z, &height);
    549 
    550     // To prevent the Merry-Go-Round room from loading when Mario passes above the hole that leads
    551     // there, SURFACE_INTANGIBLE is used. This prevent the wrong room from loading, but can also allow
    552     // Mario to pass through.
    553     if (!gFindFloorIncludeSurfaceIntangible) {
    554         //! (BBH Crash) Most NULL checking is done by checking the height of the floor returned
    555         //  instead of checking directly for a NULL floor. If this check returns a NULL floor
    556         //  (happens when there is no floor under the SURFACE_INTANGIBLE floor) but returns the height
    557         //  of the SURFACE_INTANGIBLE floor instead of the typical -11000 returned for a NULL floor.
    558         if (floor != NULL && floor->type == SURFACE_INTANGIBLE) {
    559             floor = find_floor_from_list(surfaceList, x, (s32)(height - 200.0f), z, &height);
    560         }
    561     } else {
    562         // To prevent accidentally leaving the floor tangible, stop checking for it.
    563         gFindFloorIncludeSurfaceIntangible = FALSE;
    564     }
    565 
    566     // If a floor was missed, increment the debug counter.
    567     if (floor == NULL) {
    568         gNumFindFloorMisses++;
    569     }
    570 
    571     if (dynamicHeight > height) {
    572         floor = dynamicFloor;
    573         height = dynamicHeight;
    574     }
    575 
    576     *pfloor = floor;
    577 
    578     // Increment the debug tracker.
    579     gNumCalls.floor++;
    580 
    581     return height;
    582 }
    583 
    584 /**************************************************
    585  *               ENVIRONMENTAL BOXES              *
    586  **************************************************/
    587 
    588 /**
    589  * Finds the height of water at a given location.
    590  */
    591 f32 find_water_level(f32 x, f32 z) {
    592     s32 i;
    593     s32 numRegions;
    594     TerrainData val;
    595     f32 loX, hiX, loZ, hiZ;
    596     f32 waterLevel = FLOOR_LOWER_LIMIT;
    597     TerrainData *p = gEnvironmentRegions;
    598 
    599     if (p != NULL) {
    600         numRegions = *p++;
    601 
    602         for (i = 0; i < numRegions; i++) {
    603             val = *p++;
    604             loX = *p++;
    605             loZ = *p++;
    606             hiX = *p++;
    607             hiZ = *p++;
    608 
    609             // If the location is within a water box and it is a water box.
    610             // Water is less than 50 val only, while above is gas and such.
    611             if (loX < x && x < hiX && loZ < z && z < hiZ && val < 50) {
    612                 // Set the water height. Since this breaks, only return the first height.
    613                 waterLevel = *p;
    614                 break;
    615             }
    616             p++;
    617         }
    618     }
    619 
    620     return waterLevel;
    621 }
    622 
    623 /**
    624  * Finds the height of the poison gas (used only in HMC) at a given location.
    625  */
    626 f32 find_poison_gas_level(f32 x, f32 z) {
    627     s32 i;
    628     s32 numRegions;
    629     UNUSED u8 filler[4];
    630     TerrainData val;
    631     f32 loX, hiX, loZ, hiZ;
    632     f32 gasLevel = FLOOR_LOWER_LIMIT;
    633     TerrainData *p = gEnvironmentRegions;
    634 
    635     if (p != NULL) {
    636         numRegions = *p++;
    637 
    638         for (i = 0; i < numRegions; i++) {
    639             val = *p;
    640 
    641             if (val >= 50) {
    642                 loX = p[1];
    643                 loZ = p[2];
    644                 hiX = p[3];
    645                 hiZ = p[4];
    646 
    647                 // If the location is within a gas's box and it is a gas box.
    648                 // Gas has a value of 50, 60, etc.
    649                 if (loX < x && x < hiX && loZ < z && z < hiZ && val % 10 == 0) {
    650                     // Set the gas height. Since this breaks, only return the first height.
    651                     gasLevel = p[5];
    652                     break;
    653                 }
    654             }
    655 
    656             p += 6;
    657         }
    658     }
    659 
    660     return gasLevel;
    661 }
    662 
    663 /**************************************************
    664  *                      DEBUG                     *
    665  **************************************************/
    666 
    667 /**
    668  * Finds the length of a surface list for debug purposes.
    669  */
    670 static s32 surface_list_length(struct SurfaceNode *list) {
    671     s32 count = 0;
    672 
    673     while (list != NULL) {
    674         list = list->next;
    675         count++;
    676     }
    677 
    678     return count;
    679 }
    680 
    681 /**
    682  * Print the area,number of walls, how many times they were called,
    683  * and some allocation information.
    684  */
    685 void debug_surface_list_info(f32 xPos, f32 zPos) {
    686     struct SurfaceNode *list;
    687     s32 numFloors = 0;
    688     s32 numWalls = 0;
    689     s32 numCeils = 0;
    690 
    691     s32 cellX = (xPos + LEVEL_BOUNDARY_MAX) / CELL_SIZE;
    692     s32 cellZ = (zPos + LEVEL_BOUNDARY_MAX) / CELL_SIZE;
    693 
    694     list = gStaticSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_FLOORS].next;
    695     numFloors += surface_list_length(list);
    696 
    697     list = gDynamicSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_FLOORS].next;
    698     numFloors += surface_list_length(list);
    699 
    700     list = gStaticSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_WALLS].next;
    701     numWalls += surface_list_length(list);
    702 
    703     list = gDynamicSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_WALLS].next;
    704     numWalls += surface_list_length(list);
    705 
    706     list = gStaticSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_CEILS].next;
    707     numCeils += surface_list_length(list);
    708 
    709     list = gDynamicSurfacePartition[cellZ & NUM_CELLS_INDEX][cellX & NUM_CELLS_INDEX][SPATIAL_PARTITION_CEILS].next;
    710     numCeils += surface_list_length(list);
    711 
    712     print_debug_top_down_mapinfo("area   %x", cellZ * NUM_CELLS + cellX);
    713 
    714     // Names represent ground, walls, and roofs as found in SMS.
    715     print_debug_top_down_mapinfo("dg %d", numFloors);
    716     print_debug_top_down_mapinfo("dw %d", numWalls);
    717     print_debug_top_down_mapinfo("dr %d", numCeils);
    718 
    719     set_text_array_x_y(80, -3);
    720 
    721     print_debug_top_down_mapinfo("%d", gNumCalls.floor);
    722     print_debug_top_down_mapinfo("%d", gNumCalls.wall);
    723     print_debug_top_down_mapinfo("%d", gNumCalls.ceil);
    724 
    725     set_text_array_x_y(-80, 0);
    726 
    727     // listal- List Allocated?, statbg- Static Background?, movebg- Moving Background?
    728     print_debug_top_down_mapinfo("listal %d", gSurfaceNodesAllocated);
    729     print_debug_top_down_mapinfo("statbg %d", gNumStaticSurfaces);
    730     print_debug_top_down_mapinfo("movebg %d", gSurfacesAllocated - gNumStaticSurfaces);
    731 
    732     gNumCalls.floor = 0;
    733     gNumCalls.ceil = 0;
    734     gNumCalls.wall = 0;
    735 }
    736 
    737 /**
    738  * An unused function that finds and interacts with any type of surface.
    739  * Perhaps an original implementation of surfaces before they were more specialized.
    740  */
    741 s32 unused_resolve_floor_or_ceil_collisions(s32 checkCeil, f32 *px, f32 *py, f32 *pz, f32 radius,
    742                                             struct Surface **psurface, f32 *surfaceHeight) {
    743     f32 nx, ny, nz, oo;
    744     f32 x = *px;
    745     f32 y = *py;
    746     f32 z = *pz;
    747     f32 offset, distance;
    748 
    749     *psurface = NULL;
    750 
    751     if (checkCeil) {
    752         *surfaceHeight = find_ceil(x, y, z, psurface);
    753     } else {
    754         *surfaceHeight = find_floor(x, y, z, psurface);
    755     }
    756 
    757     if (*psurface == NULL) {
    758         return -1;
    759     }
    760 
    761     nx = (*psurface)->normal.x;
    762     ny = (*psurface)->normal.y;
    763     nz = (*psurface)->normal.z;
    764     oo = (*psurface)->originOffset;
    765 
    766     offset = nx * x + ny * y + nz * z + oo;
    767     distance = offset >= 0 ? offset : -offset;
    768 
    769     // Interesting surface interaction that should be surf type independent.
    770     if (distance < radius) {
    771         *px += nx * (radius - offset);
    772         *py += ny * (radius - offset);
    773         *pz += nz * (radius - offset);
    774 
    775         return 1;
    776     }
    777 
    778     return 0;
    779 }