Init
This commit is contained in:
@@ -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
@@ -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!();
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user