Compound shapes support, better debug ui

This commit is contained in:
sergeypdev 2025-06-16 01:19:31 +04:00
parent c83e6831ea
commit 8378b943cb
15 changed files with 1359 additions and 277 deletions

4
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"type": "lldb", "type": "lldb",
"request": "attach", "request": "attach",
"name": "Attach Hot Reload (Linux/Max)", "name": "Attach Hot Reload (Linux/Max)",
"program": "${workspaceFolder}/game_hot_reload.bin" "program": "${workspaceFolder}/bin/hotreload/game.bin"
}, },
// Windows configs (only difference from linux/mac is "type" and "program") // Windows configs (only difference from linux/mac is "type" and "program")
{ {
@ -53,7 +53,7 @@
"name": "Run Hot Reload (Linux / Mac)", "name": "Run Hot Reload (Linux / Mac)",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/build/hotreload/game.bin", "program": "${workspaceFolder}/bin/hotreload/game.bin",
}, },
{ {
"type": "lldb", "type": "lldb",

View File

@ -238,7 +238,7 @@ main :: proc() {
opts := Options { opts := Options {
tracy = true, tracy = true,
debug = true, debug = true,
optimize = true, optimize = false,
run = true, run = true,
} }
flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator) flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator)
@ -352,7 +352,7 @@ main :: proc() {
"-sWASM_BIGINT", "-sWASM_BIGINT",
"-sWARN_ON_UNDEFINED_SYMBOLS=0", "-sWARN_ON_UNDEFINED_SYMBOLS=0",
"-sALLOW_MEMORY_GROWTH", "-sALLOW_MEMORY_GROWTH",
"-sASSERTIONS", "-sASSERTIONS=2",
"--shell-file", "--shell-file",
"main_web/index_template.html", "main_web/index_template.html",
"--preload-file", "--preload-file",

View File

@ -27,6 +27,7 @@ Loaded_Convex :: struct {
mesh: collision.Convex, mesh: collision.Convex,
center_of_mass: rl.Vector3, center_of_mass: rl.Vector3,
inertia_tensor: lg.Matrix3f32, inertia_tensor: lg.Matrix3f32,
total_volume: f32,
} }
Loaded_Curve_2D :: struct { Loaded_Curve_2D :: struct {
@ -670,9 +671,14 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
} }
} }
} }
inertia_tensor = inertia_tensor * lg.Matrix3f32(1.0 / total_volume) inertia_tensor = inertia_tensor
return {mesh = mesh, center_of_mass = center_of_mass, inertia_tensor = inertia_tensor} return {
mesh = mesh,
center_of_mass = center_of_mass,
inertia_tensor = inertia_tensor,
total_volume = total_volume,
}
} }
// TODO: move convex stuff out of assets.odin // TODO: move convex stuff out of assets.odin

View File

