2019-08-25 00:46:40 -04:00
|
|
|
#include <ultra64.h>
|
|
|
|
|
|
|
|
#include "sm64.h"
|
|
|
|
#include "game/level_update.h"
|
|
|
|
#include "game/debug.h"
|
|
|
|
#include "game/camera.h"
|
|
|
|
#include "game/mario.h"
|
|
|
|
#include "behavior_script.h"
|
|
|
|
#include "surface_collision.h"
|
|
|
|
#include "surface_load.h"
|
|
|
|
#include "game/object_list_processor.h"
|
|
|
|
#include "game/room.h"
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* WALLS *
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate through the list of walls until all walls are checked and
|
|
|
|
* have given their wall push.
|
|
|
|
*/
|
|
|
|
static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
|
|
|
|
struct WallCollisionData *data) {
|
|
|
|
register f32 offset;
|
|
|
|
register f32 radius = data->radius;
|
|
|
|
register struct Surface *surf;
|
|
|
|
register f32 x = data->x;
|
|
|
|
register f32 y = data->y + data->offsetY;
|
|
|
|
register f32 z = data->z;
|
|
|
|
register f32 px, pz;
|
|
|
|
register f32 w1, w2, w3;
|
|
|
|
register f32 y1, y2, y3;
|
|
|
|
s32 numCols = 0;
|
|
|
|
|
|
|
|
// Max collision radius = 200
|
2019-09-01 15:50:50 -04:00
|
|
|
if (radius > 200.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
radius = 200.0f;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// Stay in this loop until out of walls.
|
|
|
|
while (surfaceNode != NULL) {
|
|
|
|
surf = surfaceNode->surface;
|
|
|
|
surfaceNode = surfaceNode->next;
|
|
|
|
|
|
|
|
// Exclude a large number of walls immediately to optimize.
|
|
|
|
if (y < surf->lowerY || y > surf->upperY) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = surf->normal.x * x + surf->normal.y * y + surf->normal.z * z + surf->originOffset;
|
|
|
|
|
|
|
|
if (offset < -radius || offset > radius) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
px = x;
|
|
|
|
pz = z;
|
|
|
|
|
|
|
|
//! (Quantum Tunneling) Due to issues with the vertices walls choose and
|
|
|
|
// the fact they are floating point, certain floating point positions
|
|
|
|
// along the seam of two walls may collide with neither wall or both walls.
|
|
|
|
if (surf->flags & SURFACE_FLAG_X_PROJECTION) {
|
|
|
|
w1 = -surf->vertex1[2];
|
|
|
|
w2 = -surf->vertex2[2];
|
|
|
|
w3 = -surf->vertex3[2];
|
|
|
|
y1 = surf->vertex1[1];
|
|
|
|
y2 = surf->vertex2[1];
|
|
|
|
y3 = surf->vertex3[1];
|
|
|
|
|
|
|
|
if (surf->normal.x > 0.0f) {
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
} else {
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
w1 = surf->vertex1[0];
|
|
|
|
w2 = surf->vertex2[0];
|
|
|
|
w3 = surf->vertex3[0];
|
|
|
|
y1 = surf->vertex1[1];
|
|
|
|
y2 = surf->vertex2[1];
|
|
|
|
y3 = surf->vertex3[1];
|
|
|
|
|
|
|
|
if (surf->normal.z > 0.0f) {
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
} else {
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if checking for the camera or not.
|
|
|
|
if (gCheckingSurfaceCollisionsForCamera) {
|
2019-09-01 15:50:50 -04:00
|
|
|
if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
} else {
|
|
|
|
// Ignore camera only surfaces.
|
2019-09-01 15:50:50 -04:00
|
|
|
if (surf->type == SURFACE_CAMERA_BOUNDARY) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// If an object can pass through a vanish cap wall, pass through.
|
|
|
|
if (surf->type == SURFACE_VANISH_CAP_WALLS) {
|
|
|
|
// If an object can pass through a vanish cap wall, pass through.
|
|
|
|
if (gCurrentObject != NULL
|
|
|
|
&& (gCurrentObject->activeFlags & ACTIVE_FLAG_MOVE_THROUGH_GRATE)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If Mario has a vanish cap, pass through the vanish cap wall.
|
|
|
|
if (gCurrentObject != NULL && gCurrentObject == gMarioObject
|
|
|
|
&& (gMarioState->flags & MARIO_VANISH_CAP)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//! (Wall Overlaps) Because this doesn't update the x and z local variables,
|
|
|
|
// multiple walls can push mario more than is required.
|
|
|
|
data->x += surf->normal.x * (radius - offset);
|
|
|
|
data->z += surf->normal.z * (radius - offset);
|
|
|
|
|
|
|
|
//! (Unreferenced Walls) Since this only returns the first four walls,
|
|
|
|
// this can lead to wall interaction being missed. Typically unreferenced walls
|
|
|
|
// come from only using one wall, however.
|
|
|
|
if (data->numWalls < 4) {
|
|
|
|
data->walls[data->numWalls++] = surf;
|
|
|
|
}
|
|
|
|
|
|
|
|
numCols++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return numCols;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Formats the position and wall search for find_wall_collisions.
|
|
|
|
*/
|
|
|
|
s32 f32_find_wall_collision(f32 *xPtr, f32 *yPtr, f32 *zPtr, f32 offsetY, f32 radius) {
|
|
|
|
struct WallCollisionData collision;
|
|
|
|
s32 numCollisions = 0;
|
|
|
|
|
|
|
|
collision.offsetY = offsetY;
|
|
|
|
collision.radius = radius;
|
|
|
|
|
|
|
|
collision.x = *xPtr;
|
|
|
|
collision.y = *yPtr;
|
|
|
|
collision.z = *zPtr;
|
|
|
|
|
|
|
|
collision.numWalls = 0;
|
|
|
|
|
|
|
|
numCollisions = find_wall_collisions(&collision);
|
|
|
|
|
|
|
|
*xPtr = collision.x;
|
|
|
|
*yPtr = collision.y;
|
|
|
|
*zPtr = collision.z;
|
|
|
|
|
|
|
|
return numCollisions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find wall collisions and receive their push.
|
|
|
|
*/
|
|
|
|
s32 find_wall_collisions(struct WallCollisionData *colData) {
|
|
|
|
struct SurfaceNode *node;
|
|
|
|
s16 cellX, cellZ;
|
|
|
|
s32 numCollisions = 0;
|
|
|
|
s16 x = colData->x;
|
|
|
|
s16 z = colData->z;
|
|
|
|
|
|
|
|
colData->numWalls = 0;
|
|
|
|
|
2019-09-01 15:50:50 -04:00
|
|
|
if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
|
2019-08-25 00:46:40 -04:00
|
|
|
return numCollisions;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
|
2019-08-25 00:46:40 -04:00
|
|
|
return numCollisions;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// World (level) consists of a 16x16 grid. Find where the collision is on
|
|
|
|
// the grid (round toward -inf)
|
|
|
|
cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0x0F;
|
|
|
|
cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0x0F;
|
|
|
|
|
|
|
|
// Check for surfaces belonging to objects.
|
|
|
|
node = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next;
|
|
|
|
numCollisions += find_wall_collisions_from_list(node, colData);
|
|
|
|
|
|
|
|
// Check for surfaces that are a part of level geometry.
|
|
|
|
node = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next;
|
|
|
|
numCollisions += find_wall_collisions_from_list(node, colData);
|
|
|
|
|
|
|
|
// Increment the debug tracker.
|
|
|
|
gNumCalls.wall += 1;
|
|
|
|
|
|
|
|
return numCollisions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* CEILINGS *
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate through the list of ceilings and find the first ceiling over a given point.
|
|
|
|
*/
|
|
|
|
static struct Surface *find_ceil_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z,
|
|
|
|
f32 *pheight) {
|
|
|
|
register struct Surface *surf;
|
|
|
|
register s32 x1, z1, x2, z2, x3, z3;
|
|
|
|
struct Surface *ceil = NULL;
|
|
|
|
|
|
|
|
ceil = NULL;
|
|
|
|
|
|
|
|
// Stay in this loop until out of ceilings.
|
|
|
|
while (surfaceNode != NULL) {
|
|
|
|
surf = surfaceNode->surface;
|
|
|
|
surfaceNode = surfaceNode->next;
|
|
|
|
|
|
|
|
x1 = surf->vertex1[0];
|
|
|
|
z1 = surf->vertex1[2];
|
|
|
|
z2 = surf->vertex2[2];
|
|
|
|
x2 = surf->vertex2[0];
|
|
|
|
|
|
|
|
// Checking if point is in bounds of the triangle laterally.
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) > 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// Slight optimization by checking these later.
|
|
|
|
x3 = surf->vertex3[0];
|
|
|
|
z3 = surf->vertex3[2];
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) > 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((z3 - z) * (x1 - x3) - (x3 - x) * (z1 - z3) > 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// Determine if checking for the camera or not.
|
|
|
|
if (gCheckingSurfaceCollisionsForCamera != 0) {
|
2019-09-01 15:50:50 -04:00
|
|
|
if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
}
|
|
|
|
// Ignore camera only surfaces.
|
|
|
|
else if (surf->type == SURFACE_CAMERA_BOUNDARY) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
f32 nx = surf->normal.x;
|
|
|
|
f32 ny = surf->normal.y;
|
|
|
|
f32 nz = surf->normal.z;
|
|
|
|
f32 oo = surf->originOffset;
|
|
|
|
f32 height;
|
|
|
|
|
|
|
|
// If a wall, ignore it. Likely a remnant, should never occur.
|
2019-09-01 15:50:50 -04:00
|
|
|
if (ny == 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// Find the ceil height at the specific point.
|
|
|
|
height = -(x * nx + nz * z + oo) / ny;
|
|
|
|
|
|
|
|
// Checks for ceiling interaction with a 78 unit buffer.
|
|
|
|
//! (Exposed Ceilings) Because any point above a ceiling counts
|
|
|
|
// as interacting with a ceiling, ceilings far below can cause
|
|
|
|
// "invisible walls" that are really just exposed ceilings.
|
2019-09-01 15:50:50 -04:00
|
|
|
if (y - (height - -78.0f) > 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
*pheight = height;
|
|
|
|
ceil = surf;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//! (Surface Cucking) Since only the first ceil is returned and not the lowest,
|
|
|
|
// lower ceilings can be "cucked" by higher ceilings.
|
|
|
|
return ceil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the lowest ceiling above a given position and return the height.
|
|
|
|
*/
|
|
|
|
f32 find_ceil(f32 posX, f32 posY, f32 posZ, struct Surface **pceil) {
|
|
|
|
s16 cellZ, cellX;
|
|
|
|
struct Surface *ceil, *dynamicCeil;
|
|
|
|
struct SurfaceNode *surfaceList;
|
|
|
|
f32 height = 20000.0f;
|
|
|
|
f32 dynamicHeight = 20000.0f;
|
|
|
|
s16 x, y, z;
|
|
|
|
|
|
|
|
//! (Parallel Universes) Because position is casted to an s16, reaching higher
|
|
|
|
// float locations can return ceilings despite them not existing there.
|
|
|
|
//(Dynamic ceilings will unload due to the range.)
|
|
|
|
x = (s16) posX;
|
|
|
|
y = (s16) posY;
|
|
|
|
z = (s16) posZ;
|
|
|
|
*pceil = NULL;
|
|
|
|
|
|
|
|
if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each level is split into cells to limit load, find the appropriate cell.
|
|
|
|
cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0xF;
|
|
|
|
cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0xF;
|
|
|
|
|
|
|
|
// Check for surfaces belonging to objects.
|
|
|
|
surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next;
|
|
|
|
dynamicCeil = find_ceil_from_list(surfaceList, x, y, z, &dynamicHeight);
|
|
|
|
|
|
|
|
// Check for surfaces that are a part of level geometry.
|
|
|
|
surfaceList = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next;
|
|
|
|
ceil = find_ceil_from_list(surfaceList, x, y, z, &height);
|
|
|
|
|
|
|
|
if (dynamicHeight < height) {
|
|
|
|
ceil = dynamicCeil;
|
|
|
|
height = dynamicHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
*pceil = ceil;
|
|
|
|
|
|
|
|
// Increment the debug tracker.
|
|
|
|
gNumCalls.ceil += 1;
|
|
|
|
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* FLOORS *
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the height of the highest floor below an object.
|
|
|
|
*/
|
|
|
|
static f32 unused_obj_find_floor_height(struct Object *obj) {
|
|
|
|
struct Surface *floor;
|
|
|
|
f32 floorHeight = find_floor(obj->oPosX, obj->oPosY, obj->oPosZ, &floor);
|
|
|
|
return floorHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basically a local variable that passes through floor geo info.
|
|
|
|
*/
|
|
|
|
struct FloorGeometry sFloorGeo;
|
|
|
|
|
|
|
|
static u8 unused8038BE50[0x40];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the floor height underneath (xPos, yPos, zPos) and populate `floorGeo`
|
|
|
|
* with data about the floor's normal vector and origin offset. Also update
|
|
|
|
* sFloorGeo.
|
|
|
|
*/
|
|
|
|
f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometry **floorGeo) {
|
|
|
|
struct Surface *floor;
|
|
|
|
f32 floorHeight = find_floor(xPos, yPos, zPos, &floor);
|
|
|
|
|
|
|
|
*floorGeo = NULL;
|
|
|
|
|
|
|
|
if (floor != NULL) {
|
|
|
|
sFloorGeo.normalX = floor->normal.x;
|
|
|
|
sFloorGeo.normalY = floor->normal.y;
|
|
|
|
sFloorGeo.normalZ = floor->normal.z;
|
|
|
|
sFloorGeo.originOffset = floor->originOffset;
|
|
|
|
|
|
|
|
*floorGeo = &sFloorGeo;
|
|
|
|
}
|
|
|
|
return floorHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate through the list of floors and find the first floor under a given point.
|
|
|
|
*/
|
|
|
|
static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z,
|
|
|
|
f32 *pheight) {
|
|
|
|
register struct Surface *surf;
|
|
|
|
register s32 x1, z1, x2, z2, x3, z3;
|
|
|
|
f32 nx, ny, nz;
|
|
|
|
f32 oo;
|
|
|
|
f32 height;
|
|
|
|
struct Surface *floor = NULL;
|
|
|
|
|
|
|
|
// Iterate through the list of floors until there are no more floors.
|
|
|
|
while (surfaceNode != NULL) {
|
|
|
|
surf = surfaceNode->surface;
|
|
|
|
surfaceNode = surfaceNode->next;
|
|
|
|
|
|
|
|
x1 = surf->vertex1[0];
|
|
|
|
z1 = surf->vertex1[2];
|
|
|
|
x2 = surf->vertex2[0];
|
|
|
|
z2 = surf->vertex2[2];
|
|
|
|
|
|
|
|
// Check that the point is within the triangle bounds.
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// To slightly save on computation time, set this later.
|
|
|
|
x3 = surf->vertex3[0];
|
|
|
|
z3 = surf->vertex3[2];
|
|
|
|
|
2019-09-01 15:50:50 -04:00
|
|
|
if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if ((z3 - z) * (x1 - x3) - (x3 - x) * (z1 - z3) < 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
// Determine if we are checking for the camera or not.
|
|
|
|
if (gCheckingSurfaceCollisionsForCamera != 0) {
|
2019-09-01 15:50:50 -04:00
|
|
|
if (surf->flags & SURFACE_FLAG_NO_CAM_COLLISION) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
}
|
|
|
|
// If we are not checking for the camera, ignore camera only floors.
|
|
|
|
else if (surf->type == SURFACE_CAMERA_BOUNDARY) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
nx = surf->normal.x;
|
|
|
|
ny = surf->normal.y;
|
|
|
|
nz = surf->normal.z;
|
|
|
|
oo = surf->originOffset;
|
|
|
|
|
|
|
|
// If a wall, ignore it. Likely a remnant, should never occur.
|
|
|
|
if (ny == 0.0f) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the height of the floor at a given location.
|
|
|
|
height = -(x * nx + nz * z + oo) / ny;
|
|
|
|
// Checks for floor interaction with a 78 unit buffer.
|
2019-09-01 15:50:50 -04:00
|
|
|
if (y - (height + -78.0f) < 0.0f) {
|
2019-08-25 00:46:40 -04:00
|
|
|
continue;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
|
|
|
|
*pheight = height;
|
|
|
|
floor = surf;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! (Surface Cucking) Since only the first floor is returned and not the highest,
|
|
|
|
// higher floors can be "cucked" by lower floors.
|
|
|
|
return floor;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the height of the highest floor below a point.
|
|
|
|
*/
|
|
|
|
f32 find_floor_height(f32 x, f32 y, f32 z) {
|
|
|
|
struct Surface *floor;
|
|
|
|
|
|
|
|
f32 floorHeight = find_floor(x, y, z, &floor);
|
|
|
|
|
|
|
|
return floorHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the highest dynamic floor under a given position. Perhaps originally static and
|
|
|
|
* and dynamic floors were checked separately.
|
|
|
|
*/
|
|
|
|
static f32 unused_find_dynamic_floor(f32 xPos, f32 yPos, f32 zPos, struct Surface **pfloor) {
|
|
|
|
struct SurfaceNode *surfaceList;
|
|
|
|
struct Surface *floor;
|
|
|
|
f32 floorHeight = -11000.0f;
|
|
|
|
|
|
|
|
// Would normally cause PUs, but dynamic floors unload at that range.
|
|
|
|
s16 x = (s16) xPos;
|
|
|
|
s16 y = (s16) yPos;
|
|
|
|
s16 z = (s16) zPos;
|
|
|
|
|
|
|
|
// Each level is split into cells to limit load, find the appropriate cell.
|
|
|
|
s16 cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0x0F;
|
|
|
|
s16 cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0x0F;
|
|
|
|
|
|
|
|
surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
|
|
|
|
floor = find_floor_from_list(surfaceList, x, y, z, &floorHeight);
|
|
|
|
|
|
|
|
*pfloor = floor;
|
|
|
|
|
|
|
|
return floorHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the highest floor under a given position and return the height.
|
|
|
|
*/
|
|
|
|
f32 find_floor(f32 xPos, f32 yPos, f32 zPos, struct Surface **pfloor) {
|
|
|
|
s16 cellZ, cellX;
|
|
|
|
|
|
|
|
struct Surface *floor, *dynamicFloor;
|
|
|
|
struct SurfaceNode *surfaceList;
|
|
|
|
|
|
|
|
f32 height = -11000.0f;
|
|
|
|
f32 dynamicHeight = -11000.0f;
|
|
|
|
|
|
|
|
//! (Parallel Universes) Because position is casted to an s16, reaching higher
|
|
|
|
// float locations can return floors despite them not existing there.
|
|
|
|
//(Dynamic floors will unload due to the range.)
|
|
|
|
s16 x = (s16) xPos;
|
|
|
|
s16 y = (s16) yPos;
|
|
|
|
s16 z = (s16) zPos;
|
|
|
|
|
|
|
|
*pfloor = NULL;
|
|
|
|
|
|
|
|
if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
if (z <= -LEVEL_BOUNDARY_MAX || z >= LEVEL_BOUNDARY_MAX) {
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each level is split into cells to limit load, find the appropriate cell.
|
|
|
|
cellX = ((x + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0xF;
|
|
|
|
cellZ = ((z + LEVEL_BOUNDARY_MAX) / CELL_SIZE) & 0xF;
|
|
|
|
|
|
|
|
// Check for surfaces belonging to objects.
|
|
|
|
surfaceList = gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
|
|
|
|
dynamicFloor = find_floor_from_list(surfaceList, x, y, z, &dynamicHeight);
|
|
|
|
|
|
|
|
// Check for surfaces that are a part of level geometry.
|
|
|
|
surfaceList = gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next;
|
|
|
|
floor = find_floor_from_list(surfaceList, x, y, z, &height);
|
|
|
|
|
|
|
|
// To prevent the Merry-Go-Round room from loading when Mario passes above the hole that leads
|
|
|
|
// there, SURFACE_INTANGIBLE is used. This prevent the wrong room from loading, but can also allow
|
|
|
|
// Mario to pass through.
|
|
|
|
if (!gFindFloorIncludeSurfaceIntangible) {
|
|
|
|
//! (BBH Crash) Most NULL checking is done by checking the height of the floor returned
|
|
|
|
// instead of checking directly for a NULL floor. If this check returns a NULL floor
|
|
|
|
// (happens when there is no floor under the SURFACE_INTANGIBLE floor) but returns the height
|
|
|
|
// of the SURFACE_INTANGIBLE floor instead of the typical -11000 returned for a NULL floor.
|
|
|
|
if (floor != NULL && floor->type == SURFACE_INTANGIBLE) {
|
|
|
|
floor = find_floor_from_list(surfaceList, x, (s32)(height - 200.0f), z, &height);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// To prevent accidentally leaving the floor tangible, stop checking for it.
|
|
|
|
gFindFloorIncludeSurfaceIntangible = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a floor was missed, increment the debug counter.
|
|
|
|
if (floor == NULL) {
|
|
|
|
gNumFindFloorMisses += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dynamicHeight > height) {
|
|
|
|
floor = dynamicFloor;
|
|
|
|
height = dynamicHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
*pfloor = floor;
|
|
|
|
|
|
|
|
// Increment the debug tracker.
|
|
|
|
gNumCalls.floor += 1;
|
|
|
|
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* ENVIRONMENTAL BOXES *
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the height of water at a given location.
|
|
|
|
*/
|
|
|
|
f32 find_water_level(f32 x, f32 z) {
|
|
|
|
s32 i;
|
|
|
|
s32 numRegions;
|
|
|
|
s16 val;
|
|
|
|
f32 loX, hiX, loZ, hiZ;
|
|
|
|
f32 waterLevel = -11000.0f;
|
|
|
|
s16 *p = gEnvironmentRegions;
|
|
|
|
|
|
|
|
if (p != NULL) {
|
|
|
|
numRegions = *p++;
|
|
|
|
|
|
|
|
for (i = 0; i < numRegions; i++) {
|
|
|
|
val = *p++;
|
|
|
|
loX = *p++;
|
|
|
|
loZ = *p++;
|
|
|
|
hiX = *p++;
|
|
|
|
hiZ = *p++;
|
|
|
|
|
|
|
|
// If the location is within a water box and it is a water box.
|
|
|
|
// Water is less than 50 val only, while above is gas and such.
|
|
|
|
if (loX < x && x < hiX && loZ < z && z < hiZ && val < 50) {
|
|
|
|
// Set the water height. Since this breaks, only return the first height.
|
|
|
|
waterLevel = *p;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return waterLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the height of the poison gas (used only in HMC) at a given location.
|
|
|
|
*/
|
|
|
|
f32 find_poison_gas_level(f32 x, f32 z) {
|
|
|
|
s32 i;
|
|
|
|
s32 numRegions;
|
|
|
|
UNUSED s32 unused;
|
|
|
|
s16 val;
|
|
|
|
f32 loX, hiX, loZ, hiZ;
|
|
|
|
f32 gasLevel = -11000.0f;
|
|
|
|
s16 *p = gEnvironmentRegions;
|
|
|
|
|
|
|
|
if (p != NULL) {
|
|
|
|
numRegions = *p++;
|
|
|
|
|
|
|
|
for (i = 0; i < numRegions; i++) {
|
|
|
|
val = *p;
|
|
|
|
|
|
|
|
if (val >= 50) {
|
|
|
|
loX = *(p + 1);
|
|
|
|
loZ = *(p + 2);
|
|
|
|
hiX = *(p + 3);
|
|
|
|
hiZ = *(p + 4);
|
|
|
|
|
|
|
|
// If the location is within a gas's box and it is a gas box.
|
|
|
|
// Gas has a value of 50, 60, etc.
|
|
|
|
if (loX < x && x < hiX && loZ < z && z < hiZ && val % 10 == 0) {
|
|
|
|
// Set the gas height. Since this breaks, only return the first height.
|
|
|
|
gasLevel = *(p + 5);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p += 6;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return gasLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* DEBUG *
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the length of a surface list for debug purposes.
|
|
|
|
*/
|
|
|
|
static s32 surface_list_length(struct SurfaceNode *list) {
|
|
|
|
s32 count = 0;
|
|
|
|
|
|
|
|
while (list != NULL) {
|
|
|
|
list = list->next;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Print the area,number of walls, how many times they were called,
|
|
|
|
* and some allocation information.
|
|
|
|
*/
|
|
|
|
void debug_surface_list_info(f32 xPos, f32 zPos) {
|
|
|
|
struct SurfaceNode *list;
|
|
|
|
s32 numFloors = 0;
|
|
|
|
s32 numWalls = 0;
|
|
|
|
s32 numCeils = 0;
|
|
|
|
|
|
|
|
s32 cellX = (xPos + LEVEL_BOUNDARY_MAX) / CELL_SIZE;
|
|
|
|
s32 cellZ = (zPos + LEVEL_BOUNDARY_MAX) / CELL_SIZE;
|
|
|
|
|
|
|
|
list = gStaticSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_FLOORS].next;
|
|
|
|
numFloors += surface_list_length(list);
|
|
|
|
|
|
|
|
list = gDynamicSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_FLOORS].next;
|
|
|
|
numFloors += surface_list_length(list);
|
|
|
|
|
|
|
|
list = gStaticSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_WALLS].next;
|
|
|
|
numWalls += surface_list_length(list);
|
|
|
|
|
|
|
|
list = gDynamicSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_WALLS].next;
|
|
|
|
numWalls += surface_list_length(list);
|
|
|
|
|
|
|
|
list = gStaticSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_CEILS].next;
|
|
|
|
numCeils += surface_list_length(list);
|
|
|
|
|
|
|
|
list = gDynamicSurfacePartition[cellZ & 0x0F][cellX & 0x0F][SPATIAL_PARTITION_CEILS].next;
|
|
|
|
numCeils += surface_list_length(list);
|
|
|
|
|
|
|
|
print_debug_top_down_mapinfo("area %x", cellZ * 16 + cellX);
|
|
|
|
|
|
|
|
// Names represent ground, walls, and roofs as found in SMS.
|
|
|
|
print_debug_top_down_mapinfo("dg %d", numFloors);
|
|
|
|
print_debug_top_down_mapinfo("dw %d", numWalls);
|
|
|
|
print_debug_top_down_mapinfo("dr %d", numCeils);
|
|
|
|
|
|
|
|
set_text_array_x_y(80, -3);
|
|
|
|
|
|
|
|
print_debug_top_down_mapinfo("%d", gNumCalls.floor);
|
|
|
|
print_debug_top_down_mapinfo("%d", gNumCalls.wall);
|
|
|
|
print_debug_top_down_mapinfo("%d", gNumCalls.ceil);
|
|
|
|
|
|
|
|
set_text_array_x_y(-80, 0);
|
|
|
|
|
|
|
|
// listal- List Allocated?, statbg- Static Background?, movebg- Moving Background?
|
|
|
|
print_debug_top_down_mapinfo("listal %d", gSurfaceNodesAllocated);
|
|
|
|
print_debug_top_down_mapinfo("statbg %d", gNumStaticSurfaces);
|
|
|
|
print_debug_top_down_mapinfo("movebg %d", gSurfacesAllocated - gNumStaticSurfaces);
|
|
|
|
|
|
|
|
gNumCalls.floor = 0;
|
|
|
|
gNumCalls.ceil = 0;
|
|
|
|
gNumCalls.wall = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An unused function that finds and interacts with any type of surface.
|
|
|
|
* Perhaps an original implementation of surfaces before they were more specialized.
|
|
|
|
*/
|
|
|
|
static s32 unused_resolve_floor_or_ceil_collisions(s32 checkCeil, f32 *px, f32 *py, f32 *pz, f32 radius,
|
|
|
|
struct Surface **psurface, f32 *surfaceHeight) {
|
|
|
|
f32 nx, ny, nz, oo;
|
|
|
|
f32 x = *px, y = *py, z = *pz;
|
|
|
|
f32 offset, distance;
|
|
|
|
|
|
|
|
*psurface = NULL;
|
|
|
|
|
|
|
|
if (checkCeil) {
|
|
|
|
*surfaceHeight = find_ceil(x, y, z, psurface);
|
|
|
|
} else {
|
|
|
|
*surfaceHeight = find_floor(x, y, z, psurface);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*psurface == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nx = (*psurface)->normal.x;
|
|
|
|
ny = (*psurface)->normal.y;
|
|
|
|
nz = (*psurface)->normal.z;
|
|
|
|
oo = (*psurface)->originOffset;
|
|
|
|
|
|
|
|
offset = nx * x + ny * y + nz * z + oo;
|
|
|
|
distance = offset >= 0 ? offset : -offset;
|
|
|
|
|
|
|
|
// Interesting surface interaction that should be surf type independent.
|
|
|
|
if (distance < radius) {
|
|
|
|
*px += nx * (radius - offset);
|
|
|
|
*py += ny * (radius - offset);
|
|
|
|
*pz += nz * (radius - offset);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|