diff --git a/.vscode/launch.json b/.vscode/launch.json index 686d371..ff9ffac 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "type": "lldb", "request": "attach", "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") { @@ -53,7 +53,7 @@ "name": "Run Hot Reload (Linux / Mac)", "args": [], "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/build/hotreload/game.bin", + "program": "${workspaceFolder}/bin/hotreload/game.bin", }, { "type": "lldb", diff --git a/builder/builder.odin b/builder/builder.odin index 90c1235..667d7cb 100644 --- a/builder/builder.odin +++ b/builder/builder.odin @@ -238,7 +238,7 @@ main :: proc() { opts := Options { tracy = true, debug = true, - optimize = true, + optimize = false, run = true, } flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator) @@ -352,7 +352,7 @@ main :: proc() { "-sWASM_BIGINT", "-sWARN_ON_UNDEFINED_SYMBOLS=0", "-sALLOW_MEMORY_GROWTH", - "-sASSERTIONS", + "-sASSERTIONS=2", "--shell-file", "main_web/index_template.html", "--preload-file", diff --git a/game/assets/assets.odin b/game/assets/assets.odin index 2bbf201..55fe575 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -27,6 +27,7 @@ Loaded_Convex :: struct { mesh: collision.Convex, center_of_mass: rl.Vector3, inertia_tensor: lg.Matrix3f32, + total_volume: f32, } 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 diff --git a/game/fs.odin b/game/fs.odin index f011d84..6a07c60 100644 --- a/game/fs.odin +++ b/game/fs.odin @@ -39,7 +39,7 @@ init_physfs :: proc(args: []string) { // For non web builds, binary will live 2 subdirs below main content dir, so add it to search path when ODIN_OS != .JS { abs_path, ok := path_abs(args[0], context.temp_allocator) - assert(ok) + assert(ok) base_dir := path_dir(abs_path, 3) err := physfs.mount(strings.clone_to_cstring(base_dir, context.temp_allocator), "/", 1) diff --git a/game/game.odin b/game/game.odin index 88ecdce..5df2bb8 100644 --- a/game/game.odin +++ b/game/game.odin @@ -27,6 +27,7 @@ import "game:render" import rl "libs:raylib" import "libs:raylib/rlgl" import "libs:tracy" +import "name" import "ui" PIXEL_WINDOW_HEIGHT :: 360 @@ -122,6 +123,7 @@ SOLVER_CONFIG :: physics.Solver_Config { } Game_Memory :: struct { + name_container: name.Container, assetman: assets.Asset_Manager, runtime_world: Runtime_World, es: Editor_State, @@ -369,9 +371,15 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { &world.physics_scene, #hash("floor", "fnv32a"), physics.Body_Config { + name = name.from_string("Floor"), initial_pos = {0, -0.5, 0}, 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, #hash("ramp", "fnv32a"), physics.Body_Config { + name = name.from_string("Ramp"), initial_pos = {0, 0, 0}, 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,20 +406,33 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { &world.physics_scene, #hash("car", "fnv32a"), physics.Body_Config { - initial_pos = {0, 4, -10}, - initial_rot = linalg.QUATERNIONF32_IDENTITY, + name = name.from_string("Car"), + initial_pos = {0, 4, -10}, + initial_rot = linalg.QUATERNIONF32_IDENTITY, // initial_rot = linalg.quaternion_angle_axis( // math.RAD_PER_DEG * 180, // rl.Vector3{0, 0, 1}, // ) * // linalg.quaternion_angle_axis(math.RAD_PER_DEG * 30, rl.Vector3{1, 0, 0}), initial_ang_vel = {0, 0, 0}, - shape = physics.Shape_Convex { - mesh = car_convex.mesh, - center_of_mass = car_convex.center_of_mass, - inertia_tensor = auto_cast car_convex.inertia_tensor, + shapes = { + { + rel_q = linalg.QUATERNIONF32_IDENTITY, + inner_shape = physics.Shape_Convex { + mesh = car_convex.mesh, + center_of_mass = car_convex.center_of_mass, + 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, hash.fnv32a(slice.to_bytes([]int{(x | y << 8)})), 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, - shape = physics.Shape_Box{size = 1}, + shapes = { + { + rel_q = linalg.QUATERNIONF32_IDENTITY, + inner_shape = physics.Shape_Box{size = 1}, + }, + }, mass = 10, }, ) @@ -986,23 +1018,22 @@ draw_world :: proc(world: ^World) { if .ACTIVE in ui.header(ui_ctx, "Car", {.EXPANDED}) && car_body.alive && engine.alive { - ui_keyval :: proc(ctx: ^ui.Context, key: string, val: any) { - ui.layout_row(ctx, {100, -1}, 0) - ui.label(ctx, key) - ui.label(ctx, fmt.tprintf("%v", val)) - } - gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios) - ui.layout_row(ui_ctx, {100, -1}, 0) - 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) + 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) } + + if .ACTIVE in ui.header(ui_ctx, "Physics") { + physics.draw_debug_ui(ui_ctx, &world.physics_scene, SOLVER_CONFIG) + } + } else { + log.infof("Window closed") + world.debug_state.show_menu = false } } } @@ -1291,6 +1322,8 @@ game_init :: proc() { g_mem^ = Game_Memory{} + name.init(&g_mem.name_container) + name.setup_global_container(&g_mem.name_container) init_physifs_raylib_callbacks() assets.assetman_init(&g_mem.assetman) @@ -1310,6 +1343,7 @@ game_init :: proc() { @(export) game_shutdown :: proc() { + name.destroy() assets.shutdown(&g_mem.assetman) editor_state_destroy(&g_mem.es) delete(g_mem.es.point_selection) @@ -1339,6 +1373,7 @@ game_memory_size :: proc() -> int { game_hot_reloaded :: proc(mem: rawptr) { g_mem = (^Game_Memory)(mem) + name.setup_global_container(&g_mem.name_container) render.init(&g_mem.assetman) ui.rl_init() diff --git a/game/halfedge/halfedge.odin b/game/halfedge/halfedge.odin index 565943a..ce8a0da 100644 --- a/game/halfedge/halfedge.odin +++ b/game/halfedge/halfedge.odin @@ -4,6 +4,7 @@ import "core:hash/xxhash" import lg "core:math/linalg" import "core:mem" import "core:slice" +import "libs:tracy" Vec3 :: [3]f32 @@ -43,6 +44,7 @@ mesh_from_vertex_index_list :: proc( vertices_per_face: int = 3, allocator := context.allocator, ) -> Half_Edge_Mesh { + tracy.Zone() assert(vertices_per_face >= 3) num_faces := len(indices) / vertices_per_face @@ -277,6 +279,7 @@ copy_mesh :: proc( transform_mesh :: proc(mesh: ^Half_Edge_Mesh, mat: lg.Matrix4f32) { + tracy.Zone() mesh_center_avg_factor := 1.0 / f32(len(mesh.vertices)) new_center: Vec3 for i in 0 ..< len(mesh.vertices) { diff --git a/game/name/name.odin b/game/name/name.odin new file mode 100644 index 0000000..0a4c53d --- /dev/null +++ b/game/name/name.odin @@ -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] +} diff --git a/game/physics/bvh/debug.odin b/game/physics/bvh/debug.odin index 01332f4..598d877 100644 --- a/game/physics/bvh/debug.odin +++ b/game/physics/bvh/debug.odin @@ -6,9 +6,10 @@ import "core:fmt" import "core:log" import lg "core:math/linalg" import "game:debug" -import "libs:tracy" +import "game:ui" import rl "libs:raylib" import "libs:raylib/rlgl" +import "libs:tracy" _ :: fmt _ :: 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 { return Mesh { vertices = (cast([^]Vec3)mesh.vertices)[:mesh.vertexCount], diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin index 391d6ae..9818811 100644 --- a/game/physics/collision/convex.odin +++ b/game/physics/collision/convex.odin @@ -16,7 +16,7 @@ _ :: log Convex :: halfedge.Half_Edge_Mesh -BOX_CORNERS_NORM :: [8]Vec3 { +box_corners_norm := [8]Vec3 { {-1, 1, 1}, {-1, -1, 1}, {-1, 1, -1}, @@ -29,15 +29,28 @@ 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} +@(private = "file") +box_mesh: Convex + +@(init) +init_box_mesh :: proc() { + box_mesh = Convex(halfedge.mesh_from_vertex_index_list(box_corners_norm[:], box_indices[:], 4)) +} + +@(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) { - vertices := make([]Vec3, 8, context.temp_allocator) + convex = halfedge.copy_mesh(box_mesh, allocator) - for corner, i in BOX_CORNERS_NORM { - vertices[i] = box.pos + corner * box.rad + for &v in convex.vertices { + v.pos = box.pos + v.pos * box.rad } - convex = Convex(halfedge.mesh_from_vertex_index_list(vertices, box_indices[:], 4, allocator)) - return } @@ -649,5 +662,9 @@ ray_vs_convex :: proc( } } + if hit { + normal = lg.normalize0(normal) + } + return } diff --git a/game/physics/debug.odin b/game/physics/debug.odin index e805be0..0a90efa 100644 --- a/game/physics/debug.odin +++ b/game/physics/debug.odin @@ -1,16 +1,23 @@ package physics +import "bvh" import "core:fmt" import "core:log" import "core:math" import lg "core:math/linalg" +import "core:mem" +import "core:strings" import "game:debug" import he "game:halfedge" +import "game:name" import "game:ui" import rl "libs:raylib" import "libs:raylib/rlgl" import "libs:tracy" +_ :: name +_ :: mem +_ :: fmt _ :: log _ :: math _ :: debug @@ -24,6 +31,8 @@ draw_debug_shape :: proc( color: rl.Color, ) { 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() defer rlgl.PopMatrix() @@ -31,7 +40,7 @@ draw_debug_shape :: proc( rlgl.LoadIdentity() rlgl.MultMatrixf(cast([^]f32)&mat) - switch s in shape { + switch s in shape.inner_shape { case Shape_Box: rl.DrawCubeV(0, s.size, color) case Internal_Shape_Convex: @@ -45,6 +54,15 @@ draw_debug_scene :: proc(scene: ^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 { body := &sim_state.bodies_slice[i] if body.alive { @@ -59,13 +77,25 @@ draw_debug_scene :: proc(scene: ^Scene) { rl.DrawLine3D(pos, pos + y, rl.GREEN) rl.DrawLine3D(pos, pos + z, rl.BLUE) - draw_debug_shape( - sim_state, - body.shape, - body_get_shape_pos(body), - body.q, - debug.int_to_color(i32(i + 2)), - ) + it := shapes_iterator(sim_state, body.shape) + for shape in shapes_iterator_next(&it) { + draw_debug_shape( + sim_state, + shape^, + body_get_shape_pos(body), + body.q, + 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) - active_wheels := []int{0, 1} - - w, h: i32 = 500, 500 - - window_x: i32 = 0 - - for i in 0 ..< len(sim_state.suspension_constraints_slice) { - s := &sim_state.suspension_constraints_slice[i] - - if s.alive { - for idx in active_wheels { - if i == idx { - if ui.window( - ctx, - fmt.tprintf("Wheel %v", i), - ui.Rect{x = window_x, y = 0, w = w, h = h}, - ui.Options{}, - ) { - 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}, - ) - } - } - - { - 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 - } + 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) } } } } + + 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 + + ui.push_id(ctx, mem.any_to_bytes(i)) + defer ui.pop_id(ctx) + + if contact.type == .Body_vs_Body { + 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 strings.contains(title, search_str) { + ui.inspect_value(ctx, title, contact) + } + } + } + + if .ACTIVE in ui.treenode(ctx, "Dynamic TLAS") { + if sim_state.dynamic_tlas.built { + bvh.debug_ui_bvh_node_recursive( + ctx, + sim_state.dynamic_tlas.bvh_tree, + sim_state.dynamic_tlas.body_aabbs, + 0, + ) + } + } + + // active_wheels := []int{0, 1} + + // w, h: i32 = 500, 500 + + // window_x: i32 = 0 + + // for i in 0 ..< len(sim_state.suspension_constraints_slice) { + // s := &sim_state.suspension_constraints_slice[i] + + // if s.alive { + // for idx in active_wheels { + // if i == idx { + // if ui.window( + // ctx, + // fmt.tprintf("Wheel %v", i), + // ui.Rect{x = window_x, y = 0, w = w, h = h}, + // ui.Options{}, + // ) { + // 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}, + // ) + // } + // } + + // { + // 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) { diff --git a/game/physics/helpers.odin b/game/physics/helpers.odin index d5ea843..e4152be 100644 --- a/game/physics/helpers.odin +++ b/game/physics/helpers.odin @@ -1,9 +1,14 @@ package physics import "collision" +import "core:log" import "core:math" import lg "core:math/linalg" import "game:halfedge" +import "game:name" +import "libs:tracy" + +_ :: log inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) { tensor = radius * radius * (2.0 / 3.0) @@ -23,7 +28,7 @@ inertia_tensor_box :: proc(size: Vec3) -> (tensor: Matrix3) { 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 { case Shape_Box: tensor = inertia_tensor_box(s.size) @@ -35,6 +40,66 @@ inertia_tensor_collision_shape :: proc(shape: Input_Shape) -> (tensor: Matrix3) 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 { 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) { - #partial switch s in body.shape { - case Internal_Shape_Convex: - offset = -s.center_of_mass - } - return + return body.shape_offset } // 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) } -body_get_convex_shape_world :: proc( +shape_get_convex_local :: proc( sim_state: ^Sim_State, - body: Body_Ptr, + shape: Collision_Shape_Internal, allocator := context.temp_allocator, ) -> ( mesh: collision.Convex, ) { - switch s in body.shape { + switch s in shape { case Shape_Box: mesh = collision.box_to_convex(collision.Box{rad = s.size * 0.5}, allocator) case Internal_Shape_Convex: @@ -118,10 +179,125 @@ body_get_convex_shape_world :: proc( mesh = halfedge.copy_mesh(mesh, allocator) } - transform := - lg.matrix4_translate_f32(body_get_shape_pos(body)) * lg.matrix4_from_quaternion_f32(body.q) - halfedge.transform_mesh(&mesh, transform) + return +} +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 } @@ -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 { case Shape_Box: aabb.center = 0 @@ -149,22 +338,59 @@ shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) { aabb.center = s.center 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) { - 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 - 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 + return body_transform_shape_aabb(body, local_aabb) } rpm_to_angular_velocity :: proc(rpm: f32) -> f32 { diff --git a/game/physics/scene.odin b/game/physics/scene.odin index d2ac0bd..ae16459 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -3,6 +3,7 @@ package physics import "collision" import lg "core:math/linalg" import "game:container/spanpool" +import "game:name" import "libs:tracy" MAX_CONTACTS :: 1024 * 16 @@ -17,10 +18,12 @@ AABB :: struct { } Contact_Pair_Bodies :: struct { - a, b: Body_Handle, + a, b: Body_Handle, + shape_a, shape_b: i32, } Contact_Pair_Body_Level :: struct { a: Body_Handle, + shape_a: i32, tri_idx: i32, } @@ -29,15 +32,26 @@ Contact_Pair :: union { 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 { index_to_body_handle(int(min(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 { - return {index_to_body_handle(int(body)), tri_idx} +make_contact_pair_body_level :: proc( + 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 { @@ -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 bodies_slice: #soa[]Body, suspension_constraints_slice: #soa[]Suspension_Constraint, + shapes: [dynamic]Collision_Shape, first_free_body_plus_one: i32, first_free_suspension_constraint_plus_one: i32, first_free_engine_plus_one: i32, first_free_level_geom_plus_one: i32, + first_free_shape_plus_one: i32, // Persistent stuff for simulation contact_container: Contact_Container, @@ -90,6 +106,10 @@ Sim_State :: struct { // Level geometry geometry_vertices_pool: spanpool.Span_Pool(Vec3), 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 { @@ -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_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_shape_plus_one = src.first_free_shape_plus_one resize(&dst.bodies, len(src.bodies)) resize(&dst.suspension_constraints, len(src.suspension_constraints)) resize(&dst.engines, len(src.engines)) resize(&dst.level_geoms, len(src.level_geoms)) + resize(&dst.shapes, len(src.shapes)) dst.bodies_slice = dst.bodies[:] 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.level_geoms[:], src.level_geoms[:]) + copy(dst.shapes[:], src.shapes[:]) contact_container_copy(&dst.contact_container, src.contact_container) convex_container_copy(&dst.convex_container, src.convex_container) @@ -164,7 +187,11 @@ Body :: struct { inv_mass: f32, // Moment of inertia 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, } @@ -189,13 +216,30 @@ Shape_Convex :: struct { mesh: collision.Convex, center_of_mass: Vec3, inertia_tensor: Matrix3, + total_volume: f32, } -Collision_Shape :: union { +Collision_Shape_Internal :: union { Shape_Box, 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 { alive: bool, // 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] } -remove_shape :: proc(sim_state: ^Sim_State, shape: Collision_Shape) { - switch &s in shape { - case Shape_Box: - case Internal_Shape_Convex: - convex_container_remove(&sim_state.convex_container, s.mesh) +remove_shape :: proc(sim_state: ^Sim_State, idx: i32) { + cur_idx := idx + for { + shape := &sim_state.shapes[cur_idx] + + switch &s in shape.inner_shape { + case Shape_Box: + case Internal_Shape_Convex: + 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_Convex, } +Input_Shape :: struct { + rel_x: Vec3, + rel_q: Quat, + density_minus_one: f32, + inner_shape: Input_Shape_Internal, +} + Body_Config :: struct { + name: name.Name, initial_pos: Vec3, initial_rot: Quat, initial_vel: Vec3, initial_ang_vel: Vec3, - shape: Input_Shape, + shapes: []Input_Shape, mass: f32, inertia_mode: Body_Config_Inertia_Mode, // Unit inertia tensor @@ -521,7 +585,7 @@ calculate_body_params_from_config :: proc( if config.inertia_mode == .Explicit { inertia_tensor = config.inertia_tensor } else { - inertia_tensor = inertia_tensor_collision_shape(config.shape) + inertia_tensor = combined_inertia_tensor(config.shapes) } inertia_tensor = inertia_tensor * Matrix3(config.mass) @@ -530,18 +594,64 @@ calculate_body_params_from_config :: proc( return } -add_shape :: proc(sim_state: ^Sim_State, shape: Input_Shape) -> (result: Collision_Shape) { - switch s in shape { - case Shape_Box: - result = s - case Shape_Convex: - convex: Internal_Shape_Convex - convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh) - convex.center = s.mesh.center - convex.extent = s.mesh.extent - convex.center_of_mass = s.center_of_mass - convex.inertia_tensor = s.inertia_tensor - result = convex +alloc_shape :: proc(sim_state: ^Sim_State) -> (idx: i32) { + 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: + new_shape.inner_shape = s + case Shape_Convex: + convex: Internal_Shape_Convex + convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh) + convex.center = s.mesh.center + convex.extent = s.mesh.extent + convex.center_of_mass = s.center_of_mass + convex.inertia_tensor = s.inertia_tensor + new_shape.inner_shape = convex + } } return @@ -553,7 +663,10 @@ initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config: body.v = config.initial_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) } @@ -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) { // Inefficient, but eh 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) } @@ -855,6 +971,7 @@ destry_sim_state :: proc(sim_state: ^Sim_State) { delete_soa(sim_state.suspension_constraints) delete(sim_state.engines) delete(sim_state.level_geoms) + delete(sim_state.shapes) delete_soa(sim_state.contact_container.contacts) 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.geometry_vertices_pool) spanpool.destroy_spanpool(&sim_state.geometry_indices_pool) + + dynamic_tlas_destroy(&sim_state.dynamic_tlas) } destroy_physics_scene :: proc(scene: ^Scene) { diff --git a/game/physics/shapes.odin b/game/physics/shapes.odin index e9a2ee3..5b74ac9 100644 --- a/game/physics/shapes.odin +++ b/game/physics/shapes.odin @@ -2,6 +2,7 @@ package physics import "game:container/spanpool" import he "game:halfedge" +import "libs:tracy" Convex_Container :: struct { vertices: spanpool.Span_Pool(he.Vertex), @@ -34,6 +35,7 @@ convex_container_get_mesh :: proc( ) -> ( mesh: he.Half_Edge_Mesh, ) { + tracy.Zone() mesh.vertices = spanpool.resolve_slice(&container.vertices, handle.vertices) mesh.faces = spanpool.resolve_slice(&container.faces, handle.faces) mesh.edges = spanpool.resolve_slice(&container.edges, handle.edges) diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 08fae15..895ebab 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -12,8 +12,10 @@ import "core:math/rand" import "core:slice" import "game:debug" import he "game:halfedge" +import "game:name" import "libs:tracy" +_ :: name _ :: log _ :: rand _ :: math @@ -77,6 +79,7 @@ Static_TLAS :: struct { Dynamic_TLAS :: struct { bvh_tree: bvh.BVH, body_aabbs: []bvh.AABB, + built: bool, } 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 // 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() - 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) { @@ -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( @@ -251,19 +273,28 @@ raycast_bodies :: proc( 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))) - shape := body_get_convex_shape_world(sim_state, body, context.temp_allocator) - hit_t, _, tmp_normal, _, ok := collision.ray_vs_convex( - shape, - ray.origin, - ray.dir, - distance, - ) + 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, + ) - if ok && (!hit || hit_t < t) { - t = hit_t - normal = tmp_normal - hit = true + hit_t, _, tmp_normal, _, ok := collision.ray_vs_convex( + mesh, + ray.origin, + ray.dir, + distance, + ) + + if ok && (!hit || hit_t < t) { + t = hit_t + normal = tmp_normal + hit = true + } } } } @@ -384,10 +415,16 @@ remove_invalid_contacts :: proc( pair_from_contact :: proc(contact: Contact) -> (pair: Contact_Pair) { 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 { pair = make_contact_pair_body_level( i32(body_handle_to_index(contact.a)), + contact.shape_a, contact.tri_idx, ) } @@ -433,24 +470,70 @@ find_new_contacts :: proc( for j in 0 ..< leaf_node.prim_len { other_body_idx := 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] - pair := make_contact_pair_bodies(i32(body_idx), i32(other_body_idx)) if body_idx != other_body_idx && - (bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) && - !(pair in sim_state.contact_container.lookup) { + (bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) { + 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) - resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1) - contact := &sim_state.contact_container.contacts[new_contact_idx] + shape_a_aabb := shape_get_aabb(shape_a^) + shape_a_aabb = body_transform_shape_aabb(body, shape_a_aabb) - contact^ = Contact { - type = .Body_vs_Body, - a = Body_Handle(i + 1), - b = i32(other_body_idx + 1), + 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^ = Contact { + type = .Body_vs_Body, + a = Body_Handle(i + 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,21 +554,43 @@ find_new_contacts :: proc( prim_aabb := get_triangle_aabb(tri) level_geom_handle := static_tlas.tri_to_level_geom[tri_idx] - pair := make_contact_pair_body_level(i32(body_idx), i32(tri_idx)) - aabb_overlaps := bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) || true - if aabb_overlaps && !(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] + if bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) { - contact^ = Contact { - type = .Body_vs_Level, - a = index_to_body_handle(i), - b = i32(level_geom_handle), - tri_idx = i32(tri_idx), + 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) + resize_soa( + &sim_state.contact_container.contacts, + new_contact_idx + 1, + ) + contact := &sim_state.contact_container.contacts[new_contact_idx] + + contact^ = Contact { + type = .Body_vs_Level, + a = index_to_body_handle(i), + shape_a = shape_idx, + b = i32(level_geom_handle), + tri_idx = i32(tri_idx), + } + + sim_state.contact_container.lookup[pair] = i32(new_contact_idx) + } } - - sim_state.contact_container.lookup[pair] = i32(new_contact_idx) } } } @@ -557,6 +662,8 @@ Contact :: struct { a: Body_Handle, // Body_vs_Body - Body_Handle, Body_vs_Level - Level_Geom_Handle b: i32, + // shape index in each body + shape_a, shape_b: i32, tri_idx: i32, prev_x_a, prev_x_b: Vec3, prev_q_a, prev_q_b: Quat, @@ -622,12 +729,12 @@ update_contacts :: proc(sim_state: ^Sim_State, static_tlas: ^Static_TLAS) { // 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 switch contact.type { 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: // level_geom := get_level_geom(sim_state, Level_Geom_Handle(contact.b)) tri := get_triangle( @@ -755,6 +862,7 @@ pgs_solve_contacts :: proc( inv_w := 1.0 / w + { // r1, r2 := p1 - body1.x, p2 - body2.x 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) inv_dt := f32(inv_dt_64) - static_tlas := build_static_tlas(sim_state) - dyn_tlas := build_dynamic_tlas(sim_state, config) + sim_state.static_tlas = build_static_tlas(sim_state) + build_dynamic_tlas(sim_state, config, &sim_state.dynamic_tlas) - remove_invalid_contacts(sim_state, static_tlas, dyn_tlas) - find_new_contacts(sim_state, &static_tlas, &dyn_tlas) - update_contacts(sim_state, &static_tlas) + remove_invalid_contacts(sim_state, sim_state.static_tlas, sim_state.dynamic_tlas) + find_new_contacts(sim_state, &sim_state.static_tlas, &sim_state.dynamic_tlas) + update_contacts(sim_state, &sim_state.static_tlas) Solver :: enum { XPBD, @@ -1430,7 +1538,14 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi } case .PGS: 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 } -apply_velocity_correction :: #force_inline proc "contextless" ( - body: Body_Ptr, - impulse: Vec3, - pos: Vec3, -) { +apply_velocity_correction :: #force_inline proc(body: Body_Ptr, impulse: Vec3, pos: Vec3) { apply_velocity_correction_linear(body, impulse) angular_impulse := lg.cross(pos - body.x, impulse) diff --git a/game/ui/microui.odin b/game/ui/microui.odin index 1250583..60fc456 100644 --- a/game/ui/microui.odin +++ b/game/ui/microui.odin @@ -26,8 +26,13 @@ package ui +import "base:builtin" +import "base:intrinsics" +import "base:runtime" import "core:fmt" import "core:math" +import "core:mem" +import "core:reflect" import "core:sort" import "core:strconv" import "core:strings" @@ -1806,6 +1811,384 @@ end_panel :: proc(ctx: ^Context) { 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, "") + 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, "", &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, "", &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, "", &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) mouse_released :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_released_bits != nil} @(private)