mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-05-03 07:21:54 +00:00
Merge 7a50970069 into 7604ef9297
This commit is contained in:
commit
20005f5e36
25 changed files with 1363 additions and 87 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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_.*" ],
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
421
docs/lua/examples/runtime-surface-examples/lib/dbg.lua
Normal file
421
docs/lua/examples/runtime-surface-examples/lib/dbg.lua
Normal 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
|
||||
115
docs/lua/examples/runtime-surface-examples/main.lua
Normal file
115
docs/lua/examples/runtime-surface-examples/main.lua
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:](#)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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| */
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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| */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue