Suspension spring force along hit normal again, tweak tyres, add drift assist (just steering for you a bit, no ghost forces)

This commit is contained in:
sergeypdev 2025-07-30 01:58:31 +04:00
parent 88fac29a6c
commit 82e9022c73
13 changed files with 110 additions and 60 deletions

View File

@ -1,12 +1,12 @@
a a
1.5 1.1
-80 -180
1100 1500
1100 2000
10 10
0 0
0 0
-2 -1
0 0
0 0
0 0

1 a
2 1.5 1.1
3 -80 -180
4 1100 1500
5 1100 2000
6 10
7 0
8 0
9 -2 -1
10 0
11 0
12 0

View File

@ -1,7 +1,7 @@
b b
1.6 1.4
-120 -120
1100 1700
0 0
300 300
0 0

1 b
2 1.6 1.4
3 -120
4 1100 1700
5 0
6 300
7 0

View File

@ -322,6 +322,7 @@ main :: proc() {
[][]string { [][]string {
[]string{"odin", "build", "main_release", "-out:./bin/desktop/game" + EXE_EXT}, []string{"odin", "build", "main_release", "-out:./bin/desktop/game" + EXE_EXT},
tracy_flag, tracy_flag,
[]string{"-define:USE_TRACKING_ALLOCATOR=true"},
debug_flag, debug_flag,
optimize_flag, optimize_flag,
COMMON_FLAGS, COMMON_FLAGS,

View File

@ -64,6 +64,7 @@ modtime_watcher_deinit :: proc(watcher: ^Asset_Modtime_Watcher) {
if !chan.is_closed(&watcher.ops) { if !chan.is_closed(&watcher.ops) {
chan.close(&watcher.ops) chan.close(&watcher.ops)
thread.join(watcher.thread) thread.join(watcher.thread)
thread.destroy(watcher.thread)
watcher.thread = nil watcher.thread = nil
} }

View File

@ -445,7 +445,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
shapes = { shapes = {
{ {
rel_q = linalg.QUATERNIONF32_IDENTITY, rel_q = linalg.QUATERNIONF32_IDENTITY,
inner_shape = physics.Shape_Box{size = {100, 1, 100}}, inner_shape = physics.Shape_Box{size = {1000, 1, 1000}},
}, },
}, },
}, },
@ -544,7 +544,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
}, },
}, },
mass = 1000, mass = 1000,
com_shift = physics.Vec3{0, 0, -0.5}, com_shift = physics.Vec3{0, 0.8, -0.5},
}, },
) )
@ -598,8 +598,8 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
wheel_extent_x_back := f32(2) / 2 wheel_extent_x_back := f32(2) / 2
wheel_y := f32(0.5) wheel_y := f32(0.5)
rest := f32(0.7) rest := f32(0.7)
natural_frequency := f32(1.2) natural_frequency := f32(1)
damping := f32(0.4) damping := f32(0.2)
radius := f32(0.737649) / 2 radius := f32(0.737649) / 2
wheel_front_z := f32(1.6) wheel_front_z := f32(1.6)
wheel_back_z := f32(-1.63) wheel_back_z := f32(-1.63)
@ -616,6 +616,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
#hash("FL", "fnv32a"), #hash("FL", "fnv32a"),
{ {
turn_wheel = true,
rel_pos = {-wheel_extent_x_front, wheel_y, wheel_front_z}, rel_pos = {-wheel_extent_x_front, wheel_y, wheel_front_z},
rel_dir = {0, -1, 0}, rel_dir = {0, -1, 0},
radius = radius, radius = radius,
@ -632,6 +633,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene, &world.physics_scene,
#hash("FR", "fnv32a"), #hash("FR", "fnv32a"),
{ {
turn_wheel = true,
rel_pos = {wheel_extent_x_front, wheel_y, wheel_front_z}, rel_pos = {wheel_extent_x_front, wheel_y, wheel_front_z},
rel_dir = {0, -1, 0}, rel_dir = {0, -1, 0},
radius = radius, radius = radius,
@ -1495,11 +1497,11 @@ game_init :: proc() {
@(export) @(export)
game_shutdown :: proc() { game_shutdown :: proc() {
name.destroy()
assets.shutdown(&g_mem.assetman) assets.shutdown(&g_mem.assetman)
editor_state_destroy(&g_mem.es) editor_state_destroy(&g_mem.es)
delete(g_mem.es.point_selection) delete(g_mem.es.point_selection)
runtime_world_destroy(&g_mem.runtime_world) runtime_world_destroy(&g_mem.runtime_world)
name.destroy()
free(g_mem) free(g_mem)
} }

View File

@ -43,14 +43,15 @@ mesh_from_vertex_index_list :: proc(
indices: []u16, indices: []u16,
vertices_per_face: int = 3, vertices_per_face: int = 3,
allocator := context.allocator, allocator := context.allocator,
loc := #caller_location,
) -> Half_Edge_Mesh { ) -> Half_Edge_Mesh {
tracy.Zone() tracy.Zone()
assert(vertices_per_face >= 3) assert(vertices_per_face >= 3)
num_faces := len(indices) / vertices_per_face num_faces := len(indices) / vertices_per_face
mesh: Half_Edge_Mesh mesh: Half_Edge_Mesh
verts := make([]Vertex, len(vertices), allocator) verts := make([]Vertex, len(vertices), allocator, loc)
faces := make([]Face, num_faces, allocator) faces := make([]Face, num_faces, allocator, loc)
edges := make([]Half_Edge, len(indices), context.temp_allocator) edges := make([]Half_Edge, len(indices), context.temp_allocator)
mesh.vertices = verts mesh.vertices = verts
@ -127,7 +128,7 @@ mesh_from_vertex_index_list :: proc(
} }
} }
mesh.edges = make([]Half_Edge, len(edges), allocator) mesh.edges = make([]Half_Edge, len(edges), allocator, loc)
sort_edges(mesh, edges) sort_edges(mesh, edges)

View File

@ -54,9 +54,15 @@ init_globals :: proc() {
// @(fini) // @(fini)
// deinit_box_mesh :: proc() { // deinit_box_mesh :: proc() {
// context.allocator = global_allocator
//
// delete(box_mesh.vertices) // delete(box_mesh.vertices)
// delete(box_mesh.faces) // delete(box_mesh.faces)
// delete(box_mesh.edges) // delete(box_mesh.edges)
//
// delete(triangle_mesh.vertices)
// delete(triangle_mesh.faces)
// delete(triangle_mesh.edges)
// } // }
box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Convex) { box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Convex) {

View File

@ -135,7 +135,7 @@ wheel_get_right_vec :: #force_inline proc(
wheel: Suspension_Constraint_Ptr, wheel: Suspension_Constraint_Ptr,
) -> Vec3 { ) -> Vec3 {
local_right := lg.quaternion_mul_vector3( local_right := lg.quaternion_mul_vector3(
lg.quaternion_angle_axis(wheel.turn_angle, Vec3{0, 1, 0}), lg.quaternion_angle_axis(wheel.turn_angle + wheel.turn_assist, Vec3{0, 1, 0}),
Vec3{1, 0, 0}, Vec3{1, 0, 0},
) )
return body_local_to_world_vec(body, local_right) return body_local_to_world_vec(body, local_right)
@ -146,7 +146,7 @@ wheel_get_forward_vec :: #force_inline proc(
wheel: Suspension_Constraint_Ptr, wheel: Suspension_Constraint_Ptr,
) -> Vec3 { ) -> Vec3 {
local_right := lg.quaternion_mul_vector3( local_right := lg.quaternion_mul_vector3(
lg.quaternion_angle_axis(wheel.turn_angle, Vec3{0, 1, 0}), lg.quaternion_angle_axis(wheel.turn_angle + wheel.turn_assist, Vec3{0, 1, 0}),
Vec3{0, 0, 1}, Vec3{0, 0, 1},
) )
return body_local_to_world_vec(body, local_right) return body_local_to_world_vec(body, local_right)

View File

@ -262,6 +262,7 @@ Collision_Shape :: struct {
Suspension_Constraint :: struct { Suspension_Constraint :: struct {
alive: bool, alive: bool,
turn_wheel: bool,
// Pos relative to the body // Pos relative to the body
rel_pos: Vec3, rel_pos: Vec3,
// Dir relative to the body // Dir relative to the body
@ -287,6 +288,9 @@ Suspension_Constraint :: struct {
spring_impulse: f32, spring_impulse: f32,
lateral_impulse: f32, lateral_impulse: f32,
longitudinal_impulse: f32, longitudinal_impulse: f32,
// Basis vectors for lateral and long impulse
forward: Vec3,
right: Vec3,
brake_friction_impulse: f32, brake_friction_impulse: f32,
inv_mass: f32, inv_mass: f32,
// Inverse inertia along wheel rotation axis // Inverse inertia along wheel rotation axis
@ -307,6 +311,7 @@ Suspension_Constraint :: struct {
// rel_hit_point = rel_pos + rel_dir * hit_t // rel_hit_point = rel_pos + rel_dir * hit_t
hit_t: f32, hit_t: f32,
turn_angle: f32, turn_angle: f32,
turn_assist: f32,
drive_impulse: f32, drive_impulse: f32,
// Impulse of brake pad on brake disc, set outsie // Impulse of brake pad on brake disc, set outsie
brake_impulse: f32, brake_impulse: f32,
@ -581,6 +586,7 @@ Body_Config :: struct {
// TODO: rename to wheel // TODO: rename to wheel
Suspension_Constraint_Config :: struct { Suspension_Constraint_Config :: struct {
turn_wheel: bool,
rel_pos: Vec3, rel_pos: Vec3,
rel_dir: Vec3, rel_dir: Vec3,
body: Body_Handle, body: Body_Handle,
@ -746,6 +752,7 @@ update_suspension_constraint_from_config :: proc(
constraint: Suspension_Constraint_Ptr, constraint: Suspension_Constraint_Ptr,
config: Suspension_Constraint_Config, config: Suspension_Constraint_Config,
) { ) {
constraint.turn_wheel = config.turn_wheel
constraint.rel_pos = config.rel_pos constraint.rel_pos = config.rel_pos
constraint.rel_dir = config.rel_dir constraint.rel_dir = config.rel_dir
constraint.body = config.body constraint.body = config.body

View File

@ -338,6 +338,9 @@ raycast :: proc(
normal = normal2 normal = normal2
hit_body = body hit_body = body
} }
if hit {
normal = lg.normalize0(normal)
}
return return
} }
@ -960,7 +963,7 @@ pgs_solve_contacts :: proc(
inv_dt: f32, inv_dt: f32,
apply_bias: bool, apply_bias: bool,
) { ) {
bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(30, 0.8, f64(dt)) bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(240 / 8, 1, f64(dt))
if !apply_bias { if !apply_bias {
mass_coef = 1 mass_coef = 1
bias_rate = 0 bias_rate = 0
@ -1316,6 +1319,23 @@ pgs_solve_suspension :: proc(
body := get_body(sim_state, v.body) body := get_body(sim_state, v.body)
if body.alive { if body.alive {
if v.turn_wheel {
body_forward_vec := body_local_to_world_vec(body, {0, 0, 1})
body_right_vec := body_local_to_world_vec(body, {1, 0, 0})
lateral_to_longitudinal_relation :=
lg.dot(body_right_vec, body.v) / (lg.dot(body_forward_vec, body.v) + 1.0)
drift_amount :=
math.smoothstep(
f32(0.0),
f32(1.0),
abs(lateral_to_longitudinal_relation),
) *
math.sign(lateral_to_longitudinal_relation)
v.turn_assist = drift_amount * math.PI * 0.1
}
wheel_world_pos := body_local_to_world(body, v.rel_pos + body.shape_offset) wheel_world_pos := body_local_to_world(body, v.rel_pos + body.shape_offset)
dir := body_local_to_world_vec(body, v.rel_dir) dir := body_local_to_world_vec(body, v.rel_dir)
prev_hit_body := v.hit_body prev_hit_body := v.hit_body
@ -1331,8 +1351,8 @@ pgs_solve_suspension :: proc(
hit_body := get_body(sim_state, v.hit_body) hit_body := get_body(sim_state, v.hit_body)
v.hit_point = wheel_world_pos + dir * v.hit_t v.hit_point = wheel_world_pos + dir * v.hit_t
w_normal1 := get_body_inverse_mass(body, -dir, wheel_world_pos) w_normal1 := get_body_inverse_mass(body, v.hit_normal, v.hit_point)
w_normal2 := get_body_inverse_mass(hit_body, dir, v.hit_point) w_normal2 := get_body_inverse_mass(hit_body, v.hit_normal, v.hit_point)
w_normal := combine_inv_mass(w_normal1, w_normal2) w_normal := combine_inv_mass(w_normal1, w_normal2)
inv_w_normal := 1.0 / w_normal inv_w_normal := 1.0 / w_normal
@ -1369,6 +1389,8 @@ pgs_solve_suspension :: proc(
} }
if !v.hit || v.hit_body != prev_hit_body { if !v.hit || v.hit_body != prev_hit_body {
v.forward = 0
v.right = 0
v.lateral_impulse = 0 v.lateral_impulse = 0
v.spring_impulse = 0 v.spring_impulse = 0
v.longitudinal_impulse = 0 v.longitudinal_impulse = 0
@ -1379,6 +1401,10 @@ pgs_solve_suspension :: proc(
right := lg.normalize0(lg.cross(forward, v.hit_normal)) right := lg.normalize0(lg.cross(forward, v.hit_normal))
forward = lg.normalize0(lg.cross(v.hit_normal, right)) forward = lg.normalize0(lg.cross(v.hit_normal, right))
//v.longitudinal_impulse = lg.dot(forward, v.longitudinal_impulse * v.forward)
//v.lateral_impulse = lg.dot(right, v.lateral_impulse * v.right)
v.forward = forward
v.right = right
// Spring force // Spring force
{ {
@ -1388,29 +1414,37 @@ pgs_solve_suspension :: proc(
f64(dt), f64(dt),
) )
if !apply_bias { if !apply_bias {
mass_coef = 1 // mass_coef = 1
bias_coef = 0 // bias_coef = 0
impulse_coef = 0 // impulse_coef = 0
} }
vel := lg.dot( vel := lg.dot(
body_velocity_at_point(body, v.hit_point) - body_velocity_at_point(hit_body, v.hit_point) -
body_velocity_at_point(hit_body, v.hit_point), body_velocity_at_point(body, v.hit_point),
dir, dir,
) )
x := v.hit_t x := v.hit_t
separation := v.rest - x separation := x - v.rest
// spring_dot_normal := abs(lg.dot(dir, v.hit_normal)) // spring_dot_normal := abs(lg.dot(dir, v.hit_normal))
incremental_impulse := incremental_impulse :=
-inv_w_normal * mass_coef * (vel + separation * bias_coef) - -inv_w_normal * mass_coef * (vel + separation * bias_coef) -
impulse_coef * v.spring_impulse impulse_coef * v.spring_impulse
new_spring_impulse := min(0, v.spring_impulse + incremental_impulse) new_spring_impulse := max(0, v.spring_impulse + incremental_impulse)
applied_impulse := new_spring_impulse - v.spring_impulse applied_impulse := new_spring_impulse - v.spring_impulse
v.spring_impulse = new_spring_impulse v.spring_impulse = new_spring_impulse
apply_velocity_correction(body, applied_impulse * dir, wheel_world_pos) apply_velocity_correction(
apply_velocity_correction(hit_body, applied_impulse * -dir, v.hit_point) body,
applied_impulse * v.hit_normal,
v.hit_point,
)
apply_velocity_correction(
hit_body,
applied_impulse * -v.hit_normal,
v.hit_point,
)
} }
// Positive means spinning forward // Positive means spinning forward
@ -1434,7 +1468,7 @@ pgs_solve_suspension :: proc(
v.slip_ratio = slip_ratio v.slip_ratio = slip_ratio
v.slip_angle = slip_angle v.slip_angle = slip_angle
MAX_SLIP_LEN :: f32(1) MAX_SLIP_LEN :: f32(2)
slip_vec := Vec2 { slip_vec := Vec2 {
slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN, slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN,
@ -1644,24 +1678,19 @@ pgs_substep :: proc(
if s.hit { if s.hit {
body := get_body(sim_state, s.body) body := get_body(sim_state, s.body)
hit_body := get_body(sim_state, s.hit_body) hit_body := get_body(sim_state, s.hit_body)
p := body_local_to_world(body, s.rel_pos + body.shape_offset)
dir := body_local_to_world_vec(body, s.rel_dir)
hit_p := body_local_to_world( hit_p := body_local_to_world(
body, body,
s.rel_pos + body.shape_offset + s.rel_dir * s.hit_t, s.rel_pos + body.shape_offset + s.rel_dir * s.hit_t,
) )
forward := wheel_get_forward_vec(body, s)
right := lg.normalize0(lg.cross(forward, s.hit_normal))
forward = lg.normalize0(lg.cross(s.hit_normal, right))
apply_velocity_correction(body, s.spring_impulse * dir, p) apply_velocity_correction(body, s.spring_impulse * s.hit_normal, hit_p)
apply_velocity_correction(hit_body, s.spring_impulse * -dir, hit_p) apply_velocity_correction(hit_body, s.spring_impulse * -s.hit_normal, hit_p)
apply_velocity_correction(body, s.lateral_impulse * right, hit_p) apply_velocity_correction(body, s.lateral_impulse * s.right, hit_p)
apply_velocity_correction(hit_body, -s.lateral_impulse * right, hit_p) apply_velocity_correction(hit_body, -s.lateral_impulse * s.right, hit_p)
apply_velocity_correction(body, s.longitudinal_impulse * forward, hit_p) apply_velocity_correction(body, s.longitudinal_impulse * s.forward, hit_p)
apply_velocity_correction(hit_body, -s.longitudinal_impulse * forward, hit_p) apply_velocity_correction(hit_body, -s.longitudinal_impulse * s.forward, hit_p)
s.w += s.inv_inertia * s.longitudinal_impulse s.w += s.inv_inertia * s.longitudinal_impulse
} }

View File

@ -108,6 +108,15 @@ unload_game_api :: proc(bin_dir: string, api: ^Game_API) {
main :: proc() { main :: proc() {
context.logger = log.create_console_logger() context.logger = log.create_console_logger()
bin_dir := filepath.dir(os.args[0])
defer delete(bin_dir)
default_allocator := context.allocator
tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = mem.tracking_allocator(&tracking_allocator)
when TRACY_ENABLE { when TRACY_ENABLE {
context.allocator = tracy.MakeProfiledAllocator( context.allocator = tracy.MakeProfiledAllocator(
self = &tracy.ProfiledAllocatorData{}, self = &tracy.ProfiledAllocatorData{},
@ -117,14 +126,6 @@ main :: proc() {
) )
} }
bin_dir := filepath.dir(os.args[0])
defer delete(bin_dir)
default_allocator := context.allocator
tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = mem.tracking_allocator(&tracking_allocator)
reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool { reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool {
err := false err := false

View File

@ -3,8 +3,11 @@
package main_release package main_release
import "core:log" import "core:log"
import "core:mem"
import "core:os" import "core:os"
_ :: mem
import game "../game" import game "../game"
USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false) USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false)
@ -12,9 +15,9 @@ USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false)
main :: proc() { main :: proc() {
when USE_TRACKING_ALLOCATOR { when USE_TRACKING_ALLOCATOR {
default_allocator := context.allocator default_allocator := context.allocator
tracking_allocator: Tracking_Allocator tracking_allocator: mem.Tracking_Allocator
tracking_allocator_init(&tracking_allocator, default_allocator) mem.tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = allocator_from_tracking_allocator(&tracking_allocator) context.allocator = mem.tracking_allocator(&tracking_allocator)
} }
mode: int = 0 mode: int = 0
@ -60,11 +63,11 @@ main :: proc() {
} }
when USE_TRACKING_ALLOCATOR { when USE_TRACKING_ALLOCATOR {
for key, value in tracking_allocator.allocation_map { for _, value in tracking_allocator.allocation_map {
log.error("%v: Leaked %v bytes\n", value.location, value.size) log.error("%v: Leaked %v bytes\n", value.location, value.size)
} }
tracking_allocator_destroy(&tracking_allocator) mem.tracking_allocator_destroy(&tracking_allocator)
} }
} }
@ -75,4 +78,3 @@ NvOptimusEnablement: u32 = 1
@(export) @(export)
AmdPowerXpressRequestHighPerformance: i32 = 1 AmdPowerXpressRequestHighPerformance: i32 = 1

View File

@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 281, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -11,7 +11,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 282, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -44,13 +44,13 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 284, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"# Pacejka coefficients\n", "# Pacejka coefficients\n",
"slip_angle_b = [\n", "slip_angle_b = [\n",
"1.6, -150, 1500, 0, 229, -0.4, 0, 0, 0, 0, 0\n", "1.6, -150, 1700, 0, 229, -0.4, 0, 0, 0, 0, 0\n",
"]\n", "]\n",
"slip_angle_b2 = [\n", "slip_angle_b2 = [\n",
"1.45, -150, 1500, 0, 400, -0.4, 0, 0, 0, 0, 0\n", "1.45, -150, 1500, 0, 400, -0.4, 0, 0, 0, 0, 0\n",
@ -63,7 +63,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 285, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {