entities are well managed (and die lol)
This commit is contained in:
parent
83f76015d4
commit
f54ca4ed19
@ -1,6 +1,5 @@
|
|||||||
mod graphics;
|
mod graphics;
|
||||||
mod state;
|
mod state;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use state::State;
|
use state::State;
|
||||||
use graphics::Graphics;
|
use graphics::Graphics;
|
||||||
|
379
src/state.rs
379
src/state.rs
@ -1,300 +1,31 @@
|
|||||||
use std::time::Instant;
|
use std::{collections::{BTreeMap, HashMap}, time::Instant};
|
||||||
use log::info;
|
use log::info;
|
||||||
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
use map::{CellKind, Map};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use voronoice::{BoundingBox, Point, Voronoi, VoronoiBuilder};
|
use voronoice::Point;
|
||||||
use winit::{event::{DeviceEvent, Event, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, window::Window};
|
use winit::{event::{DeviceEvent, Event, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, window::Window};
|
||||||
|
|
||||||
use crate::graphics::{Uniforms, Vertex};
|
use crate::graphics::{Uniforms, Vertex};
|
||||||
|
|
||||||
const FRAMERATE: usize = 1; // Update per second
|
mod entity;
|
||||||
|
mod map;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
mod ui;
|
||||||
enum CellKind {
|
use entity::{Entity, EntityKind, ExternOp};
|
||||||
Void,
|
|
||||||
Sea,
|
|
||||||
Beach,
|
|
||||||
Forest,
|
|
||||||
Dirt,
|
|
||||||
Stone,
|
|
||||||
Grass
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CellData {
|
|
||||||
kind: CellKind,
|
|
||||||
cell: usize,
|
|
||||||
z: f32,
|
|
||||||
area: f32,
|
|
||||||
moisture: f32,
|
|
||||||
ressource: (f32, usize) // How much ressource there is (between 0 and 1) and the last time it was updated (in frames)
|
|
||||||
}
|
|
||||||
impl CellData {
|
|
||||||
fn new(kind: CellKind, cell: usize, z: f32, moisture: f32, ressource: f32, t: usize, voronoi: &Voronoi) -> Self {
|
|
||||||
let mut area = 0.;
|
|
||||||
let c = voronoi.cell(cell);
|
|
||||||
let vs = c.iter_vertices().collect::<Vec<_>>();
|
|
||||||
let a = vs[0];
|
|
||||||
for i in 1..(vs.len()-1) {
|
|
||||||
let b = vs[i];
|
|
||||||
let c = vs[i+1];
|
|
||||||
area += 0.5 * ((a.x*(b.y-c.y))+(b.x*(c.y-a.y))+(c.x*(a.y-b.y))).abs();
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
cell,
|
|
||||||
z,
|
|
||||||
area: area as f32,
|
|
||||||
moisture,
|
|
||||||
ressource: (ressource, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn color(&self) -> [f32; 4] {
|
|
||||||
// let mut rng = thread_rng();
|
|
||||||
// [rng.gen(), rng.gen(), rng.gen(), 1.]
|
|
||||||
match self.kind {
|
|
||||||
CellKind::Void => [0.; 4],
|
|
||||||
CellKind::Sea => [0., 0., 1., 1.],
|
|
||||||
CellKind::Beach => [0.82, 0.84, 0.51, 1.],
|
|
||||||
CellKind::Forest => [0., 0.5 - (self.moisture*0.4), 0., 1.],
|
|
||||||
CellKind::Dirt => [0.53 - (self.moisture*0.4), 0.38-(self.moisture*0.4), 0.29-(self.moisture*0.4), 1.],
|
|
||||||
CellKind::Stone => [0.5, 0.5, 0.5, 1.],
|
|
||||||
CellKind::Grass => [(136./255.) - (self.moisture*0.4), (204./255.) - (self.moisture*0.4), (59./255.) - (self.moisture*0.4), 1.]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn ressource(&self) -> f32 {
|
|
||||||
// How much it get by day
|
|
||||||
let recuperation_rate = match self.kind {
|
|
||||||
CellKind::Void | CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => 0.,
|
|
||||||
CellKind::Forest => 1. / (100. * 365.25), // Let's say that a forest takes 100 years to mature
|
|
||||||
CellKind::Grass => 1. / (7. * 7.) // Let's say that grass takes 7 weaks to reach its max
|
|
||||||
};
|
|
||||||
self.ressource.0 + (self.ressource.1 as f32 * recuperation_rate)
|
|
||||||
}
|
|
||||||
fn set_ressource(&mut self, val: f32, t: usize) {
|
|
||||||
assert!(val >= 0. && val <= 1.);
|
|
||||||
self.ressource = (val, t);
|
|
||||||
}
|
|
||||||
fn refresh_ressource(&mut self, t: usize) {
|
|
||||||
self.ressource = (self.ressource(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Map {
|
|
||||||
voronoi: Voronoi,
|
|
||||||
cells_data: Vec<CellData>,
|
|
||||||
seed: u32
|
|
||||||
}
|
|
||||||
impl Map {
|
|
||||||
const HEIGHT: f32 = 2.;
|
|
||||||
const WIDTH: f32 = 2.;
|
|
||||||
const REAL_HEIGHT: f32 = 500.;
|
|
||||||
const REAL_WIDTH: f32 = 500.;
|
|
||||||
const SIZE: usize = 10_000;
|
|
||||||
fn new(seed: u32, t: usize) -> Self {
|
|
||||||
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed as u64);
|
|
||||||
let mut sites = Vec::with_capacity(Self::SIZE);
|
|
||||||
for _ in 0..Self::SIZE {
|
|
||||||
sites.push(Point { x:rng.gen_range(-Self::WIDTH/2.0..Self::WIDTH/2.0) as f64, y:rng.gen_range(-Self::HEIGHT/2.0..Self::HEIGHT/2.0) as f64 })
|
|
||||||
}
|
|
||||||
let voronoi = VoronoiBuilder::default()
|
|
||||||
.set_sites(sites)
|
|
||||||
.set_bounding_box(BoundingBox::new_centered(Self::WIDTH as f64, Self::HEIGHT as f64))
|
|
||||||
.set_lloyd_relaxation_iterations(3)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let mut cells_data = Vec::with_capacity(Self::SIZE);
|
|
||||||
let z_noise = Fbm::<Perlin>::new(seed);
|
|
||||||
let moisture_noise = Fbm::<Perlin>::new(seed+1)
|
|
||||||
.set_frequency(2.);
|
|
||||||
for i in 0..Self::SIZE {
|
|
||||||
let c = voronoi.cell(i);
|
|
||||||
let site = c.site_position();
|
|
||||||
let z = (
|
|
||||||
0.3 // Arbitrary value
|
|
||||||
+ ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1]
|
|
||||||
- ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5) // Distance - [0; sqrt(2)] * 0.5
|
|
||||||
).clamp(0., 1.);
|
|
||||||
let m = (
|
|
||||||
(moisture_noise.get([site.x, site.y])+1.)/2. // Noise + [0; 1]
|
|
||||||
).clamp(0., 1.) as f32;
|
|
||||||
let k = if z <= 0.5 {
|
|
||||||
CellKind::Sea
|
|
||||||
} else if z <= 0.52 {
|
|
||||||
CellKind::Beach
|
|
||||||
} else if z < 0.8 {
|
|
||||||
CellKind::Dirt
|
|
||||||
} else {
|
|
||||||
CellKind::Stone
|
|
||||||
};
|
|
||||||
cells_data.push(CellData::new(k, i, z as f32, m, 1., t, &voronoi));
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
voronoi,
|
|
||||||
cells_data,
|
|
||||||
seed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum EntityKind {
|
|
||||||
Horse
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum EntityState {
|
|
||||||
Walking(Instant), // Start of walk
|
|
||||||
Resting
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Entity {
|
|
||||||
cell: usize,
|
|
||||||
kind: EntityKind,
|
|
||||||
start: Instant,
|
|
||||||
state: EntityState,
|
|
||||||
health: f32 // between 0 and 1
|
|
||||||
}
|
|
||||||
impl Entity {
|
|
||||||
fn new(cell: usize, kind: EntityKind) -> Self {
|
|
||||||
Self {
|
|
||||||
cell,
|
|
||||||
kind,
|
|
||||||
start: Instant::now(),
|
|
||||||
state: EntityState::Resting,
|
|
||||||
health: 1.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn pos<'a>(&self, map: &'a Map) -> &'a Point {
|
|
||||||
&map.voronoi.sites()[self.cell]
|
|
||||||
}
|
|
||||||
fn render(&self, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, map: &Map) {
|
|
||||||
match self.kind {
|
|
||||||
EntityKind::Horse => {
|
|
||||||
let color = [171./255., 122./255., 50./255., 1.];
|
|
||||||
let dark = [color[0]-0.3, color[1]-0.3, color[2]-0.3, 1.];
|
|
||||||
let (vs, is) = match self.state {
|
|
||||||
EntityState::Walking(now) => {
|
|
||||||
let now = now.elapsed().as_secs_f32()*5.;
|
|
||||||
(
|
|
||||||
[
|
|
||||||
// back left leg
|
|
||||||
Vertex { pos: [-0.5, 0.3], color: dark },
|
|
||||||
Vertex { pos: [-0.4 + (now.sin()*0.1), -0.7 + (now.cos().max(-0.5)*0.1)], color: dark },
|
|
||||||
Vertex { pos: [-0.25, 0.1], color: dark },
|
|
||||||
|
|
||||||
// back right leg
|
|
||||||
Vertex { pos: [-0.5, 0.3], color },
|
|
||||||
Vertex { pos: [-0.4 + ((now + 1.).sin()*0.1), -0.7 + ((now + 1.).cos().max(-0.5)*0.1)], color },
|
|
||||||
Vertex { pos: [-0.25, 0.1], color },
|
|
||||||
|
|
||||||
// front left leg
|
|
||||||
Vertex { pos: [0.3, 0.2], color: dark },
|
|
||||||
Vertex { pos: [0.4 + ((now-1.).sin()*0.1), -0.7 + ((now-1.).cos().max(-0.5)*0.1)], color: dark },
|
|
||||||
Vertex { pos: [0.5, 0.3], color: dark },
|
|
||||||
|
|
||||||
// front right leg
|
|
||||||
Vertex { pos: [0.3, 0.2], color },
|
|
||||||
Vertex { pos: [0.4 + ((now-2.).sin()*0.1), -0.7 + ((now-2.).cos().max(-0.5)*0.1)], color },
|
|
||||||
Vertex { pos: [0.5, 0.3], color },
|
|
||||||
|
|
||||||
// body
|
|
||||||
// 3
|
|
||||||
Vertex { pos: [-0.3, 0.], color },
|
|
||||||
Vertex { pos: [0.4, -0.1], color },
|
|
||||||
// 11
|
|
||||||
Vertex { pos: [0.3, 0.4], color },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,1,2,
|
|
||||||
3,4,5,
|
|
||||||
6,7,8,
|
|
||||||
9,10,11,
|
|
||||||
3,12,13,
|
|
||||||
3,13,11,
|
|
||||||
3,11,14
|
|
||||||
]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
EntityState::Resting => {
|
|
||||||
(
|
|
||||||
[
|
|
||||||
// back left leg
|
|
||||||
Vertex { pos: [-0.5, 0.3], color: dark },
|
|
||||||
Vertex { pos: [-0.4, -0.75], color: dark },
|
|
||||||
Vertex { pos: [-0.25, 0.1], color: dark },
|
|
||||||
|
|
||||||
// back right leg
|
|
||||||
Vertex { pos: [-0.5, 0.3], color },
|
|
||||||
Vertex { pos: [-0.4, -0.75], color },
|
|
||||||
Vertex { pos: [-0.25, 0.1], color },
|
|
||||||
|
|
||||||
// front left leg
|
|
||||||
Vertex { pos: [0.3, 0.2], color: dark },
|
|
||||||
Vertex { pos: [0.4, -0.75], color: dark },
|
|
||||||
Vertex { pos: [0.5, 0.3], color: dark },
|
|
||||||
|
|
||||||
// front right leg
|
|
||||||
Vertex { pos: [0.3, 0.2], color },
|
|
||||||
Vertex { pos: [0.4, -0.75], color },
|
|
||||||
Vertex { pos: [0.5, 0.3], color },
|
|
||||||
|
|
||||||
// body
|
|
||||||
// 3
|
|
||||||
Vertex { pos: [-0.3, 0.], color },
|
|
||||||
Vertex { pos: [0.4, -0.1], color },
|
|
||||||
// 11
|
|
||||||
Vertex { pos: [0.3, 0.4], color },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,1,2,
|
|
||||||
3,4,5,
|
|
||||||
6,7,8,
|
|
||||||
9,10,11,
|
|
||||||
3,12,13,
|
|
||||||
3,13,11,
|
|
||||||
3,11,14
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos = self.pos(map);
|
|
||||||
|
|
||||||
vertices.reserve(vs.len());
|
|
||||||
let base = vertices.len() as u32;
|
|
||||||
for mut v in vs {
|
|
||||||
v.pos[0] = v.pos[0]/50. + pos.x as f32;
|
|
||||||
v.pos[1] = (v.pos[1] + 0.75)/50. + pos.y as f32;
|
|
||||||
vertices.push(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
indices.reserve(is.len());
|
|
||||||
for i in is {
|
|
||||||
indices.push(base + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn update(&mut self, map: &mut Map) {
|
|
||||||
// Let’s take 0.57 kg of grass / m2
|
|
||||||
// Let’s take 7.5 kg of food / day for a horse
|
|
||||||
let r = dbg!(&map.cells_data[self.cell]).ressource();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub vertices: Vec<Vertex>,
|
pub vertices: Vec<Vertex>,
|
||||||
pub indices: Vec<u32>,
|
pub indices: Vec<u32>,
|
||||||
pub uniforms: Uniforms,
|
pub uniforms: Uniforms,
|
||||||
map: Map,
|
map: Map,
|
||||||
entities: Vec<Entity>,
|
|
||||||
start: Instant,
|
start: Instant,
|
||||||
|
last_frame: Instant,
|
||||||
t: usize, // Time in frames
|
t: usize, // Time in frames
|
||||||
selected_tile: usize,
|
selected_tile: usize,
|
||||||
mouse_pressed: bool
|
mouse_pressed: bool,
|
||||||
|
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
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -303,11 +34,16 @@ impl State {
|
|||||||
indices: vec![],
|
indices: vec![],
|
||||||
uniforms: Uniforms::default(),
|
uniforms: Uniforms::default(),
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
|
last_frame: Instant::now(),
|
||||||
t: 0,
|
t: 0,
|
||||||
map: Map::new(0, 0),
|
map: Map::new(0, 0),
|
||||||
entities: Vec::new(),
|
// entities: Vec::new(),
|
||||||
selected_tile: 0,
|
selected_tile: 0,
|
||||||
mouse_pressed: false
|
mouse_pressed: false,
|
||||||
|
framerate: 1.,
|
||||||
|
entities: BTreeMap::new(),
|
||||||
|
next_eid: 0,
|
||||||
|
cells_entities: HashMap::new()
|
||||||
};
|
};
|
||||||
s.render();
|
s.render();
|
||||||
s
|
s
|
||||||
@ -318,10 +54,11 @@ impl State {
|
|||||||
if state.is_pressed() {
|
if state.is_pressed() {
|
||||||
match button {
|
match button {
|
||||||
MouseButton::Left => {
|
MouseButton::Left => {
|
||||||
self.entities.push(Entity::new(self.selected_tile, EntityKind::Horse));
|
|
||||||
self.mouse_pressed = true;
|
self.mouse_pressed = true;
|
||||||
},
|
},
|
||||||
MouseButton::Right => self.map = Map::new(self.map.seed + 1, self.t),
|
MouseButton::Right => {
|
||||||
|
self.spawn_entity(self.selected_tile, Entity::new(EntityKind::Horse, self.selected_tile));
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -353,11 +90,11 @@ impl State {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Event::DeviceEvent { event: DeviceEvent::MouseWheel { delta }, ..} => {
|
Event::DeviceEvent { event: DeviceEvent::MouseWheel { delta }, ..} => {
|
||||||
self.uniforms.camera[2] -= match delta {
|
self.framerate -= match delta {
|
||||||
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
|
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
|
||||||
MouseScrollDelta::LineDelta(_, y) => *y
|
MouseScrollDelta::LineDelta(_, y) => *y
|
||||||
};
|
} * 0.1;
|
||||||
self.uniforms.camera[2] = self.uniforms.camera[2].clamp(0.1, 1.);
|
self.framerate = self.framerate.max(0.);
|
||||||
},
|
},
|
||||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(kc), state, .. }, .. }, .. } => {
|
Event::WindowEvent { event: WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(kc), state, .. }, .. }, .. } => {
|
||||||
if state.is_pressed() {
|
if state.is_pressed() {
|
||||||
@ -374,6 +111,14 @@ impl State {
|
|||||||
KeyCode::KeyD => {
|
KeyCode::KeyD => {
|
||||||
self.uniforms.camera[0] += 0.1;
|
self.uniforms.camera[0] += 0.1;
|
||||||
},
|
},
|
||||||
|
KeyCode::KeyR => {
|
||||||
|
self.uniforms.camera[2] -= 0.1;
|
||||||
|
self.uniforms.camera[2] = self.uniforms.camera[2].clamp(0.1, 1.);
|
||||||
|
},
|
||||||
|
KeyCode::KeyF => {
|
||||||
|
self.uniforms.camera[2] += 0.1;
|
||||||
|
self.uniforms.camera[2] = self.uniforms.camera[2].clamp(0.1, 1.);
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,7 +150,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for e in self.entities.iter() {
|
for e in self.entities.values() {
|
||||||
e.render(&mut self.vertices, &mut self.indices, &self.map);
|
e.render(&mut self.vertices, &mut self.indices, &self.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,19 +174,25 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn update_if_needed(&mut self) {
|
pub fn update_if_needed(&mut self) {
|
||||||
while self.start.elapsed().as_secs_f32() > (self.t as f32) / (FRAMERATE as f32) {
|
while self.last_frame.elapsed().as_secs_f32() > 1. / self.framerate {
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
|
dbg!(self.framerate);
|
||||||
info!("update");
|
info!("update");
|
||||||
|
|
||||||
|
self.last_frame = Instant::now();
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let mut new_kind = Vec::new();
|
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() {
|
for cd in self.map.cells_data.iter() {
|
||||||
if cd.kind == CellKind::Forest || cd.kind == CellKind::Grass {
|
if cd.kind == CellKind::Forest || cd.kind == CellKind::Grass {
|
||||||
let r = rng.gen::<f32>();
|
let r = rng.gen::<f32>();
|
||||||
if r < (0.035*cd.moisture) {
|
if r < (0.035*cd.moisture) {
|
||||||
let c = self.map.voronoi.cell(cd.cell);
|
let c = self.map.voronoi.cell(cd.cid);
|
||||||
let n = c.iter_neighbors().choose(&mut rng).unwrap();
|
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 {
|
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)
|
Some(CellKind::Forest)
|
||||||
@ -457,13 +208,51 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (n, k) in new_kind {
|
for (n, k) in new_kind {
|
||||||
self.map.cells_data[n].kind = k;
|
let cd = &mut self.map.cells_data[n];
|
||||||
|
cd.kind = k;
|
||||||
|
cd.resource = 1.;
|
||||||
}
|
}
|
||||||
|
|
||||||
for e in self.entities.iter_mut() {
|
// if Option is None remove the entity
|
||||||
e.update(&mut self.map);
|
let mut entities_to_move: Vec<(usize, Option<usize>)> = Vec::new();
|
||||||
|
for (eid, e) in self.entities.iter_mut() {
|
||||||
|
match dbg!(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;
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
222
src/state/entity.rs
Normal file
222
src/state/entity.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use rand::{rngs::ThreadRng, seq::IteratorRandom, Rng};
|
||||||
|
use voronoice::Point;
|
||||||
|
|
||||||
|
use crate::graphics::Vertex;
|
||||||
|
|
||||||
|
use super::{map::{CellData, CellKind}, Map};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityKind {
|
||||||
|
Horse
|
||||||
|
}
|
||||||
|
impl EntityKind {
|
||||||
|
const fn is_herbivore(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Horse => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntityState {
|
||||||
|
Walking(Instant), // Start of walk
|
||||||
|
Resting
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ExternOp {
|
||||||
|
Remove,
|
||||||
|
Move(usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Entity {
|
||||||
|
pub cid: usize,
|
||||||
|
kind: EntityKind,
|
||||||
|
start: Instant,
|
||||||
|
state: EntityState,
|
||||||
|
health: f32 // between 0 and 1
|
||||||
|
}
|
||||||
|
impl Entity {
|
||||||
|
pub fn new(kind: EntityKind, cid: usize, ) -> Self {
|
||||||
|
Self {
|
||||||
|
cid,
|
||||||
|
kind,
|
||||||
|
start: Instant::now(),
|
||||||
|
state: EntityState::Walking(Instant::now()),
|
||||||
|
health: 1.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_health(&mut self, val: f32) {
|
||||||
|
self.health = val.clamp(0., 1.);
|
||||||
|
}
|
||||||
|
pub fn render(&self, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, map: &Map) {
|
||||||
|
let pos = &map.voronoi.sites()[self.cid];
|
||||||
|
match self.kind {
|
||||||
|
EntityKind::Horse => {
|
||||||
|
let color = [171./255. * self.health, 122./255. * self.health, 50./255. * self.health, 1.];
|
||||||
|
let dark = [color[0]-0.3, color[1]-0.3, color[2]-0.3, 1.];
|
||||||
|
let (vs, is) = match self.state {
|
||||||
|
EntityState::Walking(now) => {
|
||||||
|
let now = now.elapsed().as_secs_f32()*5.;
|
||||||
|
(
|
||||||
|
[
|
||||||
|
// back left leg
|
||||||
|
Vertex { pos: [-0.5, 0.3], color: dark },
|
||||||
|
Vertex { pos: [-0.4 + (now.sin()*0.1), -0.7 + (now.cos().max(-0.5)*0.1)], color: dark },
|
||||||
|
Vertex { pos: [-0.25, 0.1], color: dark },
|
||||||
|
|
||||||
|
// back right leg
|
||||||
|
Vertex { pos: [-0.5, 0.3], color },
|
||||||
|
Vertex { pos: [-0.4 + ((now + 1.).sin()*0.1), -0.7 + ((now + 1.).cos().max(-0.5)*0.1)], color },
|
||||||
|
Vertex { pos: [-0.25, 0.1], color },
|
||||||
|
|
||||||
|
// front left leg
|
||||||
|
Vertex { pos: [0.3, 0.2], color: dark },
|
||||||
|
Vertex { pos: [0.4 + ((now-1.).sin()*0.1), -0.7 + ((now-1.).cos().max(-0.5)*0.1)], color: dark },
|
||||||
|
Vertex { pos: [0.5, 0.3], color: dark },
|
||||||
|
|
||||||
|
// front right leg
|
||||||
|
Vertex { pos: [0.3, 0.2], color },
|
||||||
|
Vertex { pos: [0.4 + ((now-2.).sin()*0.1), -0.7 + ((now-2.).cos().max(-0.5)*0.1)], color },
|
||||||
|
Vertex { pos: [0.5, 0.3], color },
|
||||||
|
|
||||||
|
// body
|
||||||
|
// 3
|
||||||
|
Vertex { pos: [-0.3, 0.], color },
|
||||||
|
Vertex { pos: [0.4, -0.1], color },
|
||||||
|
// 11
|
||||||
|
Vertex { pos: [0.3, 0.4], color },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,1,2,
|
||||||
|
3,4,5,
|
||||||
|
6,7,8,
|
||||||
|
9,10,11,
|
||||||
|
3,12,13,
|
||||||
|
3,13,11,
|
||||||
|
3,11,14
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
EntityState::Resting => {
|
||||||
|
(
|
||||||
|
[
|
||||||
|
// back left leg
|
||||||
|
Vertex { pos: [-0.5, 0.3], color: dark },
|
||||||
|
Vertex { pos: [-0.4, -0.75], color: dark },
|
||||||
|
Vertex { pos: [-0.25, 0.1], color: dark },
|
||||||
|
|
||||||
|
// back right leg
|
||||||
|
Vertex { pos: [-0.5, 0.3], color },
|
||||||
|
Vertex { pos: [-0.4, -0.75], color },
|
||||||
|
Vertex { pos: [-0.25, 0.1], color },
|
||||||
|
|
||||||
|
// front left leg
|
||||||
|
Vertex { pos: [0.3, 0.2], color: dark },
|
||||||
|
Vertex { pos: [0.4, -0.75], color: dark },
|
||||||
|
Vertex { pos: [0.5, 0.3], color: dark },
|
||||||
|
|
||||||
|
// front right leg
|
||||||
|
Vertex { pos: [0.3, 0.2], color },
|
||||||
|
Vertex { pos: [0.4, -0.75], color },
|
||||||
|
Vertex { pos: [0.5, 0.3], color },
|
||||||
|
|
||||||
|
// body
|
||||||
|
// 3
|
||||||
|
Vertex { pos: [-0.3, 0.], color },
|
||||||
|
Vertex { pos: [0.4, -0.1], color },
|
||||||
|
// 11
|
||||||
|
Vertex { pos: [0.3, 0.4], color },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,1,2,
|
||||||
|
3,4,5,
|
||||||
|
6,7,8,
|
||||||
|
9,10,11,
|
||||||
|
3,12,13,
|
||||||
|
3,13,11,
|
||||||
|
3,11,14
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vertices.reserve(vs.len());
|
||||||
|
let base = vertices.len() as u32;
|
||||||
|
for mut v in vs {
|
||||||
|
v.pos[0] = v.pos[0]/50. + pos.x as f32;
|
||||||
|
v.pos[1] = (v.pos[1] + 0.75)/50. + pos.y as f32;
|
||||||
|
vertices.push(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.reserve(is.len());
|
||||||
|
for i in is {
|
||||||
|
indices.push(base + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns new cell if entity moves
|
||||||
|
pub fn update(&mut self, map: &mut Map, t: usize, rng: &mut ThreadRng) -> Option<ExternOp> {
|
||||||
|
// Let’s take 0.57 kg of grass / m2
|
||||||
|
// Let’s take 7.5 kg of food / day for a horse
|
||||||
|
// Let’s say that a horse can survive up to 20 days without food
|
||||||
|
let cd = &mut map.cells_data[self.cid];
|
||||||
|
if self.kind.is_herbivore() {
|
||||||
|
let food_needed: f32 = match self.kind {
|
||||||
|
EntityKind::Horse => 7.5 / (0.57 * Map::CELL_AREA)
|
||||||
|
}; // in cell resource fraction
|
||||||
|
match cd.kind {
|
||||||
|
CellKind::Forest => {}, // Infinite food in forests
|
||||||
|
CellKind::Grass => {
|
||||||
|
let food_eaten = food_needed.min(cd.resource);
|
||||||
|
self.set_health(self.health - ((((food_needed - food_eaten)/food_needed)-0.5)/20.));
|
||||||
|
cd.set_resource(cd.resource-food_eaten, t);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.set_health(self.health - 1./20.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbg!(self.health) == 0. {
|
||||||
|
return Some(ExternOp::Remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = cd.resource;
|
||||||
|
if cd.kind != CellKind::Grass {
|
||||||
|
map.voronoi.cell(cd.cid).iter_neighbors().filter(|n| {
|
||||||
|
let cd = &map.cells_data[*n];
|
||||||
|
cd.kind == CellKind::Grass
|
||||||
|
}).choose(rng).map(|c| ExternOp::Move(c))
|
||||||
|
} else if r < 0.5 {
|
||||||
|
let cd = &map.cells_data[self.cid];
|
||||||
|
Some(ExternOp::Move(
|
||||||
|
map.voronoi.cell(cd.cid)
|
||||||
|
.iter_neighbors()
|
||||||
|
.filter(|n| map.cells_data[*n].kind == CellKind::Grass)
|
||||||
|
.fold(cd, |acc, c| {
|
||||||
|
let cd = &map.cells_data[c];
|
||||||
|
if acc.kind != CellKind::Grass {
|
||||||
|
cd
|
||||||
|
} else if cd.kind != CellKind::Grass {
|
||||||
|
acc
|
||||||
|
} else if acc.resource > cd.resource {
|
||||||
|
acc
|
||||||
|
} else if acc.resource < cd.resource {
|
||||||
|
cd
|
||||||
|
} else if rng.gen_bool(0.5) {
|
||||||
|
cd
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}).cid
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
src/state/map.rs
Normal file
140
src/state/map.rs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use voronoice::{BoundingBox, Point, Voronoi, VoronoiBuilder};
|
||||||
|
|
||||||
|
use super::entity::Entity;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum CellKind {
|
||||||
|
Void,
|
||||||
|
Sea,
|
||||||
|
Beach,
|
||||||
|
Forest,
|
||||||
|
Dirt,
|
||||||
|
Stone,
|
||||||
|
Grass
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CellData {
|
||||||
|
pub kind: CellKind,
|
||||||
|
pub cid: usize,
|
||||||
|
z: f32,
|
||||||
|
pub moisture: f32,
|
||||||
|
pub resource: f32 // How much resource there is (between 0 and 1)
|
||||||
|
}
|
||||||
|
impl CellData {
|
||||||
|
pub fn new(kind: CellKind, cell: usize, z: f32, moisture: f32, resource: f32, t: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
cid: cell,
|
||||||
|
z,
|
||||||
|
moisture,
|
||||||
|
resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn pos<'a>(&self, map: &'a Map) -> &'a Point {
|
||||||
|
&map.voronoi.sites()[self.cid]
|
||||||
|
}
|
||||||
|
pub fn color(&self) -> [f32; 4] {
|
||||||
|
// let mut rng = thread_rng();
|
||||||
|
// [rng.gen(), rng.gen(), rng.gen(), 1.]
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Void => [0.; 4],
|
||||||
|
CellKind::Sea => [0., 0., 1., 1.],
|
||||||
|
CellKind::Beach => [0.82, 0.84, 0.51, 1.],
|
||||||
|
CellKind::Forest => [0., 0.5 - (self.resource*0.4), 0., 1.],
|
||||||
|
CellKind::Dirt => [0.53 - (self.resource*0.4), 0.38-(self.resource*0.4), 0.29-(self.resource*0.4), 1.],
|
||||||
|
CellKind::Stone => [0.5, 0.5, 0.5, 1.],
|
||||||
|
CellKind::Grass => [(136./255.) - (self.resource*0.4), (204./255.) - (self.resource*0.4), (59./255.) - (self.resource*0.4), 1.]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
// How much it get by day
|
||||||
|
let recuperation_rate = match self.kind {
|
||||||
|
CellKind::Void | CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => 0.,
|
||||||
|
CellKind::Forest => 1. / (100. * 365.25), // Let's say that a forest takes 100 years to mature
|
||||||
|
CellKind::Grass => 1. / (7. * 7.) // Let's say that grass takes 7 weaks to reach its max
|
||||||
|
};
|
||||||
|
self.resource = (self.resource + recuperation_rate).clamp(0., 1.);
|
||||||
|
}
|
||||||
|
pub fn set_resource(&mut self, val: f32, t: usize) {
|
||||||
|
self.resource = val.clamp(0., 1.);
|
||||||
|
if self.resource == 0. {
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Forest => {
|
||||||
|
self.kind = CellKind::Grass;
|
||||||
|
self.resource = 1.;
|
||||||
|
},
|
||||||
|
CellKind::Grass => {
|
||||||
|
self.kind = CellKind::Dirt;
|
||||||
|
self.resource = 0.5;
|
||||||
|
},
|
||||||
|
CellKind::Beach => {
|
||||||
|
self.kind = CellKind::Sea;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Map {
|
||||||
|
pub voronoi: Voronoi,
|
||||||
|
pub cells_data: Vec<CellData>,
|
||||||
|
pub seed: u32
|
||||||
|
}
|
||||||
|
impl Map {
|
||||||
|
pub const HEIGHT: f32 = 2.;
|
||||||
|
pub const WIDTH: f32 = 2.;
|
||||||
|
pub const REAL_HEIGHT: f32 = 500.;
|
||||||
|
pub const REAL_WIDTH: f32 = 500.;
|
||||||
|
pub const CELL_AREA: f32 = Self::REAL_HEIGHT * Self::REAL_WIDTH / Self::SIZE as f32;
|
||||||
|
pub const SIZE: usize = 10000;
|
||||||
|
pub fn new(seed: u32, t: usize) -> Self {
|
||||||
|
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed as u64);
|
||||||
|
let mut sites = Vec::with_capacity(Self::SIZE);
|
||||||
|
for _ in 0..Self::SIZE {
|
||||||
|
sites.push(Point { x:rng.gen_range(-Self::WIDTH/2.0..Self::WIDTH/2.0) as f64, y:rng.gen_range(-Self::HEIGHT/2.0..Self::HEIGHT/2.0) as f64 })
|
||||||
|
}
|
||||||
|
let voronoi = VoronoiBuilder::default()
|
||||||
|
.set_sites(sites)
|
||||||
|
.set_bounding_box(BoundingBox::new_centered(Self::WIDTH as f64, Self::HEIGHT as f64))
|
||||||
|
.set_lloyd_relaxation_iterations(3)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let mut cells_data = Vec::with_capacity(Self::SIZE);
|
||||||
|
let z_noise = Fbm::<Perlin>::new(seed);
|
||||||
|
let moisture_noise = Fbm::<Perlin>::new(seed+1)
|
||||||
|
.set_frequency(2.);
|
||||||
|
for i in 0..Self::SIZE {
|
||||||
|
let c = voronoi.cell(i);
|
||||||
|
let site = c.site_position();
|
||||||
|
let z = (
|
||||||
|
0.3 // Arbitrary value
|
||||||
|
+ ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1]
|
||||||
|
- ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5) // Distance - [0; sqrt(2)] * 0.5
|
||||||
|
).clamp(0., 1.);
|
||||||
|
let m = (
|
||||||
|
(moisture_noise.get([site.x, site.y])+1.)/2. // Noise + [0; 1]
|
||||||
|
).clamp(0., 1.) as f32;
|
||||||
|
let k = if z <= 0.5 {
|
||||||
|
CellKind::Sea
|
||||||
|
} else if z <= 0.52 {
|
||||||
|
CellKind::Beach
|
||||||
|
} else if z < 0.8 {
|
||||||
|
CellKind::Dirt
|
||||||
|
} else {
|
||||||
|
CellKind::Stone
|
||||||
|
};
|
||||||
|
cells_data.push(CellData::new(k, i, z as f32, m, 1., t));
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
voronoi,
|
||||||
|
cells_data,
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/state/ui.rs
Normal file
8
src/state/ui.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
struct UI {
|
||||||
|
|
||||||
|
}
|
||||||
|
impl UI {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user