mod camera; mod config; mod content; mod renderer; mod sim; use futures::executor; use glam::Vec2; use rand::random_range; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ application::ApplicationHandler, event::{ ElementState, KeyEvent, MouseButton, WindowEvent::{self}, }, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, window::Window, }; use crate::{ camera::Camera, config::WINDOW_TITLE, content::{ entities::{entity_cube::entity_cube_def, entity_grenade::entity_grenade_def}, materials::MaterialId, }, renderer::RendererState, sim::{ cell::cell::Cell, cell_manager::manager::CellManager, lib::force::apply_explosion, particle_manager::particle::Particle, rb_manager::DebugRenderMode, sim_manager::{SimCtx, SimManager}, }, }; pub type Error = Box; pub type Result = std::result::Result; struct Config { brush_radius: f32, brush_material: MaterialId, dropper_material: MaterialId, use_threading: bool, cells_render: bool, debug_render: bool, debug_render_mode: DebugRenderMode, } struct Input { last_mouse_pos_on_screen: Option<(f32, f32)>, last_mouse_world_pos: Option<(f32, f32)>, last_mouse_chunk_pos: Option<(i32, i32)>, last_mouse_local_pos: Option<(u8, u8)>, is_lmb_pressed: bool, is_rmb_pressed: bool, trigger_test_1: bool, trigger_test_2: bool, trigger_test_3: bool, trigger_test_4: bool, trigger_test_5: bool, // keybindings is_up_pressed: bool, is_left_pressed: bool, is_down_pressed: bool, is_right_pressed: bool, } struct Diagnostics { frame_times: VecDeque, fps: f32, } struct App { // core window: Option>, renderer_state: Option, // game input: Input, camera: Option, sim_manager: Option, // renderer last_render: Instant, // state config: Config, diagnostics: Diagnostics, } impl App { // called as often as possible // delta time is the real seconds elapsed since the last time this was called fn update(&mut self, delta_time: f32) { // apply inputs if let Some(camera) = &mut self.camera { camera.handle_camera_input(&self.input, delta_time) } if let Some(sim) = &mut self.sim_manager { // --TEST SPAWNING-- if self.input.trigger_test_1 && let Some(lm) = self.input.last_mouse_world_pos { self.input.trigger_test_1 = false; sim.create_entity(entity_cube_def( Vec2::new(lm.0, lm.1), self.config.dropper_material, )); } if self.input.trigger_test_2 && let Some(lm) = self.input.last_mouse_world_pos { self.input.trigger_test_2 = false; sim.create_entity(entity_grenade_def(Vec2::new(lm.0, lm.1), 3.0)); } if self.input.trigger_test_3 && let Some(lm) = self.input.last_mouse_world_pos { self.input.trigger_test_3 = false; for _ in 0..100 { sim.particle_manager.particles.push(Particle { position: Vec2::new( lm.0 + random_range(-25.0..25.0), lm.1 + random_range(-25.0..25.0), ), velocity: Vec2::ZERO, material: MaterialId::Sand, life: 2.0, }) } } if self.input.trigger_test_4 && let Some(lm) = self.input.last_mouse_world_pos { let mouse = Vec2::new(lm.0, lm.1); self.input.trigger_test_4 = false; apply_explosion( &mut SimCtx { cell_manager: &mut sim.cell_manager, particle_manager: &mut sim.particle_manager, rb_manager: &mut sim.rb_manager, }, mouse, 30, Vec2::new(0.0, -0.6), 300.0, ); } // --TEST DRAWING-- if (self.input.is_lmb_pressed || self.input.is_rmb_pressed) && let Some(lm) = self.input.last_mouse_world_pos { // start with the bounding box of the drawing brush circle + some margin // clamp the bounding box to the board sie let bb_xl = (lm.0 - self.config.brush_radius).round() as i32; let bb_xu = (lm.0 + self.config.brush_radius).round() as i32; let bb_yl = (lm.1 - self.config.brush_radius).round() as i32; let bb_yu = (lm.1 + self.config.brush_radius).round() as i32; // for each point, check if the distance is less than the brush size and write the pixel for x in bb_xl..bb_xu { for y in bb_yl..bb_yu { let r = random_range(0.0..1.0); if ((x - lm.0.round() as i32).pow(2) + (y - lm.1.round() as i32).pow(2)) < (self.config.brush_radius as i32).pow(2) && r > 0.9 { let cell = if self.input.is_lmb_pressed { let mut cell = Cell::from_material(self.config.brush_material); // ensure we simulate on the first tick cell.match_parity(sim.cell_manager.seqno); cell } else { Cell::void() }; sim.cell_manager.set_cell_from_game_position( x, y, cell, false, // wake the chunk ) } } } } sim.update(&self.config, delta_time); } } } impl Default for App { fn default() -> Self { Self { window: None, renderer_state: None, input: Input { last_mouse_pos_on_screen: None, last_mouse_world_pos: None, last_mouse_chunk_pos: None, last_mouse_local_pos: None, trigger_test_1: false, trigger_test_2: false, trigger_test_3: false, trigger_test_4: false, trigger_test_5: false, is_lmb_pressed: false, is_rmb_pressed: false, is_up_pressed: false, is_left_pressed: false, is_down_pressed: false, is_right_pressed: false, }, camera: None, sim_manager: Some(SimManager::new()), last_render: Instant::now(), config: Config { use_threading: true, brush_radius: 10.0, brush_material: MaterialId::Sand, dropper_material: MaterialId::Wood, cells_render: true, debug_render: false, debug_render_mode: DebugRenderMode::default(), }, diagnostics: Diagnostics { fps: 0.0, frame_times: VecDeque::new(), }, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = Arc::new( event_loop .create_window(Window::default_attributes().with_title(WINDOW_TITLE)) .unwrap(), ); self.window = Some(window.clone()); self.renderer_state = Some(executor::block_on(RendererState::new(window.clone()))); let size = window.inner_size(); self.camera = Some(Camera::new((size.width as i32, size.height as i32))); // let surface = SurfaceTexture::new(size.width, size.height, window_ref); // let mut pixels = Pixels::new(PIXEL_BUFFER_WIDTH, PIXEL_BUFFER_HEIGHT, surface).unwrap(); // pixels.set_scaling_mode(ScalingMode::Fill); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _: winit::window::WindowId, event: WindowEvent, ) { if let Some(renderer_state) = &mut self.renderer_state && let Some(window) = &mut self.window { let egui_response = renderer_state.egui_state.on_window_event(window, &event); // if egui consumed the event, it means we shouldn't treat any e.g., mouse clicks if egui_response.consumed { return; } } match event { WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(code), state, repeat, .. }, .. } => { if let Some(sim) = &mut self.sim_manager { let pressed = state.is_pressed(); match code { KeyCode::KeyW => self.input.is_up_pressed = pressed, KeyCode::KeyA => self.input.is_left_pressed = pressed, KeyCode::KeyS => self.input.is_down_pressed = pressed, KeyCode::KeyD => self.input.is_right_pressed = pressed, KeyCode::KeyC => sim.cell_manager = CellManager::from_default_size(), KeyCode::KeyP => sim.particle_manager.particles = Vec::new(), KeyCode::KeyV => { let ids: Vec = sim.entities.keys().cloned().collect(); for id in ids { sim.destroy_entity(id); } } KeyCode::Space if pressed => sim.paused = !sim.paused, KeyCode::KeyX if pressed => sim.ignore_pause_next_tick = true, KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true, KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true, KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true, KeyCode::Digit4 if pressed && !repeat => self.input.trigger_test_4 = true, KeyCode::Digit5 if pressed && !repeat => self.input.trigger_test_5 = true, _ => {} } } } WindowEvent::CursorMoved { position, .. } => { self.input.last_mouse_pos_on_screen = Some((position.x as f32, position.y as f32)); if let Some(camera) = &mut self.camera { let world_pos = camera.screen_position_to_world(position.x as f32, position.y as f32); self.input.last_mouse_world_pos = Some(world_pos); let ((cx, cy), (lx, ly)) = CellManager::split_game_position(world_pos.0 as i32, world_pos.1 as i32); self.input.last_mouse_chunk_pos = Some((cx, cy)); self.input.last_mouse_local_pos = Some((lx, ly)); } } WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left { self.input.is_lmb_pressed = state == ElementState::Pressed } else if button == MouseButton::Right { self.input.is_rmb_pressed = state == ElementState::Pressed } } WindowEvent::Resized(size) => { if let Some(renderer_state) = &mut self.renderer_state && let Some(camera) = &mut self.camera { renderer_state.resize(size.width, size.height); camera.resize((size.width as i32, size.height as i32)); } } WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::RedrawRequested => { #[cfg(feature = "profiler")] puffin::GlobalProfiler::lock().new_frame(); puffin::profile_scope!("redraw_requested"); // compute FPS diagnostics let now = Instant::now(); let delta_time = (now - self.last_render).as_secs_f32(); self.last_render = now; self.diagnostics.frame_times.push_back(delta_time); if self.diagnostics.frame_times.len() > 30 { self.diagnostics.frame_times.pop_front(); } let average_frame_time = self.diagnostics.frame_times.iter().sum::() / self.diagnostics.frame_times.len() as f32; self.diagnostics.fps = 1.0 / average_frame_time; self.update(delta_time); if let Some(renderer_state) = &mut self.renderer_state && let Some(sim) = &mut self.sim_manager && let Some(camera) = &mut self.camera { renderer_state.render( sim, camera, &mut self.config, &self.diagnostics, &self.input, ); } } _ => {} } } fn about_to_wait(&mut self, _: &ActiveEventLoop) { if let Some(window) = &self.window { window.request_redraw(); } else { panic!("No window!") } } } #[cfg(feature = "profiler")] fn start_profiler() { let _server = puffin_http::Server::new("127.0.0.1:8585").unwrap(); puffin::set_scopes_on(true); std::mem::forget(_server); // keep serving for the process lifetime std::process::Command::new("puffin_viewer") .args(["--url", "127.0.0.1:8585"]) .spawn() .ok(); // don't die if it isn't installed } fn main() -> Result<()> { #[cfg(feature = "profiler")] start_profiler(); let event_loop = EventLoop::new()?; event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::default(); event_loop.run_app(&mut app)?; Ok(()) }