From 5181b9b81355b67ef06cfd34ccbf7b93847c963f Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Sat, 2 Aug 2025 20:41:22 +0400 Subject: [PATCH] Change how interpolation works, add mouse movement override to follow camera --- game/game.odin | 96 ++++++++++++++++++++++++++++-------- game/physics/scene.odin | 75 ++++++++++++++++------------ game/physics/simulation.odin | 36 +++++++++++++- 3 files changed, 153 insertions(+), 54 deletions(-) diff --git a/game/game.odin b/game/game.odin index a62bcf3..bbf8e5e 100644 --- a/game/game.odin +++ b/game/game.odin @@ -597,8 +597,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) - damping := f32(0.2) + natural_frequency := f32(1.2) + damping := f32(0.3) radius := f32(0.737649) / 2 wheel_front_z := f32(1.6) wheel_back_z := f32(-1.63) @@ -861,14 +861,20 @@ Orbit_Camera :: struct { Car_Follow_Camera :: struct { // Params - body: physics.Body_Handle, - com_offset: rl.Vector3, - distance: f32, + body: physics.Body_Handle, + com_offset: rl.Vector3, + distance: f32, + yaw, pitch: f32, + + // if > 0 manual rotation is enabled, ticked down by dt + manual_rotation_time: f32, + has_yaw_pitch: bool, // State - pos: rl.Vector3, - target: rl.Vector3, - up: rl.Vector3, + pos: rl.Vector3, + follow_target: rl.Vector3, + target: rl.Vector3, + up: rl.Vector3, } Game_Camera :: union { @@ -879,7 +885,7 @@ Game_Camera :: union { GAMEPAD_DEADZONE :: f32(0.07) -collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) { +collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32, is_mouse: bool) { gamepad_delta := rl.Vector2 { rl.GetGamepadAxisMovement(0, .RIGHT_X), rl.GetGamepadAxisMovement(0, .RIGHT_Y), @@ -903,15 +909,25 @@ collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) { mouse_delta := g_mem.mouse_captured ? rl.GetMouseDelta() : 0 + @(static) first_delta := true + if mouse_delta != 0 && first_delta { + first_delta = false + mouse_delta = 0 + } else if mouse_delta == 0 { + first_delta = true + } + MOUSE_SENSE :: 0.005 GAMEPAD_SENSE :: 1 if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) { sense = MOUSE_SENSE delta = mouse_delta + is_mouse = true } else { sense = GAMEPAD_SENSE * rl.GetFrameTime() delta = gamepad_delta + is_mouse = false } return @@ -922,7 +938,7 @@ orbit_camera_update :: proc(camera: ^Orbit_Camera) { camera.target = physics.get_body(physics.get_sim_state(&world.physics_scene), world.car_handle).x - delta, sense := collect_camera_input() + delta, sense, _ := collect_camera_input() camera.yaw += delta.x * sense camera.pitch += delta.y * sense @@ -950,23 +966,61 @@ orbit_camera_to_rl :: proc(camera: Orbit_Camera) -> rl.Camera3D { return result } +dir_to_yaw_pitch :: proc "contextless" (dir: rl.Vector3) -> (yaw: f32, pitch: f32) { + pitch = math.asin(dir.y) + yaw = -math.atan2(dir.x, dir.z) + math.PI + return +} + +yaw_pitch_to_rotation :: proc "contextless" (yaw, pitch: f32) -> linalg.Matrix3f32 { + return( + linalg.matrix3_rotate(-yaw, rl.Vector3{0, 1, 0}) * + linalg.matrix3_rotate(-pitch, rl.Vector3{1, 0, 0}) \ + ) +} + follow_camera_update :: proc(world: ^World, camera: ^Car_Follow_Camera, dt: f32) { body := physics.get_interpolated_body(&world.physics_scene, SOLVER_CONFIG, camera.body) + camera.follow_target = body.x + physics.Vec3{0, 4, 0} camera.target = body.x + physics.Vec3{0, 2.5, 0} - // forward := physics.body_local_to_world_vec(body, physics.Vec3{0, 0, 1}) - // up := physics.body_local_to_world_vec(body, physics.Vec3{0, 1, 0}) + delta, sense, _ := collect_camera_input() - dir := linalg.normalize0(camera.pos - camera.target) - if dir == 0 { - dir = {0, 0, -1} + distance := f32(7) + + if delta != 0 { + camera.manual_rotation_time = 2 // 3 seconds we keep rotation } - distance := f32(8) - pos_target := camera.target + dir * distance - log.debugf("pos_target: {}", pos_target) - camera.pos = emath.exp_smooth(pos_target, camera.pos, 40, dt) + if camera.manual_rotation_time > 0 { + camera.manual_rotation_time -= dt + + if !camera.has_yaw_pitch { + camera.has_yaw_pitch = true + camera.yaw, camera.pitch = dir_to_yaw_pitch( + linalg.normalize0(camera.follow_target - camera.pos), + ) + } + + camera.yaw += delta.x * sense + camera.pitch += delta.y * sense + camera.pitch = math.clamp(camera.pitch, -math.PI / 2.0 + 0.0001, math.PI / 2.0 - 0.0001) + + camera.pos = + camera.follow_target + + yaw_pitch_to_rotation(camera.yaw, camera.pitch) * rl.Vector3{0, 0, 1} * distance + } else { + dir := linalg.normalize0(camera.pos - camera.follow_target) + if dir == 0 { + dir = {0, 0, -1} + } + + camera.has_yaw_pitch = false + + pos_target := camera.follow_target + dir * distance + camera.pos = pos_target + } } follow_camera_to_rl :: proc(camera: Car_Follow_Camera) -> rl.Camera3D { @@ -974,7 +1028,7 @@ follow_camera_to_rl :: proc(camera: Car_Follow_Camera) -> rl.Camera3D { position = camera.pos, target = camera.target, up = rl.Vector3{0, 1, 0}, - fovy = 50, + fovy = 60, projection = .PERSPECTIVE, } } @@ -1014,7 +1068,7 @@ free_camera_rotation :: proc(camera: Free_Camera) -> linalg.Matrix3f32 { } free_camera_update :: proc(camera: ^Free_Camera) { - delta, sense := collect_camera_input() + delta, sense, _ := collect_camera_input() camera.yaw -= delta.x * sense camera.pitch -= delta.y * sense diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 037b1e7..9d9a648 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -155,7 +155,7 @@ sim_state_interp_cache_destroy :: proc(cache: ^Sim_State_Interp_Cache) { } DEV_BUILD :: #config(DEV, false) -NUM_SIM_STATES :: 2 +NUM_SIM_STATES :: 2 when DEV_BUILD else 1 Scene :: struct { assetman: ^assets.Asset_Manager, @@ -352,6 +352,17 @@ Suspension_Constraint :: struct { brake_impulse: f32, applied_impulse: Vec3, + // Prev state for interp + prev_q: f32, + prev_w: f32, + prev_hit: bool, + prev_hit_normal: Vec3, + prev_hit_body: Body_Handle, + prev_hit_point: Vec3, + prev_hit_t: f32, + prev_turn_angle: f32, + prev_turn_assist: f32, + // Convenience for debug visualization to avoid recomputing slip_angle: f32, slip_ratio: f32, @@ -591,6 +602,16 @@ allocate_interpolated_body :: proc( return idx, false } +calc_interpolate_alpha :: proc "contextless" ( + scene: ^Scene, + solver_config: Solver_Config, +) -> ( + t: f32, +) { + t = scene.solver_state.accumulated_time / solver_config.timestep + return +} + get_interpolated_body :: proc( scene: ^Scene, solver_config: Solver_Config, @@ -604,27 +625,18 @@ get_interpolated_body :: proc( return result } - prev_sim_state := get_prev_sim_state(scene) sim_state := get_sim_state(scene) - - prev_body := get_body(prev_sim_state, handle) body := get_body(sim_state, handle) - result^ = Body { - q = lg.QUATERNIONF32_IDENTITY, - } - - if prev_body.alive && body.alive { + if body.alive { // interpolate - t := scene.solver_state.accumulated_time / solver_config.timestep + t := calc_interpolate_alpha(scene, solver_config) result^ = body^ - result.x = lg.lerp(prev_body.x, body.x, t) - result.q = lg.quaternion_slerp_f32(prev_body.q, body.q, t) - result.v = lg.lerp(prev_body.v, body.v, t) + result.x = lg.lerp(body.prev_x, body.x, t) + result.q = lg.quaternion_slerp_f32(body.prev_q, body.q, t) + result.v = lg.lerp(body.prev_v, body.v, t) // I don't think that's right, but not going to be used anyway probably - result.w = lg.lerp(prev_body.w, body.w, t) - } else if body.alive { - result^ = body^ + result.w = lg.lerp(body.prev_w, body.w, t) } return result @@ -665,27 +677,21 @@ get_interpolated_wheel :: proc( return result } - prev_sim_state := get_prev_sim_state(scene) sim_state := get_sim_state(scene) - prev_wheel := get_suspension_constraint(prev_sim_state, handle) wheel := get_suspension_constraint(sim_state, handle) - if prev_wheel.alive && wheel.alive { + result^ = wheel^ + if wheel.alive { // interpolate - t := scene.solver_state.accumulated_time / solver_config.timestep - result^ = prev_wheel^ - result.hit_t = lg.lerp(prev_wheel.hit_t, wheel.hit_t, t) - result.hit_point = lg.lerp(prev_wheel.hit_point, wheel.hit_point, t) - result.hit_normal = lg.normalize0(lg.lerp(prev_wheel.hit_normal, wheel.hit_normal, t)) - result.turn_angle = lg.lerp(prev_wheel.turn_angle, wheel.turn_angle, t) - result.turn_assist = lg.lerp(prev_wheel.turn_assist, wheel.turn_assist, t) - result.q = lg.lerp(prev_wheel.q, wheel.q, t) - result.w = lg.lerp(prev_wheel.w, wheel.w, t) - } else if prev_wheel.alive { - result^ = prev_wheel^ - } else if wheel.alive { - result^ = wheel^ + t := calc_interpolate_alpha(scene, solver_config) + result.hit_t = lg.lerp(wheel.prev_hit_t, wheel.hit_t, t) + result.hit_point = lg.lerp(wheel.prev_hit_point, wheel.hit_point, t) + result.hit_normal = lg.normalize0(lg.lerp(wheel.prev_hit_normal, wheel.hit_normal, t)) + result.turn_angle = lg.lerp(wheel.prev_turn_angle, wheel.turn_angle, t) + result.turn_assist = lg.lerp(wheel.prev_turn_assist, wheel.turn_assist, t) + result.q = lg.lerp(wheel.prev_q, wheel.q, t) + result.w = lg.lerp(wheel.prev_w, wheel.w, t) } return result @@ -883,6 +889,11 @@ initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config: body.v = config.initial_vel body.w = config.initial_ang_vel + body.prev_x = body.x + body.prev_q = body.q + body.prev_v = body.v + body.prev_w = body.w + body.shape = add_shape(sim_state, config.shapes) body.shape_offset = -combined_center_of_mass(config.shapes) + config.com_shift body.shape_aabb = input_shape_get_aabb(config.shapes) diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 3dc401a..efa32bc 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -1344,7 +1344,11 @@ pgs_solve_suspension :: proc( ) * math.sign(lateral_to_longitudinal_relation) - v.turn_assist = drift_amount * math.PI * 0.15 + v.turn_assist = + math.smoothstep(f32(0), f32(1), lg.dot(body_forward_vec, body.v)) * + drift_amount * + math.PI * + 0.15 } @@ -1781,6 +1785,36 @@ simulate_step :: proc( ) { tracy.Zone() + // Save prev state + { + for i in 0 ..< len(sim_state.bodies) { + body := &sim_state.bodies_slice[i] + + if body.alive { + body.prev_x = body.x + body.prev_v = body.v + body.prev_q = body.q + body.prev_w = body.w + } + } + + for i in 0 ..< len(sim_state.suspension_constraints) { + wheel := &sim_state.suspension_constraints_slice[i] + + if wheel.alive { + wheel.prev_q = wheel.q + wheel.prev_w = wheel.w + wheel.prev_hit = wheel.hit + wheel.prev_hit_normal = wheel.hit_normal + wheel.prev_hit_body = wheel.hit_body + wheel.prev_hit_point = wheel.hit_point + wheel.prev_hit_t = wheel.hit_t + wheel.prev_turn_angle = wheel.turn_angle + wheel.prev_turn_assist = wheel.turn_assist + } + } + } + substeps := config.substreps_minus_one + 1 dt_64 := f64(config.timestep) / f64(substeps)