This commit is contained in:
Paul Brinkmeier
2025-11-29 11:25:25 +01:00
commit c00dd3d37a
9 changed files with 1447 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
use std::ops::{Add, Div, Sub};
#[derive(Clone, Copy, Debug, PartialEq)]
struct P2 {
x: i32,
y: i32,
}
impl P2 {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl Add<V2> for P2 {
type Output = P2;
fn add(self, rhs: V2) -> Self::Output {
P2::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl Sub<P2> for P2 {
type Output = V2;
fn sub(self, rhs: P2) -> Self::Output {
V2::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl Sub<V2> for P2 {
type Output = P2;
fn sub(self, rhs: V2) -> Self::Output {
P2::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl Div<i32> for P2 {
type Output = P2;
fn div(self, rhs: i32) -> Self::Output {
Self::new(self.x / rhs, self.y / rhs)
}
}
#[derive(Debug)]
struct V2 {
x: i32,
y: i32,
}
impl V2 {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl Div<i32> for V2 {
type Output = V2;
fn div(self, rhs: i32) -> Self::Output {
V2::new(self.x / rhs, self.y / rhs)
}
}
+29
View File
@@ -0,0 +1,29 @@
use std::cmp::max;
mod geometry;
mod noise;
mod terrain;
use noise::Noise;
fn main() {
let width = 50;
let height = 20;
let long_side = max(width, height) as f32;
let mut rand = rand::rng();
let height_noise = Noise::from_freqs(&mut rand, 12, 16, 3);
let tree_cover_noise = Noise::from_freqs(&mut rand, 12, 16, 4);
for y in 0..height {
for x in 0..width {
let h = height_noise.sample(x as f32 / long_side, y as f32 / long_side);
let t = tree_cover_noise.sample(x as f32 / long_side, y as f32 / long_side);
let terrain = terrain::terrain(h, t);
print!("{}", terrain.printable());
}
println!();
}
}
+87
View File
@@ -0,0 +1,87 @@
use rand::{Rng, rngs::ThreadRng};
pub struct Noise {
levels: Vec<Grid<f32>>,
}
impl Noise {
pub fn sample(&self, x: f32, y: f32) -> f32 {
let mut s = 0.0;
let mut max_possible = 0.0;
for (i, level) in self.levels.iter().enumerate() {
let f = 1.0 / 2.0_f32.powi(i as i32);
s += f * interpolate_bilinear(level, x, y);
max_possible += f;
}
s / max_possible
}
pub fn from_freqs(rand: &mut ThreadRng, width: usize, height: usize, n_levels: i32) -> Noise {
Noise {
levels: (0..n_levels)
.map(|i| {
let m = 2_usize.pow(i as u32);
Grid::from_fn(m * width, m * height, |_x, _y| rand.random_range(0.0..1.0))
})
.collect(),
}
}
}
fn interpolate_bilinear(level: &Grid<f32>, uni_x: f32, uni_y: f32) -> f32 {
let x = uni_x * ((level.width - 1) as f32);
let y = uni_y * ((level.height - 1) as f32);
let ox1 = x as usize;
let oy1 = y as usize;
let ox2 = if ox1 == level.width - 1 { ox1 } else { ox1 + 1 };
let oy2 = if oy1 == level.height - 1 {
oy1
} else {
oy1 + 1
};
let top = linear(
level.get_at(ox1, oy1),
level.get_at(ox2, oy1),
x - (ox1 as f32),
);
let bottom = linear(
level.get_at(ox1, oy2),
level.get_at(ox2, oy2),
x - (ox1 as f32),
);
linear(top, bottom, y - (oy1 as f32))
}
fn linear(a: f32, b: f32, f: f32) -> f32 {
a + f * (b - a)
}
struct Grid<T: Copy> {
width: usize,
height: usize,
elements: Vec<T>,
}
impl<T: Copy> Grid<T> {
fn from_fn(width: usize, height: usize, mut f: impl FnMut(usize, usize) -> T) -> Self {
let mut elements = Vec::new();
for y in 0..height {
for x in 0..width {
elements.push(f(x, y));
}
}
Self {
width,
height,
elements,
}
}
fn get_at(&self, x: usize, y: usize) -> T {
self.elements[y * self.height + x]
}
}
+55
View File
@@ -0,0 +1,55 @@
use crossterm::style::Stylize;
pub enum Terrain {
DeepWater,
ShallowWater,
Beach,
Plains(bool),
Mountain(bool),
MountainTop,
}
impl Terrain {
pub fn printable(&self) -> String {
match self {
Terrain::DeepWater => ".".blue().on_dark_blue().to_string(),
Terrain::ShallowWater => " ".on_blue().to_string(),
Terrain::Beach => ".".grey().on_yellow().to_string(),
Terrain::Plains(has_trees) => {
if *has_trees {
"Q".dark_green().on_green().to_string()
} else {
",".on_green().to_string()
}
}
Terrain::Mountain(has_trees) => {
if *has_trees {
"Q".dark_green().on_grey().to_string()
} else {
" ".on_grey().to_string()
}
}
Terrain::MountainTop => " ".on_white().to_string(),
}
}
}
pub fn terrain(height: f32, tree_cover: f32) -> Terrain {
let has_trees = tree_cover > 0.6;
if height < 0.3 {
Terrain::DeepWater
} else if height < 0.35 {
Terrain::ShallowWater
} else if height < 0.4 {
Terrain::Beach
} else if height < 0.6 {
Terrain::Plains(has_trees)
} else if height < 0.65 {
Terrain::Mountain(has_trees)
} else if height < 0.8 {
Terrain::Mountain(false)
} else {
Terrain::MountainTop
}
}