use std::{collections::{BTreeMap, HashMap}, time::{Duration, Instant}}; use log::{debug, trace}; use map::{CellKind, Map}; use rand::prelude::*; use voronoice::Point; use winit::{dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, KeyEvent, MouseButton, MouseScrollDelta, Touch as WTouch, TouchPhase, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, window::Window}; use crate::{dbg, graphics::{Uniforms, Vertex}}; mod entity; mod map; mod ui; use entity::{Entity, EntityKind, ExternOp}; use ui::{Kind, UI}; fn rgba_to_grayscale(c: [f32; 4]) -> [f32; 4] { let v = (c[0]*0.299) + (c[1]*0.587) + (c[2]*0.114); [v, v, v, c[3]] } struct Touch { pub pos: PhysicalPosition, /// id=1000 is for mouse pub id: u64, pub start: Instant } impl PartialEq for Touch { fn eq(&self, other: &Self) -> bool { self.id == other.id } } impl From for Touch { fn from(value: WTouch) -> Self { Self::new(value.location, value.id) } } impl Touch { pub fn new(pos: PhysicalPosition, id: u64) -> Self { Self { pos, id, start: Instant::now() } } } pub struct State { pub vertices: Vec, pub indices: Vec, pub uniforms: Uniforms, zoom: f32, map: Map, ui: UI, start: Instant, last_frame: Instant, t: usize, // Time in frames selected_tile: usize, framerate: f32, // Update per second pub entities: BTreeMap, // entity id --> Entities next_eid: usize, pub cells_entities: HashMap>, // cell id --> entities id /// Also acount for mouse touches: Vec, mouse_pos: Option> } impl State { pub fn new() -> Self { let mut s = Self { vertices: vec![], indices: vec![], uniforms: Uniforms::default(), zoom: 1., start: Instant::now(), last_frame: Instant::now(), t: 0, map: Map::new(0, 0), ui: UI::new(), selected_tile: 0, framerate: 1., entities: BTreeMap::new(), next_eid: 0, cells_entities: HashMap::new(), touches: Vec::new(), mouse_pos: None }; // Create vertices / indices to estimate vertex / index buffer size s.render(PhysicalSize::new(1, 1)); s } fn set_zoom(&mut self, v: f32) { self.zoom = v.clamp(1., 10.); } fn update_zooms(&mut self, screen_size: PhysicalSize) { self.uniforms.zooms = [ (screen_size.height as f32 / screen_size.width as f32).max(1.) * self.zoom, (screen_size.width as f32 / screen_size.height as f32).max(1.) * self.zoom ]; } fn handle_click(&mut self, window: &Window, pos: &PhysicalPosition) { } pub fn window_event(&mut self, event: &WindowEvent, window: &Window) { match event { WindowEvent::Touch(touch) => { match touch.phase { TouchPhase::Started => { self.touches.push((*touch).into()); }, TouchPhase::Moved => { let w_size = window.inner_size(); let old_touch_n = self.touches.iter().position(|t| t.id == touch.id).unwrap(); let old_touch = &self.touches[old_touch_n]; let t = [ touch.location.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0], touch.location.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1] ]; let old_t = [ old_touch.pos.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0], old_touch.pos.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1] ]; // Handle pinch zoom if self.touches.len() == 2 { let old_touch2 = &self.touches[if old_touch_n == 0 {1} else {0}]; let old_t2 = [ old_touch2.pos.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0], old_touch2.pos.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1] ]; self.set_zoom(self.zoom + (((((t[0]-old_t2[0])*self.zoom).powi(2)+((t[0]-old_t2[0])*self.zoom).powi(2)).sqrt() - (((old_t[0]-old_t2[0])*self.zoom).powi(2)+((old_t[0]-old_t2[0])*self.zoom).powi(2)).sqrt())*4.)); } self.uniforms.camera[0] -= (t[0] - old_t[0]) / self.touches.len() as f32; self.uniforms.camera[1] += (t[1] - old_t[1]) / self.touches.len() as f32; dbg(&self.uniforms.camera); self.touches[old_touch_n].pos = touch.location; }, TouchPhase::Ended => { let old_touch_n = self.touches.iter().position(|t| t.id == touch.id).unwrap(); let old_touch = &self.touches[old_touch_n]; if old_touch.start.elapsed() < Duration::from_millis(500) { self.handle_click(window, &touch.location); } self.touches.remove(self.touches.iter().position(|t| t.id == touch.id).unwrap()); }, TouchPhase::Cancelled => { self.touches.remove(self.touches.iter().position(|t| t.id == touch.id).unwrap()); } } } WindowEvent::MouseInput { state, button, ..} => { if state.is_pressed() { match button { MouseButton::Left => { if !self.touches.iter().any(|t| t.id == 1000) { if let Some(pos) = self.mouse_pos { self.touches.push(Touch::new(pos, 1000)); } } }, MouseButton::Right => { self.spawn_entity(self.selected_tile, Entity::new(EntityKind::Horse, self.selected_tile)); }, _ => {} }; } else { match button { MouseButton::Left => { if let Some(i) = self.touches.iter().position(|t| t.id == 1000) { self.touches.remove(i); } }, _ => {} } } }, WindowEvent::CursorLeft { .. } => { self.mouse_pos = None; if let Some(i) = self.touches.iter().position(|t| t.id == 1000) { self.touches.remove(i); } }, WindowEvent::CursorMoved { position, .. } => { self.mouse_pos = Some(*position); let w_size = window.inner_size(); self.update_zooms(w_size); let pos = Point { x: (((position.x / w_size.width as f64)*2.)-1.)/(self.uniforms.zooms[0] as f64) + self.uniforms.camera[0] as f64, y: -(((position.y / w_size.height as f64)*2.)-1.)/(self.uniforms.zooms[1] as f64) + self.uniforms.camera[1] as f64 }; let c = self.map.voronoi.cell(self.selected_tile); if let Some(i) = self.touches.iter().position(|t| t.id == 1000) { self.touches[i].pos = *position; for i in c.iter_path(pos.clone()) { self.selected_tile = i; let cd = &mut self.map.cells_data[self.selected_tile]; if let CellKind::Dirt = cd.kind { cd.kind = CellKind::Forest; } } } else { self.selected_tile = c.iter_path(pos).last().unwrap(); } }, WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(kc), state, .. }, .. } => { if state.is_pressed() { match kc { KeyCode::KeyW => { self.uniforms.camera[1] += 0.1 * self.zoom; }, KeyCode::KeyS => { self.uniforms.camera[1] -= 0.1 / self.zoom; }, KeyCode::KeyA => { self.uniforms.camera[0] -= 0.1 / self.zoom; }, KeyCode::KeyD => { self.uniforms.camera[0] += 0.1 / self.zoom; }, KeyCode::KeyR => { self.set_zoom(self.zoom + 0.1); }, KeyCode::KeyF => { self.set_zoom(self.zoom - 0.1); }, _ => {} } self.update_zooms(window.inner_size()); } }, WindowEvent::MouseWheel { delta, .. } => { self.framerate -= match delta { MouseScrollDelta::PixelDelta(pos) => pos.y as f32, MouseScrollDelta::LineDelta(_, y) => *y } * 0.1; self.framerate = self.framerate.max(0.); } _ => {} } } pub fn render(&mut self, screen_size: PhysicalSize) { trace!("render"); self.update_zooms(screen_size); self.vertices = Vec::new(); self.indices = Vec::new(); for (c, cd) in self.map.voronoi.iter_cells().zip(self.map.cells_data.iter()).filter(|(_,cd)| cd.kind != CellKind::Forest) { let mut color = cd.color(); if c.site() == self.selected_tile { color[0] = (color[0]+0.4).clamp(0., 1.); color[1] = (color[1]+0.4).clamp(0., 1.); color[2] = (color[2]+0.4).clamp(0., 1.); } let vs = c.iter_vertices().collect::>(); let i = self.vertices.len() as u32; for v in vs.iter() { self.vertices.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1)); } for v in 1..(vs.len()-1) as u32 { self.indices.push(i); self.indices.push(i+v); self.indices.push(i+v+1); } } for e in self.entities.values() { e.render(&mut self.vertices, &mut self.indices, &self.map, ); } for (c, cd) in self.map.voronoi.iter_cells().zip(self.map.cells_data.iter()).filter(|(_,cd)| cd.kind == CellKind::Forest) { let mut color = cd.color(); if c.site() == self.selected_tile { color[0] = (color[0]+0.4).clamp(0., 1.); color[1] = (color[1]+0.4).clamp(0., 1.); color[2] = (color[2]+0.4).clamp(0., 1.); } let vs = c.iter_vertices().collect::>(); let i = self.vertices.len() as u32; for v in vs.iter() { self.vertices.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1)); } for v in 1..(vs.len()-1) as u32 { self.indices.push(i); self.indices.push(i+v); self.indices.push(i+v+1); } } self.ui.render(&mut self.vertices, &mut self.indices, screen_size); } pub fn update_if_needed(&mut self) { while self.last_frame.elapsed().as_secs_f32() > 1. / self.framerate { self.update(); } } pub fn update(&mut self) { trace!("update"); self.last_frame = Instant::now(); let mut rng = thread_rng(); let mut new_kind = Vec::new(); for cd in self.map.cells_data.iter_mut() { cd.update(); } for cd in self.map.cells_data.iter() { if cd.kind == CellKind::Forest || cd.kind == CellKind::Grass { let r = rng.gen::(); if r < (0.035*cd.moisture) { let c = self.map.voronoi.cell(cd.cid); let n = c.iter_neighbors().choose(&mut rng).unwrap(); let k = if r < (0.005*cd.moisture) && (self.map.cells_data[n].kind == CellKind::Dirt || self.map.cells_data[n].kind == CellKind::Grass) && cd.kind == CellKind::Forest { Some(CellKind::Forest) } else if self.map.cells_data[n].kind == CellKind::Dirt { Some(CellKind::Grass) } else { None }; if let Some(k) = k { new_kind.push((n, k)); } } } } for (n, k) in new_kind { let cd = &mut self.map.cells_data[n]; cd.kind = k; cd.resource = 1.; } // if Option is None remove the entity let mut entities_to_move: Vec<(usize, Option)> = Vec::new(); for (eid, e) in self.entities.iter_mut() { match e.update(&mut self.map, self.t, &mut rng) { Some(ExternOp::Move(cid)) => { entities_to_move.push((*eid, Some(cid))); }, Some(ExternOp::Remove) => { entities_to_move.push((*eid, None)); }, None => {} } } for (eid, new_cid) in entities_to_move { let entity = self.entities.get_mut(&eid).unwrap(); let cell_entities = self.cells_entities.get_mut(&entity.cid).unwrap(); cell_entities.remove(cell_entities.iter().position(|e| *e == eid).unwrap()); match new_cid { Some(new_cid) => { entity.cid = new_cid; match self.cells_entities.get_mut(&new_cid) { Some(v) => v.push(eid), None => {self.cells_entities.insert(new_cid, vec![eid]);} } }, None => {self.entities.remove(&eid);} } } self.t += 1; } pub fn spawn_entity(&mut self, cid: usize, e: Entity) { let eid = self.next_eid; self.next_eid += 1; self.entities.insert(eid, e); if let Some(v) = self.cells_entities.get_mut(&cid) { v.push(eid); } else { self.cells_entities.insert(cid, vec![eid]); } } }