From 82e9022c7389c4d5c691af802de43ca6e27b0ce3 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Wed, 30 Jul 2025 01:58:31 +0400 Subject: [PATCH] Suspension spring force along hit normal again, tweak tyres, add drift assist (just steering for you a bit, no ghost forces) --- assets/tyre_lateral.csv | 10 ++-- assets/tyre_longitudinal.csv | 4 +- builder/builder.odin | 1 + game/assets/watcher_desktop.odin | 1 + game/game.odin | 12 +++-- game/halfedge/halfedge.odin | 7 +-- game/physics/collision/convex.odin | 6 +++ game/physics/helpers.odin | 4 +- game/physics/scene.odin | 7 +++ game/physics/simulation.odin | 77 +++++++++++++++++++--------- main_hot_reload/main_hot_reload.odin | 17 +++--- main_release/main_release.odin | 14 ++--- research/pacejka94.ipynb | 10 ++-- 13 files changed, 110 insertions(+), 60 deletions(-) diff --git a/assets/tyre_lateral.csv b/assets/tyre_lateral.csv index a6cb641..edf51bf 100644 --- a/assets/tyre_lateral.csv +++ b/assets/tyre_lateral.csv @@ -1,12 +1,12 @@ a -1.5 --80 -1100 -1100 +1.1 +-180 +1500 +2000 10 0 0 --2 +-1 0 0 0 diff --git a/assets/tyre_longitudinal.csv b/assets/tyre_longitudinal.csv index 8496077..691d00a 100644 --- a/assets/tyre_longitudinal.csv +++ b/assets/tyre_longitudinal.csv @@ -1,7 +1,7 @@ b -1.6 +1.4 -120 -1100 +1700 0 300 0 diff --git a/builder/builder.odin b/builder/builder.odin index 2d897b0..4ab9e89 100644 --- a/builder/builder.odin +++ b/builder/builder.odin @@ -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, diff --git a/game/assets/watcher_desktop.odin b/game/assets/watcher_desktop.odin index 77e0843..0a87fdb 100644 --- a/game/assets/watcher_desktop.odin +++ b/game/assets/watcher_desktop.odin @@ -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 } diff --git a/game/game.odin b/game/game.odin index 450bbea..7a17872 100644 --- a/game/game.odin +++ b/game/game.odin @@ -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) } diff --git a/game/halfedge/halfedge.odin b/game/halfedge/halfedge.odin index 9a4c26e..d917aed 100644 --- a/game/halfedge/halfedge.odin +++ b/game/halfedge/halfedge.odin @@ -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) diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin index c92819b..a74b97f 100644 --- a/game/physics/collision/convex.odin +++ b/game/physics/collision/convex.odin @@ -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) { diff --git a/game/physics/helpers.odin b/game/physics/helpers.odin index 480d89d..62912a7 100644 --- a/game/physics/helpers.odin +++ b/game/physics/helpers.odin @@ -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) diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 5269677..8b06e99 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -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 diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 2f4f6ce..2cf8c86 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -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 } diff --git a/main_hot_reload/main_hot_reload.odin b/main_hot_reload/main_hot_reload.odin index 9859c81..7880a4d 100644 --- a/main_hot_reload/main_hot_reload.odin +++ b/main_hot_reload/main_hot_reload.odin @@ -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 diff --git a/main_release/main_release.odin b/main_release/main_release.odin index 955ef8a..262a7d2 100644 --- a/main_release/main_release.odin +++ b/main_release/main_release.odin @@ -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 - diff --git a/research/pacejka94.ipynb b/research/pacejka94.ipynb index 3fa5b8b..0dc75b2 100644 --- a/research/pacejka94.ipynb +++ b/research/pacejka94.ipynb @@ -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": [ {