diff --git a/game/editor.odin b/game/editor.odin index 98d5047..c3a2765 100644 --- a/game/editor.odin +++ b/game/editor.odin @@ -3,151 +3,122 @@ package game import lg "core:math/linalg" import rl "libs:raylib" -update_free_look_camera :: proc(es: ^Editor_State) { - input: rl.Vector2 - - if rl.IsKeyDown(.UP) || rl.IsKeyDown(.W) { - input.y -= 1 - } - if rl.IsKeyDown(.DOWN) || rl.IsKeyDown(.S) { - input.y += 1 - } - if rl.IsKeyDown(.LEFT) || rl.IsKeyDown(.A) { - input.x -= 1 - } - if rl.IsKeyDown(.RIGHT) || rl.IsKeyDown(.D) { - input.x += 1 - } - - should_capture_mouse := rl.IsMouseButtonDown(.RIGHT) - if es.mouse_captured != should_capture_mouse { - if should_capture_mouse { - rl.DisableCursor() - } else { - rl.EnableCursor() - } - } - es.mouse_captured = should_capture_mouse - - if es.mouse_captured { - get_runtime_world().camera_yaw_pitch += rl.GetMouseDelta().yx * -1 * 0.001 - } - - get_runtime_world().camera_speed += rl.GetMouseWheelMove() * 0.01 - get_runtime_world().camera_speed = lg.clamp(get_runtime_world().camera_speed, 0.01, 10) - - rotation_matrix := camera_rotation_matrix() - forward := -rotation_matrix[2] - right := lg.cross(rl.Vector3{0, 1, 0}, forward) - - input = lg.normalize0(input) - get_world().player_pos += - (input.x * right + input.y * forward) * get_runtime_world().camera_speed -} - update_editor :: proc(es: ^Editor_State, dt: f32) { - update_world(&es.world, dt, false) - update_free_look_camera(es) + free_camera_update(&es.camera) - switch es.track_edit_state { - case .Select: - { - if rl.IsKeyPressed(.F) { - add_track_spline_point() - } + did_undo_redo := false - if is_point_selected() { - if rl.IsKeyPressed(.X) { + if rl.IsKeyPressed(.Z) && rl.IsKeyDown(.LEFT_CONTROL) { + world_stack_pop(&es.world_stack) + did_undo_redo = true + } - if len(es.point_selection) <= 1 { - for i in es.point_selection { - ordered_remove(&get_world().track.points, i) - } - } else { - #reverse for _, i in get_world().track.points { - if i in es.point_selection { + if !did_undo_redo { + switch es.track_edit_state { + case .Select: + { + if rl.IsKeyPressed(.F) { + world_stack_push(&es.world_stack) + add_track_spline_point() + } + + if is_point_selected() { + if rl.IsKeyPressed(.X) { + world_stack_push(&es.world_stack) + if len(es.point_selection) <= 1 { + for i in es.point_selection { ordered_remove(&get_world().track.points, i) } + } else { + #reverse for _, i in get_world().track.points { + if i in es.point_selection { + ordered_remove(&get_world().track.points, i) + } + } + } + + clear(&es.point_selection) + } + if rl.IsKeyPressed(.G) { + es.track_edit_state = .Move + es.move_axis = .None + es.total_movement_world = {} + world_stack_push(&es.world_stack) + // es.initial_point_pos = g_mem.track.points[es.selected_track_point] + } + } + } + case .Move: + { + if rl.IsKeyPressed(.ESCAPE) { + es.track_edit_state = .Select + // g_mem.track.points[es.selected_track_point] = es.initial_point_pos + break + } + + if (rl.IsMouseButtonPressed(.LEFT)) { + es.track_edit_state = .Select + break + } + + if !es.mouse_captured { + // Blender style movement + if rl.IsKeyDown(.LEFT_SHIFT) { + if rl.IsKeyPressed(.X) { + es.move_axis = .YZ + } + if rl.IsKeyPressed(.Y) { + es.move_axis = .XZ + } + if rl.IsKeyPressed(.Z) { + es.move_axis = .XY + } + } else { + if rl.IsKeyPressed(.X) { + es.move_axis = .X + } + if rl.IsKeyPressed(.Y) { + es.move_axis = .Y + } + if rl.IsKeyPressed(.Z) { + es.move_axis = .Z } } - clear(&es.point_selection) - } - if rl.IsKeyPressed(.G) { - es.track_edit_state = .Move - es.move_axis = .None - es.total_movement_world = {} - // es.initial_point_pos = g_mem.track.points[es.selected_track_point] + // log.debugf("Move axis %v", es.move_axis) + + camera := game_camera_3d() + + mouse_delta := rl.GetMouseDelta() * 0.05 + + view_rotation := lg.transpose(rl.GetCameraMatrix(camera)) + view_rotation[3].xyz = 0 + view_proj := view_rotation * rl.MatrixOrtho(-1, 1, 1, -1, -1, 1) + + axes_buf: [2]rl.Vector3 + colors_buf: [2]rl.Color + axes, _ := get_movement_axes(es.move_axis, &axes_buf, &colors_buf) + + movement_world: rl.Vector3 + for axis in axes { + axis_screen := (rl.Vector4{axis.x, axis.y, axis.z, 1} * view_proj).xy + axis_screen = lg.normalize0(axis_screen) + + movement_screen := lg.dot(axis_screen, mouse_delta) * axis_screen + movement_world += + (rl.Vector4{movement_screen.x, movement_screen.y, 0, 1} * rl.MatrixInvert(view_proj)).xyz + } + + for k in es.point_selection { + get_world().track.points[k] += movement_world + } + + es.total_movement_world += movement_world } } } - case .Move: - { - if rl.IsKeyPressed(.ESCAPE) { - es.track_edit_state = .Select - // g_mem.track.points[es.selected_track_point] = es.initial_point_pos - break - } - if (rl.IsMouseButtonPressed(.LEFT)) { - es.track_edit_state = .Select - break - } - - if !es.mouse_captured { - // Blender style movement - if rl.IsKeyDown(.LEFT_SHIFT) { - if rl.IsKeyPressed(.X) { - es.move_axis = .YZ - } - if rl.IsKeyPressed(.Y) { - es.move_axis = .XZ - } - if rl.IsKeyPressed(.Z) { - es.move_axis = .XY - } - } else { - if rl.IsKeyPressed(.X) { - es.move_axis = .X - } - if rl.IsKeyPressed(.Y) { - es.move_axis = .Y - } - if rl.IsKeyPressed(.Z) { - es.move_axis = .Z - } - } - - // log.debugf("Move axis %v", es.move_axis) - - camera := game_camera_3d() - - mouse_delta := rl.GetMouseDelta() * 0.05 - - view_rotation := lg.transpose(rl.GetCameraMatrix(camera)) - view_rotation[3].xyz = 0 - view_proj := view_rotation * rl.MatrixOrtho(-1, 1, 1, -1, -1, 1) - - axes_buf: [2]rl.Vector3 - colors_buf: [2]rl.Color - axes, _ := get_movement_axes(es.move_axis, &axes_buf, &colors_buf) - - movement_world: rl.Vector3 - for axis in axes { - axis_screen := (rl.Vector4{axis.x, axis.y, axis.z, 1} * view_proj).xy - axis_screen = lg.normalize0(axis_screen) - - movement_screen := lg.dot(axis_screen, mouse_delta) * axis_screen - movement_world += - (rl.Vector4{movement_screen.x, movement_screen.y, 0, 1} * rl.MatrixInvert(view_proj)).xyz - } - - for k in es.point_selection { - get_world().track.points[k] += movement_world - } - - es.total_movement_world += movement_world - } - } } + // world := world_stack_current(&es.world_stack) + // update_world(world, dt, false) } diff --git a/game/game.odin b/game/game.odin index ca94976..6dcd00e 100644 --- a/game/game.odin +++ b/game/game.odin @@ -157,14 +157,82 @@ Move_Axis :: enum { YZ, } +// For undo/redo +World_Stack :: struct { + worlds: []World, + head, tail: int, +} + +world_stack_init :: proc(stack: ^World_Stack, num_snapshots: int, allocator := context.allocator) { + assert(num_snapshots > 0) + stack.worlds = make([]World, num_snapshots, allocator) + world_stack_push(stack) +} + +world_stack_destroy :: proc(stack: ^World_Stack, allocator := context.allocator) { + for &world in stack.worlds { + destroy_world(&world) + } + delete(stack.worlds, allocator) +} + +world_stack_len :: proc(stack: ^World_Stack) -> int { + if stack.tail >= stack.head { + return stack.tail - stack.head + } else { + return (stack.tail + len(stack.worlds)) - stack.head + } +} + +world_stack_current :: proc(stack: ^World_Stack) -> ^World { + if world_stack_len(stack) > 0 { + return &stack.worlds[(stack.tail - 1) %% len(stack.worlds)] + } + + return nil +} + +world_stack_push :: proc(stack: ^World_Stack) { + stack_len := world_stack_len(stack) + assert(stack_len <= len(stack.worlds)) + + if stack_len == len(stack.worlds) { + stack.head = (stack.head + 1) %% len(stack.worlds) + } + assert(world_stack_len(stack) < len(stack.worlds)) + + prev_world := world_stack_current(stack) + stack.tail = (stack.tail + 1) %% len(stack.worlds) + new_world := world_stack_current(stack) + + if prev_world != nil { + copy_world(new_world, prev_world) + } +} + +world_stack_pop :: proc(stack: ^World_Stack) { + if world_stack_len(stack) > 1 { + stack.tail = (stack.tail - 1) %% len(stack.worlds) + } +} + Editor_State :: struct { - world: World, + world_stack: World_Stack, mouse_captured: bool, point_selection: map[int]bool, track_edit_state: Track_Edit_State, move_axis: Move_Axis, total_movement_world: rl.Vector3, initial_point_pos: rl.Vector3, + camera: Free_Camera, +} + +editor_state_init :: proc(es: ^Editor_State, num_snapshots: int) { + world_stack_init(&es.world_stack, num_snapshots) +} + +editor_state_destroy :: proc(es: ^Editor_State) { + world_stack_destroy(&es.world_stack) } g_mem: ^Game_Memory @@ -175,7 +243,7 @@ get_runtime_world :: proc() -> ^Runtime_World { get_world :: proc() -> ^World { return( - g_mem.editor ? &g_mem.es.world : &g_mem.runtime_world.world_snapshots[g_mem.runtime_world.current_world_index] \ + g_mem.editor ? world_stack_current(&g_mem.es.world_stack) : &g_mem.runtime_world.world_snapshots[g_mem.runtime_world.current_world_index] \ ) } @@ -208,13 +276,7 @@ camera_forward_vec :: proc() -> rl.Vector3 { game_camera_3d :: proc() -> rl.Camera3D { if g_mem.editor || g_mem.free_cam { - return { - position = get_world().player_pos, - up = {0, 1, 0}, - fovy = 60, - target = get_world().player_pos + camera_forward_vec(), - projection = .PERSPECTIVE, - } + return free_camera_to_rl(&g_mem.es.camera) } return get_runtime_world().camera @@ -234,9 +296,9 @@ is_point_selected :: proc() -> bool { } add_track_spline_point :: proc() { - forward := camera_rotation_matrix()[2] + forward := -free_camera_rotation(g_mem.es.camera)[2] - append(&get_world().track.points, get_world().player_pos + forward) + append(&get_world().track.points, g_mem.es.camera.pos + forward) select_track_point(len(&get_world().track.points) - 1) } @@ -576,6 +638,12 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { } } +Free_Camera :: struct { + pos: rl.Vector3, + yaw, pitch: f32, + speed: f32, +} + Orbit_Camera :: struct { target: rl.Vector3, yaw, pitch: f32, @@ -584,11 +652,7 @@ 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(&world.physics_scene), world.car_handle).x - +collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) { gamepad_delta := rl.Vector2 { rl.GetGamepadAxisMovement(0, .RIGHT_X), rl.GetGamepadAxisMovement(0, .RIGHT_Y), @@ -615,19 +679,26 @@ orbit_camera_update :: proc(camera: ^Orbit_Camera) { MOUSE_SENSE :: 0.01 GAMEPAD_SENSE :: 1 - final_sense: f32 - delta: rl.Vector2 - if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) { - final_sense = MOUSE_SENSE + sense = MOUSE_SENSE delta = mouse_delta } else { - final_sense = GAMEPAD_SENSE * rl.GetFrameTime() + sense = GAMEPAD_SENSE * rl.GetFrameTime() delta = gamepad_delta } - camera.yaw += delta.x * final_sense - camera.pitch += delta.y * final_sense + return +} + +orbit_camera_update :: proc(camera: ^Orbit_Camera) { + world := runtime_world_current_world(get_runtime_world()) + camera.target = + physics.get_body(physics.get_sim_state(&world.physics_scene), world.car_handle).x + + delta, sense := collect_camera_input() + + camera.yaw += delta.x * sense + camera.pitch += delta.y * sense camera.pitch = math.clamp(camera.pitch, -math.PI / 2.0 + 0.0001, math.PI / 2.0 - 0.0001) } @@ -652,6 +723,60 @@ orbit_camera_to_rl :: proc(camera: Orbit_Camera) -> rl.Camera3D { return result } +Matrix3 :: # row_major matrix[3, 3]f32 + +free_camera_rotation :: proc(camera: Free_Camera) -> linalg.Matrix3f32 { + return( + linalg.matrix3_rotate_f32(camera.yaw, {0, 1, 0}) * + linalg.matrix3_rotate_f32(camera.pitch, {1, 0, 0}) \ + ) +} + +free_camera_update :: proc(camera: ^Free_Camera) { + delta, sense := collect_camera_input() + + camera.yaw -= delta.x * sense + camera.pitch -= delta.y * sense + camera.pitch = math.clamp(camera.pitch, -math.PI / 2.0 + 0.0001, math.PI / 2.0 - 0.0001) + + input: rl.Vector2 + + if rl.IsKeyDown(.UP) || rl.IsKeyDown(.W) { + input.y += 1 + } + if rl.IsKeyDown(.DOWN) || rl.IsKeyDown(.S) { + input.y -= 1 + } + if rl.IsKeyDown(.LEFT) || rl.IsKeyDown(.A) { + input.x -= 1 + } + if rl.IsKeyDown(.RIGHT) || rl.IsKeyDown(.D) { + input.x += 1 + } + + camera.speed += rl.GetMouseWheelMove() * 0.01 + camera.speed = linalg.clamp(camera.speed, 0.01, 10) + + rotation := free_camera_rotation(camera^) + forward := -rotation[2] + right := rotation[0] + + input = linalg.normalize0(input) + camera.pos += (input.x * right + input.y * forward) * camera.speed +} + +free_camera_to_rl :: proc(camera: ^Free_Camera) -> (result: rl.Camera3D) { + rotation := free_camera_rotation(camera^) + forward := -rotation[2] + + result.position = camera.pos + result.target = camera.pos + forward + result.up = rl.Vector3{0, 1, 0} + result.fovy = 60 + + return result +} + update :: proc() { tracy.Zone() @@ -666,7 +791,7 @@ update :: proc() { if rl.IsKeyPressed(.F1) { g_mem.free_cam = !g_mem.free_cam - g_mem.es.world.player_pos = g_mem.runtime_world.camera.position + // g_mem.es.world.player_pos = g_mem.runtime_world.camera.position } if rl.IsKeyPressed(.F2) && !g_mem.free_cam { @@ -721,7 +846,7 @@ update :: proc() { world := runtime_world_current_world(get_runtime_world()) if g_mem.free_cam { - update_free_look_camera(get_editor_state()) + free_camera_update(&g_mem.es.camera) } else { switch get_runtime_world().camera_mode { case .Orbit: @@ -1119,6 +1244,7 @@ game_init :: proc() { init_physifs_raylib_callbacks() assets.assetman_init(&g_mem.assetman) + editor_state_init(&g_mem.es, 100) runtime_world_init(&g_mem.runtime_world, 100) g_mem.default_font = rl.GetFontDefault() @@ -1135,7 +1261,7 @@ game_init :: proc() { @(export) game_shutdown :: proc() { assets.shutdown(&g_mem.assetman) - destroy_world(&g_mem.es.world) + editor_state_destroy(&g_mem.es) delete(g_mem.es.point_selection) runtime_world_destroy(&g_mem.runtime_world)