use std::collections::HashSet;
use std::sync::Arc;
use std::time::Instant;
use pollster::FutureExt;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowId};
use rsaber_lib::{APP_NAME, Main};
use rsaber_lib::asset::EmbedAssetManager;
use rsaber_lib::cgmath::{Deg, InnerSpace, Matrix3, Quaternion, Rotation3, Vector3};
use rsaber_lib::output::{WindowBegin, WindowOutput};
use rsaber_lib::scene::{SceneInput, ScenePose, ScenePoseScroll};
use rsaber_lib::util::Stats;
use rsaber_lib::wgpu::{InstanceDescriptor, SurfaceTarget};
const COMMENT: &str = "You can use keys w-a-s-d to move, z-x to change elevation, r to reset view, p to pause/resume audio engine and arrow keys to rotate camera. Interaction with UI controls can be done with mouse.";
const MIN_SIZE: PhysicalSize<u32> = PhysicalSize { width: 800, height: 600 };
const DEFAULT_POS: Vector3<f32> = Vector3::new(0.0, -2.5, 1.8); const ROT_SPEED: f32 = 50.0; const MOVE_SPEED: f32 = 5.0; const SCROLL_LINE_SPEED: f32 = 30.0; const SCROLL_PIXEL_FACTOR: f32 = 5.0;
struct App {
asset_mgr: Option<EmbedAssetManager>,
data: Option<AppData>,
}
struct AppData {
window: Arc<Window>,
output: WindowOutput,
main: Main,
pos: Vector3<f32>,
pitch: f32,
yaw: f32,
keys: HashSet<KeyCode>,
prev_ts_opt: Option<Instant>,
cursor_pos: Option<(u32, u32)>,
cursor_click: bool,
cursor_scroll: ScenePoseScroll,
size_opt: Option<(u32, u32)>,
audio_running: bool,
}
impl App {
fn new(asset_mgr: EmbedAssetManager) -> Self {
Self {
asset_mgr: Some(asset_mgr),
data: None,
}
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.data.is_none() {
let window_attrs = Window::default_attributes()
.with_title(APP_NAME)
.with_min_inner_size(MIN_SIZE);
let window = Arc::new(event_loop.create_window(window_attrs).expect("Unable to create window"));
let output = WindowOutput::new(InstanceDescriptor::new_with_display_handle(Box::new(event_loop.owned_display_handle())), SurfaceTarget::from(Arc::clone(&window))).block_on();
let stats = Stats::new(COMMENT);
let main = Main::new(self.asset_mgr.take().unwrap(), output.get_output_device(), stats);
self.data = Some(AppData {
window,
output,
main,
pos: DEFAULT_POS,
pitch: 0.0,
yaw: 0.0,
keys: HashSet::new(),
prev_ts_opt: None,
cursor_pos: None,
cursor_click: false,
cursor_scroll: (0.0, 0.0),
size_opt: None,
audio_running: true,
});
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
let data = match &mut self.data {
Some(data) => data,
None => return,
};
let window = &data.window;
let output = &data.output;
let main = &data.main;
let audio_engine = main.get_audio_engine();
let keys = &mut data.keys;
let prev_ts_opt = &mut data.prev_ts_opt;
let cursor_pos = &mut data.cursor_pos;
let cursor_click = &mut data.cursor_click;
let cursor_scroll = &mut data.cursor_scroll;
let size_opt = &mut data.size_opt;
let audio_running = &mut data.audio_running;
match event {
WindowEvent::Resized(_) => {
let size = window.inner_size();
if size.width == 0 || size.height == 0 {
audio_engine.pause();
keys.clear();
*prev_ts_opt = None;
*cursor_pos = None;
*cursor_click = false;
*cursor_scroll = (0.0, 0.0);
*size_opt = None;
} else {
output.resize(size.width, size.height);
main.configure(size.width, size.height);
if *audio_running {
audio_engine.start();
}
*size_opt = Some((size.width, size.height));
}
},
WindowEvent::RedrawRequested => {
if let Some((width, height)) = size_opt {
let ts: Instant = Instant::now();
let pos = &mut data.pos;
let pitch = &mut data.pitch;
let yaw = &mut data.yaw;
if keys.contains(&KeyCode::KeyR) { *pos = DEFAULT_POS;
*pitch = 0.0;
*yaw = 0.0;
} else if let Some(prev_ts) = prev_ts_opt {
let ts_diff = ts.duration_since(*prev_ts).as_secs_f32();
let value = ROT_SPEED * ts_diff;
if keys.contains(&KeyCode::ArrowUp) {
*pitch += value;
}
if keys.contains(&KeyCode::ArrowDown) {
*pitch -= value;
}
if keys.contains(&KeyCode::ArrowLeft) {
*yaw += value;
}
if keys.contains(&KeyCode::ArrowRight) {
*yaw -= value;
}
let value = MOVE_SPEED * ts_diff * (Quaternion::from_angle_z(Deg(*yaw)) * Vector3::unit_y());
if keys.contains(&KeyCode::KeyW) {
*pos += value;
}
if keys.contains(&KeyCode::KeyS) {
*pos -= value;
}
let value = Vector3::unit_z().cross(value);
if keys.contains(&KeyCode::KeyA) {
*pos += value;
}
if keys.contains(&KeyCode::KeyD) {
*pos -= value;
}
let value = MOVE_SPEED * ts_diff * Vector3::unit_z();
if keys.contains(&KeyCode::KeyX) {
*pos += value;
}
if keys.contains(&KeyCode::KeyZ) {
*pos -= value;
}
}
*prev_ts_opt = Some(ts);
let dir = Quaternion::from_angle_z(Deg(*yaw)) * Quaternion::from_angle_x(Deg(*pitch)) * Vector3::unit_y();
match output.begin(pos, &dir) {
WindowBegin::Skip => (),
WindowBegin::ResizeNeeded => {
output.resize(*width, *height);
main.configure(*width, *height);
},
WindowBegin::Frame(frame) => {
let mut scene_input = SceneInput {
pose_l_opt: None,
pose_r_opt: None,
};
let pose;
if let Some((x, y)) = *cursor_pos {
let ndc_x = 2.0 * x as f32 / *width as f32 - 1.0;
let ndc_y = -(2.0 * y as f32 / *height as f32 - 1.0);
let unit_y = frame.raycast(ndc_x, ndc_y);
let unit_x = unit_y.cross(Vector3::unit_z()).normalize();
let unit_z = unit_x.cross(unit_y);
let rot_m = Matrix3::from_cols(unit_x, unit_y, unit_z) * Matrix3::from_angle_x(Deg(-90.0));
let rot = Quaternion::from(rot_m);
pose = Pose::new(pos, &rot, *cursor_click, *cursor_scroll);
scene_input.pose_r_opt = Some(&pose);
*cursor_scroll = (0.0, 0.0);
}
main.render(frame, &scene_input);
}
}
window.request_redraw(); }
},
WindowEvent::KeyboardInput { event, .. } => {
if let PhysicalKey::Code(key) = event.physical_key && !event.repeat {
let pressed = match event.state {
ElementState::Pressed => true,
ElementState::Released => false,
};
if key == KeyCode::KeyP {
if pressed {
*audio_running = !*audio_running;
if *audio_running {
audio_engine.start();
} else {
audio_engine.pause();
}
}
} else {
if pressed {
keys.insert(key);
} else {
keys.remove(&key);
}
}
}
},
WindowEvent::CursorMoved { position, .. } => {
*cursor_pos = Some((position.x as u32, position.y as u32));
},
WindowEvent::CursorLeft { .. } => {
*cursor_pos = None;
*cursor_click = false;
},
WindowEvent::MouseInput { state, button, .. } => {
if matches!(button, MouseButton::Left) {
*cursor_click = match state {
ElementState::Pressed => true,
ElementState::Released => false,
};
}
},
WindowEvent::MouseWheel { delta, phase, .. } => {
if matches!(phase, TouchPhase::Moved) {
*cursor_scroll = match delta {
MouseScrollDelta::LineDelta(scroll_x, scroll_y) => (SCROLL_LINE_SPEED * scroll_x, SCROLL_LINE_SPEED * scroll_y),
MouseScrollDelta::PixelDelta(scroll) => (SCROLL_PIXEL_FACTOR * scroll.x as f32, SCROLL_PIXEL_FACTOR * scroll.y as f32),
}
}
},
WindowEvent::CloseRequested => {
event_loop.exit();
},
_ => (),
}
}
}
struct Pose {
pos: Vector3<f32>,
rot: Quaternion<f32>,
click: bool,
scroll: ScenePoseScroll,
}
impl Pose {
fn new(pos: &Vector3<f32>, rot: &Quaternion<f32>, click: bool, scroll: ScenePoseScroll) -> Self {
Self {
pos: *pos,
rot: *rot,
click,
scroll,
}
}
}
impl ScenePose for Pose {
fn get_pos(&self) -> &Vector3<f32> {
&self.pos
}
fn get_rot(&self) -> &Quaternion<f32> {
&self.rot
}
fn get_click(&self) -> bool {
self.click
}
fn get_scroll(&self) -> ScenePoseScroll {
self.scroll
}
fn get_render(&self) -> bool {
false
}
fn apply_haptic(&self) {
}
}
fn main() {
let asset_mgr = EmbedAssetManager::new();
let mut app = App::new(asset_mgr);
let event_loop = EventLoop::new().expect("Unable to create event loop");
event_loop.run_app(&mut app).expect("Unable to run event loop");
}