@ -27,6 +27,7 @@ import "game:render"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
import "libs:tracy" import "libs:tracy"
import "name"
import "ui" import "ui"
PIXEL_WINDOW_HEIGHT :: 360 PIXEL_WINDOW_HEIGHT :: 360
@ -122,6 +123,7 @@ SOLVER_CONFIG :: physics.Solver_Config {
} }
Game_Memory :: struct { Game_Memory :: struct {
name_container: name.Container,
assetman: assets.Asset_Manager, assetman: assets.Asset_Manager,
runtime_world: Runtime_World, runtime_world: Runtime_World,
es: Editor_State, es: Editor_State,
@ -369,9 +371,15 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
#hash("floor", "fnv32a"), #hash("floor", "fnv32a"),
physics.Body_Config { physics.Body_Config {
name = name.from_string("Floor"),
initial_pos = {0, -0.5, 0}, initial_pos = {0, -0.5, 0},
initial_rot = linalg.QUATERNIONF32_IDENTITY, initial_rot = linalg.QUATERNIONF32_IDENTITY,
shape = physics.Shape_Box{size = {100, 1, 100}}, shapes = {
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
inner_shape = physics.Shape_Box{size = {100, 1, 100}},
},
},
}, },
) )
@ -379,9 +387,15 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
#hash("ramp", "fnv32a"), #hash("ramp", "fnv32a"),
physics.Body_Config { physics.Body_Config {
name = name.from_string("Ramp"),
initial_pos = {0, 0, 0}, initial_pos = {0, 0, 0},
initial_rot = linalg.quaternion_from_euler_angle_x_f32(-10 * math.RAD_PER_DEG), initial_rot = linalg.quaternion_from_euler_angle_x_f32(-10 * math.RAD_PER_DEG),
shape = physics.Shape_Box{size = {5, 1, 100}}, shapes = {
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
inner_shape = physics.Shape_Box{size = {5, 1, 100}},
},
},
}, },
) )
} }
@ -392,6 +406,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
#hash("car", "fnv32a"), #hash("car", "fnv32a"),
physics.Body_Config { physics.Body_Config {
name = name.from_string("Car"),
initial_pos = {0, 4, -10}, initial_pos = {0, 4, -10},
initial_rot = linalg.QUATERNIONF32_IDENTITY, initial_rot = linalg.QUATERNIONF32_IDENTITY,
// initial_rot = linalg.quaternion_angle_axis( // initial_rot = linalg.quaternion_angle_axis(
@ -400,10 +415,22 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
// ) * // ) *
// linalg.quaternion_angle_axis(math.RAD_PER_DEG * 30, rl.Vector3{1, 0, 0}), // linalg.quaternion_angle_axis(math.RAD_PER_DEG * 30, rl.Vector3{1, 0, 0}),
initial_ang_vel = {0, 0, 0}, initial_ang_vel = {0, 0, 0},
shape = physics.Shape_Convex { shapes = {
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
inner_shape = physics.Shape_Convex {
mesh = car_convex.mesh, mesh = car_convex.mesh,
center_of_mass = car_convex.center_of_mass, center_of_mass = car_convex.center_of_mass,
inertia_tensor = auto_cast car_convex.inertia_tensor, inertia_tensor = auto_cast car_convex.inertia_tensor,
total_volume = car_convex.total_volume,
},
},
{
rel_x = {0, 1, 0},
rel_q = linalg.QUATERNIONF32_IDENTITY,
density_minus_one = 0.1 - 1,
inner_shape = physics.Shape_Box{size = 1},
},
}, },
mass = 1000, mass = 1000,
}, },
@ -416,9 +443,14 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
hash.fnv32a(slice.to_bytes([]int{(x | y << 8)})), hash.fnv32a(slice.to_bytes([]int{(x | y << 8)})),
physics.Body_Config { physics.Body_Config {
initial_pos = {5, 0.5 + f32(y) * 1.1, f32(x) * 3 + 10}, initial_pos = {-5, 0.5 + f32(y) * 1.1, f32(x) * 3 + 10},
initial_rot = linalg.QUATERNIONF32_IDENTITY, initial_rot = linalg.QUATERNIONF32_IDENTITY,
shape = physics.Shape_Box{size = 1}, shapes = {
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
inner_shape = physics.Shape_Box{size = 1},
},
},
mass = 10, mass = 10,
}, },
) )
@ -986,23 +1018,22 @@ draw_world :: proc(world: ^World) {
if .ACTIVE in ui.header(ui_ctx, "Car", {.EXPANDED}) && if .ACTIVE in ui.header(ui_ctx, "Car", {.EXPANDED}) &&
car_body.alive && car_body.alive &&
engine.alive { engine.alive {
ui_keyval :: proc(ctx: ^ui.Context, key: string, val: any) { gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios)
ui.layout_row(ctx, {100, -1}, 0) ui.keyval(ui_ctx, "p", car_body.x)
ui.label(ctx, key) ui.keyval(ui_ctx, "v", car_body.v)
ui.label(ctx, fmt.tprintf("%v", val)) ui.keyval(ui_ctx, "gear", engine.gear)
ui.keyval(ui_ctx, "ratio", physics.lookup_gear_ratio(gear_ratios, engine.gear))
ui.keyval(ui_ctx, "rpm", physics.angular_velocity_to_rpm(engine.w))
ui.keyval(ui_ctx, "clutch", engine.clutch)
ui.keyval(ui_ctx, "speed", linalg.length(car_body.v) * 3.6)
} }
gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios) if .ACTIVE in ui.header(ui_ctx, "Physics") {
ui.layout_row(ui_ctx, {100, -1}, 0) physics.draw_debug_ui(ui_ctx, &world.physics_scene, SOLVER_CONFIG)
ui.layout_row(ui_ctx, {100, -1}, 0)
ui_keyval(ui_ctx, "p", car_body.x)
ui_keyval(ui_ctx, "v", car_body.v)
ui_keyval(ui_ctx, "gear", engine.gear)
ui_keyval(ui_ctx, "ratio", physics.lookup_gear_ratio(gear_ratios, engine.gear))
ui_keyval(ui_ctx, "rpm", physics.angular_velocity_to_rpm(engine.w))
ui_keyval(ui_ctx, "clutch", engine.clutch)
ui_keyval(ui_ctx, "speed", linalg.length(car_body.v) * 3.6)
} }
} else {
log.infof("Window closed")
world.debug_state.show_menu = false
} }
} }
} }
@ -1291,6 +1322,8 @@ game_init :: proc() {
g_mem^ = Game_Memory{} g_mem^ = Game_Memory{}
name.init(&g_mem.name_container)
name.setup_global_container(&g_mem.name_container)
init_physifs_raylib_callbacks() init_physifs_raylib_callbacks()
assets.assetman_init(&g_mem.assetman) assets.assetman_init(&g_mem.assetman)
@ -1310,6 +1343,7 @@ game_init :: proc() {
@(export) @(export)
game_shutdown :: proc() { game_shutdown :: proc() {
name.destroy()
assets.shutdown(&g_mem.assetman) assets.shutdown(&g_mem.assetman)
editor_state_destroy(&g_mem.es) editor_state_destroy(&g_mem.es)
delete(g_mem.es.point_selection) delete(g_mem.es.point_selection)
@ -1339,6 +1373,7 @@ game_memory_size :: proc() -> int {
game_hot_reloaded :: proc(mem: rawptr) { game_hot_reloaded :: proc(mem: rawptr) {
g_mem = (^Game_Memory)(mem) g_mem = (^Game_Memory)(mem)
name.setup_global_container(&g_mem.name_container)
render.init(&g_mem.assetman) render.init(&g_mem.assetman)
ui.rl_init() ui.rl_init()

View File

@ -4,6 +4,7 @@ import "core:hash/xxhash"
import lg "core:math/linalg" import lg "core:math/linalg"
import "core:mem" import "core:mem"
import "core:slice" import "core:slice"
import "libs:tracy"
Vec3 :: [3]f32 Vec3 :: [3]f32
@ -43,6 +44,7 @@ mesh_from_vertex_index_list :: proc(
vertices_per_face: int = 3, vertices_per_face: int = 3,
allocator := context.allocator, allocator := context.allocator,
) -> Half_Edge_Mesh { ) -> Half_Edge_Mesh {
tracy.Zone()
assert(vertices_per_face >= 3) assert(vertices_per_face >= 3)
num_faces := len(indices) / vertices_per_face num_faces := len(indices) / vertices_per_face
@ -277,6 +279,7 @@ copy_mesh :: proc(
transform_mesh :: proc(mesh: ^Half_Edge_Mesh, mat: lg.Matrix4f32) { transform_mesh :: proc(mesh: ^Half_Edge_Mesh, mat: lg.Matrix4f32) {
tracy.Zone()
mesh_center_avg_factor := 1.0 / f32(len(mesh.vertices)) mesh_center_avg_factor := 1.0 / f32(len(mesh.vertices))
new_center: Vec3 new_center: Vec3
for i in 0 ..< len(mesh.vertices) { for i in 0 ..< len(mesh.vertices) {

62
game/name/name.odin Normal file
View File

@ -0,0 +1,62 @@
// String interning thingy
package name
import "core:mem"
import "core:strings"
import "core:sync"
Name :: distinct u32
NONE :: Name(0)
Container :: struct {
lock: sync.Atomic_RW_Mutex,
names_lookup: map[string]Name,
names_allocator: mem.Dynamic_Arena,
names_array: [dynamic]string,
}
@(private = "file")
global_container: ^Container
setup_global_container :: proc(cnt: ^Container) {
global_container = cnt
}
init :: proc(cnt: ^Container) {
mem.dynamic_arena_init(&cnt.names_allocator)
assert(len(cnt.names_array) == 0)
append(&cnt.names_array, "None")
}
destroy :: proc() {
assert(global_container != nil)
delete(global_container.names_array)
delete(global_container.names_lookup)
mem.dynamic_arena_destroy(&global_container.names_allocator)
global_container = nil
}
from_string :: proc(str: string) -> Name {
sync.atomic_rw_mutex_guard(&global_container.lock)
existing, ok := global_container.names_lookup[str]
if ok {
return existing
} else {
new_str := strings.clone(
str,
mem.dynamic_arena_allocator(&global_container.names_allocator),
)
idx := u32(len(global_container.names_array))
append(&global_container.names_array, new_str)
global_container.names_lookup[str] = Name(idx)
return Name(idx)
}
}
to_string :: proc(name: Name) -> string {
sync.atomic_rw_mutex_shared_guard(&global_container.lock)
return global_container.names_array[name]
}

View File

@ -6,9 +6,10 @@ import "core:fmt"
import "core:log" import "core:log"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:debug" import "game:debug"
import "libs:tracy" import "game:ui"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
import "libs:tracy"
_ :: fmt _ :: fmt
_ :: log _ :: log
@ -145,6 +146,36 @@ debug_draw_bvh_bounds :: proc(bvh: ^BVH, primitive_bounds: []AABB, node_index: i
} }
} }
debug_ui_bvh_node_recursive :: proc(
ctx: ^ui.Context,
bvh: BVH,
primitive_bounds: []AABB,
node_idx: i32,
) {
if .ACTIVE in ui.treenode(ctx, fmt.tprintf("Node %v", node_idx)) {
node := bvh.nodes[node_idx]
if !is_leaf_node(node) {
debug_ui_bvh_node_recursive(ctx, bvh, primitive_bounds, node.child_or_prim_start)
debug_ui_bvh_node_recursive(ctx, bvh, primitive_bounds, node.child_or_prim_start + 1)
} else {
for i in node.child_or_prim_start ..< node.child_or_prim_start + node.prim_len {
prim := bvh.primitives[i]
aabb := primitive_bounds[prim]
ui.layout_row(ctx, {-1})
ui.layout_next(ctx)
ui.label(ctx, fmt.tprintf("Child: %v", aabb))
}
}
}
}
debug_ui_bvh_tree :: proc(ctx: ^ui.Context, bvh: BVH, primitive_bounds: []AABB) {
if .ACTIVE in ui.treenode(ctx, "BVH") {
debug_ui_bvh_node_recursive(ctx, bvh, primitive_bounds, 0)
}
}
bvh_mesh_from_rl_mesh :: proc(mesh: rl.Mesh) -> Mesh { bvh_mesh_from_rl_mesh :: proc(mesh: rl.Mesh) -> Mesh {
return Mesh { return Mesh {
vertices = (cast([^]Vec3)mesh.vertices)[:mesh.vertexCount], vertices = (cast([^]Vec3)mesh.vertices)[:mesh.vertexCount],

View File

@ -16,7 +16,7 @@ _ :: log
Convex :: halfedge.Half_Edge_Mesh Convex :: halfedge.Half_Edge_Mesh
BOX_CORNERS_NORM :: [8]Vec3 { box_corners_norm := [8]Vec3 {
{-1, 1, 1}, {-1, 1, 1},
{-1, -1, 1}, {-1, -1, 1},
{-1, 1, -1}, {-1, 1, -1},
@ -29,14 +29,27 @@ BOX_CORNERS_NORM :: [8]Vec3 {
box_indices := [6 * 4]u16{0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1} box_indices := [6 * 4]u16{0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1}
box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Convex) { @(private = "file")
vertices := make([]Vec3, 8, context.temp_allocator) box_mesh: Convex
for corner, i in BOX_CORNERS_NORM { @(init)
vertices[i] = box.pos + corner * box.rad init_box_mesh :: proc() {
box_mesh = Convex(halfedge.mesh_from_vertex_index_list(box_corners_norm[:], box_indices[:], 4))
} }
convex = Convex(halfedge.mesh_from_vertex_index_list(vertices, box_indices[:], 4, allocator)) @(fini)
deinit_box_mesh :: proc() {
delete(box_mesh.vertices)
delete(box_mesh.faces)
delete(box_mesh.edges)
}
box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Convex) {
convex = halfedge.copy_mesh(box_mesh, allocator)
for &v in convex.vertices {
v.pos = box.pos + v.pos * box.rad
}
return return
} }
@ -649,5 +662,9 @@ ray_vs_convex :: proc(
} }
} }
if hit {
normal = lg.normalize0(normal)
}
return return
} }

View File

@ -1,16 +1,23 @@
package physics package physics
import "bvh"
import "core:fmt" import "core:fmt"
import "core:log" import "core:log"
import "core:math" import "core:math"
import lg "core:math/linalg" import lg "core:math/linalg"
import "core:mem"
import "core:strings"
import "game:debug" import "game:debug"
import he "game:halfedge" import he "game:halfedge"
import "game:name"
import "game:ui" import "game:ui"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
import "libs:tracy" import "libs:tracy"
_ :: name
_ :: mem
_ :: fmt
_ :: log _ :: log
_ :: math _ :: math
_ :: debug _ :: debug
@ -24,6 +31,8 @@ draw_debug_shape :: proc(
color: rl.Color, color: rl.Color,
) { ) {
mat := lg.matrix4_from_trs(pos, rot, 1) mat := lg.matrix4_from_trs(pos, rot, 1)
shape_mat := lg.matrix4_translate_f32(shape.rel_x) * lg.matrix4_from_quaternion(shape.rel_q)
mat *= auto_cast shape_mat
rlgl.PushMatrix() rlgl.PushMatrix()
defer rlgl.PopMatrix() defer rlgl.PopMatrix()
@ -31,7 +40,7 @@ draw_debug_shape :: proc(
rlgl.LoadIdentity() rlgl.LoadIdentity()
rlgl.MultMatrixf(cast([^]f32)&mat) rlgl.MultMatrixf(cast([^]f32)&mat)
switch s in shape { switch s in shape.inner_shape {
case Shape_Box: case Shape_Box:
rl.DrawCubeV(0, s.size, color) rl.DrawCubeV(0, s.size, color)
case Internal_Shape_Convex: case Internal_Shape_Convex:
@ -45,6 +54,15 @@ draw_debug_scene :: proc(scene: ^Scene) {
sim_state := get_sim_state(scene) sim_state := get_sim_state(scene)
// Dynamic TLAS
if true && sim_state.dynamic_tlas.built {
bvh.debug_draw_bvh_bounds(
&sim_state.dynamic_tlas.bvh_tree,
sim_state.dynamic_tlas.body_aabbs,
0,
)
}
for _, i in sim_state.bodies { for _, i in sim_state.bodies {
body := &sim_state.bodies_slice[i] body := &sim_state.bodies_slice[i]
if body.alive { if body.alive {
@ -59,13 +77,25 @@ draw_debug_scene :: proc(scene: ^Scene) {
rl.DrawLine3D(pos, pos + y, rl.GREEN) rl.DrawLine3D(pos, pos + y, rl.GREEN)
rl.DrawLine3D(pos, pos + z, rl.BLUE) rl.DrawLine3D(pos, pos + z, rl.BLUE)
it := shapes_iterator(sim_state, body.shape)
for shape in shapes_iterator_next(&it) {
draw_debug_shape( draw_debug_shape(
sim_state, sim_state,
body.shape, shape^,
body_get_shape_pos(body), body_get_shape_pos(body),
body.q, body.q,
debug.int_to_color(i32(i + 2)), debug.int_to_color(i32(i + 2)),
) )
shape_aabb := body_transform_shape_aabb(body, shape_get_aabb(shape^))
rl.DrawBoundingBox(
rl.BoundingBox {
min = shape_aabb.center - shape_aabb.extent,
max = shape_aabb.center + shape_aabb.extent,
},
debug.int_to_color(i32(i + 2)),
)
}
} }
} }
@ -168,133 +198,190 @@ draw_debug_ui :: proc(ctx: ^ui.Context, scene: ^Scene, config: Solver_Config) {
sim_state := get_sim_state(scene) sim_state := get_sim_state(scene)
active_wheels := []int{0, 1} if .ACTIVE in ui.treenode(ctx, "Bodies") {
for _, i in sim_state.bodies_slice {
body := &sim_state.bodies_slice[i]
if body.alive {
if .ACTIVE in
ui.treenode(ctx, fmt.tprintf("%v (%v)", name.to_string(body.name), i)) {
ui.keyval(ctx, "Pos", body.x)
ui.keyval(ctx, "Shape Offset", body.shape_offset)
aabb := body_get_aabb(body)
ui.keyval(ctx, "AABB", aabb)
ui.keyval(ctx, "Inv Inertia", body.inv_inertia_tensor)
}
}
}
}
w, h: i32 = 500, 500 if .ACTIVE in ui.treenode(ctx, "Contacts") {
@(static) search_buf: [1024]u8
@(static) search_len: int
ui.textbox(ctx, search_buf[:], &search_len)
search_str := string(search_buf[0:search_len])
for &contact, i in sim_state.contact_container.contacts {
title: string
window_x: i32 = 0 ui.push_id(ctx, mem.any_to_bytes(i))
defer ui.pop_id(ctx)
for i in 0 ..< len(sim_state.suspension_constraints_slice) { if contact.type == .Body_vs_Body {
s := &sim_state.suspension_constraints_slice[i] title = fmt.tprintf(
"%v v %v",
name.to_string(get_body(sim_state, contact.a).name),
name.to_string(get_body(sim_state, Body_Handle(contact.b)).name),
)
} else {
title = fmt.tprintf(
"%v v lvl",
name.to_string(get_body(sim_state, contact.a).name),
)
}
if s.alive { if strings.contains(title, search_str) {
for idx in active_wheels { ui.inspect_value(ctx, title, contact)
if i == idx { }
if ui.window( }
}
if .ACTIVE in ui.treenode(ctx, "Dynamic TLAS") {
if sim_state.dynamic_tlas.built {
bvh.debug_ui_bvh_node_recursive(
ctx, ctx,
fmt.tprintf("Wheel %v", i), sim_state.dynamic_tlas.bvh_tree,
ui.Rect{x = window_x, y = 0, w = w, h = h}, sim_state.dynamic_tlas.body_aabbs,
ui.Options{}, 0,
) {
NUM_SAMPLES :: 100
dt := f32(config.timestep) / f32(config.substreps_minus_one + 1)
inv_dt := 1.0 / dt
{
ui.layout_row(ctx, {-1}, 300)
{
ui.begin_line(ctx, ui.Color{255, 0, 0, 255})
defer ui.end_line(ctx)
for j in 0 ..< NUM_SAMPLES {
alpha := f32(j) / f32(NUM_SAMPLES - 1)
x := alpha * 200.0 - 100.0
long_friction := abs(
pacejka_94_longitudinal(
s.pacejka_long,
x,
max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
),
)
ui.push_line_point(
ctx,
ui.Vec2f{alpha, long_friction * -0.5 + 1},
)
}
long_friction := abs(
pacejka_94_longitudinal(
s.pacejka_long,
s.slip_ratio,
max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
),
)
rect := ui.get_line(ctx).rect
cur_point :=
Vec2 {
(s.slip_ratio + 100.0) / 200.0,
long_friction * -0.5 + 1,
} *
Vec2{f32(rect.w), f32(rect.h)} +
Vec2{f32(rect.x), f32(rect.y)}
ui.draw_rect(
ctx,
ui.rect_from_point_extent(
ui.Vec2{i32(cur_point.x), i32(cur_point.y)},
2,
),
ui.Color{255, 255, 0, 255},
) )
} }
} }
{ // active_wheels := []int{0, 1}
ui.layout_row(ctx, {-1}, 300)
ui.begin_line(ctx, ui.Color{0, 255, 0, 255})
defer ui.end_line(ctx)
for j in 0 ..< NUM_SAMPLES { // w, h: i32 = 500, 500
alpha := f32(j) / f32(NUM_SAMPLES - 1)
x := alpha * 180.0 - 90.0
lat_friction := abs( // window_x: i32 = 0
pacejka_94_lateral(
s.pacejka_lat,
x,
max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
0.0,
),
)
ui.push_line_point(ctx, ui.Vec2f{alpha, lat_friction * -0.5 + 1}) // for i in 0 ..< len(sim_state.suspension_constraints_slice) {
} // s := &sim_state.suspension_constraints_slice[i]
lat_friction := abs( // if s.alive {
pacejka_94_lateral( // for idx in active_wheels {
s.pacejka_lat, // if i == idx {
s.slip_angle, // if ui.window(
max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001, // ctx,
0.0, // fmt.tprintf("Wheel %v", i),
), // ui.Rect{x = window_x, y = 0, w = w, h = h},
) // ui.Options{},
// ) {
// NUM_SAMPLES :: 100
rect := ui.get_line(ctx).rect // dt := f32(config.timestep) / f32(config.substreps_minus_one + 1)
// inv_dt := 1.0 / dt
cur_point := // {
Vec2{(s.slip_angle + 100.0) / 200.0, lat_friction * -0.5 + 1} * // ui.layout_row(ctx, {-1}, 300)
Vec2{f32(rect.w), f32(rect.h)} + // {
Vec2{f32(rect.x), f32(rect.y)} // ui.begin_line(ctx, ui.Color{255, 0, 0, 255})
ui.draw_rect( // defer ui.end_line(ctx)
ctx,
ui.rect_from_point_extent(
ui.Vec2{i32(cur_point.x), i32(cur_point.y)},
2,
),
ui.Color{255, 255, 0, 255},
)
}
window_x += w // for j in 0 ..< NUM_SAMPLES {
} // alpha := f32(j) / f32(NUM_SAMPLES - 1)
} // x := alpha * 200.0 - 100.0
}
} // long_friction := abs(
} // pacejka_94_longitudinal(
// s.pacejka_long,
// x,
// max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
// ),
// )
// ui.push_line_point(
// ctx,
// ui.Vec2f{alpha, long_friction * -0.5 + 1},
// )
// }
// long_friction := abs(
// pacejka_94_longitudinal(
// s.pacejka_long,
// s.slip_ratio,
// max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
// ),
// )
// rect := ui.get_line(ctx).rect
// cur_point :=
// Vec2 {
// (s.slip_ratio + 100.0) / 200.0,
// long_friction * -0.5 + 1,
// } *
// Vec2{f32(rect.w), f32(rect.h)} +
// Vec2{f32(rect.x), f32(rect.y)}
// ui.draw_rect(
// ctx,
// ui.rect_from_point_extent(
// ui.Vec2{i32(cur_point.x), i32(cur_point.y)},
// 2,
// ),
// ui.Color{255, 255, 0, 255},
// )
// }
// }
// {
// ui.layout_row(ctx, {-1}, 300)
// ui.begin_line(ctx, ui.Color{0, 255, 0, 255})
// defer ui.end_line(ctx)
// for j in 0 ..< NUM_SAMPLES {
// alpha := f32(j) / f32(NUM_SAMPLES - 1)
// x := alpha * 180.0 - 90.0
// lat_friction := abs(
// pacejka_94_lateral(
// s.pacejka_lat,
// x,
// max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
// 0.0,
// ),
// )
// ui.push_line_point(ctx, ui.Vec2f{alpha, lat_friction * -0.5 + 1})
// }
// lat_friction := abs(
// pacejka_94_lateral(
// s.pacejka_lat,
// s.slip_angle,
// max(abs(s.spring_impulse), 0.001) * inv_dt * 0.001,
// 0.0,
// ),
// )
// rect := ui.get_line(ctx).rect
// cur_point :=
// Vec2{(s.slip_angle + 100.0) / 200.0, lat_friction * -0.5 + 1} *
// Vec2{f32(rect.w), f32(rect.h)} +
// Vec2{f32(rect.x), f32(rect.y)}
// ui.draw_rect(
// ctx,
// ui.rect_from_point_extent(
// ui.Vec2{i32(cur_point.x), i32(cur_point.y)},
// 2,
// ),
// ui.Color{255, 255, 0, 255},
// )
// }
// window_x += w
// }
// }
// }
// }
// }
} }
debug_transform_points_local_to_world :: proc(body: Body_Ptr, points: []Vec3) { debug_transform_points_local_to_world :: proc(body: Body_Ptr, points: []Vec3) {

View File

@ -1,9 +1,14 @@
package physics package physics
import "collision" import "collision"
import "core:log"
import "core:math" import "core:math"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:halfedge" import "game:halfedge"
import "game:name"
import "libs:tracy"
_ :: log
inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) { inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) {
tensor = radius * radius * (2.0 / 3.0) tensor = radius * radius * (2.0 / 3.0)
@ -23,7 +28,7 @@ inertia_tensor_box :: proc(size: Vec3) -> (tensor: Matrix3) {
return return
} }
inertia_tensor_collision_shape :: proc(shape: Input_Shape) -> (tensor: Matrix3) { inertia_tensor_collision_shape :: proc(shape: Input_Shape_Internal) -> (tensor: Matrix3) {
switch s in shape { switch s in shape {
case Shape_Box: case Shape_Box:
tensor = inertia_tensor_box(s.size) tensor = inertia_tensor_box(s.size)
@ -35,6 +40,66 @@ inertia_tensor_collision_shape :: proc(shape: Input_Shape) -> (tensor: Matrix3)
return return
} }
center_of_mass_shape :: proc(shape: Input_Shape_Internal) -> (com: Vec3) {
switch s in shape {
case Shape_Box:
com = 0
case Shape_Convex:
com = s.center_of_mass
}
return
}
volume_shape :: proc(shape: Input_Shape_Internal) -> (vol: f32) {
switch s in shape {
case Shape_Box:
vol = s.size.x * s.size.y * s.size.z
case Shape_Convex:
vol = s.total_volume
}
return
}
combined_center_of_mass :: proc(shapes: []Input_Shape) -> (com: Vec3) {
total_weight := f32(0)
for shape in shapes {
inner_com := center_of_mass_shape(shape.inner_shape) + shape.rel_x
volume := volume_shape(shape.inner_shape) * (shape.density_minus_one + 1)
com += inner_com * volume
total_weight += volume
}
com = total_weight > 0 ? com / total_weight : 0
return
}
// Returns normalized inertia
// https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=246
combined_inertia_tensor :: proc(shapes: []Input_Shape) -> (inertia: Matrix3) {
combined_com := combined_center_of_mass(shapes)
total_volume := f32(0)
for shape in shapes {
com := center_of_mass_shape(shape.inner_shape) + shape.rel_x
r := com - combined_com
local_inertia := inertia_tensor_collision_shape(shape.inner_shape)
rotation_mat: Matrix3 = auto_cast lg.matrix3_from_quaternion(shape.rel_q)
inertia_common_align := lg.transpose(rotation_mat) * local_inertia * rotation_mat
volume := volume_shape(shape.inner_shape) * (shape.density_minus_one + 1)
dot_r := lg.dot(r, r)
// outer_r: Matrix3 = auto_cast lg.outer_product(r, r)
#unroll for i in 0 ..< 3 {
#unroll for j in 0 ..< 3 {
kron: f32 = i == j ? 1 : 0
inertia[i][j] += inertia_common_align[i][j] + volume * (dot_r * kron - r[i] * r[j])
}
}
// inertia = inertia + inertia_common_align * (Matrix3(dot_r) - outer_r) * Matrix3(volume)
total_volume += volume
}
inertia *= Matrix3(1.0 / total_volume)
return
}
body_local_to_world :: #force_inline proc(body: Body_Ptr, pos: Vec3) -> Vec3 { body_local_to_world :: #force_inline proc(body: Body_Ptr, pos: Vec3) -> Vec3 {
return body.x + lg.quaternion_mul_vector3(body.q, pos) return body.x + lg.quaternion_mul_vector3(body.q, pos)
} }
@ -88,11 +153,7 @@ wheel_get_forward_vec :: #force_inline proc(
} }
body_get_shape_offset_local :: proc(body: Body_Ptr) -> (offset: Vec3) { body_get_shape_offset_local :: proc(body: Body_Ptr) -> (offset: Vec3) {
#partial switch s in body.shape { return body.shape_offset
case Internal_Shape_Convex:
offset = -s.center_of_mass
}
return
} }
// Returns the shape's world position // Returns the shape's world position
@ -102,14 +163,14 @@ body_get_shape_pos :: proc(body: Body_Ptr) -> Vec3 {
return body_local_to_world(body, offset) return body_local_to_world(body, offset)
} }
body_get_convex_shape_world :: proc( shape_get_convex_local :: proc(
sim_state: ^Sim_State, sim_state: ^Sim_State,
body: Body_Ptr, shape: Collision_Shape_Internal,
allocator := context.temp_allocator, allocator := context.temp_allocator,
) -> ( ) -> (
mesh: collision.Convex, mesh: collision.Convex,
) { ) {
switch s in body.shape { switch s in shape {
case Shape_Box: case Shape_Box:
mesh = collision.box_to_convex(collision.Box{rad = s.size * 0.5}, allocator) mesh = collision.box_to_convex(collision.Box{rad = s.size * 0.5}, allocator)
case Internal_Shape_Convex: case Internal_Shape_Convex:
@ -118,10 +179,125 @@ body_get_convex_shape_world :: proc(
mesh = halfedge.copy_mesh(mesh, allocator) mesh = halfedge.copy_mesh(mesh, allocator)
} }
transform := return
lg.matrix4_translate_f32(body_get_shape_pos(body)) * lg.matrix4_from_quaternion_f32(body.q) }
halfedge.transform_mesh(&mesh, transform)
Shapes_Iterator :: struct {
sim_state: ^Sim_State,
first_idx: i32,
cur_idx: i32,
counter: i32,
}
shapes_iterator :: proc(sim_state: ^Sim_State, first_shape: i32) -> (it: Shapes_Iterator) {
it.sim_state = sim_state
it.first_idx = first_shape
it.cur_idx = it.first_idx
return
}
shapes_iterator_next :: proc(
it: ^Shapes_Iterator,
) -> (
shape: ^Collision_Shape,
idx: i32,
ok: bool,
) {
if it.cur_idx == it.first_idx && it.counter != 0 {
return nil, it.counter, false
}
shape = &it.sim_state.shapes[it.cur_idx]
idx = it.cur_idx
ok = true
it.cur_idx = shape.next_index
it.counter += 1
return
}
get_shape_by_index :: proc(
sim_state: ^Sim_State,
first_shape: i32,
index: i32,
) -> (
shape_idx: i32,
) {
tracy.Zone()
shape_idx = -1
i := i32(0)
it := shapes_iterator(sim_state, first_shape)
for _, idx in shapes_iterator_next(&it) {
if i == index {
shape_idx = idx
break
}
i += 1
}
return
}
body_get_convex_shape_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
shape: Collision_Shape,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
tracy.Zone()
body_transform := lg.matrix4_from_trs_f32(body_get_shape_pos(body), body.q, 1)
shape_transform := lg.matrix4_translate(shape.rel_x) * lg.matrix4_from_quaternion(shape.rel_q)
transform := body_transform * shape_transform
mesh = shape_get_convex_local(sim_state, shape.inner_shape, allocator)
halfedge.transform_mesh(&mesh, transform)
return
}
body_get_convex_shape_by_linear_index_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
shape_linear_idx: i32,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
tracy.Zone()
if shape_linear_idx == 1 {
log.debugf("linear idx: %v, body %v", shape_linear_idx, name.to_string(body.name))
}
shape_idx := get_shape_by_index(sim_state, body.shape, shape_linear_idx)
return body_get_convex_shape_world(sim_state, body, sim_state.shapes[shape_idx], allocator)
}
body_get_convex_shapes_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
allocator := context.temp_allocator,
) -> (
meshes: []collision.Convex,
) {
tracy.Zone()
body_transform :=
lg.matrix4_translate_f32(body_get_shape_pos(body)) * lg.matrix4_from_quaternion_f32(body.q)
meshes_arr := make([dynamic]collision.Convex, context.temp_allocator)
defer delete(meshes_arr)
it := shapes_iterator(sim_state, body.shape)
for shape in shapes_iterator_next(&it) {
shape_transform :=
lg.matrix4_translate(shape.rel_x) * lg.matrix4_from_quaternion(shape.rel_q)
transform := body_transform * shape_transform
mesh := shape_get_convex_local(sim_state, shape.inner_shape, allocator)
halfedge.transform_mesh(&mesh, transform)
append(&meshes_arr, mesh)
}
meshes = make([]collision.Convex, len(meshes_arr), allocator)
copy(meshes, meshes_arr[:])
return return
} }
@ -140,7 +316,20 @@ level_geom_get_convex_shape :: proc(
) )
} }
shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) { input_shape_internal_get_aabb :: proc(shape: Input_Shape_Internal) -> (aabb: AABB) {
switch s in shape {
case Shape_Box:
aabb.center = 0
aabb.extent = s.size * 0.5
case Shape_Convex:
aabb.center = s.mesh.center
aabb.extent = s.mesh.extent
}
return aabb
}
internal_shape_get_aabb :: proc(shape: Collision_Shape_Internal) -> (aabb: AABB) {
switch s in shape { switch s in shape {
case Shape_Box: case Shape_Box:
aabb.center = 0 aabb.center = 0
@ -149,22 +338,59 @@ shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) {
aabb.center = s.center aabb.center = s.center
aabb.extent = s.extent aabb.extent = s.extent
} }
return
}
return aabb shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) {
local_aabb := internal_shape_get_aabb(shape.inner_shape)
aabb = aabb_transform(local_aabb, shape.rel_x, shape.rel_q)
return
}
rotate_extent :: proc(extent: Vec3, q: Quat) -> Vec3 {
rotation_mat := lg.matrix3_from_quaternion(q)
result := lg.abs(extent.xxx * rotation_mat[0])
result += lg.abs(extent.yyy * rotation_mat[1])
result += lg.abs(extent.zzz * rotation_mat[2])
return result
}
aabb_transform :: proc(local_aabb: AABB, x: Vec3, q: Quat) -> (aabb: AABB) {
aabb.center = lg.quaternion_mul_vector3(q, local_aabb.center) + x
aabb.extent = rotate_extent(local_aabb.extent, q)
return
}
input_shape_get_aabb :: proc(shapes: []Input_Shape) -> (aabb: AABB) {
min, max: Vec3 = max(f32), min(f32)
for shape in shapes {
local_aabb := input_shape_internal_get_aabb(shape.inner_shape)
aabb = aabb_transform(local_aabb, shape.rel_x, shape.rel_q)
shape_min := aabb.center - aabb.extent
shape_max := aabb.center + aabb.extent
min, max = lg.min(shape_min, min), lg.max(shape_max, max)
}
aabb.center = (max + min) * 0.5
aabb.extent = (max - min) * 0.5
return
}
body_transform_shape_aabb :: proc(body: Body_Ptr, local_aabb: AABB) -> (aabb: AABB) {
offset_world := body_local_to_world(body, body.shape_offset)
aabb = aabb_transform(local_aabb, offset_world, body.q)
return
} }
body_get_aabb :: proc(body: Body_Ptr) -> (aabb: AABB) { body_get_aabb :: proc(body: Body_Ptr) -> (aabb: AABB) {
local_aabb := shape_get_aabb(body.shape) local_aabb := body.shape_aabb
aabb_center_local_to_body := body_get_shape_offset_local(body) + local_aabb.center return body_transform_shape_aabb(body, local_aabb)
aabb.center = body_local_to_world(body, aabb_center_local_to_body)
rotation_mat := lg.matrix3_from_quaternion(body.q)
aabb.extent = lg.abs(local_aabb.extent.xxx * rotation_mat[0])
aabb.extent += lg.abs(local_aabb.extent.yyy * rotation_mat[1])
aabb.extent += lg.abs(local_aabb.extent.zzz * rotation_mat[2])
return aabb
} }
rpm_to_angular_velocity :: proc(rpm: f32) -> f32 { rpm_to_angular_velocity :: proc(rpm: f32) -> f32 {

View File

@ -3,6 +3,7 @@ package physics
import "collision" import "collision"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:container/spanpool" import "game:container/spanpool"
import "game:name"
import "libs:tracy" import "libs:tracy"
MAX_CONTACTS :: 1024 * 16 MAX_CONTACTS :: 1024 * 16
@ -18,9 +19,11 @@ AABB :: struct {
Contact_Pair_Bodies :: struct { Contact_Pair_Bodies :: struct {
a, b: Body_Handle, a, b: Body_Handle,
shape_a, shape_b: i32,
} }
Contact_Pair_Body_Level :: struct { Contact_Pair_Body_Level :: struct {
a: Body_Handle, a: Body_Handle,
shape_a: i32,
tri_idx: i32, tri_idx: i32,
} }
@ -29,15 +32,26 @@ Contact_Pair :: union {
Contact_Pair_Body_Level, Contact_Pair_Body_Level,
} }
make_contact_pair_bodies :: proc(body_a: i32, body_b: i32) -> Contact_Pair_Bodies { make_contact_pair_bodies :: proc(
body_a: i32,
body_b: i32,
shape_a: i32,
shape_b: i32,
) -> Contact_Pair_Bodies {
return { return {
index_to_body_handle(int(min(body_a, body_b))), index_to_body_handle(int(min(body_a, body_b))),
index_to_body_handle(int(max(body_a, body_b))), index_to_body_handle(int(max(body_a, body_b))),
shape_a,
shape_b,
} }
} }
make_contact_pair_body_level :: proc(body: i32, tri_idx: i32) -> Contact_Pair_Body_Level { make_contact_pair_body_level :: proc(
return {index_to_body_handle(int(body)), tri_idx} body: i32,
shape_a: i32,
tri_idx: i32,
) -> Contact_Pair_Body_Level {
return {index_to_body_handle(int(body)), shape_a, tri_idx}
} }
Contact_Container :: struct { Contact_Container :: struct {
@ -73,10 +87,12 @@ Sim_State :: struct {
// Slices. When you call get_body or get_suspension_constraint you will get a pointer to an element in this slice // Slices. When you call get_body or get_suspension_constraint you will get a pointer to an element in this slice
bodies_slice: #soa[]Body, bodies_slice: #soa[]Body,
suspension_constraints_slice: #soa[]Suspension_Constraint, suspension_constraints_slice: #soa[]Suspension_Constraint,
shapes: [dynamic]Collision_Shape,
first_free_body_plus_one: i32, first_free_body_plus_one: i32,
first_free_suspension_constraint_plus_one: i32, first_free_suspension_constraint_plus_one: i32,
first_free_engine_plus_one: i32, first_free_engine_plus_one: i32,
first_free_level_geom_plus_one: i32, first_free_level_geom_plus_one: i32,
first_free_shape_plus_one: i32,
// Persistent stuff for simulation // Persistent stuff for simulation
contact_container: Contact_Container, contact_container: Contact_Container,
@ -90,6 +106,10 @@ Sim_State :: struct {
// Level geometry // Level geometry
geometry_vertices_pool: spanpool.Span_Pool(Vec3), geometry_vertices_pool: spanpool.Span_Pool(Vec3),
geometry_indices_pool: spanpool.Span_Pool(u16), geometry_indices_pool: spanpool.Span_Pool(u16),
// Temp stuff, kept for one frame, allocated from temp_allocator
static_tlas: Static_TLAS,
dynamic_tlas: Dynamic_TLAS,
} }
Scene :: struct { Scene :: struct {
@ -107,11 +127,13 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
dst.first_free_body_plus_one = src.first_free_body_plus_one dst.first_free_body_plus_one = src.first_free_body_plus_one
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
dst.first_free_engine_plus_one = src.first_free_engine_plus_one dst.first_free_engine_plus_one = src.first_free_engine_plus_one
dst.first_free_shape_plus_one = src.first_free_shape_plus_one
resize(&dst.bodies, len(src.bodies)) resize(&dst.bodies, len(src.bodies))
resize(&dst.suspension_constraints, len(src.suspension_constraints)) resize(&dst.suspension_constraints, len(src.suspension_constraints))
resize(&dst.engines, len(src.engines)) resize(&dst.engines, len(src.engines))
resize(&dst.level_geoms, len(src.level_geoms)) resize(&dst.level_geoms, len(src.level_geoms))
resize(&dst.shapes, len(src.shapes))
dst.bodies_slice = dst.bodies[:] dst.bodies_slice = dst.bodies[:]
dst.suspension_constraints_slice = dst.suspension_constraints[:] dst.suspension_constraints_slice = dst.suspension_constraints[:]
@ -124,6 +146,7 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
} }
copy(dst.engines[:], src.engines[:]) copy(dst.engines[:], src.engines[:])
copy(dst.level_geoms[:], src.level_geoms[:]) copy(dst.level_geoms[:], src.level_geoms[:])
copy(dst.shapes[:], src.shapes[:])
contact_container_copy(&dst.contact_container, src.contact_container) contact_container_copy(&dst.contact_container, src.contact_container)
convex_container_copy(&dst.convex_container, src.convex_container) convex_container_copy(&dst.convex_container, src.convex_container)
@ -164,7 +187,11 @@ Body :: struct {
inv_mass: f32, inv_mass: f32,
// Moment of inertia // Moment of inertia
inv_inertia_tensor: Matrix3, inv_inertia_tensor: Matrix3,
shape: Collision_Shape, shape: i32,
// Collision shape offset = -combined_center_of_mass
shape_offset: Vec3,
shape_aabb: AABB,
name: name.Name,
// //
next_plus_one: i32, next_plus_one: i32,
} }
@ -189,13 +216,30 @@ Shape_Convex :: struct {
mesh: collision.Convex, mesh: collision.Convex,
center_of_mass: Vec3, center_of_mass: Vec3,
inertia_tensor: Matrix3, inertia_tensor: Matrix3,
total_volume: f32,
} }
Collision_Shape :: union { Collision_Shape_Internal :: union {
Shape_Box, Shape_Box,
Internal_Shape_Convex, Internal_Shape_Convex,
} }
Shape_Compound :: struct {
rel_x: Vec3,
rel_q: Quat,
inner_shape: Collision_Shape_Internal,
next_index: i32,
prev_index: i32,
}
Collision_Shape :: struct {
rel_x: Vec3,
rel_q: Quat,
inner_shape: Collision_Shape_Internal,
next_index: i32,
prev_index: i32,
}
Suspension_Constraint :: struct { Suspension_Constraint :: struct {
alive: bool, alive: bool,
// Pos relative to the body // Pos relative to the body
@ -442,25 +486,45 @@ get_body :: proc(sim_state: ^Sim_State, handle: Body_Handle) -> Body_Ptr {
return &sim_state.bodies_slice[index] return &sim_state.bodies_slice[index]
} }
remove_shape :: proc(sim_state: ^Sim_State, shape: Collision_Shape) { remove_shape :: proc(sim_state: ^Sim_State, idx: i32) {
switch &s in shape { cur_idx := idx
for {
shape := &sim_state.shapes[cur_idx]
switch &s in shape.inner_shape {
case Shape_Box: case Shape_Box:
case Internal_Shape_Convex: case Internal_Shape_Convex:
convex_container_remove(&sim_state.convex_container, s.mesh) convex_container_remove(&sim_state.convex_container, s.mesh)
} }
prev_idx := cur_idx
cur_idx = shape.next_index
free_shape(sim_state, prev_idx)
if cur_idx == idx {
break
}
}
} }
Input_Shape :: union { Input_Shape_Internal :: union {
Shape_Box, Shape_Box,
Shape_Convex, Shape_Convex,
} }
Input_Shape :: struct {
rel_x: Vec3,
rel_q: Quat,
density_minus_one: f32,
inner_shape: Input_Shape_Internal,
}
Body_Config :: struct { Body_Config :: struct {
name: name.Name,
initial_pos: Vec3, initial_pos: Vec3,
initial_rot: Quat, initial_rot: Quat,
initial_vel: Vec3, initial_vel: Vec3,
initial_ang_vel: Vec3, initial_ang_vel: Vec3,
shape: Input_Shape, shapes: []Input_Shape,
mass: f32, mass: f32,
inertia_mode: Body_Config_Inertia_Mode, inertia_mode: Body_Config_Inertia_Mode,
// Unit inertia tensor // Unit inertia tensor
@ -521,7 +585,7 @@ calculate_body_params_from_config :: proc(
if config.inertia_mode == .Explicit { if config.inertia_mode == .Explicit {
inertia_tensor = config.inertia_tensor inertia_tensor = config.inertia_tensor
} else { } else {
inertia_tensor = inertia_tensor_collision_shape(config.shape) inertia_tensor = combined_inertia_tensor(config.shapes)
} }
inertia_tensor = inertia_tensor * Matrix3(config.mass) inertia_tensor = inertia_tensor * Matrix3(config.mass)
@ -530,10 +594,55 @@ calculate_body_params_from_config :: proc(
return return
} }
add_shape :: proc(sim_state: ^Sim_State, shape: Input_Shape) -> (result: Collision_Shape) { alloc_shape :: proc(sim_state: ^Sim_State) -> (idx: i32) {
switch s in shape { if sim_state.first_free_shape_plus_one != 0 {
idx = sim_state.first_free_shape_plus_one - 1
new_shape := &sim_state.shapes[idx]
sim_state.first_free_shape_plus_one = new_shape.next_index + 1
new_shape.prev_index = idx
new_shape.next_index = idx
return idx
} else {
idx = i32(len(sim_state.shapes))
append(&sim_state.shapes, Collision_Shape{})
new_shape := &sim_state.shapes[idx]
new_shape.prev_index = idx
new_shape.next_index = idx
return idx
}
}
free_shape :: proc(sim_state: ^Sim_State, idx: i32) {
sim_state.shapes[idx].next_index = sim_state.first_free_shape_plus_one - 1
sim_state.first_free_shape_plus_one = idx + 1
}
add_shape :: proc(sim_state: ^Sim_State, shapes: []Input_Shape) -> (first_idx: i32) {
first_idx = -1
last_idx := i32(-1)
for shape in shapes {
new_idx := alloc_shape(sim_state)
new_shape := &sim_state.shapes[new_idx]
if first_idx == -1 {
first_idx = new_idx
} else {
sim_state.shapes[last_idx].next_index = new_idx
sim_state.shapes[first_idx].prev_index = new_idx
new_shape.prev_index = last_idx
new_shape.next_index = first_idx
}
last_idx = new_idx
new_shape.rel_x = shape.rel_x
new_shape.rel_q = shape.rel_q
switch s in shape.inner_shape {
case Shape_Box: case Shape_Box:
result = s new_shape.inner_shape = s
case Shape_Convex: case Shape_Convex:
convex: Internal_Shape_Convex convex: Internal_Shape_Convex
convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh) convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh)
@ -541,7 +650,8 @@ add_shape :: proc(sim_state: ^Sim_State, shape: Input_Shape) -> (result: Collisi
convex.extent = s.mesh.extent convex.extent = s.mesh.extent
convex.center_of_mass = s.center_of_mass convex.center_of_mass = s.center_of_mass
convex.inertia_tensor = s.inertia_tensor convex.inertia_tensor = s.inertia_tensor
result = convex new_shape.inner_shape = convex
}
} }
return return
@ -553,7 +663,10 @@ initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config:
body.v = config.initial_vel body.v = config.initial_vel
body.w = config.initial_ang_vel body.w = config.initial_ang_vel
body.shape = add_shape(sim_state, config.shape) body.shape = add_shape(sim_state, config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes)
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name
body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config) body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config)
} }
@ -561,7 +674,10 @@ initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config:
update_body_from_config :: proc(sim_state: ^Sim_State, body: Body_Ptr, config: Body_Config) { update_body_from_config :: proc(sim_state: ^Sim_State, body: Body_Ptr, config: Body_Config) {
// Inefficient, but eh // Inefficient, but eh
remove_shape(sim_state, body.shape) remove_shape(sim_state, body.shape)
body.shape = add_shape(sim_state, config.shape) body.shape = add_shape(sim_state, config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes)
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name
body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config) body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config)
} }
@ -855,6 +971,7 @@ destry_sim_state :: proc(sim_state: ^Sim_State) {
delete_soa(sim_state.suspension_constraints) delete_soa(sim_state.suspension_constraints)
delete(sim_state.engines) delete(sim_state.engines)
delete(sim_state.level_geoms) delete(sim_state.level_geoms)
delete(sim_state.shapes)
delete_soa(sim_state.contact_container.contacts) delete_soa(sim_state.contact_container.contacts)
delete_map(sim_state.contact_container.lookup) delete_map(sim_state.contact_container.lookup)
@ -863,6 +980,8 @@ destry_sim_state :: proc(sim_state: ^Sim_State) {
spanpool.destroy_spanpool(&sim_state.gear_ratios_pool) spanpool.destroy_spanpool(&sim_state.gear_ratios_pool)
spanpool.destroy_spanpool(&sim_state.geometry_vertices_pool) spanpool.destroy_spanpool(&sim_state.geometry_vertices_pool)
spanpool.destroy_spanpool(&sim_state.geometry_indices_pool) spanpool.destroy_spanpool(&sim_state.geometry_indices_pool)
dynamic_tlas_destroy(&sim_state.dynamic_tlas)
} }
destroy_physics_scene :: proc(scene: ^Scene) { destroy_physics_scene :: proc(scene: ^Scene) {

View File

@ -2,6 +2,7 @@ package physics
import "game:container/spanpool" import "game:container/spanpool"
import he "game:halfedge" import he "game:halfedge"
import "libs:tracy"
Convex_Container :: struct { Convex_Container :: struct {
vertices: spanpool.Span_Pool(he.Vertex), vertices: spanpool.Span_Pool(he.Vertex),
@ -34,6 +35,7 @@ convex_container_get_mesh :: proc(
) -> ( ) -> (
mesh: he.Half_Edge_Mesh, mesh: he.Half_Edge_Mesh,
) { ) {
tracy.Zone()
mesh.vertices = spanpool.resolve_slice(&container.vertices, handle.vertices) mesh.vertices = spanpool.resolve_slice(&container.vertices, handle.vertices)
mesh.faces = spanpool.resolve_slice(&container.faces, handle.faces) mesh.faces = spanpool.resolve_slice(&container.faces, handle.faces)
mesh.edges = spanpool.resolve_slice(&container.edges, handle.edges) mesh.edges = spanpool.resolve_slice(&container.edges, handle.edges)

View File

@ -12,8 +12,10 @@ import "core:math/rand"
import "core:slice" import "core:slice"
import "game:debug" import "game:debug"
import he "game:halfedge" import he "game:halfedge"
import "game:name"
import "libs:tracy" import "libs:tracy"
_ :: name
_ :: log _ :: log
_ :: rand _ :: rand
_ :: math _ :: math
@ -77,6 +79,7 @@ Static_TLAS :: struct {
Dynamic_TLAS :: struct { Dynamic_TLAS :: struct {
bvh_tree: bvh.BVH, bvh_tree: bvh.BVH,
body_aabbs: []bvh.AABB, body_aabbs: []bvh.AABB,
built: bool,
} }
build_static_tlas :: proc(sim_state: ^Sim_State) -> Static_TLAS { build_static_tlas :: proc(sim_state: ^Sim_State) -> Static_TLAS {
@ -146,10 +149,17 @@ build_static_tlas :: proc(sim_state: ^Sim_State) -> Static_TLAS {
// TODO: free intermediate temp allocs // TODO: free intermediate temp allocs
// Creates TLAS using temp allocator // Creates TLAS using temp allocator
build_dynamic_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> Dynamic_TLAS { build_dynamic_tlas :: proc(
sim_state: ^Sim_State,
config: Solver_Config,
out_tlas: ^Dynamic_TLAS,
allocator := context.allocator,
) {
tracy.Zone() tracy.Zone()
body_aabbs := make([]bvh.AABB, sim_state.num_bodies, context.temp_allocator) dynamic_tlas_destroy(out_tlas)
body_aabbs := make([]bvh.AABB, sim_state.num_bodies, allocator)
body_indices := make([]u16, sim_state.num_bodies, context.temp_allocator) body_indices := make([]u16, sim_state.num_bodies, context.temp_allocator)
{ {
@ -174,9 +184,21 @@ build_dynamic_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> Dyna
} }
} }
sim_state_bvh := bvh.build_bvh_from_aabbs(body_aabbs, body_indices, context.temp_allocator) sim_state_bvh := bvh.build_bvh_from_aabbs(body_aabbs, body_indices, allocator)
return Dynamic_TLAS{bvh_tree = sim_state_bvh, body_aabbs = body_aabbs} out_tlas^ = Dynamic_TLAS {
bvh_tree = sim_state_bvh,
body_aabbs = body_aabbs,
built = true,
}
}
dynamic_tlas_destroy :: proc(dyn_tlas: ^Dynamic_TLAS) {
if dyn_tlas.built {
bvh.destroy_bvh(&dyn_tlas.bvh_tree)
delete(dyn_tlas.body_aabbs)
dyn_tlas^ = {}
}
} }
raycasts_level :: proc( raycasts_level :: proc(
@ -251,10 +273,18 @@ raycast_bodies :: proc(
body_idx := tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j] body_idx := tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j]
body := get_body(sim_state, index_to_body_handle(int(body_idx))) body := get_body(sim_state, index_to_body_handle(int(body_idx)))
shape := body_get_convex_shape_world(sim_state, body, context.temp_allocator)
it := shapes_iterator(sim_state, body.shape)
for shape in shapes_iterator_next(&it) {
mesh := body_get_convex_shape_world(
sim_state,
body,
shape^,
context.temp_allocator,
)
hit_t, _, tmp_normal, _, ok := collision.ray_vs_convex( hit_t, _, tmp_normal, _, ok := collision.ray_vs_convex(
shape, mesh,
ray.origin, ray.origin,
ray.dir, ray.dir,
distance, distance,
@ -267,6 +297,7 @@ raycast_bodies :: proc(
} }
} }
} }
}
return return
} }
@ -384,10 +415,16 @@ remove_invalid_contacts :: proc(
pair_from_contact :: proc(contact: Contact) -> (pair: Contact_Pair) { pair_from_contact :: proc(contact: Contact) -> (pair: Contact_Pair) {
if contact.type == .Body_vs_Body { if contact.type == .Body_vs_Body {
pair = make_contact_pair_bodies(i32(contact.a) - 1, i32(contact.b) - 1) pair = make_contact_pair_bodies(
i32(contact.a) - 1,
i32(contact.b) - 1,
contact.shape_a,
contact.shape_b,
)
} else { } else {
pair = make_contact_pair_body_level( pair = make_contact_pair_body_level(
i32(body_handle_to_index(contact.a)), i32(body_handle_to_index(contact.a)),
contact.shape_a,
contact.tri_idx, contact.tri_idx,
) )
} }
@ -433,24 +470,70 @@ find_new_contacts :: proc(
for j in 0 ..< leaf_node.prim_len { for j in 0 ..< leaf_node.prim_len {
other_body_idx := other_body_idx :=
dyn_tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j] dyn_tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j]
other_body := &sim_state.bodies_slice[other_body_idx]
prim_aabb := dyn_tlas.body_aabbs[other_body_idx] prim_aabb := dyn_tlas.body_aabbs[other_body_idx]
pair := make_contact_pair_bodies(i32(body_idx), i32(other_body_idx))
if body_idx != other_body_idx && if body_idx != other_body_idx &&
(bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) && (bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) {
!(pair in sim_state.contact_container.lookup) { shapes_a_it := shapes_iterator(sim_state, body.shape)
for shape_a in shapes_iterator_next(&shapes_a_it) {
shape_a_idx := shapes_a_it.counter - 1
new_contact_idx := len(sim_state.contact_container.contacts) shape_a_aabb := shape_get_aabb(shape_a^)
resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1) shape_a_aabb = body_transform_shape_aabb(body, shape_a_aabb)
shapes_b_it := shapes_iterator(sim_state, other_body.shape)
for shape_b in shapes_iterator_next(&shapes_b_it) {
shape_b_idx := shapes_b_it.counter - 1
shape_b_aabb := shape_get_aabb(shape_b^)
shape_b_aabb = body_transform_shape_aabb(
other_body,
shape_b_aabb,
)
pair := make_contact_pair_bodies(
i32(body_idx),
i32(other_body_idx),
shape_a_idx,
shape_b_idx,
)
bvh_aabb_a := bvh.AABB {
min = shape_a_aabb.center - shape_a_aabb.extent,
max = shape_a_aabb.center + shape_a_aabb.extent,
}
bvh_aabb_b := bvh.AABB {
min = shape_b_aabb.center - shape_b_aabb.extent,
max = shape_b_aabb.center + shape_b_aabb.extent,
}
if bvh.test_aabb_vs_aabb(bvh_aabb_a, bvh_aabb_b) &&
!(pair in sim_state.contact_container.lookup) {
new_contact_idx := len(
sim_state.contact_container.contacts,
)
resize_soa(
&sim_state.contact_container.contacts,
new_contact_idx + 1,
)
contact := &sim_state.contact_container.contacts[new_contact_idx] contact := &sim_state.contact_container.contacts[new_contact_idx]
contact^ = Contact { contact^ = Contact {
type = .Body_vs_Body, type = .Body_vs_Body,
a = Body_Handle(i + 1), a = Body_Handle(i + 1),
b = i32(other_body_idx + 1), b = i32(other_body_idx + 1),
shape_a = shape_a_idx,
shape_b = shape_b_idx,
} }
sim_state.contact_container.lookup[pair] = i32(new_contact_idx) sim_state.contact_container.lookup[pair] = i32(
new_contact_idx,
)
}
}
}
} }
} }
} }
@ -471,16 +554,36 @@ find_new_contacts :: proc(
prim_aabb := get_triangle_aabb(tri) prim_aabb := get_triangle_aabb(tri)
level_geom_handle := static_tlas.tri_to_level_geom[tri_idx] level_geom_handle := static_tlas.tri_to_level_geom[tri_idx]
pair := make_contact_pair_body_level(i32(body_idx), i32(tri_idx)) if bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) {
aabb_overlaps := bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) || true
if aabb_overlaps && !(pair in sim_state.contact_container.lookup) { shapes_it := shapes_iterator(sim_state, body.shape)
for shape, shape_idx in shapes_iterator_next(&shapes_it) {
shape_aabb := shape_get_aabb(shape^)
shape_aabb = body_transform_shape_aabb(body, shape_aabb)
bvh_shape_aabb := bvh.AABB {
min = shape_aabb.center - shape_aabb.extent,
max = shape_aabb.center + shape_aabb.extent,
}
pair := make_contact_pair_body_level(
i32(body_idx),
shape_idx,
i32(tri_idx),
)
if bvh.test_aabb_vs_aabb(prim_aabb, bvh_shape_aabb) &&
!(pair in sim_state.contact_container.lookup) {
new_contact_idx := len(sim_state.contact_container.contacts) new_contact_idx := len(sim_state.contact_container.contacts)
resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1) resize_soa(
&sim_state.contact_container.contacts,
new_contact_idx + 1,
)
contact := &sim_state.contact_container.contacts[new_contact_idx] contact := &sim_state.contact_container.contacts[new_contact_idx]
contact^ = Contact { contact^ = Contact {
type = .Body_vs_Level, type = .Body_vs_Level,
a = index_to_body_handle(i), a = index_to_body_handle(i),
shape_a = shape_idx,
b = i32(level_geom_handle), b = i32(level_geom_handle),
tri_idx = i32(tri_idx), tri_idx = i32(tri_idx),
} }
@ -493,6 +596,8 @@ find_new_contacts :: proc(
} }
} }
} }
}
}
// Outer simulation loop for fixed timestepping // Outer simulation loop for fixed timestepping
simulate :: proc( simulate :: proc(
@ -557,6 +662,8 @@ Contact :: struct {
a: Body_Handle, a: Body_Handle,
// Body_vs_Body - Body_Handle, Body_vs_Level - Level_Geom_Handle // Body_vs_Body - Body_Handle, Body_vs_Level - Level_Geom_Handle
b: i32, b: i32,
// shape index in each body
shape_a, shape_b: i32,
tri_idx: i32, tri_idx: i32,
prev_x_a, prev_x_b: Vec3, prev_x_a, prev_x_b: Vec3,
prev_q_a, prev_q_b: Quat, prev_q_a, prev_q_b: Quat,
@ -622,12 +729,12 @@ update_contacts :: proc(sim_state: ^Sim_State, static_tlas: ^Static_TLAS) {
// continue // continue
// } // }
m1 := body_get_convex_shape_world(sim_state, body) m1 := body_get_convex_shape_by_linear_index_world(sim_state, body, contact.shape_a)
m2: collision.Convex m2: collision.Convex
switch contact.type { switch contact.type {
case .Body_vs_Body: case .Body_vs_Body:
m2 = body_get_convex_shape_world(sim_state, body2) m2 = body_get_convex_shape_by_linear_index_world(sim_state, body2, contact.shape_b)
case .Body_vs_Level: case .Body_vs_Level:
// level_geom := get_level_geom(sim_state, Level_Geom_Handle(contact.b)) // level_geom := get_level_geom(sim_state, Level_Geom_Handle(contact.b))
tri := get_triangle( tri := get_triangle(
@ -755,6 +862,7 @@ pgs_solve_contacts :: proc(
inv_w := 1.0 / w inv_w := 1.0 / w
{ {
// r1, r2 := p1 - body1.x, p2 - body2.x // r1, r2 := p1 - body1.x, p2 - body2.x
v1 := body_velocity_at_point(body1, p1) v1 := body_velocity_at_point(body1, p1)
@ -1409,12 +1517,12 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
dt := f32(dt_64) dt := f32(dt_64)
inv_dt := f32(inv_dt_64) inv_dt := f32(inv_dt_64)
static_tlas := build_static_tlas(sim_state) sim_state.static_tlas = build_static_tlas(sim_state)
dyn_tlas := build_dynamic_tlas(sim_state, config) build_dynamic_tlas(sim_state, config, &sim_state.dynamic_tlas)
remove_invalid_contacts(sim_state, static_tlas, dyn_tlas) remove_invalid_contacts(sim_state, sim_state.static_tlas, sim_state.dynamic_tlas)
find_new_contacts(sim_state, &static_tlas, &dyn_tlas) find_new_contacts(sim_state, &sim_state.static_tlas, &sim_state.dynamic_tlas)
update_contacts(sim_state, &static_tlas) update_contacts(sim_state, &sim_state.static_tlas)
Solver :: enum { Solver :: enum {
XPBD, XPBD,
@ -1430,7 +1538,14 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
} }
case .PGS: case .PGS:
for _ in 0 ..< substeps { for _ in 0 ..< substeps {
pgs_substep(sim_state, &static_tlas, &dyn_tlas, config, dt, inv_dt) pgs_substep(
sim_state,
&sim_state.static_tlas,
&sim_state.dynamic_tlas,
config,
dt,
inv_dt,
)
} }
} }
@ -1564,11 +1679,7 @@ multiply_inv_intertia :: proc(body: Body_Ptr, vec: Vec3) -> (result: Vec3) {
return result return result
} }
apply_velocity_correction :: #force_inline proc "contextless" ( apply_velocity_correction :: #force_inline proc(body: Body_Ptr, impulse: Vec3, pos: Vec3) {
body: Body_Ptr,
impulse: Vec3,
pos: Vec3,
) {
apply_velocity_correction_linear(body, impulse) apply_velocity_correction_linear(body, impulse)
angular_impulse := lg.cross(pos - body.x, impulse) angular_impulse := lg.cross(pos - body.x, impulse)

View File

@ -26,8 +26,13 @@
package ui package ui
import "base:builtin"
import "base:intrinsics"
import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:math" import "core:math"
import "core:mem"
import "core:reflect"
import "core:sort" import "core:sort"
import "core:strconv" import "core:strconv"
import "core:strings" import "core:strings"
@ -1806,6 +1811,384 @@ end_panel :: proc(ctx: ^Context) {
pop_container(ctx) pop_container(ctx)
} }
keyval :: proc(ctx: ^Context, key: string, val: any, key_width := i32(100)) {
layout_row(ctx, {key_width, -1}, 0)
label(ctx, key)
label(ctx, fmt.tprintf("%v", val))
}
// inspect_array(ctx, ptr, n, info.elem_size, info.elem)
inspect_array :: proc(
ctx: ^Context,
name: string,
ptr: rawptr,
n: int,
elem_size: int,
type_info: ^runtime.Type_Info,
) {
if ptr == nil && n > 0 {
keyval(ctx, name, "nil")
return
}
if .ACTIVE in treenode(ctx, fmt.tprintf("%s len(%v)", name, n)) {
for i in 0 ..< n {
data := uintptr(ptr) + uintptr(elem_size * i)
inspect_value(ctx, fmt.tprintf("[%v]", i), any{rawptr(data), type_info.id})
}
}
}
inspect_struct :: proc(
ctx: ^Context,
name: string,
v: any,
info: runtime.Type_Info_Struct,
type_name: string,
) {
if .ACTIVE in treenode(ctx, name) {
if .raw_union in info.flags {
if type_name == "" {
keyval(ctx, name, "(raw union)")
} else {
keyval(ctx, name, fmt.tprintf("%v{}", type_name))
}
return
}
is_soa := info.soa_kind != .None
// is_empty := info.field_count == 0
if is_soa {
base_type_name: string
if v, ok := info.soa_base_type.variant.(runtime.Type_Info_Named); ok {
base_type_name = v.name
}
actual_field_count := info.field_count
n := uintptr(info.soa_len)
if info.soa_kind == .Slice {
actual_field_count = info.field_count - 1 // len
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
} else if info.soa_kind == .Dynamic {
actual_field_count = info.field_count - 3 // len, cap, allocator
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
}
for index in 0 ..< n {
field_count := -1
for i in 0 ..< actual_field_count {
field_name := info.names[i]
field_count += 1
if info.soa_kind == .Fixed {
t := info.types[i].variant.(runtime.Type_Info_Array).elem
t_size := uintptr(t.size)
if reflect.is_any(t) {
keyval(ctx, field_name, "any{}")
} else {
data := rawptr(uintptr(v.data) + info.offsets[i] + index * t_size)
inspect_value(ctx, field_name, any{data, t.id})
}
} else {
t := info.types[i].variant.(runtime.Type_Info_Multi_Pointer).elem
t_size := uintptr(t.size)
if reflect.is_any(t) {
keyval(ctx, field_name, "any{}")
} else {
field_ptr := (^^byte)(uintptr(v.data) + info.offsets[i])^
data := rawptr(uintptr(field_ptr) + index * t_size)
inspect_value(ctx, field_name, any{data, t.id})
}
}
}
}
} else {
field_count := -1
for field_name, i in info.names[:info.field_count] {
field_count += 1
if t := info.types[i]; reflect.is_any(t) {
keyval(ctx, field_name, "any{}")
} else {
data := rawptr(uintptr(v.data) + info.offsets[i])
inspect_value(ctx, field_name, any{data, t.id})
}
}
}
}
}
is_type_numeric :: proc(type_info: ^runtime.Type_Info) -> bool {
#partial switch info in type_info.variant {
case runtime.Type_Info_Float, runtime.Type_Info_Integer:
return true
}
return false
}
inspect_value :: proc(ctx: ^Context, name: string, v: any, type_name: string = "") {
if v.data == nil || v.id == nil {
label(ctx, "<nil>")
return
}
type_info := type_info_of(v.id)
switch info in type_info.variant {
case runtime.Type_Info_Any: // Ignore
case runtime.Type_Info_Parameters: // Ignore
case runtime.Type_Info_Named:
inspect_value(ctx, name, any{v.data, info.base.id}, info.name)
case runtime.Type_Info_Boolean,
runtime.Type_Info_Integer,
runtime.Type_Info_Rune,
runtime.Type_Info_Float,
runtime.Type_Info_Complex,
runtime.Type_Info_Quaternion,
runtime.Type_Info_Simd_Vector,
runtime.Type_Info_Type_Id,
runtime.Type_Info_Bit_Set,
runtime.Type_Info_Matrix,
runtime.Type_Info_Bit_Field,
runtime.Type_Info_Procedure,
runtime.Type_Info_String,
runtime.Type_Info_Enum:
if len(type_name) != 0 {
keyval(ctx, name, fmt.tprintf("%s(%v)", type_name, v))
} else {
keyval(ctx, name, fmt.tprintf("%v", v))
}
case runtime.Type_Info_Pointer:
// if v.id == typeid_of(^runtime.Type_Info) {
// reflect.write_type(fi.writer, (^^runtime.Type_Info)(v.data)^, &fi.n)
// } else {
// ptr := (^rawptr)(v.data)^
// if verb != 'p' && info.elem != nil {
// a := any{ptr, info.elem.id}
// elem := runtime.type_info_base(info.elem)
// if elem != nil {
// #partial switch e in elem.variant {
// case runtime.Type_Info_Array,
// runtime.Type_Info_Slice,
// runtime.Type_Info_Dynamic_Array,
// runtime.Type_Info_Map:
// if ptr == nil {
// io.write_string(fi.writer, "<nil>", &fi.n)
// return
// }
// if fi.indirection_level < 1 {
// fi.indirection_level += 1
// defer fi.indirection_level -= 1
// io.write_byte(fi.writer, '&')
// fmt_value(fi, a, verb)
// return
// }
// case runtime.Type_Info_Struct,
// runtime.Type_Info_Union,
// runtime.Type_Info_Bit_Field:
// if ptr == nil {
// io.write_string(fi.writer, "<nil>", &fi.n)
// return
// }
// if fi.indirection_level < 1 {
// fi.indirection_level += 1
// defer fi.indirection_level -= 1
// io.write_byte(fi.writer, '&', &fi.n)
// fmt_value(fi, a, verb)
// return
// }
// }
// }
// }
// fmt_pointer(fi, ptr, verb)
// }
case runtime.Type_Info_Soa_Pointer:
// ptr := (^runtime.Raw_Soa_Pointer)(v.data)^
// fmt_soa_pointer(fi, ptr, verb)
case runtime.Type_Info_Multi_Pointer:
// ptr := (^rawptr)(v.data)^
// if ptr == nil {
// io.write_string(fi.writer, "<nil>", &fi.n)
// return
// }
// if verb != 'p' && info.elem != nil {
// a := any{ptr, info.elem.id}
// elem := runtime.type_info_base(info.elem)
// if elem != nil {
// if n, ok := fi.optional_len.?; ok {
// fi.optional_len = nil
// fmt_array(fi, ptr, n, elem.size, elem, verb)
// return
// } else if fi.use_nul_termination {
// fi.use_nul_termination = false
// fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb)
// return
// }
// #partial switch e in elem.variant {
// case runtime.Type_Info_Integer:
// switch verb {
// case 's', 'q':
// switch elem.id {
// case u8:
// fmt_cstring(fi, cstring(ptr), verb)
// return
// case u16, u32, rune:
// n := search_nul_termination(ptr, elem.size, -1)
// fmt_array(fi, ptr, n, elem.size, elem, verb)
// return
// }
// }
// case runtime.Type_Info_Array,
// runtime.Type_Info_Slice,
// runtime.Type_Info_Dynamic_Array,
// runtime.Type_Info_Map:
// if fi.indirection_level < 1 {
// fi.indirection_level += 1
// defer fi.indirection_level -= 1
// io.write_byte(fi.writer, '&', &fi.n)
// fmt_value(fi, a, verb)
// return
// }
// case runtime.Type_Info_Struct, runtime.Type_Info_Union:
// if fi.indirection_level < 1 {
// fi.indirection_level += 1
// defer fi.indirection_level -= 1
// io.write_byte(fi.writer, '&', &fi.n)
// fmt_value(fi, a, verb)
// return
// }
// }
// }
// }
// fmt_pointer(fi, ptr, verb)
case runtime.Type_Info_Enumerated_Array:
// fi.record_level += 1
// defer fi.record_level -= 1
// if fi.hash {
// io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n)
// io.write_byte(fi.writer, '\n', &fi.n)
// defer {
// fmt_write_indent(fi)
// io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n)
// }
// indent := fi.indent
// fi.indent += 1
// defer fi.indent = indent
// for i in 0 ..< info.count {
// fmt_write_indent(fi)
// idx, ok := stored_enum_value_to_string(info.index, info.min_value, i)
// if ok {
// io.write_byte(fi.writer, '.', &fi.n)
// io.write_string(fi.writer, idx, &fi.n)
// } else {
// io.write_i64(fi.writer, i64(info.min_value) + i64(i), 10, &fi.n)
// }
// io.write_string(fi.writer, " = ", &fi.n)
// data := uintptr(v.data) + uintptr(i * info.elem_size)
// fmt_arg(fi, any{rawptr(data), info.elem.id}, verb)
// io.write_string(fi.writer, ",\n", &fi.n)
// }
// } else {
// io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n)
// defer io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n)
// for i in 0 ..< info.count {
// if i > 0 {io.write_string(fi.writer, ", ", &fi.n)}
// idx, ok := stored_enum_value_to_string(info.index, info.min_value, i)
// if ok {
// io.write_byte(fi.writer, '.', &fi.n)
// io.write_string(fi.writer, idx, &fi.n)
// } else {
// io.write_i64(fi.writer, i64(info.min_value) + i64(i), 10, &fi.n)
// }
// io.write_string(fi.writer, " = ", &fi.n)
// data := uintptr(v.data) + uintptr(i * info.elem_size)
// fmt_arg(fi, any{rawptr(data), info.elem.id}, verb)
// }
// }
case runtime.Type_Info_Array:
n := info.count
ptr := v.data
if info.count <= 4 && is_type_numeric(info.elem) {
keyval(ctx, name, v)
} else {
inspect_array(ctx, name, ptr, n, info.elem_size, info.elem)
}
case runtime.Type_Info_Slice:
slice := cast(^mem.Raw_Slice)v.data
n := slice.len
ptr := slice.data
inspect_array(ctx, name, ptr, n, info.elem_size, info.elem)
case runtime.Type_Info_Dynamic_Array:
array := cast(^mem.Raw_Dynamic_Array)v.data
n := array.len
ptr := array.data
inspect_array(ctx, name, ptr, n, info.elem_size, info.elem)
case runtime.Type_Info_Map:
if .ACTIVE in treenode(ctx, name) {
m := (^mem.Raw_Map)(v.data)
if m != nil {
if info.map_info == nil {
return
}
map_cap := uintptr(runtime.map_cap(m^))
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
j := 0
for bucket_index in 0 ..< map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
j += 1
key := runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index)
value := runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index)
inspect_value(
ctx,
fmt.tprintf("%v", any{rawptr(key), info.key.id}),
any{rawptr(value), info.value.id},
)
}
}
}
case runtime.Type_Info_Struct:
inspect_struct(ctx, name, v, info, "")
case runtime.Type_Info_Union:
// fmt_union(fi, v, verb, info, type_info.size)
}
}
@(private) @(private)
mouse_released :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_released_bits != nil} mouse_released :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_released_bits != nil}
@(private) @(private)