Fix a bunch of collision bugs

This commit is contained in:
sergeypdev 2025-05-03 23:24:45 +04:00
parent 1c3810483a
commit a4ed430efe
13 changed files with 232 additions and 162 deletions

View File

@ -1,19 +1,7 @@
# Blender 4.4.1 # Blender 4.4.1
# www.blender.org # www.blender.org
o Object_5 o Object_5
v -0.812577 0.172395 1.254792
v -0.646673 -0.190612 1.848713
v -0.666983 -0.006338 1.874366
v -0.635056 0.256211 1.783894
v -0.508939 0.803110 -0.009132
v -0.769510 0.352081 -1.291787
v -0.761933 0.013607 -1.722252
v 0.677186 -0.189706 1.840916 v 0.677186 -0.189706 1.840916
v -0.590011 0.087673 1.933771
v -0.669674 -0.180060 -1.336290
v -0.512564 0.833925 -0.989792
v -0.627160 0.507336 -2.050498
v -0.682347 0.022719 -2.244187
v 0.678533 0.090349 1.926879 v 0.678533 0.090349 1.926879
v 0.700321 -0.179143 -1.337300 v 0.700321 -0.179143 -1.337300
v 0.792520 0.013658 -1.722244 v 0.792520 0.013658 -1.722244
@ -24,32 +12,41 @@ v 0.543159 0.833925 -0.989791
v 0.657754 0.507336 -2.050497 v 0.657754 0.507336 -2.050497
v 0.712870 0.023363 -2.243935 v 0.712870 0.023363 -2.243935
v 0.800105 0.352079 -1.291788 v 0.800105 0.352079 -1.291788
v -0.646592 -0.189706 1.840916
v -0.647939 0.090349 1.926879
v -0.669726 -0.179143 -1.337300
v -0.761926 0.013658 -1.722244
v -0.812613 0.172325 1.254793
v -0.635077 0.258142 1.777555
v -0.508940 0.803109 -0.009139
v -0.512565 0.833925 -0.989791
v -0.627160 0.507336 -2.050497
v -0.682276 0.023363 -2.243935
v -0.769511 0.352079 -1.291788
s 0 s 0
f 1 2 3 f 11 8 7 5
f 1 3 4 f 4 5 1 3
f 1 4 5 f 12 1 2 13
f 23 20 19 17 f 9 11 4 10
f 1 6 7 f 1 5 2
f 16 17 8 15 f 9 20 19 8
f 2 9 3 f 2 5 6
f 3 9 4 f 3 10 4
f 9 14 18 4 f 4 11 5
f 6 11 12 f 5 7 6
f 21 23 16 22 f 8 11 9
f 7 13 10 f 14 21 10 3
f 8 17 14 f 19 18 7 8
f 8 14 9 2 f 7 18 17 6
f 12 21 22 13 f 6 17 13 2
f 20 11 5 19 f 12 14 3 1
f 15 10 13 22 f 22 16 18 19
f 14 17 18 f 15 14 12 16
f 15 22 16 f 20 21 15 22
f 16 23 17 f 12 13 16
f 7 10 2 1 f 13 17 16
f 17 19 18 f 14 15 21
f 20 23 21 f 15 16 22
f 5 11 6 1 f 16 17 18
f 5 4 18 19 f 19 20 22
f 13 7 6 12 f 10 21 20 9
f 12 11 20 21
f 8 2 10 15

View File

@ -16,6 +16,7 @@ out vec4 finalColor;
uniform vec3 ambient; uniform vec3 ambient;
uniform vec3 lightDir; uniform vec3 lightDir;
uniform vec3 lightColor;
// NOTE: Add your custom variables here // NOTE: Add your custom variables here
@ -30,8 +31,8 @@ void main()
// times the fragment color (interpolated vertex color) // times the fragment color (interpolated vertex color)
float NDotL = dot(lightDir, -worldNormal); float NDotL = dot(lightDir, -worldNormal);
float toon = 0.5 * smoothstep(0.66, 0.67, NDotL) + 0.5; float toon = 0.5 * smoothstep(0.5, 0.51, NDotL) + 0.5;
vec3 light = mix(vec3(0), vec3(1), toon); vec3 light = mix(vec3(ambient), lightColor, toon);
finalColor = texelColor*colDiffuse*fragColor * vec4(light, 1); finalColor = texelColor*colDiffuse*fragColor * vec4(light, 1);
} }

