entities are well managed (and die lol)
This commit is contained in:
parent
83f76015d4
commit
f54ca4ed19
@ -1,6 +1,5 @@
|
||||
mod graphics;
|
||||
mod state;
|
||||
use std::time::Duration;
|
||||
|
||||
use state::State;
|
||||
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 noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
||||
use map::{CellKind, Map};
|
||||
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 crate::graphics::{Uniforms, Vertex};
|
||||
|
||||
const FRAMERATE: usize = 1; // Update per second
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum CellKind {
|
||||
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();
|
||||
|
||||
}
|
||||
}
|
||||
mod entity;
|
||||
mod map;
|
||||
mod ui;
|
||||
use entity::{Entity, EntityKind, ExternOp};
|
||||
|
||||
pub struct State {
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub indices: Vec<u32>,
|
||||
pub uniforms: Uniforms,
|
||||
map: Map,
|
||||
entities: Vec<Entity>,
|
||||
start: Instant,
|
||||
last_frame: Instant,
|
||||
t: usize, // Time in frames
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
@ -303,11 +34,16 @@ impl State {
|
||||
indices: vec![],
|
||||
uniforms: Uniforms::default(),
|
||||
start: Instant::now(),
|
||||
last_frame: Instant::now(),
|
||||
t: 0,
|
||||
map: Map::new(0, 0),
|
||||
entities: Vec::new(),
|
||||
// entities: Vec::new(),
|
||||
selected_tile: 0,
|
||||
mouse_pressed: false
|
||||
mouse_pressed: false,
|
||||
framerate: 1.,
|
||||
entities: BTreeMap::new(),
|
||||
next_eid: 0,
|
||||
cells_entities: HashMap::new()
|
||||
};
|
||||
s.render();
|
||||
s
|
||||
@ -318,10 +54,11 @@ impl State {
|
||||
if state.is_pressed() {
|
||||
match button {
|
||||
MouseButton::Left => {
|
||||
self.entities.push(Entity::new(self.selected_tile, EntityKind::Horse));
|
||||
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 {
|
||||
@ -353,11 +90,11 @@ impl State {
|
||||
}
|
||||
},
|
||||
Event::DeviceEvent { event: DeviceEvent::MouseWheel { delta }, ..} => {
|
||||
self.uniforms.camera[2] -= match delta {
|
||||
self.framerate -= match delta {
|
||||
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
|
||||
MouseScrollDelta::LineDelta(_, y) => *y
|
||||
};
|
||||
self.uniforms.camera[2] = self.uniforms.camera[2].clamp(0.1, 1.);
|
||||
} * 0.1;
|
||||
self.framerate = self.framerate.max(0.);
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(kc), state, .. }, .. }, .. } => {
|
||||
if state.is_pressed() {
|
||||
@ -374,6 +111,14 @@ impl State {
|
||||
KeyCode::KeyD => {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -429,19 +174,25 @@ impl State {
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self) {
|
||||
dbg!(self.framerate);
|
||||
info!("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.cell);
|
||||
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)
|
||||
@ -457,13 +208,51 @@ impl State {
|
||||
}
|
||||
}
|
||||
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() {
|
||||
e.update(&mut self.map);
|
||||
// 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 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;
|
||||
}
|
||||
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