diff --git a/build_hot_reload.sh b/build_hot_reload.sh index a990553..1a6965e 100755 --- a/build_hot_reload.sh +++ b/build_hot_reload.sh @@ -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 diff --git a/game/container/spanpool/spanpool.odin b/game/container/spanpool/spanpool.odin index 527210f..06b59d4 100644 --- a/game/container/spanpool/spanpool.odin +++ b/game/container/spanpool/spanpool.odin @@ -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] } diff --git a/game/editor.odin b/game/editor.odin index 10a994a..98d5047 100644 --- a/game/editor.odin +++ b/game/editor.odin @@ -45,8 +45,9 @@ 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_free_look_camera(es) +update_editor :: proc(es: ^Editor_State, dt: f32) { + update_world(&es.world, dt, false) + update_free_look_camera(es) switch es.track_edit_state { case .Select: @@ -150,4 +151,3 @@ update_editor :: proc(es: ^Editor_State) { } } } - diff --git a/game/encoding/lbp/lbp.odin b/game/encoding/lbp/lbp.odin new file mode 100644 index 0000000..986ee33 --- /dev/null +++ b/game/encoding/lbp/lbp.odin @@ -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 +} diff --git a/game/encoding/toml/toml.odin b/game/encoding/toml/toml.odin new file mode 100644 index 0000000..7b663d4 --- /dev/null +++ b/game/encoding/toml/toml.odin @@ -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 +} diff --git a/game/game.odin b/game/game.odin index 761e03d..ca94976 100644 --- a/game/game.odin +++ b/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, + pause: bool, + 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) { - delete(world.track.points) + destroy_track(&world.track) 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, + 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,20 +118,21 @@ 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 { - 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, - free_cam: bool, + 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 { @@ -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 } - 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.single_step_simulation = should_single_step + 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.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,6 +776,100 @@ catmull_rom :: proc(a, b, c, d: rl.Vector3, t: f32) -> rl.Vector3 { return a * t3 + b * t2 + c * t + d } +draw_world :: proc(world: ^World) { + tracy.Zone() + + 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, world.car_handle) + car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") + 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 { + 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)) + } + + 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) + } + } + } + + } + + 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(car_model, basic_shader.shader, 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() @@ -714,10 +878,8 @@ draw :: proc() { 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 @@ -729,113 +891,11 @@ draw :: proc() { // rl.GetScreenToWorldRay(rl.GetMousePosition(), camera), // ) - sim_state := physics.get_sim_state(&world.physics_scene) - - car_body := physics.get_body(sim_state, runtime_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 - { rl.BeginMode3D(camera) defer rl.EndMode3D() - // rl.DrawGrid(100, 1) - - physics.draw_debug_scene(&world.physics_scene) - physics.draw_debug_ui(&g_mem.ui_context, &world.physics_scene, SOLVER_CONFIG) - - 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, - } - - box1_convex := collision.box_to_convex(box1, context.temp_allocator) - box2_convex := collision.box_to_convex(box2, context.temp_allocator) - - halfedge.transform_mesh(&box1_convex, box1_mat) - halfedge.transform_mesh(&box2_convex, box2_mat) - - 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, - ) - } - } - - 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(car_model, basic_shader.shader, 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_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 } diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin index bbaa22d..2e02de1 100644 --- a/game/physics/collision/convex.odin +++ b/game/physics/collision/convex.odin @@ -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) diff --git a/game/physics/debug.odin b/game/physics/debug.odin index 1e6041c..8971cf8 100644 --- a/game/physics/debug.odin +++ b/game/physics/debug.odin @@ -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) diff --git a/game/physics/immediate.odin b/game/physics/immediate.odin index ceaf4a0..b6708d2 100644 --- a/game/physics/immediate.odin +++ b/game/physics/immediate.odin @@ -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 } diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 9f9e511..984369b 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -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) } diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 0e28aa3..b5c0c0a 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -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] - aabb_a := tlas.body_aabbs[int(contact.a) - 1] - aabb_b := tlas.body_aabbs[int(contact.b) - 1] + should_remove := false - if false && !bvh.test_aabb_vs_aabb(aabb_a, aabb_b) { + 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] + + 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) diff --git a/game/ui/microui.odin b/game/ui/microui.odin index 63a975e..36b2a4c 100644 --- a/game/ui/microui.odin +++ b/game/ui/microui.odin @@ -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) diff --git a/game/ui/raylib.odin b/game/ui/raylib.odin index 0ec89b9..6dc7db9 100644 --- a/game/ui/raylib.odin +++ b/game/ui/raylib.odin @@ -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) + } } diff --git a/src_assets/car_convex.blend b/src_assets/car_convex.blend index 3188bc9..edaed36 100644 --- a/src_assets/car_convex.blend +++ b/src_assets/car_convex.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58a3c0f89125eb052fd01ab21c80cfbf60e5f2527e779ca537a5acc055070e5f +oid sha256:fc26741ecf2eafcb012d504d72ce852f6c296373a2194461124d383daa2ef508 size 476544 diff --git a/src_assets/car_convex.blend1 b/src_assets/car_convex.blend1 index 4d1c7e2..3188bc9 100644 --- a/src_assets/car_convex.blend1 +++ b/src_assets/car_convex.blend1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32e2f161dc45431c698a753bbcaee06cb95c4136d5736571527451db4c66e413 -size 478070 +oid sha256:58a3c0f89125eb052fd01ab21c80cfbf60e5f2527e779ca537a5acc055070e5f +size 476544