Collisions for the convex shape

This commit is contained in:
sergeypdev 2025-02-02 02:41:24 +04:00
parent 999a7a4631
commit 25ff57168b
9 changed files with 131 additions and 64 deletions

View File

@ -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

View File

@ -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}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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
}

View File

@ -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 {

View File

@ -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 :=