Added generation of images based on the map

Tweaks to consistency with DIRECTIONS and HEX_CORNERS
Misc debug visualizations
This commit is contained in:
2024-08-04 21:45:23 -04:00
parent bde25435ec
commit f7a3a56a0a
13 changed files with 671 additions and 21 deletions

View File

@@ -18,6 +18,7 @@ bevy_asset_loader = { version = "0.21.0", features = [
"3d",
] }
ron = "0.8.1"
image = "0.25.2"
[features]
tracing = ["bevy/trace_tracy"]

View File

@@ -31,7 +31,7 @@ fn create_tile_collider(pos: Vec3, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32
verts.push(p);
}
//Top Surfave
//Top Surface
indices.push([idx, idx + 1, idx + 5]);
indices.push([idx + 1, idx + 2, idx + 5]);
indices.push([idx + 2, idx + 4, idx + 5]);
@@ -43,7 +43,7 @@ fn create_tile_collider(pos: Vec3, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32
create_tile_wall_collider(
idx,
Vec3::new(pos.x, n_height.min(pos.y - OUTER_RADIUS / 2.), pos.z),
i,
(i + 1) % 6,
verts,
indices,
);
@@ -54,7 +54,7 @@ fn create_tile_collider(pos: Vec3, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32
fn create_tile_wall_collider(idx: u32, pos: Vec3, dir: usize, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32; 3]>) {
let idx2 = verts.len() as u32;
verts.push(pos + HEX_CORNERS[dir]);
verts.push(pos + HEX_CORNERS[(dir) % 6]);
verts.push(pos + HEX_CORNERS[(dir + 1) % 6]);
let off = dir as u32;

View File

@@ -1,5 +1,8 @@
use core::f32;
use bevy::math::{IVec2, UVec2};
use bevy::prelude::{FloatExt, Vec2};
use bevy::render::render_resource::encase::internal::BufferMut;
use bevy::utils::default;
use noise::{NoiseFn, SuperSimplex};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
@@ -11,7 +14,7 @@ use crate::prelude::*;
pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32, painter: &BiomePainter) -> Map {
let biomes = &generate_biomes(cfg, seed, painter);
// let mut chunks: Vec<Chunk> = Vec::with_capacity(cfg.size.length_squared() as usize);
let chunks = (0..cfg.size.y)
let chunks: Vec<Chunk> = (0..cfg.size.y)
.into_par_iter()
.flat_map(|z| {
(0..cfg.size.x).into_par_iter().map(move |x| {
@@ -20,11 +23,24 @@ pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32, painter: &BiomePain
})
})
.collect();
let mut min = f32::MAX;
let mut max = f32::MIN;
for chunk in &chunks {
if chunk.min_level < min {
min = chunk.min_level;
}
if chunk.max_level > max {
max = chunk.max_level;
}
}
return Map {
chunks,
height: cfg.size.y as usize,
width: cfg.size.x as usize,
sea_level: cfg.sea_level as f32,
min_level: min,
max_level: max,
};
}
@@ -66,7 +82,7 @@ pub fn generate_biome_chunk(
&cfg.moisture_noise,
&noise_m,
cfg.size.as_vec2(),
cfg.border_size,
0.0,
);
let temperature = sample_point(
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
@@ -74,7 +90,7 @@ pub fn generate_biome_chunk(
&cfg.temperature_noise,
&noise_t,
cfg.size.as_vec2(),
cfg.border_size,
0.0,
);
let continentality = sample_point(
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
@@ -82,7 +98,7 @@ pub fn generate_biome_chunk(
&cfg.continent_noise,
&noise_c,
cfg.size.as_vec2(),
cfg.border_size,
0.0,
);
let data = BiomeData {
moisture: moisture.clamp(0., 100.),
@@ -100,6 +116,29 @@ pub fn generate_biome_chunk(
return chunk;
}
pub fn generate_noise_map(size: UVec2, seed: u32, cfg: &NoiseConfig, border_size: f32) -> Vec<f32> {
let noise = SuperSimplex::new(seed);
let data: Vec<_> = (0..(size.y as usize * Chunk::SIZE))
.into_par_iter()
.flat_map(|y| {
let mut row = Vec::with_capacity(size.x as usize * Chunk::SIZE);
for x in 0..row.capacity() {
row.push(sample_point(
x as f64,
y as f64,
cfg,
&noise,
size.as_vec2(),
border_size,
));
}
return row;
})
.collect();
return data;
}
pub fn generate_chunk(
chunk_x: u32,
chunk_z: u32,
@@ -112,6 +151,8 @@ pub fn generate_chunk(
let mut data = [BiomeData::default(); Chunk::AREA];
let mut biome_ids = [0; Chunk::AREA];
let noise = SuperSimplex::new(seed);
let mut min = f32::MAX;
let mut max = f32::MIN;
for z in 0..Chunk::SIZE {
for x in 0..Chunk::SIZE {
let biome_data = biome_chunk.get_biome_data(x, z);
@@ -135,6 +176,12 @@ pub fn generate_chunk(
let idx = x + z * Chunk::SIZE;
biome_ids[idx] = biome_chunk.get_biome_id_dithered(x, z, &noise, cfg.biome_dither);
result[idx] = sample;
if sample > max {
max = sample;
}
if sample < min {
min = sample;
}
data[idx] = biome_data.clone();
}
}
@@ -143,6 +190,8 @@ pub fn generate_chunk(
biome_data: data,
biome_id: biome_ids,
chunk_offset: IVec2::new(chunk_x as i32, chunk_z as i32),
max_level: max,
min_level: min,
..default()
};
}
@@ -171,6 +220,10 @@ fn sample_point(x: f64, z: f64, cfg: &NoiseConfig, noise: &impl NoiseFn<f64, 2>,
}
}
if border_size == 0.0 {
return elevation as f32;
}
let outer = size * Chunk::SIZE as f32;
let p = Vec2::new(x as f32, z as f32);

View File

@@ -72,12 +72,12 @@ impl Display for HexCoord {
impl HexCoord {
pub const DIRECTIONS: [IVec3; 6] = [
IVec3::new(0, 1, -1),
IVec3::new(1, 0, -1),
IVec3::new(1, -1, 0),
IVec3::new(0, -1, 1),
IVec3::new(-1, 0, 1),
IVec3::new(1, 0, -1),
IVec3::new(0, 1, -1),
IVec3::new(-1, 1, 0),
IVec3::new(-1, 0, 1),
IVec3::new(0, -1, 1),
];
pub const ZERO: HexCoord = HexCoord { hex: IVec3::ZERO };

View File

@@ -4,6 +4,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use super::chunk::Chunk;
#[derive(Clone)]
pub struct BiomeMap {
pub height: usize,
pub width: usize,

View File

@@ -10,6 +10,8 @@ pub struct Chunk {
pub biome_data: [BiomeData; Chunk::AREA],
pub biome_id: [usize; Chunk::AREA],
pub chunk_offset: IVec2,
pub min_level: f32,
pub max_level: f32,
}
impl Default for Chunk {
@@ -20,6 +22,8 @@ impl Default for Chunk {
biome_data: [BiomeData::default(); Chunk::AREA],
biome_id: [0; Chunk::AREA],
chunk_offset: Default::default(),
min_level: 0.0,
max_level: 0.0,
}
}
}

View File

@@ -2,7 +2,11 @@ use bevy::prelude::*;
use crate::hex_utils::*;
use super::{chunk::Chunk, mesh_chunk::MeshChunkData};
use super::{
biome_map::{BiomeData, BiomeMap},
chunk::Chunk,
mesh_chunk::MeshChunkData,
};
#[derive(Resource, Clone)]
pub struct Map {
@@ -10,6 +14,8 @@ pub struct Map {
pub height: usize,
pub width: usize,
pub sea_level: f32,
pub min_level: f32,
pub max_level: f32,
}
impl Map {
@@ -43,11 +49,21 @@ impl Map {
}
pub fn sample_height(&self, pos: &HexCoord) -> f32 {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
return chunk.heights[pos.to_chunk_local_index()];
}
pub fn sample_height_mut(&mut self, pos: &HexCoord) -> &mut f32 {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &mut self.chunks[pos.to_chunk_index(self.width)];
return &mut chunk.heights[pos.to_chunk_local_index()];
}
@@ -56,16 +72,46 @@ impl Map {
return pos.is_in_bounds(self.height * Chunk::SIZE, self.width * Chunk::SIZE);
}
pub fn get_biome(&self, pos: &HexCoord) -> &BiomeData {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
return &chunk.biome_data[pos.to_chunk_local_index()];
}
pub fn get_moisture(&self, pos: &HexCoord) -> f32 {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
return chunk.biome_data[pos.to_chunk_local_index()].moisture;
}
pub fn get_tempurature(&self, pos: &HexCoord) -> f32 {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
return chunk.biome_data[pos.to_chunk_local_index()].temperature;
}
pub fn get_continentality(&self, pos: &HexCoord) -> f32 {
assert!(
self.is_in_bounds(pos),
"The provided coordinate is not within the map bounds"
);
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
return chunk.biome_data[pos.to_chunk_local_index()].continentality;
}
pub fn get_center(&self) -> Vec3 {
let w = self.get_world_width();
let h = self.get_world_height();
@@ -96,7 +142,7 @@ impl Map {
let h2 = cur - depth;
*h = h2.lerp(cur, d * d).max(0.);
return (*p, *h);
return (*p, *h);
});
return tiles;

View File

@@ -0,0 +1,115 @@
use bevy::{math::VectorSpace, prelude::*};
use image::ImageBuffer;
use rayon::prelude::*;
use crate::hex_utils::HexCoord;
use super::{biome_map::BiomeMap, chunk::Chunk, map::Map};
pub fn render_image(
size: UVec2,
data: &Vec<f32>,
color1: LinearRgba,
color2: LinearRgba,
) -> ImageBuffer<image::Rgb<u8>, Vec<u8>> {
let mut image = ImageBuffer::new(size.x * Chunk::SIZE as u32, size.y * Chunk::SIZE as u32);
update_image(size, data, color1, color2, &mut image);
return image;
}
pub fn update_image(
size: UVec2,
data: &Vec<f32>,
color1: LinearRgba,
color2: LinearRgba,
image: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
) {
let min = *data.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or(&0.0);
let max = *data.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or(&1.0);
let w = size.x * Chunk::SIZE as u32;
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
let idx = (y * w + x) as usize;
let v = data[idx];
let t = v.remap(min, max, 0.0, 1.0);
let col = LinearRgba::lerp(&color1, color2, t);
*pixel = to_pixel(&col);
});
}
fn to_pixel(col: &LinearRgba) -> image::Rgb<u8> {
return image::Rgb([
(col.red * 255.0) as u8,
(col.green * 255.0) as u8,
(col.blue * 255.0) as u8,
]);
}
pub fn render_map(map: &Map, smooth: f32) -> ImageBuffer<image::Rgb<u8>, Vec<u8>> {
let mut image = ImageBuffer::new(
map.width as u32 * Chunk::SIZE as u32,
map.height as u32 * Chunk::SIZE as u32,
);
update_map(map, smooth, &mut image);
return image;
}
pub fn update_map(map: &Map, smooth: f32, image: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>) {
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
let coord = HexCoord::from_grid_pos(x as usize, y as usize);
let right = coord.get_neighbor(1);
let height = map.sample_height(&coord);
let mut color = Hsla::hsl(138.0, 1.0, 0.4);
if height < map.sea_level {
color.hue = 217.0;
}
if map.is_in_bounds(&right) {
let h2 = map.sample_height(&right);
let mut d = h2 - height;
if smooth == 0.0 || d.abs() > smooth {
if d > 0.0 {
color.lightness += 0.1;
} else if d < 0.0 {
color.lightness -= 0.1;
}
} else {
if d.abs() <= smooth {
d /= smooth;
if d > 0.0 {
let c2: LinearRgba = color.with_lightness(color.lightness + 0.1).into();
color = LinearRgba::lerp(&color.into(), c2, d).into();
} else {
let c2: LinearRgba = color.with_lightness(color.lightness - 0.1).into();
color = LinearRgba::lerp(&color.into(), c2, d.abs()).into();
}
}
}
}
*pixel = to_pixel(&color.into());
});
}
pub fn render_biome_map(map: &Map) -> ImageBuffer<image::Rgb<u8>, Vec<u8>> {
let mut image = ImageBuffer::new(
map.width as u32 * Chunk::SIZE as u32,
map.height as u32 * Chunk::SIZE as u32,
);
update_biome_map(map, &mut image);
return image;
}
pub fn update_biome_map(map: &Map, image: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>) {
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
let coord = HexCoord::from_grid_pos(x as usize, y as usize);
let tile = map.get_biome(&coord);
let color = LinearRgba::rgb(
tile.temperature / 100.0,
tile.continentality / 100.0,
tile.moisture / 100.0,
);
*pixel = to_pixel(&color);
});
}

View File

@@ -11,7 +11,7 @@ impl MeshChunkData {
pub fn get_neighbors(&self, coord: &HexCoord) -> [f32; 6] {
let mut data = [0.; 6];
let n_tiles = coord.get_neighbors();
for i in 0..6 {
for i in 6..0 {
let n = n_tiles[i];
if !n.is_in_bounds(Chunk::SIZE, Chunk::SIZE) {
continue;

View File

@@ -2,4 +2,5 @@ pub mod chunk;
pub mod mesh_chunk;
pub mod config;
pub mod map;
pub mod biome_map;
pub mod biome_map;
pub mod map_utils;