mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-05-31 21:21:07 +00:00
The existing draw distance options don't actually increase the draw distance. It only impacts whether or not distant objects are rendered. This PR: - Adds another option to the draw distance setting in the display menu, called "Infinite". It is not truly infinite, but it is significantly larger than what current options allow. Due to it not being truly infinite, we could use the name "Max" instead to be more accurate. - Exposes a new function to the Lua API `draw_distance_scalar_is_infinite` which returns whether or not the infinite setting is enabled. - `draw_distance_scalar_is_infinite` is now used in several places in this repo to bypass distance checks for objects if infinite mode is enabled. - Fixes a bug where you couldn't bypass the distance check in `obj_is_in_view`, meaning you could never disable all object distance culling. - The infinite setting now forces the far plane to be at least a minimum of `1,000,000`.
1159 lines
35 KiB
C
1159 lines
35 KiB
C
#include <PR/ultratypes.h>
|
|
|
|
#include "prevent_bss_reordering.h"
|
|
|
|
#include "sm64.h"
|
|
#include "game/ingame_menu.h"
|
|
#include "graph_node.h"
|
|
#include "behavior_script.h"
|
|
#include "behavior_data.h"
|
|
#include "game/memory.h"
|
|
#include "game/object_helpers.h"
|
|
#include "game/macro_special_objects.h"
|
|
#include "surface_collision.h"
|
|
#include "game/mario.h"
|
|
#include "game/object_list_processor.h"
|
|
#include "surface_load.h"
|
|
#include "game/game_init.h"
|
|
#include "engine/math_util.h"
|
|
#include "game/level_update.h"
|
|
#include "game/hardcoded.h"
|
|
#include "pc/network/network.h"
|
|
#include "pc/lua/smlua_hooks.h"
|
|
|
|
/**
|
|
* Partitions for course and object surfaces. The arrays represent
|
|
* the 16x16 cells that each level is split into.
|
|
*/
|
|
SpatialPartitionCell gStaticSurfacePartition[NUM_CELLS][NUM_CELLS];
|
|
SpatialPartitionCell gDynamicSurfacePartition[NUM_CELLS][NUM_CELLS];
|
|
|
|
/**
|
|
* The total number of surface nodes allocated (a node is allocated for each
|
|
* spatial partition cell that a surface intersects).
|
|
*/
|
|
s32 gSurfaceNodesAllocated;
|
|
|
|
/**
|
|
* The total number of surfaces allocated.
|
|
*/
|
|
s32 gSurfacesAllocated;
|
|
|
|
/**
|
|
* The number of nodes that have been created for static surfaces.
|
|
*/
|
|
s32 gNumStaticSurfaceNodes;
|
|
|
|
/**
|
|
* The number of static surfaces in the pool.
|
|
*/
|
|
s32 gNumStaticSurfaces;
|
|
|
|
/**
|
|
* The number of nodes that have been created for static object collision surfaces.
|
|
*/
|
|
s32 gNumSOCSurfaceNodes;
|
|
|
|
/**
|
|
* The number of static object collision surfaces in the pool.
|
|
*/
|
|
s32 gNumSOCSurfaces;
|
|
|
|
/**
|
|
* Pools of data to contain either surface nodes or surfaces.
|
|
*/
|
|
static struct GrowingArray *sSurfaceStaticNodePool = NULL;
|
|
static struct GrowingArray *sSurfaceStaticPool = NULL;
|
|
static struct GrowingArray *sSurfaceSOCNodePool = NULL;
|
|
static struct GrowingArray *sSurfaceSOCPool = NULL;
|
|
static struct GrowingArray *sSurfaceDynamicNodePool = NULL;
|
|
static struct GrowingArray *sSurfaceDynamicPool = NULL;
|
|
|
|
/**
|
|
* Pool of data for static object collisions.
|
|
*/
|
|
static struct GrowingArray *sSOCPool = NULL;
|
|
|
|
/**
|
|
* Counter for assigning unique SOC IDs.
|
|
*/
|
|
static u32 sSOCIdCounter = 0;
|
|
|
|
/**
|
|
* When true, add_surface skips firing HOOK_ON_ADD_SURFACE.
|
|
*/
|
|
static bool sSkipAddSurfaceHook = false;
|
|
|
|
/**
|
|
* Custom free function for StaticObjectCollision entries.
|
|
* Invalidates the Lua CObject, then frees the struct.
|
|
*/
|
|
static void free_static_object_collision(void *ptr) {
|
|
smlua_free_soc(ptr);
|
|
}
|
|
|
|
/**
|
|
* Allocate a surface node from the appropriate pool.
|
|
*/
|
|
static struct SurfaceNode *alloc_surface_node(s32 poolType) {
|
|
gSurfaceNodesAllocated++;
|
|
struct GrowingArray *pool;
|
|
switch (poolType) {
|
|
case SURFACE_POOL_DYNAMIC: pool = sSurfaceDynamicNodePool; break;
|
|
case SURFACE_POOL_SOC: pool = sSurfaceSOCNodePool; break;
|
|
default: pool = sSurfaceStaticNodePool; break;
|
|
}
|
|
return growing_array_alloc(pool, sizeof(struct SurfaceNode));
|
|
}
|
|
|
|
/**
|
|
* Allocate a surface from the appropriate pool.
|
|
*/
|
|
struct Surface *alloc_surface(s32 poolType) {
|
|
gSurfacesAllocated++;
|
|
struct GrowingArray *pool;
|
|
switch (poolType) {
|
|
case SURFACE_POOL_DYNAMIC: pool = sSurfaceDynamicPool; break;
|
|
case SURFACE_POOL_SOC: pool = sSurfaceSOCPool; break;
|
|
default: pool = sSurfaceStaticPool; break;
|
|
}
|
|
struct Surface *surface = growing_array_alloc(pool, sizeof(struct Surface));
|
|
if (surface != NULL) {
|
|
surface->poolType = poolType;
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
static struct StaticObjectCollision *alloc_static_object_collision(void) {
|
|
return growing_array_alloc(sSOCPool, sizeof(struct StaticObjectCollision));
|
|
}
|
|
|
|
/**
|
|
* Iterates through the entire partition, clearing the surfaces.
|
|
*/
|
|
static void clear_spatial_partition(SpatialPartitionCell *cells) {
|
|
register s32 i = NUM_CELLS * NUM_CELLS;
|
|
|
|
while (i--) {
|
|
(*cells)[SPATIAL_PARTITION_FLOORS].next = NULL;
|
|
(*cells)[SPATIAL_PARTITION_CEILS].next = NULL;
|
|
(*cells)[SPATIAL_PARTITION_WALLS].next = NULL;
|
|
|
|
cells++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the static (level) surface partitions for new use.
|
|
*/
|
|
static void clear_static_surfaces(void) {
|
|
clear_spatial_partition(&gStaticSurfacePartition[0][0]);
|
|
|
|
// Invalidate Lua CObjects for surfaces that are about to be recycled
|
|
if (sSurfaceStaticPool) {
|
|
for (u32 i = 0; i < sSurfaceStaticPool->count; i++) {
|
|
if (sSurfaceStaticPool->buffer[i]) {
|
|
smlua_invalidate_surface(sSurfaceStaticPool->buffer[i]);
|
|
}
|
|
}
|
|
sSurfaceStaticPool->count = 0;
|
|
}
|
|
if (sSurfaceSOCPool) {
|
|
for (u32 i = 0; i < sSurfaceSOCPool->count; i++) {
|
|
if (sSurfaceSOCPool->buffer[i]) {
|
|
smlua_invalidate_surface(sSurfaceSOCPool->buffer[i]);
|
|
}
|
|
}
|
|
sSurfaceSOCPool->count = 0;
|
|
}
|
|
if (sSurfaceStaticNodePool) { sSurfaceStaticNodePool->count = 0; }
|
|
if (sSurfaceSOCNodePool) { sSurfaceSOCNodePool->count = 0; }
|
|
|
|
sSOCPool = growing_array_init(sSOCPool, 0x100, malloc, free_static_object_collision);
|
|
sSOCIdCounter = 0;
|
|
}
|
|
|
|
/**
|
|
* Add a surface to the correct cell list of surfaces.
|
|
* @param cellX The X position of the cell in which the surface resides
|
|
* @param cellZ The Z position of the cell in which the surface resides
|
|
* @param surface The surface to add
|
|
*/
|
|
static void add_surface_to_cell(s16 cellX, s16 cellZ, struct Surface *surface) {
|
|
struct SurfaceNode *newNode = alloc_surface_node(surface->poolType);
|
|
if (newNode == NULL) { return; }
|
|
struct SurfaceNode *list;
|
|
s16 surfacePriority;
|
|
s16 priority;
|
|
s16 sortDir;
|
|
s16 listIndex;
|
|
|
|
if (surface->normal.y > gLevelValues.floorNormalMinY) {
|
|
listIndex = SPATIAL_PARTITION_FLOORS;
|
|
sortDir = 1; // highest to lowest, then insertion order
|
|
} else if (surface->normal.y < gLevelValues.ceilNormalMaxY) {
|
|
listIndex = SPATIAL_PARTITION_CEILS;
|
|
sortDir = -1; // lowest to highest, then insertion order
|
|
} else {
|
|
listIndex = SPATIAL_PARTITION_WALLS;
|
|
sortDir = 0; // insertion order
|
|
|
|
if (surface->normal.x < -0.707 || surface->normal.x > 0.707) {
|
|
surface->flags |= SURFACE_FLAG_X_PROJECTION;
|
|
}
|
|
}
|
|
|
|
//! (Surface Cucking) Surfaces are sorted by the height of their first
|
|
// vertex. Since vertices aren't ordered by height, this causes many
|
|
// lower triangles to be sorted higher. This worsens surface cucking since
|
|
// many functions only use the first triangle in surface order that fits,
|
|
// missing higher surfaces.
|
|
// upperY would be a better sort method.
|
|
// <Fixed when gLevelValues.fixCollisionBugs != 0>
|
|
|
|
surfacePriority = gLevelValues.fixCollisionBugs
|
|
? (surface->upperY * sortDir)
|
|
: (surface->vertex1[1] * sortDir);
|
|
|
|
newNode->surface = surface;
|
|
|
|
if (surface->poolType == SURFACE_POOL_DYNAMIC) {
|
|
list = &gDynamicSurfacePartition[cellZ][cellX][listIndex];
|
|
} else {
|
|
list = &gStaticSurfacePartition[cellZ][cellX][listIndex];
|
|
}
|
|
|
|
// Loop until we find the appropriate place for the surface in the list.
|
|
while (list->next != NULL) {
|
|
priority = list->next->surface->vertex1[1] * sortDir;
|
|
|
|
if (surfacePriority > priority) {
|
|
break;
|
|
}
|
|
|
|
list = list->next;
|
|
}
|
|
|
|
newNode->next = list->next;
|
|
list->next = newNode;
|
|
}
|
|
|
|
/**
|
|
* Returns the lowest of three values.
|
|
*/
|
|
|
|
#define min_3(a0, a1, a2) MIN(MIN(a0, a1), a2)
|
|
|
|
/**
|
|
* Returns the highest of three values.
|
|
*/
|
|
|
|
#define max_3(a0, a1, a2) MAX(MAX(a0, a1), a2)
|
|
|
|
/**
|
|
* Every level is split into 16 * 16 cells of surfaces (to limit computing
|
|
* time). This function determines the lower cell for a given x/z position.
|
|
* @param coord The coordinate to test
|
|
*/
|
|
static s16 lower_cell_index(s32 coord) {
|
|
s16 index;
|
|
|
|
// Move from range [-0x2000, 0x2000) to [0, 0x4000)
|
|
coord += LEVEL_BOUNDARY_MAX;
|
|
if (coord < 0) {
|
|
coord = 0;
|
|
}
|
|
|
|
// [0, 16)
|
|
index = coord / CELL_SIZE;
|
|
|
|
// Include extra cell if close to boundary
|
|
//! Some wall checks are larger than the buffer, meaning wall checks can
|
|
// miss walls that are near a cell border.
|
|
if (coord % CELL_SIZE < 50) {
|
|
index -= 1;
|
|
}
|
|
|
|
if (index < 0) {
|
|
index = 0;
|
|
}
|
|
|
|
// Potentially > 15, but since the upper index is <= 15, not exploitable
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Every level is split into 16 * 16 cells of surfaces (to limit computing
|
|
* time). This function determines the upper cell for a given x/z position.
|
|
* @param coord The coordinate to test
|
|
*/
|
|
static s16 upper_cell_index(s32 coord) {
|
|
s16 index;
|
|
|
|
// Move from range [-0x2000, 0x2000) to [0, 0x4000)
|
|
coord += LEVEL_BOUNDARY_MAX;
|
|
if (coord < 0) {
|
|
coord = 0;
|
|
}
|
|
|
|
// [0, 16)
|
|
index = coord / CELL_SIZE;
|
|
|
|
// Include extra cell if close to boundary
|
|
//! Some wall checks are larger than the buffer, meaning wall checks can
|
|
// miss walls that are near a cell border.
|
|
if (coord % CELL_SIZE > CELL_SIZE - 50) {
|
|
index += 1;
|
|
}
|
|
|
|
if (index > NUM_CELLS_INDEX) {
|
|
index = NUM_CELLS_INDEX;
|
|
}
|
|
|
|
// Potentially < 0, but since lower index is >= 0, not exploitable
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Every level is split into 16x16 cells, this takes a surface, finds
|
|
* the appropriate cells (with a buffer), and adds the surface to those
|
|
* cells.
|
|
* @param surface The surface to add
|
|
*/
|
|
void add_surface(struct Surface *surface) {
|
|
// minY/maxY maybe? s32 instead of s16, though.
|
|
s16 minX, minZ, maxX, maxZ;
|
|
|
|
s16 minCellX, minCellZ, maxCellX, maxCellZ;
|
|
|
|
s16 cellZ, cellX;
|
|
|
|
if (!sSkipAddSurfaceHook) {
|
|
smlua_call_event_hooks(HOOK_ON_ADD_SURFACE, surface, surface->poolType == SURFACE_POOL_DYNAMIC);
|
|
}
|
|
|
|
minX = min_3(surface->vertex1[0], surface->vertex2[0], surface->vertex3[0]);
|
|
minZ = min_3(surface->vertex1[2], surface->vertex2[2], surface->vertex3[2]);
|
|
maxX = max_3(surface->vertex1[0], surface->vertex2[0], surface->vertex3[0]);
|
|
maxZ = max_3(surface->vertex1[2], surface->vertex2[2], surface->vertex3[2]);
|
|
|
|
minCellX = lower_cell_index(minX);
|
|
maxCellX = upper_cell_index(maxX);
|
|
minCellZ = lower_cell_index(minZ);
|
|
maxCellZ = upper_cell_index(maxZ);
|
|
|
|
for (cellZ = minCellZ; cellZ <= maxCellZ; cellZ++) {
|
|
for (cellX = minCellX; cellX <= maxCellX; cellX++) {
|
|
add_surface_to_cell(cellX, cellZ, surface);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Add a surface, but don't call HOOK_ON_ADD_SURFACE.
|
|
*/
|
|
void add_surface_without_hook(struct Surface *surface) {
|
|
sSkipAddSurfaceHook = true;
|
|
add_surface(surface);
|
|
sSkipAddSurfaceHook = false;
|
|
}
|
|
|
|
/**
|
|
* Swap-and-pop a surface out of its owning pool, selected by poolType.
|
|
* Does not touch counters or partitions; the caller is responsible.
|
|
*/
|
|
bool swap_and_pop_surface_pool(s32 poolType, struct Surface *surface) {
|
|
switch (poolType) {
|
|
case SURFACE_POOL_DYNAMIC: return growing_array_swap_and_pop(sSurfaceDynamicPool, surface);
|
|
case SURFACE_POOL_SOC: return growing_array_swap_and_pop(sSurfaceSOCPool, surface);
|
|
default: return growing_array_swap_and_pop(sSurfaceStaticPool, surface);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a surface from the spatial partition, reclaiming any nodes that were allocated for it.
|
|
*/
|
|
void remove_surface_from_partition(struct Surface *surface) {
|
|
if (surface == NULL) { return; }
|
|
|
|
s32 poolType = surface->poolType;
|
|
bool isDynamic = (poolType == SURFACE_POOL_DYNAMIC);
|
|
|
|
SpatialPartitionCell (*partition)[NUM_CELLS] = isDynamic
|
|
? gDynamicSurfacePartition
|
|
: gStaticSurfacePartition;
|
|
|
|
struct GrowingArray *nodePool;
|
|
|
|
switch (poolType) {
|
|
case SURFACE_POOL_DYNAMIC: nodePool = sSurfaceDynamicNodePool; break;
|
|
case SURFACE_POOL_SOC: nodePool = sSurfaceSOCNodePool; break;
|
|
default: nodePool = sSurfaceStaticNodePool; break;
|
|
}
|
|
|
|
for (s32 cellZ = 0; cellZ < NUM_CELLS; cellZ++) {
|
|
for (s32 cellX = 0; cellX < NUM_CELLS; cellX++) {
|
|
for (s32 listIndex = 0; listIndex < 3; listIndex++) {
|
|
struct SurfaceNode *prev = &partition[cellZ][cellX][listIndex];
|
|
struct SurfaceNode *node = prev->next;
|
|
|
|
while (node != NULL) {
|
|
if (node->surface != surface) {
|
|
prev = node;
|
|
node = node->next;
|
|
continue;
|
|
}
|
|
|
|
if (growing_array_swap_and_pop(nodePool, node)) {
|
|
prev->next = node->next;
|
|
gSurfaceNodesAllocated--;
|
|
node = prev->next;
|
|
continue;
|
|
}
|
|
|
|
prev = node;
|
|
node = node->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fully delete a surface: invalidate any Lua CObject reference,
|
|
* remove it from spatial partitions, swap-and-pop it out of its
|
|
* owning pool, and update counters.
|
|
*/
|
|
void delete_surface(struct Surface *surface) {
|
|
if (surface == NULL) { return; }
|
|
|
|
s32 poolType = surface->poolType;
|
|
|
|
// Invalidate Lua CObject reference
|
|
smlua_invalidate_surface(surface);
|
|
|
|
// Snapshot the relevant node pool count to track how many nodes are reclaimed
|
|
struct GrowingArray *nodePool;
|
|
switch (poolType) {
|
|
case SURFACE_POOL_DYNAMIC: nodePool = sSurfaceDynamicNodePool; break;
|
|
case SURFACE_POOL_SOC: nodePool = sSurfaceSOCNodePool; break;
|
|
default: nodePool = sSurfaceStaticNodePool; break;
|
|
}
|
|
u32 nodesBefore = nodePool ? nodePool->count : 0;
|
|
|
|
// Remove from spatial partitions (also reclaims node pool slots)
|
|
remove_surface_from_partition(surface);
|
|
|
|
u32 nodesRemoved = nodesBefore - (nodePool ? nodePool->count : 0);
|
|
|
|
// Decrement the owning SOC's tracked length
|
|
if (poolType == SURFACE_POOL_SOC && sSOCPool) {
|
|
for (u32 ci = 0; ci < sSOCPool->count; ci++) {
|
|
struct StaticObjectCollision *col = sSOCPool->buffer[ci];
|
|
if (col && col->index == surface->socId) {
|
|
if (col->length > 0) { col->length--; }
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Swap-and-pop from the owning pool and update counters
|
|
switch (poolType) {
|
|
case SURFACE_POOL_SOC:
|
|
growing_array_swap_and_pop(sSurfaceSOCPool, surface);
|
|
gNumSOCSurfaces--;
|
|
gNumSOCSurfaceNodes -= nodesRemoved;
|
|
break;
|
|
case SURFACE_POOL_STATIC:
|
|
growing_array_swap_and_pop(sSurfaceStaticPool, surface);
|
|
gNumStaticSurfaces--;
|
|
gNumStaticSurfaceNodes -= nodesRemoved;
|
|
break;
|
|
case SURFACE_POOL_DYNAMIC:
|
|
if (surface->object && surface->object->numSurfaces > 0) {
|
|
surface->object->numSurfaces--;
|
|
}
|
|
growing_array_swap_and_pop(sSurfaceDynamicPool, surface);
|
|
break;
|
|
}
|
|
gSurfacesAllocated--;
|
|
}
|
|
|
|
/**
|
|
* Initializes a Surface struct using the given vertex data
|
|
* @param vertexData The raw data containing vertex positions
|
|
* @param vertexIndices Helper which tells positions in vertexData to start reading vertices
|
|
* @param poolType The pool type to allocate the surface from
|
|
* @return A pointer to the newly allocated surface, or NULL on failure
|
|
*/
|
|
static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices, s32 poolType) {
|
|
if (vertexData == NULL || vertexIndices == NULL || *vertexIndices == NULL) { return NULL; }
|
|
|
|
struct Surface *surface;
|
|
register s32 x1, y1, z1;
|
|
register s32 x2, y2, z2;
|
|
register s32 x3, y3, z3;
|
|
s32 maxY, minY;
|
|
f32 nx, ny, nz;
|
|
f32 mag;
|
|
s32 offset1, offset2, offset3;
|
|
|
|
offset1 = 3 * (*vertexIndices)[0];
|
|
offset2 = 3 * (*vertexIndices)[1];
|
|
offset3 = 3 * (*vertexIndices)[2];
|
|
|
|
x1 = *(vertexData + offset1 + 0);
|
|
y1 = *(vertexData + offset1 + 1);
|
|
z1 = *(vertexData + offset1 + 2);
|
|
|
|
x2 = *(vertexData + offset2 + 0);
|
|
y2 = *(vertexData + offset2 + 1);
|
|
z2 = *(vertexData + offset2 + 2);
|
|
|
|
x3 = *(vertexData + offset3 + 0);
|
|
y3 = *(vertexData + offset3 + 1);
|
|
z3 = *(vertexData + offset3 + 2);
|
|
|
|
// (v2 - v1) x (v3 - v2)
|
|
nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2);
|
|
ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2);
|
|
nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2);
|
|
mag = sqrtf(nx * nx + ny * ny + nz * nz);
|
|
|
|
// Could have used min_3 and max_3 for this...
|
|
minY = y1;
|
|
if (y2 < minY) {
|
|
minY = y2;
|
|
}
|
|
if (y3 < minY) {
|
|
minY = y3;
|
|
}
|
|
|
|
maxY = y1;
|
|
if (y2 > maxY) {
|
|
maxY = y2;
|
|
}
|
|
if (y3 > maxY) {
|
|
maxY = y3;
|
|
}
|
|
|
|
// Checking to make sure no DIV/0
|
|
if (mag < 0.0001) {
|
|
return NULL;
|
|
}
|
|
mag = (f32)(1.0 / mag);
|
|
nx *= mag;
|
|
ny *= mag;
|
|
nz *= mag;
|
|
|
|
surface = alloc_surface(poolType);
|
|
if (surface == NULL) { return NULL; }
|
|
|
|
vec3s_copy(surface->prevVertex1, surface->vertex1);
|
|
vec3s_copy(surface->prevVertex2, surface->vertex2);
|
|
vec3s_copy(surface->prevVertex3, surface->vertex3);
|
|
surface->modifiedTimestamp = gGlobalTimer;
|
|
|
|
surface->vertex1[0] = x1;
|
|
surface->vertex2[0] = x2;
|
|
surface->vertex3[0] = x3;
|
|
|
|
surface->vertex1[1] = y1;
|
|
surface->vertex2[1] = y2;
|
|
surface->vertex3[1] = y3;
|
|
|
|
surface->vertex1[2] = z1;
|
|
surface->vertex2[2] = z2;
|
|
surface->vertex3[2] = z3;
|
|
|
|
surface->normal.x = nx;
|
|
surface->normal.y = ny;
|
|
surface->normal.z = nz;
|
|
|
|
surface->originOffset = -(nx * x1 + ny * y1 + nz * z1);
|
|
|
|
surface->lowerY = minY - 5;
|
|
surface->upperY = maxY + 5;
|
|
|
|
return surface;
|
|
}
|
|
|
|
/**
|
|
* Returns whether a surface has exertion/moves Mario
|
|
* based on the surface type.
|
|
*/
|
|
bool surface_has_force(s16 surfaceType) {
|
|
return surfaceType == SURFACE_0004 ||
|
|
surfaceType == SURFACE_FLOWING_WATER ||
|
|
surfaceType == SURFACE_HORIZONTAL_WIND ||
|
|
surfaceType == SURFACE_MOVING_QUICKSAND ||
|
|
surfaceType == SURFACE_DEEP_MOVING_QUICKSAND ||
|
|
surfaceType == SURFACE_SHALLOW_MOVING_QUICKSAND ||
|
|
surfaceType == SURFACE_INSTANT_MOVING_QUICKSAND;
|
|
}
|
|
|
|
/**
|
|
* Returns whether a surface should have the
|
|
* SURFACE_FLAG_NO_CAM_COLLISION flag.
|
|
*/
|
|
static bool surf_has_no_cam_collision(s16 surfaceType) {
|
|
return surfaceType == SURFACE_RAYCAST ||
|
|
surfaceType == SURFACE_NO_CAM_COLLISION ||
|
|
surfaceType == SURFACE_NO_CAM_COLLISION_77 ||
|
|
surfaceType == SURFACE_NO_CAM_COL_VERY_SLIPPERY ||
|
|
surfaceType == SURFACE_VANISH_CAP_WALLS ||
|
|
surfaceType == SURFACE_SWITCH;
|
|
}
|
|
|
|
/**
|
|
* Load in the surfaces for a given surface type. This includes setting the flags,
|
|
* exertion, and room.
|
|
*/
|
|
static void load_static_surfaces(s16 **data, s16 *vertexData, s16 surfaceType, s8 **surfaceRooms) {
|
|
s32 numSurfaces;
|
|
struct Surface *surface;
|
|
s8 room = 0;
|
|
bool hasForce = surface_has_force(surfaceType);
|
|
s8 flags = surf_has_no_cam_collision(surfaceType) ? SURFACE_FLAG_NO_CAM_COLLISION : 0;
|
|
|
|
numSurfaces = *(*data);
|
|
*data += 1;
|
|
|
|
for (s32 i = 0; i < numSurfaces; i++) {
|
|
if (*surfaceRooms != NULL) {
|
|
room = *(*surfaceRooms);
|
|
*surfaceRooms += 1;
|
|
}
|
|
|
|
surface = read_surface_data(vertexData, data, SURFACE_POOL_STATIC);
|
|
if (surface != NULL) {
|
|
surface->room = room;
|
|
surface->type = surfaceType;
|
|
surface->flags = flags;
|
|
|
|
if (hasForce) {
|
|
surface->force = *(*data + 3);
|
|
} else {
|
|
surface->force = 0;
|
|
}
|
|
|
|
add_surface(surface);
|
|
}
|
|
|
|
*data += 3;
|
|
if (hasForce) {
|
|
*data += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read the data for vertices for reference by triangles.
|
|
*/
|
|
static s16 *read_vertex_data(s16 **data) {
|
|
s32 numVertices;
|
|
s16 *vertexData;
|
|
|
|
numVertices = *(*data);
|
|
(*data)++;
|
|
|
|
vertexData = *data;
|
|
*data += 3 * numVertices;
|
|
|
|
return vertexData;
|
|
}
|
|
|
|
/**
|
|
* Loads in special environmental regions, such as water, poison gas, and JRB fog.
|
|
*/
|
|
static void load_environmental_regions(s16 **data) {
|
|
s32 numRegions;
|
|
|
|
gEnvironmentRegionsLength = 0;
|
|
gEnvironmentRegions = *data;
|
|
numRegions = *(*data)++;
|
|
gEnvironmentRegionsLength++;
|
|
|
|
if (numRegions > 20) {
|
|
numRegions = 20;
|
|
}
|
|
|
|
for (s32 i = 0; i < numRegions; i++) {
|
|
UNUSED s16 val, loX, loZ, hiX, hiZ;
|
|
s16 height;
|
|
|
|
val = *(*data)++;
|
|
|
|
loX = *(*data)++;
|
|
hiX = *(*data)++;
|
|
loZ = *(*data)++;
|
|
hiZ = *(*data)++;
|
|
|
|
height = *(*data)++;
|
|
|
|
gEnvironmentRegionsLength += 6;
|
|
gEnvironmentLevels[i] = height;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocate some of the main pool for surfaces (2300 surf) and for surface nodes (7000 nodes).
|
|
*/
|
|
void alloc_surface_pools(void) {
|
|
clear_static_surfaces();
|
|
clear_dynamic_surfaces();
|
|
|
|
sSurfaceStaticNodePool = growing_array_init(sSurfaceStaticNodePool, 0x1000, malloc, free);
|
|
sSurfaceStaticPool = growing_array_init(sSurfaceStaticPool, 0x400, malloc, smlua_free_surface);
|
|
|
|
sSurfaceSOCNodePool = growing_array_init(sSurfaceSOCNodePool, 0x800, malloc, free);
|
|
sSurfaceSOCPool = growing_array_init(sSurfaceSOCPool, 0x200, malloc, smlua_free_surface);
|
|
|
|
sSurfaceDynamicNodePool = growing_array_init(sSurfaceDynamicNodePool, 0x1000, malloc, free);
|
|
sSurfaceDynamicPool = growing_array_init(sSurfaceDynamicPool, 0x400, malloc, smlua_free_surface);
|
|
|
|
gEnvironmentRegions = NULL;
|
|
gSurfaceNodesAllocated = 0;
|
|
gSurfacesAllocated = 0;
|
|
gNumStaticSurfaceNodes = 0;
|
|
gNumStaticSurfaces = 0;
|
|
gNumSOCSurfaceNodes = 0;
|
|
gNumSOCSurfaces = 0;
|
|
|
|
gCCMEnteredSlide = 0;
|
|
reset_red_coins_collected();
|
|
}
|
|
|
|
/**
|
|
* Get the size of the terrain data, to get the correct size when copying later.
|
|
*/
|
|
u32 get_area_terrain_size(s16 *terrainData) {
|
|
s16 *startPos = terrainData;
|
|
s32 end = FALSE;
|
|
s16 terrainLoadType;
|
|
s32 numVertices;
|
|
s32 numRegions;
|
|
s32 numSurfaces;
|
|
s16 hasForce;
|
|
while (!end) {
|
|
terrainLoadType = *terrainData++;
|
|
|
|
switch (terrainLoadType) {
|
|
case TERRAIN_LOAD_VERTICES:
|
|
numVertices = *terrainData++;
|
|
terrainData += 3 * numVertices;
|
|
break;
|
|
|
|
case TERRAIN_LOAD_OBJECTS:
|
|
terrainData += get_special_objects_size(terrainData);
|
|
break;
|
|
|
|
case TERRAIN_LOAD_ENVIRONMENT:
|
|
numRegions = *terrainData++;
|
|
terrainData += 6 * numRegions;
|
|
break;
|
|
|
|
case TERRAIN_LOAD_CONTINUE:
|
|
continue;
|
|
|
|
case TERRAIN_LOAD_END:
|
|
end = TRUE;
|
|
break;
|
|
|
|
default:
|
|
numSurfaces = *terrainData++;
|
|
hasForce = surface_has_force(terrainLoadType);
|
|
terrainData += (3 + hasForce) * numSurfaces;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return terrainData - startPos;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process the level file, loading in vertices, surfaces, some objects, and environmental
|
|
* boxes (water, gas, JRB fog).
|
|
*/
|
|
void load_area_terrain(s16 index, s16 *data, s8 *surfaceRooms, s16 *macroObjects) {
|
|
s16 terrainLoadType = 0;
|
|
s16 *vertexData = NULL;
|
|
|
|
// Initialize the data for this.
|
|
gEnvironmentRegions = NULL;
|
|
gSurfaceNodesAllocated = 0;
|
|
gSurfacesAllocated = 0;
|
|
|
|
clear_static_surfaces();
|
|
|
|
// A while loop iterating through each section of the level data. Sections of data
|
|
// are prefixed by a terrain "type." This type is reused for surfaces as the surface
|
|
// type.
|
|
while (TRUE) {
|
|
terrainLoadType = *data;
|
|
data++;
|
|
|
|
if (TERRAIN_LOAD_IS_SURFACE_TYPE_LOW(terrainLoadType)) {
|
|
load_static_surfaces(&data, vertexData, terrainLoadType, &surfaceRooms);
|
|
} else if (terrainLoadType == TERRAIN_LOAD_VERTICES) {
|
|
vertexData = read_vertex_data(&data);
|
|
} else if (terrainLoadType == TERRAIN_LOAD_OBJECTS) {
|
|
spawn_special_objects(index, &data);
|
|
} else if (terrainLoadType == TERRAIN_LOAD_ENVIRONMENT) {
|
|
load_environmental_regions(&data);
|
|
} else if (terrainLoadType == TERRAIN_LOAD_CONTINUE) {
|
|
continue;
|
|
} else if (terrainLoadType == TERRAIN_LOAD_END) {
|
|
break;
|
|
} else if (TERRAIN_LOAD_IS_SURFACE_TYPE_HIGH(terrainLoadType)) {
|
|
load_static_surfaces(&data, vertexData, terrainLoadType, &surfaceRooms);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (macroObjects != NULL && *macroObjects != -1) {
|
|
// If the first macro object presetID is within the range [0, 29].
|
|
// Generally an early spawning method, every object is in BBH (the first level).
|
|
if (0 <= *macroObjects && *macroObjects < 30) {
|
|
spawn_macro_objects_hardcoded(index, macroObjects);
|
|
}
|
|
// A more general version that can spawn more objects.
|
|
else {
|
|
spawn_macro_objects(index, macroObjects);
|
|
}
|
|
}
|
|
|
|
gNumStaticSurfaceNodes = gSurfaceNodesAllocated;
|
|
gNumStaticSurfaces = gSurfacesAllocated;
|
|
gNumSOCSurfaceNodes = 0;
|
|
gNumSOCSurfaces = 0;
|
|
}
|
|
|
|
/**
|
|
* If not in time stop, clear the surface partitions.
|
|
*/
|
|
void clear_dynamic_surfaces(void) {
|
|
if (gTimeStopState & TIME_STOP_ACTIVE) { return; }
|
|
|
|
if (sSurfaceDynamicPool) {
|
|
// Invalidate Lua CObjects for dynamic surfaces being recycled
|
|
for (u32 i = 0; i < sSurfaceDynamicPool->count; i++) {
|
|
if (sSurfaceDynamicPool->buffer[i]) {
|
|
smlua_invalidate_surface(sSurfaceDynamicPool->buffer[i]);
|
|
}
|
|
}
|
|
sSurfaceDynamicPool->count = 0;
|
|
}
|
|
|
|
if (sSurfaceDynamicNodePool) {
|
|
sSurfaceDynamicNodePool->count = 0;
|
|
}
|
|
|
|
gSurfacesAllocated = (sSurfaceStaticPool ? sSurfaceStaticPool->count : 0)
|
|
+ (sSurfaceSOCPool ? sSurfaceSOCPool->count : 0);
|
|
|
|
gSurfaceNodesAllocated = (sSurfaceStaticNodePool ? sSurfaceStaticNodePool->count : 0)
|
|
+ (sSurfaceSOCNodePool ? sSurfaceSOCNodePool->count : 0);
|
|
|
|
clear_spatial_partition(&gDynamicSurfacePartition[0][0]);
|
|
|
|
for (u16 i = 0; i < OBJECT_POOL_CAPACITY; i++) {
|
|
gObjectPool[i].numSurfaces = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies an object's transformation to the object's vertices.
|
|
*/
|
|
void transform_object_vertices(s16 **data, s16 *vertexData) {
|
|
if (!gCurrentObject) { return; }
|
|
register s16 *vertices;
|
|
register f32 vx, vy, vz;
|
|
register s32 numVertices;
|
|
|
|
Mat4 *objectTransform;
|
|
Mat4 m;
|
|
|
|
objectTransform = &gCurrentObject->transform;
|
|
|
|
numVertices = *(*data);
|
|
(*data)++;
|
|
|
|
vertices = *data;
|
|
|
|
if (gCurrentObject->header.gfx.throwMatrix == NULL) {
|
|
gCurrentObject->header.gfx.throwMatrix = objectTransform;
|
|
obj_build_transform_from_pos_and_angle(gCurrentObject, O_POS_INDEX, O_FACE_ANGLE_INDEX);
|
|
}
|
|
|
|
obj_apply_scale_to_matrix(gCurrentObject, m, *objectTransform);
|
|
|
|
// Go through all vertices, rotating and translating them to transform the object.
|
|
while (numVertices--) {
|
|
vx = *(vertices++);
|
|
vy = *(vertices++);
|
|
vz = *(vertices++);
|
|
|
|
//! No bounds check on vertex data
|
|
*vertexData++ = (s16)(vx * m[0][0] + vy * m[1][0] + vz * m[2][0] + m[3][0]);
|
|
*vertexData++ = (s16)(vx * m[0][1] + vy * m[1][1] + vz * m[2][1] + m[3][1]);
|
|
*vertexData++ = (s16)(vx * m[0][2] + vy * m[1][2] + vz * m[2][2] + m[3][2]);
|
|
}
|
|
|
|
*data = vertices;
|
|
}
|
|
|
|
/**
|
|
* Load in the surfaces for the gCurrentObject. This includes setting the flags, exertion, and room.
|
|
*/
|
|
void load_object_surfaces(s16** data, s16* vertexData, bool isSOC) {
|
|
if (!gCurrentObject) { return; }
|
|
s32 surfaceType;
|
|
s32 i;
|
|
s32 numSurfaces;
|
|
s16 hasForce;
|
|
s16 flags;
|
|
s16 room;
|
|
|
|
surfaceType = *(*data);
|
|
(*data)++;
|
|
|
|
numSurfaces = *(*data);
|
|
(*data)++;
|
|
|
|
hasForce = surface_has_force(surfaceType);
|
|
|
|
flags = surf_has_no_cam_collision(surfaceType) ? SURFACE_FLAG_NO_CAM_COLLISION : 0;
|
|
if (!isSOC) {
|
|
flags |= SURFACE_FLAG_DYNAMIC;
|
|
}
|
|
|
|
// The DDD warp is initially loaded at the origin and moved to the proper
|
|
// position in paintings.c and doesn't update its room, so set it here.
|
|
if (gCurrentObject->behavior == segmented_to_virtual(smlua_override_behavior(bhvDddWarp))) {
|
|
room = 5;
|
|
} else {
|
|
room = 0;
|
|
}
|
|
|
|
for (i = 0; i < numSurfaces; i++) {
|
|
s32 poolType = isSOC ? SURFACE_POOL_SOC : SURFACE_POOL_DYNAMIC;
|
|
struct Surface* surface = read_surface_data(vertexData, data, poolType);
|
|
|
|
if (surface != NULL) {
|
|
surface->object = gCurrentObject;
|
|
surface->type = surfaceType;
|
|
|
|
if (hasForce) {
|
|
surface->force = *(*data + 3);
|
|
} else {
|
|
surface->force = 0;
|
|
}
|
|
|
|
surface->flags |= flags;
|
|
surface->room = (s8)room;
|
|
add_surface(surface);
|
|
|
|
if (!isSOC) {
|
|
gCurrentObject->numSurfaces++;
|
|
}
|
|
}
|
|
|
|
if (hasForce) {
|
|
*data += 4;
|
|
} else {
|
|
*data += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform an object's vertices, reload them, and render the object.
|
|
*/
|
|
static void load_object_collision_model_internal(bool isSOC) {
|
|
static bool sIsLoadingCollision = false;
|
|
|
|
if (!gCurrentObject) { return; }
|
|
if (gCurrentObject->collisionData == NULL) { return; }
|
|
if (sIsLoadingCollision) { return; }
|
|
|
|
s32 numVertices = 64;
|
|
if (gCurrentObject->collisionData[0] == COL_INIT()) {
|
|
numVertices = gCurrentObject->collisionData[1];
|
|
}
|
|
if (numVertices <= 0) {
|
|
LOG_ERROR("Object collisions had invalid vertex count");
|
|
return;
|
|
}
|
|
if (numVertices >= 4096) {
|
|
LOG_ERROR("Object collisions had too many vertices");
|
|
return;
|
|
}
|
|
|
|
static s32 sVertexDataCount = 0;
|
|
static s16* sVertexData = NULL;
|
|
|
|
// start loading collision
|
|
sIsLoadingCollision = true;
|
|
|
|
// allocate vertex data
|
|
if (numVertices > sVertexDataCount || sVertexData == NULL) {
|
|
if (sVertexData) { free(sVertexData); }
|
|
sVertexDataCount = numVertices;
|
|
if (sVertexDataCount < 64) { sVertexDataCount = 64; }
|
|
sVertexData = malloc((3 * sVertexDataCount + 1) * sizeof(s16));
|
|
LOG_INFO("Reallocating object vertex data: %u", sVertexDataCount);
|
|
}
|
|
|
|
s16* collisionData = gCurrentObject->collisionData;
|
|
|
|
u8 anyPlayerInTangibleRange = FALSE;
|
|
if (!isSOC) {
|
|
f32 tangibleDist = gCurrentObject->oCollisionDistance;
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
|
f32 dist = dist_between_objects(gCurrentObject, gMarioStates[i].marioObj);
|
|
if (dist < tangibleDist) {
|
|
anyPlayerInTangibleRange = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the object collision is supposed to be loaded more than the
|
|
// drawing distance of 4000, extend the drawing range.
|
|
if (gCurrentObject->oCollisionDistance > 4000.0f) {
|
|
gCurrentObject->oDrawingDistance = gCurrentObject->oCollisionDistance;
|
|
}
|
|
}
|
|
|
|
// Update if no Time Stop, in range, and in the current room. (or if static)
|
|
if (isSOC ||
|
|
(!(gTimeStopState & TIME_STOP_ACTIVE)
|
|
&& (anyPlayerInTangibleRange)
|
|
&& !(gCurrentObject->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM))
|
|
) {
|
|
collisionData++;
|
|
transform_object_vertices(&collisionData, sVertexData);
|
|
|
|
// TERRAIN_LOAD_CONTINUE acts as an "end" to the terrain data.
|
|
while (*collisionData != TERRAIN_LOAD_CONTINUE) {
|
|
load_object_surfaces(&collisionData, sVertexData, isSOC);
|
|
}
|
|
}
|
|
|
|
if (!isSOC) {
|
|
f32 marioDist = dist_between_objects(gCurrentObject, gMarioStates[0].marioObj);
|
|
if (draw_distance_scalar_is_infinite() ||
|
|
marioDist < gCurrentObject->oDrawingDistance * draw_distance_scalar()
|
|
) {
|
|
gCurrentObject->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
|
|
} else {
|
|
gCurrentObject->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
|
|
}
|
|
}
|
|
|
|
// stop loading collision
|
|
sIsLoadingCollision = false;
|
|
}
|
|
|
|
void load_object_collision_model(void) {
|
|
load_object_collision_model_internal(false);
|
|
}
|
|
|
|
struct StaticObjectCollision *load_static_object_collision() {
|
|
u32 startCount = sSurfaceSOCPool->count;
|
|
u32 startNodeCount = sSurfaceSOCNodePool->count;
|
|
|
|
load_object_collision_model_internal(true);
|
|
|
|
u32 addedSurfaces = sSurfaceSOCPool->count - startCount;
|
|
u32 addedNodes = sSurfaceSOCNodePool->count - startNodeCount;
|
|
|
|
gNumSOCSurfaces += addedSurfaces;
|
|
gNumSOCSurfaceNodes += addedNodes;
|
|
|
|
struct StaticObjectCollision *col = alloc_static_object_collision();
|
|
col->index = ++sSOCIdCounter;
|
|
col->length = addedSurfaces;
|
|
|
|
// Tag each new surface with this SOC's unique ID
|
|
for (u32 i = 0; i < addedSurfaces; i++) {
|
|
struct Surface *surf = sSurfaceSOCPool->buffer[startCount + i];
|
|
if (surf) { surf->socId = col->index; }
|
|
}
|
|
|
|
return col;
|
|
}
|
|
|
|
void toggle_static_object_collision(struct StaticObjectCollision *col, bool tangible) {
|
|
if (!col || !sSurfaceSOCPool) { return; }
|
|
|
|
for (u32 i = 0; i < sSurfaceSOCPool->count; i++) {
|
|
struct Surface *surf = sSurfaceSOCPool->buffer[i];
|
|
|
|
if (!surf || surf->socId != col->index) { continue; }
|
|
|
|
if (tangible) {
|
|
surf->flags &= ~SURFACE_FLAG_INTANGIBLE;
|
|
} else {
|
|
surf->flags |= SURFACE_FLAG_INTANGIBLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Surface *get_static_object_surface(struct StaticObjectCollision *col, u32 index) {
|
|
if (!col || !sSurfaceSOCPool) { return NULL; }
|
|
|
|
u32 count = 0;
|
|
|
|
for (u32 i = 0; i < sSurfaceSOCPool->count; i++) {
|
|
struct Surface *surf = sSurfaceSOCPool->buffer[i];
|
|
if (!surf || surf->socId != col->index) { continue; }
|
|
if (count == index) { return surf; }
|
|
count++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void remove_static_object_collision(struct StaticObjectCollision *col) {
|
|
if (!col || !sSurfaceSOCPool) { return; }
|
|
|
|
// delete_surface uses swap-and-pop, so after deleting buffer[i] the slot
|
|
// is filled by the former last element (and must be checked again)
|
|
u32 i = 0;
|
|
while (i < sSurfaceSOCPool->count) {
|
|
struct Surface *surf = sSurfaceSOCPool->buffer[i];
|
|
if (surf && surf->socId == col->index) {
|
|
delete_surface(surf);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
col->length = 0;
|
|
col->index = 0;
|
|
|
|
// reclaim the SOC metadata from the pool
|
|
smlua_cobject_invalidate(col, LOT_STATICOBJECTCOLLISION);
|
|
growing_array_swap_and_pop(sSOCPool, col);
|
|
}
|
|
|
|
struct Surface *obj_get_surface_from_index(struct Object *o, u32 index) {
|
|
if (!o || !sSurfaceDynamicPool) { return NULL; }
|
|
|
|
u32 count = 0;
|
|
for (u32 i = 0; i < sSurfaceDynamicPool->count; i++) {
|
|
struct Surface *surf = sSurfaceDynamicPool->buffer[i];
|
|
if (surf && surf->object == o) {
|
|
if (count == index) {
|
|
return surf;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|