package app import "core:math" import "core:time" import gpu ".." import "window:window" import platform "../platform" State :: struct { platform: platform.Platform_State, bench: Bench_State, defaults: Default_Pipelines_State, } Bench_State :: struct { max_seconds: f64, max_frames: u64, profile_path: string, start_time: time.Time, screenshot_hold_seconds: f64, screenshot_ready_path: string, } Key :: window.Key_Code Mouse_Button :: window.Mouse_Button Input_Source :: platform.Input_Source Action :: distinct u32 INVALID_ACTION :: Action(0) init_window :: proc(width, height: i32, title: cstring) -> (state: State, ok: bool) { plat, plat_ok := platform.init_platform(width, height, string(title)) if !plat_ok { return {}, false } surface := platform.surface_desc(&plat) w := u32(width) if width > 0 else gpu.DEFAULT_WINDOW_WIDTH h := u32(height) if height > 0 else gpu.DEFAULT_WINDOW_HEIGHT if !gpu.init_surface(surface, w, h, title) { platform.shutdown_platform(&plat) return {}, false } window.set_gpu_surface(plat.win, true) state.platform = plat bench_init(&state) if !default_pipelines_init(&state) { gpu.shutdown() platform.shutdown_platform(&state.platform) return {}, false } return state, true } shutdown :: proc(state: ^State) { bench_shutdown(state) default_pipelines_shutdown(state) gpu.shutdown() platform.shutdown_platform(&state.platform) } window_should_close :: proc(state: ^State) -> bool { return state == nil || state.platform.should_close || bench_should_close(state) } begin_drawing :: proc(state: ^State, clear_color: gpu.Color = gpu.BLACK) { platform.poll_events(&state.platform) platform.update_time(&state.platform) if state.platform.was_resized { size := state.platform.new_size gpu.resize_surface(size.width, size.height) state.platform.was_resized = false } gpu.begin_drawing(clear_color, platform.get_frame_time(&state.platform)) } begin_frame :: proc(state: ^State, clear_color: gpu.Color = gpu.BLACK, begin_present: bool = true) -> gpu.Frame { platform.poll_events(&state.platform) platform.update_time(&state.platform) if state.platform.was_resized { size := state.platform.new_size gpu.resize_surface(size.width, size.height) state.platform.was_resized = false } ctx := gpu.context_from_runtime() frame := gpu.begin_frame(ctx) if begin_present { target_desc := gpu.present_target(ctx) target_desc.clear_color = clear_color _ = gpu.begin_target(&frame, target_desc) _ = gpu.push_layer(&frame, {name = "default", order = .Strict}) } return frame } end_drawing :: proc(state: ^State) { _ = gpu.end_drawing() platform.wait_for_target_fps(&state.platform) free_all(context.temp_allocator) } submit_frame :: proc(state: ^State, frame: ^gpu.Frame) -> bool { ok := gpu.submit(frame) gpu.destroy_frame(frame) platform.wait_for_target_fps(&state.platform) free_all(context.temp_allocator) return ok } set_target_fps :: proc(state: ^State, fps: i32) { platform.set_target_fps(&state.platform, fps) } get_frame_time :: proc(state: ^State) -> f32 { return platform.get_frame_time(&state.platform) } get_time :: proc(state: ^State) -> f64 { return platform.get_time(&state.platform) } get_fps :: proc(state: ^State) -> i32 { return platform.get_fps(&state.platform) } is_key_pressed :: proc(state: ^State, key: Key) -> bool { return platform.is_key_pressed(&state.platform, key) } is_key_down :: proc(state: ^State, key: Key) -> bool { return platform.is_key_down(&state.platform, key) } is_key_released :: proc(state: ^State, key: Key) -> bool { return platform.is_key_released(&state.platform, key) } is_mouse_button_pressed :: proc(state: ^State, btn: Mouse_Button) -> bool { return platform.is_mouse_button_pressed(&state.platform, btn) } is_mouse_button_down :: proc(state: ^State, btn: Mouse_Button) -> bool { return platform.is_mouse_button_down(&state.platform, btn) } get_mouse_position :: proc(state: ^State) -> gpu.Vec2 { pos := platform.get_mouse_position(&state.platform) return {pos[0], pos[1]} } get_mouse_delta :: proc(state: ^State) -> gpu.Vec2 { delta := platform.get_mouse_delta(&state.platform) return {delta[0], delta[1]} } get_mouse_wheel_move :: proc(state: ^State) -> f32 { return platform.get_mouse_wheel_move(&state.platform) } register_action :: proc(state: ^State, name: string) -> Action { id := platform.register_action(&state.platform.actions, name) if id == 0 { return INVALID_ACTION } return Action(id) } bind_action_key :: proc(state: ^State, action: Action, key: Key) { platform.bind_action_key(&state.platform.actions, u32(action), key) } bind_action_mouse :: proc(state: ^State, action: Action, btn: Mouse_Button) { platform.bind_action_mouse(&state.platform.actions, u32(action), btn) } unbind_action :: proc(state: ^State, action: Action) { platform.unbind_action(&state.platform.actions, u32(action)) } is_action_pressed :: proc(state: ^State, action: Action) -> bool { return platform.is_action_pressed(&state.platform, u32(action)) } is_action_down :: proc(state: ^State, action: Action) -> bool { return platform.is_action_down(&state.platform, u32(action)) } is_action_released :: proc(state: ^State, action: Action) -> bool { return platform.is_action_released(&state.platform, u32(action)) } get_action_name :: proc(state: ^State, action: Action) -> string { return platform.get_action_name(&state.platform.actions, u32(action)) } get_action_bindings :: proc(state: ^State, action: Action) -> []Input_Source { return platform.get_action_bindings(&state.platform.actions, u32(action)) } vec3_length :: proc(v: gpu.Vec3) -> f32 { return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) } vec3_normalize :: proc(v: gpu.Vec3) -> gpu.Vec3 { len := vec3_length(v) if len < 1e-8 { return {} } return v / len } vec3_cross :: proc(a, b: gpu.Vec3) -> gpu.Vec3 { return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x, } } update_camera :: proc(state: ^State, camera: ^gpu.Camera3D, mode: gpu.Camera_Mode = .FREE) { if state == nil || camera == nil { return } dt := get_frame_time(state) mouse_delta := get_mouse_delta(state) #partial switch mode { case .FREE: sensitivity: f32 = 0.003 yaw := -mouse_delta.x * sensitivity pitch := -mouse_delta.y * sensitivity forward := vec3_normalize(camera.target - camera.position) right := vec3_normalize(vec3_cross(forward, camera.up)) cos_y := math.cos(yaw) sin_y := math.sin(yaw) forward = { forward.x * cos_y + forward.z * sin_y, forward.y, -forward.x * sin_y + forward.z * cos_y, } cos_p := math.cos(pitch) sin_p := math.sin(pitch) new_forward := vec3_normalize(gpu.Vec3{ forward.x * cos_p, forward.y * cos_p + sin_p, forward.z * cos_p, }) if math.abs(new_forward.y) < 0.99 { forward = new_forward } camera.target = camera.position + forward speed: f32 = 5.0 * dt if is_key_down(state, .Shift_Left) { speed *= 2.5 } right = vec3_normalize(vec3_cross(forward, camera.up)) if is_key_down(state, .W) { camera.position += forward * speed camera.target += forward * speed } if is_key_down(state, .S) { camera.position -= forward * speed camera.target -= forward * speed } if is_key_down(state, .D) { camera.position += right * speed camera.target += right * speed } if is_key_down(state, .A) { camera.position -= right * speed camera.target -= right * speed } if is_key_down(state, .E) || is_key_down(state, .Space) { camera.position.y += speed camera.target.y += speed } if is_key_down(state, .Q) || is_key_down(state, .Ctrl_Left) { camera.position.y -= speed camera.target.y -= speed } case .ORBITAL: sensitivity: f32 = 0.005 dist := vec3_length(camera.position - camera.target) offset := camera.position - camera.target theta := math.atan2(offset.x, offset.z) phi := math.acos(clamp(offset.y / dist, -1, 1)) if is_mouse_button_down(state, .Right) { theta -= mouse_delta.x * sensitivity phi -= mouse_delta.y * sensitivity phi = clamp(phi, 0.05, math.PI - 0.05) } scroll := get_mouse_wheel_move(state) dist -= scroll * dist * 0.1 dist = max(dist, 0.1) camera.position = camera.target + gpu.Vec3{ dist * math.sin(phi) * math.sin(theta), dist * math.cos(phi), dist * math.sin(phi) * math.cos(theta), } } }