This commit is contained in:
djoslin0 2026-03-30 20:59:56 +03:00 committed by GitHub
commit 20005f5e36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1363 additions and 87 deletions

View file

@ -55,6 +55,7 @@ in_files = [
"src/engine/lighting_engine.h",
"include/PR/gbi.h",
"include/PR/gbi_extension.h",
"src/engine/surface_load.h",
]
exclude_constants = {
@ -68,6 +69,7 @@ exclude_constants = {
"src/pc/lua/smlua_hooks.h": [ "MAX_HOOKED_MOD_MENU_ELEMENTS", "^HOOK_RETURN_.*", "^ACTION_HOOK_.*", "^MOD_MENU_ELEMENT_.*" ],
"src/pc/djui/djui_panel_menu.h": [ "RAINBOW_TEXT_LEN" ],
"src/pc/mods/mod_fs.h": [ "INT_TYPE_MAX", "FLOAT_TYPE_MAX", "FILE_SEEK_MAX" ],
"src/engine/surface_load.h": [ "NUM_CELLS" ],
}
include_constants = {

View file

@ -105,7 +105,7 @@ override_allowed_functions = {
override_disallowed_functions = {
"src/audio/external.h": [ " func_" ],
"src/engine/surface_load.h": [ "load_area_terrain", "alloc_surface_pools", "clear_dynamic_surfaces", "get_area_terrain_size" ],
"src/engine/surface_load.h": [ "load_area_terrain", "alloc_surface_pools", "clear_dynamic_surfaces", "get_area_terrain_size", "alloc_surface", "add_surface", "remove_surface_from_partition", "delete_surface", "swap_and_pop_surface_pool", "add_surface_without_hook" ],
"src/engine/surface_collision.h": [ " debug_", "f32_find_wall_collision" ],
"src/game/mario_actions_airborne.c": [ "^[us]32 act_.*" ],
"src/game/mario_actions_automatic.c": [ "^[us]32 act_.*" ],

View file

@ -105,6 +105,7 @@ override_field_invisible = {
override_field_deprecated = {
"NetworkPlayer": [ "paletteIndex", "overridePaletteIndex", "overridePaletteIndexLp" ],
"StaticObjectCollision": [ "index" ],
}
override_field_immutable = {
@ -115,6 +116,7 @@ override_field_immutable = {
"NetworkPlayer": [ "*" ],
"TextureInfo": [ "*" ],
"Object": ["oSyncID", "coopFlags", "oChainChompSegments", "oWigglerSegments", "oHauntedChairUnk100", "oTTCTreadmillBigSurface", "oTTCTreadmillSmallSurface", "bhvStackIndex", "respawnInfoType", "numSurfaces", "bhvStack" ],
"Surface": [ "poolType", "socId" ],
"GlobalObjectAnimations": [ "*"],
"SpawnParticlesInfo": [ "model" ],
"WaterDropletParams": [ "model" ],

View file

@ -10602,6 +10602,15 @@ SOUND_OBJ2_BOSS_DIALOG_GRUNT = SOUND_ARG_LOAD(SOUND_BANK_OBJ2, 0x69, 0x40, SOUND
--- @type integer
SOUND_OBJ2_MRI_SPINNING = SOUND_ARG_LOAD(SOUND_BANK_OBJ2, 0x6B, 0x00, SOUND_DISCRETE)
--- @type integer
SURFACE_POOL_STATIC = 0
--- @type integer
SURFACE_POOL_DYNAMIC = 1
--- @type integer
SURFACE_POOL_SOC = 2
--- @type integer
SURFACE_DEFAULT = 0x0000

View file

@ -10706,6 +10706,32 @@ function smlua_collision_util_find_surface_types(data)
-- ...
end
--- @param dynamic boolean
--- @param surfaceType integer
--- @param vertex1 Vec3s
--- @param vertex2 Vec3s
--- @param vertex3 Vec3s
--- @return Surface
--- Allocates a new collision surface with the given vertices, computes the surface normal and other fields, and inserts it into the spatial partition. Returns the new surface, or `nil` if the triangle is degenerate (zero area). Set `dynamic` to `true` for surfaces that are cleared each frame, or `false` for persistent static surfaces
function smlua_collision_add_surface(dynamic, surfaceType, vertex1, vertex2, vertex3)
-- ...
end
--- @param surface Surface
--- @param vertex1 Vec3s
--- @param vertex2 Vec3s
--- @param vertex3 Vec3s
--- Moves an existing collision surface to new vertex positions. Recalculates the surface normal, origin offset, and Y bounds, removes the surface from its old spatial partition cells, and re-adds it to the correct cells. The previous vertices are preserved for interpolation
function smlua_collision_move_surface(surface, vertex1, vertex2, vertex3)
-- ...
end
--- @param surface Surface
--- Fully deletes a collision surface: removes it from the spatial partitions and frees its pool slot.
function smlua_collision_delete_surface(surface)
-- ...
end
--- @param surf Surface
--- @return boolean
--- Checks if the surface is quicksand
@ -12544,6 +12570,12 @@ function get_static_object_surface(col, index)
-- ...
end
--- @param col StaticObjectCollision
--- Removes all surfaces belonging to a static object collision and reclaims the SOC metadata
function remove_static_object_collision(col)
-- ...
end
--- @param o Object
--- @param index integer
--- @return Surface

View file

@ -2235,6 +2235,7 @@
--- @field public type integer
--- @field public flags integer
--- @field public room integer
--- @field public poolType integer
--- @field public force integer
--- @field public lowerY integer
--- @field public upperY integer
@ -2247,6 +2248,7 @@
--- @field public normal Vec3f
--- @field public originOffset number
--- @field public modifiedTimestamp integer
--- @field public socId integer
--- @field public object Object
--- @class TextureInfo

View file

@ -90,6 +90,7 @@
- [smlua_model_utils.h](#smlua_model_utilsh)
- [enum ModelExtendedId](#enum-ModelExtendedId)
- [sounds.h](#soundsh)
- [surface_load.h](#surface_loadh)
- [surface_terrains.h](#surface_terrainsh)
- [types.h](#typesh)
- [enum SpTaskState](#enum-SpTaskState)
@ -4489,6 +4490,15 @@
<br />
## [surface_load.h](#surface_load.h)
- SURFACE_POOL_STATIC
- SURFACE_POOL_DYNAMIC
- SURFACE_POOL_SOC
[:arrow_up_small:](#)
<br />
## [surface_terrains.h](#surface_terrains.h)
- SURFACE_DEFAULT
- SURFACE_BURNING

View file

@ -0,0 +1,421 @@
-----------------
-- Dbg Library --
-- v1.1 --
-----------------
---------------------------------------------------------------------
--
-- A tiny, immediate-mode debug-drawing library for Lua that
-- lets you quickly visualize things in your game world or on
-- the HUD.
--
-- Use the following functions at any time:
-- Dbg.point(pos, color, size) to draw 3D points
-- Dbg.line(a, b, color, thickness) to draw 3D lines
-- Dbg.text(str, pos, color, scale) for world-space labels
--
-- The rendered points/lines/text automatically disappear on the
-- next frame unless you set Dbg.clear_automatically to false
--
-- Credits for the original line rendering code go to Isaac
--
---------------------------------------------------------------------
---@class Dbg
local Dbg = {
clear_automatically = true,
alter_position_function = nil
}
Dbg.colors = {
red = { 1, 0, 0 },
green = { 0, 1, 0 },
blue = { 0, 0, 1 },
cyan = { 0, 1, 1 },
magenta = { 1, 0, 1 },
yellow = { 1, 1, 0 },
white = { 1, 1, 1 },
grey = { 0.2, 0.2, 0.2 },
black = { 0, 0, 0 },
}
Dbg.htext = nil
---------------------------------------------------------------------
-- FRAME-LOCAL BUFFERS (auto-cleared each world_step)
---------------------------------------------------------------------
local _debug_points = { points = {}, count = 0 }
local _debug_lines = { lines = {}, count = 0 }
local _debug_texts = { texts = {}, count = 0 }
local _debug_game_tick = 0
local function point_new()
return {
position = { 0, 0, 0 },
color = { 0, 0, 0 },
scale = 1
}
end
local function line_new()
return {
p1 = { 0, 0, 0 },
p2 = { 0, 0, 0 },
color = { 0, 0, 0 },
thickness = 1
}
end
local function text_new()
return {
string = '',
position = { 0, 0, 0 },
color = { 0, 0, 0 },
scale = 1
}
end
local function clear_check()
if not Dbg.clear_automatically then return end
local tick = get_global_timer()
if _debug_game_tick ~= tick then
_debug_game_tick = tick
Dbg.clear()
end
end
---------------------------------------------------------------------
-- LINES - CLIPPING
---------------------------------------------------------------------
local tmp_out1 = { x = 0, y = 0, z = 0}
local tmp_out2 = { x = 0, y = 0, z = 0}
-- scratch buffers (create once, reuse every call)
local low_pt = { 0, 0, 0 }
local high_pt = { 0, 0, 0 }
local mid_pt = { 0, 0, 0 }
local screen = { x=0, y=0, z=0 }
local tmp_world = { x=0, y=0, z=0 }
local MAX_ITERS = 8 -- tweak for precision vs cost
--- try to clip the line so that it fits in the camera
local function clip_segment_against_camera(out_a, out_b, p1, p2)
-- test both endpoints
tmp_world.x, tmp_world.y, tmp_world.z = p1[1], p1[2], p1[3]
djui_hud_world_pos_to_screen_pos(tmp_world, screen)
local ok1 = screen.z < -260
tmp_world.x, tmp_world.y, tmp_world.z = p2[1], p2[2], p2[3]
djui_hud_world_pos_to_screen_pos(tmp_world, screen)
local ok2 = screen.z < -260
-- trivial accept / reject
if ok1 and ok2 then
out_a[1], out_a[2], out_a[3] = p1[1], p1[2], p1[3]
out_b[1], out_b[2], out_b[3] = p2[1], p2[2], p2[3]
return true
end
if not ok1 and not ok2 then
return false
end
-- decide which end is inside, which is outside
local inside, outside
local p1Inside = ok1
if p1Inside then
inside, outside = p1, p2
else
inside, outside = p2, p1
end
-- seed the binary-search bracket
low_pt[1], low_pt[2], low_pt[3] = inside[1], inside[2], inside[3]
high_pt[1], high_pt[2], high_pt[3] = outside[1], outside[2], outside[3]
-- binary-search edge of the frustum in MAX_ITERS steps
for i = 1, MAX_ITERS do
mid_pt[1], mid_pt[2], mid_pt[3] =
low_pt[1] + (high_pt[1] - low_pt[1]) * 0.5,
low_pt[2] + (high_pt[2] - low_pt[2]) * 0.5,
low_pt[3] + (high_pt[3] - low_pt[3]) * 0.5
-- test mid_pt
tmp_world.x, tmp_world.y, tmp_world.z = mid_pt[1], mid_pt[2], mid_pt[3]
djui_hud_world_pos_to_screen_pos(tmp_world, screen)
if screen.z < -260 then
-- mid is still in front -> push low_pt up
low_pt[1], low_pt[2], low_pt[3] = mid_pt[1], mid_pt[2], mid_pt[3]
else
-- mid went behind -> pull high_pt down
high_pt[1], high_pt[2], high_pt[3] = mid_pt[1], mid_pt[2], mid_pt[3]
end
end
-- assign outputs in the original p1->p2 order
if p1Inside then
-- segment is p1 (inside) -> boundary near p2
out_a[1], out_a[2], out_a[3] = p1[1], p1[2], p1[3]
out_b[1], out_b[2], out_b[3] = low_pt[1], low_pt[2], low_pt[3]
else
-- segment is boundary near p1 -> p2 (inside)
out_a[1], out_a[2], out_a[3] = low_pt[1], low_pt[2], low_pt[3]
out_b[1], out_b[2], out_b[3] = p2[1], p2[2], p2[3]
end
return true
end
---------------------------------------------------------------------
-- RENDER: LINES
---------------------------------------------------------------------
local function render_debug_lines()
local cam = (gMarioStates[0].area or {}).camera
if not cam then return end
djui_hud_set_resolution(RESOLUTION_N64) -- use default pixel grid
for i = 1, _debug_lines.count do
local l = _debug_lines.lines[i]
local c = l.color
djui_hud_set_color(c[1]*255, c[2]*255, c[3]*255, c[4] and c[4]*255 or 255)
if not clip_segment_against_camera(tmp_out1, tmp_out2, l.p1, l.p2) then
goto continue
end
djui_hud_world_pos_to_screen_pos({ x = tmp_out1[1], y = tmp_out1[2], z = tmp_out1[3] }, screen)
local s1x, s1y = screen.x, screen.y
djui_hud_world_pos_to_screen_pos({ x = tmp_out2[1], y = tmp_out2[2], z = tmp_out2[3] }, screen)
local s2x, s2y = screen.x, screen.y
if s1x and s2x then
local dx, dy = s2x - s1x, s2y - s1y
local angle = atan2s(dy, dx) - 0x4000 -- rotate by -90 degree for Djui rects
local len = math.sqrt(dx * dx + dy * dy)
djui_hud_set_rotation(angle, 0, 0.5)
djui_hud_render_rect(s1x, s1y, len, l.thickness * 0.5)
end
::continue::
end
end
---------------------------------------------------------------------
-- RENDER: POINT ICONS
---------------------------------------------------------------------
local tmp_pos_in = { x = 0, y = 0, z = 0 }
local function render_debug_points()
djui_hud_set_resolution(RESOLUTION_N64) -- use default pixel grid
tmp_out1.x, tmp_out1.y, tmp_out1.z = 0, 0, 0
for i = 1, _debug_points.count do
local p = _debug_points.points[i]
tmp_pos_in.x, tmp_pos_in.y, tmp_pos_in.z = p.position[1], p.position[2], p.position[3]
djui_hud_world_pos_to_screen_pos(tmp_pos_in, tmp_out1)
if tmp_out1.z <= -260 then -- clip very near points
local c = p.color
local scale = p.scale * 2
local x, y = tmp_out1.x - scale * 0.5, tmp_out1.y - scale * 0.5
local padding = 0.3
djui_hud_set_color(0, 0, 0, c[4] and c[4]*200 or 200)
djui_hud_render_rect(x - padding, y - padding, scale + padding * 2, scale + padding * 2)
djui_hud_set_color(c[1] * 255, c[2] * 255, c[3] * 255, c[4] and c[4]*255 or 255)
djui_hud_render_rect(x, y, scale, scale)
end
end
end
---------------------------------------------------------------------
-- RENDER: TEXTS
---------------------------------------------------------------------
local function render_debug_texts()
djui_hud_set_resolution(RESOLUTION_N64) -- use default pixel grid
tmp_out1.x, tmp_out1.y, tmp_out1.z = 0, 0, 0
for i = 1, _debug_texts.count do
local t = _debug_texts.texts[i]
tmp_pos_in.x, tmp_pos_in.y, tmp_pos_in.z = t.position[1], t.position[2], t.position[3]
djui_hud_world_pos_to_screen_pos(tmp_pos_in, tmp_out1)
if tmp_out1.z <= -260 then -- clip very near points
local c = t.color
djui_hud_set_font(FONT_ALIASED)
djui_hud_set_color(0, 0, 0, c[4] and c[4] * 200 or 200)
djui_hud_print_text(t.string, tmp_out1.x + 0.4, tmp_out1.y + 0.4, t.scale * 0.3)
djui_hud_set_color(c[1] * 255, c[2] * 255, c[3] * 255, c[4] and c[4] * 255 or 255)
djui_hud_print_text(t.string, tmp_out1.x, tmp_out1.y, t.scale * 0.3)
end
end
end
---------------------------------------------------------------------
-- RENDER: DEBUG TEXT
---------------------------------------------------------------------
local function render_debug_text()
-- set text and scale
local text = Dbg.htext
local scale = 1
-- render to native screen space, with the MENU font
djui_hud_set_resolution(RESOLUTION_DJUI)
djui_hud_set_font(FONT_MENU)
-- get width of screen and text
local screen_width = djui_hud_get_screen_width()
local width = djui_hud_measure_text(text) * scale
-- get height of screen and text
local screen_height = djui_hud_get_screen_height()
local height = 64 * scale
-- set location
local x = screen_width - width - 100
local y = screen_height - height - 50
-- set color and render
djui_hud_set_color(255, 0, 255, 255)
djui_hud_print_text(text, x, y, scale)
end
---------------------------------------------------------------------
-- ENTRY HOOK
---------------------------------------------------------------------
local function on_debug_hud_render()
clear_check()
render_debug_points()
render_debug_lines()
render_debug_texts()
if Dbg.htext ~= nil then render_debug_text() end
end
hook_event(HOOK_ON_HUD_RENDER, on_debug_hud_render)
---------------------------------------------------------------------
-- UTILS
---------------------------------------------------------------------
local num_str = "number"
local function parse_vec3(dst, src)
local t = type(src)
if t ~= "table" and t ~= "userdata" then return nil end
-- parse { x = ..., y = ..., z = ... }
if src.x and src.y and src.z then
if type(src.x) == num_str and type(src.y) == num_str and type(src.z) == num_str then
dst[1], dst[2], dst[3] = src.x, src.y, src.z
else
return nil
end
-- parse { ..., ..., ... }
elseif #src == 3 and type(src[1]) == num_str and type(src[2]) == num_str and type(src[3]) == num_str then
dst[1], dst[2], dst[3] = src[1], src[2], src[3]
else
return nil
end
-- alter if we want to
if Dbg.alter_position_function then
Dbg.alter_position_function(dst)
end
return dst
end
---------------------------------------------------------------------
-- PUBLIC API
---------------------------------------------------------------------
function Dbg.point(pos, color, scale)
clear_check()
color = color or Dbg.colors.white
scale = scale or 1
-- increment / allocate
_debug_points.count = _debug_points.count + 1
if not _debug_points.points[_debug_points.count] then
_debug_points.points[_debug_points.count] = point_new()
end
local p = _debug_points.points[_debug_points.count]
-- parse vec
if not parse_vec3(p.position, pos) then
_debug_points.count = _debug_points.count - 1
return
end
p.color[1], p.color[2], p.color[3], p.color[4] = color[1], color[2], color[3], color[4] or 1
p.scale = scale
end
function Dbg.line(p1, p2, color, thickness)
clear_check()
color = color or Dbg.colors.white
thickness = thickness or 1
if not p1 or not p2 then return end
-- increment / allocate
_debug_lines.count = _debug_lines.count + 1
if not _debug_lines.lines[_debug_lines.count] then
_debug_lines.lines[_debug_lines.count] = line_new()
end
local l = _debug_lines.lines[_debug_lines.count]
-- parse vecs
if not parse_vec3(l.p1, p1) or not parse_vec3(l.p2, p2) then
_debug_lines.count = _debug_lines.count - 1
return
end
l.color[1], l.color[2], l.color[3], l.color[4] = color[1], color[2], color[3], color[4] or 1
l.thickness = thickness
end
function Dbg.text(string, pos, color, scale)
clear_check()
color = color or Dbg.colors.white
scale = scale or 1
-- increment / allocate
_debug_texts.count = _debug_texts.count + 1
if not _debug_texts.texts[_debug_texts.count] then
_debug_texts.texts[_debug_texts.count] = text_new()
end
local t = _debug_texts.texts[_debug_texts.count]
-- parse vec
if not parse_vec3(t.position, pos) then
_debug_texts.count = _debug_texts.count - 1
return
end
t.string = tostring(string)
t.color[1], t.color[2], t.color[3], t.color[4] = color[1], color[2], color[3], color[4] or 1
t.scale = scale
end
function Dbg.clear()
_debug_points.count = 0
_debug_lines.count = 0
_debug_texts.count = 0
end
return Dbg

View file

@ -0,0 +1,115 @@
-- name: Runtime Surface Examples
-- description: Shows how to create, move, and delete surfaces at runtime. Also shows how to load static object collision and draw debug visuals for surfaces.
local Dbg = require('/lib/dbg')
local moving_surface = nil
local function draw_surface(surface, color, thickness)
thickness = thickness or 0.5
Dbg.line(surface.vertex1, surface.vertex2, color, thickness)
Dbg.line(surface.vertex2, surface.vertex3, color, thickness)
Dbg.line(surface.vertex3, surface.vertex1, color, thickness)
end
local sSoc = nil
local sObject = nil
local id_bhvSOC = hook_behavior(nil, OBJ_LIST_SURFACE, false,
function (obj)
obj.collisionData = gGlobalObjectCollisionData.bbh_seg7_collision_coffin
sSoc = load_static_object_collision()
end,
function (obj)
if not sSoc then return end
for i = 1, sSoc.length do
local surf = get_static_object_surface(sSoc, i)
if surf then
draw_surface(surf, Dbg.colors.yellow)
end
end
end
)
hook_event(HOOK_UPDATE, function ()
local m = gMarioStates[0]
local pressed = m.controller.buttonPressed
-- D_JPAD: create a surface at Mario's position
if (pressed & D_JPAD) ~= 0 then
local v1 = { x = m.pos.x - 500, y = m.pos.y, z = m.pos.z - 500 }
local v2 = { x = m.pos.x + 500, y = m.pos.y, z = m.pos.z - 500 }
local v3 = { x = m.pos.x, y = m.pos.y, z = m.pos.z + 500 }
moving_surface = smlua_collision_add_surface(false, SURFACE_DEFAULT, v3, v2, v1)
end
-- U_JPAD: move the last created surface to Mario's position
if (pressed & U_JPAD) ~= 0 and moving_surface then
local v1 = { x = m.pos.x - 500, y = m.pos.y - 100, z = m.pos.z - 500 }
local v2 = { x = m.pos.x + 500, y = m.pos.y - 100, z = m.pos.z - 500 }
local v3 = { x = m.pos.x, y = m.pos.y - 100, z = m.pos.z + 500 }
smlua_collision_move_surface(moving_surface, v3, v2, v1)
end
-- L_JPAD: delete the floor
if (pressed & L_JPAD) ~= 0 and m.floor then
smlua_collision_delete_surface(m.floor)
end
-- R_JPAD: delete the wall
if (pressed & R_JPAD) ~= 0 and m.wall then
smlua_collision_delete_surface(m.wall)
end
-- X_BUTTON: load static object collision
if (pressed & X_BUTTON) ~= 0 then
if sSoc and not ~sSoc then
remove_static_object_collision(sSoc)
sSoc = nil
end
if sObject then
sObject.activeFlags = ACTIVE_FLAG_DEACTIVATED
sObject = nil
end
sObject = spawn_non_sync_object(id_bhvSOC, E_MODEL_BBH_WOODEN_TOMB, m.pos.x, m.pos.y, m.pos.z, nil)
end
-- draw debug visuals
if m.floor then
draw_surface(m.floor, Dbg.colors.blue, 1)
end
if m.wall then
draw_surface(m.wall, Dbg.colors.green, 1)
end
if moving_surface and not ~moving_surface then
draw_surface(moving_surface, Dbg.colors.red)
end
end)
hook_event(HOOK_ON_HUD_RENDER, function ()
djui_hud_set_resolution(RESOLUTION_N64)
djui_hud_set_font(FONT_NORMAL)
local scale = 0.25
local lineH = 32 * scale
local x = 16
local y = 32
local function row(label, desc)
djui_hud_set_color(0, 0, 0, 255)
djui_hud_print_text(label, x + 0.4, y + 0.4, scale)
djui_hud_set_color(255, 220, 60, 255)
djui_hud_print_text(label, x, y, scale)
local desc_x = x + djui_hud_measure_text(label) * scale + 8
djui_hud_set_color(0, 0, 0, 255)
djui_hud_print_text(desc, desc_x + 0.4, y + 0.4, scale)
djui_hud_set_color(255, 255, 255, 255)
djui_hud_print_text(desc, desc_x, y, scale)
y = y + lineH
end
row("D-pad Down: ", "create surface")
row("D-pad Up: ", moving_surface and not ~moving_surface and "move surface" or "move surface (none)")
row("D-pad Left: ", "delete floor")
row("D-pad Right:", "delete wall")
row("X: ", "reload static collision")
end)

View file

@ -7399,6 +7399,82 @@ Gets a table of the surface types from `data`
<br />
## [smlua_collision_add_surface](#smlua_collision_add_surface)
### Description
Allocates a new collision surface with the given vertices, computes the surface normal and other fields, and inserts it into the spatial partition. Returns the new surface, or `nil` if the triangle is degenerate (zero area). Set `dynamic` to `true` for surfaces that are cleared each frame, or `false` for persistent static surfaces
### Lua Example
`local surfaceValue = smlua_collision_add_surface(dynamic, surfaceType, vertex1, vertex2, vertex3)`
### Parameters
| Field | Type |
| ----- | ---- |
| dynamic | `boolean` |
| surfaceType | `integer` |
| vertex1 | [Vec3s](structs.md#Vec3s) |
| vertex2 | [Vec3s](structs.md#Vec3s) |
| vertex3 | [Vec3s](structs.md#Vec3s) |
### Returns
- [Surface](structs.md#Surface)
### C Prototype
`struct Surface* smlua_collision_add_surface(bool dynamic, s16 surfaceType, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3);`
[:arrow_up_small:](#)
<br />
## [smlua_collision_move_surface](#smlua_collision_move_surface)
### Description
Moves an existing collision surface to new vertex positions. Recalculates the surface normal, origin offset, and Y bounds, removes the surface from its old spatial partition cells, and re-adds it to the correct cells. The previous vertices are preserved for interpolation
### Lua Example
`smlua_collision_move_surface(surface, vertex1, vertex2, vertex3)`
### Parameters
| Field | Type |
| ----- | ---- |
| surface | [Surface](structs.md#Surface) |
| vertex1 | [Vec3s](structs.md#Vec3s) |
| vertex2 | [Vec3s](structs.md#Vec3s) |
| vertex3 | [Vec3s](structs.md#Vec3s) |
### Returns
- None
### C Prototype
`void smlua_collision_move_surface(struct Surface *surface, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3);`
[:arrow_up_small:](#)
<br />
## [smlua_collision_delete_surface](#smlua_collision_delete_surface)
### Description
Fully deletes a collision surface: removes it from the spatial partitions and frees its pool slot.
### Lua Example
`smlua_collision_delete_surface(surface)`
### Parameters
| Field | Type |
| ----- | ---- |
| surface | [Surface](structs.md#Surface) |
### Returns
- None
### C Prototype
`void smlua_collision_delete_surface(struct Surface *surface);`
[:arrow_up_small:](#)
<br />
## [surface_is_quicksand](#surface_is_quicksand)
### Description

View file

@ -5062,6 +5062,29 @@ Gets a surface corresponding to `index` from the static object collision
<br />
## [remove_static_object_collision](#remove_static_object_collision)
### Description
Removes all surfaces belonging to a static object collision and reclaims the SOC metadata
### Lua Example
`remove_static_object_collision(col)`
### Parameters
| Field | Type |
| ----- | ---- |
| col | [StaticObjectCollision](structs.md#StaticObjectCollision) |
### Returns
- None
### C Prototype
`void remove_static_object_collision(struct StaticObjectCollision *col);`
[:arrow_up_small:](#)
<br />
## [obj_get_surface_from_index](#obj_get_surface_from_index)
### Description

View file

@ -1915,6 +1915,9 @@
- [smlua_collision_util_get_current_terrain_collision](functions-6.md#smlua_collision_util_get_current_terrain_collision)
- [smlua_collision_util_get_level_collision](functions-6.md#smlua_collision_util_get_level_collision)
- [smlua_collision_util_find_surface_types](functions-6.md#smlua_collision_util_find_surface_types)
- [smlua_collision_add_surface](functions-6.md#smlua_collision_add_surface)
- [smlua_collision_move_surface](functions-6.md#smlua_collision_move_surface)
- [smlua_collision_delete_surface](functions-6.md#smlua_collision_delete_surface)
- [surface_is_quicksand](functions-6.md#surface_is_quicksand)
- [surface_is_not_hard](functions-6.md#surface_is_not_hard)
- [surface_is_painting_warp](functions-6.md#surface_is_painting_warp)
@ -2225,6 +2228,7 @@
- [load_static_object_collision](functions-7.md#load_static_object_collision)
- [toggle_static_object_collision](functions-7.md#toggle_static_object_collision)
- [get_static_object_surface](functions-7.md#get_static_object_surface)
- [remove_static_object_collision](functions-7.md#remove_static_object_collision)
- [obj_get_surface_from_index](functions-7.md#obj_get_surface_from_index)
- [surface_has_force](functions-7.md#surface_has_force)

View file

@ -2931,7 +2931,6 @@
| Field | Type | Access |
| ----- | ---- | ------ |
| index | `integer` | read-only |
| length | `integer` | read-only |
[:arrow_up_small:](#)
@ -2945,6 +2944,7 @@
| type | `integer` | |
| flags | `integer` | |
| room | `integer` | |
| poolType | `integer` | read-only |
| force | `integer` | |
| lowerY | `integer` | |
| upperY | `integer` | |
@ -2957,6 +2957,7 @@
| normal | [Vec3f](structs.md#Vec3f) | read-only |
| originOffset | `number` | |
| modifiedTimestamp | `integer` | |
| socId | `integer` | read-only |
| object | [Object](structs.md#Object) | |
[:arrow_up_small:](#)

View file

@ -282,10 +282,13 @@ struct Object
enum AreaTimerType areaTimerType;
Mat4 transform;
// Obsolete: pool index is no longer stable
u32 firstSurface;
// Tracks the number of dynamic surfaces currently owned by this object.
u32 numSurfaces;
u32 heldByPlayerIndex;
u8 setHome;
@ -344,14 +347,15 @@ struct Waypoint
struct Surface
{
// For optimization reasons, See MarioState
s16 type;
s8 flags;
s8 room;
s8 poolType;
s16 force;
s16 lowerY;
s16 upperY;
Vec3s vertex1;
Vec3s vertex2;
Vec3s vertex3;
@ -363,10 +367,11 @@ struct Surface
f32 y;
f32 z;
} normal;
f32 originOffset;
u32 modifiedTimestamp;
u32 socId;
struct Object *object;
};

View file

@ -62,8 +62,12 @@ s32 gNumSOCSurfaces;
/**
* Pools of data to contain either surface nodes or surfaces.
*/
static struct GrowingArray *sSurfaceNodePool = NULL;
static struct GrowingArray *sSurfacePool = NULL;
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.
@ -71,20 +75,53 @@ static struct GrowingArray *sSurfacePool = NULL;
static struct GrowingArray *sSOCPool = NULL;
/**
* Allocate the part of the surface node pool to contain a surface node.
* Counter for assigning unique SOC IDs.
*/
static struct SurfaceNode *alloc_surface_node(void) {
sSurfaceNodePool->count = gSurfaceNodesAllocated++;
return growing_array_alloc(sSurfaceNodePool, sizeof(struct SurfaceNode));
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 the part of the surface pool to contain a surface and
* initialize the surface.
* Allocate a surface node from the appropriate pool.
*/
static struct Surface *alloc_surface(void) {
sSurfacePool->count = gSurfacesAllocated++;
return growing_array_alloc(sSurfacePool, sizeof(struct Surface));
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) {
@ -111,18 +148,39 @@ static void clear_spatial_partition(SpatialPartitionCell *cells) {
*/
static void clear_static_surfaces(void) {
clear_spatial_partition(&gStaticSurfacePartition[0][0]);
sSOCPool = growing_array_init(sSOCPool, 0x100, malloc, smlua_free_soc);
// 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 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();
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;
@ -159,7 +217,7 @@ static void add_surface_to_cell(s16 dynamic, s16 cellX, s16 cellZ, struct Surfac
newNode->surface = surface;
if (dynamic) {
if (surface->poolType == SURFACE_POOL_DYNAMIC) {
list = &gDynamicSurfacePartition[cellZ][cellX][listIndex];
} else {
list = &gStaticSurfacePartition[cellZ][cellX][listIndex];
@ -260,10 +318,9 @@ static s16 upper_cell_index(s32 coord) {
* 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
* @param surface The surface to add
*/
static void add_surface(struct Surface *surface, s32 dynamic) {
void add_surface(struct Surface *surface) {
// minY/maxY maybe? s32 instead of s16, though.
s16 minX, minZ, maxX, maxZ;
@ -271,7 +328,9 @@ static void add_surface(struct Surface *surface, s32 dynamic) {
s16 cellZ, cellX;
smlua_call_event_hooks(HOOK_ON_ADD_SURFACE, surface, dynamic);
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]);
@ -285,18 +344,150 @@ static void add_surface(struct Surface *surface, s32 dynamic) {
for (cellZ = minCellZ; cellZ <= maxCellZ; cellZ++) {
for (cellX = minCellX; cellX <= maxCellX; cellX++) {
add_surface_to_cell(dynamic, cellX, cellZ, surface);
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) {
static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices, s32 poolType) {
if (vertexData == NULL || vertexIndices == NULL || *vertexIndices == NULL) { return NULL; }
struct Surface *surface;
@ -356,7 +547,7 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) {
ny *= mag;
nz *= mag;
surface = alloc_surface();
surface = alloc_surface(poolType);
if (surface == NULL) { return NULL; }
vec3s_copy(surface->prevVertex1, surface->vertex1);
@ -435,7 +626,7 @@ static void load_static_surfaces(s16 **data, s16 *vertexData, s16 surfaceType, s
*surfaceRooms += 1;
}
surface = read_surface_data(vertexData, data);
surface = read_surface_data(vertexData, data, SURFACE_POOL_STATIC);
if (surface != NULL) {
surface->room = room;
surface->type = surfaceType;
@ -447,7 +638,7 @@ static void load_static_surfaces(s16 **data, s16 *vertexData, s16 surfaceType, s
surface->force = 0;
}
add_surface(surface, FALSE);
add_surface(surface);
}
*data += 3;
@ -513,8 +704,14 @@ void alloc_surface_pools(void) {
clear_static_surfaces();
clear_dynamic_surfaces();
sSurfaceNodePool = growing_array_init(sSurfaceNodePool, 0x1000, malloc, free);
sSurfacePool = growing_array_init(sSurfacePool, 0x400, malloc, smlua_free_surface);
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;
@ -638,17 +835,32 @@ void load_area_terrain(s16 index, s16 *data, s8 *surfaceRooms, s16 *macroObjects
* If not in time stop, clear the surface partitions.
*/
void clear_dynamic_surfaces(void) {
if (!(gTimeStopState & TIME_STOP_ACTIVE)) {
gSurfacesAllocated = gNumStaticSurfaces + gNumSOCSurfaces;
gSurfaceNodesAllocated = gNumStaticSurfaceNodes + gNumSOCSurfaceNodes;
if (gTimeStopState & TIME_STOP_ACTIVE) { return; }
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;
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;
}
}
@ -714,7 +926,9 @@ void load_object_surfaces(s16** data, s16* vertexData, bool isSOC) {
hasForce = surface_has_force(surfaceType);
flags = surf_has_no_cam_collision(surfaceType) ? SURFACE_FLAG_NO_CAM_COLLISION : 0;
flags |= SURFACE_FLAG_DYNAMIC;
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.
@ -725,19 +939,10 @@ void load_object_surfaces(s16** data, s16* vertexData, bool isSOC) {
}
for (i = 0; i < numSurfaces; i++) {
struct Surface* surface = read_surface_data(vertexData, data);
s32 poolType = isSOC ? SURFACE_POOL_SOC : SURFACE_POOL_DYNAMIC;
struct Surface* surface = read_surface_data(vertexData, data, poolType);
if (surface != NULL) {
if (!isSOC) {
// Set index of first surface
if (gCurrentObject->firstSurface == 0) {
gCurrentObject->firstSurface = gSurfacesAllocated - 1;
}
// Increase surface count
gCurrentObject->numSurfaces++;
}
surface->object = gCurrentObject;
surface->type = surfaceType;
@ -749,7 +954,11 @@ void load_object_surfaces(s16** data, s16* vertexData, bool isSOC) {
surface->flags |= flags;
surface->room = (s8)room;
add_surface(surface, !isSOC);
add_surface(surface);
if (!isSOC) {
gCurrentObject->numSurfaces++;
}
}
if (hasForce) {
@ -852,36 +1061,38 @@ void load_object_collision_model(void) {
}
struct StaticObjectCollision *load_static_object_collision() {
struct StaticObjectCollision *col;
u32 lastSurfaceIndex = gSurfacesAllocated;
u32 lastSurfaceNodeIndex = gSurfaceNodesAllocated;
u32 lastSOCSurfaceIndex = gNumStaticSurfaces + gNumSOCSurfaces;
u32 lastSOCSurfaceNodeIndex = gNumStaticSurfaceNodes + gNumSOCSurfaceNodes;
u32 startCount = sSurfaceSOCPool->count;
u32 startNodeCount = sSurfaceSOCNodePool->count;
load_object_collision_model_internal(true);
// Reorder surfaces and nodes and update SOC variables
u32 addedSurfaces = gSurfacesAllocated - lastSurfaceIndex;
u32 addedSurfaceNodes = gSurfaceNodesAllocated - lastSurfaceNodeIndex;
if (addedSurfaces > 0) {
growing_array_move(sSurfacePool, lastSurfaceIndex, lastSOCSurfaceIndex, addedSurfaces);
gNumSOCSurfaces += addedSurfaces;
}
if (addedSurfaceNodes > 0) {
growing_array_move(sSurfaceNodePool, lastSurfaceNodeIndex, lastSOCSurfaceNodeIndex, addedSurfaceNodes);
gNumSOCSurfaceNodes += addedSurfaceNodes;
}
u32 addedSurfaces = sSurfaceSOCPool->count - startCount;
u32 addedNodes = sSurfaceSOCNodePool->count - startNodeCount;
col = alloc_static_object_collision();
col->index = lastSOCSurfaceIndex;
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) {
for (s32 i = 0; i < col->length; i++) {
struct Surface *surf = sSurfacePool->buffer[col->index + i];
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 {
@ -891,15 +1102,56 @@ void toggle_static_object_collision(struct StaticObjectCollision *col, bool tang
}
struct Surface *get_static_object_surface(struct StaticObjectCollision *col, u32 index) {
if (!col) { return NULL; }
if (index >= col->length) { return NULL; }
struct Surface *surf = sSurfacePool->buffer[col->index + index];
return surf;
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 || o->firstSurface == 0) { return NULL; }
if (index >= o->numSurfaces) { return NULL; }
struct Surface *surf = sSurfacePool->buffer[o->firstSurface + index];
return surf;
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;
}

View file

@ -34,7 +34,17 @@ extern s32 gNumStaticSurfaces;
extern s32 gNumSOCSurfaceNodes;
extern s32 gNumSOCSurfaces;
#define SURFACE_POOL_STATIC 0
#define SURFACE_POOL_DYNAMIC 1
#define SURFACE_POOL_SOC 2
void alloc_surface_pools(void);
struct Surface *alloc_surface(s32 poolType);
void add_surface(struct Surface *surface);
void add_surface_without_hook(struct Surface *surface);
void remove_surface_from_partition(struct Surface *surface);
void delete_surface(struct Surface *surface);
bool swap_and_pop_surface_pool(s32 poolType, struct Surface *surface);
u32 get_area_terrain_size(s16 *data);
@ -54,6 +64,8 @@ struct StaticObjectCollision *load_static_object_collision();
void toggle_static_object_collision(struct StaticObjectCollision *col, bool tangible);
/* |description|Gets a surface corresponding to `index` from the static object collision|descriptionEnd| */
struct Surface *get_static_object_surface(struct StaticObjectCollision *col, u32 index);
/* |description|Removes all surfaces belonging to a static object collision and reclaims the SOC metadata|descriptionEnd| */
void remove_static_object_collision(struct StaticObjectCollision *col);
/* |description|Gets a surface corresponding to `index` from the surface pool buffer|descriptionEnd| */
struct Surface *obj_get_surface_from_index(struct Object *o, u32 index);
/* |description|Checks if a surface has force|descriptionEnd| */

View file

@ -246,6 +246,34 @@ void growing_array_move(struct GrowingArray *array, u32 from, u32 to, u32 count)
}
}
/**
* Swap-and-pop the entry at position `index` out of the array.
* The slot is filled by the last live entry, and the count is decremented.
* Returns true if the index was valid.
*/
bool growing_array_swap_and_pop_index(struct GrowingArray *array, u32 index) {
if (!array || index >= array->count) { return false; }
array->count--;
void *tmp = array->buffer[index];
array->buffer[index] = array->buffer[array->count];
array->buffer[array->count] = tmp;
return true;
}
/**
* Swap-and-pop the first slot whose pointer equals `ptr`.
* Returns true if the entry was found and removed.
*/
bool growing_array_swap_and_pop(struct GrowingArray *array, void *ptr) {
if (!array) { return false; }
for (u32 i = 0; i < array->count; i++) {
if (array->buffer[i] == ptr) {
return growing_array_swap_and_pop_index(array, i);
}
}
return false;
}
void growing_array_free(struct GrowingArray **array) {
if (*array) {
for (u32 i = 0; i != (*array)->capacity; ++i) {

View file

@ -78,6 +78,8 @@ void growing_pool_free_pool(struct GrowingPool *pool);
struct GrowingArray *growing_array_init(struct GrowingArray *array, u32 capacity, GrowingArrayAllocFunc alloc, GrowingArrayFreeFunc free);
void *growing_array_alloc(struct GrowingArray *array, u32 size);
void growing_array_move(struct GrowingArray *array, u32 from, u32 to, u32 count);
bool growing_array_swap_and_pop_index(struct GrowingArray *array, u32 index);
bool growing_array_swap_and_pop(struct GrowingArray *array, void *ptr);
void growing_array_free(struct GrowingArray **array);
void growing_array_debug_print(struct GrowingArray *array, const char *name, s32 x, s32 y);

View file

@ -2576,7 +2576,7 @@ static struct LuaObjectField sStaticObjectCollisionFields[LUA_STATIC_OBJECT_COLL
{ "length", LVT_U16, offsetof(struct StaticObjectCollision, length), true, LOT_NONE, 1, sizeof(u16) },
};
#define LUA_SURFACE_FIELD_COUNT 16
#define LUA_SURFACE_FIELD_COUNT 18
static struct LuaObjectField sSurfaceFields[LUA_SURFACE_FIELD_COUNT] = {
{ "flags", LVT_S8, offsetof(struct Surface, flags), false, LOT_NONE, 1, sizeof(s8) },
{ "force", LVT_S16, offsetof(struct Surface, force), false, LOT_NONE, 1, sizeof(s16) },
@ -2585,10 +2585,12 @@ static struct LuaObjectField sSurfaceFields[LUA_SURFACE_FIELD_COUNT] = {
{ "normal", LVT_COBJECT, offsetof(struct Surface, normal), true, LOT_VEC3F, 1, sizeof(Vec3f) },
{ "object", LVT_COBJECT_P, offsetof(struct Surface, object), false, LOT_OBJECT, 1, sizeof(struct Object*) },
{ "originOffset", LVT_F32, offsetof(struct Surface, originOffset), false, LOT_NONE, 1, sizeof(f32) },
{ "poolType", LVT_S8, offsetof(struct Surface, poolType), true, LOT_NONE, 1, sizeof(s8) },
{ "prevVertex1", LVT_COBJECT, offsetof(struct Surface, prevVertex1), true, LOT_VEC3S, 1, sizeof(Vec3s) },
{ "prevVertex2", LVT_COBJECT, offsetof(struct Surface, prevVertex2), true, LOT_VEC3S, 1, sizeof(Vec3s) },
{ "prevVertex3", LVT_COBJECT, offsetof(struct Surface, prevVertex3), true, LOT_VEC3S, 1, sizeof(Vec3s) },
{ "room", LVT_S8, offsetof(struct Surface, room), false, LOT_NONE, 1, sizeof(s8) },
{ "socId", LVT_U32, offsetof(struct Surface, socId), true, LOT_NONE, 1, sizeof(u32) },
{ "type", LVT_S16, offsetof(struct Surface, type), false, LOT_NONE, 1, sizeof(s16) },
{ "upperY", LVT_S16, offsetof(struct Surface, upperY), false, LOT_NONE, 1, sizeof(s16) },
{ "vertex1", LVT_COBJECT, offsetof(struct Surface, vertex1), true, LOT_VEC3S, 1, sizeof(Vec3s) },

View file

@ -4459,6 +4459,9 @@ char gSmluaConstants[] = ""
"SOUND_OBJ2_MONTY_MOLE_APPEAR=SOUND_ARG_LOAD(SOUND_BANK_OBJ2, 0x67, 0x80, SOUND_DISCRETE)\n"
"SOUND_OBJ2_BOSS_DIALOG_GRUNT=SOUND_ARG_LOAD(SOUND_BANK_OBJ2, 0x69, 0x40, SOUND_DISCRETE)\n"
"SOUND_OBJ2_MRI_SPINNING=SOUND_ARG_LOAD(SOUND_BANK_OBJ2, 0x6B, 0x00, SOUND_DISCRETE)\n"
"SURFACE_POOL_STATIC=0\n"
"SURFACE_POOL_DYNAMIC=1\n"
"SURFACE_POOL_SOC=2\n"
"SURFACE_DEFAULT=0x0000\n"
"SURFACE_BURNING=0x0001\n"
"SURFACE_RAYCAST=0x0003\n"

View file

@ -31747,6 +31747,83 @@ int smlua_func_smlua_collision_util_find_surface_types(lua_State* L) {
return 1;
}
int smlua_func_smlua_collision_add_surface(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 5) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "smlua_collision_add_surface", 5, top);
return 0;
}
bool dynamic = smlua_to_boolean(L, 1);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "smlua_collision_add_surface"); return 0; }
s16 surfaceType = smlua_to_integer(L, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "smlua_collision_add_surface"); return 0; }
Vec3s vertex1;
smlua_get_vec3s(vertex1, 3);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "smlua_collision_add_surface"); return 0; }
Vec3s vertex2;
smlua_get_vec3s(vertex2, 4);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "smlua_collision_add_surface"); return 0; }
Vec3s vertex3;
smlua_get_vec3s(vertex3, 5);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 5, "smlua_collision_add_surface"); return 0; }
smlua_push_object(L, LOT_SURFACE, smlua_collision_add_surface(dynamic, surfaceType, vertex1, vertex2, vertex3), NULL);
return 1;
}
int smlua_func_smlua_collision_move_surface(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 4) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "smlua_collision_move_surface", 4, top);
return 0;
}
struct Surface* surface = (struct Surface*)smlua_to_cobject(L, 1, LOT_SURFACE);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "smlua_collision_move_surface"); return 0; }
Vec3s vertex1;
smlua_get_vec3s(vertex1, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "smlua_collision_move_surface"); return 0; }
Vec3s vertex2;
smlua_get_vec3s(vertex2, 3);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "smlua_collision_move_surface"); return 0; }
Vec3s vertex3;
smlua_get_vec3s(vertex3, 4);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "smlua_collision_move_surface"); return 0; }
smlua_collision_move_surface(surface, vertex1, vertex2, vertex3);
return 1;
}
int smlua_func_smlua_collision_delete_surface(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "smlua_collision_delete_surface", 1, top);
return 0;
}
struct Surface* surface = (struct Surface*)smlua_to_cobject(L, 1, LOT_SURFACE);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "smlua_collision_delete_surface"); return 0; }
smlua_collision_delete_surface(surface);
return 1;
}
int smlua_func_surface_is_quicksand(lua_State* L) {
if (L == NULL) { return 0; }
@ -36678,6 +36755,23 @@ int smlua_func_get_static_object_surface(lua_State* L) {
return 1;
}
int smlua_func_remove_static_object_collision(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "remove_static_object_collision", 1, top);
return 0;
}
struct StaticObjectCollision* col = (struct StaticObjectCollision*)smlua_to_cobject(L, 1, LOT_STATICOBJECTCOLLISION);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "remove_static_object_collision"); return 0; }
remove_static_object_collision(col);
return 1;
}
int smlua_func_obj_get_surface_from_index(lua_State* L) {
if (L == NULL) { return 0; }
@ -38568,6 +38662,9 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "smlua_collision_util_get_current_terrain_collision", smlua_func_smlua_collision_util_get_current_terrain_collision);
smlua_bind_function(L, "smlua_collision_util_get_level_collision", smlua_func_smlua_collision_util_get_level_collision);
smlua_bind_function(L, "smlua_collision_util_find_surface_types", smlua_func_smlua_collision_util_find_surface_types);
smlua_bind_function(L, "smlua_collision_add_surface", smlua_func_smlua_collision_add_surface);
smlua_bind_function(L, "smlua_collision_move_surface", smlua_func_smlua_collision_move_surface);
smlua_bind_function(L, "smlua_collision_delete_surface", smlua_func_smlua_collision_delete_surface);
smlua_bind_function(L, "surface_is_quicksand", smlua_func_surface_is_quicksand);
smlua_bind_function(L, "surface_is_not_hard", smlua_func_surface_is_not_hard);
smlua_bind_function(L, "surface_is_painting_warp", smlua_func_surface_is_painting_warp);
@ -38864,6 +38961,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "load_static_object_collision", smlua_func_load_static_object_collision);
smlua_bind_function(L, "toggle_static_object_collision", smlua_func_toggle_static_object_collision);
smlua_bind_function(L, "get_static_object_surface", smlua_func_get_static_object_surface);
smlua_bind_function(L, "remove_static_object_collision", smlua_func_remove_static_object_collision);
smlua_bind_function(L, "obj_get_surface_from_index", smlua_func_obj_get_surface_from_index);
smlua_bind_function(L, "surface_has_force", smlua_func_surface_has_force);

View file

@ -869,7 +869,7 @@ void smlua_logline(void) {
}
}
void smlua_free(void *ptr, u16 lot) {
static void smlua_cobject_invalidate_internal(void *ptr, u16 lot) {
if (ptr && gLuaState) {
lua_State *L = gLuaState;
LUA_STACK_CHECK_BEGIN(L);
@ -890,5 +890,13 @@ void smlua_free(void *ptr, u16 lot) {
lua_pop(L, 1);
LUA_STACK_CHECK_END(L);
}
}
void smlua_free(void *ptr, u16 lot) {
smlua_cobject_invalidate_internal(ptr, lot);
free(ptr);
}
void smlua_cobject_invalidate(void *ptr, u16 lot) {
smlua_cobject_invalidate_internal(ptr, lot);
}

View file

@ -67,10 +67,16 @@ void smlua_dump_stack(void);
void smlua_dump_globals(void);
void smlua_dump_table(int index);
void smlua_free(void *ptr, u16 lot);
void smlua_cobject_invalidate(void *ptr, u16 lot);
#define smlua_free_lot(name, lot) \
static inline void smlua_free_##name(void *ptr) { smlua_free(ptr, lot); }
#define smlua_invalidate_lot(name, lot) \
static inline void smlua_invalidate_##name(void *ptr) { smlua_cobject_invalidate(ptr, lot); }
smlua_invalidate_lot(surface, LOT_SURFACE);
smlua_free_lot(surface, LOT_SURFACE);
smlua_free_lot(soc, LOT_STATICOBJECTCOLLISION);

View file

@ -1,10 +1,12 @@
#include "types.h"
#include "engine/math_util.h"
#include "engine/surface_collision.h"
#include "include/surface_terrains.h"
#include "game/mario_step.h"
#include "game/area.h"
#include "engine/surface_load.h"
#include "game/game_init.h"
#include "pc/lua/smlua.h"
#include "smlua_collision_utils.h"
@ -238,6 +240,150 @@ void smlua_collision_util_find_surface_types(Collision* data) {
lua_pushnil(L);
}
/**
* Compute the normal, plane equation, and Y bounds for a triangle.
* Only writes to the surface on success. Returns false if the triangle
* is degenerate, leaving the surface completely untouched.
*/
static bool calc_surface_fields(struct Surface *surface, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
s32 x1 = vertex1[0], y1 = vertex1[1], z1 = vertex1[2];
s32 x2 = vertex2[0], y2 = vertex2[1], z2 = vertex2[2];
s32 x3 = vertex3[0], y3 = vertex3[1], z3 = vertex3[2];
// Compute normal via cross product: (v2 - v1) x (v3 - v2)
f32 nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2);
f32 ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2);
f32 nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2);
f32 mag = sqrtf(nx * nx + ny * ny + nz * nz);
// Reject degenerate triangles without touching the surface
if (mag < 0.0001f) { return false; }
mag = 1.0f / mag;
// All checks passed; commit fields to the surface
surface->normal.x = nx * mag;
surface->normal.y = ny * mag;
surface->normal.z = nz * mag;
surface->originOffset = -(surface->normal.x * x1 + surface->normal.y * y1 + surface->normal.z * z1);
surface->lowerY = MIN(MIN(y1, y2), y3) - 5;
surface->upperY = MAX(MAX(y1, y2), y3) + 5;
return true;
}
struct Surface* smlua_collision_add_surface(bool dynamic, s16 surfaceType, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
s32 poolType = dynamic ? SURFACE_POOL_DYNAMIC : SURFACE_POOL_STATIC;
// Allocate surface from the appropriate pool
struct Surface *surface = alloc_surface(poolType);
if (surface == NULL) { return NULL; }
// Compute normal, plane equation, and Y bounds
if (!calc_surface_fields(surface, vertex1, vertex2, vertex3)) {
// Degenerate triangle: reclaim the pool slot that alloc_surface reserved.
// Cannot use delete_surface() here because the surface was never added
// to a partition or counter, so delete_surface's counter decrements
// would cause drift.
smlua_invalidate_surface(surface);
swap_and_pop_surface_pool(poolType, surface);
gSurfacesAllocated--;
return NULL;
}
// Set vertices (prevVertex = vertex for first frame)
vec3s_copy(surface->vertex1, vertex1);
vec3s_copy(surface->vertex2, vertex2);
vec3s_copy(surface->vertex3, vertex3);
vec3s_copy(surface->prevVertex1, vertex1);
vec3s_copy(surface->prevVertex2, vertex2);
vec3s_copy(surface->prevVertex3, vertex3);
surface->modifiedTimestamp = gGlobalTimer;
// Set surface properties
surface->type = surfaceType;
surface->force = 0;
surface->flags = dynamic ? SURFACE_FLAG_DYNAMIC : 0;
surface->room = 0;
surface->object = NULL;
// Snapshot node count before add_surface allocates nodes
s32 nodesBefore = gSurfaceNodesAllocated;
// Add to spatial partition
add_surface(surface);
s32 nodesAdded = gSurfaceNodesAllocated - nodesBefore;
// Update surface/node counters to stay in sync with load_area_terrain
if (poolType == SURFACE_POOL_STATIC) {
gNumStaticSurfaces++;
gNumStaticSurfaceNodes += nodesAdded;
}
return surface;
}
void smlua_collision_move_surface(struct Surface *surface, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
if (surface == NULL) { return; }
s32 poolType = surface->poolType;
// Snapshot node count before removal
s32 nodesBefore = gSurfaceNodesAllocated;
// Remove from old spatial partition cells
remove_surface_from_partition(surface);
// Compute normal, plane equation, and Y bounds (surface is untouched on failure)
if (!calc_surface_fields(surface, vertex1, vertex2, vertex3)) {
// Degenerate triangle: re-add at old position without firing the hook
add_surface_without_hook(surface);
// Fix node counter drift from the remove + re-add round-trip
s32 nodeDelta = gSurfaceNodesAllocated - nodesBefore;
if (poolType == SURFACE_POOL_STATIC) {
gNumStaticSurfaceNodes += nodeDelta;
} else if (poolType == SURFACE_POOL_SOC) {
gNumSOCSurfaceNodes += nodeDelta;
}
return;
}
// Update previous vertices for interpolation
vec3s_copy(surface->prevVertex1, surface->vertex1);
vec3s_copy(surface->prevVertex2, surface->vertex2);
vec3s_copy(surface->prevVertex3, surface->vertex3);
// Set new vertices
vec3s_copy(surface->vertex1, vertex1);
vec3s_copy(surface->vertex2, vertex2);
vec3s_copy(surface->vertex3, vertex3);
surface->modifiedTimestamp = gGlobalTimer;
// Clear X_PROJECTION flag so add_surface can re-evaluate it
surface->flags &= ~SURFACE_FLAG_X_PROJECTION;
// Re-add to spatial partition without firing the hook (this is a move, not a new insertion)
add_surface_without_hook(surface);
// Update gNum*SurfaceNodes to account for the net change;
// cell range may differ after a move, so node count can change.
s32 nodeDelta = gSurfaceNodesAllocated - nodesBefore;
if (poolType == SURFACE_POOL_STATIC) {
gNumStaticSurfaceNodes += nodeDelta;
} else if (poolType == SURFACE_POOL_SOC) {
gNumSOCSurfaceNodes += nodeDelta;
}
// Dynamic nodes are reset each frame, no tracking needed
}
void smlua_collision_delete_surface(struct Surface *surface) {
if (surface == NULL) { return; }
delete_surface(surface);
}
bool surface_is_quicksand(struct Surface* surf) {
if (surf == NULL) { return FALSE; }
return SURFACE_IS_QUICKSAND(surf->type);

View file

@ -142,6 +142,23 @@ Collision *smlua_collision_util_get_level_collision(u32 level, u16 area);
/* |description|Gets a table of the surface types from `data`|descriptionEnd| */
void smlua_collision_util_find_surface_types(Collision* data);
/* |description|
Allocates a new collision surface with the given vertices, computes the surface normal and other fields, and inserts it into the spatial partition.
Returns the new surface, or `nil` if the triangle is degenerate (zero area).
Set `dynamic` to `true` for surfaces that are cleared each frame, or `false` for persistent static surfaces
|descriptionEnd| */
struct Surface* smlua_collision_add_surface(bool dynamic, s16 surfaceType, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3);
/* |description|
Moves an existing collision surface to new vertex positions.
Recalculates the surface normal, origin offset, and Y bounds, removes the surface from its old spatial partition cells, and re-adds it to the correct cells.
The previous vertices are preserved for interpolation
|descriptionEnd| */
void smlua_collision_move_surface(struct Surface *surface, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3);
/* |description|Fully deletes a collision surface: removes it from the spatial partitions and frees its pool slot.|descriptionEnd| */
void smlua_collision_delete_surface(struct Surface *surface);
/* |description|Checks if the surface is quicksand|descriptionEnd| */
bool surface_is_quicksand(struct Surface* surf);
/* |description|Checks if the surface is not a hard surface|descriptionEnd| */