View File

@ -36,7 +36,7 @@ esac
# Build the game. # Build the game.
echo "Building game$DLL_EXT" echo "Building game$DLL_EXT"
odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_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 odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_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
# 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. # 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 mv game_tmp$DLL_EXT game$DLL_EXT

View File

@ -78,13 +78,15 @@ Asset_Cache :: struct($E: typeid) {
Shader_Location :: enum { Shader_Location :: enum {
Ambient, Ambient,
LightDir, LightDir,
LightColor,
} }
Shader_Location_Set :: bit_set[Shader_Location] Shader_Location_Set :: bit_set[Shader_Location]
Shader_Location_Array :: [Shader_Location]i32 Shader_Location_Array :: [Shader_Location]i32
SHADER_LOCATION_NAMES := [Shader_Location]cstring { SHADER_LOCATION_NAMES := [Shader_Location]cstring {
.Ambient = "ambient", .Ambient = "ambient",
.LightDir = "lightDir", .LightDir = "lightDir",
.LightColor = "lightColor",
} }
Loaded_Shader :: struct { Loaded_Shader :: struct {

View File

@ -78,7 +78,7 @@ Car :: struct {
SOLVER_CONFIG :: physics.Solver_Config { SOLVER_CONFIG :: physics.Solver_Config {
timestep = 1.0 / 60, timestep = 1.0 / 60,
gravity = rl.Vector3{0, -9.8, 0}, gravity = rl.Vector3{0, -9.8, 0},
substreps_minus_one = 8 - 1, substreps_minus_one = 4 - 1,
} }
Game_Memory :: struct { Game_Memory :: struct {
@ -433,11 +433,11 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
&g_mem.assetman, &g_mem.assetman,
"assets/ae86_rpm_torque.csv", "assets/ae86_rpm_torque.csv",
), ),
lowest_rpm = 1100, lowest_rpm = 1200,
rev_limit_rpm = 7800, rev_limit_rpm = 7800,
rev_limit_interval = 0.025, rev_limit_interval = 0.025,
inertia = 0.264 * 0.5, inertia = 0.264 * 0.5,
internal_friction = 0.01, internal_friction = 0.005,
gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861}, gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861},
axle = physics.Drive_Axle_Config { axle = physics.Drive_Axle_Config {
wheels = {wheel_rl, wheel_rr}, wheels = {wheel_rl, wheel_rr},
@ -733,6 +733,7 @@ draw :: proc() {
car_body := physics.get_body(sim_state, runtime_world.car_handle) car_body := physics.get_body(sim_state, runtime_world.car_handle)
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
_ = car_model
mesh_col: bvh.Collision mesh_col: bvh.Collision
hit_mesh_idx := -1 hit_mesh_idx := -1
@ -803,10 +804,11 @@ draw :: proc() {
&g_mem.assetman, &g_mem.assetman,
"assets/shaders/lit_vs.glsl", "assets/shaders/lit_vs.glsl",
"assets/shaders/lit_ps.glsl", "assets/shaders/lit_ps.glsl",
{.Ambient, .LightDir}, {.Ambient, .LightDir, .LightColor},
) )
light_dir := linalg.normalize(rl.Vector3{1, -1, 0}) light_dir := linalg.normalize(rl.Vector3{1, -1, 0})
ambient := linalg.normalize(rl.Vector3{0.1, 0.1, 0.1}) ambient := rl.Vector3{0.1, 0.1, 0.1}
light_color := rl.Vector3{0.816, 0.855, 0.89}
rl.SetShaderValue( rl.SetShaderValue(
basic_shader.shader, basic_shader.shader,
basic_shader.locations[.LightDir], basic_shader.locations[.LightDir],
@ -819,6 +821,12 @@ draw :: proc() {
&ambient, &ambient,
.VEC3, .VEC3,
) )
rl.SetShaderValue(
basic_shader.shader,
basic_shader.locations[.LightColor],
&light_color,
.VEC3,
)
render.draw_model(car_model, basic_shader.shader, car_matrix) render.draw_model(car_model, basic_shader.shader, car_matrix)
@ -952,12 +960,13 @@ draw :: proc() {
gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios) gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios)
rl.DrawText( rl.DrawText(
fmt.ctprintf( fmt.ctprintf(
"p: %v\nv: %v\ngear: %v\nratio: %v\nrpm: %v\nspeed: %v km/h", "p: %v\nv: %v\ngear: %v\nratio: %v\nrpm: %v\nclutch: %v\nspeed: %v km/h",
car.x, car.x,
car.v, car.v,
engine.gear, engine.gear,
physics.lookup_gear_ratio(gear_ratios, engine.gear), physics.lookup_gear_ratio(gear_ratios, engine.gear),
physics.angular_velocity_to_rpm(engine.w), physics.angular_velocity_to_rpm(engine.w),
engine.clutch,
linalg.length(car.v) * 3.6, linalg.length(car.v) * 3.6,
), ),
5, 5,

View File

@ -3,13 +3,9 @@ package halfedge
import rl "libs:raylib" import rl "libs:raylib"
debug_draw_mesh_wires :: proc(mesh: Half_Edge_Mesh, color: rl.Color) { debug_draw_mesh_wires :: proc(mesh: Half_Edge_Mesh, color: rl.Color) {
for _, f in mesh.faces { for edge in mesh.edges {
it := iterator_face_edges(mesh, Face_Index(f)) a, b := get_edge_points(mesh, edge)
for edge in iterate_next_edge(&it) { rl.DrawLine3D(a, b, color)
a, b := get_edge_points(mesh, edge)
rl.DrawLine3D(a, b, color)
}
} }
} }

View File

@ -5,9 +5,9 @@ import "core:log"
import "core:math" import "core:math"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:halfedge" import "game:halfedge"
import "libs:tracy"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
import "libs:tracy"
_ :: math _ :: math
_ :: rl _ :: rl
@ -84,12 +84,9 @@ convex_vs_convex_sat :: proc(a, b: Convex) -> (manifold: Contact_Manifold, colli
if edge_separation > 0 { if edge_separation > 0 {
return return
} }
biased_face_a_separation := face_query_a.separation + 0.3
biased_face_b_separation := face_query_b.separation + 0.2
biased_edge_separation := edge_separation
is_face_a_contact := biased_face_a_separation >= biased_edge_separation is_face_a_contact := face_query_a.separation > edge_separation
is_face_b_contact := biased_face_b_separation >= biased_edge_separation is_face_b_contact := face_query_b.separation > edge_separation
if is_face_a_contact || is_face_b_contact { if is_face_a_contact || is_face_b_contact {
manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b) manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b)
@ -290,7 +287,7 @@ create_face_contact_manifold :: proc(
) { ) {
tracy.Zone() tracy.Zone()
is_ref_a := face_query_a.separation > face_query_b.separation is_ref_a := (face_query_a.separation + 0.1) > face_query_b.separation
ref_face_query := is_ref_a ? face_query_a : face_query_b ref_face_query := is_ref_a ? face_query_a : face_query_b
ref_convex := is_ref_a ? a : b ref_convex := is_ref_a ? a : b
inc_convex := is_ref_a ? b : a inc_convex := is_ref_a ? b : a
@ -303,13 +300,19 @@ create_face_contact_manifold :: proc(
inc_face_idx: halfedge.Face_Index inc_face_idx: halfedge.Face_Index
// Find the most anti parallel face // Find the most anti parallel face
{ {
min_dot := f32(1.0) _, support_idx, _ := find_support_point(inc_convex, -ref_face.normal)
for face, face_idx in inc_convex.faces {
it := halfedge.iterator_vertex_edges(inc_convex, support_idx)
min_dot := max(f32)
for edge in halfedge.iterate_next_vertex_edge(&it) {
face := inc_convex.faces[edge.face]
dot := lg.dot(ref_face.normal, face.normal) dot := lg.dot(ref_face.normal, face.normal)
if dot < min_dot { if dot < min_dot {
min_dot = dot min_dot = dot
inc_face = face inc_face = face
inc_face_idx = halfedge.Face_Index(face_idx) inc_face_idx = halfedge.Face_Index(edge.face)
} }
} }
} }
@ -336,6 +339,7 @@ create_face_contact_manifold :: proc(
} }
} }
assert(len(inc_polygon) > 0) assert(len(inc_polygon) > 0)
// Set up ping pong buffers // Set up ping pong buffers
@ -352,6 +356,14 @@ create_face_contact_manifold :: proc(
step := 0 step := 0
vert_count := original_vert_count vert_count := original_vert_count
// {
// polygon := clip_bufs[get_other_clip_buf(clip_buf_idx)]
// for i in 2 ..< vert_count {
// rl.DrawTriangle3D(polygon[0], polygon[i - 1], polygon[i], rl.RED)
// }
// }
EPS :: 1e-6 EPS :: 1e-6
{ {

View File

@ -5,15 +5,16 @@ import "core:log"
import "core:math" import "core:math"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:debug" import "game:debug"
import "game:halfedge" import he "game:halfedge"
import "game:ui" import "game:ui"
import "libs:tracy"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
import "libs:tracy"
_ :: log _ :: log
_ :: math _ :: math
_ :: debug _ :: debug
_ :: he
draw_debug_shape :: proc( draw_debug_shape :: proc(
sim_state: ^Sim_State, sim_state: ^Sim_State,
@ -35,7 +36,7 @@ draw_debug_shape :: proc(
rl.DrawCubeV(0, s.size, color) rl.DrawCubeV(0, s.size, color)
case Internal_Shape_Convex: case Internal_Shape_Convex:
mesh := convex_container_get_mesh(&sim_state.convex_container, s.mesh) mesh := convex_container_get_mesh(&sim_state.convex_container, s.mesh)
halfedge.debug_draw_mesh_wires(mesh, color) he.debug_draw_mesh_wires(mesh, color)
} }
} }
@ -316,6 +317,6 @@ debug_draw_manifold_points :: proc(
contact.total_friction_impulse[point_idx].y * contact.manifold.bitangent contact.total_friction_impulse[point_idx].y * contact.manifold.bitangent
rl.DrawLine3D(p, p + total_impulse * impulse_sign, color) rl.DrawLine3D(p, p + total_impulse * impulse_sign, color)
// rl.DrawSphereWires(p, 0.1, 4, 4, color) rl.DrawSphereWires(p, 0.1, 4, 4, color)
} }
} }

View File

@ -119,7 +119,7 @@ body_get_convex_shape_world :: proc(
} }
transform := transform :=
lg.matrix4_translate(body_get_shape_pos(body)) * lg.matrix4_from_quaternion(body.q) lg.matrix4_translate_f32(body_get_shape_pos(body)) * lg.matrix4_from_quaternion_f32(body.q)
halfedge.transform_mesh(&mesh, transform) halfedge.transform_mesh(&mesh, transform)
return return

View File

@ -254,6 +254,7 @@ Engine :: struct {
// Controls // Controls
throttle: f32, throttle: f32,
clutch: f32,
// Free list // Free list
next_plus_one: i32, next_plus_one: i32,

View File

@ -11,6 +11,9 @@ import lg "core:math/linalg"
import "core:math/rand" import "core:math/rand"
import "core:slice" import "core:slice"
import "game:container/spanpool" import "game:container/spanpool"
import "game:debug"
import he "game:halfedge"
import rl "libs:raylib"
import "libs:tracy" import "libs:tracy"
_ :: log _ :: log
@ -18,6 +21,8 @@ _ :: rand
_ :: math _ :: math
_ :: fmt _ :: fmt
_ :: slice _ :: slice
_ :: he
_ :: debug
Solver_Config :: struct { Solver_Config :: struct {
// Will automatically do fixed timestep // Will automatically do fixed timestep
@ -198,7 +203,7 @@ find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) {
pair := make_contact_pair(i32(body_idx), i32(other_body_idx)) pair := make_contact_pair(i32(body_idx), i32(other_body_idx))
if body_idx != other_body_idx && if body_idx != other_body_idx &&
bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) && (true || bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) &&
!(pair in sim_state.contact_container.lookup) { !(pair in sim_state.contact_container.lookup) {
new_contact_idx := len(sim_state.contact_container.contacts) new_contact_idx := len(sim_state.contact_container.contacts)
@ -341,6 +346,11 @@ update_contacts :: proc(sim_state: ^Sim_State) {
body_get_convex_shape_world(sim_state, body), body_get_convex_shape_world(sim_state, body),
body_get_convex_shape_world(sim_state, body2) body_get_convex_shape_world(sim_state, body2)
if contact_idx == 2 {
he.debug_draw_mesh_wires(m1, rl.RED)
he.debug_draw_mesh_wires(m2, rl.BLUE)
}
// Raw manifold has contact points in world space // Raw manifold has contact points in world space
raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2) raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2)
@ -440,24 +450,24 @@ pgs_solve_contacts :: proc(
body_local_to_world(body2, manifold.points_b[point_idx]) body_local_to_world(body2, manifold.points_b[point_idx])
p_diff_normal := lg.dot(p2 - p1, manifold.normal) p_diff_normal := lg.dot(p2 - p1, manifold.normal)
separation := min(p_diff_normal, 0) separation := p_diff_normal
if separation < 0 { w1 := get_body_inverse_mass(body1, manifold.normal, p1)
w2 := get_body_inverse_mass(body2, manifold.normal, p2)
w := w1 + w2
if w == 0 {
continue
}
inv_w := 1.0 / w
{
// r1, r2 := p1 - body1.x, p2 - body2.x // r1, r2 := p1 - body1.x, p2 - body2.x
v1 := body_velocity_at_point(body1, p1) v1 := body_velocity_at_point(body1, p1)
v2 := body_velocity_at_point(body2, p2) v2 := body_velocity_at_point(body2, p2)
w1 := get_body_inverse_mass(body1, manifold.normal, p1)
w2 := get_body_inverse_mass(body2, manifold.normal, p2)
w := w1 + w2
if w == 0 {
continue
}
inv_w := 1.0 / w
delta_v := v2 - v1 delta_v := v2 - v1
{ {
delta_v_norm := lg.dot(delta_v, manifold.normal) delta_v_norm := lg.dot(delta_v, manifold.normal)
@ -488,45 +498,51 @@ pgs_solve_contacts :: proc(
apply_velocity_correction(body1, -applied_impulse_vec, p1) apply_velocity_correction(body1, -applied_impulse_vec, p1)
apply_velocity_correction(body2, applied_impulse_vec, p2) apply_velocity_correction(body2, applied_impulse_vec, p2)
} }
}
{ {
DYNAMIC_FRICTION :: 0.6 // r1, r2 := p1 - body1.x, p2 - body2.x
STATIC_FRICTION :: 0.8 v1 := body_velocity_at_point(body1, p1)
STATIC_FRICTION_VELOCITY_THRESHOLD :: 0.01 v2 := body_velocity_at_point(body2, p2)
delta_v_tang := Vec2 { delta_v := v2 - v1
lg.dot(delta_v, manifold.tangent),
lg.dot(delta_v, manifold.bitangent),
}
use_static_friction := DYNAMIC_FRICTION :: 0.6
lg.dot(delta_v_tang, delta_v_tang) < STATIC_FRICTION :: 0.8
STATIC_FRICTION_VELOCITY_THRESHOLD * STATIC_FRICTION_VELOCITY_THRESHOLD STATIC_FRICTION_VELOCITY_THRESHOLD :: 0.01
friction: f32 = use_static_friction ? STATIC_FRICTION : DYNAMIC_FRICTION
friction_clamp := contact.total_normal_impulse[point_idx] * friction
incremental_impulse := -inv_w * delta_v_tang delta_v_tang := Vec2 {
lg.dot(delta_v, manifold.tangent),
new_total_impulse: Vec2 = lg.clamp( lg.dot(delta_v, manifold.bitangent),
contact.total_friction_impulse[point_idx] + incremental_impulse,
Vec2(-friction_clamp),
Vec2(friction_clamp),
)
applied_impulse :=
new_total_impulse - contact.total_friction_impulse[point_idx]
contact.total_friction_impulse[point_idx] = new_total_impulse
applied_impulse_vec :=
applied_impulse.x * manifold.tangent +
applied_impulse.y * manifold.bitangent
apply_velocity_correction(body1, -applied_impulse_vec, p1)
apply_velocity_correction(body2, applied_impulse_vec, p2)
} }
} else {
contact.total_normal_impulse[point_idx] = 0 use_static_friction :=
contact.total_friction_impulse[point_idx] = 0 lg.dot(delta_v_tang, delta_v_tang) <
STATIC_FRICTION_VELOCITY_THRESHOLD * STATIC_FRICTION_VELOCITY_THRESHOLD
friction: f32 = use_static_friction ? STATIC_FRICTION : DYNAMIC_FRICTION
friction_clamp := contact.total_normal_impulse[point_idx] * friction
incremental_impulse := -inv_w * delta_v_tang
new_total_impulse: Vec2 = lg.clamp(
contact.total_friction_impulse[point_idx] + incremental_impulse,
Vec2(-friction_clamp),
Vec2(friction_clamp),
)
applied_impulse := new_total_impulse - contact.total_friction_impulse[point_idx]
contact.total_friction_impulse[point_idx] = new_total_impulse
applied_impulse_vec :=
applied_impulse.x * manifold.tangent + applied_impulse.y * manifold.bitangent
rl.DrawSphereWires(p1, 0.05, 8, 8, rl.RED)
rl.DrawLine3D(p1, p1 + v1, rl.RED)
rl.DrawSphereWires(p2, 0.05, 8, 8, rl.BLUE)
rl.DrawLine3D(p2, p2 + v2, rl.BLUE)
apply_velocity_correction(body1, -applied_impulse_vec, p1)
apply_velocity_correction(body2, applied_impulse_vec, p2)
} }
} }
} }
@ -555,24 +571,29 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
rpm_torque_curve := get_engine_curve(sim_state, engine.rpm_torque_curve) rpm_torque_curve := get_engine_curve(sim_state, engine.rpm_torque_curve)
gear_ratios := get_gear_ratios(sim_state, engine.gear_ratios) gear_ratios := get_gear_ratios(sim_state, engine.gear_ratios)
// Unstall impulse
{
engine_lowest_velocity := rpm_to_angular_velocity(engine.lowest_rpm)
delta_omega := engine_lowest_velocity - engine.w
inv_w := engine.inertia
incremental_impulse := inv_w * delta_omega
new_total_impulse := max(engine.unstall_impulse + incremental_impulse, 0)
applied_impulse := new_total_impulse - engine.unstall_impulse
engine.unstall_impulse = new_total_impulse
engine.w += applied_impulse / engine.inertia
}
rpm := angular_velocity_to_rpm(engine.w) rpm := angular_velocity_to_rpm(engine.w)
throttle := engine.throttle throttle := engine.throttle
clutch := f32(0)
// Blending range in rpm of auto clutch activation
AUTO_CLUTCH_RPM_RANGE :: f32(100)
AUTO_BLIP_RPM_RANGE :: f32(100)
// Prevent stalling
{
if rpm < engine.lowest_rpm {
clutch =
min(engine.lowest_rpm - rpm, AUTO_CLUTCH_RPM_RANGE) / AUTO_CLUTCH_RPM_RANGE
throttle = max(
throttle,
min((engine.lowest_rpm - AUTO_BLIP_RPM_RANGE) - rpm, AUTO_BLIP_RPM_RANGE) /
AUTO_BLIP_RPM_RANGE,
)
}
}
engine.clutch = clutch
if engine.rev_limit_time < 0.0 { if engine.rev_limit_time < 0.0 {
engine.rev_limit_time += dt engine.rev_limit_time += dt
@ -582,7 +603,6 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
throttle = 0 throttle = 0
} }
// Throttle // Throttle
{ {
@ -670,6 +690,8 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
incremental_impulse := -inv_w * delta_omega incremental_impulse := -inv_w * delta_omega
engine.axle.engine_impulse += incremental_impulse engine.axle.engine_impulse += incremental_impulse
incremental_impulse *= (1.0 - clutch)
engine.w += incremental_impulse * w1 engine.w += incremental_impulse * w1
wheel1.w += incremental_impulse * w2 * inv_ratio wheel1.w += incremental_impulse * w2 * inv_ratio
wheel2.w += incremental_impulse * w3 * inv_ratio wheel2.w += incremental_impulse * w3 * inv_ratio
@ -706,6 +728,21 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
} }
} }
calculate_ground_vel :: proc(
body: Body_Ptr,
p: Vec3,
right, forward: Vec3,
) -> (
body_vel_at_contact_patch: Vec3,
ground_vel: Vec2,
) {
body_vel_at_contact_patch = body_velocity_at_point(body, p)
ground_vel.x = lg.dot(body_vel_at_contact_patch, right)
ground_vel.y = lg.dot(body_vel_at_contact_patch, forward)
return
}
pgs_solve_suspension :: proc( pgs_solve_suspension :: proc(
sim_state: ^Sim_State, sim_state: ^Sim_State,
tlas: ^TLAS, tlas: ^TLAS,
@ -735,8 +772,6 @@ pgs_solve_suspension :: proc(
forward := wheel_get_forward_vec(body, v) forward := wheel_get_forward_vec(body, v)
right := wheel_get_right_vec(body, v) right := wheel_get_right_vec(body, v)
body_vel_at_contact_patch := body_velocity_at_point(body, v.hit_point)
w_normal := get_body_angular_inverse_mass(body, dir) w_normal := get_body_angular_inverse_mass(body, dir)
inv_w_normal := 1.0 / w_normal inv_w_normal := 1.0 / w_normal
@ -796,22 +831,25 @@ pgs_solve_suspension :: proc(
// Positive means spinning forward // Positive means spinning forward
wheel_spin_vel := -v.radius * v.w wheel_spin_vel := -v.radius * v.w
ground_vel_x := lg.dot(body_vel_at_contact_patch, right)
ground_vel_y := lg.dot(body_vel_at_contact_patch, forward) body_vel_at_contact_patch, ground_vel := calculate_ground_vel(
// contact_patch_linear_vel := body,
// body_vel_at_contact_patch + (v.radius * v.w * forward) v.hit_point,
right,
forward,
)
slip_ratio := slip_ratio :=
ground_vel_y == 0 ? 0 : clamp(wheel_spin_vel / ground_vel_y - 1, -1, 1) * 100.0 ground_vel.y == 0 ? 0 : clamp(wheel_spin_vel / ground_vel.y - 1, -1, 1) * 100.0
slip_angle := slip_angle :=
-lg.angle_between(Vec2{0, 1}, Vec2{ground_vel_x, ground_vel_y}) * -lg.angle_between(Vec2{0, 1}, Vec2{ground_vel.x, ground_vel.y}) *
math.DEG_PER_RAD math.DEG_PER_RAD
v.slip_ratio = slip_ratio v.slip_ratio = slip_ratio
v.slip_angle = slip_angle v.slip_angle = slip_angle
MAX_SLIP_LEN :: f32(2.0) MAX_SLIP_LEN :: f32(1.5)
slip_vec := Vec2 { slip_vec := Vec2 {
slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN, slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN,
@ -848,8 +886,15 @@ pgs_solve_suspension :: proc(
// Longitudinal friction // Longitudinal friction
if true { if true {
body_vel_at_contact_patch, ground_vel = calculate_ground_vel(
body,
v.hit_point,
right,
forward,
)
// Wheel linear velocity relative to ground // Wheel linear velocity relative to ground
relative_vel := ground_vel_y - wheel_spin_vel relative_vel := ground_vel.y - wheel_spin_vel
friction_clamp := abs(v.spring_impulse) * long_friction friction_clamp := abs(v.spring_impulse) * long_friction
@ -872,9 +917,13 @@ pgs_solve_suspension :: proc(
} }
// Lateral friction // Lateral friction
if true { if true {
vel_contact := body_vel_at_contact_patch body_vel_at_contact_patch, ground_vel = calculate_ground_vel(
body,
lateral_vel := lg.dot(right, vel_contact) v.hit_point,
right,
forward,
)
lateral_vel := ground_vel.x
friction_clamp := abs(v.spring_impulse) * lat_friction friction_clamp := abs(v.spring_impulse) * lat_friction
incremental_impulse := -inv_w_normal * lateral_vel incremental_impulse := -inv_w_normal * lateral_vel
@ -944,7 +993,7 @@ pgs_substep :: proc(
if e.alive { if e.alive {
gear_ratios := get_gear_ratios(sim_state, e.gear_ratios) gear_ratios := get_gear_ratios(sim_state, e.gear_ratios)
e.w += e.unstall_impulse / e.inertia // e.w += e.unstall_impulse / e.inertia
e.w += e.friction_impulse / e.inertia e.w += e.friction_impulse / e.inertia
@ -966,9 +1015,9 @@ pgs_substep :: proc(
w2 := wheel1.inv_inertia w2 := wheel1.inv_inertia
w3 := wheel2.inv_inertia w3 := wheel2.inv_inertia
e.w += e.axle.engine_impulse * w1 e.w += e.axle.engine_impulse * w1 * (1.0 - e.clutch)
wheel1.w += e.axle.engine_impulse * w2 * inv_ratio wheel1.w += e.axle.engine_impulse * w2 * inv_ratio * (1.0 - e.clutch)
wheel2.w += e.axle.engine_impulse * w3 * inv_ratio wheel2.w += e.axle.engine_impulse * w3 * inv_ratio * (1.0 - e.clutch)
} }
// Warmp start diff impulse // Warmp start diff impulse
@ -1058,9 +1107,11 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
substeps := config.substreps_minus_one + 1 substeps := config.substreps_minus_one + 1
dt := config.timestep / f32(substeps) dt_64 := f64(config.timestep) / f64(substeps)
inv_dt := 1.0 / dt inv_dt_64 := f64(1.0) / dt_64
dt := f32(dt_64)
inv_dt := f32(inv_dt_64)
tlas := build_tlas(sim_state, config) tlas := build_tlas(sim_state, config)
@ -1073,7 +1124,7 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
aabb_a := tlas.body_aabbs[int(contact.a) - 1] aabb_a := tlas.body_aabbs[int(contact.a) - 1]
aabb_b := tlas.body_aabbs[int(contact.b) - 1] aabb_b := tlas.body_aabbs[int(contact.b) - 1]
if !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) { if false && !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) {
removed_pair := make_contact_pair(i32(contact.a) - 1, i32(contact.b) - 1) removed_pair := make_contact_pair(i32(contact.a) - 1, i32(contact.b) - 1)
delete_key(&sim_state.contact_container.lookup, removed_pair) delete_key(&sim_state.contact_container.lookup, removed_pair)

BIN
src_assets/car_convex.blend (Stored with Git LFS)

Binary file not shown.

BIN
src_assets/car_convex.blend1 (Stored with Git LFS)

Binary file not shown.