diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-11 00:46:56 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-11 00:46:56 -0700 |
| commit | 6f67586b8fc6efdb86d0c9a9744c546da97c4dab (patch) | |
| tree | 2c0b328f7427efc5647cde810a361405d914a37c /src/main.rs | |
| parent | 167e31655b63aa4d4548532cf0410cea9fd145ca (diff) | |
physics for sand and water
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 184 |
1 files changed, 118 insertions, 66 deletions
diff --git a/src/main.rs b/src/main.rs index 64e57df..02920d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,19 +7,28 @@ use egui::Id; use egui_wgpu::{RendererOptions, ScreenDescriptor}; use egui_winit::egui::{self, Context}; use pixels::{Pixels, ScalingMode, SurfaceTexture}; -use std::time::{Duration, Instant}; +use std::{ + cmp::{max, min}, + time::{Duration, Instant}, +}; use winit::{ application::ApplicationHandler, - event::{ElementState, MouseButton, WindowEvent}, + event::{ + ElementState, KeyEvent, MouseButton, + WindowEvent::{self}, + }, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, - keyboard::Key::{self}, + keyboard::{KeyCode, PhysicalKey}, window::Window, }; use crate::{ - camera::{CameraState, screen_position_to_board, write_frame_view}, + camera::Camera, config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH, WINDOW_TITLE}, - sim::{board::Board, overlay::create_compute_combined_overlay_offset}, + sim::{ + board::Board, materials::MaterialId, overlay::create_compute_combined_overlay_offset, + sim::sim_tick, + }, ui::draw_egui, }; @@ -27,21 +36,22 @@ pub type Error = Box<dyn std::error::Error>; pub type Result<T> = std::result::Result<T, Error>; struct Config { + show_ticks: bool, fps: u16, - brush_size: u8, + brush_radius: u8, + brush_material: MaterialId, } struct Input { - last_mouse_pos_on_screen: (f64, f64), - last_mouse_pos_on_board: (i32, i32), - is_lmb_down: bool, -} + last_mouse_pos_on_screen: Option<(f64, f64)>, + last_mouse_pos_on_board: Option<(i32, i32)>, + is_lmb_pressed: bool, -struct State { - // debug - red_level: u8, - green_level: u8, - blue_level: u8, + // keybindings + is_up_pressed: bool, + is_left_pressed: bool, + is_down_pressed: bool, + is_right_pressed: bool, } struct Diagnostics { @@ -56,22 +66,24 @@ struct App { window: Option<&'static Window>, pixels: Option<Pixels<'static>>, - // input state input: Input, - // camera state - camera: Option<CameraState>, + camera: Option<Camera>, - // game state board: Option<Board>, + // sim state + // the last/current (not yet completed) seqno + sim_seqno: u64, + sim_paused: bool, + ignore_pause_next_tick: bool, + // used to wait for drawing last_frame_requested: Instant, // used to compute delta_time last_frame_real: Instant, config: Config, - state: State, diagnostics: Diagnostics, } @@ -85,12 +97,17 @@ impl Default for App { pixels: None, input: Input { - last_mouse_pos_on_screen: (0.0, 0.0), - last_mouse_pos_on_board: (0, 0), - is_lmb_down: false, + last_mouse_pos_on_screen: None, + last_mouse_pos_on_board: None, + + is_lmb_pressed: false, + is_up_pressed: false, + is_left_pressed: false, + is_down_pressed: false, + is_right_pressed: false, }, - camera: Some(CameraState { + camera: Some(Camera { x: 0.0, y: 0.0, zoom: 0.5, @@ -98,17 +115,18 @@ impl Default for App { board: Some(Board::empty()), + sim_seqno: 0, + sim_paused: false, + ignore_pause_next_tick: false, + last_frame_requested: Instant::now(), last_frame_real: Instant::now(), config: Config { fps: 120, - brush_size: 10, - }, - state: State { - red_level: 0xFF, - green_level: 0xFF, - blue_level: 0xFF, + show_ticks: false, + brush_radius: 10, + brush_material: MaterialId::Sand, }, diagnostics: Diagnostics { fps: 0.0 }, } @@ -174,25 +192,46 @@ impl ApplicationHandler for App { } match event { - WindowEvent::KeyboardInput { event, .. } => match event.logical_key { - Key::Character(char) => { - if char == "z" { - camera.zoom += 0.1; + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: PhysicalKey::Code(code), + state, + .. + }, + .. + } => { + 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 => self.board = Some(Board::empty()), + KeyCode::Space => { + if pressed { + self.sim_paused = !self.sim_paused + } + } + KeyCode::KeyX => { + if pressed { + self.ignore_pause_next_tick = true + } } + _ => {} } - _ => {} - }, + } WindowEvent::CursorMoved { position, .. } => { - self.input.last_mouse_pos_on_screen = (position.x, position.y); + self.input.last_mouse_pos_on_screen = Some((position.x, position.y)); self.input.last_mouse_pos_on_board = pixels .window_pos_to_pixel((position.x as f32, position.y as f32)) - .map(|v| screen_position_to_board(board, camera, v.0 as f64, v.1 as f64)) + .map(|v| camera.screen_position_to_board(board, v.0 as f64, v.1 as f64)) .map(|v| (v.0 as i32, v.1 as i32)) - .unwrap(); + .ok() } WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left { - self.input.is_lmb_down = state == ElementState::Pressed + self.input.is_lmb_pressed = state == ElementState::Pressed } } WindowEvent::Resized(size) => { @@ -212,54 +251,68 @@ impl ApplicationHandler for App { // TODO: can smooth and round this self.diagnostics.fps = instantaneous_fps; + // apply inputs + camera.handle_camera_input(&self.input, delta_time); + // pixels/camera logic let frame = pixels.frame_mut(); frame.fill(0); // --TEST DRAWING-- - if self.input.is_lmb_down { + if self.input.is_lmb_pressed && self.input.last_mouse_pos_on_board.is_some() { // start with the bounding box of the drawing brush circle + some margin - let bb_xl = self.input.last_mouse_pos_on_board.0 - - self.config.brush_size as i32 / 2 - - 5; - let bb_xu = self.input.last_mouse_pos_on_board.0 - + self.config.brush_size as i32 / 2 - + 5; - let bb_yl = self.input.last_mouse_pos_on_board.1 - - self.config.brush_size as i32 / 2 - - 5; - let bb_yu = self.input.last_mouse_pos_on_board.1 - + self.config.brush_size as i32 / 2 - + 5; + // clamp the bounding box to the board sie + // TODO better way to do this without unwrap? + let bb_xl = max( + self.input.last_mouse_pos_on_board.unwrap().0 + - self.config.brush_radius as i32, + -((board.size_x / 2) as i32), + ); + let bb_xu = min( + self.input.last_mouse_pos_on_board.unwrap().0 + + self.config.brush_radius as i32, + (board.size_x / 2) as i32, + ); + let bb_yl = max( + self.input.last_mouse_pos_on_board.unwrap().1 + - self.config.brush_radius as i32, + -((board.size_y / 2) as i32), + ); + let bb_yu = min( + self.input.last_mouse_pos_on_board.unwrap().1 + + self.config.brush_radius as i32, + (board.size_y / 2) 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 { // brush/selection - if (((x - self.input.last_mouse_pos_on_board.0).pow(2) - + (y - self.input.last_mouse_pos_on_board.1).pow(2)) - as f32) - .sqrt() - < self.config.brush_size as f32 + if ((x - self.input.last_mouse_pos_on_board.unwrap().0).pow(2) + + (y - self.input.last_mouse_pos_on_board.unwrap().1).pow(2)) + < (self.config.brush_radius as i32).pow(2) { board.set_cell_at_position( x, y, - sim::board::Cell { - material: 1, - velocity_x: 0, - velocity_y: 0, - flags: 0, - }, + sim::board::Cell::from_material(self.config.brush_material), ); } } } } + // TODO check if we need to run another sim tick given the sim speed + // SIM logic + if !self.sim_paused || self.ignore_pause_next_tick { + sim_tick(board, self.sim_seqno, delta_time); + self.sim_seqno += 1; + self.ignore_pause_next_tick = false; + } + let get_overlay = create_compute_combined_overlay_offset(board, &self.config, &self.input); - write_frame_view(frame, board, camera, get_overlay); + camera.write_frame_view(frame, board, get_overlay); // egui logic let raw_input = egui_state.take_egui_input(window); @@ -272,7 +325,6 @@ impl ApplicationHandler for App { draw_egui( panel_ui, &mut self.config, - &mut self.state, camera, &self.diagnostics, &self.input, |
