369 lines
15 KiB
Rust
369 lines
15 KiB
Rust
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<f64>,
|
|
/// 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<WTouch> for Touch {
|
|
fn from(value: WTouch) -> Self {
|
|
Self::new(value.location, value.id)
|
|
}
|
|
}
|
|
impl Touch {
|
|
pub fn new(pos: PhysicalPosition<f64>, id: u64) -> Self {
|
|
Self {
|
|
pos,
|
|
id,
|
|
start: Instant::now()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
pub vertices: Vec<Vertex>,
|
|
pub indices: Vec<u32>,
|
|
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<usize, Entity>, // entity id --> Entities
|
|
next_eid: usize,
|
|
pub cells_entities: HashMap<usize, Vec<usize>>, // cell id --> entities id
|
|
/// Also acount for mouse
|
|
touches: Vec<Touch>,
|
|
mouse_pos: Option<PhysicalPosition<f64>>
|
|
}
|
|
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<u32>) {
|
|
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<f64>) {
|
|
|
|
}
|
|
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<u32>) {
|
|
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::<Vec<_>>();
|
|
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::<Vec<_>>();
|
|
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::<f32>();
|
|
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<usize>)> = 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]);
|
|
}
|
|
}
|
|
}
|