1522 lines
38 KiB
Odin
1522 lines
38 KiB
Odin
// This file is compiled as part of the `odin.dll` file. It contains the
|
|
// procs that `game_hot_reload.exe` will call, such as:
|
|
//
|
|
// game_init: Sets up the game state
|
|
// game_update: Run once per frame
|
|
// game_shutdown: Shuts down game and frees memory
|
|
// game_memory: Run just before a hot reload, so game.exe has a pointer to the
|
|
// game's memory.
|
|
// game_hot_reloaded: Run after a hot reload so that the `g_mem` global variable
|
|
// can be set to whatever pointer it was in the old DLL.
|
|
//
|
|
// Note: When compiled as part of the release executable this whole package is imported as a normal
|
|
// odin package instead of a DLL.
|
|
|
|
package game
|
|
|
|
import "assets"
|
|
import "common:name"
|
|
import "core:fmt"
|
|
import "core:hash"
|
|
import "core:log"
|
|
import "core:math"
|
|
import "core:math/linalg"
|
|
import "game:physics"
|
|
import "game:render"
|
|
import rl "libs:raylib"
|
|
import "libs:raylib/rlgl"
|
|
import "libs:tracy"
|
|
import "ui"
|
|
|
|
PIXEL_WINDOW_HEIGHT :: 360
|
|
|
|
Track :: struct {
|
|
points: [dynamic]rl.Vector3,
|
|
}
|
|
|
|
copy_track :: proc(dst, src: ^Track) {
|
|
resize(&dst.points, len(src.points))
|
|
copy(dst.points[:], src.points[:])
|
|
}
|
|
|
|
destroy_track :: proc(track: ^Track) {
|
|
delete(track.points)
|
|
}
|
|
|
|
Debug_Draw_State :: struct {
|
|
show_menu: bool,
|
|
draw_physics_scene: bool,
|
|
}
|
|
|
|
World :: struct {
|
|
player_pos: rl.Vector3,
|
|
track: Track,
|
|
physics_scene: physics.Scene,
|
|
pause: bool,
|
|
car_com: rl.Vector3,
|
|
car_handle: physics.Body_Handle,
|
|
engine_handle: physics.Engine_Handle,
|
|
debug_state: Debug_Draw_State,
|
|
}
|
|
world_init :: proc(world: ^World) {
|
|
physics.scene_init(&world.physics_scene, &g_mem.assetman)
|
|
}
|
|
copy_world :: proc(dst, src: ^World) {
|
|
copy_track(&dst.track, &src.track)
|
|
physics.copy_physics_scene(&dst.physics_scene, &src.physics_scene)
|
|
dst.player_pos = src.player_pos
|
|
dst.pause = src.pause
|
|
dst.car_com = src.car_com
|
|
dst.car_handle = src.car_handle
|
|
dst.engine_handle = src.engine_handle
|
|
dst.debug_state = src.debug_state
|
|
}
|
|
destroy_world :: proc(world: ^World) {
|
|
destroy_track(&world.track)
|
|
physics.scene_destroy(&world.physics_scene)
|
|
}
|
|
|
|
Runtime_World :: struct {
|
|
world_snapshots: []World,
|
|
current_world_index: int,
|
|
camera_yaw_pitch: rl.Vector2,
|
|
camera_speed: f32,
|
|
camera: rl.Camera3D,
|
|
orbit_camera: Orbit_Camera,
|
|
camera_mode: Camera_Mode,
|
|
dt: f32,
|
|
rewind_simulation: bool,
|
|
commit_simulation: bool,
|
|
single_step_simulation: bool,
|
|
}
|
|
|
|
runtime_world_init :: proc(runtime_world: ^Runtime_World, num_snapshots: int = 2) {
|
|
runtime_world.world_snapshots = make([]World, num_snapshots)
|
|
world := runtime_world_current_world(runtime_world)
|
|
physics.scene_init(&world.physics_scene, &g_mem.assetman)
|
|
world.debug_state.show_menu = true
|
|
world.debug_state.draw_physics_scene = true
|
|
}
|
|
|
|
runtime_world_current_world :: proc(runtime_world: ^Runtime_World) -> ^World {
|
|
return &runtime_world.world_snapshots[runtime_world.current_world_index]
|
|
}
|
|
runtime_world_next_world :: proc(runtime_world: ^Runtime_World) -> ^World {
|
|
next_world_index :=
|
|
(runtime_world.current_world_index + 1) %% len(runtime_world.world_snapshots)
|
|
return &runtime_world.world_snapshots[next_world_index]
|
|
}
|
|
|
|
runtime_world_destroy :: proc(runtime_world: ^Runtime_World) {
|
|
for &world in runtime_world.world_snapshots {
|
|
destroy_world(&world)
|
|
}
|
|
delete(runtime_world.world_snapshots)
|
|
}
|
|
|
|
Car :: struct {
|
|
pos: rl.Vector3,
|
|
}
|
|
|
|
SOLVER_CONFIG :: physics.Solver_Config {
|
|
timestep = 1.0 / 60,
|
|
gravity = rl.Vector3{0, -9.8, 0},
|
|
substreps_minus_one = 4 - 1,
|
|
}
|
|
|
|
Game_Memory :: struct {
|
|
name_container: name.Container,
|
|
assetman: assets.Asset_Manager,
|
|
runtime_world: Runtime_World,
|
|
es: Editor_State,
|
|
ui_context: ui.Context,
|
|
default_font: rl.Font,
|
|
editor: bool,
|
|
preview_bvh: int,
|
|
preview_node: int,
|
|
physics_pause: bool,
|
|
mouse_captured: bool,
|
|
free_cam: bool,
|
|
}
|
|
|
|
Camera_Mode :: enum {
|
|
Orbit,
|
|
Hood,
|
|
}
|
|
|
|
Track_Edit_State :: enum {
|
|
// Point selection
|
|
Select,
|
|
// Moving points
|
|
Move,
|
|
}
|
|
|
|
Move_Axis :: enum {
|
|
None,
|
|
X,
|
|
Y,
|
|
Z,
|
|
XZ,
|
|
XY,
|
|
YZ,
|
|
}
|
|
|
|
// For undo/redo
|
|
World_Stack :: struct {
|
|
worlds: []World,
|
|
head, tail: int,
|
|
}
|
|
|
|
world_stack_init :: proc(stack: ^World_Stack, num_snapshots: int, allocator := context.allocator) {
|
|
assert(num_snapshots > 0)
|
|
stack.worlds = make([]World, num_snapshots, allocator)
|
|
world_stack_push(stack)
|
|
|
|
world_init(world_stack_current(stack))
|
|
}
|
|
|
|
world_stack_destroy :: proc(stack: ^World_Stack, allocator := context.allocator) {
|
|
for &world in stack.worlds {
|
|
destroy_world(&world)
|
|
}
|
|
delete(stack.worlds, allocator)
|
|
}
|
|
|
|
world_stack_len :: proc(stack: ^World_Stack) -> int {
|
|
if stack.tail >= stack.head {
|
|
return stack.tail - stack.head
|
|
} else {
|
|
return (stack.tail + len(stack.worlds)) - stack.head
|
|
}
|
|
}
|
|
|
|
world_stack_current :: proc(stack: ^World_Stack) -> ^World {
|
|
if world_stack_len(stack) > 0 {
|
|
return &stack.worlds[(stack.tail - 1) %% len(stack.worlds)]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
world_stack_push :: proc(stack: ^World_Stack) {
|
|
stack_len := world_stack_len(stack)
|
|
assert(stack_len <= len(stack.worlds))
|
|
|
|
if stack_len == len(stack.worlds) {
|
|
stack.head = (stack.head + 1) %% len(stack.worlds)
|
|
}
|
|
assert(world_stack_len(stack) < len(stack.worlds))
|
|
|
|
prev_world := world_stack_current(stack)
|
|
stack.tail = (stack.tail + 1) %% len(stack.worlds)
|
|
new_world := world_stack_current(stack)
|
|
|
|
if prev_world != nil {
|
|
copy_world(new_world, prev_world)
|
|
}
|
|
}
|
|
|
|
world_stack_pop :: proc(stack: ^World_Stack) {
|
|
if world_stack_len(stack) > 1 {
|
|
stack.tail = (stack.tail - 1) %% len(stack.worlds)
|
|
}
|
|
}
|
|
|
|
Editor_State :: struct {
|
|
world_stack: World_Stack,
|
|
mouse_captured: bool,
|
|
point_selection: map[int]bool,
|
|
track_edit_state: Track_Edit_State,
|
|
move_axis: Move_Axis,
|
|
prev_mouse_pos: rl.Vector2,
|
|
move_initial_pos: rl.Vector3,
|
|
total_movement_world: rl.Vector3,
|
|
camera: Free_Camera,
|
|
}
|
|
|
|
editor_state_init :: proc(es: ^Editor_State, num_snapshots: int) {
|
|
world_stack_init(&es.world_stack, num_snapshots)
|
|
world_stack_current(&es.world_stack).debug_state = {
|
|
show_menu = true,
|
|
draw_physics_scene = true,
|
|
}
|
|
}
|
|
|
|
editor_state_destroy :: proc(es: ^Editor_State) {
|
|
world_stack_destroy(&es.world_stack)
|
|
}
|
|
|
|
g_mem: ^Game_Memory
|
|
|
|
get_runtime_world :: proc() -> ^Runtime_World {
|
|
return &g_mem.runtime_world
|
|
}
|
|
|
|
get_world :: proc() -> ^World {
|
|
return(
|
|
g_mem.editor ? world_stack_current(&g_mem.es.world_stack) : &g_mem.runtime_world.world_snapshots[g_mem.runtime_world.current_world_index] \
|
|
)
|
|
}
|
|
|
|
get_editor_state :: proc() -> ^Editor_State {
|
|
return &g_mem.es
|
|
}
|
|
|
|
game_camera :: proc() -> rl.Camera2D {
|
|
w := f32(rl.GetScreenWidth())
|
|
h := f32(rl.GetScreenHeight())
|
|
|
|
return {
|
|
zoom = h / PIXEL_WINDOW_HEIGHT,
|
|
target = get_world().player_pos.xy,
|
|
offset = {w / 2, h / 2},
|
|
}
|
|
}
|
|
|
|
camera_rotation_matrix :: proc() -> matrix[3, 3]f32 {
|
|
return linalg.matrix3_from_euler_angles_xy(
|
|
get_runtime_world().camera_yaw_pitch.x,
|
|
get_runtime_world().camera_yaw_pitch.y,
|
|
)
|
|
}
|
|
|
|
camera_forward_vec :: proc() -> rl.Vector3 {
|
|
rotation_matrix := camera_rotation_matrix()
|
|
return rotation_matrix * rl.Vector3{0, 0, 1}
|
|
}
|
|
|
|
game_camera_3d :: proc() -> rl.Camera3D {
|
|
if g_mem.editor || g_mem.free_cam {
|
|
return free_camera_to_rl(&g_mem.es.camera)
|
|
}
|
|
|
|
return get_runtime_world().camera
|
|
}
|
|
|
|
ui_camera :: proc() -> rl.Camera2D {
|
|
return {zoom = 1}
|
|
}
|
|
|
|
select_track_point :: proc(index: int) {
|
|
clear(&g_mem.es.point_selection)
|
|
g_mem.es.point_selection[index] = true
|
|
}
|
|
|
|
is_point_selected :: proc() -> bool {
|
|
return len(g_mem.es.point_selection) > 0
|
|
}
|
|
|
|
add_track_spline_point :: proc() {
|
|
forward := -free_camera_rotation(g_mem.es.camera)[2]
|
|
|
|
append(&get_world().track.points, g_mem.es.camera.pos + forward)
|
|
select_track_point(len(&get_world().track.points) - 1)
|
|
}
|
|
|
|
get_movement_axes :: proc(
|
|
axis: Move_Axis,
|
|
out_axes: ^[2]rl.Vector3,
|
|
out_colors: ^[2]rl.Color,
|
|
) -> (
|
|
axes: []rl.Vector3,
|
|
colors: []rl.Color,
|
|
) {
|
|
switch axis {
|
|
case .None:
|
|
return out_axes[0:0], {}
|
|
case .X:
|
|
out_axes[0] = {1, 0, 0}
|
|
out_colors[0] = rl.RED
|
|
return out_axes[0:1], out_colors[0:1]
|
|
case .Y:
|
|
out_axes[0] = {0, 1, 0}
|
|
out_colors[0] = rl.GREEN
|
|
return out_axes[0:1], out_colors[0:1]
|
|
case .Z:
|
|
out_axes[0] = {0, 0, 1}
|
|
out_colors[0] = rl.BLUE
|
|
return out_axes[0:1], out_colors[0:1]
|
|
case .XZ:
|
|
out_axes[0] = {1, 0, 0}
|
|
out_axes[1] = {0, 0, 1}
|
|
out_colors[0] = rl.RED
|
|
out_colors[1] = rl.BLUE
|
|
return out_axes[:], out_colors[:]
|
|
case .XY:
|
|
out_axes[0] = {1, 0, 0}
|
|
out_axes[1] = {0, 1, 0}
|
|
out_colors[0] = rl.RED
|
|
out_colors[1] = rl.GREEN
|
|
return out_axes[:], out_colors[:]
|
|
case .YZ:
|
|
out_axes[0] = {0, 1, 0}
|
|
out_axes[1] = {0, 0, 1}
|
|
out_colors[0] = rl.GREEN
|
|
out_colors[1] = rl.BLUE
|
|
return out_axes[:], out_colors[:]
|
|
}
|
|
|
|
return out_axes[0:0], out_colors[0:0]
|
|
}
|
|
|
|
World_Update_Config :: struct {
|
|
single_step_physics: bool,
|
|
commit_physics: bool,
|
|
}
|
|
|
|
update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
|
|
|
|
if !world.pause {
|
|
car_model := assets.get_model(&g_mem.assetman, "assets/ice_cream_truck.glb")
|
|
car_bounds := rl.GetModelBoundingBox(car_model)
|
|
world.car_com = (car_bounds.min + car_bounds.max) / 2
|
|
|
|
if true {
|
|
physics.immediate_body(
|
|
&world.physics_scene,
|
|
#hash("floor", "fnv32a"),
|
|
physics.Body_Config {
|
|
name = name.from_string("Floor"),
|
|
initial_pos = {0, -0.5, 0},
|
|
initial_rot = linalg.QUATERNIONF32_IDENTITY,
|
|
shapes = {
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
inner_shape = physics.Shape_Box{size = {100, 1, 100}},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
physics.immediate_body(
|
|
&world.physics_scene,
|
|
#hash("ramp", "fnv32a"),
|
|
physics.Body_Config {
|
|
name = name.from_string("Ramp"),
|
|
initial_pos = {0, 0, 0},
|
|
initial_rot = linalg.quaternion_from_euler_angle_x_f32(-10 * math.RAD_PER_DEG),
|
|
shapes = {
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
inner_shape = physics.Shape_Box{size = {5, 1, 100}},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
body_low_convex := assets.get_convex(
|
|
&g_mem.assetman,
|
|
"assets/ice_cream_truck_body_low_convex.obj",
|
|
)
|
|
body_high_convex := assets.get_convex(
|
|
&g_mem.assetman,
|
|
"assets/ice_cream_truck_body_high_convex.obj",
|
|
)
|
|
cone_convex := assets.get_convex(&g_mem.assetman, "assets/cone_convex.obj")
|
|
left_fence_convex := assets.get_convex(&g_mem.assetman, "assets/left_fence_convex.obj")
|
|
right_fence_convex := assets.get_convex(&g_mem.assetman, "assets/right_fence_convex.obj")
|
|
|
|
world.car_handle = physics.immediate_body(
|
|
&world.physics_scene,
|
|
#hash("car", "fnv32a"),
|
|
physics.Body_Config {
|
|
name = name.from_string("Car"),
|
|
initial_pos = {0, 4, -10},
|
|
initial_rot = linalg.QUATERNIONF32_IDENTITY,
|
|
// initial_rot = linalg.quaternion_angle_axis(
|
|
// math.RAD_PER_DEG * 180,
|
|
// rl.Vector3{0, 0, 1},
|
|
// ) *
|
|
// linalg.quaternion_angle_axis(math.RAD_PER_DEG * 30, rl.Vector3{1, 0, 0}),
|
|
initial_ang_vel = {0, 0, 0},
|
|
shapes = {
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
inner_shape = physics.Shape_Convex {
|
|
mesh = body_low_convex.mesh,
|
|
center_of_mass = body_low_convex.center_of_mass,
|
|
inertia_tensor = auto_cast body_low_convex.inertia_tensor,
|
|
total_volume = body_low_convex.total_volume,
|
|
},
|
|
},
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
inner_shape = physics.Shape_Convex {
|
|
mesh = body_high_convex.mesh,
|
|
center_of_mass = body_high_convex.center_of_mass,
|
|
inertia_tensor = auto_cast body_high_convex.inertia_tensor,
|
|
total_volume = body_high_convex.total_volume,
|
|
},
|
|
},
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
density_minus_one = 0.2 - 1,
|
|
inner_shape = physics.Shape_Convex {
|
|
mesh = cone_convex.mesh,
|
|
center_of_mass = cone_convex.center_of_mass,
|
|
inertia_tensor = auto_cast cone_convex.inertia_tensor,
|
|
total_volume = cone_convex.total_volume,
|
|
},
|
|
},
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
density_minus_one = 0.2 - 1,
|
|
inner_shape = physics.Shape_Convex {
|
|
mesh = left_fence_convex.mesh,
|
|
center_of_mass = left_fence_convex.center_of_mass,
|
|
inertia_tensor = auto_cast left_fence_convex.inertia_tensor,
|
|
total_volume = left_fence_convex.total_volume,
|
|
},
|
|
},
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
density_minus_one = 0.2 - 1,
|
|
inner_shape = physics.Shape_Convex {
|
|
mesh = right_fence_convex.mesh,
|
|
center_of_mass = right_fence_convex.center_of_mass,
|
|
inertia_tensor = auto_cast right_fence_convex.inertia_tensor,
|
|
total_volume = right_fence_convex.total_volume,
|
|
},
|
|
},
|
|
},
|
|
mass = 1000,
|
|
com_shift = physics.Vec3{0, 0, -0.5},
|
|
},
|
|
)
|
|
|
|
if true {
|
|
for x in 0 ..< 10 {
|
|
for y in 0 ..< 10 {
|
|
box_name := name.from_string(fmt.tprintf("box[{},{}]", x, y))
|
|
physics.immediate_body(
|
|
&world.physics_scene,
|
|
u32(box_name),
|
|
physics.Body_Config {
|
|
name = box_name,
|
|
initial_pos = {-5, 0.5 + f32(y) * 1.1, f32(x) * 3 + 10},
|
|
initial_rot = linalg.QUATERNIONF32_IDENTITY,
|
|
shapes = {
|
|
{
|
|
rel_q = linalg.QUATERNIONF32_IDENTITY,
|
|
inner_shape = physics.Shape_Box{size = 1},
|
|
},
|
|
},
|
|
mass = 10,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// car_body := physics.get_body(&world.physics_scene, runtime_world.car_handle)
|
|
|
|
// camera := &runtime_world.camera
|
|
|
|
// camera.up = rl.Vector3{0, 1, 0}
|
|
// camera.fovy = 60
|
|
// camera.projection = .PERSPECTIVE
|
|
// camera.position = physics.body_local_to_world(
|
|
// car_body,
|
|
// physics.body_world_to_local(
|
|
// car_body,
|
|
// physics.body_local_to_world(car_body, rl.Vector3{1, 0, -2}),
|
|
// ),
|
|
// )
|
|
// camera.target = physics.get_body(&world.physics_scene, runtime_world.car_handle).x
|
|
// if runtime_world.camera.position == {} {
|
|
// runtime_world.camera.position = runtime_world.camera.target - rl.Vector3{10, 0, 10}
|
|
// }
|
|
|
|
sim_state := physics.get_sim_state(&world.physics_scene)
|
|
|
|
wheel_extent_x_front := f32(2) / 2
|
|
wheel_extent_x_back := f32(2) / 2
|
|
wheel_y := f32(0.5)
|
|
rest := f32(0.7)
|
|
natural_frequency := f32(0.4)
|
|
damping := f32(0.06)
|
|
radius := f32(0.737649) / 2
|
|
wheel_front_z := f32(1.6)
|
|
wheel_back_z := f32(-1.63)
|
|
wheel_mass := f32(14)
|
|
|
|
pacejka_long := physics.slice_to_pacejka94_long(
|
|
assets.get_curve_1d(&g_mem.assetman, "assets/tyre_longitudinal.csv"),
|
|
)
|
|
pacejka_lat := physics.slice_to_pacejka94_lat(
|
|
assets.get_curve_1d(&g_mem.assetman, "assets/tyre_lateral.csv"),
|
|
)
|
|
|
|
wheel_fl := physics.immediate_suspension_constraint(
|
|
&world.physics_scene,
|
|
#hash("FL", "fnv32a"),
|
|
{
|
|
rel_pos = {-wheel_extent_x_front, wheel_y, wheel_front_z},
|
|
rel_dir = {0, -1, 0},
|
|
radius = radius,
|
|
rest = rest,
|
|
natural_frequency = natural_frequency,
|
|
damping = damping,
|
|
body = world.car_handle,
|
|
mass = wheel_mass,
|
|
pacejka_lat = pacejka_lat,
|
|
pacejka_long = pacejka_long,
|
|
},
|
|
)
|
|
wheel_fr := physics.immediate_suspension_constraint(
|
|
&world.physics_scene,
|
|
#hash("FR", "fnv32a"),
|
|
{
|
|
rel_pos = {wheel_extent_x_front, wheel_y, wheel_front_z},
|
|
rel_dir = {0, -1, 0},
|
|
radius = radius,
|
|
rest = rest,
|
|
natural_frequency = natural_frequency,
|
|
damping = damping,
|
|
body = world.car_handle,
|
|
mass = wheel_mass,
|
|
pacejka_lat = pacejka_lat,
|
|
pacejka_long = pacejka_long,
|
|
},
|
|
)
|
|
wheel_rl := physics.immediate_suspension_constraint(
|
|
&world.physics_scene,
|
|
#hash("RL", "fnv32a"),
|
|
{
|
|
rel_pos = {-wheel_extent_x_back, wheel_y, wheel_back_z},
|
|
rel_dir = {0, -1, 0},
|
|
radius = radius,
|
|
rest = rest,
|
|
natural_frequency = natural_frequency,
|
|
damping = damping,
|
|
body = world.car_handle,
|
|
mass = wheel_mass,
|
|
pacejka_lat = pacejka_lat,
|
|
pacejka_long = pacejka_long,
|
|
},
|
|
)
|
|
wheel_rr := physics.immediate_suspension_constraint(
|
|
&world.physics_scene,
|
|
#hash("RR", "fnv32a"),
|
|
{
|
|
rel_pos = {wheel_extent_x_back, wheel_y, wheel_back_z},
|
|
rel_dir = {0, -1, 0},
|
|
radius = radius,
|
|
rest = rest,
|
|
natural_frequency = natural_frequency,
|
|
damping = damping,
|
|
body = world.car_handle,
|
|
mass = wheel_mass,
|
|
pacejka_lat = pacejka_lat,
|
|
pacejka_long = pacejka_long,
|
|
},
|
|
)
|
|
|
|
world.engine_handle = physics.immediate_engine(
|
|
&world.physics_scene,
|
|
#hash("engine", "fnv32a"),
|
|
physics.Engine_Config {
|
|
rpm_torque_curve = assets.get_curve_2d(
|
|
&g_mem.assetman,
|
|
"assets/ae86_rpm_torque.csv",
|
|
),
|
|
lowest_rpm = 1200,
|
|
rev_limit_rpm = 7800,
|
|
rev_limit_interval = 0.01,
|
|
inertia = 0.264 * 0.5,
|
|
internal_friction = 0.005,
|
|
gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861},
|
|
axle = physics.Drive_Axle_Config {
|
|
wheels = {wheel_rl, wheel_rr},
|
|
wheel_count = 2,
|
|
diff_type = .Fixed,
|
|
final_drive_ratio = 4.1,
|
|
},
|
|
},
|
|
)
|
|
|
|
drive_wheels := []physics.Suspension_Constraint_Handle{wheel_rl, wheel_rr}
|
|
turn_wheels := []physics.Suspension_Constraint_Handle{wheel_fl, wheel_fr}
|
|
front_wheels := turn_wheels
|
|
back_wheels := drive_wheels
|
|
|
|
DRIVE_IMPULSE :: 3000
|
|
BRAKE_IMPULSE :: 10
|
|
TURN_ANGLE :: -f32(50) * math.RAD_PER_DEG
|
|
// 68% front, 32% rear
|
|
BRAKE_BIAS :: f32(0.68)
|
|
|
|
gas_input := rl.GetGamepadAxisMovement(0, .RIGHT_TRIGGER) * 0.5 + 0.5
|
|
brake_input := rl.GetGamepadAxisMovement(0, .LEFT_TRIGGER) * 0.5 + 0.5
|
|
if rl.IsKeyDown(.S) && !g_mem.free_cam {
|
|
brake_input = 1
|
|
}
|
|
if rl.IsKeyDown(.W) && !g_mem.free_cam {
|
|
gas_input = 1
|
|
}
|
|
|
|
for wheel_handle in front_wheels {
|
|
wheel := physics.get_suspension_constraint(sim_state, wheel_handle)
|
|
|
|
wheel.brake_impulse = brake_input * BRAKE_BIAS * BRAKE_IMPULSE
|
|
}
|
|
|
|
for wheel_handle in back_wheels {
|
|
wheel := physics.get_suspension_constraint(sim_state, wheel_handle)
|
|
|
|
wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE
|
|
}
|
|
|
|
engine := physics.get_engine(sim_state, 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, world.car_handle)
|
|
turn_vel_correction := clamp(4.0 / linalg.length(car_body.v), 0, 1)
|
|
|
|
turn_input := rl.GetGamepadAxisMovement(0, .LEFT_X)
|
|
if abs(turn_input) < GAMEPAD_DEADZONE {
|
|
turn_input = 0
|
|
}
|
|
if rl.IsKeyDown(.A) && !g_mem.free_cam {
|
|
turn_input = -1
|
|
}
|
|
if rl.IsKeyDown(.D) && !g_mem.free_cam {
|
|
turn_input = 1
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
{
|
|
physics.immediate_level_geom(
|
|
&world.physics_scene,
|
|
u32(name.from_string("level_geom_from_asset")),
|
|
{
|
|
position = physics.Vec3{5, 0, 0},
|
|
rotation = linalg.QUATERNIONF32_IDENTITY,
|
|
source = physics.Level_Geometry_Asset(
|
|
"assets/blender/test_level_blend/NurbsPath.glb",
|
|
),
|
|
},
|
|
)
|
|
}
|
|
|
|
{
|
|
level_model := assets.get_model(&g_mem.assetman, "assets/testblend.glb")
|
|
|
|
for i in 0 ..< level_model.meshCount {
|
|
mesh := &level_model.meshes[i]
|
|
|
|
if mesh.triangleCount > 0 {
|
|
assert(mesh.vertexCount <= i32(max(u16)))
|
|
m := level_model.transform
|
|
pos := physics.Vec3{m[3][0], m[3][1], m[3][2]}
|
|
rotation := linalg.quaternion_from_matrix4_f32(auto_cast level_model.transform)
|
|
physics.immediate_level_geom(
|
|
&world.physics_scene,
|
|
hash.fnv32a(transmute([]byte)(fmt.tprintf("level mesh {}", i))),
|
|
{
|
|
position = pos,
|
|
rotation = rotation,
|
|
source = physics.Level_Geometry_Mesh {
|
|
vertices = (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount],
|
|
indices = mesh.indices[:mesh.triangleCount * 3],
|
|
},
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(world.track.points) > 1 {
|
|
interpolated_points := calculate_spline_interpolated_points(
|
|
world.track.points[:],
|
|
context.temp_allocator,
|
|
)
|
|
track_verts, track_inds := spline_generate_mesh(
|
|
interpolated_points,
|
|
context.temp_allocator,
|
|
)
|
|
physics.immediate_level_geom(
|
|
&world.physics_scene,
|
|
#hash("track", "fnv32a"),
|
|
{
|
|
rotation = linalg.QUATERNIONF32_IDENTITY,
|
|
source = physics.Level_Geometry_Mesh {
|
|
vertices = track_verts,
|
|
indices = track_inds,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
physics.simulate(
|
|
&world.physics_scene,
|
|
SOLVER_CONFIG,
|
|
dt,
|
|
commit = config.commit_physics,
|
|
step_mode = config.single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time,
|
|
)
|
|
}
|
|
|
|
{
|
|
engine_loop := assets.get_music(&g_mem.assetman, "assets/engine_loop.wav")
|
|
|
|
if !rl.IsMusicStreamPlaying(engine_loop) {
|
|
rl.PlayMusicStream(engine_loop)
|
|
}
|
|
rl.UpdateMusicStream(engine_loop)
|
|
|
|
sim_state := physics.get_sim_state(&world.physics_scene)
|
|
engine := physics.get_engine(sim_state, world.engine_handle)
|
|
rpm_percent :=
|
|
max(physics.angular_velocity_to_rpm(engine.w) - engine.lowest_rpm, 0.0) /
|
|
(engine.rev_limit_rpm - engine.lowest_rpm)
|
|
|
|
pitch := linalg.lerp(f32(1.0), f32(3.0), rpm_percent)
|
|
rl.SetMusicPitch(engine_loop, pitch)
|
|
}
|
|
}
|
|
|
|
update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
|
cur_world := runtime_world_current_world(runtime_world)
|
|
|
|
runtime_world.dt = dt
|
|
should_single_step := rl.IsKeyPressed(.PERIOD)
|
|
runtime_world.rewind_simulation = rl.IsKeyPressed(.COMMA)
|
|
runtime_world.commit_simulation = !g_mem.physics_pause || should_single_step
|
|
runtime_world.single_step_simulation = should_single_step
|
|
|
|
if !runtime_world.rewind_simulation {
|
|
next_world := runtime_world_next_world(runtime_world)
|
|
copy_world(next_world, cur_world)
|
|
config := World_Update_Config {
|
|
commit_physics = true,
|
|
single_step_physics = should_single_step,
|
|
}
|
|
update_world(next_world, dt, config)
|
|
|
|
if runtime_world.commit_simulation {
|
|
runtime_world.current_world_index =
|
|
(runtime_world.current_world_index + 1) %% len(runtime_world.world_snapshots)
|
|
}
|
|
} else {
|
|
runtime_world.current_world_index =
|
|
(runtime_world.current_world_index - 1) %% len(runtime_world.world_snapshots)
|
|
}
|
|
}
|
|
|
|
Free_Camera :: struct {
|
|
pos: rl.Vector3,
|
|
yaw, pitch: f32,
|
|
speed: f32,
|
|
}
|
|
|
|
Orbit_Camera :: struct {
|
|
target: rl.Vector3,
|
|
yaw, pitch: f32,
|
|
distance: f32,
|
|
}
|
|
|
|
GAMEPAD_DEADZONE :: f32(0.07)
|
|
|
|
collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) {
|
|
gamepad_delta := rl.Vector2 {
|
|
rl.GetGamepadAxisMovement(0, .RIGHT_X),
|
|
rl.GetGamepadAxisMovement(0, .RIGHT_Y),
|
|
}
|
|
if abs(gamepad_delta.x) < GAMEPAD_DEADZONE {
|
|
gamepad_delta.x = 0
|
|
}
|
|
if abs(gamepad_delta.y) < GAMEPAD_DEADZONE {
|
|
gamepad_delta.y = 0
|
|
}
|
|
|
|
should_capture_mouse := rl.IsMouseButtonDown(.RIGHT)
|
|
if g_mem.mouse_captured != should_capture_mouse {
|
|
if should_capture_mouse {
|
|
rl.DisableCursor()
|
|
} else {
|
|
rl.EnableCursor()
|
|
}
|
|
g_mem.mouse_captured = should_capture_mouse
|
|
}
|
|
|
|
mouse_delta := g_mem.mouse_captured ? rl.GetMouseDelta() : 0
|
|
|
|
MOUSE_SENSE :: 0.005
|
|
GAMEPAD_SENSE :: 1
|
|
|
|
if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) {
|
|
sense = MOUSE_SENSE
|
|
delta = mouse_delta
|
|
} else {
|
|
sense = GAMEPAD_SENSE * rl.GetFrameTime()
|
|
delta = gamepad_delta
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
orbit_camera_update :: proc(camera: ^Orbit_Camera) {
|
|
world := runtime_world_current_world(get_runtime_world())
|
|
camera.target =
|
|
physics.get_body(physics.get_sim_state(&world.physics_scene), world.car_handle).x
|
|
|
|
delta, sense := collect_camera_input()
|
|
|
|
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)
|
|
}
|
|
|
|
orbit_camera_to_rl :: proc(camera: Orbit_Camera) -> rl.Camera3D {
|
|
result: rl.Camera3D
|
|
|
|
result.target = camera.target
|
|
|
|
rotation :=
|
|
linalg.matrix3_rotate(-camera.yaw, rl.Vector3{0, 1, 0}) *
|
|
linalg.matrix3_rotate(-camera.pitch, rl.Vector3{1, 0, 0})
|
|
|
|
// rotation = linalg.transpose(rotation)
|
|
|
|
position := rotation * rl.Vector3{0, 0, 1}
|
|
position *= camera.distance
|
|
|
|
result.position = result.target + position
|
|
result.up = rl.Vector3{0, 1, 0}
|
|
result.fovy = 60
|
|
|
|
return result
|
|
}
|
|
|
|
Matrix3 :: # row_major matrix[3, 3]f32
|
|
|
|
free_camera_rotation :: proc(camera: Free_Camera) -> linalg.Matrix3f32 {
|
|
return(
|
|
linalg.matrix3_rotate_f32(camera.yaw, {0, 1, 0}) *
|
|
linalg.matrix3_rotate_f32(camera.pitch, {1, 0, 0}) \
|
|
)
|
|
}
|
|
|
|
free_camera_update :: proc(camera: ^Free_Camera) {
|
|
delta, sense := collect_camera_input()
|
|
|
|
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)
|
|
|
|
input: rl.Vector2
|
|
|
|
if rl.IsKeyDown(.UP) || rl.IsKeyDown(.W) {
|
|
input.y += 1
|
|
}
|
|
if rl.IsKeyDown(.DOWN) || rl.IsKeyDown(.S) {
|
|
input.y -= 1
|
|
}
|
|
if rl.IsKeyDown(.LEFT) || rl.IsKeyDown(.A) {
|
|
input.x -= 1
|
|
}
|
|
if rl.IsKeyDown(.RIGHT) || rl.IsKeyDown(.D) {
|
|
input.x += 1
|
|
}
|
|
|
|
camera.speed += rl.GetMouseWheelMove() * 0.01
|
|
camera.speed = linalg.clamp(camera.speed, 0.01, 10)
|
|
|
|
rotation := free_camera_rotation(camera^)
|
|
forward := -rotation[2]
|
|
right := rotation[0]
|
|
|
|
input = linalg.normalize0(input)
|
|
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^)
|
|
forward := -rotation[2]
|
|
|
|
result.position = camera.pos
|
|
result.target = camera.pos + forward
|
|
result.up = rl.Vector3{0, 1, 0}
|
|
result.fovy = 60
|
|
|
|
return result
|
|
}
|
|
|
|
update :: proc() {
|
|
tracy.Zone()
|
|
|
|
ui.rl_update_inputs(&g_mem.ui_context)
|
|
|
|
ui.begin(&g_mem.ui_context)
|
|
|
|
if rl.IsKeyPressed(.TAB) {
|
|
g_mem.editor = !g_mem.editor
|
|
|
|
// When switching from editor to game, copy editor world into game world
|
|
if !g_mem.editor {
|
|
es := &g_mem.es
|
|
|
|
editor_world := world_stack_current(&es.world_stack)
|
|
copy_world(runtime_world_current_world(&g_mem.runtime_world), editor_world)
|
|
}
|
|
}
|
|
|
|
if rl.IsKeyPressed(.F1) {
|
|
g_mem.free_cam = !g_mem.free_cam
|
|
|
|
// 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
|
|
// mesh_bvh := assets.get_bvh(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
|
|
|
|
// if rl.IsKeyDown(.LEFT_SHIFT) {
|
|
// if g_mem.preview_bvh >= 0 && g_mem.preview_bvh < len(mesh_bvh.bvhs) {
|
|
// b := mesh_bvh.bvhs[g_mem.preview_bvh]
|
|
// node := &b.nodes[g_mem.preview_node]
|
|
|
|
// if !bvh.is_leaf_node(node^) {
|
|
// if rl.IsKeyPressed(.LEFT_BRACKET) {
|
|
// g_mem.preview_node = int(node.child_or_prim_start)
|
|
// } else if rl.IsKeyPressed(.RIGHT_BRACKET) {
|
|
// g_mem.preview_node = int(node.child_or_prim_start + 1)
|
|
// } else if rl.IsKeyPressed(.P) {
|
|
// g_mem.preview_node = 0
|
|
// }
|
|
// }
|
|
// }
|
|
// } else {
|
|
// if rl.IsKeyPressed(.LEFT_BRACKET) {
|
|
// g_mem.preview_bvh -= 1
|
|
// g_mem.preview_node = 0
|
|
// }
|
|
// if rl.IsKeyPressed(.RIGHT_BRACKET) {
|
|
// g_mem.preview_bvh += 1
|
|
// g_mem.preview_node = 0
|
|
// }
|
|
// }
|
|
|
|
if rl.IsKeyPressed(.SPACE) {
|
|
g_mem.physics_pause = !g_mem.physics_pause
|
|
}
|
|
|
|
if g_mem.editor {
|
|
update_editor(get_editor_state(), dt)
|
|
} else {
|
|
update_runtime_world(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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catmull_rom_coefs :: proc(
|
|
v0, v1, v2, v3: rl.Vector3,
|
|
alpha, tension: f32,
|
|
) -> (
|
|
a, b, c, d: rl.Vector3,
|
|
) {
|
|
t01 := math.pow(linalg.distance(v0, v1), alpha)
|
|
t12 := math.pow(linalg.distance(v1, v2), alpha)
|
|
t23 := math.pow(linalg.distance(v2, v3), alpha)
|
|
|
|
m1 := (1.0 - tension) * (v2 - v1 + t12 * ((v1 - v0) / t01 - (v2 - v0) / (t01 + t12)))
|
|
m2 := (1.0 - tension) * (v2 - v1 + t12 * ((v3 - v2) / t23 - (v3 - v1) / (t12 + t23)))
|
|
|
|
a = 2.0 * (v1 - v2) + m1 + m2
|
|
b = -3.0 * (v1 - v2) - m1 - m1 - m2
|
|
c = m1
|
|
d = v1
|
|
|
|
return
|
|
}
|
|
|
|
catmull_rom :: proc(a, b, c, d: rl.Vector3, t: f32) -> rl.Vector3 {
|
|
t2 := t * t
|
|
t3 := t2 * t
|
|
|
|
return a * t3 + b * t2 + c * t + d
|
|
}
|
|
|
|
draw_world :: proc(world: ^World) {
|
|
tracy.Zone()
|
|
|
|
sim_state := physics.get_sim_state(&world.physics_scene)
|
|
|
|
level_model := assets.get_model(&g_mem.assetman, "assets/testblend.glb")
|
|
car_model := assets.get_model(&g_mem.assetman, "assets/ice_cream_truck.glb")
|
|
|
|
phys_debug_state: physics.Debug_State
|
|
physics.init_debug_state(&phys_debug_state)
|
|
|
|
car_body := physics.get_body(sim_state, world.car_handle)
|
|
engine := physics.get_engine(sim_state, world.engine_handle)
|
|
|
|
{
|
|
if rl.IsKeyPressed(.F8) {
|
|
world.debug_state.show_menu = !world.debug_state.show_menu
|
|
}
|
|
if world.debug_state.show_menu {
|
|
ui_ctx := &g_mem.ui_context
|
|
|
|
//ui.push_font_size_style(ui_ctx, 20)
|
|
// defer ui.pop_style(ui_ctx)
|
|
|
|
if ui.window(ui_ctx, "Debug Menu", {x = 0, y = 0, w = 200, h = 300}) {
|
|
cnt := ui.get_current_container(ui_ctx)
|
|
cnt.rect.x = max(cnt.rect.x, 0)
|
|
cnt.rect.y = max(cnt.rect.y, 0)
|
|
cnt.rect.w = max(cnt.rect.w, 200)
|
|
cnt.rect.h = max(cnt.rect.h, 300)
|
|
|
|
if .ACTIVE in ui.header(ui_ctx, "General", {.EXPANDED}) {
|
|
ui.checkbox(
|
|
ui_ctx,
|
|
"Show Physics Scene",
|
|
&world.debug_state.draw_physics_scene,
|
|
)
|
|
}
|
|
|
|
if .ACTIVE in ui.header(ui_ctx, "Car", {.EXPANDED}) &&
|
|
car_body.alive &&
|
|
engine.alive {
|
|
gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios)
|
|
ui.keyval(ui_ctx, "p", car_body.x)
|
|
ui.keyval(ui_ctx, "v", car_body.v)
|
|
ui.keyval(ui_ctx, "gear", engine.gear)
|
|
ui.keyval(ui_ctx, "ratio", physics.lookup_gear_ratio(gear_ratios, engine.gear))
|
|
ui.keyval(ui_ctx, "rpm", physics.angular_velocity_to_rpm(engine.w))
|
|
ui.keyval(ui_ctx, "clutch", engine.clutch)
|
|
ui.keyval(ui_ctx, "speed", linalg.length(car_body.v) * 3.6)
|
|
}
|
|
|
|
if .ACTIVE in ui.header(ui_ctx, "Physics") {
|
|
physics.draw_debug_ui(
|
|
&phys_debug_state,
|
|
ui_ctx,
|
|
&world.physics_scene,
|
|
SOLVER_CONFIG,
|
|
)
|
|
}
|
|
} else {
|
|
log.infof("Window closed")
|
|
world.debug_state.show_menu = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if world.debug_state.draw_physics_scene {
|
|
physics.draw_debug_scene(&world.physics_scene, &phys_debug_state)
|
|
}
|
|
|
|
|
|
car_matrix := rl.QuaternionToMatrix(car_body.q)
|
|
car_matrix =
|
|
(auto_cast linalg.matrix4_translate_f32(physics.body_get_shape_pos(car_body))) * car_matrix
|
|
|
|
// basic_shader := assets.get_shader(
|
|
// &g_mem.assetman,
|
|
// "assets/shaders/lit_vs.glsl",
|
|
// "assets/shaders/lit_ps.glsl",
|
|
// {.Ambient, .LightDir, .LightColor},
|
|
// )
|
|
// light_dir := linalg.normalize(rl.Vector3{1, -1, 0})
|
|
// ambient := rl.Vector3{0.1, 0.1, 0.1}
|
|
// light_color := rl.Vector3{0.816, 0.855, 0.89}
|
|
// rl.SetShaderValue(basic_shader.shader, basic_shader.locations[.LightDir], &light_dir, .VEC3)
|
|
// rl.SetShaderValue(basic_shader.shader, basic_shader.locations[.Ambient], &ambient, .VEC3)
|
|
// rl.SetShaderValue(
|
|
// basic_shader.shader,
|
|
// basic_shader.locations[.LightColor],
|
|
// &light_color,
|
|
// .VEC3,
|
|
// )
|
|
|
|
render.draw_model(level_model, {}, 1)
|
|
|
|
render.draw_model(car_model, {}, car_matrix)
|
|
|
|
render.draw_mesh_light(
|
|
assets.get_model(&g_mem.assetman, "assets/ae86_lights.glb"),
|
|
car_matrix,
|
|
rl.Color{255, 255, 255, 100},
|
|
)
|
|
}
|
|
|
|
draw :: proc() {
|
|
tracy.Zone()
|
|
|
|
rl.BeginDrawing()
|
|
defer rl.EndDrawing()
|
|
rl.ClearBackground(rl.GRAY)
|
|
render.clear_stencil()
|
|
|
|
world := get_world()
|
|
|
|
camera := game_camera_3d()
|
|
|
|
points := &world.track.points
|
|
|
|
interpolated_points := calculate_spline_interpolated_points(points[:], context.temp_allocator)
|
|
|
|
// collision, segment_idx := raycast_spline_tube(
|
|
// interpolated_points,
|
|
// rl.GetScreenToWorldRay(rl.GetMousePosition(), camera),
|
|
// )
|
|
|
|
{
|
|
rl.BeginMode3D(camera)
|
|
defer rl.EndMode3D()
|
|
|
|
draw_world(world)
|
|
|
|
{
|
|
// Debug draw spline road
|
|
if false {
|
|
rlgl.EnableWireMode()
|
|
defer rlgl.DisableWireMode()
|
|
|
|
rlgl.Color3f(1, 0, 0)
|
|
|
|
debug_draw_spline(interpolated_points)
|
|
debug_draw_spline_mesh(interpolated_points)
|
|
}
|
|
|
|
if g_mem.editor {
|
|
es := &g_mem.es
|
|
|
|
switch es.track_edit_state {
|
|
case .Select:
|
|
case .Move:
|
|
rlgl.Begin(rlgl.LINES)
|
|
defer rlgl.End()
|
|
axes_buf: [2]rl.Vector3
|
|
colors_buf: [2]rl.Color
|
|
axes, colors := get_movement_axes(es.move_axis, &axes_buf, &colors_buf)
|
|
|
|
|
|
for v in soa_zip(axis = axes, color = colors) {
|
|
rlgl.Color4ub(v.color.r, v.color.g, v.color.b, v.color.a)
|
|
start, end :=
|
|
es.move_initial_pos -
|
|
v.axis * 100000,
|
|
es.move_initial_pos +
|
|
v.axis * 100000
|
|
|
|
rlgl.Vertex3f(start.x, start.y, start.z)
|
|
rlgl.Vertex3f(end.x, end.y, end.z)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
ui.end(&g_mem.ui_context)
|
|
|
|
rl.BeginMode2D(ui_camera())
|
|
defer rl.EndMode2D()
|
|
|
|
rl.DrawFPS(0, 0)
|
|
|
|
if g_mem.editor {
|
|
rl.DrawText("Editor", 5, 5, 8, rl.ORANGE)
|
|
|
|
switch g_mem.es.track_edit_state {
|
|
case .Select:
|
|
case .Move:
|
|
rl.DrawText(
|
|
fmt.ctprintf("%v %v", g_mem.es.move_axis, g_mem.es.total_movement_world),
|
|
5,
|
|
16,
|
|
8,
|
|
rl.ORANGE,
|
|
)
|
|
}
|
|
}
|
|
|
|
ui.rl_draw(&g_mem.ui_context)
|
|
}
|
|
|
|
if g_mem.editor {
|
|
es := &g_mem.es
|
|
|
|
points_len := len(points)
|
|
|
|
if points_len > 0 {
|
|
// Add point before first
|
|
{
|
|
tangent: rl.Vector3
|
|
|
|
if points_len > 1 {
|
|
tangent = linalg.normalize0(points[1] - points[0])
|
|
} else {
|
|
tangent = rl.Vector3{-1, 0, 0}
|
|
}
|
|
|
|
new_point_pos := points[0] - tangent * 4
|
|
|
|
if (spline_handle(new_point_pos, camera, false)) {
|
|
inject_at(&world.track.points, 0, new_point_pos)
|
|
log.debugf("add point before 0")
|
|
}
|
|
}
|
|
|
|
// Add point after last
|
|
{
|
|
tangent: rl.Vector3
|
|
|
|
if points_len > 1 {
|
|
tangent = linalg.normalize0(points[points_len - 1] - points[points_len - 2])
|
|
} else {
|
|
tangent = rl.Vector3{-1, 0, 0}
|
|
}
|
|
|
|
new_point_pos := points[points_len - 1] + tangent * 4
|
|
|
|
if (spline_handle(new_point_pos, camera, false)) {
|
|
inject_at(&world.track.points, points_len - 1 + 1, new_point_pos)
|
|
log.debugf("add point before 0")
|
|
}
|
|
}
|
|
}
|
|
|
|
selected_point := false
|
|
for i in 0 ..< points_len {
|
|
|
|
if i < points_len - 1 {
|
|
t := (f32(i) + 0.5) / f32(points_len)
|
|
middle_pos := sample_spline(points[:], t)
|
|
|
|
if (spline_handle(middle_pos, camera, false)) {
|
|
inject_at(&world.track.points, i + 1, middle_pos)
|
|
log.debugf("add point after %d", i)
|
|
}
|
|
}
|
|
|
|
if spline_handle(world.track.points[i], camera, es.point_selection[i]) {
|
|
if !rl.IsKeyDown(.LEFT_CONTROL) {
|
|
clear(&g_mem.es.point_selection)
|
|
}
|
|
|
|
g_mem.es.point_selection[i] = true
|
|
selected_point = true
|
|
}
|
|
}
|
|
|
|
if rl.IsMouseButtonPressed(.LEFT) && !selected_point {
|
|
clear(&g_mem.es.point_selection)
|
|
}
|
|
}
|
|
|
|
// axis lines
|
|
if g_mem.editor {
|
|
size := f32(100)
|
|
pos := rl.Vector2{20, f32(rl.GetScreenHeight()) - 20 - size}
|
|
view_rotation := linalg.transpose(rl.GetCameraMatrix(camera))
|
|
view_rotation[3].xyz = 0
|
|
view_proj := view_rotation * rl.MatrixOrtho(-1, 1, 1, -1, -1, 1)
|
|
|
|
center := (rl.Vector4{0, 0, 0, 1} * view_proj).xy * 0.5 + 0.5
|
|
x_axis := (rl.Vector4{1, 0, 0, 1} * view_proj).xy * 0.5 + 0.5
|
|
y_axis := (rl.Vector4{0, 1, 0, 1} * view_proj).xy * 0.5 + 0.5
|
|
z_axis := (rl.Vector4{0, 0, 1, 1} * view_proj).xy * 0.5 + 0.5
|
|
|
|
old_width := rlgl.GetLineWidth()
|
|
rlgl.SetLineWidth(4)
|
|
defer rlgl.SetLineWidth(old_width)
|
|
rl.DrawLineV(pos + center * size, pos + x_axis * size, rl.RED)
|
|
rl.DrawLineV(pos + center * size, pos + y_axis * size, rl.GREEN)
|
|
rl.DrawLineV(pos + center * size, pos + z_axis * size, rl.BLUE)
|
|
}
|
|
|
|
}
|
|
|
|
spline_handle :: proc(
|
|
world_pos: rl.Vector3,
|
|
camera: rl.Camera,
|
|
selected: bool,
|
|
size := f32(20),
|
|
) -> (
|
|
clicked: bool,
|
|
) {
|
|
if linalg.dot(camera.target - camera.position, world_pos - camera.position) < 0 {
|
|
return
|
|
}
|
|
pos := rl.GetWorldToScreen(world_pos, camera)
|
|
|
|
min, max := pos - size, pos + size
|
|
mouse_pos := rl.GetMousePosition()
|
|
|
|
is_hover :=
|
|
(mouse_pos.x >= min.x &&
|
|
mouse_pos.y >= min.y &&
|
|
mouse_pos.x <= max.x &&
|
|
mouse_pos.y <= max.y)
|
|
|
|
|
|
rl.DrawCircleV(pos, size / 2, selected ? rl.BLUE : (is_hover ? rl.ORANGE : rl.WHITE))
|
|
return rl.IsMouseButtonPressed(.LEFT) && is_hover
|
|
}
|
|
|
|
@(export)
|
|
game_update :: proc() -> bool {
|
|
tracy.Zone()
|
|
defer tracy.FrameMark()
|
|
|
|
update()
|
|
draw()
|
|
|
|
when ODIN_OS != .JS {
|
|
// Never run this proc in browser. It contains a 16 ms sleep on web!
|
|
return !rl.WindowShouldClose()
|
|
}
|
|
return true
|
|
}
|
|
|
|
@(export)
|
|
game_init_window :: proc(args: []string) {
|
|
tracy.SetThreadName("Main")
|
|
|
|
init_physfs(args)
|
|
|
|
rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT})
|
|
rl.InitWindow(1280, 720, "Gutter Runner")
|
|
rl.InitAudioDevice()
|
|
rl.SetExitKey(.KEY_NULL)
|
|
rl.SetWindowPosition(200, 200)
|
|
rl.SetTargetFPS(120)
|
|
}
|
|
|
|
DEV_BUILD :: #config(DEV, false)
|
|
|
|
@(export)
|
|
game_init :: proc() {
|
|
g_mem = new(Game_Memory)
|
|
|
|
g_mem^ = Game_Memory{}
|
|
|
|
name.init(&g_mem.name_container)
|
|
name.setup_global_container(&g_mem.name_container)
|
|
init_physifs_raylib_callbacks()
|
|
|
|
assets.assetman_init(&g_mem.assetman)
|
|
assets.init(&g_mem.assetman)
|
|
|
|
editor_state_init(&g_mem.es, 100)
|
|
runtime_world_init(&g_mem.runtime_world, DEV_BUILD ? 100 : 2)
|
|
|
|
g_mem.default_font = rl.GetFontDefault()
|
|
ui.init(&g_mem.ui_context)
|
|
|
|
g_mem.ui_context.default_style.font = ui.Font(&g_mem.default_font)
|
|
g_mem.ui_context.default_style.font_size = 20
|
|
g_mem.ui_context.text_size = ui.rl_measure_text_2d
|
|
|
|
log.debugf("game_init")
|
|
game_hot_reloaded(g_mem)
|
|
}
|
|
|
|
@(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)
|
|
|
|
free(g_mem)
|
|
}
|
|
|
|
@(export)
|
|
game_shutdown_window :: proc() {
|
|
rl.CloseAudioDevice()
|
|
rl.CloseWindow()
|
|
|
|
deinit_physfs()
|
|
}
|
|
|
|
@(export)
|
|
game_memory :: proc() -> rawptr {
|
|
return g_mem
|
|
}
|
|
|
|
@(export)
|
|
game_memory_size :: proc() -> int {
|
|
return size_of(Game_Memory)
|
|
}
|
|
|
|
@(export)
|
|
game_hot_reloaded :: proc(mem: rawptr) {
|
|
g_mem = (^Game_Memory)(mem)
|
|
|
|
name.setup_global_container(&g_mem.name_container)
|
|
render.init(&g_mem.assetman)
|
|
ui.rl_init()
|
|
|
|
g_mem.runtime_world.orbit_camera.distance = 6
|
|
log.debugf("hot reloaded")
|
|
}
|
|
|
|
@(export)
|
|
game_force_reload :: proc() -> bool {
|
|
return rl.IsKeyPressed(.F5)
|
|
}
|
|
|
|
@(export)
|
|
game_force_restart :: proc() -> bool {
|
|
return rl.IsKeyPressed(.F6)
|
|
}
|