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
1.5
-80
1100
1100
1.1
-180
1500
2000
10
0
0
-2
-1
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
1.6
1.4
-120
1100
1700
0
300
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{"odin", "build", "main_release", "-out:./bin/desktop/game" + EXE_EXT},
tracy_flag,
[]string{"-define:USE_TRACKING_ALLOCATOR=true"},
debug_flag,
optimize_flag,
COMMON_FLAGS,

View File

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

View File

@ -445,7 +445,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
shapes = {
{
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,
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_y := f32(0.5)
rest := f32(0.7)
natural_frequency := f32(1.2)
damping := f32(0.4)
natural_frequency := f32(1)
damping := f32(0.2)
radius := f32(0.737649) / 2
wheel_front_z := f32(1.6)
wheel_back_z := f32(-1.63)
@ -616,6 +616,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene,
#hash("FL", "fnv32a"),
{
turn_wheel = true,
rel_pos = {-wheel_extent_x_front, wheel_y, wheel_front_z},
rel_dir = {0, -1, 0},
radius = radius,
@ -632,6 +633,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
&world.physics_scene,
#hash("FR", "fnv32a"),
{
turn_wheel = true,
rel_pos = {wheel_extent_x_front, wheel_y, wheel_front_z},
rel_dir = {0, -1, 0},
radius = radius,
@ -1495,11 +1497,11 @@ game_init :: proc() {
@(export)
game_shutdown :: proc() {
name.destroy()
assets.shutdown(&g_mem.assetman)
editor_state_destroy(&g_mem.es)
delete(g_mem.es.point_selection)
runtime_world_destroy(&g_mem.runtime_world)
name.destroy()
free(g_mem)
}

View File

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

View File

@ -54,9 +54,15 @@ init_globals :: proc() {
// @(fini)
// deinit_box_mesh :: proc() {
// context.allocator = global_allocator
//
// delete(box_mesh.vertices)
// delete(box_mesh.faces)
// 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) {

View File

@ -135,7 +135,7 @@ wheel_get_right_vec :: #force_inline proc(
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
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},
)
return body_local_to_world_vec(body, local_right)
@ -146,7 +146,7 @@ wheel_get_forward_vec :: #force_inline proc(
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
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},
)
return body_local_to_world_vec(body, local_right)

View File

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

View File

@ -338,6 +338,9 @@ raycast :: proc(
normal = normal2
hit_body = body
}
if hit {
normal = lg.normalize0(normal)
}
return
}
@ -960,7 +963,7 @@ pgs_solve_contacts :: proc(
inv_dt: f32,
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 {
mass_coef = 1
bias_rate = 0
@ -1316,6 +1319,23 @@ pgs_solve_suspension :: proc(
body := get_body(sim_state, v.body)
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)
dir := body_local_to_world_vec(body, v.rel_dir)
prev_hit_body := v.hit_body
@ -1331,8 +1351,8 @@ pgs_solve_suspension :: proc(
hit_body := get_body(sim_state, v.hit_body)
v.hit_point = wheel_world_pos + dir * v.hit_t
w_normal1 := get_body_inverse_mass(body, -dir, wheel_world_pos)
w_normal2 := get_body_inverse_mass(hit_body, dir, v.hit_point)
w_normal1 := get_body_inverse_mass(body, v.hit_normal, 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)
inv_w_normal := 1.0 / w_normal
@ -1369,6 +1389,8 @@ pgs_solve_suspension :: proc(
}
if !v.hit || v.hit_body != prev_hit_body {
v.forward = 0
v.right = 0
v.lateral_impulse = 0
v.spring_impulse = 0
v.longitudinal_impulse = 0
@ -1379,6 +1401,10 @@ pgs_solve_suspension :: proc(
right := lg.normalize0(lg.cross(forward, v.hit_normal))
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
{
@ -1388,29 +1414,37 @@ pgs_solve_suspension :: proc(
f64(dt),
)
if !apply_bias {
mass_coef = 1
bias_coef = 0
impulse_coef = 0
// mass_coef = 1
// bias_coef = 0
// impulse_coef = 0
}
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,
)
x := v.hit_t
separation := v.rest - x
separation := x - v.rest
// spring_dot_normal := abs(lg.dot(dir, v.hit_normal))
incremental_impulse :=
-inv_w_normal * mass_coef * (vel + separation * bias_coef) -
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
v.spring_impulse = new_spring_impulse
apply_velocity_correction(body, applied_impulse * dir, wheel_world_pos)
apply_velocity_correction(hit_body, applied_impulse * -dir, v.hit_point)
apply_velocity_correction(
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
@ -1434,7 +1468,7 @@ pgs_solve_suspension :: proc(
v.slip_ratio = slip_ratio
v.slip_angle = slip_angle
MAX_SLIP_LEN :: f32(1)
MAX_SLIP_LEN :: f32(2)
slip_vec := Vec2 {
slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN,
@ -1644,24 +1678,19 @@ pgs_substep :: proc(
if s.hit {
body := get_body(sim_state, s.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(
body,
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(hit_body, s.spring_impulse * -dir, hit_p)
apply_velocity_correction(body, s.spring_impulse * s.hit_normal, 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(hit_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 * s.right, hit_p)
apply_velocity_correction(body, s.longitudinal_impulse * forward, hit_p)
apply_velocity_correction(hit_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 * s.forward, hit_p)
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() {
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 {
context.allocator = tracy.MakeProfiledAllocator(
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 {
err := false

View File

@ -3,8 +3,11 @@
package main_release
import "core:log"
import "core:mem"
import "core:os"
_ :: mem
import game "../game"
USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false)
@ -12,9 +15,9 @@ USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false)
main :: proc() {
when USE_TRACKING_ALLOCATOR {
default_allocator := context.allocator
tracking_allocator: Tracking_Allocator
tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = allocator_from_tracking_allocator(&tracking_allocator)
tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = mem.tracking_allocator(&tracking_allocator)
}
mode: int = 0
@ -60,11 +63,11 @@ main :: proc() {
}
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)
}
tracking_allocator_destroy(&tracking_allocator)
mem.tracking_allocator_destroy(&tracking_allocator)
}
}
@ -75,4 +78,3 @@ NvOptimusEnablement: u32 = 1
@(export)
AmdPowerXpressRequestHighPerformance: i32 = 1

View File

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