mirror of
https://github.com/osmarks/ewo3.git
synced 2025-01-06 23:40:36 +00:00
Fix rivers and lakes
This commit is contained in:
parent
561063f953
commit
7977ed4d10
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/node_modules
|
/node_modules
|
||||||
out.png
|
out.png
|
||||||
|
world.bin
|
26
Cargo.lock
generated
26
Cargo.lock
generated
@ -56,6 +56,25 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.0-rc.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
|
||||||
|
dependencies = [
|
||||||
|
"bincode_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode_derive"
|
||||||
|
version = "2.0.0-rc.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c"
|
||||||
|
dependencies = [
|
||||||
|
"virtue",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@ -203,6 +222,7 @@ name = "ewo3"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"euclid",
|
"euclid",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -869,6 +889,12 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtue"
|
||||||
|
version = "0.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -21,6 +21,7 @@ noise-functions = "0.2"
|
|||||||
indexmap = "2"
|
indexmap = "2"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
|
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "worldgen"
|
name = "worldgen"
|
||||||
|
61
src/main.rs
61
src/main.rs
@ -183,6 +183,9 @@ struct Energy { current: f32, regeneration_rate: f32, burst: f32 }
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Drops(Vec<(Item, StochasticNumber)>);
|
struct Drops(Vec<(Item, StochasticNumber)>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Jump(i64);
|
||||||
|
|
||||||
impl Energy {
|
impl Energy {
|
||||||
fn try_consume(&mut self, cost: f32) -> bool {
|
fn try_consume(&mut self, cost: f32) -> bool {
|
||||||
if self.current >= -1e-12 { // numerics
|
if self.current >= -1e-12 { // numerics
|
||||||
@ -245,6 +248,7 @@ struct EnemySpec {
|
|||||||
move_delay: usize,
|
move_delay: usize,
|
||||||
attack_cooldown: u64,
|
attack_cooldown: u64,
|
||||||
ranged: bool,
|
ranged: bool,
|
||||||
|
movement: i64,
|
||||||
drops: Vec<(Item, StochasticNumber)>
|
drops: Vec<(Item, StochasticNumber)>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,14 +256,14 @@ impl EnemySpec {
|
|||||||
// Numbers ported from original EWO. Fudge constants added elsewhere.
|
// Numbers ported from original EWO. Fudge constants added elsewhere.
|
||||||
fn random() -> EnemySpec {
|
fn random() -> EnemySpec {
|
||||||
match fastrand::usize(0..650) {
|
match fastrand::usize(0..650) {
|
||||||
0..=99 => EnemySpec { symbol: 'I', min_damage: 10.0, damage_range: 5.0, initial_health: 50.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![] }, // IBIS
|
0..=99 => EnemySpec { symbol: 'I', min_damage: 10.0, damage_range: 5.0, initial_health: 50.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // IBIS
|
||||||
100..=199 => EnemySpec { symbol: 'K', min_damage: 5.0, damage_range: 15.0, initial_health: 30.0, move_delay: 40, attack_cooldown: 10, ranged: false, drops: vec![] }, // KESTREL
|
100..=199 => EnemySpec { symbol: 'K', min_damage: 5.0, damage_range: 25.0, initial_health: 60.0, move_delay: 30, attack_cooldown: 12, ranged: false, drops: vec![], movement: 2 }, // KANGAROO
|
||||||
200..=299 => EnemySpec { symbol: 'S', min_damage: 5.0, damage_range: 5.0, initial_health: 20.0, move_delay: 50, attack_cooldown: 10, ranged: false, drops: vec![] }, // SNAKE
|
200..=299 => EnemySpec { symbol: 'S', min_damage: 5.0, damage_range: 5.0, initial_health: 20.0, move_delay: 50, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // SNAKE
|
||||||
300..=399 => EnemySpec { symbol: 'E', min_damage: 10.0, damage_range: 20.0, initial_health: 80.0, move_delay: 80, attack_cooldown: 10, ranged: false, drops: vec![] }, // EMU
|
300..=399 => EnemySpec { symbol: 'E', min_damage: 10.0, damage_range: 20.0, initial_health: 80.0, move_delay: 80, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // EMU
|
||||||
400..=499 => EnemySpec { symbol: 'O', min_damage: 8.0, damage_range: 17.0, initial_health: 150.0, move_delay: 100, attack_cooldown: 10, ranged: false, drops: vec![] }, // OGRE
|
400..=499 => EnemySpec { symbol: 'O', min_damage: 8.0, damage_range: 17.0, initial_health: 150.0, move_delay: 100, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // OGRE
|
||||||
500..=599 => EnemySpec { symbol: 'R', min_damage: 5.0, damage_range: 5.0, initial_health: 15.0, move_delay: 40, attack_cooldown: 10, ranged: false, drops: vec![] }, // RAT
|
500..=599 => EnemySpec { symbol: 'R', min_damage: 5.0, damage_range: 5.0, initial_health: 15.0, move_delay: 40, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // RAT
|
||||||
600..=609 => EnemySpec { symbol: 'M' , min_damage: 20.0, damage_range: 10.0, initial_health: 150.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![] }, // MOA
|
600..=609 => EnemySpec { symbol: 'M' , min_damage: 20.0, damage_range: 10.0, initial_health: 150.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![], movement: 1 }, // MOA
|
||||||
610..=649 => EnemySpec { symbol: 'P', min_damage: 10.0, damage_range: 5.0, initial_health: 15.0, move_delay: 20, attack_cooldown: 10, ranged: true, drops: vec![] }, // PLATYPUS
|
610..=649 => EnemySpec { symbol: 'P', min_damage: 10.0, damage_range: 5.0, initial_health: 15.0, move_delay: 20, attack_cooldown: 10, ranged: true, drops: vec![], movement: 1 }, // PLATYPUS
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,7 +369,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
|||||||
Collidable,
|
Collidable,
|
||||||
DespawnRandomly(RANDOM_DESPAWN_INV_RATE),
|
DespawnRandomly(RANDOM_DESPAWN_INV_RATE),
|
||||||
Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 },
|
Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 },
|
||||||
Drops(spec.drops)
|
Drops(spec.drops),
|
||||||
|
Jump(spec.movement)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
buffer.spawn((
|
buffer.spawn((
|
||||||
@ -378,7 +383,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
|||||||
Collidable,
|
Collidable,
|
||||||
DespawnRandomly(RANDOM_DESPAWN_INV_RATE),
|
DespawnRandomly(RANDOM_DESPAWN_INV_RATE),
|
||||||
Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 },
|
Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 },
|
||||||
Drops(spec.drops)
|
Drops(spec.drops),
|
||||||
|
Jump(spec.movement)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,7 +392,7 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process enemy motion and ranged attacks
|
// Process enemy motion and ranged attacks
|
||||||
for (entity, (pos, ranged, energy)) in state.world.query::<hecs::With<(&Position, Option<&mut RangedAttack>, Option<&mut Energy>), &Enemy>>().iter() {
|
for (entity, (pos, ranged, energy, jump)) in state.world.query::<hecs::With<(&Position, Option<&mut RangedAttack>, Option<&mut Energy>, Option<&Jump>), &Enemy>>().iter() {
|
||||||
let pos = pos.head();
|
let pos = pos.head();
|
||||||
|
|
||||||
for direction in DIRECTIONS.iter() {
|
for direction in DIRECTIONS.iter() {
|
||||||
@ -433,8 +439,18 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let direction = DIRECTIONS.iter().min_by_key(|dir| hex_distance(pos + **dir, target_pos)).unwrap();
|
let direction = *DIRECTIONS.iter().min_by_key(|dir| hex_distance(pos + **dir, target_pos)).unwrap();
|
||||||
buffer.insert_one(entity, MovingInto(pos + *direction));
|
let max_movement_distance = jump.map(|j| j.0).unwrap_or(1);
|
||||||
|
let mut best_scale = 1;
|
||||||
|
let mut best_distance = hex_distance(pos + direction, target_pos);
|
||||||
|
for i in 1..=max_movement_distance {
|
||||||
|
let new_distance = hex_distance(pos + direction * i, target_pos);
|
||||||
|
if new_distance < best_distance {
|
||||||
|
best_distance = new_distance;
|
||||||
|
best_scale = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.insert_one(entity, MovingInto(pos + direction * best_scale));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// wander randomly (ethical)
|
// wander randomly (ethical)
|
||||||
@ -529,6 +545,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
|||||||
for (entity, (current_pos, MovingInto(target_pos), damage, mut energy, move_cost, despawn_on_impact)) in state.world.query::<(&mut Position, &MovingInto, Option<&mut Attack>, Option<&mut Energy>, Option<&MoveCost>, Option<&DespawnOnImpact>)>().iter() {
|
for (entity, (current_pos, MovingInto(target_pos), damage, mut energy, move_cost, despawn_on_impact)) in state.world.query::<(&mut Position, &MovingInto, Option<&mut Attack>, Option<&mut Energy>, Option<&MoveCost>, Option<&DespawnOnImpact>)>().iter() {
|
||||||
let mut move_cost = move_cost.map(|x| x.0.sample()).unwrap_or(0.0);
|
let mut move_cost = move_cost.map(|x| x.0.sample()).unwrap_or(0.0);
|
||||||
|
|
||||||
|
move_cost *= (hex_distance(*target_pos, current_pos.head()) as f32).powf(0.5);
|
||||||
|
|
||||||
for tile in current_pos.0.iter() {
|
for tile in current_pos.0.iter() {
|
||||||
// TODO: perhaps large enemies should not be exponentially more vulnerable to environmental hazards
|
// TODO: perhaps large enemies should not be exponentially more vulnerable to environmental hazards
|
||||||
if let Some(current_terrain) = terrain_positions.get(tile) {
|
if let Some(current_terrain) = terrain_positions.get(tile) {
|
||||||
@ -703,15 +721,30 @@ fn add_new_player(state: &mut GameState) -> Result<Entity> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_world() -> Result<worldgen::GeneratedWorld> {
|
||||||
|
let data = tokio::fs::read("world.bin").await?;
|
||||||
|
Ok(bincode::serde::decode_from_slice(&data, bincode::config::standard())?.0)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let addr = std::env::args().nth(1).unwrap_or_else(|| "0.0.0.0:8080".to_string());
|
let addr = std::env::args().nth(1).unwrap_or_else(|| "0.0.0.0:8080".to_string());
|
||||||
|
|
||||||
|
let world = match load_world().await {
|
||||||
|
Ok(world) => world,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to load world, generating new one: {:?}", e);
|
||||||
|
let world = worldgen::generate_world();
|
||||||
|
tokio::fs::write("world.bin", bincode::serde::encode_to_vec(&world, bincode::config::standard())?).await?;
|
||||||
|
world
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(GameState {
|
let state = Arc::new(Mutex::new(GameState {
|
||||||
world: World::new(),
|
world: World::new(),
|
||||||
clients: Slab::new(),
|
clients: Slab::new(),
|
||||||
ticks: 0,
|
ticks: 0,
|
||||||
map: worldgen::generate_world()
|
map: world
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let try_socket = TcpListener::bind(&addr).await;
|
let try_socket = TcpListener::bind(&addr).await;
|
||||||
|
@ -10,9 +10,13 @@ pub fn to_cubic(p0: Coord) -> CubicCoord {
|
|||||||
CubicCoord::new(p0.x, p0.y, -p0.x - p0.y)
|
CubicCoord::new(p0.x, p0.y, -p0.x - p0.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vec_length(ax_dist: CoordVec) -> i64 {
|
||||||
|
(ax_dist.x.abs() + ax_dist.y.abs() + (ax_dist.x + ax_dist.y).abs()) / 2
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hex_distance(p0: Coord, p1: Coord) -> i64 {
|
pub fn hex_distance(p0: Coord, p1: Coord) -> i64 {
|
||||||
let ax_dist = p0 - p1;
|
let ax_dist = p0 - p1;
|
||||||
(ax_dist.x.abs() + ax_dist.y.abs() + (ax_dist.x + ax_dist.y).abs()) / 2
|
vec_length(ax_dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_axis(p: CoordVec) -> bool {
|
pub fn on_axis(p: CoordVec) -> bool {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::{cmp::Ordering, collections::{hash_map::Entry, BinaryHeap, HashMap, HashSet}, hash::{Hash, Hasher}, ops::{Index, IndexMut}};
|
use std::{cmp::Ordering, collections::{hash_map::Entry, BinaryHeap, HashMap, HashSet, VecDeque}, hash::{Hash, Hasher}, ops::{Index, IndexMut}};
|
||||||
|
|
||||||
use noise_functions::Sample3;
|
use noise_functions::Sample3;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::map::*;
|
use crate::map::*;
|
||||||
|
|
||||||
pub const WORLD_RADIUS: i64 = 1024;
|
pub const WORLD_RADIUS: i64 = 1024;
|
||||||
@ -300,13 +299,56 @@ pub fn compute_humidity(distances: Map<f32>, heightmap: &Map<f32>) -> Map<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SEA_LEVEL: f32 = -0.8;
|
const SEA_LEVEL: f32 = -0.8;
|
||||||
const EROSION: f32 = 0.05;
|
const EROSION: f32 = 0.09;
|
||||||
|
const EROSION_EXPONENT: f32 = 1.5;
|
||||||
|
|
||||||
|
fn floodfill(src: Coord, all: &HashSet<Coord>) -> HashSet<Coord> {
|
||||||
|
let mut out = HashSet::new();
|
||||||
|
let mut queue = VecDeque::new();
|
||||||
|
queue.push_back(src);
|
||||||
|
out.insert(src);
|
||||||
|
while let Some(coord) = queue.pop_front() {
|
||||||
|
for offset in DIRECTIONS {
|
||||||
|
let neighbor = coord + *offset;
|
||||||
|
if all.contains(&neighbor) && !out.contains(&neighbor) {
|
||||||
|
queue.push_back(neighbor);
|
||||||
|
out.insert(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simulate_water(heightmap: &mut Map<f32>) -> Map<f32> {
|
pub fn simulate_water(heightmap: &mut Map<f32>) -> Map<f32> {
|
||||||
let mut watermap = Map::<f32>::new(heightmap.radius, 0.0);
|
let mut watermap = Map::<f32>::new(heightmap.radius, 0.0);
|
||||||
|
|
||||||
let sources = generate_separated_high_points(WATER_SOURCES, WORLD_RADIUS / 10, &heightmap);
|
let sources = generate_separated_high_points(WATER_SOURCES, WORLD_RADIUS / 10, &heightmap);
|
||||||
let sinks = heightmap.iter_coords().filter(|(c, _)| heightmap[*c] <= SEA_LEVEL).map(|(c, _)| c).collect::<HashSet<_>>();
|
let sinks = heightmap.iter_coords().filter(|(c, _)| heightmap[*c] <= SEA_LEVEL).map(|(c, _)| c).collect::<HashSet<_>>();
|
||||||
|
let mut remainder = sinks.clone();
|
||||||
|
let sea = floodfill(Coord::new(0, WORLD_RADIUS), &sinks);
|
||||||
|
|
||||||
|
for s in sea.iter() {
|
||||||
|
remainder.remove(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lakes = vec![];
|
||||||
|
loop {
|
||||||
|
let next = remainder.iter().next();
|
||||||
|
match next {
|
||||||
|
Some(s) => {
|
||||||
|
let lake = floodfill(*s, &remainder);
|
||||||
|
for l in lake.iter() {
|
||||||
|
remainder.remove(l);
|
||||||
|
}
|
||||||
|
lakes.push(lake);
|
||||||
|
},
|
||||||
|
None => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for sink in sinks.iter() {
|
||||||
|
watermap[*sink] = 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
for source in sources.iter() {
|
for source in sources.iter() {
|
||||||
let heightmap_ = &*heightmap;
|
let heightmap_ = &*heightmap;
|
||||||
@ -316,7 +358,7 @@ pub fn simulate_water(heightmap: &mut Map<f32>) -> Map<f32> {
|
|||||||
let neighbor = c + *offset;
|
let neighbor = c + *offset;
|
||||||
if heightmap_.in_range(neighbor) {
|
if heightmap_.in_range(neighbor) {
|
||||||
let factor = if watermap_[neighbor] > 0.0 { 0.1 } else { 1.0 };
|
let factor = if watermap_[neighbor] > 0.0 { 0.1 } else { 1.0 };
|
||||||
Some((neighbor, factor * (heightmap_[neighbor] - heightmap_[c] + 0.00).max(0.0)))
|
Some((neighbor, factor * (heightmap_[neighbor] - heightmap_[c] + 0.0001).max(0.0)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -325,7 +367,13 @@ pub fn simulate_water(heightmap: &mut Map<f32>) -> Map<f32> {
|
|||||||
let heuristic = |c: Coord| {
|
let heuristic = |c: Coord| {
|
||||||
(heightmap[c] * 0.00).max(0.0)
|
(heightmap[c] * 0.00).max(0.0)
|
||||||
};
|
};
|
||||||
let path = astar(*source, |c| sinks.contains(&c), heuristic, get_neighbours);
|
let mut path = astar(*source, |c| sinks.contains(&c), heuristic, get_neighbours);
|
||||||
|
|
||||||
|
let end = path.last().unwrap();
|
||||||
|
if !sea.contains(end) {
|
||||||
|
// route lake to sea
|
||||||
|
path.extend(astar(*end, |c| sea.contains(&c), heuristic, get_neighbours));
|
||||||
|
}
|
||||||
|
|
||||||
for point in path {
|
for point in path {
|
||||||
let water_range_raw = watermap[point] * 1.0 - heightmap[point];
|
let water_range_raw = watermap[point] * 1.0 - heightmap[point];
|
||||||
@ -338,24 +386,20 @@ pub fn simulate_water(heightmap: &mut Map<f32>) -> Map<f32> {
|
|||||||
watermap[point + nearby] = watermap[point + nearby].min(3.0);
|
watermap[point + nearby] = watermap[point + nearby].min(3.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let erosion_range_raw = water_range_raw * 2.0 + 2.0;
|
let erosion_range_raw = (water_range_raw * 2.0 + 2.0).powf(EROSION_EXPONENT);
|
||||||
let erosion_range = erosion_range_raw.ceil() as i64;
|
let erosion_range = erosion_range_raw.ceil() as i64;
|
||||||
for (this_range, nearby) in hex_range(erosion_range) {
|
for (this_range, nearby) in hex_range(erosion_range) {
|
||||||
if !watermap.in_range(point + nearby) {
|
if !watermap.in_range(point + nearby) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if this_range > 0 {
|
if this_range > 0 {
|
||||||
heightmap[point + nearby] -= EROSION * watermap[point] / (this_range as f32) / erosion_range_raw.max(1.0);
|
heightmap[point + nearby] -= EROSION * watermap[point] / (this_range as f32) / erosion_range_raw.max(1.0).powf(EROSION_EXPONENT);
|
||||||
heightmap[point + nearby] = heightmap[point + nearby].max(SEA_LEVEL);
|
heightmap[point + nearby] = heightmap[point + nearby].max(SEA_LEVEL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for sink in sinks {
|
|
||||||
watermap[sink] = 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
watermap
|
watermap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +36,11 @@ fn main() -> Result<()> {
|
|||||||
for (position, value) in heightmap.iter() {
|
for (position, value) in heightmap.iter() {
|
||||||
let col = position.x + (position.y - (position.y & 1)) / 2 + WORLD_RADIUS;
|
let col = position.x + (position.y - (position.y & 1)) / 2 + WORLD_RADIUS;
|
||||||
let row = position.y + WORLD_RADIUS;
|
let row = position.y + WORLD_RADIUS;
|
||||||
//let contour = contour_points.get(&position).copied().unwrap_or_default();
|
let height = ((*value + 1.0) * 127.5) as u8;
|
||||||
let contour = (255.0 * humidity[position]) as u8;
|
let contour = contour_points.get(&position).copied().unwrap_or_default();
|
||||||
|
//let contour = (255.0 * humidity[position]) as u8;
|
||||||
let water = water[position];
|
let water = water[position];
|
||||||
image.put_pixel(col as u32, row as u32, Rgb::from([contour, 0, (water.min(1.0) * 255.0) as u8]));
|
image.put_pixel(col as u32, row as u32, Rgb::from([contour, height, (water.min(1.0) * 255.0) as u8]));
|
||||||
}
|
}
|
||||||
|
|
||||||
image.save("./out.png")?;
|
image.save("./out.png")?;
|
||||||
|
Loading…
Reference in New Issue
Block a user