diff --git a/game/container/spanpool/spanpool.odin b/game/container/spanpool/spanpool.odin index 06b59d4..0de440f 100644 --- a/game/container/spanpool/spanpool.odin +++ b/game/container/spanpool/spanpool.odin @@ -28,9 +28,17 @@ destroy_spanpool :: proc(s: ^$T/Span_Pool($E)) { delete(s.free_spans) } +// TODO: use everywhere +is_handle_valid :: proc(s: ^$T/Span_Pool($E), handle: Handle) -> bool { + return( + handle.gen != 0 && + int(handle.first + handle.len) <= len(s.elems) && + s.generations[handle.first] == handle.gen \ + ) +} + resolve_slice :: proc(s: ^$T/Span_Pool($E), handle: Handle, loc := #caller_location) -> []E { - assert(int(handle.first + handle.len) <= len(s.elems), "invalid spanpool handle", loc) - assert(s.generations[handle.first] == handle.gen, "invalid spanpool handle", loc) + assert(is_handle_valid(s, handle), "invalid spanpool handle", loc) return s.elems[handle.first:handle.first + handle.len] } diff --git a/game/game.odin b/game/game.odin index a040fef..13f10a7 100644 --- a/game/game.odin +++ b/game/game.odin @@ -118,7 +118,7 @@ Car :: struct { SOLVER_CONFIG :: physics.Solver_Config { timestep = 1.0 / 60, gravity = rl.Vector3{0, -9.8, 0}, - substreps_minus_one = 8 - 1, + substreps_minus_one = 4 - 1, } Game_Memory :: struct { @@ -359,30 +359,34 @@ World_Update_Config :: struct { } update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { + rl.BeginDrawing() + rl.BeginMode3D(game_camera_3d()) if !world.pause { car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") car_bounds := rl.GetModelBoundingBox(car_model) world.car_com = (car_bounds.min + car_bounds.max) / 2 - physics.immediate_body( - &world.physics_scene, - #hash("floor", "fnv32a"), - physics.Body_Config { - initial_pos = {0, -0.5, 0}, - initial_rot = linalg.QUATERNIONF32_IDENTITY, - shape = physics.Shape_Box{size = {1000, 1, 1000}}, - }, - ) + if true { + physics.immediate_body( + &world.physics_scene, + #hash("floor", "fnv32a"), + physics.Body_Config { + initial_pos = {0, -0.5, 0}, + initial_rot = linalg.QUATERNIONF32_IDENTITY, + shape = physics.Shape_Box{size = {100, 1, 100}}, + }, + ) - physics.immediate_body( - &world.physics_scene, - #hash("ramp", "fnv32a"), - physics.Body_Config { - 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}}, - }, - ) + physics.immediate_body( + &world.physics_scene, + #hash("ramp", "fnv32a"), + physics.Body_Config { + 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}}, + }, + ) + } car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj") @@ -614,6 +618,26 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { wheel.turn_angle = TURN_ANGLE * turn_vel_correction * turn_input } + if len(world.track.points) > 1 { + interpolated_points := calculate_spline_interpolated_points( + world.track.points[:], + context.temp_allocator, + ) + track_verts, track_inds := spline_generate_mesh( + interpolated_points, + context.temp_allocator, + ) + physics.immediate_level_geom( + &world.physics_scene, + #hash("track", "fnv32a"), + { + rotation = linalg.QUATERNIONF32_IDENTITY, + vertices = track_verts, + indices = track_inds, + }, + ) + } + physics.simulate( &world.physics_scene, SOLVER_CONFIG, @@ -1019,7 +1043,6 @@ draw_world :: proc(world: ^World) { draw :: proc() { tracy.Zone() - rl.BeginDrawing() defer rl.EndDrawing() rl.ClearBackground(rl.GRAY) render.clear_stencil() @@ -1038,14 +1061,13 @@ draw :: proc() { // ) { - rl.BeginMode3D(camera) defer rl.EndMode3D() draw_world(world) { // Debug draw spline road - { + if false { rlgl.EnableWireMode() defer rlgl.DisableWireMode() diff --git a/game/halfedge/halfedge.odin b/game/halfedge/halfedge.odin index 845f89f..565943a 100644 --- a/game/halfedge/halfedge.odin +++ b/game/halfedge/halfedge.odin @@ -102,8 +102,8 @@ mesh_from_vertex_index_list :: proc( faces[f].edge = Edge_Index(e) } - next_edge := f * vertices_per_face + ((i + 1) % vertices_per_face) - prev_edge := f * vertices_per_face + ((i - 1) % vertices_per_face) + next_edge := f * vertices_per_face + ((i + 1) %% vertices_per_face) + prev_edge := f * vertices_per_face + ((i - 1) %% vertices_per_face) edges[e] = { origin = Vertex_Index(index), diff --git a/game/physics/bvh/bvh.odin b/game/physics/bvh/bvh.odin index cbd26c1..ef74022 100644 --- a/game/physics/bvh/bvh.odin +++ b/game/physics/bvh/bvh.odin @@ -8,8 +8,8 @@ import "core:math" import lg "core:math/linalg" import "core:mem" import "game:debug" -import "libs:tracy" import rl "libs:raylib" +import "libs:tracy" _ :: log _ :: rl @@ -70,6 +70,7 @@ build_bvh_from_mesh :: proc(mesh: Mesh, allocator := context.allocator) -> (mesh assert(len(indices) % 3 == 0) bvh := &mesh_bvh.bvh + mesh_bvh.mesh = mesh num_triangles := len(indices) / 3 @@ -93,18 +94,10 @@ build_bvh_from_mesh :: proc(mesh: Mesh, allocator := context.allocator) -> (mesh i1, i2, i3 := indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2] v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3] - centroids[i] = (v1 + v2 + v3) * 0.33333333333 + centroids[i] = (v1 + v2 + v3) / 3.0 - aabbs[i].min = Vec3 { - min(v1.x, v2.x, v3.x), - min(v1.y, v2.y, v3.y), - min(v1.z, v2.z, v3.z), - } - aabbs[i].max = Vec3 { - max(v1.x, v2.x, v3.x), - max(v1.y, v2.y, v3.y), - max(v1.z, v2.z, v3.z), - } + aabbs[i].min = lg.min_triple(v1, v2, v3) + aabbs[i].max = lg.max_triple(v1, v2, v3) size := aabbs[i].max - aabbs[i].min assert(size.x >= 0) @@ -286,8 +279,10 @@ iterator_intersect_leaf_aabb :: proc( ) { it.bvh = bvh it.bounds = bounds - queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator) - queue.push_back(&it.nodes_to_process, 0) + if len(it.bvh.nodes) > 0 { + queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator) + queue.push_back(&it.nodes_to_process, 0) + } return it } @@ -302,8 +297,10 @@ iterator_intersect_leaf_ray :: proc( it.bvh = bvh it.ray = ray it.min_t = distance - queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator) - queue.push_back(&it.nodes_to_process, 0) + if len(it.bvh.nodes) > 0 { + queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator) + queue.push_back(&it.nodes_to_process, 0) + } return it } diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin index 2e02de1..391d6ae 100644 --- a/game/physics/collision/convex.odin +++ b/game/physics/collision/convex.odin @@ -41,6 +41,26 @@ box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Conv return } +double_sided_triangle_indices := [6]u16{0, 1, 2, 0, 2, 1} + +double_sided_triangle_to_convex :: proc( + tri: [3]Vec3, + allocator := context.allocator, +) -> ( + convex: Convex, +) { + tri := tri + convex = Convex( + halfedge.mesh_from_vertex_index_list( + tri[:], + double_sided_triangle_indices[:], + 3, + allocator, + ), + ) + return +} + Contact_Type_Face :: struct { face_idx_a: halfedge.Face_Index, face_idx_b: halfedge.Face_Index, @@ -85,8 +105,8 @@ convex_vs_convex_sat :: proc(a, b: Convex) -> (manifold: Contact_Manifold, colli return } - is_face_a_contact := (face_query_a.separation + 0.2) > edge_separation - is_face_b_contact := (face_query_b.separation + 0.2) > edge_separation + is_face_a_contact := (face_query_a.separation + 0.001) > edge_separation + is_face_b_contact := (face_query_b.separation + 0.001) > edge_separation if is_face_a_contact || is_face_b_contact { manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b) @@ -287,7 +307,7 @@ create_face_contact_manifold :: proc( ) { tracy.Zone() - is_ref_a := (face_query_a.separation + 0.1) > face_query_b.separation + is_ref_a := (face_query_a.separation + 0.01) > face_query_b.separation ref_face_query := is_ref_a ? face_query_a : face_query_b ref_convex := is_ref_a ? a : b inc_convex := is_ref_a ? b : a @@ -400,7 +420,18 @@ create_face_contact_manifold :: proc( clipping_plane_center /= f32(num) } - clipping_plane := plane_from_point_normal(clipping_face_vert, clipping_face.normal) + clipping_normal := clipping_face.normal + + // If adjacent face is facing backwards from us (like a double sided triangle) + // use the edge normal + if lg.dot(ref_face.normal, clipping_face.normal) < -0.99 { + e1, e2 := halfedge.get_edge_points(ref_convex, edge) + + clipping_normal = lg.cross(e2 - e1, clipping_face_vert - clipping_plane_center) + clipping_normal = -lg.normalize0(lg.cross(e2 - e1, clipping_normal)) + } + + clipping_plane := plane_from_point_normal(clipping_face_vert, clipping_normal) // Actual clipping { @@ -578,7 +609,7 @@ create_edge_contact_manifold :: proc( edge_idx_b = edge_b, } manifold.normal = separating_plane.normal - manifold.separation = separation + manifold.separation = lg.dot(ps[0] - ps[1], separating_plane.normal) manifold.points_a[0] = ps[0] manifold.points_b[0] = ps[1] manifold.points_len = 1 diff --git a/game/physics/debug.odin b/game/physics/debug.odin index 8971cf8..dbeb5cf 100644 --- a/game/physics/debug.odin +++ b/game/physics/debug.odin @@ -69,6 +69,21 @@ draw_debug_scene :: proc(scene: ^Scene) { } } + if false { + for &level_geom, geom_idx in sim_state.level_geoms { + if level_geom.alive { + vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + + for i in 0 ..< len(indices) / 3 { + i1, i2, i3 := indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2] + v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3] + + rl.DrawTriangle3D(v1, v2, v3, debug.int_to_color(i32(geom_idx + 1))) + } + } + } + } + for _, i in sim_state.suspension_constraints { wheel := &sim_state.suspension_constraints_slice[i] if wheel.alive { @@ -130,7 +145,8 @@ draw_debug_scene :: proc(scene: ^Scene) { points_a_slice, points_b_slice := points_a[:contact.manifold.points_len], points_b[:contact.manifold.points_len] debug_transform_points_local_to_world(get_body(sim_state, contact.a), points_a_slice) - debug_transform_points_local_to_world(get_body(sim_state, contact.b), points_b_slice) + b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY + debug_transform_points_local_to_world(get_body(sim_state, b_handle), points_b_slice) debug_draw_manifold_points( contact, -1, diff --git a/game/physics/helpers.odin b/game/physics/helpers.odin index beb3bd3..d5ea843 100644 --- a/game/physics/helpers.odin +++ b/game/physics/helpers.odin @@ -125,6 +125,21 @@ body_get_convex_shape_world :: proc( return } +level_geom_get_convex_shape :: proc( + sim_state: ^Sim_State, + level_geom: Level_Geom_Ptr, + tri_idx: int, + allocator := context.temp_allocator, +) -> ( + mesh: collision.Convex, +) { + vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + return collision.double_sided_triangle_to_convex( + get_transformed_triangle(vertices, indices, tri_idx, level_geom.x, level_geom.q), + allocator, + ) +} + shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) { switch s in shape { case Shape_Box: diff --git a/game/physics/immediate.odin b/game/physics/immediate.odin index 5b2b018..9c0a267 100644 --- a/game/physics/immediate.odin +++ b/game/physics/immediate.odin @@ -170,6 +170,29 @@ immediate_engine :: proc( return } +immediate_level_geom :: proc( + scene: ^Scene, + id: u32, + config: Level_Geom_Config, +) -> ( + handle: Level_Geom_Handle, +) { + state := &scene.solver_state + sim_state := get_sim_state(scene) + h, ok := immediate_container_find_or_add( + &state.immediate_level_geoms, + state.simulation_frame, + id, + ) + + if ok { + update_level_geom_from_config(sim_state, get_level_geom(sim_state, h^), config) + } else { + h^ = add_level_geom(sim_state, config) + } + return +} + prune_immediate :: proc(scene: ^Scene) { tracy.Zone() @@ -181,6 +204,10 @@ prune_immediate :: proc(scene: ^Scene) { state.simulation_frame, ) removed_engines := immediate_container_prune(&state.immediate_engines, state.simulation_frame) + removed_level_geoms := immediate_container_prune( + &state.immediate_level_geoms, + state.simulation_frame, + ) for handle in removed_bodies { remove_body(sim_state, handle) } @@ -190,4 +217,7 @@ prune_immediate :: proc(scene: ^Scene) { for handle in removed_engines { remove_engine(sim_state, handle) } + for handle in removed_level_geoms { + remove_level_geom(sim_state, handle) + } } diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 984369b..d2ac0bd 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -16,10 +16,28 @@ AABB :: struct { extent: Vec3, } -Contact_Pair :: [2]i32 +Contact_Pair_Bodies :: struct { + a, b: Body_Handle, +} +Contact_Pair_Body_Level :: struct { + a: Body_Handle, + tri_idx: i32, +} -make_contact_pair :: proc(body_a: i32, body_b: i32) -> Contact_Pair { - return {min(body_a, body_b), max(body_a, body_b)} +Contact_Pair :: union { + Contact_Pair_Bodies, + Contact_Pair_Body_Level, +} + +make_contact_pair_bodies :: proc(body_a: i32, body_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))), + } +} + +make_contact_pair_body_level :: proc(body: i32, tri_idx: i32) -> Contact_Pair_Body_Level { + return {index_to_body_handle(int(body)), tri_idx} } Contact_Container :: struct { @@ -46,9 +64,11 @@ Sim_State :: struct { bodies: #soa[dynamic]Body, suspension_constraints: #soa[dynamic]Suspension_Constraint, engines: [dynamic]Engine, + level_geoms: [dynamic]Level_Geom, // Number of alive bodies num_bodies: i32, num_engines: i32, + num_level_geoms: i32, // 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, @@ -56,6 +76,7 @@ Sim_State :: struct { 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, // Persistent stuff for simulation contact_container: Contact_Container, @@ -65,6 +86,10 @@ Sim_State :: struct { // Engine array data rpm_torque_curves_pool: spanpool.Span_Pool([2]f32), gear_ratios_pool: spanpool.Span_Pool(f32), + + // Level geometry + geometry_vertices_pool: spanpool.Span_Pool(Vec3), + geometry_indices_pool: spanpool.Span_Pool(u16), } Scene :: struct { @@ -86,6 +111,7 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) { 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)) dst.bodies_slice = dst.bodies[:] dst.suspension_constraints_slice = dst.suspension_constraints[:] @@ -97,11 +123,14 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) { dst.suspension_constraints[i] = src.suspension_constraints[i] } copy(dst.engines[:], src.engines[:]) + copy(dst.level_geoms[:], src.level_geoms[:]) contact_container_copy(&dst.contact_container, src.contact_container) convex_container_copy(&dst.convex_container, src.convex_container) spanpool.copy(&dst.rpm_torque_curves_pool, src.rpm_torque_curves_pool) spanpool.copy(&dst.gear_ratios_pool, src.gear_ratios_pool) + spanpool.copy(&dst.geometry_vertices_pool, src.geometry_vertices_pool) + spanpool.copy(&dst.geometry_indices_pool, src.geometry_indices_pool) } copy_physics_scene :: proc(dst, src: ^Scene) { @@ -299,14 +328,30 @@ Engine :: struct { next_plus_one: i32, } +Geometry_Handle :: struct { + vertices: spanpool.Handle, + indices: spanpool.Handle, +} + +Level_Geom :: struct { + alive: bool, + geometry: Geometry_Handle, + x: Vec3, + q: Quat, + next_plus_one: i32, +} + // Index plus one, so handle 0 maps to invalid body Body_Handle :: distinct i32 Suspension_Constraint_Handle :: distinct i32 Engine_Handle :: distinct i32 +// Handle for static geometry +Level_Geom_Handle :: distinct i32 INVALID_BODY :: Body_Handle(0) INVALID_SUSPENSION_CONSTRAINT :: Suspension_Constraint_Handle(0) INVALID_ENGINE :: Engine_Handle(0) +INVALID_LEVEL_GEOM :: Level_Geom_Handle(0) is_body_handle_valid :: proc(handle: Body_Handle) -> bool { return i32(handle) > 0 @@ -317,27 +362,41 @@ is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Hand is_engine_handle_valid :: proc(handle: Engine_Handle) -> bool { return i32(handle) > 0 } +is_level_geom_handle_valid :: proc(handle: Level_Geom_Handle) -> bool { + return i32(handle) > 0 +} is_handle_valid :: proc { is_body_handle_valid, is_suspension_constraint_handle_valid, is_engine_handle_valid, + is_level_geom_handle_valid, } index_to_body_handle :: proc(idx: int) -> Body_Handle { return Body_Handle(idx + 1) } +index_to_level_geom :: proc(idx: int) -> Level_Geom_Handle { + return Level_Geom_Handle(idx + 1) +} + body_handle_to_index :: proc(handle: Body_Handle) -> int { return int(handle) - 1 } +level_geom_handle_to_index :: proc(handle: Level_Geom_Handle) -> int { + return int(handle) - 1 +} + handle_to_index :: proc { body_handle_to_index, + level_geom_handle_to_index, } Body_Ptr :: #soa^#soa[]Body Suspension_Constraint_Ptr :: #soa^#soa[]Suspension_Constraint Engine_Ptr :: ^Engine +Level_Geom_Ptr :: ^Level_Geom _invalid_body: #soa[1]Body _invalid_body_slice := _invalid_body[:] @@ -372,6 +431,11 @@ flip_sim_state :: proc(scene: ^Scene) { get_body :: proc(sim_state: ^Sim_State, handle: Body_Handle) -> Body_Ptr { index := int(handle) - 1 if index < 0 || index >= len(sim_state.bodies_slice) { + _invalid_body_slice[0] = { + alive = true, + q = lg.QUATERNIONF32_IDENTITY, + prev_q = lg.QUATERNIONF32_IDENTITY, + } return &_invalid_body_slice[0] } @@ -438,6 +502,13 @@ Engine_Config :: struct { axle: Drive_Axle_Config, } +Level_Geom_Config :: struct { + position: Vec3, + rotation: Quat, + vertices: []Vec3, + indices: []u16, +} + calculate_body_params_from_config :: proc( config: Body_Config, ) -> ( @@ -701,6 +772,79 @@ remove_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) { } } +invalid_level_geom: Level_Geom + +get_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) -> Level_Geom_Ptr { + index := int(handle) - 1 + if index < 0 || index >= len(sim_state.bodies_slice) { + return &invalid_level_geom + } + + return &sim_state.level_geoms[index] +} + +get_level_geom_data :: proc( + sim_state: ^Sim_State, + handle: Geometry_Handle, +) -> ( + vertices: []Vec3, + indices: []u16, +) { + vertices = spanpool.resolve_slice(&sim_state.geometry_vertices_pool, handle.vertices) + indices = spanpool.resolve_slice(&sim_state.geometry_indices_pool, handle.indices) + return +} + +update_level_geom_from_config :: proc( + sim_state: ^Sim_State, + level_geom: Level_Geom_Ptr, + config: Level_Geom_Config, +) { + level_geom.x = config.position + level_geom.q = config.rotation + + if spanpool.is_handle_valid(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) { + spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) + spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices) + } + + level_geom.geometry.vertices = spanpool.allocate_elems( + &sim_state.geometry_vertices_pool, + ..config.vertices, + ) + level_geom.geometry.indices = spanpool.allocate_elems( + &sim_state.geometry_indices_pool, + ..config.indices, + ) +} + +add_level_geom :: proc(sim_state: ^Sim_State, config: Level_Geom_Config) -> Level_Geom_Handle { + sim_state.num_level_geoms += 1 + + level_geom: Level_Geom + level_geom.alive = true + update_level_geom_from_config(sim_state, &level_geom, config) + + if sim_state.first_free_level_geom_plus_one > 0 { + index := sim_state.first_free_level_geom_plus_one + new_level_geom := get_level_geom(sim_state, Level_Geom_Handle(index)) + next_plus_one := new_level_geom.next_plus_one + new_level_geom^ = level_geom + sim_state.first_free_level_geom_plus_one = next_plus_one + return Level_Geom_Handle(index) + } + + append(&sim_state.level_geoms, level_geom) + index := len(sim_state.level_geoms) + return Level_Geom_Handle(index) +} + +remove_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) { + level_geom := get_level_geom(sim_state, handle) + + spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) + spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices) +} _get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 { return sim_state.first_free_body_plus_one - 1 @@ -709,12 +853,16 @@ _get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 { destry_sim_state :: proc(sim_state: ^Sim_State) { delete_soa(sim_state.bodies) delete_soa(sim_state.suspension_constraints) + delete(sim_state.engines) + delete(sim_state.level_geoms) + delete_soa(sim_state.contact_container.contacts) delete_map(sim_state.contact_container.lookup) - delete(sim_state.engines) convex_container_destroy(&sim_state.convex_container) spanpool.destroy_spanpool(&sim_state.rpm_torque_curves_pool) spanpool.destroy_spanpool(&sim_state.gear_ratios_pool) + spanpool.destroy_spanpool(&sim_state.geometry_vertices_pool) + spanpool.destroy_spanpool(&sim_state.geometry_indices_pool) } destroy_physics_scene :: proc(scene: ^Scene) { diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index f8fecb1..dba09d2 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -1,5 +1,6 @@ package physics +import rl " libs:raylib" import "base:runtime" import "bvh" import "collision" @@ -38,6 +39,7 @@ Solver_State :: struct { immediate_bodies: Immediate_Container(Body_Handle), immediate_suspension_constraints: Immediate_Container(Suspension_Constraint_Handle), immediate_engines: Immediate_Container(Engine_Handle), + immediate_level_geoms: Immediate_Container(Level_Geom_Handle), } copy_solver_state :: proc(dst, src: ^Solver_State) { @@ -50,12 +52,14 @@ copy_solver_state :: proc(dst, src: ^Solver_State) { &src.immediate_suspension_constraints, ) immediate_container_copy(&dst.immediate_engines, &src.immediate_engines) + immediate_container_copy(&dst.immediate_level_geoms, &src.immediate_level_geoms) } destroy_solver_state :: proc(state: ^Solver_State) { immediate_container_destroy(&state.immediate_bodies) immediate_container_destroy(&state.immediate_suspension_constraints) immediate_container_destroy(&state.immediate_engines) + immediate_container_destroy(&state.immediate_level_geoms) } MAX_STEPS :: 10 @@ -65,15 +69,85 @@ Step_Mode :: enum { Single, } +Static_TLAS :: struct { + bvh_tree: bvh.Mesh_BVH, + tri_to_level_geom: []Level_Geom_Handle, +} + // Top Level Acceleration Structure -TLAS :: struct { +Dynamic_TLAS :: struct { bvh_tree: bvh.BVH, body_aabbs: []bvh.AABB, } +build_static_tlas :: proc(sim_state: ^Sim_State) -> Static_TLAS { + tracy.Zone() + + num_vertices, num_indices: int + for i in 0 ..< len(sim_state.level_geoms) { + level_geom := &sim_state.level_geoms[i] + + if level_geom.alive { + num_vertices += int(level_geom.geometry.vertices.len) + num_indices += int(level_geom.geometry.indices.len) + } + } + + if num_vertices == 0 { + return Static_TLAS{} + } + + vertices := make([]bvh.Vec3, num_vertices, context.temp_allocator) + indices := make([]u16, num_indices, context.temp_allocator) + + tri_to_level_geom := make([]Level_Geom_Handle, num_indices / 3, context.temp_allocator) + + num_vertices, num_indices = 0, 0 + + for i in 0 ..< len(sim_state.level_geoms) { + level_geom := &sim_state.level_geoms[i] + + if level_geom.alive { + geom_verts, geom_inds := get_level_geom_data(sim_state, level_geom.geometry) + + base_vertex := num_vertices + copy_slice(vertices[base_vertex:], geom_verts) + num_vertices += len(geom_verts) + + for j in 0 ..< len(geom_inds) { + if num_indices %% 3 == 0 { + tri := num_indices / 3 + tri_to_level_geom[tri] = index_to_level_geom(i) + } + ind := geom_inds[j] + indices[num_indices] = u16(base_vertex + int(ind)) + num_indices += 1 + } + } + } + + assert(num_vertices == len(vertices)) + assert(num_indices == len(indices)) + + // for i in 0 ..< len(indices) / 3 { + // tri := get_triangle(vertices, indices, i) + // rl.DrawTriangle3D(tri[0], tri[1], tri[2], rl.RED) + // } + + result := Static_TLAS { + bvh_tree = bvh.build_bvh_from_mesh( + bvh.Mesh{vertices = vertices, indices = indices}, + context.temp_allocator, + ), + tri_to_level_geom = tri_to_level_geom, + } + + return result +} + // TODO: free intermediate temp allocs // Creates TLAS using temp allocator -build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS { +build_dynamic_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> Dynamic_TLAS { tracy.Zone() body_aabbs := make([]bvh.AABB, sim_state.num_bodies, context.temp_allocator) @@ -92,7 +166,7 @@ build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS { phys_aabb := body_get_aabb(body) EXPAND_K :: 2 - expand := lg.abs(EXPAND_K * config.timestep * body.v) + expand := lg.abs(EXPAND_K * config.timestep * body.v) + 0.1 phys_aabb.extent += expand * 0.5 aabb.min = phys_aabb.center - phys_aabb.extent @@ -103,12 +177,12 @@ build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS { sim_state_bvh := bvh.build_bvh_from_aabbs(body_aabbs, body_indices, context.temp_allocator) - return TLAS{bvh_tree = sim_state_bvh, body_aabbs = body_aabbs} + return Dynamic_TLAS{bvh_tree = sim_state_bvh, body_aabbs = body_aabbs} } raycast_bodies :: proc( sim_state: ^Sim_State, - tlas: ^TLAS, + tlas: ^Dynamic_TLAS, origin, dir: Vec3, distance := max(f32), ) -> ( @@ -152,8 +226,115 @@ raycast_bodies :: proc( return } +get_triangle :: proc(vertices: []Vec3, indices: []u16, tri_idx: int) -> (tri: [3]Vec3) { + i1, i2, i3 := indices[tri_idx * 3 + 0], indices[tri_idx * 3 + 1], indices[tri_idx * 3 + 2] + tri[0], tri[1], tri[2] = vertices[i1], vertices[i2], vertices[i3] + return +} + +get_transformed_triangle :: proc( + vertices: []Vec3, + indices: []u16, + tri_idx: int, + position: Vec3, + rotation: Quat, +) -> ( + tri: [3]Vec3, +) { + tri = get_triangle(vertices, indices, tri_idx) + + tri[0] = lg.quaternion_mul_vector3(rotation, tri[0]) + position + tri[1] = lg.quaternion_mul_vector3(rotation, tri[1]) + position + tri[2] = lg.quaternion_mul_vector3(rotation, tri[2]) + position + return +} + +get_triangle_aabb :: proc(tri: [3]Vec3) -> (aabb: bvh.AABB) { + aabb.min = lg.min(tri[0], lg.min(tri[1], tri[2])) + aabb.max = lg.max(tri[0], lg.max(tri[1], tri[2])) + + return +} + +remove_invalid_contacts :: proc( + sim_state: ^Sim_State, + static_tlas: Static_TLAS, + dyn_tlas: Dynamic_TLAS, +) { + tracy.Zone() + + i := 0 + for i < len(sim_state.contact_container.contacts) { + contact := sim_state.contact_container.contacts[i] + + should_remove := false + + if contact.type == .Body_vs_Body { + should_remove |= !get_body(sim_state, contact.a).alive + should_remove |= !get_body(sim_state, Body_Handle(contact.b)).alive + + if !should_remove { + aabb_a := dyn_tlas.body_aabbs[int(contact.a) - 1] + aabb_b := dyn_tlas.body_aabbs[int(contact.b) - 1] + + should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) + } + } else { + // a is a body, b is a triangle index + should_remove |= !get_body(sim_state, contact.a).alive + + if !should_remove { + should_remove |= int(contact.tri_idx * 3) > len(static_tlas.bvh_tree.mesh.indices) + + if !should_remove { + aabb_a := dyn_tlas.body_aabbs[int(contact.a) - 1] + tri := get_triangle( + static_tlas.bvh_tree.mesh.vertices, + static_tlas.bvh_tree.mesh.indices, + int(contact.tri_idx), + ) + aabb_b := get_triangle_aabb(tri) + + should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) + } + } + } + + 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) + } else { + pair = make_contact_pair_body_level( + i32(body_handle_to_index(contact.a)), + contact.tri_idx, + ) + } + return + } + + if should_remove { + removed_pair := pair_from_contact(contact) + delete_key(&sim_state.contact_container.lookup, removed_pair) + + unordered_remove_soa(&sim_state.contact_container.contacts, i) + + if i < len(sim_state.contact_container.contacts) { + moved_contact := &sim_state.contact_container.contacts[i] + moved_pair := pair_from_contact(moved_contact^) + sim_state.contact_container.lookup[moved_pair] = i32(i) + } + } else { + i += 1 + } + } +} + // TODO: free intermediate temp allocs -find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) { +find_new_contacts :: proc( + sim_state: ^Sim_State, + static_tlas: ^Static_TLAS, + dyn_tlas: ^Dynamic_TLAS, +) { tracy.Zone() for i in 0 ..< len(sim_state.bodies_slice) { @@ -162,29 +343,68 @@ find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) { body := &sim_state.bodies_slice[i] if body.alive { - body_aabb := tlas.body_aabbs[i] - it := bvh.iterator_intersect_leaf_aabb(&tlas.bvh_tree, body_aabb) + body_aabb := dyn_tlas.body_aabbs[i] + { + it := bvh.iterator_intersect_leaf_aabb(&dyn_tlas.bvh_tree, body_aabb) - for leaf_node in bvh.iterator_intersect_leaf_next(&it) { - for j in 0 ..< leaf_node.prim_len { - other_body_idx := tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j] - prim_aabb := tlas.body_aabbs[other_body_idx] + for leaf_node in bvh.iterator_intersect_leaf_next(&it) { + for j in 0 ..< leaf_node.prim_len { + other_body_idx := + dyn_tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j] + prim_aabb := dyn_tlas.body_aabbs[other_body_idx] - pair := make_contact_pair(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) { + 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) { - 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] + 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 { - a = Body_Handle(i + 1), - b = Body_Handle(other_body_idx + 1), + contact^ = Contact { + type = .Body_vs_Body, + a = Body_Handle(i + 1), + b = i32(other_body_idx + 1), + } + + sim_state.contact_container.lookup[pair] = i32(new_contact_idx) } + } + } + } - sim_state.contact_container.lookup[pair] = i32(new_contact_idx) + { + it := bvh.iterator_intersect_leaf_aabb(&static_tlas.bvh_tree.bvh, body_aabb) + + for leaf_node in bvh.iterator_intersect_leaf_next(&it) { + for j in 0 ..< leaf_node.prim_len { + tri_idx := + static_tlas.bvh_tree.bvh.primitives[leaf_node.child_or_prim_start + j] + tri := get_triangle( + static_tlas.bvh_tree.mesh.vertices, + static_tlas.bvh_tree.mesh.indices, + int(tri_idx), + ) + 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] + + contact^ = Contact { + type = .Body_vs_Level, + a = index_to_body_handle(i), + b = i32(level_geom_handle), + tri_idx = i32(tri_idx), + } + + sim_state.contact_container.lookup[pair] = i32(new_contact_idx) + } } } } @@ -237,6 +457,7 @@ simulate :: proc( state.immediate_bodies.num_items = 0 state.immediate_suspension_constraints.num_items = 0 state.immediate_engines.num_items = 0 + state.immediate_level_geoms.num_items = 0 } GLOBAL_PLANE :: collision.Plane { @@ -244,8 +465,17 @@ GLOBAL_PLANE :: collision.Plane { dist = 0, } +Contact_Type :: enum { + Body_vs_Body, + Body_vs_Level, +} + Contact :: struct { - a, b: Body_Handle, + type: Contact_Type, + a: Body_Handle, + // Body_vs_Body - Body_Handle, Body_vs_Level - Level_Geom_Handle + b: i32, + tri_idx: i32, prev_x_a, prev_x_b: Vec3, prev_q_a, prev_q_b: Quat, manifold: collision.Contact_Manifold, @@ -263,7 +493,7 @@ Contact :: struct { applied_normal_correction: [4]f32, } -update_contacts :: proc(sim_state: ^Sim_State) { +update_contacts :: proc(sim_state: ^Sim_State, static_tlas: ^Static_TLAS) { tracy.Zone() graph_color_bitmask: [4]bit_array.Bit_Array @@ -279,12 +509,10 @@ update_contacts :: proc(sim_state: ^Sim_State) { for contact_idx in 0 ..< len(sim_state.contact_container.contacts) { contact := &sim_state.contact_container.contacts[contact_idx] - i, j := i32(contact.a) - 1, i32(contact.b) - 1 - - body, body2 := &sim_state.bodies_slice[i], &sim_state.bodies_slice[j] + b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY + body, body2 := get_body(sim_state, contact.a), get_body(sim_state, b_handle) assert(body.alive) - assert(body2.alive) old_manifold := contact.manifold old_total_normal_impulse := contact.total_normal_impulse @@ -302,19 +530,35 @@ update_contacts :: proc(sim_state: ^Sim_State) { contact.applied_static_friction = false contact.applied_normal_correction = 0 - aabb1, aabb2 := body_get_aabb(body), body_get_aabb(body2) + // aabb1, aabb2 := body_get_aabb(body), body_get_aabb(body2) // TODO: extract common math functions into a sane place - if !collision.test_aabb_vs_aabb( - {min = aabb1.center - aabb1.extent, max = aabb1.center + aabb1.extent}, - {min = aabb2.center - aabb2.extent, max = aabb2.center + aabb2.extent}, - ) { - continue - } + // if !collision.test_aabb_vs_aabb( + // {min = aabb1.center - aabb1.extent, max = aabb1.center + aabb1.extent}, + // {min = aabb2.center - aabb2.extent, max = aabb2.center + aabb2.extent}, + // ) { + // continue + // } - m1, m2 := - body_get_convex_shape_world(sim_state, body), - body_get_convex_shape_world(sim_state, body2) + m1 := body_get_convex_shape_world(sim_state, body) + m2: collision.Convex + + switch contact.type { + case .Body_vs_Body: + m2 = body_get_convex_shape_world(sim_state, body2) + case .Body_vs_Level: + // level_geom := get_level_geom(sim_state, Level_Geom_Handle(contact.b)) + tri := get_triangle( + static_tlas.bvh_tree.mesh.vertices, + static_tlas.bvh_tree.mesh.indices, + int(contact.tri_idx), + ) + // rl.DrawTriangle3D(tri[0], tri[1], tri[2], rl.BLUE) + + m2 = collision.double_sided_triangle_to_convex(tri, context.temp_allocator) + // m2 = level_geom_get_convex_shape(sim_state, level_geom, int(contact.tri_idx)) + he.debug_draw_mesh_wires(m2, rl.BLUE) + } // Raw manifold has contact points in world space raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2) @@ -384,7 +628,7 @@ pgs_solve_contacts :: proc( inv_dt: f32, apply_bias: bool, ) { - bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(30, 0.8, dt) + bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(16, 1, dt) if !apply_bias { mass_coef = 1 bias_rate = 0 @@ -397,17 +641,18 @@ pgs_solve_contacts :: proc( random_order[i] = i32(i) } - // for i in 0 ..< len(random_order) - 1 { - // j := rand.int_max(len(random_order)) - // slice.swap(random_order, i, j) - // } + for i in 0 ..< len(random_order) - 1 { + j := rand.int_max(len(random_order)) + slice.swap(random_order, i, j) + } } for i in 0 ..< len(sim_state.contact_container.contacts) { contact := &sim_state.contact_container.contacts[random_order[i]] manifold := &contact.manifold - body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b) + b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY + body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, b_handle) for point_idx in 0 ..< manifold.points_len { p1, p2 := @@ -710,7 +955,7 @@ calculate_ground_vel :: proc( pgs_solve_suspension :: proc( sim_state: ^Sim_State, - tlas: ^TLAS, + tlas: ^Dynamic_TLAS, config: Solver_Config, dt: f32, inv_dt: f32, @@ -914,7 +1159,7 @@ pgs_solve_suspension :: proc( pgs_substep :: proc( sim_state: ^Sim_State, - tlas: ^TLAS, + tlas: ^Dynamic_TLAS, config: Solver_Config, dt: f32, inv_dt: f32, @@ -933,7 +1178,8 @@ pgs_substep :: proc( contact := &sim_state.contact_container.contacts[i] manifold := &contact.manifold - body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b) + body1, body2 := + get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b)) for point_idx in 0 ..< manifold.points_len { p1, p2 := @@ -1078,49 +1324,12 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi dt := f32(dt_64) inv_dt := f32(inv_dt_64) - tlas := build_tlas(sim_state, config) + static_tlas := build_static_tlas(sim_state) + dyn_tlas := build_dynamic_tlas(sim_state, config) - { - tracy.ZoneN("simulate_step::remove_invalid_contacts") - i := 0 - for i < len(sim_state.contact_container.contacts) { - contact := sim_state.contact_container.contacts[i] - - should_remove := false - - should_remove |= !get_body(sim_state, contact.a).alive - should_remove |= !get_body(sim_state, contact.b).alive - - if !should_remove { - aabb_a := tlas.body_aabbs[int(contact.a) - 1] - aabb_b := tlas.body_aabbs[int(contact.b) - 1] - - should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) - } - - if should_remove { - removed_pair := make_contact_pair(i32(contact.a) - 1, i32(contact.b) - 1) - delete_key(&sim_state.contact_container.lookup, removed_pair) - - unordered_remove_soa(&sim_state.contact_container.contacts, i) - - if i < len(sim_state.contact_container.contacts) { - moved_contact := &sim_state.contact_container.contacts[i] - moved_pair := make_contact_pair( - i32(moved_contact.a) - 1, - i32(moved_contact.b) - 1, - ) - sim_state.contact_container.lookup[moved_pair] = i32(i) - } - } else { - i += 1 - } - } - } - - find_new_contacts(sim_state, &tlas) - - update_contacts(sim_state) + remove_invalid_contacts(sim_state, static_tlas, dyn_tlas) + find_new_contacts(sim_state, &static_tlas, &dyn_tlas) + update_contacts(sim_state, &static_tlas) Solver :: enum { XPBD, @@ -1136,7 +1345,7 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi } case .PGS: for _ in 0 ..< substeps { - pgs_substep(sim_state, &tlas, config, dt, inv_dt) + pgs_substep(sim_state, &dyn_tlas, config, dt, inv_dt) } } diff --git a/game/physics/xpbd.odin b/game/physics/xpbd.odin index e60966a..a30bcb1 100644 --- a/game/physics/xpbd.odin +++ b/game/physics/xpbd.odin @@ -43,7 +43,9 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_ tracy.ZoneN("simulate_step::solve_collisions") for i in 0 ..< len(sim_state.contact_container.contacts) { contact := &sim_state.contact_container.contacts[i] - body, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b) + + body, body2 := + get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b)) contact^ = Contact { a = contact.a, @@ -137,7 +139,8 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_ for &contact in sim_state.contact_container.contacts { manifold := contact.manifold - body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b) + body1, body2 := + get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b)) prev_p1, prev_p2: Vec3 p1, p2: Vec3 @@ -227,7 +230,8 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_ for &contact in sim_state.contact_container.contacts { manifold := &contact.manifold - body, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b) + body, body2 := + get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b)) prev_q1, prev_q2 := body.prev_q, body2.prev_q for point_idx in 0 ..< manifold.points_len { @@ -285,7 +289,7 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_ manifold := &contact.manifold body1 := get_body(sim_state, contact.a) - body2 := get_body(sim_state, contact.b) + body2 := get_body(sim_state, Body_Handle(contact.b)) friction_p1, friction_p2: Vec3 total_lambda_normal := f32(0) diff --git a/game/render/debug.odin b/game/render/debug.odin new file mode 100644 index 0000000..ad52f72 --- /dev/null +++ b/game/render/debug.odin @@ -0,0 +1,145 @@ +package render + +import rl "libs:raylib" +import gl "vendor:OpenGL" + +MAX_VERTICES :: 4096 +MAX_INDICES :: 4096 +MAX_DRAWS :: 4096 + +Vec3 :: [3]f32 + +Draw_Call :: struct { + color: rl.Color, + first_index, num_indices: i32, + base_vertex: i32, +} + +Immediate_Buffer :: enum { + Vertices = 0, + Indices, +} + +Immediate_Draw_State :: struct { + vertices: [MAX_VERTICES]Vec3, + indices: [MAX_INDICES]u16, + draws: [MAX_DRAWS]Draw_Call, + num_vertices, num_indices, num_draws: int, + vbo_ids: [Immediate_Buffer]u32, + vao_id: u32, +} + +push_mesh :: proc(ds: ^Immediate_Draw_State, vertices: []Vec3, indices: []u16, color: rl.Color) { + copy(ds.vertices[ds.num_vertices:][:len(vertices)], vertices) + copy(ds.indices[ds.num_indices:][:len(indices)], indices) + ds.draws[ds.num_draws] = { + color = color, + first_index = i32(ds.num_indices), + num_indices = i32(len(indices)), + base_vertex = i32(ds.num_vertices), + } + ds.num_vertices += len(vertices) + ds.num_indices += len(indices) + ds.num_draws += 1 +} + +// rudimentary batching +find_or_push_cmd :: proc(ds: ^Immediate_Draw_State, color: rl.Color) -> (cmd: ^Draw_Call) { + if ds.num_draws > 0 && ds.draws[ds.num_draws - 1].color == color { + cmd = &ds.draws[ds.num_draws - 1] + assert(i32(ds.num_indices) == cmd.first_index + cmd.num_indices) + } else { + ds.draws[ds.num_draws] = { + color = color, + first_index = i32(ds.num_indices), + num_indices = 0, + base_vertex = i32(ds.num_vertices), + } + cmd = &ds.draws[ds.num_draws] + ds.num_draws += 1 + } + return +} + +push_tri :: proc(ds: ^Immediate_Draw_State, v1, v2, v3: Vec3, color: rl.Color) { + draw := find_or_push_cmd(ds, color) + + ds.vertices[ds.num_vertices + 0] = v1 + ds.vertices[ds.num_vertices + 1] = v2 + ds.vertices[ds.num_vertices + 2] = v3 + ds.indices[ds.num_indices + 0] = u16(ds.num_vertices + 0) + ds.indices[ds.num_indices + 1] = u16(ds.num_vertices + 1) + ds.indices[ds.num_indices + 2] = u16(ds.num_vertices + 2) + ds.num_vertices += 3 + ds.num_indices += 3 + draw.num_indices += 3 +} + +push_quad :: proc(ds: ^Immediate_Draw_State, v1, v2, v3, v4: Vec3, color: rl.Color) { + push_tri(ds, v1, v2, v3, color) + push_tri(ds, v1, v3, v4, color) +} + +draw_batch :: proc(ds: ^Immediate_Draw_State) { + if ds.vbo_ids == {} { + gl.GenBuffers(len(ds.vbo_ids), &ds.vbo_ids[.Vertices]) + gl.GenVertexArrays(1, &ds.vao_id) + + gl.BindVertexArray(ds.vao_id) + gl.EnableVertexAttribArray(u32(rl.ShaderLocationIndex.VERTEX_POSITION)) + + gl.BindBuffer(gl.ARRAY_BUFFER, ds.vbo_ids[.Vertices]) + gl.VertexAttribPointer( + u32(rl.ShaderLocationIndex.VERTEX_POSITION), + 3, + gl.FLOAT, + false, + 0, + 0, + ) + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ds.vbo_ids[.Indices]) + + } else { + gl.BindVertexArray(ds.vao_id) + } + gl.BindVertexArray(0) + + defer gl.BindBuffer(gl.ARRAY_BUFFER, 0) + + gl.BufferData(gl.ARRAY_BUFFER, MAX_VERTICES * size_of(Vec3), nil, gl.STREAM_DRAW) + gl.BufferSubData(gl.ARRAY_BUFFER, 0, ds.num_vertices * size_of(Vec3), raw_data(ds.vertices[:])) + gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 0, 0) + gl.EnableVertexAttribArray(0) + defer gl.DisableVertexAttribArray(0) + + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ds.vbo_ids[.Indices]) + defer gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0) + gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, MAX_INDICES * size_of(u16), nil, gl.STREAM_DRAW) + gl.BufferSubData( + gl.ELEMENT_ARRAY_BUFFER, + 0, + ds.num_indices * size_of(u16), + raw_data(ds.indices[:]), + ) + + shader := rl.LoadMaterialDefault().shader + rl.BeginShaderMode(shader) + defer rl.EndShaderMode() + + for draw in ds.draws[:ds.num_draws] { + color: [4]f32 = { + f32(draw.color.r) / 255.0, + f32(draw.color.g) / 255.0, + f32(draw.color.b) / 255.0, + f32(draw.color.a) / 255.0, + } + gl.Uniform4fv(shader.locs[rl.ShaderLocationIndex.COLOR_DIFFUSE], 1, raw_data(color[:])) + gl.DrawElementsBaseVertex( + gl.TRIANGLES, + i32(ds.num_indices / 3), + gl.UNSIGNED_SHORT, + rawptr(uintptr(draw.first_index) * size_of(u16)), + draw.base_vertex, + ) + } +} diff --git a/game/track.odin b/game/track.odin index 712f11d..4b3906d 100644 --- a/game/track.odin +++ b/game/track.odin @@ -258,6 +258,70 @@ calculate_spline_interpolated_points :: proc( return nil } +spline_generate_mesh :: proc( + interpolated_points: #soa[]Interpolated_Point, + allocator := context.allocator, +) -> ( + vertices: []rl.Vector3, + indices: []u16, +) { + vertices = make([]rl.Vector3, (len(interpolated_points) - 1) * 4, allocator) + indices = make([]u16, (len(interpolated_points) - 1) * 6, allocator) + + for i in 0 ..< len(interpolated_points) - 1 { + cur, next := interpolated_points[i], interpolated_points[i + 1] + + tangent := lg.normalize0(next.pos - cur.pos) + normal := interpolated_points[i].normal + bitangent := lg.normalize0(lg.cross(tangent, normal)) + + next_tangent: rl.Vector3 + if i < len(interpolated_points) - 2 { + next2 := interpolated_points[i + 2] + next_tangent = next2.pos - next.pos + } else { + next_tangent = tangent + } + + next_normal := interpolated_points[i + 1].normal + next_bitangent := lg.normalize0(lg.cross(next_tangent, next_normal)) + + base_vert, base_ind := i * 4, i * 6 + + u_dt := 1.0 / f32(SPLINE_SUBDIVS_U) + for u in 0 ..< SPLINE_SUBDIVS_U { + u_t := u_dt * f32(u) + u_t2 := u_t + u_dt + + // [-1, 1] + u_t = u_t * 2 - 1 + u_t2 = u_t2 * 2 - 1 + u_t *= ROAD_WIDTH + u_t2 *= ROAD_WIDTH + + p1 := cur.pos + bitangent * u_t + p2 := cur.pos + bitangent * u_t2 + p3 := next.pos + next_bitangent * u_t + p4 := next.pos + next_bitangent * u_t2 + + vertices[base_vert + 0] = p1 + vertices[base_vert + 1] = p2 + vertices[base_vert + 2] = p3 + vertices[base_vert + 3] = p4 + + indices[base_ind + 0] = u16(base_vert + 0) + indices[base_ind + 1] = u16(base_vert + 1) + indices[base_ind + 2] = u16(base_vert + 2) + + indices[base_ind + 3] = u16(base_vert + 1) + indices[base_ind + 4] = u16(base_vert + 3) + indices[base_ind + 5] = u16(base_vert + 2) + } + } + + return +} + debug_draw_spline :: proc(interpolated_points: #soa[]Interpolated_Point) { rlgl.Begin(rlgl.LINES) defer rlgl.End()