Gears and engine->wheel power transfer. Not working well yet, it's losing a lot of power somewhere. Maybe it needs to be a position constraint

This commit is contained in:
sergeypdev 2025-04-27 00:02:23 +04:00
parent c3945a80e5
commit cbe0dd20e5
3 changed files with 104 additions and 69 deletions

View File

@ -53,6 +53,7 @@ Runtime_World :: struct {
solver_state: physics.Solver_State, solver_state: physics.Solver_State,
car_com: rl.Vector3, car_com: rl.Vector3,
car_handle: physics.Body_Handle, car_handle: physics.Body_Handle,
engine_handle: physics.Engine_Handle,
camera_yaw_pitch: rl.Vector2, camera_yaw_pitch: rl.Vector2,
camera_speed: f32, camera_speed: f32,
camera: rl.Camera3D, camera: rl.Camera3D,
@ -331,7 +332,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
radius := f32(0.2888) radius := f32(0.2888)
wheel_front_z := f32(1.3) wheel_front_z := f32(1.3)
wheel_back_z := f32(-1.1) wheel_back_z := f32(-1.1)
wheel_mass := f32(30) wheel_mass := f32(14)
wheel_fl := physics.immediate_suspension_constraint( wheel_fl := physics.immediate_suspension_constraint(
&world.physics_scene, &world.physics_scene,
@ -394,21 +395,21 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
}, },
) )
engine := physics.immediate_engine( runtime_world.engine_handle = physics.immediate_engine(
&world.physics_scene, &world.physics_scene,
&runtime_world.solver_state, &runtime_world.solver_state,
#hash("engine", "fnv32a"), #hash("engine", "fnv32a"),
physics.Engine_Config { physics.Engine_Config {
rpm_torque_curve = assets.get_curve_2d(&g_mem.assetman, "assets/ae86_rpm_torque.csv").points, rpm_torque_curve = assets.get_curve_2d(&g_mem.assetman, "assets/ae86_rpm_torque.csv").points,
lowest_rpm = 1000, lowest_rpm = 1000,
inertia = 10, inertia = 0.0264,
internal_friction = 8, internal_friction = 0.00,
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},
wheel_count = 2, wheel_count = 2,
diff_type = .Fixed, diff_type = .Fixed,
final_drive_ratio = 4.3, final_drive_ratio = 4.1,
}, },
}, },
) )
@ -445,7 +446,15 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE
} }
physics.get_engine(sim_state, engine).throttle = gas_input engine := physics.get_engine(sim_state, runtime_world.engine_handle)
engine.throttle = gas_input
if rl.IsKeyPressed(.LEFT_SHIFT) || rl.IsGamepadButtonPressed(0, .RIGHT_FACE_DOWN) {
engine.gear += 1
}
if rl.IsKeyPressed(.LEFT_CONTROL) || rl.IsGamepadButtonPressed(0, .RIGHT_FACE_LEFT) {
engine.gear -= 1
}
car_body := physics.get_body(sim_state, runtime_world.car_handle) car_body := physics.get_body(sim_state, runtime_world.car_handle)
turn_vel_correction := clamp(30.0 / linalg.length(car_body.v), 0, 1) turn_vel_correction := clamp(30.0 / linalg.length(car_body.v), 0, 1)
@ -867,13 +876,17 @@ draw :: proc() {
} }
} else { } else {
car := physics.get_body(sim_state, runtime_world.car_handle) car := physics.get_body(sim_state, runtime_world.car_handle)
engine := physics.get_engine(sim_state, runtime_world.engine_handle)
gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios)
rl.DrawText( rl.DrawText(
fmt.ctprintf( fmt.ctprintf(
"p: %v\nv: %v\nw: %v\ng: %v", "p: %v\nv: %v\ngear: %v\nratio: %v\nrpm: %v\nspeed: %v km/h",
car.x, car.x,
car.v, car.v,
car.w, engine.gear,
SOLVER_CONFIG.gravity, physics.lookup_gear_ratio(gear_ratios, engine.gear),
physics.angular_velocity_to_rpm(engine.w),
linalg.length(car.v) * 3.6,
), ),
5, 5,
32, 32,

View File

@ -12,6 +12,7 @@ import "core:slice"
import "game:container/spanpool" import "game:container/spanpool"
import "libs:tracy" import "libs:tracy"
_ :: log
_ :: rand _ :: rand
_ :: math _ :: math
_ :: fmt _ :: fmt
@ -484,6 +485,23 @@ pgs_solve_contacts :: proc(
} }
} }
// Returns index into the gear ratios array
// -1 (revers) mapped to 0
// 1..N mapped to 0..N-1
lookup_gear_ratio :: #force_inline proc(gear_ratios: []f32, gear: i32) -> (ratio: f32) {
assert(len(gear_ratios) > 1)
if gear == 0 {
return 0
} else {
index := int(gear + 1)
if index > 0 {
index -= 1
}
index = clamp(index, 0, len(gear_ratios) - 1)
return gear_ratios[index]
}
}
pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_dt: f32) { pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_dt: f32) {
for &engine in sim_state.engines { for &engine in sim_state.engines {
if engine.alive { if engine.alive {
@ -506,6 +524,38 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
engine.w += applied_impulse / engine.inertia engine.w += applied_impulse / engine.inertia
} }
// Throttle
{
rpm := angular_velocity_to_rpm(engine.w)
torque: f32
idx, _ := slice.binary_search_by(
rpm_torque_curve,
rpm,
proc(a: [2]f32, k: f32) -> slice.Ordering {
return slice.cmp(a[0], k)
},
)
if idx > 0 && idx < len(rpm_torque_curve) - 1 {
cur_point := rpm_torque_curve[idx]
next_point := rpm_torque_curve[idx + 1]
rpm_diff := next_point[0] - cur_point[0]
alpha := (rpm - cur_point[0]) / rpm_diff
torque = math.lerp(cur_point[1], next_point[1], alpha)
} else {
torque = rpm_torque_curve[math.clamp(idx, 0, len(rpm_torque_curve) - 1)][1]
}
log.debugf("torque: %v Nm", torque)
torque *= engine.throttle
engine.w += (torque / engine.inertia) * dt
}
// Internal Friction // Internal Friction
{ {
delta_omega := -engine.w delta_omega := -engine.w
@ -532,67 +582,34 @@ pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32,
engine.w += applied_impulse / engine.inertia engine.w += applied_impulse / engine.inertia
} }
// Throttle
{
rpm := angular_velocity_to_rpm(engine.w)
torque: f32
idx, _ := slice.binary_search_by(
rpm_torque_curve,
rpm,
proc(a: [2]f32, k: f32) -> slice.Ordering {
return slice.cmp(a[0], k)
},
)
if idx > 0 && idx < len(rpm_torque_curve) - 1 {
cur_point := rpm_torque_curve[idx]
next_point := rpm_torque_curve[idx + 1]
rpm_diff := next_point[0] - cur_point[0]
alpha := (rpm - cur_point[0]) / rpm_diff
torque = math.lerp(cur_point[1], next_point[1], alpha)
} else {
torque = rpm_torque_curve[math.clamp(idx, 0, len(rpm_torque_curve) - 1)][1]
}
torque *= engine.throttle
engine.w += torque / engine.inertia
}
// Transmission // Transmission
{ {
// TODO: update from game gear_ratio := lookup_gear_ratio(gear_ratios, engine.gear)
engine.gear = 1
power_split := 1.0 / f32(engine.axle.wheel_count) if engine.gear != 0 {
for i in 0 ..< engine.axle.wheel_count { for i in 0 ..< engine.axle.wheel_count {
drive_wheel := &engine.axle.wheels[i] drive_wheel := &engine.axle.wheels[i]
wheel := get_suspension_constraint(sim_state, drive_wheel.wheel) wheel := get_suspension_constraint(sim_state, drive_wheel.wheel)
ratio := gear_ratios[1] * engine.axle.final_drive_ratio ratio := gear_ratio * engine.axle.final_drive_ratio
inv_ratio := f32(1.0 / ratio) inv_ratio := f32(1.0 / (ratio))
w1 := wheel.inv_inertia w1 := wheel.inv_inertia
w2 := f32(1.0 / (engine.inertia * ratio)) w2 := f32(1.0 / (engine.inertia * ratio * ratio))
w := w1 + w2 w := w1 + w2
inv_w := f32(1.0 / w) inv_w := f32(1.0 / w)
delta_omega := -engine.w - wheel.w * ratio delta_omega := wheel.w * ratio - (-engine.w)
incremental_impulse := -inv_w * delta_omega * power_split incremental_impulse := -inv_w * delta_omega
drive_wheel.impulse += incremental_impulse // drive_wheel.impulse += incremental_impulse
wheel.w += -incremental_impulse * wheel.inv_inertia * inv_ratio wheel.w += incremental_impulse * w1 * inv_ratio
engine.w += -(incremental_impulse / engine.inertia) engine.w += incremental_impulse / (engine.inertia * ratio * ratio)
}
} }
} }
// tracy.Plot("rpm", f64(angular_velocity_to_rpm(engine.w)))
log.debugf("rpm: %v", angular_velocity_to_rpm(engine.w))
} }
} }
} }
@ -844,15 +861,20 @@ pgs_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_d
e.w += e.friction_impulse / e.inertia e.w += e.friction_impulse / e.inertia
if true {
if e.gear != 0 {
for i in 0 ..< e.axle.wheel_count { for i in 0 ..< e.axle.wheel_count {
drive_wheel := &e.axle.wheels[i] drive_wheel := &e.axle.wheels[i]
wheel := get_suspension_constraint(sim_state, drive_wheel.wheel) wheel := get_suspension_constraint(sim_state, drive_wheel.wheel)
ratio := gear_ratios[1] * e.axle.final_drive_ratio ratio :=
lookup_gear_ratio(gear_ratios, e.gear) * e.axle.final_drive_ratio
inv_ratio := f32(1.0 / ratio) inv_ratio := f32(1.0 / ratio)
wheel.w += -drive_wheel.impulse * wheel.inv_inertia * inv_ratio wheel.w += drive_wheel.impulse * wheel.inv_inertia * inv_ratio
e.w += -(drive_wheel.impulse / e.inertia) e.w += (drive_wheel.impulse / (e.inertia * ratio * ratio))
}
}
} }
} }
} }

View File

@ -23,14 +23,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 29,
"id": "3074424c", "id": "3074424c",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"application/vnd.jupyter.widget-view+json": { "application/vnd.jupyter.widget-view+json": {
"model_id": "dfc8c93ea1774a788835f524f65b6843", "model_id": "c2fcd32bbbd1482ebe2be970bfe3ed33",
"version_major": 2, "version_major": 2,
"version_minor": 0 "version_minor": 0
}, },
@ -58,7 +58,7 @@
"10" "10"
] ]
}, },
"execution_count": 28, "execution_count": 29,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }