Refactor rewind to include the whole world, not just physics sceen
This commit is contained in:
parent
a4ed430efe
commit
df0fe56368
@ -36,7 +36,7 @@ esac
|
||||
|
||||
# Build the game.
|
||||
echo "Building game$DLL_EXT"
|
||||
odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug
|
||||
odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed
|
||||
|
||||
# Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written.
|
||||
mv game_tmp$DLL_EXT game$DLL_EXT
|
||||
|
@ -28,9 +28,9 @@ destroy_spanpool :: proc(s: ^$T/Span_Pool($E)) {
|
||||
delete(s.free_spans)
|
||||
}
|
||||
|
||||
resolve_slice :: proc(s: ^$T/Span_Pool($E), handle: Handle) -> []E {
|
||||
assert(int(handle.first + handle.len) <= len(s.elems))
|
||||
assert(s.generations[handle.first] == handle.gen)
|
||||
resolve_slice :: proc(s: ^$T/Span_Pool($E), handle: Handle, loc := #caller_location) -> []E {
|
||||
assert(int(handle.first + handle.len) <= len(s.elems), "invalid spanpool handle", loc)
|
||||
assert(s.generations[handle.first] == handle.gen, "invalid spanpool handle", loc)
|
||||
|
||||
return s.elems[handle.first:handle.first + handle.len]
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ update_free_look_camera :: proc(es: ^Editor_State) {
|
||||
(input.x * right + input.y * forward) * get_runtime_world().camera_speed
|
||||
}
|
||||
|
||||
update_editor :: proc(es: ^Editor_State) {
|
||||
update_editor :: proc(es: ^Editor_State, dt: f32) {
|
||||
update_world(&es.world, dt, false)
|
||||
update_free_look_camera(es)
|
||||
|
||||
switch es.track_edit_state {
|
||||
@ -150,4 +151,3 @@ update_editor :: proc(es: ^Editor_State) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
92
game/encoding/lbp/lbp.odin
Normal file
92
game/encoding/lbp/lbp.odin
Normal file
@ -0,0 +1,92 @@
|
||||
package lbp
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
|
||||
Error :: union #shared_nil {
|
||||
io.Error,
|
||||
mem.Allocator_Error,
|
||||
}
|
||||
|
||||
Coder :: struct {
|
||||
stream: io.Stream,
|
||||
version: i32,
|
||||
allocator: mem.Allocator,
|
||||
write: bool,
|
||||
}
|
||||
|
||||
init_read :: proc(reader: io.Reader, allocator: mem.Allocator) -> (coder: Coder, err: Error) {
|
||||
_, ok := io.to_reader(reader)
|
||||
assert(ok, "init_read expected a reader")
|
||||
|
||||
coder = Coder {
|
||||
stream = reader,
|
||||
allocator = allocator,
|
||||
write = false,
|
||||
}
|
||||
err = serialize_number(&coder, &coder.version)
|
||||
|
||||
return coder, err
|
||||
}
|
||||
|
||||
init_write :: proc(writer: io.Writer, version: i32) -> (coder: Coder, err: Error) {
|
||||
_, ok := io.to_writer(writer)
|
||||
assert(ok, "init_write expected a writer")
|
||||
|
||||
coder = Coder {
|
||||
stream = writer,
|
||||
allocator = mem.panic_allocator(),
|
||||
write = true,
|
||||
version = version,
|
||||
}
|
||||
return coder, nil
|
||||
}
|
||||
|
||||
serialize_bytes :: proc(e: ^Coder, val: []byte) -> io.Error {
|
||||
if e.write {
|
||||
_, err := io.write(e.stream, val)
|
||||
return err
|
||||
} else {
|
||||
_, err := io.read(e.stream, val)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
serialize_number :: proc(
|
||||
c: ^Coder,
|
||||
val: ^$T,
|
||||
) -> Error where intrinsics.type_is_integer(T) ||
|
||||
intrinsics.type_is_float(T) {
|
||||
num_bytes := size_of(val)
|
||||
|
||||
return serialize_bytes(c, mem.byte_slice(rawptr(val), size_of(T)))
|
||||
}
|
||||
|
||||
serialize_vector :: proc(
|
||||
c: ^Coder,
|
||||
val: $T/^[$C]$V,
|
||||
) -> Error where intrinsics.type_is_integer(V) ||
|
||||
intrinsics.type_is_float(T) {
|
||||
|
||||
for i in 0 ..< len(val) {
|
||||
serialize_number(c, &val[i])
|
||||
}
|
||||
}
|
||||
|
||||
serialize_string :: proc(c: ^Coder, val: ^string) -> Error {
|
||||
if c.write {
|
||||
bytes := transmute([]byte)(val^)
|
||||
length := i32(len(bytes))
|
||||
serialize_number(c, &length) or_return
|
||||
serialize_bytes(c, bytes) or_return
|
||||
} else {
|
||||
length: i32
|
||||
serialize_number(c, &length) or_return
|
||||
bytes := make([]byte, length, c.allocator) or_return
|
||||
serialize_bytes(c, bytes) or_return
|
||||
val^ = string(bytes)
|
||||
}
|
||||
return nil
|
||||
}
|
56
game/encoding/toml/toml.odin
Normal file
56
game/encoding/toml/toml.odin
Normal file
@ -0,0 +1,56 @@
|
||||
package toml
|
||||
|
||||
import "core:text/scanner"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
Tokenizer :: struct {
|
||||
scanner: scanner.Scanner,
|
||||
}
|
||||
|
||||
EOF :: scanner.EOF
|
||||
Ident :: scanner.Ident
|
||||
Int :: scanner.Int
|
||||
Float :: scanner.Float
|
||||
String :: scanner.String
|
||||
Comment :: scanner.Comment
|
||||
New_Line :: -9
|
||||
|
||||
tokenizer_init :: proc(tokenizer: ^Tokenizer, src: string) {
|
||||
tokenizer^ = {}
|
||||
scanner.init(&tokenizer.scanner, src)
|
||||
tokenizer.scanner.flags = {.Scan_Ints, .Scan_Floats, .Scan_Strings, .Scan_Idents}
|
||||
tokenizer.scanner.whitespace = {' ', '\t'}
|
||||
}
|
||||
|
||||
is_ident :: #force_inline proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 'a' ..= 'z', 'A' ..= 'Z', '0' ..= '9', '-', '_':
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
next_token :: proc(tokenizer: ^Tokenizer) -> rune {
|
||||
ch := scanner.scan(&tokenizer.scanner)
|
||||
tok := ch
|
||||
|
||||
switch ch {
|
||||
case EOF, Int, Float, String:
|
||||
case '\r':
|
||||
if scanner.peek(&tokenizer.scanner) != '\n' {
|
||||
scanner.error(&tokenizer.scanner, "invalid line ending, expected \n or \r\n")
|
||||
} else {
|
||||
scanner.next(&tokenizer.scanner)
|
||||
}
|
||||
tok = New_Line
|
||||
case '\n':
|
||||
tok = New_Line
|
||||
case:
|
||||
if is_ident(tok) {
|
||||
ch = scanner.next(&tokenizer.scanner)
|
||||
}
|
||||
}
|
||||
|
||||
return tok
|
||||
}
|
431
game/game.odin
431
game/game.odin
@ -21,10 +21,8 @@ import "core:log"
|
||||
import "core:math"
|
||||
import "core:math/linalg"
|
||||
import "core:slice"
|
||||
import "game:halfedge"
|
||||
import "game:physics"
|
||||
import "game:physics/bvh"
|
||||
import "game:physics/collision"
|
||||
import "game:render"
|
||||
import rl "libs:raylib"
|
||||
import "libs:raylib/rlgl"
|
||||
@ -37,24 +35,48 @@ 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,
|
||||
}
|
||||
destroy_world :: proc(world: ^World) {
|
||||
delete(world.track.points)
|
||||
physics.destroy_physics_scene(&world.physics_scene)
|
||||
}
|
||||
|
||||
|
||||
Runtime_World :: struct {
|
||||
world: World,
|
||||
pause: bool,
|
||||
solver_state: physics.Solver_State,
|
||||
car_com: rl.Vector3,
|
||||
car_handle: physics.Body_Handle,
|
||||
engine_handle: physics.Engine_Handle,
|
||||
debug_state: Debug_Draw_State,
|
||||
}
|
||||
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.destroy_physics_scene(&world.physics_scene)
|
||||
}
|
||||
|
||||
Runtime_World :: struct {
|
||||
world_snapshots: []World,
|
||||
current_world_index: int,
|
||||
camera_yaw_pitch: rl.Vector2,
|
||||
camera_speed: f32,
|
||||
camera: rl.Camera3D,
|
||||
@ -62,13 +84,31 @@ Runtime_World :: struct {
|
||||
camera_mode: Camera_Mode,
|
||||
dt: f32,
|
||||
rewind_simulation: bool,
|
||||
step_simulation: bool,
|
||||
commit_simulation: bool,
|
||||
single_step_simulation: bool,
|
||||
}
|
||||
|
||||
destroy_runtime_world :: proc(runtime_world: ^Runtime_World) {
|
||||
destroy_world(&runtime_world.world)
|
||||
physics.destroy_solver_state(&runtime_world.solver_state)
|
||||
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)
|
||||
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 {
|
||||
@ -78,7 +118,7 @@ Car :: struct {
|
||||
SOLVER_CONFIG :: physics.Solver_Config {
|
||||
timestep = 1.0 / 60,
|
||||
gravity = rl.Vector3{0, -9.8, 0},
|
||||
substreps_minus_one = 4 - 1,
|
||||
substreps_minus_one = 8 - 1,
|
||||
}
|
||||
|
||||
Game_Memory :: struct {
|
||||
@ -91,6 +131,7 @@ Game_Memory :: struct {
|
||||
preview_bvh: int,
|
||||
preview_node: int,
|
||||
physics_pause: bool,
|
||||
mouse_captured: bool,
|
||||
free_cam: bool,
|
||||
}
|
||||
|
||||
@ -133,7 +174,9 @@ get_runtime_world :: proc() -> ^Runtime_World {
|
||||
}
|
||||
|
||||
get_world :: proc() -> ^World {
|
||||
return g_mem.editor ? &g_mem.es.world : &g_mem.runtime_world.world
|
||||
return(
|
||||
g_mem.editor ? &g_mem.es.world : &g_mem.runtime_world.world_snapshots[g_mem.runtime_world.current_world_index] \
|
||||
)
|
||||
}
|
||||
|
||||
get_editor_state :: proc() -> ^Editor_State {
|
||||
@ -178,7 +221,7 @@ game_camera_3d :: proc() -> rl.Camera3D {
|
||||
}
|
||||
|
||||
ui_camera :: proc() -> rl.Camera2D {
|
||||
return {zoom = f32(rl.GetScreenHeight()) / PIXEL_WINDOW_HEIGHT}
|
||||
return {zoom = 1}
|
||||
}
|
||||
|
||||
select_track_point :: proc(index: int) {
|
||||
@ -243,17 +286,14 @@ get_movement_axes :: proc(
|
||||
return out_axes[0:0], out_colors[0:0]
|
||||
}
|
||||
|
||||
update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
world := &runtime_world.world
|
||||
|
||||
if !runtime_world.pause {
|
||||
update_world :: proc(world: ^World, dt: f32, single_step_physics: bool) {
|
||||
if !world.pause {
|
||||
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
|
||||
car_bounds := rl.GetModelBoundingBox(car_model)
|
||||
runtime_world.car_com = (car_bounds.min + car_bounds.max) / 2
|
||||
world.car_com = (car_bounds.min + car_bounds.max) / 2
|
||||
|
||||
physics.immediate_body(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("floor", "fnv32a"),
|
||||
physics.Body_Config {
|
||||
initial_pos = {0, -0.5, 0},
|
||||
@ -264,7 +304,6 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
|
||||
physics.immediate_body(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("ramp", "fnv32a"),
|
||||
physics.Body_Config {
|
||||
initial_pos = {0, 0, 0},
|
||||
@ -275,9 +314,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
|
||||
car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj")
|
||||
|
||||
runtime_world.car_handle = physics.immediate_body(
|
||||
world.car_handle = physics.immediate_body(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("car", "fnv32a"),
|
||||
physics.Body_Config {
|
||||
initial_pos = {0, 4, -10},
|
||||
@ -297,15 +335,14 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
},
|
||||
)
|
||||
|
||||
if false {
|
||||
if true {
|
||||
for x in 0 ..< 10 {
|
||||
for y in 0 ..< 10 {
|
||||
physics.immediate_body(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
hash.fnv32a(slice.to_bytes([]int{(x | y << 8)})),
|
||||
physics.Body_Config {
|
||||
initial_pos = {0, 0.5 + f32(y) * 1.1, f32(x) * 3 + 10},
|
||||
initial_pos = {5, 0.5 + f32(y) * 1.1, f32(x) * 3 + 10},
|
||||
initial_rot = linalg.QUATERNIONF32_IDENTITY,
|
||||
shape = physics.Shape_Box{size = 1},
|
||||
mass = 10,
|
||||
@ -357,7 +394,6 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
|
||||
wheel_fl := physics.immediate_suspension_constraint(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("FL", "fnv32a"),
|
||||
{
|
||||
rel_pos = {-wheel_extent_x_front, wheel_y, wheel_front_z},
|
||||
@ -366,7 +402,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
rest = rest,
|
||||
natural_frequency = natural_frequency,
|
||||
damping = damping,
|
||||
body = runtime_world.car_handle,
|
||||
body = world.car_handle,
|
||||
mass = wheel_mass,
|
||||
pacejka_lat = pacejka_lat,
|
||||
pacejka_long = pacejka_long,
|
||||
@ -374,7 +410,6 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
)
|
||||
wheel_fr := physics.immediate_suspension_constraint(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("FR", "fnv32a"),
|
||||
{
|
||||
rel_pos = {wheel_extent_x_front, wheel_y, wheel_front_z},
|
||||
@ -383,7 +418,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
rest = rest,
|
||||
natural_frequency = natural_frequency,
|
||||
damping = damping,
|
||||
body = runtime_world.car_handle,
|
||||
body = world.car_handle,
|
||||
mass = wheel_mass,
|
||||
pacejka_lat = pacejka_lat,
|
||||
pacejka_long = pacejka_long,
|
||||
@ -391,7 +426,6 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
)
|
||||
wheel_rl := physics.immediate_suspension_constraint(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("RL", "fnv32a"),
|
||||
{
|
||||
rel_pos = {-wheel_extent_x_back, wheel_y, wheel_back_z},
|
||||
@ -400,7 +434,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
rest = rest,
|
||||
natural_frequency = natural_frequency,
|
||||
damping = damping,
|
||||
body = runtime_world.car_handle,
|
||||
body = world.car_handle,
|
||||
mass = wheel_mass,
|
||||
pacejka_lat = pacejka_lat,
|
||||
pacejka_long = pacejka_long,
|
||||
@ -408,7 +442,6 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
)
|
||||
wheel_rr := physics.immediate_suspension_constraint(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("RR", "fnv32a"),
|
||||
{
|
||||
rel_pos = {wheel_extent_x_back, wheel_y, wheel_back_z},
|
||||
@ -417,16 +450,15 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
rest = rest,
|
||||
natural_frequency = natural_frequency,
|
||||
damping = damping,
|
||||
body = runtime_world.car_handle,
|
||||
body = world.car_handle,
|
||||
mass = wheel_mass,
|
||||
pacejka_lat = pacejka_lat,
|
||||
pacejka_long = pacejka_long,
|
||||
},
|
||||
)
|
||||
|
||||
runtime_world.engine_handle = physics.immediate_engine(
|
||||
world.engine_handle = physics.immediate_engine(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
#hash("engine", "fnv32a"),
|
||||
physics.Engine_Config {
|
||||
rpm_torque_curve = assets.get_curve_2d(
|
||||
@ -435,14 +467,14 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
),
|
||||
lowest_rpm = 1200,
|
||||
rev_limit_rpm = 7800,
|
||||
rev_limit_interval = 0.025,
|
||||
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,
|
||||
diff_type = .Open,
|
||||
final_drive_ratio = 4.1,
|
||||
},
|
||||
},
|
||||
@ -480,7 +512,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE
|
||||
}
|
||||
|
||||
engine := physics.get_engine(sim_state, runtime_world.engine_handle)
|
||||
engine := physics.get_engine(sim_state, world.engine_handle)
|
||||
engine.throttle = gas_input
|
||||
|
||||
if rl.IsKeyPressed(.LEFT_SHIFT) || rl.IsGamepadButtonPressed(0, .RIGHT_FACE_DOWN) {
|
||||
@ -490,8 +522,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
engine.gear -= 1
|
||||
}
|
||||
|
||||
car_body := physics.get_body(sim_state, runtime_world.car_handle)
|
||||
turn_vel_correction := clamp(30.0 / linalg.length(car_body.v), 0, 1)
|
||||
car_body := physics.get_body(sim_state, world.car_handle)
|
||||
turn_vel_correction := clamp(10.0 / linalg.length(car_body.v), 0, 1)
|
||||
|
||||
turn_input := rl.GetGamepadAxisMovement(0, .LEFT_X)
|
||||
if abs(turn_input) < GAMEPAD_DEADZONE {
|
||||
@ -510,11 +542,37 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
||||
wheel.turn_angle = TURN_ANGLE * turn_vel_correction * turn_input
|
||||
}
|
||||
|
||||
physics.simulate(
|
||||
&world.physics_scene,
|
||||
SOLVER_CONFIG,
|
||||
dt,
|
||||
commit = true,
|
||||
step_mode = single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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.step_simulation = !g_mem.physics_pause || should_single_step
|
||||
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)
|
||||
update_world(next_world, dt, should_single_step)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,8 +585,9 @@ Orbit_Camera :: struct {
|
||||
GAMEPAD_DEADZONE :: f32(0.07)
|
||||
|
||||
orbit_camera_update :: proc(camera: ^Orbit_Camera) {
|
||||
world := runtime_world_current_world(get_runtime_world())
|
||||
camera.target =
|
||||
physics.get_body(physics.get_sim_state(&get_runtime_world().world.physics_scene), get_runtime_world().car_handle).x
|
||||
physics.get_body(physics.get_sim_state(&world.physics_scene), world.car_handle).x
|
||||
|
||||
gamepad_delta := rl.Vector2 {
|
||||
rl.GetGamepadAxisMovement(0, .RIGHT_X),
|
||||
@ -541,7 +600,17 @@ orbit_camera_update :: proc(camera: ^Orbit_Camera) {
|
||||
gamepad_delta.y = 0
|
||||
}
|
||||
|
||||
mouse_delta := rl.GetMouseDelta()
|
||||
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.01
|
||||
GAMEPAD_SENSE :: 1
|
||||
@ -557,8 +626,6 @@ orbit_camera_update :: proc(camera: ^Orbit_Camera) {
|
||||
delta = gamepad_delta
|
||||
}
|
||||
|
||||
rl.HideCursor()
|
||||
|
||||
camera.yaw += delta.x * final_sense
|
||||
camera.pitch += delta.y * final_sense
|
||||
camera.pitch = math.clamp(camera.pitch, -math.PI / 2.0 + 0.0001, math.PI / 2.0 - 0.0001)
|
||||
@ -588,6 +655,8 @@ orbit_camera_to_rl :: proc(camera: Orbit_Camera) -> rl.Camera3D {
|
||||
update :: proc() {
|
||||
tracy.Zone()
|
||||
|
||||
ui.rl_update_inputs(&g_mem.ui_context)
|
||||
|
||||
ui.begin(&g_mem.ui_context)
|
||||
|
||||
if rl.IsKeyPressed(.TAB) {
|
||||
@ -646,8 +715,11 @@ update :: proc() {
|
||||
}
|
||||
|
||||
if g_mem.editor {
|
||||
update_editor(get_editor_state())
|
||||
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 {
|
||||
update_free_look_camera(get_editor_state())
|
||||
} else {
|
||||
@ -657,8 +729,8 @@ update :: proc() {
|
||||
get_runtime_world().camera = orbit_camera_to_rl(get_runtime_world().orbit_camera)
|
||||
case .Hood:
|
||||
car := physics.get_body(
|
||||
physics.get_sim_state(&get_runtime_world().world.physics_scene),
|
||||
get_runtime_world().car_handle,
|
||||
physics.get_sim_state(&world.physics_scene),
|
||||
world.car_handle,
|
||||
)
|
||||
|
||||
cam: rl.Camera3D
|
||||
@ -673,8 +745,6 @@ update :: proc() {
|
||||
get_runtime_world().camera = cam
|
||||
}
|
||||
}
|
||||
|
||||
update_runtime_world(get_runtime_world(), dt)
|
||||
}
|
||||
}
|
||||
|
||||
@ -706,99 +776,72 @@ catmull_rom :: proc(a, b, c, d: rl.Vector3, t: f32) -> rl.Vector3 {
|
||||
return a * t3 + b * t2 + c * t + d
|
||||
}
|
||||
|
||||
draw :: proc() {
|
||||
draw_world :: proc(world: ^World) {
|
||||
tracy.Zone()
|
||||
|
||||
rl.BeginDrawing()
|
||||
defer rl.EndDrawing()
|
||||
rl.ClearBackground(rl.GRAY)
|
||||
render.clear_stencil()
|
||||
|
||||
runtime_world := get_runtime_world()
|
||||
world := get_world()
|
||||
|
||||
dt := runtime_world.dt
|
||||
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),
|
||||
// )
|
||||
if world.debug_state.draw_physics_scene {
|
||||
physics.draw_debug_scene(&world.physics_scene)
|
||||
}
|
||||
|
||||
sim_state := physics.get_sim_state(&world.physics_scene)
|
||||
|
||||
car_body := physics.get_body(sim_state, runtime_world.car_handle)
|
||||
car_body := physics.get_body(sim_state, world.car_handle)
|
||||
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
|
||||
_ = car_model
|
||||
|
||||
mesh_col: bvh.Collision
|
||||
hit_mesh_idx := -1
|
||||
|
||||
rl_ray := rl.GetScreenToWorldRay(rl.GetMousePosition(), camera)
|
||||
ray := bvh.Ray {
|
||||
origin = rl_ray.position,
|
||||
dir = rl_ray.direction,
|
||||
}
|
||||
_ = ray
|
||||
engine := physics.get_engine(sim_state, world.engine_handle)
|
||||
|
||||
{
|
||||
rl.BeginMode3D(camera)
|
||||
defer rl.EndMode3D()
|
||||
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
|
||||
|
||||
// rl.DrawGrid(100, 1)
|
||||
ui.push_font_size_style(ui_ctx, 20)
|
||||
defer ui.pop_style(ui_ctx)
|
||||
|
||||
physics.draw_debug_scene(&world.physics_scene)
|
||||
physics.draw_debug_ui(&g_mem.ui_context, &world.physics_scene, SOLVER_CONFIG)
|
||||
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)
|
||||
|
||||
box1_mat := linalg.Matrix4f32(1)
|
||||
box1_mat = linalg.matrix4_rotate(45 * math.RAD_PER_DEG, rl.Vector3{0, 1, 0}) * box1_mat
|
||||
box2_mat := linalg.Matrix4f32(1)
|
||||
box2_mat = linalg.matrix4_translate(rl.Vector3{0.0, 0.2, 0}) * box2_mat
|
||||
box2_mat = linalg.matrix4_rotate(45 * math.RAD_PER_DEG, rl.Vector3{0, 0, 1}) * box2_mat
|
||||
// box2_mat = linalg.matrix4_rotate(f32(rl.GetTime()), rl.Vector3{0, -1, 0}) * box2_mat
|
||||
box2_mat = linalg.matrix4_translate(rl.Vector3{0.0, 0, 0}) * box2_mat
|
||||
box2_mat = linalg.matrix4_rotate(f32(rl.GetTime()) * 0.1, rl.Vector3{0, 1, 0}) * box2_mat
|
||||
|
||||
box1, box2 := collision.Box {
|
||||
pos = 0,
|
||||
rad = 0.5,
|
||||
}, collision.Box {
|
||||
pos = 0,
|
||||
rad = 0.5,
|
||||
if .ACTIVE in ui.header(ui_ctx, "General", {.EXPANDED}) {
|
||||
ui.checkbox(
|
||||
ui_ctx,
|
||||
"Show Physics Scene",
|
||||
&world.debug_state.draw_physics_scene,
|
||||
)
|
||||
}
|
||||
|
||||
box1_convex := collision.box_to_convex(box1, context.temp_allocator)
|
||||
box2_convex := collision.box_to_convex(box2, context.temp_allocator)
|
||||
if .ACTIVE in ui.header(ui_ctx, "Car", {.EXPANDED}) &&
|
||||
car_body.alive &&
|
||||
engine.alive {
|
||||
ui_keyval :: proc(ctx: ^ui.Context, key: string, val: any) {
|
||||
ui.layout_row(ctx, {100, -1}, 0)
|
||||
ui.label(ctx, key)
|
||||
ui.label(ctx, fmt.tprintf("%v", val))
|
||||
}
|
||||
|
||||
halfedge.transform_mesh(&box1_convex, box1_mat)
|
||||
halfedge.transform_mesh(&box2_convex, box2_mat)
|
||||
gear_ratios := physics.get_gear_ratios(sim_state, engine.gear_ratios)
|
||||
ui.layout_row(ui_ctx, {100, -1}, 0)
|
||||
ui.layout_row(ui_ctx, {100, -1}, 0)
|
||||
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 !g_mem.editor {
|
||||
car_matrix := rl.QuaternionToMatrix(car_body.q)
|
||||
car_matrix =
|
||||
(auto_cast linalg.matrix4_translate_f32(physics.body_get_shape_pos(car_body))) *
|
||||
car_matrix
|
||||
|
||||
if !runtime_world.pause {
|
||||
if runtime_world.rewind_simulation {
|
||||
world.physics_scene.simulation_state_index = physics.get_prev_sim_state_index(
|
||||
&world.physics_scene,
|
||||
)
|
||||
} else {
|
||||
physics.simulate(
|
||||
&world.physics_scene,
|
||||
&runtime_world.solver_state,
|
||||
SOLVER_CONFIG,
|
||||
dt,
|
||||
commit = runtime_world.step_simulation,
|
||||
step_mode = g_mem.physics_pause ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time,
|
||||
)
|
||||
}
|
||||
}
|
||||
(auto_cast linalg.matrix4_translate_f32(physics.body_get_shape_pos(car_body))) * car_matrix
|
||||
|
||||
basic_shader := assets.get_shader(
|
||||
&g_mem.assetman,
|
||||
@ -809,18 +852,8 @@ draw :: proc() {
|
||||
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[.LightDir], &light_dir, .VEC3)
|
||||
rl.SetShaderValue(basic_shader.shader, basic_shader.locations[.Ambient], &ambient, .VEC3)
|
||||
rl.SetShaderValue(
|
||||
basic_shader.shader,
|
||||
basic_shader.locations[.LightColor],
|
||||
@ -837,6 +870,33 @@ draw :: proc() {
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
@ -875,36 +935,6 @@ draw :: proc() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if mesh_col.hit {
|
||||
// mesh := car_model.meshes[hit_mesh_idx]
|
||||
// vertices := (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount]
|
||||
// indices := mesh.indices[:mesh.triangleCount * 3]
|
||||
// car_halfedge := halfedge.mesh_from_vertex_index_list(vertices, indices, 3, context.temp_allocator)
|
||||
//
|
||||
// face_idx := halfedge.Face_Index(mesh_col.prim)
|
||||
// face := car_halfedge.faces[face_idx]
|
||||
// first_edge_idx := face.edge
|
||||
//
|
||||
// first := true
|
||||
// cur_edge_idx := first_edge_idx
|
||||
// for first || cur_edge_idx != first_edge_idx {
|
||||
// first = false
|
||||
// edge := car_halfedge.edges[cur_edge_idx]
|
||||
// cur_edge_idx = edge.next
|
||||
//
|
||||
// if edge.twin >= 0 {
|
||||
// twin_edge := car_halfedge.edges[edge.twin]
|
||||
// face := twin_edge.face
|
||||
//
|
||||
// i1, i2, i3 := indices[face * 3 + 0], indices[face * 3 + 1], indices[face * 3 + 2]
|
||||
// v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3]
|
||||
//
|
||||
// rl.DrawTriangle3D(v1, v2, v3, rl.RED)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@ -920,29 +950,6 @@ draw :: proc() {
|
||||
if g_mem.editor {
|
||||
rl.DrawText("Editor", 5, 5, 8, rl.ORANGE)
|
||||
|
||||
|
||||
rl.DrawText(
|
||||
fmt.ctprintf(
|
||||
"mesh: %v, aabb tests: %v, tri tests: %v",
|
||||
hit_mesh_idx,
|
||||
mesh_col.aabb_tests,
|
||||
mesh_col.triangle_tests,
|
||||
),
|
||||
5,
|
||||
32,
|
||||
8,
|
||||
rl.ORANGE,
|
||||
)
|
||||
|
||||
rl.DrawText(
|
||||
fmt.ctprintf("bvh: %v, node: %v", g_mem.preview_bvh, g_mem.preview_node),
|
||||
5,
|
||||
48,
|
||||
8,
|
||||
rl.ORANGE,
|
||||
)
|
||||
|
||||
|
||||
switch g_mem.es.track_edit_state {
|
||||
case .Select:
|
||||
case .Move:
|
||||
@ -954,26 +961,6 @@ draw :: proc() {
|
||||
rl.ORANGE,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
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(
|
||||
fmt.ctprintf(
|
||||
"p: %v\nv: %v\ngear: %v\nratio: %v\nrpm: %v\nclutch: %v\nspeed: %v km/h",
|
||||
car.x,
|
||||
car.v,
|
||||
engine.gear,
|
||||
physics.lookup_gear_ratio(gear_ratios, engine.gear),
|
||||
physics.angular_velocity_to_rpm(engine.w),
|
||||
engine.clutch,
|
||||
linalg.length(car.v) * 3.6,
|
||||
),
|
||||
5,
|
||||
32,
|
||||
8,
|
||||
rl.ORANGE,
|
||||
)
|
||||
}
|
||||
|
||||
ui.rl_draw(&g_mem.ui_context)
|
||||
@ -1117,7 +1104,7 @@ game_init_window :: proc(args: []string) {
|
||||
init_physfs(args)
|
||||
|
||||
rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT})
|
||||
rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!")
|
||||
rl.InitWindow(1280, 720, "Gutter Runner")
|
||||
rl.SetExitKey(.KEY_NULL)
|
||||
rl.SetWindowPosition(200, 200)
|
||||
rl.SetTargetFPS(120)
|
||||
@ -1132,12 +1119,13 @@ game_init :: proc() {
|
||||
init_physifs_raylib_callbacks()
|
||||
assets.assetman_init(&g_mem.assetman)
|
||||
|
||||
physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100)
|
||||
runtime_world_init(&g_mem.runtime_world, 100)
|
||||
|
||||
g_mem.default_font = rl.GetFontDefault()
|
||||
ui.init(&g_mem.ui_context)
|
||||
|
||||
g_mem.ui_context.style.font = ui.Font(&g_mem.default_font)
|
||||
g_mem.ui_context.default_style.font = ui.Font(&g_mem.default_font)
|
||||
g_mem.ui_context.default_style.font_size = 32
|
||||
g_mem.ui_context.text_width = ui.rl_measure_text_width
|
||||
g_mem.ui_context.text_height = ui.rl_measure_text_height
|
||||
|
||||
@ -1149,7 +1137,7 @@ game_shutdown :: proc() {
|
||||
assets.shutdown(&g_mem.assetman)
|
||||
destroy_world(&g_mem.es.world)
|
||||
delete(g_mem.es.point_selection)
|
||||
destroy_runtime_world(&g_mem.runtime_world)
|
||||
runtime_world_destroy(&g_mem.runtime_world)
|
||||
|
||||
free(g_mem)
|
||||
}
|
||||
@ -1176,6 +1164,7 @@ game_hot_reloaded :: proc(mem: rawptr) {
|
||||
g_mem = (^Game_Memory)(mem)
|
||||
|
||||
render.init(&g_mem.assetman)
|
||||
ui.rl_init()
|
||||
|
||||
g_mem.runtime_world.orbit_camera.distance = 4
|
||||
}
|
||||
|
@ -85,8 +85,8 @@ convex_vs_convex_sat :: proc(a, b: Convex) -> (manifold: Contact_Manifold, colli
|
||||
return
|
||||
}
|
||||
|
||||
is_face_a_contact := face_query_a.separation > edge_separation
|
||||
is_face_b_contact := face_query_b.separation > edge_separation
|
||||
is_face_a_contact := (face_query_a.separation + 0.2) > edge_separation
|
||||
is_face_b_contact := (face_query_b.separation + 0.2) > edge_separation
|
||||
|
||||
if is_face_a_contact || is_face_b_contact {
|
||||
manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b)
|
||||
|
@ -152,9 +152,9 @@ draw_debug_ui :: proc(ctx: ^ui.Context, scene: ^Scene, config: Solver_Config) {
|
||||
|
||||
sim_state := get_sim_state(scene)
|
||||
|
||||
active_wheels := []int{}
|
||||
active_wheels := []int{0, 1}
|
||||
|
||||
w, h: i32 = 200, 200
|
||||
w, h: i32 = 500, 500
|
||||
|
||||
window_x: i32 = 0
|
||||
|
||||
@ -168,7 +168,7 @@ draw_debug_ui :: proc(ctx: ^ui.Context, scene: ^Scene, config: Solver_Config) {
|
||||
ctx,
|
||||
fmt.tprintf("Wheel %v", i),
|
||||
ui.Rect{x = window_x, y = 0, w = w, h = h},
|
||||
ui.Options{.AUTO_SIZE},
|
||||
ui.Options{},
|
||||
) {
|
||||
NUM_SAMPLES :: 100
|
||||
|
||||
@ -176,7 +176,7 @@ draw_debug_ui :: proc(ctx: ^ui.Context, scene: ^Scene, config: Solver_Config) {
|
||||
inv_dt := 1.0 / dt
|
||||
|
||||
{
|
||||
// ui.layout_row(ctx, {0}, 200)
|
||||
ui.layout_row(ctx, {-1}, 300)
|
||||
{
|
||||
ui.begin_line(ctx, ui.Color{255, 0, 0, 255})
|
||||
defer ui.end_line(ctx)
|
||||
@ -228,8 +228,7 @@ draw_debug_ui :: proc(ctx: ^ui.Context, scene: ^Scene, config: Solver_Config) {
|
||||
}
|
||||
|
||||
{
|
||||
// ui.layout_row(ctx, {0}, 200)
|
||||
|
||||
ui.layout_row(ctx, {-1}, 300)
|
||||
ui.begin_line(ctx, ui.Color{0, 255, 0, 255})
|
||||
defer ui.end_line(ctx)
|
||||
|
||||
|
@ -10,14 +10,8 @@ Body_Config_Inertia_Mode :: enum {
|
||||
Explicit,
|
||||
}
|
||||
|
||||
immediate_body :: proc(
|
||||
scene: ^Scene,
|
||||
state: ^Solver_State,
|
||||
id: u32,
|
||||
config: Body_Config,
|
||||
) -> (
|
||||
handle: Body_Handle,
|
||||
) {
|
||||
immediate_body :: proc(scene: ^Scene, id: u32, config: Body_Config) -> (handle: Body_Handle) {
|
||||
state := &scene.solver_state
|
||||
sim_state := get_sim_state(scene)
|
||||
if id in state.immedate_bodies {
|
||||
body := &state.immedate_bodies[id]
|
||||
@ -41,12 +35,12 @@ immediate_body :: proc(
|
||||
|
||||
immediate_suspension_constraint :: proc(
|
||||
scene: ^Scene,
|
||||
state: ^Solver_State,
|
||||
id: u32,
|
||||
config: Suspension_Constraint_Config,
|
||||
) -> (
|
||||
handle: Suspension_Constraint_Handle,
|
||||
) {
|
||||
state := &scene.solver_state
|
||||
if id in state.immediate_suspension_constraints {
|
||||
constraint := &state.immediate_suspension_constraints[id]
|
||||
if constraint.last_ref != state.simulation_frame {
|
||||
@ -73,12 +67,12 @@ immediate_suspension_constraint :: proc(
|
||||
|
||||
immediate_engine :: proc(
|
||||
scene: ^Scene,
|
||||
state: ^Solver_State,
|
||||
id: u32,
|
||||
config: Engine_Config,
|
||||
) -> (
|
||||
handle: Engine_Handle,
|
||||
) {
|
||||
state := &scene.solver_state
|
||||
sim_state := get_sim_state(scene)
|
||||
if id in state.immediate_engines {
|
||||
engine := &state.immediate_engines[id]
|
||||
@ -100,15 +94,16 @@ immediate_engine :: proc(
|
||||
return
|
||||
}
|
||||
|
||||
prune_immediate :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||
prune_immediate :: proc(scene: ^Scene) {
|
||||
tracy.Zone()
|
||||
prune_immediate_bodies(scene, state)
|
||||
prune_immediate_suspension_constraints(scene, state)
|
||||
prune_immediate_engines(scene, state)
|
||||
prune_immediate_bodies(scene)
|
||||
prune_immediate_suspension_constraints(scene)
|
||||
prune_immediate_engines(scene)
|
||||
}
|
||||
|
||||
// TODO: Generic version
|
||||
prune_immediate_bodies :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||
prune_immediate_bodies :: proc(scene: ^Scene) {
|
||||
state := &scene.solver_state
|
||||
if int(state.num_referenced_bodies) == len(state.immedate_bodies) {
|
||||
return
|
||||
}
|
||||
@ -135,7 +130,8 @@ prune_immediate_bodies :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||
}
|
||||
}
|
||||
|
||||
prune_immediate_suspension_constraints :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||
prune_immediate_suspension_constraints :: proc(scene: ^Scene) {
|
||||
state := &scene.solver_state
|
||||
if int(state.num_referenced_suspension_constraints) ==
|
||||
len(state.immediate_suspension_constraints) {
|
||||
return
|
||||
@ -165,7 +161,8 @@ prune_immediate_suspension_constraints :: proc(scene: ^Scene, state: ^Solver_Sta
|
||||
}
|
||||
}
|
||||
|
||||
prune_immediate_engines :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||
prune_immediate_engines :: proc(scene: ^Scene) {
|
||||
state := &scene.solver_state
|
||||
if int(state.num_referenced_engines) == len(state.immediate_engines) {
|
||||
return
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package physics
|
||||
import "collision"
|
||||
import lg "core:math/linalg"
|
||||
import "game:container/spanpool"
|
||||
import "libs:tracy"
|
||||
|
||||
MAX_CONTACTS :: 1024 * 16
|
||||
|
||||
@ -67,14 +68,52 @@ Sim_State :: struct {
|
||||
}
|
||||
|
||||
Scene :: struct {
|
||||
simulation_states: []Sim_State,
|
||||
// Speculative prediction state to find collisions
|
||||
scratch_sim_state: Sim_State,
|
||||
simulation_states: [2]Sim_State,
|
||||
simulation_state_index: i32,
|
||||
solver_state: Solver_State,
|
||||
}
|
||||
|
||||
init_physics_scene :: proc(scene: ^Scene, max_history := int(2)) {
|
||||
scene.simulation_states = make([]Sim_State, max(max_history, 2))
|
||||
// Copy current state to next
|
||||
copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
|
||||
tracy.Zone()
|
||||
convex_container_reconcile(&src.convex_container)
|
||||
|
||||
dst.num_bodies = src.num_bodies
|
||||
dst.first_free_body_plus_one = src.first_free_body_plus_one
|
||||
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
|
||||
dst.first_free_engine_plus_one = src.first_free_engine_plus_one
|
||||
|
||||
resize(&dst.bodies, len(src.bodies))
|
||||
resize(&dst.suspension_constraints, len(src.suspension_constraints))
|
||||
resize(&dst.engines, len(src.engines))
|
||||
|
||||
dst.bodies_slice = dst.bodies[:]
|
||||
dst.suspension_constraints_slice = dst.suspension_constraints[:]
|
||||
|
||||
for i in 0 ..< len(dst.bodies) {
|
||||
dst.bodies[i] = src.bodies[i]
|
||||
}
|
||||
for i in 0 ..< len(dst.suspension_constraints) {
|
||||
dst.suspension_constraints[i] = src.suspension_constraints[i]
|
||||
}
|
||||
copy(dst.engines[:], src.engines[:])
|
||||
|
||||
contact_container_copy(&dst.contact_container, src.contact_container)
|
||||
convex_container_copy(&dst.convex_container, src.convex_container)
|
||||
spanpool.copy(&dst.rpm_torque_curves_pool, src.rpm_torque_curves_pool)
|
||||
spanpool.copy(&dst.gear_ratios_pool, src.gear_ratios_pool)
|
||||
}
|
||||
|
||||
copy_physics_scene :: proc(dst, src: ^Scene) {
|
||||
tracy.Zone()
|
||||
|
||||
dst.simulation_state_index = src.simulation_state_index
|
||||
src_sim_state := get_sim_state(src)
|
||||
dst_sim_state := get_sim_state(dst)
|
||||
|
||||
copy_sim_state(dst_sim_state, src_sim_state)
|
||||
|
||||
copy_solver_state(&dst.solver_state, &src.solver_state)
|
||||
}
|
||||
|
||||
Body :: struct {
|
||||
@ -494,8 +533,12 @@ remove_gear_ratios :: proc(sim_state: ^Sim_State, handle: Gear_Ratios_Handle) {
|
||||
spanpool.free(&sim_state.gear_ratios_pool, spanpool.Handle(handle))
|
||||
}
|
||||
|
||||
get_gear_ratios :: proc(sim_state: ^Sim_State, handle: Gear_Ratios_Handle) -> []f32 {
|
||||
return spanpool.resolve_slice(&sim_state.gear_ratios_pool, spanpool.Handle(handle))
|
||||
get_gear_ratios :: proc(
|
||||
sim_state: ^Sim_State,
|
||||
handle: Gear_Ratios_Handle,
|
||||
loc := #caller_location,
|
||||
) -> []f32 {
|
||||
return spanpool.resolve_slice(&sim_state.gear_ratios_pool, spanpool.Handle(handle), loc)
|
||||
}
|
||||
|
||||
update_engine_from_config :: proc(
|
||||
@ -678,6 +721,5 @@ destroy_physics_scene :: proc(scene: ^Scene) {
|
||||
for &sim_state in scene.simulation_states {
|
||||
destry_sim_state(&sim_state)
|
||||
}
|
||||
destry_sim_state(&scene.scratch_sim_state)
|
||||
delete(scene.simulation_states)
|
||||
destroy_solver_state(&scene.solver_state)
|
||||
}
|
||||
|
@ -10,10 +10,8 @@ import "core:math"
|
||||
import lg "core:math/linalg"
|
||||
import "core:math/rand"
|
||||
import "core:slice"
|
||||
import "game:container/spanpool"
|
||||
import "game:debug"
|
||||
import he "game:halfedge"
|
||||
import rl "libs:raylib"
|
||||
import "libs:tracy"
|
||||
|
||||
_ :: log
|
||||
@ -45,6 +43,28 @@ Solver_State :: struct {
|
||||
immediate_engines: map[u32]Immedate_State(Engine_Handle),
|
||||
}
|
||||
|
||||
copy_map :: proc(dst, src: $T/^map[$K]$V) {
|
||||
clear(dst)
|
||||
reserve_map(dst, len(src))
|
||||
|
||||
for k, v in src {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
copy_solver_state :: proc(dst, src: ^Solver_State) {
|
||||
dst.accumulated_time = src.accumulated_time
|
||||
dst.simulation_frame = src.simulation_frame
|
||||
|
||||
dst.num_referenced_bodies = src.num_referenced_bodies
|
||||
dst.num_referenced_suspension_constraints = src.num_referenced_suspension_constraints
|
||||
dst.num_referenced_engines = src.num_referenced_engines
|
||||
|
||||
copy_map(&dst.immedate_bodies, &src.immedate_bodies)
|
||||
copy_map(&dst.immediate_suspension_constraints, &src.immediate_suspension_constraints)
|
||||
copy_map(&dst.immediate_engines, &src.immediate_engines)
|
||||
}
|
||||
|
||||
destroy_solver_state :: proc(state: ^Solver_State) {
|
||||
delete(state.immedate_bodies)
|
||||
delete(state.immediate_suspension_constraints)
|
||||
@ -59,38 +79,6 @@ Immedate_State :: struct($T: typeid) {
|
||||
|
||||
MAX_STEPS :: 10
|
||||
|
||||
// TODO: move into scene.odin
|
||||
// Copy current state to next
|
||||
sim_state_copy :: proc(dst: ^Sim_State, src: ^Sim_State) {
|
||||
tracy.Zone()
|
||||
convex_container_reconcile(&src.convex_container)
|
||||
|
||||
dst.num_bodies = src.num_bodies
|
||||
dst.first_free_body_plus_one = src.first_free_body_plus_one
|
||||
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
|
||||
dst.first_free_engine_plus_one = src.first_free_engine_plus_one
|
||||
|
||||
resize(&dst.bodies, len(src.bodies))
|
||||
resize(&dst.suspension_constraints, len(src.suspension_constraints))
|
||||
resize(&dst.engines, len(src.engines))
|
||||
|
||||
dst.bodies_slice = dst.bodies[:]
|
||||
dst.suspension_constraints_slice = dst.suspension_constraints[:]
|
||||
|
||||
for i in 0 ..< len(dst.bodies) {
|
||||
dst.bodies[i] = src.bodies[i]
|
||||
}
|
||||
for i in 0 ..< len(dst.suspension_constraints) {
|
||||
dst.suspension_constraints[i] = src.suspension_constraints[i]
|
||||
}
|
||||
copy(dst.engines[:], src.engines[:])
|
||||
|
||||
contact_container_copy(&dst.contact_container, src.contact_container)
|
||||
convex_container_copy(&dst.convex_container, src.convex_container)
|
||||
spanpool.copy(&dst.rpm_torque_curves_pool, src.rpm_torque_curves_pool)
|
||||
spanpool.copy(&dst.gear_ratios_pool, src.gear_ratios_pool)
|
||||
}
|
||||
|
||||
Step_Mode :: enum {
|
||||
Accumulated_Time,
|
||||
Single,
|
||||
@ -203,7 +191,7 @@ find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) {
|
||||
|
||||
pair := make_contact_pair(i32(body_idx), i32(other_body_idx))
|
||||
if body_idx != other_body_idx &&
|
||||
(true || bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) &&
|
||||
(bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) &&
|
||||
!(pair in sim_state.contact_container.lookup) {
|
||||
|
||||
new_contact_idx := len(sim_state.contact_container.contacts)
|
||||
@ -226,7 +214,6 @@ find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) {
|
||||
// Outer simulation loop for fixed timestepping
|
||||
simulate :: proc(
|
||||
scene: ^Scene,
|
||||
state: ^Solver_State,
|
||||
config: Solver_Config,
|
||||
dt: f32,
|
||||
commit := true, // commit = false is a special mode for debugging physics stepping to allow rerunning the same step each frame
|
||||
@ -235,9 +222,11 @@ simulate :: proc(
|
||||
tracy.Zone()
|
||||
assert(config.timestep > 0)
|
||||
|
||||
prune_immediate(scene, state)
|
||||
state := &scene.solver_state
|
||||
|
||||
sim_state_copy(get_next_sim_state(scene), get_sim_state(scene))
|
||||
prune_immediate(scene)
|
||||
|
||||
copy_sim_state(get_next_sim_state(scene), get_sim_state(scene))
|
||||
|
||||
sim_state := get_next_sim_state(scene)
|
||||
|
||||
@ -346,11 +335,6 @@ update_contacts :: proc(sim_state: ^Sim_State) {
|
||||
body_get_convex_shape_world(sim_state, body),
|
||||
body_get_convex_shape_world(sim_state, body2)
|
||||
|
||||
if contact_idx == 2 {
|
||||
he.debug_draw_mesh_wires(m1, rl.RED)
|
||||
he.debug_draw_mesh_wires(m2, rl.BLUE)
|
||||
}
|
||||
|
||||
// Raw manifold has contact points in world space
|
||||
raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2)
|
||||
|
||||
@ -450,7 +434,7 @@ pgs_solve_contacts :: proc(
|
||||
body_local_to_world(body2, manifold.points_b[point_idx])
|
||||
|
||||
p_diff_normal := lg.dot(p2 - p1, manifold.normal)
|
||||
separation := p_diff_normal
|
||||
separation := p_diff_normal + 0.01
|
||||
|
||||
w1 := get_body_inverse_mass(body1, manifold.normal, p1)
|
||||
w2 := get_body_inverse_mass(body2, manifold.normal, p2)
|
||||
@ -536,10 +520,10 @@ pgs_solve_contacts :: proc(
|
||||
applied_impulse_vec :=
|
||||
applied_impulse.x * manifold.tangent + applied_impulse.y * manifold.bitangent
|
||||
|
||||
rl.DrawSphereWires(p1, 0.05, 8, 8, rl.RED)
|
||||
rl.DrawLine3D(p1, p1 + v1, rl.RED)
|
||||
rl.DrawSphereWires(p2, 0.05, 8, 8, rl.BLUE)
|
||||
rl.DrawLine3D(p2, p2 + v2, rl.BLUE)
|
||||
// rl.DrawSphereWires(p1, 0.05, 8, 8, rl.RED)
|
||||
// rl.DrawLine3D(p1, p1 + v1, rl.RED)
|
||||
// rl.DrawSphereWires(p2, 0.05, 8, 8, rl.BLUE)
|
||||
// rl.DrawLine3D(p2, p2 + v2, rl.BLUE)
|
||||
|
||||
apply_velocity_correction(body1, -applied_impulse_vec, p1)
|
||||
apply_velocity_correction(body2, applied_impulse_vec, p2)
|
||||
@ -849,7 +833,7 @@ pgs_solve_suspension :: proc(
|
||||
v.slip_ratio = slip_ratio
|
||||
v.slip_angle = slip_angle
|
||||
|
||||
MAX_SLIP_LEN :: f32(1.5)
|
||||
MAX_SLIP_LEN :: f32(1)
|
||||
|
||||
slip_vec := Vec2 {
|
||||
slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN,
|
||||
@ -1121,10 +1105,19 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
|
||||
for i < len(sim_state.contact_container.contacts) {
|
||||
contact := sim_state.contact_container.contacts[i]
|
||||
|
||||
should_remove := false
|
||||
|
||||
should_remove |= !get_body(sim_state, contact.a).alive
|
||||
should_remove |= !get_body(sim_state, contact.b).alive
|
||||
|
||||
if !should_remove {
|
||||
aabb_a := tlas.body_aabbs[int(contact.a) - 1]
|
||||
aabb_b := tlas.body_aabbs[int(contact.b) - 1]
|
||||
|
||||
if false && !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) {
|
||||
should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b)
|
||||
}
|
||||
|
||||
if should_remove {
|
||||
removed_pair := make_contact_pair(i32(contact.a) - 1, i32(contact.b) - 1)
|
||||
delete_key(&sim_state.contact_container.lookup, removed_pair)
|
||||
|
||||
|
@ -40,6 +40,7 @@ CLIP_STACK_SIZE :: #config(MICROUI_CLIP_STACK_SIZE, 32)
|
||||
ID_STACK_SIZE :: #config(MICROUI_ID_STACK_SIZE, 32)
|
||||
LAYOUT_STACK_SIZE :: #config(MICROUI_LAYOUT_STACK_SIZE, 16)
|
||||
LINE_STACK_SIZE :: #config(MICROUI_LINE_STACK_SIZE, 16)
|
||||
STYLE_STACK_SIZE :: #config(MICROUI_STYLE_STACK_SIZE, 16)
|
||||
CONTAINER_POOL_SIZE :: #config(MICROUI_CONTAINER_POOL_SIZE, 48)
|
||||
TREENODE_POOL_SIZE :: #config(MICROUI_TREENODE_POOL_SIZE, 48)
|
||||
MAX_WIDTHS :: #config(MICROUI_MAX_WIDTHS, 16)
|
||||
@ -178,6 +179,7 @@ Command_Text :: struct {
|
||||
pos: Vec2,
|
||||
color: Color,
|
||||
str: string, /* + string data (VLA) */
|
||||
font_size: i32,
|
||||
}
|
||||
Command_Icon :: struct {
|
||||
using command: Command,
|
||||
@ -225,6 +227,7 @@ Container :: struct {
|
||||
|
||||
Style :: struct {
|
||||
font: Font,
|
||||
font_size: i32,
|
||||
size: Vec2,
|
||||
padding: i32,
|
||||
spacing: i32,
|
||||
@ -238,12 +241,11 @@ Style :: struct {
|
||||
|
||||
Context :: struct {
|
||||
/* callbacks */
|
||||
text_width: proc(font: Font, str: string) -> i32,
|
||||
text_height: proc(font: Font) -> i32,
|
||||
text_width: proc(font: Font, font_size: i32, str: string) -> i32,
|
||||
text_height: proc(font: Font, font_size: i32) -> i32,
|
||||
draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type),
|
||||
/* core state */
|
||||
_style: Style,
|
||||
style: ^Style,
|
||||
default_style: Style,
|
||||
hover_id, focus_id, last_id: Id,
|
||||
last_rect: Rect,
|
||||
last_zindex: i32,
|
||||
@ -262,6 +264,7 @@ Context :: struct {
|
||||
id_stack: Stack(Id, ID_STACK_SIZE),
|
||||
layout_stack: Stack(Layout, LAYOUT_STACK_SIZE),
|
||||
lines_stack: Stack(Line, LINE_STACK_SIZE),
|
||||
style_stack: Stack(Style, STYLE_STACK_SIZE),
|
||||
/* retained state pools */
|
||||
container_pool: [CONTAINER_POOL_SIZE]Pool_Item,
|
||||
containers: [CONTAINER_POOL_SIZE]Container,
|
||||
@ -349,14 +352,36 @@ rect_from_point_extent :: proc(p, e: Vec2) -> Rect {
|
||||
return Rect{x = p.x - e.x, y = p.y - e.y, w = e.x * 2, h = e.y * 2}
|
||||
}
|
||||
|
||||
get_style :: proc(ctx: ^Context) -> ^Style {
|
||||
if ctx.style_stack.idx > 0 {
|
||||
return &ctx.style_stack.items[ctx.style_stack.idx - 1]
|
||||
}
|
||||
|
||||
return &ctx.default_style
|
||||
}
|
||||
|
||||
push_style :: proc(ctx: ^Context, style: Style) {
|
||||
push(&ctx.style_stack, style)
|
||||
}
|
||||
|
||||
pop_style :: proc(ctx: ^Context) {
|
||||
pop(&ctx.style_stack)
|
||||
}
|
||||
|
||||
push_font_size_style :: proc(ctx: ^Context, font_size: i32) {
|
||||
style := get_style(ctx)^
|
||||
style.font_size = font_size
|
||||
push_style(ctx, style)
|
||||
}
|
||||
|
||||
@(private)
|
||||
default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) {
|
||||
draw_rect(ctx, rect, ctx.style.colors[colorid])
|
||||
draw_rect(ctx, rect, get_style(ctx).colors[colorid])
|
||||
if colorid == .SCROLL_BASE || colorid == .SCROLL_THUMB || colorid == .TITLE_BG {
|
||||
return
|
||||
}
|
||||
if ctx.style.colors[.BORDER].a != 0 { /* draw border */
|
||||
draw_box(ctx, expand_rect(rect, 1), ctx.style.colors[.BORDER])
|
||||
if get_style(ctx).colors[.BORDER].a != 0 { /* draw border */
|
||||
draw_box(ctx, expand_rect(rect, 1), get_style(ctx).colors[.BORDER])
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,8 +393,7 @@ init :: proc(
|
||||
) {
|
||||
ctx^ = {} // zero memory
|
||||
ctx.draw_frame = default_draw_frame
|
||||
ctx._style = default_style
|
||||
ctx.style = &ctx._style
|
||||
ctx.default_style = default_style
|
||||
ctx.text_input = strings.builder_from_bytes(ctx._text_store[:])
|
||||
|
||||
ctx.textbox_state.set_clipboard = set_clipboard
|
||||
@ -398,6 +422,7 @@ end :: proc(ctx: ^Context) {
|
||||
assert(ctx.id_stack.idx == 0)
|
||||
assert(ctx.layout_stack.idx == 0)
|
||||
assert(ctx.lines_stack.idx == 0)
|
||||
assert(ctx.style_stack.idx == 0)
|
||||
|
||||
/* handle scroll input */
|
||||
if ctx.scroll_target != nil {
|
||||
@ -759,8 +784,20 @@ draw_box :: proc(ctx: ^Context, rect: Rect, color: Color) {
|
||||
draw_rect(ctx, Rect{rect.x + rect.w - 1, rect.y, 1, rect.h}, color)
|
||||
}
|
||||
|
||||
draw_text :: proc(ctx: ^Context, font: Font, str: string, pos: Vec2, color: Color) {
|
||||
rect := Rect{pos.x, pos.y, ctx.text_width(font, str), ctx.text_height(font)}
|
||||
draw_text :: proc(
|
||||
ctx: ^Context,
|
||||
font: Font,
|
||||
font_size: i32,
|
||||
str: string,
|
||||
pos: Vec2,
|
||||
color: Color,
|
||||
) {
|
||||
rect := Rect {
|
||||
pos.x,
|
||||
pos.y,
|
||||
ctx.text_width(font, font_size, str),
|
||||
ctx.text_height(font, font_size),
|
||||
}
|
||||
clipped := check_clip(ctx, rect)
|
||||
switch clipped {
|
||||
case .NONE: // okay
|
||||
@ -774,6 +811,7 @@ draw_text :: proc(ctx: ^Context, font: Font, str: string, pos: Vec2, color: Colo
|
||||
text_cmd.pos = pos
|
||||
text_cmd.color = color
|
||||
text_cmd.font = font
|
||||
text_cmd.font_size = font_size
|
||||
/* copy string */
|
||||
dst_str := ([^]byte)(text_cmd)[size_of(Command_Text):][:len(str)]
|
||||
copy(dst_str, str)
|
||||
@ -825,10 +863,23 @@ end_line :: proc(ctx: ^Context) {
|
||||
first_segment := line.first_segment
|
||||
num_segments := ctx.line_segments_num - first_segment
|
||||
|
||||
clipped := check_clip(ctx, line.rect)
|
||||
switch clipped {
|
||||
case .NONE:
|
||||
case .ALL:
|
||||
return
|
||||
case .PART:
|
||||
set_clip(ctx, get_clip_rect(ctx))
|
||||
}
|
||||
|
||||
cmd := push_command(ctx, Command_Line)
|
||||
cmd.first_segment = first_segment
|
||||
cmd.num_segments = num_segments
|
||||
cmd.color = line.color
|
||||
|
||||
if clipped != .NONE {
|
||||
set_clip(ctx, unclipped_rect)
|
||||
}
|
||||
}
|
||||
|
||||
push_line_point :: proc(ctx: ^Context, p: Vec2f) {
|
||||
@ -905,7 +956,7 @@ layout_set_next :: proc(ctx: ^Context, r: Rect, relative: bool) {
|
||||
|
||||
layout_next :: proc(ctx: ^Context) -> (res: Rect) {
|
||||
layout := get_layout(ctx)
|
||||
style := ctx.style
|
||||
style := get_style(ctx)
|
||||
defer ctx.last_rect = res
|
||||
|
||||
if layout.next_type != .NONE {
|
||||
@ -996,18 +1047,19 @@ draw_control_text :: proc(
|
||||
opt := Options{},
|
||||
) {
|
||||
pos: Vec2
|
||||
font := ctx.style.font
|
||||
tw := ctx.text_width(font, str)
|
||||
font := get_style(ctx).font
|
||||
font_size := get_style(ctx).font_size
|
||||
tw := ctx.text_width(font, font_size, str)
|
||||
push_clip_rect(ctx, rect)
|
||||
pos.y = rect.y + (rect.h - ctx.text_height(font)) / 2
|
||||
pos.y = rect.y + (rect.h - ctx.text_height(font, font_size)) / 2
|
||||
if .ALIGN_CENTER in opt {
|
||||
pos.x = rect.x + (rect.w - tw) / 2
|
||||
} else if .ALIGN_RIGHT in opt {
|
||||
pos.x = rect.x + rect.w - tw - ctx.style.padding
|
||||
pos.x = rect.x + rect.w - tw - get_style(ctx).padding
|
||||
} else {
|
||||
pos.x = rect.x + ctx.style.padding
|
||||
pos.x = rect.x + get_style(ctx).padding
|
||||
}
|
||||
draw_text(ctx, font, str, pos, ctx.style.colors[colorid])
|
||||
draw_text(ctx, font, font_size, str, pos, get_style(ctx).colors[colorid])
|
||||
pop_clip_rect(ctx)
|
||||
}
|
||||
|
||||
@ -1052,10 +1104,12 @@ update_control :: proc(ctx: ^Context, id: Id, rect: Rect, opt := Options{}) {
|
||||
|
||||
text :: proc(ctx: ^Context, text: string) {
|
||||
text := text
|
||||
font := ctx.style.font
|
||||
color := ctx.style.colors[.TEXT]
|
||||
style := get_style(ctx)
|
||||
font := style.font
|
||||
font_size := style.font_size
|
||||
color := style.colors[.TEXT]
|
||||
layout_begin_column(ctx)
|
||||
layout_row(ctx, {-1}, ctx.text_height(font))
|
||||
layout_row(ctx, {-1}, ctx.text_height(font, font_size))
|
||||
for len(text) > 0 {
|
||||
w: i32
|
||||
start: int
|
||||
@ -1064,12 +1118,12 @@ text :: proc(ctx: ^Context, text: string) {
|
||||
for ch, i in text {
|
||||
if ch == ' ' || ch == '\n' {
|
||||
word := text[start:i]
|
||||
w += ctx.text_width(font, word)
|
||||
w += ctx.text_width(font, font_size, word)
|
||||
if w > r.w && start != 0 {
|
||||
end = start
|
||||
break
|
||||
}
|
||||
w += ctx.text_width(font, text[i:i + 1])
|
||||
w += ctx.text_width(font, font_size, text[i:i + 1])
|
||||
if ch == '\n' {
|
||||
end = i + 1
|
||||
break
|
||||
@ -1077,14 +1131,14 @@ text :: proc(ctx: ^Context, text: string) {
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
draw_text(ctx, font, text[:end], Vec2{r.x, r.y}, color)
|
||||
draw_text(ctx, font, font_size, text[:end], Vec2{r.x, r.y}, color)
|
||||
text = text[end:]
|
||||
}
|
||||
layout_end_column(ctx)
|
||||
}
|
||||
|
||||
label :: proc(ctx: ^Context, text: string) {
|
||||
draw_control_text(ctx, text, layout_next(ctx), .TEXT)
|
||||
label :: proc(ctx: ^Context, text: string, opt := Options{}) {
|
||||
draw_control_text(ctx, text, layout_next(ctx), .TEXT, opt)
|
||||
}
|
||||
|
||||
button :: proc(
|
||||
@ -1108,7 +1162,7 @@ button :: proc(
|
||||
draw_control_text(ctx, label, r, .TEXT, opt)
|
||||
}
|
||||
if icon != .NONE {
|
||||
draw_icon(ctx, icon, r, ctx.style.colors[.TEXT])
|
||||
draw_icon(ctx, icon, r, get_style(ctx).colors[.TEXT])
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1126,7 +1180,7 @@ checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set
|
||||
/* draw */
|
||||
draw_control_frame(ctx, id, box, .BASE, {})
|
||||
if state^ {
|
||||
draw_icon(ctx, .CHECK, box, ctx.style.colors[.TEXT])
|
||||
draw_icon(ctx, .CHECK, box, get_style(ctx).colors[.TEXT])
|
||||
}
|
||||
r = Rect{r.x + box.w, r.y, r.w - box.w, r.h}
|
||||
draw_control_text(ctx, label, r, .TEXT)
|
||||
@ -1145,7 +1199,9 @@ textbox_raw :: proc(
|
||||
) {
|
||||
update_control(ctx, id, r, opt | {.HOLD_FOCUS})
|
||||
|
||||
font := ctx.style.font
|
||||
style := get_style(ctx)
|
||||
font := style.font
|
||||
font_size := style.font_size
|
||||
|
||||
if ctx.focus_id == id {
|
||||
/* create a builder backed by the user's buffer */
|
||||
@ -1259,7 +1315,9 @@ textbox_raw :: proc(
|
||||
continue
|
||||
}
|
||||
if ctx.mouse_pos.x <
|
||||
r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) {
|
||||
r.x +
|
||||
ctx.textbox_offset +
|
||||
ctx.text_width(font, font_size, string(textbuf[:i])) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
@ -1276,14 +1334,14 @@ textbox_raw :: proc(
|
||||
/* draw */
|
||||
draw_control_frame(ctx, id, r, .BASE, opt)
|
||||
if ctx.focus_id == id {
|
||||
text_color := ctx.style.colors[.TEXT]
|
||||
sel_color := ctx.style.colors[.SELECTION_BG]
|
||||
textw := ctx.text_width(font, textstr)
|
||||
texth := ctx.text_height(font)
|
||||
headx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]])
|
||||
tailx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]])
|
||||
ofmin := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding)
|
||||
ofmax := min(r.w - headx - ctx.style.padding, ctx.style.padding)
|
||||
text_color := style.colors[.TEXT]
|
||||
sel_color := style.colors[.SELECTION_BG]
|
||||
textw := ctx.text_width(font, font_size, textstr)
|
||||
texth := ctx.text_height(font, font_size)
|
||||
headx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[0]])
|
||||
tailx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[1]])
|
||||
ofmin := max(get_style(ctx).padding - headx, r.w - textw - get_style(ctx).padding)
|
||||
ofmax := min(r.w - headx - get_style(ctx).padding, get_style(ctx).padding)
|
||||
ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)
|
||||
textx := r.x + ctx.textbox_offset
|
||||
texty := r.y + (r.h - texth) / 2
|
||||
@ -1293,7 +1351,7 @@ textbox_raw :: proc(
|
||||
Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth},
|
||||
sel_color,
|
||||
)
|
||||
draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color)
|
||||
draw_text(ctx, font, font_size, textstr, Vec2{textx, texty}, text_color)
|
||||
draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color)
|
||||
pop_clip_rect(ctx)
|
||||
} else {
|
||||
@ -1372,7 +1430,7 @@ slider :: proc(
|
||||
/* draw base */
|
||||
draw_control_frame(ctx, id, base, .BASE, opt)
|
||||
/* draw thumb */
|
||||
w := ctx.style.thumb_size
|
||||
w := get_style(ctx).thumb_size
|
||||
x := i32((v - low) * Real(base.w - w) / (high - low))
|
||||
thumb := Rect{base.x + x, base.y, w, base.h}
|
||||
draw_control_frame(ctx, id, thumb, .BUTTON, opt)
|
||||
@ -1452,14 +1510,15 @@ _header :: proc(ctx: ^Context, label: string, is_treenode: bool, opt := Options{
|
||||
} else {
|
||||
draw_control_frame(ctx, id, r, .BUTTON)
|
||||
}
|
||||
style := get_style(ctx)
|
||||
draw_icon(
|
||||
ctx,
|
||||
expanded ? .EXPANDED : .COLLAPSED,
|
||||
Rect{r.x, r.y, r.h, r.h},
|
||||
ctx.style.colors[.TEXT],
|
||||
style.colors[.TEXT],
|
||||
)
|
||||
r.x += r.h - ctx.style.padding
|
||||
r.w -= r.h - ctx.style.padding
|
||||
r.x += r.h - style.padding
|
||||
r.w -= r.h - style.padding
|
||||
draw_control_text(ctx, label, r, .TEXT)
|
||||
return expanded ? {.ACTIVE} : {}
|
||||
}
|
||||
@ -1471,14 +1530,14 @@ header :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
|
||||
begin_treenode :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
|
||||
res := _header(ctx, label, true, opt)
|
||||
if .ACTIVE in res {
|
||||
get_layout(ctx).indent += ctx.style.indent
|
||||
get_layout(ctx).indent += get_style(ctx).indent
|
||||
push(&ctx.id_stack, ctx.last_id)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
end_treenode :: proc(ctx: ^Context) {
|
||||
get_layout(ctx).indent -= ctx.style.indent
|
||||
get_layout(ctx).indent -= get_style(ctx).indent
|
||||
pop_id(ctx)
|
||||
}
|
||||
|
||||
@ -1512,7 +1571,7 @@ scrollbar :: proc(ctx: ^Context, cnt: ^Container, _b: ^Rect, cs: Vec2, id_string
|
||||
/* get sizing / positioning */
|
||||
base := b^
|
||||
base.pos[1 - i] = b.pos[1 - i] + b.size[1 - i]
|
||||
base.size[1 - i] = ctx.style.scrollbar_size
|
||||
base.size[1 - i] = get_style(ctx).scrollbar_size
|
||||
|
||||
/* handle input */
|
||||
update_control(ctx, id, transmute(Rect)base)
|
||||
@ -1525,7 +1584,7 @@ scrollbar :: proc(ctx: ^Context, cnt: ^Container, _b: ^Rect, cs: Vec2, id_string
|
||||
/* draw base and thumb */
|
||||
ctx.draw_frame(ctx, transmute(Rect)base, .SCROLL_BASE)
|
||||
thumb := base
|
||||
thumb.size[i] = max(ctx.style.thumb_size, base.size[i] * b.size[i] / cs[i])
|
||||
thumb.size[i] = max(get_style(ctx).thumb_size, base.size[i] * b.size[i] / cs[i])
|
||||
thumb.pos[i] += cnt.scroll[i] * (base.size[i] - thumb.size[i]) / maxscroll
|
||||
ctx.draw_frame(ctx, transmute(Rect)thumb, .SCROLL_THUMB)
|
||||
|
||||
@ -1541,10 +1600,10 @@ scrollbar :: proc(ctx: ^Context, cnt: ^Container, _b: ^Rect, cs: Vec2, id_string
|
||||
|
||||
@(private)
|
||||
scrollbars :: proc(ctx: ^Context, cnt: ^Container, body: ^Rect) {
|
||||
sz := ctx.style.scrollbar_size
|
||||
sz := get_style(ctx).scrollbar_size
|
||||
cs := cnt.content_size
|
||||
cs.x += ctx.style.padding * 2
|
||||
cs.y += ctx.style.padding * 2
|
||||
cs.x += get_style(ctx).padding * 2
|
||||
cs.y += get_style(ctx).padding * 2
|
||||
push_clip_rect(ctx, body^)
|
||||
/* resize body to make room for scrollbars */
|
||||
if cs.y > cnt.body.h {body.w -= sz}
|
||||
@ -1562,7 +1621,7 @@ push_container_body :: proc(ctx: ^Context, cnt: ^Container, body: Rect, opt := O
|
||||
if .NO_SCROLL not_in opt {
|
||||
scrollbars(ctx, cnt, &body)
|
||||
}
|
||||
push_layout(ctx, expand_rect(body, -ctx.style.padding), cnt.scroll)
|
||||
push_layout(ctx, expand_rect(body, -get_style(ctx).padding), cnt.scroll)
|
||||
cnt.body = body
|
||||
}
|
||||
|
||||
@ -1621,7 +1680,7 @@ begin_window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{})
|
||||
/* do title bar */
|
||||
if .NO_TITLE not_in opt {
|
||||
tr := rect
|
||||
tr.h = ctx.style.title_height
|
||||
tr.h = get_style(ctx).title_height
|
||||
ctx.draw_frame(ctx, tr, .TITLE_BG)
|
||||
|
||||
/* do title text */
|
||||
@ -1642,7 +1701,7 @@ begin_window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{})
|
||||
cid := get_id(ctx, "!close")
|
||||
r := Rect{tr.x + tr.w - tr.h, tr.y, tr.h, tr.h}
|
||||
tr.w -= r.w
|
||||
draw_icon(ctx, .CLOSE, r, ctx.style.colors[.TITLE_TEXT])
|
||||
draw_icon(ctx, .CLOSE, r, get_style(ctx).colors[.TITLE_TEXT])
|
||||
update_control(ctx, cid, r, opt)
|
||||
if .LEFT in ctx.mouse_released_bits && cid == ctx.hover_id {
|
||||
cnt.open = false
|
||||
@ -1652,10 +1711,10 @@ begin_window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{})
|
||||
|
||||
/* do `resize` handle */
|
||||
if .NO_RESIZE not_in opt {
|
||||
sz := ctx.style.footer_height
|
||||
sz := get_style(ctx).footer_height
|
||||
rid := get_id(ctx, "!resize")
|
||||
r := Rect{rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz}
|
||||
draw_icon(ctx, .RESIZE, r, ctx.style.colors[.TEXT])
|
||||
draw_icon(ctx, .RESIZE, r, get_style(ctx).colors[.TEXT])
|
||||
update_control(ctx, rid, r, opt)
|
||||
if rid == ctx.focus_id && .LEFT in ctx.mouse_down_bits {
|
||||
cnt.rect.w = max(96, cnt.rect.w + ctx.mouse_delta.x)
|
||||
|
@ -6,51 +6,78 @@ import "core:log"
|
||||
import "core:strings"
|
||||
import rl "libs:raylib"
|
||||
import "libs:raylib/rlgl"
|
||||
import gl "vendor:OpenGL"
|
||||
|
||||
_ :: log
|
||||
|
||||
default_atlas_texture: rl.Texture2D
|
||||
|
||||
rl_init :: proc() {
|
||||
rl.UnloadTexture(default_atlas_texture)
|
||||
default_atlas_texture = {}
|
||||
|
||||
image := rl.Image{
|
||||
data = &default_atlas_alpha,
|
||||
width = DEFAULT_ATLAS_WIDTH,
|
||||
height = DEFAULT_ATLAS_HEIGHT,
|
||||
mipmaps = 1,
|
||||
format = .UNCOMPRESSED_GRAYSCALE,
|
||||
}
|
||||
|
||||
default_atlas_texture = rl.LoadTextureFromImage(image)
|
||||
rl.SetTextureFilter(default_atlas_texture, .POINT)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, default_atlas_texture.id)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_SWIZZLE_A, gl.RED)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
}
|
||||
|
||||
to_rl_color :: proc(c: Color) -> rl.Color {
|
||||
return rl.Color{c.r, c.g, c.b, c.a}
|
||||
}
|
||||
|
||||
rl_get_font_size :: proc(font: Font) -> i32 {
|
||||
font := cast(^rl.Font)font
|
||||
return font.baseSize if font != nil else 16
|
||||
to_rl_rect :: proc(r: Rect) -> rl.Rectangle {
|
||||
return rl.Rectangle{x = f32(r.x), y = f32(r.y), width = f32(r.w), height = f32(r.h)}
|
||||
}
|
||||
|
||||
rl_measure_text_2d :: #force_inline proc(font: Font, text: string) -> rl.Vector2 {
|
||||
font_size := rl_get_font_size(font)
|
||||
font := (cast(^rl.Font)font)^ if font != nil else rl.GetFontDefault()
|
||||
rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: string) -> rl.Vector2 {
|
||||
font := (cast(^rl.Font)font)
|
||||
size := rl.MeasureTextEx(
|
||||
font,
|
||||
font^ if font != nil else rl.GetFontDefault(),
|
||||
strings.clone_to_cstring(text, context.temp_allocator),
|
||||
f32(font_size),
|
||||
0,
|
||||
f32(font_size / (font.baseSize if font != nil else 10)),
|
||||
)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
rl_measure_text_width :: proc(font: Font, text: string) -> i32 {
|
||||
return i32(rl_measure_text_2d(font, text).x)
|
||||
rl_measure_text_width :: proc(font: Font, font_size: i32, text: string) -> i32 {
|
||||
return i32(rl_measure_text_2d(font, font_size, text).x)
|
||||
}
|
||||
|
||||
rl_measure_text_height :: proc(font: Font) -> i32 {
|
||||
return i32(rl_measure_text_2d(font, "A").y)
|
||||
rl_measure_text_height :: proc(font: Font, font_size: i32) -> i32 {
|
||||
return i32(rl_measure_text_2d(font, font_size, "A").y)
|
||||
}
|
||||
|
||||
rl_draw :: proc(ctx: ^Context) {
|
||||
tmp_cmd: ^Command
|
||||
for cmd in next_command_iterator(ctx, &tmp_cmd) {
|
||||
log.debugf("ui cmd: %v", cmd)
|
||||
switch c in cmd {
|
||||
case ^Command_Clip:
|
||||
rlgl.Scissor(c.rect.x, c.rect.y, c.rect.w, c.rect.h)
|
||||
if c.rect == unclipped_rect {
|
||||
rl.EndScissorMode()
|
||||
} else {
|
||||
rl.BeginScissorMode(c.rect.x, c.rect.y, c.rect.w, c.rect.h)
|
||||
}
|
||||
case ^Command_Text:
|
||||
font := cast(^rl.Font)c.font
|
||||
rl.DrawText(
|
||||
rl.DrawTextEx(
|
||||
font^ if font != nil else rl.GetFontDefault(),
|
||||
strings.clone_to_cstring(c.str, context.temp_allocator),
|
||||
c.pos.x,
|
||||
c.pos.y,
|
||||
font.baseSize if font != nil else 16,
|
||||
rl.Vector2{f32(c.pos.x), f32(c.pos.y)},
|
||||
f32(c.font_size),
|
||||
f32(c.font_size / (font.baseSize if font != nil else 10)),
|
||||
to_rl_color(c.color),
|
||||
)
|
||||
case ^Command_Rect:
|
||||
@ -58,14 +85,74 @@ rl_draw :: proc(ctx: ^Context) {
|
||||
case ^Command_Line:
|
||||
segments := get_line_segments(ctx, c.first_segment, c.num_segments)
|
||||
|
||||
for i in 1 ..< len(segments) {
|
||||
p1 := segments[i - 1]
|
||||
p2 := segments[i]
|
||||
|
||||
rl.DrawLineV(p1, p2, to_rl_color(c.color))
|
||||
}
|
||||
rl.DrawLineStrip(&segments[0], i32(len(segments)), to_rl_color(c.color))
|
||||
case ^Command_Jump:
|
||||
case ^Command_Icon:
|
||||
src_rect := default_atlas[int(c.id)]
|
||||
x := f32(c.rect.x + (c.rect.w - src_rect.w) / 2)
|
||||
y := f32(c.rect.y + (c.rect.h - src_rect.h) / 2)
|
||||
rl.DrawTextureRec(
|
||||
default_atlas_texture,
|
||||
to_rl_rect(src_rect),
|
||||
{x, y},
|
||||
to_rl_color(c.color),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
rlgl.DrawRenderBatchActive()
|
||||
rlgl.DisableScissorTest()
|
||||
}
|
||||
|
||||
RL_MOUSE_BUTTON_MAPPING :: [Mouse]rl.MouseButton {
|
||||
.LEFT = .LEFT,
|
||||
.RIGHT = .RIGHT,
|
||||
.MIDDLE = .MIDDLE,
|
||||
}
|
||||
|
||||
RL_KEY_MAPPING :: [Key]rl.KeyboardKey {
|
||||
.SHIFT = .LEFT_SHIFT,
|
||||
.CTRL = .LEFT_CONTROL,
|
||||
.ALT = .LEFT_ALT,
|
||||
.BACKSPACE = .BACKSPACE,
|
||||
.DELETE = .DELETE,
|
||||
.RETURN = .ENTER,
|
||||
.LEFT = .LEFT,
|
||||
.RIGHT = .RIGHT,
|
||||
.HOME = .HOME,
|
||||
.END = .END,
|
||||
.A = .A,
|
||||
.X = .X,
|
||||
.C = .C,
|
||||
.V = .V,
|
||||
}
|
||||
|
||||
rl_update_inputs :: proc(ctx: ^Context) {
|
||||
ctx.mouse_pos.x = rl.GetMouseX()
|
||||
ctx.mouse_pos.y = rl.GetMouseY()
|
||||
|
||||
for rl_btn, ui_btn in RL_MOUSE_BUTTON_MAPPING {
|
||||
if rl.IsMouseButtonPressed(rl_btn) {
|
||||
input_mouse_down(ctx, ctx.mouse_pos.x, ctx.mouse_pos.y, ui_btn)
|
||||
}
|
||||
if rl.IsMouseButtonReleased(rl_btn) {
|
||||
input_mouse_up(ctx, ctx.mouse_pos.x, ctx.mouse_pos.y, ui_btn)
|
||||
}
|
||||
}
|
||||
|
||||
wheel_move := rl.GetMouseWheelMoveV() * -50
|
||||
input_scroll(ctx, i32(wheel_move.x), i32(wheel_move.y))
|
||||
|
||||
for rl_key, ui_key in RL_KEY_MAPPING {
|
||||
if rl.IsKeyPressed(rl_key) {
|
||||
input_key_down(ctx, ui_key)
|
||||
}
|
||||
if rl.IsKeyReleased(rl_key) {
|
||||
input_key_up(ctx, ui_key)
|
||||
}
|
||||
}
|
||||
|
||||
for char := rl.GetCharPressed(); char != 0; char = rl.GetCharPressed() {
|
||||
strings.write_rune(&ctx.text_input, char)
|
||||
}
|
||||
}
|
||||
|
BIN
src_assets/car_convex.blend
(Stored with Git LFS)
BIN
src_assets/car_convex.blend
(Stored with Git LFS)
Binary file not shown.
BIN
src_assets/car_convex.blend1
(Stored with Git LFS)
BIN
src_assets/car_convex.blend1
(Stored with Git LFS)
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user