mirror of
				https://github.com/coop-deluxe/sm64coopdx.git
				synced 2025-10-30 08:01:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			787 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			787 lines
		
	
	
	
		
			23 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];
 | 
						|
 | 
						|
/**
 | 
						|
 * Pools of data to contain either surface nodes or surfaces.
 | 
						|
 */
 | 
						|
static struct GrowingArray *sSurfaceNodePool = NULL;
 | 
						|
static struct GrowingArray *sSurfacePool = NULL;
 | 
						|
 | 
						|
/**
 | 
						|
 * Allocate the part of the surface node pool to contain a surface node.
 | 
						|
 */
 | 
						|
static struct SurfaceNode *alloc_surface_node(void) {
 | 
						|
    sSurfaceNodePool->count = gSurfaceNodesAllocated++;
 | 
						|
    return growing_array_alloc(sSurfaceNodePool, sizeof(struct SurfaceNode));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Allocate the part of the surface pool to contain a surface and
 | 
						|
 * initialize the surface.
 | 
						|
 */
 | 
						|
static struct Surface *alloc_surface(void) {
 | 
						|
    sSurfacePool->count = gSurfacesAllocated++;
 | 
						|
    return growing_array_alloc(sSurfacePool, sizeof(struct Surface));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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]);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Add a surface to the correct cell list of surfaces.
 | 
						|
 * @param dynamic Determines whether the surface is static or dynamic
 | 
						|
 * @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 dynamic, s16 cellX, s16 cellZ, struct Surface *surface) {
 | 
						|
    struct SurfaceNode *newNode = alloc_surface_node();
 | 
						|
    if (newNode == NULL) { return; }
 | 
						|
    struct SurfaceNode *list;
 | 
						|
    s16 surfacePriority;
 | 
						|
    s16 priority;
 | 
						|
    s16 sortDir;
 | 
						|
    s16 listIndex;
 | 
						|
 | 
						|
    if (surface->normal.y > 0.01) {
 | 
						|
        listIndex = SPATIAL_PARTITION_FLOORS;
 | 
						|
        sortDir = 1; // highest to lowest, then insertion order
 | 
						|
    } else if (surface->normal.y < -0.01) {
 | 
						|
        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 (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 check
 | 
						|
 * @param dynamic Boolean determining whether the surface is static or dynamic
 | 
						|
 */
 | 
						|
static void add_surface(struct Surface *surface, s32 dynamic) {
 | 
						|
    // minY/maxY maybe? s32 instead of s16, though.
 | 
						|
    s16 minX, minZ, maxX, maxZ;
 | 
						|
 | 
						|
    s16 minCellX, minCellZ, maxCellX, maxCellZ;
 | 
						|
 | 
						|
    s16 cellZ, cellX;
 | 
						|
 | 
						|
    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(dynamic, cellX, cellZ, surface);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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
 | 
						|
 */
 | 
						|
static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) {
 | 
						|
    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();
 | 
						|
    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);
 | 
						|
        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, FALSE);
 | 
						|
        }
 | 
						|
 | 
						|
        *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();
 | 
						|
 | 
						|
    sSurfaceNodePool = growing_array_init(sSurfaceNodePool, 0x1000);
 | 
						|
    sSurfacePool = growing_array_init(sSurfacePool, 0x400);
 | 
						|
 | 
						|
    gEnvironmentRegions = NULL;
 | 
						|
    gSurfaceNodesAllocated = 0;
 | 
						|
    gSurfacesAllocated = 0;
 | 
						|
    gNumStaticSurfaceNodes = 0;
 | 
						|
    gNumStaticSurfaces = 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;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * If not in time stop, clear the surface partitions.
 | 
						|
 */
 | 
						|
void clear_dynamic_surfaces(void) {
 | 
						|
    if (!(gTimeStopState & TIME_STOP_ACTIVE)) {
 | 
						|
        gSurfacesAllocated = gNumStaticSurfaces;
 | 
						|
        gSurfaceNodesAllocated = gNumStaticSurfaceNodes;
 | 
						|
 | 
						|
        clear_spatial_partition(&gDynamicSurfacePartition[0][0]);
 | 
						|
 | 
						|
        for (u16 i = 0; i < OBJECT_POOL_CAPACITY; i++) {
 | 
						|
            struct Object *obj = &gObjectPool[i];
 | 
						|
            obj->firstSurface = 0;
 | 
						|
            obj->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) {
 | 
						|
    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;
 | 
						|
    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++) {
 | 
						|
        struct Surface* surface = read_surface_data(vertexData, data);
 | 
						|
 | 
						|
        if (surface != NULL) {
 | 
						|
 | 
						|
            // Set index of first surface
 | 
						|
            if (gCurrentObject->firstSurface == 0) {
 | 
						|
                gCurrentObject->firstSurface = gSurfacesAllocated - 1;
 | 
						|
            }
 | 
						|
 | 
						|
            // Increase surface count
 | 
						|
            gCurrentObject->numSurfaces++;
 | 
						|
 | 
						|
            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, TRUE);
 | 
						|
        }
 | 
						|
 | 
						|
        if (hasForce) {
 | 
						|
            *data += 4;
 | 
						|
        } else {
 | 
						|
            *data += 3;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Transform an object's vertices, reload them, and render the object.
 | 
						|
 */
 | 
						|
void load_object_collision_model(void) {
 | 
						|
    if (!gCurrentObject) { return; }
 | 
						|
    if (gCurrentObject->collisionData == NULL) { 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;
 | 
						|
 | 
						|
    // 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;
 | 
						|
    f32 tangibleDist = gCurrentObject->oCollisionDistance;
 | 
						|
 | 
						|
    u8 anyPlayerInTangibleRange = FALSE;
 | 
						|
    for (s32 i = 0; i < MAX_PLAYERS; i++) {
 | 
						|
        f32 dist = dist_between_objects(gCurrentObject, gMarioStates[i].marioObj);
 | 
						|
        if (dist < tangibleDist) { anyPlayerInTangibleRange = TRUE; }
 | 
						|
    }
 | 
						|
 | 
						|
    // 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.
 | 
						|
    if (!(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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    f32 marioDist = dist_between_objects(gCurrentObject, gMarioStates[0].marioObj);
 | 
						|
    if (marioDist < gCurrentObject->oDrawingDistance * draw_distance_scalar()) {
 | 
						|
        gCurrentObject->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
 | 
						|
    } else {
 | 
						|
        gCurrentObject->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct Surface *obj_get_surface_from_index(struct Object *o, u32 index) {
 | 
						|
    if (!o || o->firstSurface == 0) { return NULL; }
 | 
						|
    if (index >= o->numSurfaces) { return NULL; }
 | 
						|
    struct Surface *surf = sSurfacePool->buffer[o->firstSurface + index];
 | 
						|
    return surf;
 | 
						|
}
 |