diff --git a/build_hot_reload.sh b/build_hot_reload.sh index 64d2beb..1dabf5d 100755 --- a/build_hot_reload.sh +++ b/build_hot_reload.sh @@ -35,7 +35,7 @@ esac # Build the game. echo "Building game$DLL_EXT" -odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug +odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed # Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written. mv game_tmp$DLL_EXT game$DLL_EXT diff --git a/game/assets/assets.odin b/game/assets/assets.odin index 268c9bb..526d980 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -453,6 +453,7 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C } } log.infof("inertia tensor: %v", inertia_tensor) + inertia_tensor = inertia_tensor * lg.Matrix3f32(1.0 / total_volume) return {mesh = mesh, center_of_mass = center_of_mass, inertia_tensor = inertia_tensor} } diff --git a/game/game.odin b/game/game.odin index b0a40b7..08e727a 100644 --- a/game/game.odin +++ b/game/game.odin @@ -69,7 +69,7 @@ Car :: struct { SOLVER_CONFIG :: physics.Solver_Config { timestep = 1.0 / 120, gravity = rl.Vector3{0, -9.8, 0}, - substreps_minus_one = 2 - 1, + substreps_minus_one = 1 - 1, } Game_Memory :: struct { @@ -246,6 +246,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { }, ) + car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj") + runtime_world.car_handle = physics.immediate_body( &world.physics_scene, &runtime_world.solver_state, @@ -257,12 +259,16 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { rl.Vector3{0, 1, 0}, ), initial_ang_vel = {0, 0, 0}, - shape = physics.Shape_Box{size = car_bounds.max - car_bounds.min}, + shape = physics.Shape_Convex { + mesh = car_convex.mesh, + center_of_mass = car_convex.center_of_mass, + inertia_tensor = auto_cast car_convex.inertia_tensor, + }, mass = 100, }, ) - if false { + if true { for x in -3 ..< 3 { for y in -3 ..< 3 { @@ -271,9 +277,9 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { &runtime_world.solver_state, hash.fnv32a(slice.to_bytes([]int{(x | y << 8)})), physics.Body_Config { - initial_pos = {f32(x), 5, f32(y)}, + initial_pos = {f32(x) * 2, 5, f32(y) * 2}, initial_rot = linalg.QUATERNIONF32_IDENTITY, - shape = physics.Shape_Box{size = 0.5}, + shape = physics.Shape_Box{size = 1.5}, mass = 10, }, ) @@ -302,20 +308,22 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { } // 1.6 is a good value - wheel_extent_x := f32(2) - wheel_y := f32(-1) + wheel_extent_x := f32(1.7) + wheel_y := f32(-0.5) rest := f32(1) suspension_stiffness := f32(2000) compliance := 1.0 / suspension_stiffness damping := f32(0.01) radius := f32(0.6) + wheel_front_z := f32(3.05) + wheel_back_z := f32(-2.45) wheel_fl := physics.immediate_suspension_constraint( &world.physics_scene, &runtime_world.solver_state, #hash("FL", "fnv32a"), { - rel_pos = {-wheel_extent_x, wheel_y, 2.9}, + rel_pos = {-wheel_extent_x, wheel_y, wheel_front_z}, rel_dir = {0, -1, 0}, radius = radius, rest = rest, @@ -329,7 +337,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { &runtime_world.solver_state, #hash("FR", "fnv32a"), { - rel_pos = {wheel_extent_x, wheel_y, 2.9}, + rel_pos = {wheel_extent_x, wheel_y, wheel_front_z}, rel_dir = {0, -1, 0}, radius = radius, rest = rest, @@ -343,7 +351,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { &runtime_world.solver_state, #hash("RL", "fnv32a"), { - rel_pos = {-wheel_extent_x, wheel_y, -2.6}, + rel_pos = {-wheel_extent_x, wheel_y, wheel_back_z}, rel_dir = {0, -1, 0}, radius = radius, rest = rest, @@ -357,7 +365,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { &runtime_world.solver_state, #hash("RR", "fnv32a"), { - rel_pos = {wheel_extent_x, wheel_y, -2.6}, + rel_pos = {wheel_extent_x, wheel_y, wheel_back_z}, rel_dir = {0, -1, 0}, radius = radius, rest = rest, @@ -552,12 +560,6 @@ draw :: proc() { rad = 0.5, } - car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj") - - halfedge.debug_draw_mesh_wires(car_convex.mesh, rl.RED) - rl.DrawSphereWires(car_convex.mesh.center, 0.0, 8, 8, rl.BLUE) - rl.DrawSphereWires(car_convex.center_of_mass, 0.5, 8, 8, rl.RED) - box1_convex := collision.box_to_convex(box1, context.temp_allocator) box2_convex := collision.box_to_convex(box2, context.temp_allocator) @@ -617,12 +619,7 @@ draw :: proc() { car_matrix := rl.QuaternionToMatrix(car_body.q) car_model.transform = car_matrix - rl.DrawModel( - car_model, - physics.body_local_to_world(car_body, -runtime_world.car_com), - 1, - rl.WHITE, - ) + rl.DrawModel(car_model, physics.body_get_shape_pos(car_body), 1, rl.WHITE) } else { // rl.DrawModel(car_model, 0, 1, rl.WHITE) } diff --git a/game/halfedge/halfedge.odin b/game/halfedge/halfedge.odin index 44748bc..8ff0e21 100644 --- a/game/halfedge/halfedge.odin +++ b/game/halfedge/halfedge.odin @@ -193,6 +193,24 @@ iterate_next_edge :: proc( Vec4 :: [4]f32 +copy_mesh :: proc( + mesh: Half_Edge_Mesh, + allocator := context.allocator, +) -> ( + result: Half_Edge_Mesh, +) { + result.center = mesh.center + result.vertices = make([]Vertex, len(mesh.vertices), allocator) + result.faces = make([]Face, len(mesh.faces), allocator) + result.edges = make([]Half_Edge, len(mesh.edges), allocator) + + copy(result.vertices, mesh.vertices) + copy(result.faces, mesh.faces) + copy(result.edges, mesh.edges) + + return +} + transform_mesh :: proc(mesh: ^Half_Edge_Mesh, mat: lg.Matrix4f32) { mesh_center_avg_factor := 1.0 / f32(len(mesh.vertices)) new_center: Vec3 diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin index 6f1e3c2..41c6673 100644 --- a/game/physics/collision/convex.odin +++ b/game/physics/collision/convex.odin @@ -177,8 +177,27 @@ query_separation_edges :: proc( separating_plane_p: Vec3 success_step: int + Edge_Pair :: [2]halfedge.Edge_Index + checked_pairs := make(map[Edge_Pair]bool, context.temp_allocator) + for edge_a, edge_a_idx in a.edges { - for edge_b in b.edges { + for edge_b, edge_b_idx in b.edges { + pair := Edge_Pair{halfedge.Edge_Index(edge_a_idx), halfedge.Edge_Index(edge_b_idx)} + if checked_pairs[pair] { + continue + } + + checked_pairs[pair] = true + if edge_a.twin >= 0 { + checked_pairs[{edge_a.twin, halfedge.Edge_Index(edge_b_idx)}] = true + } + if edge_b.twin >= 0 { + checked_pairs[{halfedge.Edge_Index(edge_a_idx), edge_b.twin}] = true + } + if edge_a.twin >= 0 && edge_b.twin >= 0 { + checked_pairs[{edge_a.twin, edge_b.twin}] = true + } + edge_a_dir := halfedge.get_edge_direction_normalized(a, edge_a) edge_b_dir := halfedge.get_edge_direction_normalized(b, edge_b) diff --git a/game/physics/debug.odin b/game/physics/debug.odin index 97fa0f3..05f8f9f 100644 --- a/game/physics/debug.odin +++ b/game/physics/debug.odin @@ -4,6 +4,7 @@ import "core:log" import "core:math" import lg "core:math/linalg" import "game:debug" +import "game:halfedge" import "libs:tracy" import rl "vendor:raylib" import "vendor:raylib/rlgl" @@ -18,29 +19,27 @@ draw_debug_shape :: proc( rot: rl.Quaternion, color: rl.Color, ) { + mat := lg.matrix4_from_trs(pos, rot, 1) + rlgl.PushMatrix() defer rlgl.PopMatrix() - rlgl.Begin(rlgl.LINES) - defer rlgl.End() + rlgl.LoadIdentity() + rlgl.MultMatrixf(cast([^]f32)&mat) switch s in shape { - case Shape_Sphere: - rl.DrawSphere(pos, s.radius, color) case Shape_Box: - mat := lg.matrix4_from_trs(pos, rot, 1) - - rlgl.LoadIdentity() - rlgl.MultMatrixf(cast([^]f32)&mat) - - rl.DrawCubeWiresV(0, s.size, color) + rl.DrawCubeV(0, s.size, color) + case Shape_Convex: + halfedge.debug_draw_mesh_wires(s.mesh, color) } } draw_debug_scene :: proc(scene: ^Scene) { tracy.Zone() - for &body, i in scene.bodies { + for _, i in scene.bodies { + body := &scene.bodies_slice[i] if body.alive { pos := body.x @@ -53,7 +52,12 @@ draw_debug_scene :: proc(scene: ^Scene) { rl.DrawLine3D(pos, pos + y, rl.GREEN) rl.DrawLine3D(pos, pos + z, rl.BLUE) - draw_debug_shape(body.shape, body.x, body.q, debug.int_to_color(i32(i + 1))) + draw_debug_shape( + body.shape, + body_get_shape_pos(body), + body.q, + debug.int_to_color(i32(i + 2)), + ) } } @@ -83,7 +87,7 @@ draw_debug_scene :: proc(scene: ^Scene) { rl.RED, ) - rl.DrawLine3D(wheel_pos, wheel_pos + right * 10, rl.RED) + // rl.DrawLine3D(wheel_pos, wheel_pos + right * 10, rl.RED) if wheel.hit { // rl.DrawLine3D( @@ -99,7 +103,7 @@ draw_debug_scene :: proc(scene: ^Scene) { } } - if false { + if true { for &contact, contact_idx in scene.contact_pairs[:scene.contact_pairs_len] { points_a := contact.manifold.points_a[:contact.manifold.points_len] points_b := contact.manifold.points_b[:contact.manifold.points_len] diff --git a/game/physics/helpers.odin b/game/physics/helpers.odin index c03c4c9..f78fc2b 100644 --- a/game/physics/helpers.odin +++ b/game/physics/helpers.odin @@ -1,6 +1,8 @@ package physics +import "collision" import lg "core:math/linalg" +import "game:halfedge" import rl "vendor:raylib" inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) { @@ -23,10 +25,11 @@ inertia_tensor_box :: proc(size: rl.Vector3) -> (tensor: Matrix3) { inertia_tensor_collision_shape :: proc(shape: Collision_Shape) -> (tensor: Matrix3) { switch s in shape { - case Shape_Sphere: - tensor = inertia_tensor_sphere(s.radius) case Shape_Box: tensor = inertia_tensor_box(s.size) + case Shape_Convex: + // TODO: assuming precomputed + tensor = s.inertia_tensor } return @@ -72,3 +75,38 @@ wheel_get_right_vec :: #force_inline proc( ) return body_local_to_world_vec(body, local_right) } + +body_get_shape_offset_local :: proc(body: Body_Ptr) -> (offset: rl.Vector3) { + #partial switch s in body.shape { + case Shape_Convex: + offset = -s.center_of_mass + } + return +} + +// Returns the shape's world position +// Shape can be offset from COM +body_get_shape_pos :: proc(body: Body_Ptr) -> rl.Vector3 { + offset := body_get_shape_offset_local(body) + return body_local_to_world(body, offset) +} + +body_get_convex_shape_world :: proc( + body: Body_Ptr, + allocator := context.temp_allocator, +) -> ( + mesh: collision.Convex, +) { + switch s in body.shape { + case Shape_Box: + mesh = collision.box_to_convex(collision.Box{rad = s.size * 0.5}, allocator) + case Shape_Convex: + mesh = halfedge.copy_mesh(s.mesh, context.temp_allocator) + } + + transform := + lg.matrix4_translate(body_get_shape_pos(body)) * lg.matrix4_from_quaternion(body.q) + halfedge.transform_mesh(&mesh, transform) + + return +} diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 32e9f29..8b38d08 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -1,5 +1,6 @@ package physics +import "collision" import rl "vendor:raylib" MAX_CONTACTS :: 1024 @@ -49,11 +50,16 @@ Shape_Box :: struct { size: rl.Vector3, } -Shape_Convex :: struct {} +// TODO: Assuming mesh is generated and reinserted every frame for now, make it persistent yada yada +Shape_Convex :: struct { + mesh: collision.Convex, + center_of_mass: rl.Vector3, + inertia_tensor: Matrix3, +} Collision_Shape :: union { - Shape_Sphere, Shape_Box, + Shape_Convex, } Suspension_Constraint :: struct { diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 2fc4901..c91342e 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -4,7 +4,6 @@ import "collision" import "core:fmt" import "core:math" import lg "core:math/linalg" -import "game:halfedge" import "libs:tracy" import rl "vendor:raylib" @@ -139,27 +138,12 @@ simulate_step :: proc(scene: ^Scene, config: Solver_Config) { if i != j && body2.alive && !handled_pairs[{a = min(i, j), b = max(i, j)}] { - s1, s2 := body.shape.(Shape_Box), body2.shape.(Shape_Box) - - box1 := collision.box_to_convex( - collision.Box{rad = s1.size * 0.5}, - context.temp_allocator, - ) - box2 := collision.box_to_convex( - collision.Box{rad = s2.size * 0.5}, - context.temp_allocator, - ) - - mat1 := - lg.matrix4_translate(body.x) * lg.matrix4_from_quaternion(body.q) - mat2 := - lg.matrix4_translate(body2.x) * lg.matrix4_from_quaternion(body2.q) - - halfedge.transform_mesh(&box1, mat1) - halfedge.transform_mesh(&box2, mat2) + m1, m2 := + body_get_convex_shape_world(body), + body_get_convex_shape_world(body2) // Raw manifold has contact points in world space - raw_manifold, collision := collision.convex_vs_convex_sat(box1, box2) + raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2) if collision { contact_pair := &scene.contact_pairs[scene.contact_pairs_len] @@ -327,7 +311,7 @@ simulate_step :: proc(scene: ^Scene, config: Solver_Config) { v_normal := lg.dot(manifold.normal, v) * manifold.normal v_tangent := v - v_normal - DYNAMIC_FRICTION :: 0.1 + DYNAMIC_FRICTION :: 0.2 v_tangent_len := lg.length(v_tangent) if v_tangent_len > 0 { delta_v :=