More tyre tweaks, chase camera like in GTA 5, basic rigid body interpolation so rendering can run faster than physics and still see everything smoothly
This commit is contained in:
parent
82e9022c73
commit
a6cbfaf88c
@ -1,12 +1,12 @@
|
||||
a
|
||||
1.1
|
||||
-180
|
||||
1500
|
||||
-200
|
||||
1700
|
||||
2000
|
||||
10
|
||||
0
|
||||
0
|
||||
-1
|
||||
-0.5
|
||||
0
|
||||
0
|
||||
0
|
||||
|
|
@ -1,9 +1,9 @@
|
||||
b
|
||||
1.4
|
||||
-120
|
||||
1700
|
||||
0
|
||||
300
|
||||
1.2
|
||||
-200
|
||||
1400
|
||||
20
|
||||
1
|
||||
0
|
||||
0
|
||||
0
|
||||
|
|
10
common/emath/math.odin
Normal file
10
common/emath/math.odin
Normal file
@ -0,0 +1,10 @@
|
||||
// "Engine" math, to avoid aliasing with core:math
|
||||
package emath
|
||||
|
||||
import "core:math"
|
||||
|
||||
_ :: math
|
||||
|
||||
exp_smooth :: proc "contextless" (target: $T, pos: T, speed: f32, dt: f32) -> T {
|
||||
return pos + ((target - pos) * (1.0 - math.exp_f32(-speed * dt)))
|
||||
}
|
167
game/game.odin
167
game/game.odin
@ -15,6 +15,7 @@
|
||||
package game
|
||||
|
||||
import "assets"
|
||||
import "common:emath"
|
||||
import "common:name"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
@ -27,6 +28,8 @@ import "libs:raylib/rlgl"
|
||||
import "libs:tracy"
|
||||
import "ui"
|
||||
|
||||
_ :: emath
|
||||
|
||||
PIXEL_WINDOW_HEIGHT :: 360
|
||||
|
||||
Track :: struct {
|
||||
@ -115,6 +118,8 @@ World :: struct {
|
||||
player_pos: rl.Vector3,
|
||||
track: Track,
|
||||
physics_scene: physics.Scene,
|
||||
// How much time passed in physics scene, can be 0 when paused
|
||||
physics_dt: f32,
|
||||
pause: bool,
|
||||
car_handle: physics.Body_Handle,
|
||||
engine_handle: physics.Engine_Handle,
|
||||
@ -149,8 +154,7 @@ Runtime_World :: struct {
|
||||
camera_yaw_pitch: rl.Vector2,
|
||||
camera_speed: f32,
|
||||
camera: rl.Camera3D,
|
||||
orbit_camera: Orbit_Camera,
|
||||
camera_mode: Camera_Mode,
|
||||
game_camera: Game_Camera,
|
||||
dt: f32,
|
||||
rewind_simulation: bool,
|
||||
commit_simulation: bool,
|
||||
@ -206,11 +210,6 @@ Game_Memory :: struct {
|
||||
free_cam: bool,
|
||||
}
|
||||
|
||||
Camera_Mode :: enum {
|
||||
Orbit,
|
||||
Hood,
|
||||
}
|
||||
|
||||
Track_Edit_State :: enum {
|
||||
// Point selection
|
||||
Select,
|
||||
@ -354,7 +353,7 @@ camera_forward_vec :: proc() -> rl.Vector3 {
|
||||
|
||||
game_camera_3d :: proc() -> rl.Camera3D {
|
||||
if g_mem.editor || g_mem.free_cam {
|
||||
return free_camera_to_rl(&g_mem.es.camera)
|
||||
return free_camera_to_rl(g_mem.es.camera)
|
||||
}
|
||||
|
||||
return get_runtime_world().camera
|
||||
@ -557,7 +556,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
u32(box_name),
|
||||
physics.Body_Config {
|
||||
name = box_name,
|
||||
initial_pos = {-5 + f32(y) * 1.01, 1, f32(x) * 1.01 + -11.5},
|
||||
initial_pos = {5 + f32(y) * 1.01, 1, f32(x) * 1.01 + -11.5},
|
||||
initial_rot = linalg.QUATERNIONF32_IDENTITY,
|
||||
shapes = {
|
||||
{
|
||||
@ -709,7 +708,8 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
|
||||
DRIVE_IMPULSE :: 3000
|
||||
BRAKE_IMPULSE :: 10
|
||||
TURN_ANGLE :: -f32(50) * math.RAD_PER_DEG
|
||||
TURN_ANGLE_AT_HIGH_SPEED :: f32(10) * math.RAD_PER_DEG
|
||||
TURN_ANGLE_AT_LOW_SPEED :: f32(30) * math.RAD_PER_DEG
|
||||
// 68% front, 32% rear
|
||||
BRAKE_BIAS :: f32(0.68)
|
||||
|
||||
@ -745,7 +745,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
}
|
||||
|
||||
car_body := physics.get_body(sim_state, world.car_handle)
|
||||
turn_vel_correction := clamp(4.0 / linalg.length(car_body.v), 0, 1)
|
||||
turn_vel_correction := math.smoothstep(f32(90), f32(10), linalg.length(car_body.v) * 3.6)
|
||||
|
||||
turn_input := rl.GetGamepadAxisMovement(0, .LEFT_X)
|
||||
if abs(turn_input) < GAMEPAD_DEADZONE {
|
||||
@ -761,7 +761,10 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
for wheel_handle in turn_wheels {
|
||||
wheel := physics.get_suspension_constraint(sim_state, wheel_handle)
|
||||
|
||||
wheel.turn_angle = TURN_ANGLE * turn_vel_correction * turn_input
|
||||
wheel.turn_angle =
|
||||
TURN_ANGLE_AT_HIGH_SPEED +
|
||||
(TURN_ANGLE_AT_LOW_SPEED - TURN_ANGLE_AT_HIGH_SPEED) * turn_vel_correction
|
||||
wheel.turn_angle *= -turn_input
|
||||
}
|
||||
|
||||
immediate_scene(world, &world.main_scene, "assets/blender/test_level_blend/Scene.scn")
|
||||
@ -788,7 +791,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
)
|
||||
}
|
||||
|
||||
physics.simulate(
|
||||
world.physics_dt = physics.simulate(
|
||||
&world.physics_scene,
|
||||
SOLVER_CONFIG,
|
||||
dt,
|
||||
@ -816,7 +819,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
||||
}
|
||||
}
|
||||
|
||||
update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
runtime_world_update :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
cur_world := runtime_world_current_world(runtime_world)
|
||||
|
||||
runtime_world.dt = dt
|
||||
@ -856,6 +859,24 @@ Orbit_Camera :: struct {
|
||||
distance: f32,
|
||||
}
|
||||
|
||||
Car_Follow_Camera :: struct {
|
||||
// Params
|
||||
body: physics.Body_Handle,
|
||||
com_offset: rl.Vector3,
|
||||
distance: f32,
|
||||
|
||||
// State
|
||||
pos: rl.Vector3,
|
||||
target: rl.Vector3,
|
||||
up: rl.Vector3,
|
||||
}
|
||||
|
||||
Game_Camera :: union {
|
||||
Free_Camera,
|
||||
Orbit_Camera,
|
||||
Car_Follow_Camera,
|
||||
}
|
||||
|
||||
GAMEPAD_DEADZONE :: f32(0.07)
|
||||
|
||||
collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) {
|
||||
@ -929,6 +950,60 @@ orbit_camera_to_rl :: proc(camera: Orbit_Camera) -> rl.Camera3D {
|
||||
return result
|
||||
}
|
||||
|
||||
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.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})
|
||||
|
||||
dir := linalg.normalize0(camera.pos - camera.target)
|
||||
if dir == 0 {
|
||||
dir = {0, 0, -1}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
follow_camera_to_rl :: proc(camera: Car_Follow_Camera) -> rl.Camera3D {
|
||||
return rl.Camera3D {
|
||||
position = camera.pos,
|
||||
target = camera.target,
|
||||
up = rl.Vector3{0, 1, 0},
|
||||
fovy = 50,
|
||||
projection = .PERSPECTIVE,
|
||||
}
|
||||
}
|
||||
|
||||
game_camera_update :: proc(world: ^World, camera: ^Game_Camera, dt: f32) {
|
||||
switch &c in camera {
|
||||
case Free_Camera:
|
||||
free_camera_update(&c)
|
||||
case Orbit_Camera:
|
||||
orbit_camera_update(&c)
|
||||
case Car_Follow_Camera:
|
||||
follow_camera_update(world, &c, dt)
|
||||
}
|
||||
}
|
||||
|
||||
game_camera_to_rl :: proc(camera: Game_Camera) -> rl.Camera3D {
|
||||
result: rl.Camera3D
|
||||
switch c in camera {
|
||||
case Free_Camera:
|
||||
result = free_camera_to_rl(c)
|
||||
case Orbit_Camera:
|
||||
result = orbit_camera_to_rl(c)
|
||||
case Car_Follow_Camera:
|
||||
result = follow_camera_to_rl(c)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
Matrix3 :: # row_major matrix[3, 3]f32
|
||||
|
||||
free_camera_rotation :: proc(camera: Free_Camera) -> linalg.Matrix3f32 {
|
||||
@ -971,8 +1046,8 @@ free_camera_update :: proc(camera: ^Free_Camera) {
|
||||
camera.pos += (input.x * right + input.y * forward) * camera.speed
|
||||
}
|
||||
|
||||
free_camera_to_rl :: proc(camera: ^Free_Camera) -> (result: rl.Camera3D) {
|
||||
rotation := free_camera_rotation(camera^)
|
||||
free_camera_to_rl :: proc(camera: Free_Camera) -> (result: rl.Camera3D) {
|
||||
rotation := free_camera_rotation(camera)
|
||||
forward := -rotation[2]
|
||||
|
||||
result.position = camera.pos
|
||||
@ -1008,16 +1083,6 @@ update :: proc() {
|
||||
// g_mem.es.world.player_pos = g_mem.runtime_world.camera.position
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(.F2) && !g_mem.free_cam {
|
||||
cam_mode := &get_runtime_world().camera_mode
|
||||
switch cam_mode^ {
|
||||
case .Orbit:
|
||||
cam_mode^ = .Hood
|
||||
case .Hood:
|
||||
cam_mode^ = .Orbit
|
||||
}
|
||||
}
|
||||
|
||||
dt := rl.GetFrameTime()
|
||||
|
||||
// Debug BVH traversal
|
||||
@ -1056,33 +1121,25 @@ update :: proc() {
|
||||
if g_mem.editor {
|
||||
update_editor(get_editor_state(), dt)
|
||||
} else {
|
||||
update_runtime_world(get_runtime_world(), dt)
|
||||
runtime_world_update(get_runtime_world(), dt)
|
||||
world := runtime_world_current_world(get_runtime_world())
|
||||
|
||||
if g_mem.free_cam {
|
||||
free_camera_update(&g_mem.es.camera)
|
||||
} else {
|
||||
switch get_runtime_world().camera_mode {
|
||||
case .Orbit:
|
||||
orbit_camera_update(&get_runtime_world().orbit_camera)
|
||||
get_runtime_world().camera = orbit_camera_to_rl(get_runtime_world().orbit_camera)
|
||||
case .Hood:
|
||||
car := physics.get_body(
|
||||
physics.get_sim_state(&world.physics_scene),
|
||||
world.car_handle,
|
||||
)
|
||||
|
||||
cam: rl.Camera3D
|
||||
cam.position = physics.body_local_to_world(car, physics.Vec3{0, 0.9, 2})
|
||||
cam.target =
|
||||
cam.position + physics.body_local_to_world_vec(car, physics.Vec3{0, 0, 1})
|
||||
cam.up = physics.body_local_to_world_vec(car, physics.Vec3{0, 1, 0})
|
||||
cam.fovy = 60
|
||||
cam.projection = .PERSPECTIVE
|
||||
|
||||
|
||||
get_runtime_world().camera = cam
|
||||
game_cam := &get_runtime_world().game_camera
|
||||
if _, ok := game_cam.(Car_Follow_Camera); !ok {
|
||||
game_cam^ = Car_Follow_Camera{}
|
||||
log.debugf("overwriting cam")
|
||||
}
|
||||
car_follow_cam := &game_cam.(Car_Follow_Camera)
|
||||
car_follow_cam.body = world.car_handle
|
||||
|
||||
if world.physics_dt == 0 {
|
||||
log.debugf("phys dt == 0")
|
||||
}
|
||||
game_camera_update(world, &get_runtime_world().game_camera, dt)
|
||||
get_runtime_world().camera = game_camera_to_rl(get_runtime_world().game_camera)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1125,7 +1182,11 @@ draw_world :: proc(world: ^World) {
|
||||
phys_debug_state: physics.Debug_State
|
||||
physics.init_debug_state(&phys_debug_state)
|
||||
|
||||
car_body := physics.get_body(sim_state, world.car_handle)
|
||||
car_body := physics.get_interpolated_body(
|
||||
&world.physics_scene,
|
||||
SOLVER_CONFIG,
|
||||
world.car_handle,
|
||||
)
|
||||
engine := physics.get_engine(sim_state, world.engine_handle)
|
||||
|
||||
{
|
||||
@ -1187,8 +1248,9 @@ draw_world :: proc(world: ^World) {
|
||||
|
||||
|
||||
car_matrix := rl.QuaternionToMatrix(car_body.q)
|
||||
car_matrix =
|
||||
(auto_cast linalg.matrix4_translate_f32(physics.body_get_shape_pos(car_body))) * car_matrix
|
||||
// TODO: figure out how to use helper funcs that take Body_Ptr for interpolated body which is just Body
|
||||
car_pos := linalg.quaternion_mul_vector3(car_body.q, car_body.shape_offset) + car_body.x
|
||||
car_matrix = (auto_cast linalg.matrix4_translate_f32(car_pos)) * car_matrix
|
||||
|
||||
// basic_shader := assets.get_shader(
|
||||
// &g_mem.assetman,
|
||||
@ -1532,7 +1594,10 @@ game_hot_reloaded :: proc(mem: rawptr) {
|
||||
render.init(&g_mem.assetman)
|
||||
ui.rl_init()
|
||||
|
||||
g_mem.runtime_world.orbit_camera.distance = 6
|
||||
orbit, ok := &g_mem.runtime_world.game_camera.(Orbit_Camera)
|
||||
if ok {
|
||||
orbit.distance = 6
|
||||
}
|
||||
log.debugf("hot reloaded")
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,14 @@ package physics
|
||||
import "bvh"
|
||||
import "collision"
|
||||
import "common:name"
|
||||
import "core:log"
|
||||
import lg "core:math/linalg"
|
||||
import "game:assets"
|
||||
import "game:container/spanpool"
|
||||
import "libs:tracy"
|
||||
|
||||
_ :: log
|
||||
|
||||
MAX_CONTACTS :: 1024 * 16
|
||||
|
||||
Vec3 :: [3]f32
|
||||
@ -122,7 +125,7 @@ Sim_State :: struct {
|
||||
}
|
||||
|
||||
DEV_BUILD :: #config(DEV, false)
|
||||
NUM_SIM_STATES :: 2 when DEV_BUILD else 1
|
||||
NUM_SIM_STATES :: 2
|
||||
|
||||
Scene :: struct {
|
||||
assetman: ^assets.Asset_Manager,
|
||||
@ -537,6 +540,40 @@ get_body :: proc(sim_state: ^Sim_State, handle: Body_Handle) -> Body_Ptr {
|
||||
return &sim_state.bodies_slice[index]
|
||||
}
|
||||
|
||||
get_interpolated_body :: proc(
|
||||
scene: ^Scene,
|
||||
solver_config: Solver_Config,
|
||||
handle: Body_Handle,
|
||||
) -> Body {
|
||||
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 = Body {
|
||||
q = lg.QUATERNIONF32_IDENTITY,
|
||||
}
|
||||
|
||||
if prev_body.alive && body.alive {
|
||||
// interpolate
|
||||
t := scene.solver_state.accumulated_time / solver_config.timestep
|
||||
log.debugf("t = {}", t)
|
||||
result = prev_body^
|
||||
result.x = body.x * t + (1.0 - t) * prev_body.x
|
||||
result.q = lg.quaternion_slerp_f32(prev_body.q, body.q, t)
|
||||
result.v = lg.lerp(prev_body.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 prev_body.alive {
|
||||
result = prev_body^
|
||||
} else if body.alive {
|
||||
result = body^
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
remove_shape :: proc(sim_state: ^Sim_State, idx: i32) {
|
||||
cur_idx := idx
|
||||
for {
|
||||
|
@ -696,6 +696,8 @@ simulate :: proc(
|
||||
dt: f32,
|
||||
commit := true, // commit = false is a special mode for debugging physics stepping to allow rerunning the same step each frame
|
||||
step_mode := Step_Mode.Accumulated_Time,
|
||||
) -> (
|
||||
accumulated_dt: f32,
|
||||
) {
|
||||
tracy.Zone()
|
||||
assert(config.timestep > 0)
|
||||
@ -704,31 +706,37 @@ simulate :: proc(
|
||||
|
||||
prune_immediate(scene)
|
||||
|
||||
copy_sim_state(get_next_sim_state(scene), get_sim_state(scene))
|
||||
did_copy := false
|
||||
sim_state := get_next_sim_state(scene)
|
||||
|
||||
// runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
sim_cache: Sim_Cache
|
||||
sim_cache.level_geom_asset_bvh = make_map(
|
||||
map[Level_Geom_Handle]assets.Loaded_BVH,
|
||||
context.temp_allocator,
|
||||
)
|
||||
|
||||
simulated_dt := f32(0)
|
||||
|
||||
num_steps := 0
|
||||
switch step_mode {
|
||||
case .Accumulated_Time:
|
||||
state.accumulated_time += dt
|
||||
|
||||
for state.accumulated_time >= config.timestep {
|
||||
if !did_copy {
|
||||
did_copy = true
|
||||
copy_sim_state(get_next_sim_state(scene), get_sim_state(scene))
|
||||
}
|
||||
num_steps += 1
|
||||
state.accumulated_time -= config.timestep
|
||||
simulated_dt += config.timestep
|
||||
|
||||
if num_steps < MAX_STEPS {
|
||||
simulate_step(scene, sim_state, &sim_cache, config)
|
||||
}
|
||||
}
|
||||
case .Single:
|
||||
copy_sim_state(get_next_sim_state(scene), get_sim_state(scene))
|
||||
simulate_step(scene, get_next_sim_state(scene), &sim_cache, config)
|
||||
num_steps += 1
|
||||
}
|
||||
@ -742,6 +750,8 @@ simulate :: proc(
|
||||
state.immediate_suspension_constraints.num_items = 0
|
||||
state.immediate_engines.num_items = 0
|
||||
state.immediate_level_geoms.num_items = 0
|
||||
|
||||
return simulated_dt
|
||||
}
|
||||
|
||||
GLOBAL_PLANE :: collision.Plane {
|
||||
@ -1332,7 +1342,7 @@ pgs_solve_suspension :: proc(
|
||||
) *
|
||||
math.sign(lateral_to_longitudinal_relation)
|
||||
|
||||
v.turn_assist = drift_amount * math.PI * 0.1
|
||||
v.turn_assist = drift_amount * math.PI * 0.15
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user