trying to re-implement chunk_manager
This commit is contained in:
@@ -16,7 +16,7 @@ pub struct Chunk {
|
|||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
/// pos_z and pos_x are `world` coordinates of the chunk, inside the world
|
/// pos_z and pos_x are `world` coordinates of the chunk, inside the world
|
||||||
pub fn new(world_pos_x: f64, world_pos_y: f64, world_pos_z: f64, size: Vector3i) -> Self {
|
pub fn new(world_pos_x: f64, world_pos_y: f64, world_pos_z: f64) -> Self {
|
||||||
Chunk {
|
Chunk {
|
||||||
voxels: vec![0u32; BITPACKED_SIZE],
|
voxels: vec![0u32; BITPACKED_SIZE],
|
||||||
world_position: (world_pos_x, world_pos_y, world_pos_z),
|
world_position: (world_pos_x, world_pos_y, world_pos_z),
|
||||||
@@ -24,10 +24,14 @@ impl Chunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
// #[inline]
|
||||||
pub fn get_voxel_index(&self, local_pos: Vector3i, chunk_size: Vector3i) -> usize {
|
// pub fn get_voxel_index(&self, local_pos: Vector3i, chunk_size: Vector3i) -> usize {
|
||||||
((local_pos.x * chunk_size.y * chunk_size.z) + (local_pos.y * chunk_size.x) + local_pos.z)
|
// ((local_pos.x * chunk_size.y * chunk_size.z) + (local_pos.y * chunk_size.x) + local_pos.z)
|
||||||
as usize
|
// as usize
|
||||||
|
// }
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_voxel_index(local: Vector3i, chunk_size: Vector3i) -> usize {
|
||||||
|
((local.x << 10) | (local.y << 5) | local.z) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -48,16 +52,15 @@ impl Chunk {
|
|||||||
local_pos
|
local_pos
|
||||||
);
|
);
|
||||||
|
|
||||||
let index = self.get_voxel_index(local_pos, chunk_size);
|
let index = Self::get_voxel_index(local_pos, chunk_size);
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
index < ARR_SIZE,
|
index < BITPACKED_SIZE,
|
||||||
"Voxel index {:?} out of chunk bounds",
|
"Voxel index {:?} out of chunk bounds",
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the u32 containing this bit and the bit position within it
|
let word_index = index >> 5; // divide by 32
|
||||||
let word_index = index / chunk_size.x as usize;
|
let bit_index = index & 31; // modulo 32
|
||||||
let bit_index = index % chunk_size.x as usize;
|
|
||||||
|
|
||||||
(self.voxels[word_index] & (1 << bit_index)) != 0
|
(self.voxels[word_index] & (1 << bit_index)) != 0
|
||||||
}
|
}
|
||||||
@@ -68,10 +71,10 @@ impl Chunk {
|
|||||||
"Voxel position {:?} out of chunk bounds",
|
"Voxel position {:?} out of chunk bounds",
|
||||||
voxel_pos
|
voxel_pos
|
||||||
);
|
);
|
||||||
let index = self.get_voxel_index(voxel_pos, chunk_size);
|
let index = Self::get_voxel_index(voxel_pos, chunk_size);
|
||||||
|
|
||||||
let word_index = index / chunk_size.x as usize;
|
let word_index = index >> 5;
|
||||||
let bit_index = index % chunk_size.x as usize;
|
let bit_index = index & 31;
|
||||||
|
|
||||||
if is_solid {
|
if is_solid {
|
||||||
self.voxels[word_index] |= 1 << bit_index;
|
self.voxels[word_index] |= 1 << bit_index;
|
||||||
@@ -80,29 +83,4 @@ impl Chunk {
|
|||||||
}
|
}
|
||||||
self.modified = true;
|
self.modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(&mut self, is_solid: bool) {
|
|
||||||
let fill_value = if is_solid { u32::MAX } else { 0 };
|
|
||||||
self.voxels.fill(fill_value);
|
|
||||||
self.modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position(&self) -> (f64, f64, f64) {
|
|
||||||
self.world_position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_world_bounds(&self, chunk_size: Vector3i) -> (Vector3, Vector3) {
|
|
||||||
let (x, y, z) = self.world_position;
|
|
||||||
let min = Vector3::new(
|
|
||||||
(x * chunk_size.x as f64) as f32,
|
|
||||||
(y * chunk_size.y as f64) as f32,
|
|
||||||
(z * chunk_size.z as f64) as f32,
|
|
||||||
);
|
|
||||||
let max = Vector3::new(
|
|
||||||
((x + 1.0) * chunk_size.x as f64) as f32,
|
|
||||||
((y + 1.0) * chunk_size.y as f64) as f32,
|
|
||||||
((z + 1.0) * chunk_size.z as f64) as f32,
|
|
||||||
);
|
|
||||||
(min, max)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use godot::obj::Gd;
|
|||||||
|
|
||||||
use crate::chunk::{Chunk, ChunkColumn, ChunkMesh};
|
use crate::chunk::{Chunk, ChunkColumn, ChunkMesh};
|
||||||
use crate::editor::voxel_registry::VoxelRegistry;
|
use crate::editor::voxel_registry::VoxelRegistry;
|
||||||
use crate::generation::WorldGenerator;
|
use crate::generation::SimpleSurfaceGenerator;
|
||||||
|
use crate::generation::generator::SurfaceGenerator;
|
||||||
use crate::meshing::Mesher;
|
use crate::meshing::Mesher;
|
||||||
use crate::rendering::Renderer;
|
use crate::rendering::Renderer;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -37,21 +38,18 @@ impl ChunkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_chunk_size(&mut self, chunk_size: Vector3i) {
|
|
||||||
self.chunk_size = chunk_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_around_player(
|
pub fn update_around_player(
|
||||||
&mut self,
|
&mut self,
|
||||||
player_world_pos: Vector3,
|
player_world_pos: Vector3,
|
||||||
render_distance: i32,
|
render_distance: i32,
|
||||||
registry: &VoxelRegistry,
|
registry: &VoxelRegistry,
|
||||||
generator: &dyn WorldGenerator,
|
surface_generator: &dyn SurfaceGenerator,
|
||||||
mesher: &dyn Mesher,
|
mesher: &dyn Mesher,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let player_chunk_x = player_world_pos.x as i32 / self.chunk_size.x;
|
let player_chunk_index_x = (player_world_pos.x as i32).div_euclid(self.chunk_size.x);
|
||||||
let player_chunk_z = player_world_pos.z as i32 / self.chunk_size.z;
|
let player_chunk_index_z = (player_world_pos.z as i32).div_euclid(self.chunk_size.z);
|
||||||
let current_chunk = (player_chunk_x, player_chunk_z);
|
|
||||||
|
let current_chunk = (player_chunk_index_x, player_chunk_index_z);
|
||||||
|
|
||||||
// Early return if player is still in the same chunk
|
// Early return if player is still in the same chunk
|
||||||
if current_chunk == self.last_player_chunk {
|
if current_chunk == self.last_player_chunk {
|
||||||
@@ -67,20 +65,17 @@ impl ChunkManager {
|
|||||||
self.pending_mesh_instances.clear();
|
self.pending_mesh_instances.clear();
|
||||||
|
|
||||||
// Unload distant chunks
|
// Unload distant chunks
|
||||||
self.unload_distant_chunks(player_chunk_x, player_chunk_z, render_distance);
|
self.update_loaded_chunks(
|
||||||
|
player_chunk_index_x,
|
||||||
let after_unload = now.elapsed().as_micros();
|
player_chunk_index_z,
|
||||||
|
|
||||||
// Load new chunks
|
|
||||||
self.load_chunks_around(
|
|
||||||
player_chunk_x,
|
|
||||||
player_chunk_z,
|
|
||||||
render_distance,
|
render_distance,
|
||||||
registry,
|
registry,
|
||||||
generator,
|
surface_generator,
|
||||||
mesher,
|
mesher,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let after_unload = now.elapsed().as_micros();
|
||||||
|
|
||||||
godot_print!(
|
godot_print!(
|
||||||
"Loading {}ms || Unloading {}us, ",
|
"Loading {}ms || Unloading {}us, ",
|
||||||
(now.elapsed().as_micros() - after_unload) / 1000,
|
(now.elapsed().as_micros() - after_unload) / 1000,
|
||||||
@@ -90,72 +85,58 @@ impl ChunkManager {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unload_distant_chunks(&mut self, center_x: i32, center_z: i32, distance: i32) {
|
fn update_loaded_chunks(
|
||||||
let mut to_remove = Vec::new();
|
|
||||||
|
|
||||||
for (&(x, z), _) in &self.chunk_columns {
|
|
||||||
let dx = (x - center_x).abs();
|
|
||||||
let dz = (z - center_z).abs();
|
|
||||||
|
|
||||||
if dx > distance || dz > distance {
|
|
||||||
to_remove.push((x, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (x, z) in to_remove {
|
|
||||||
if let Some(_) = self.chunk_columns.remove(&(x, z)) {
|
|
||||||
// Remove all chunk meshes in this column
|
|
||||||
for y in 0..8 {
|
|
||||||
self.renderer.remove_chunk((x, y, z));
|
|
||||||
}
|
|
||||||
godot_print!("Unloaded chunk column ({}, {})", x, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_chunks_around(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
center_x: i32,
|
center_x: i32,
|
||||||
center_z: i32,
|
center_z: i32,
|
||||||
distance: i32,
|
distance: i32,
|
||||||
registry: &VoxelRegistry,
|
registry: &VoxelRegistry,
|
||||||
generator: &dyn WorldGenerator,
|
generator: &dyn SurfaceGenerator,
|
||||||
mesher: &dyn Mesher,
|
mesher: &dyn Mesher,
|
||||||
) {
|
) {
|
||||||
let mut chunks_loaded = 0;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
let mut desired = HashSet::new();
|
||||||
|
|
||||||
for x in (center_x - distance)..=(center_x + distance) {
|
for x in (center_x - distance)..=(center_x + distance) {
|
||||||
for z in (center_z - distance)..=(center_z + distance) {
|
for z in (center_z - distance)..=(center_z + distance) {
|
||||||
if !self.chunk_columns.contains_key(&(x, z)) {
|
desired.insert((x, z));
|
||||||
self.generate_chunk_column(x, z, registry, generator, mesher);
|
|
||||||
chunks_loaded += 1;
|
|
||||||
godot_print!("Loaded chunk column ({}, {})", x, z)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunks_loaded > 0 {
|
// Unload
|
||||||
godot_print!("Loaded {} new chunk columns", chunks_loaded);
|
self.chunk_columns.retain(|&(x, z), _| {
|
||||||
|
if desired.contains(&(x, z)) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
for y in 0..self.terrain_height {
|
||||||
|
self.renderer.remove_chunk((x, y as i32, z));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load
|
||||||
|
for &(x, z) in &desired {
|
||||||
|
if !self.chunk_columns.contains_key(&(x, z)) {
|
||||||
|
self.generate_chunk_column(x, z, registry, generator, mesher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_chunk_column(
|
pub fn generate_chunk_column(
|
||||||
&mut self,
|
&mut self,
|
||||||
x: i32,
|
index_x: i32,
|
||||||
z: i32,
|
index_z: i32,
|
||||||
registry: &VoxelRegistry,
|
registry: &VoxelRegistry,
|
||||||
generator: &dyn WorldGenerator,
|
generator: &dyn SurfaceGenerator,
|
||||||
mesher: &dyn Mesher,
|
mesher: &dyn Mesher,
|
||||||
) {
|
) {
|
||||||
let mut column = ChunkColumn::new(x, z, self.chunk_size);
|
let mut column = ChunkColumn::new(index_x, index_z, self.chunk_size);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
// Generate all chunks first
|
generator.generate(&mut column);
|
||||||
for i in 0..self.terrain_height {
|
|
||||||
let chunk = column.get_or_create_chunk(i as i32);
|
|
||||||
generator.generate_chunk(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
let generate_elapsed = start.elapsed().as_micros();
|
let generate_elapsed = start.elapsed().as_micros();
|
||||||
godot_print!("Generation took: {}μs", generate_elapsed);
|
godot_print!("Generation took: {}μs", generate_elapsed);
|
||||||
@@ -168,7 +149,7 @@ impl ChunkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert the column only after we're completely done with it
|
// Insert the column only after we're completely done with it
|
||||||
self.chunk_columns.insert((x, z), column);
|
self.chunk_columns.insert((index_x, index_z), column);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mesh_and_render_chunk(
|
fn mesh_and_render_chunk(
|
||||||
@@ -180,6 +161,7 @@ impl ChunkManager {
|
|||||||
let mut mesh = ChunkMesh::new();
|
let mut mesh = ChunkMesh::new();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
mesher.generate_mesh_with_registry(chunk, registry, &mut mesh);
|
mesher.generate_mesh_with_registry(chunk, registry, &mut mesh);
|
||||||
|
|
||||||
if mesh.is_empty() {
|
if mesh.is_empty() {
|
||||||
@@ -207,8 +189,8 @@ impl ChunkManager {
|
|||||||
|
|
||||||
pub fn get_voxel_at(&self, world_pos: Vector3) -> bool {
|
pub fn get_voxel_at(&self, world_pos: Vector3) -> bool {
|
||||||
// Convert world position to chunk coordinates
|
// Convert world position to chunk coordinates
|
||||||
let chunk_x = (world_pos.x / 32.0).floor() as i32;
|
let chunk_x = world_pos.x as i32 / self.chunk_size.x;
|
||||||
let chunk_z = (world_pos.z / 32.0).floor() as i32;
|
let chunk_z = world_pos.z as i32 / self.chunk_size.z;
|
||||||
|
|
||||||
if let Some(column) = self.chunk_columns.get(&(chunk_x, chunk_z)) {
|
if let Some(column) = self.chunk_columns.get(&(chunk_x, chunk_z)) {
|
||||||
// Pass the world position directly - let column handle the conversion
|
// Pass the world position directly - let column handle the conversion
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ use super::chunk::{CHUNK_SIZE, Chunk};
|
|||||||
pub const CHUNKS_PER_COLUMN: usize = 8;
|
pub const CHUNKS_PER_COLUMN: usize = 8;
|
||||||
|
|
||||||
pub struct ChunkColumn {
|
pub struct ChunkColumn {
|
||||||
// from bottom to top
|
|
||||||
// TODO: replace with vec![]
|
|
||||||
pub chunks: [Option<Chunk>; CHUNKS_PER_COLUMN],
|
pub chunks: [Option<Chunk>; CHUNKS_PER_COLUMN],
|
||||||
pub world_position: (i32, i32), // XZ
|
pub world_position: (i32, i32), // XZ
|
||||||
pub chunk_size: Vector3i,
|
pub chunk_size: Vector3i,
|
||||||
@@ -41,9 +39,8 @@ impl ChunkColumn {
|
|||||||
let (world_x, world_z) = self.world_position;
|
let (world_x, world_z) = self.world_position;
|
||||||
self.chunks[chunk_y_index as usize] = Some(Chunk::new(
|
self.chunks[chunk_y_index as usize] = Some(Chunk::new(
|
||||||
world_x as f64,
|
world_x as f64,
|
||||||
chunk_y_index as f64,
|
(chunk_y_index) as f64,
|
||||||
world_z as f64,
|
world_z as f64,
|
||||||
self.chunk_size,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.chunks[chunk_y_index as usize].as_mut().unwrap()
|
self.chunks[chunk_y_index as usize].as_mut().unwrap()
|
||||||
@@ -60,7 +57,7 @@ impl ChunkColumn {
|
|||||||
pub fn set_voxel(&mut self, world_pos: Vector3i, is_solid: bool) -> bool {
|
pub fn set_voxel(&mut self, world_pos: Vector3i, is_solid: bool) -> bool {
|
||||||
let chunk_index = Self::get_chunk_index(self.chunk_size.y, world_pos.y);
|
let chunk_index = Self::get_chunk_index(self.chunk_size.y, world_pos.y);
|
||||||
if chunk_index < CHUNKS_PER_COLUMN {
|
if chunk_index < CHUNKS_PER_COLUMN {
|
||||||
let size = self.chunk_size.clone();
|
let size = self.chunk_size;
|
||||||
let chunk = self.get_or_create_chunk(chunk_index as i32);
|
let chunk = self.get_or_create_chunk(chunk_index as i32);
|
||||||
|
|
||||||
let local_x = world_pos.x.rem_euclid(size.x);
|
let local_x = world_pos.x.rem_euclid(size.x);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use crate::chunk::Chunk;
|
use godot::classes::class_macros::private::virtuals::Os::Vector3i;
|
||||||
|
|
||||||
|
use crate::chunk::{Chunk, ChunkColumn, column};
|
||||||
|
|
||||||
pub trait WorldGenerator: Send + Sync {
|
pub trait WorldGenerator: Send + Sync {
|
||||||
fn generate_chunk(&self, chunk: &mut Chunk);
|
fn generate_chunk(&self, chunk: &mut Chunk);
|
||||||
@@ -6,3 +8,8 @@ pub trait WorldGenerator: Send + Sync {
|
|||||||
fn should_be_solid(&self, density: f32, relative_height: f32) -> bool;
|
fn should_be_solid(&self, density: f32, relative_height: f32) -> bool;
|
||||||
fn get_name(&self) -> &str;
|
fn get_name(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait SurfaceGenerator: Send + Sync {
|
||||||
|
fn generate(&self, column: &mut ChunkColumn);
|
||||||
|
fn sample_voxel(&self, pos: Vector3i) -> bool;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
use super::generator::WorldGenerator;
|
|
||||||
use crate::chunk::Chunk;
|
|
||||||
use fastnoise_lite::*;
|
|
||||||
use godot::classes::class_macros::private::virtuals::Os::Vector3i;
|
|
||||||
|
|
||||||
pub struct HeightmapGenerator {
|
|
||||||
noise: FastNoiseLite,
|
|
||||||
noise_detail: FastNoiseLite,
|
|
||||||
noise_caves: FastNoiseLite,
|
|
||||||
terrain_height: i32,
|
|
||||||
chunk_size: Vector3i,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeightmapGenerator {
|
|
||||||
pub fn new(seed: i64, frequency: f32, terrain_height: i32) -> Self {
|
|
||||||
// Main terrain noise
|
|
||||||
let mut noise = FastNoiseLite::new();
|
|
||||||
noise.set_seed(Some(seed as i32));
|
|
||||||
noise.set_frequency(Some(frequency * 0.5)); // Lower frequency for larger features
|
|
||||||
noise.set_noise_type(Some(fastnoise_lite::NoiseType::OpenSimplex2S));
|
|
||||||
noise.set_fractal_type(Some(fastnoise_lite::FractalType::FBm));
|
|
||||||
noise.set_fractal_octaves(Some(4));
|
|
||||||
noise.set_fractal_lacunarity(Some(2.0));
|
|
||||||
noise.set_fractal_gain(Some(0.5));
|
|
||||||
|
|
||||||
// Detail noise for small features
|
|
||||||
let mut noise_detail = FastNoiseLite::new();
|
|
||||||
noise_detail.set_seed(Some(seed as i32 + 1));
|
|
||||||
noise_detail.set_frequency(Some(frequency * 2.0));
|
|
||||||
noise_detail.set_noise_type(Some(fastnoise_lite::NoiseType::OpenSimplex2S));
|
|
||||||
|
|
||||||
// Cave noise
|
|
||||||
let mut noise_caves = FastNoiseLite::new();
|
|
||||||
noise_caves.set_seed(Some(seed as i32 + 2));
|
|
||||||
noise_caves.set_frequency(Some(frequency * 1.5));
|
|
||||||
noise_caves.set_noise_type(Some(fastnoise_lite::NoiseType::Perlin));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
noise,
|
|
||||||
noise_detail,
|
|
||||||
noise_caves,
|
|
||||||
terrain_height,
|
|
||||||
chunk_size: Vector3i::new(32, 32, 32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorldGenerator for HeightmapGenerator {
|
|
||||||
// fn generate_chunk(&self, chunk: &mut Chunk) {
|
|
||||||
// let (chunk_x, chunk_y, chunk_z) = chunk.world_position;
|
|
||||||
|
|
||||||
// for local_x in 0..CHUNK_SIZE {
|
|
||||||
// for local_z in 0..CHUNK_SIZE {
|
|
||||||
// let world_x = chunk_x * CHUNK_SIZE as f64 + local_x as f64;
|
|
||||||
// let world_z = chunk_z * CHUNK_SIZE as f64 + local_z as f64;
|
|
||||||
|
|
||||||
// let mut noise_value = self.noise.get_noise_2d(world_x as f32, world_z as f32);
|
|
||||||
// noise_value *= 2f32;
|
|
||||||
// let height = ((noise_value + 1.0) * 0.5 * self.terrain_height as f32) as i32;
|
|
||||||
|
|
||||||
// for local_y in 0..=height.min(CHUNK_SIZE - 1) {
|
|
||||||
// let world_y = chunk_y as f64 * CHUNK_SIZE as f64 + local_y as f64;
|
|
||||||
|
|
||||||
// let voxel = if world_y < height as f64 - 3.0 {
|
|
||||||
// Voxel::Stone
|
|
||||||
// } else if world_y < height as f64 - 1.0 {
|
|
||||||
// Voxel::Dirt
|
|
||||||
// } else if world_y <= height as f64 {
|
|
||||||
// Voxel::Grass
|
|
||||||
// } else if world_y < height as f64 + 2.0 {
|
|
||||||
// Voxel::Water
|
|
||||||
// } else {
|
|
||||||
// Voxel::Grass
|
|
||||||
// };
|
|
||||||
// chunk.set_voxel(
|
|
||||||
// Vector3i::new(local_x, local_y, local_z),
|
|
||||||
// true,
|
|
||||||
// Vector3i {
|
|
||||||
// x: 32,
|
|
||||||
// y: 32,
|
|
||||||
// z: 32,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
fn generate_chunk(&self, chunk: &mut Chunk) {
|
|
||||||
let (chunk_x, chunk_y, chunk_z) = chunk.world_position;
|
|
||||||
let chunk_world_y = chunk_y * self.chunk_size.y as f64;
|
|
||||||
|
|
||||||
// Early exit: check if entire chunk is above or below terrain
|
|
||||||
let chunk_min_y = chunk_world_y as f32;
|
|
||||||
let chunk_max_y = (chunk_world_y + self.chunk_size.y as f64) as f32;
|
|
||||||
|
|
||||||
// Pre-calculate all base heights for this chunk (32x32 = 1024 calculations instead of 32768)
|
|
||||||
let mut height_cache = [[0.0f32; 32]; 32];
|
|
||||||
for local_x in 0..self.chunk_size.x {
|
|
||||||
let world_x = (chunk_x * self.chunk_size.x as f64 + local_x as f64) as f32;
|
|
||||||
for local_z in 0..self.chunk_size.z {
|
|
||||||
let world_z = (chunk_z * self.chunk_size.z as f64 + local_z as f64) as f32;
|
|
||||||
height_cache[local_x as usize][local_z as usize] =
|
|
||||||
self.get_base_height(world_x, world_z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we can fill entire chunk
|
|
||||||
let max_height = height_cache
|
|
||||||
.iter()
|
|
||||||
.flat_map(|row| row.iter())
|
|
||||||
.cloned()
|
|
||||||
.fold(f32::MIN, f32::max);
|
|
||||||
if chunk_max_y < max_height - 10.0 {
|
|
||||||
// Entire chunk is underground - fill it
|
|
||||||
chunk.fill(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let min_height = height_cache
|
|
||||||
.iter()
|
|
||||||
.flat_map(|row| row.iter())
|
|
||||||
.cloned()
|
|
||||||
.fold(f32::MAX, f32::min);
|
|
||||||
if chunk_min_y > min_height + 10.0 {
|
|
||||||
// Entire chunk is above terrain - leave as air
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate voxels with cached heights
|
|
||||||
for local_x in 0..self.chunk_size.x {
|
|
||||||
let world_x = (chunk_x * self.chunk_size.x as f64 + local_x as f64) as f32;
|
|
||||||
for local_z in 0..self.chunk_size.z {
|
|
||||||
let world_z = (chunk_z * self.chunk_size.z as f64 + local_z as f64) as f32;
|
|
||||||
let base_height = height_cache[local_x as usize][local_z as usize];
|
|
||||||
|
|
||||||
for local_y in 0..self.chunk_size.y {
|
|
||||||
let world_y = (chunk_world_y + local_y as f64) as f32;
|
|
||||||
let relative_height = world_y - base_height;
|
|
||||||
|
|
||||||
// Use 3D noise for density only when needed
|
|
||||||
let is_solid = if relative_height < -3.0 {
|
|
||||||
// Deep underground - check for caves
|
|
||||||
let cave_noise = self.noise_caves.get_noise_3d(world_x, world_y, world_z);
|
|
||||||
cave_noise < 0.6 // Creates cave systems
|
|
||||||
} else if relative_height < 0.0 {
|
|
||||||
true // Just below surface - always solid
|
|
||||||
} else if relative_height < 15.0 {
|
|
||||||
let density_value = self.noise.get_noise_3d(world_x, world_y, world_z);
|
|
||||||
self.should_be_solid(density_value, relative_height)
|
|
||||||
} else {
|
|
||||||
false // Above terrain
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_solid {
|
|
||||||
chunk.set_voxel(
|
|
||||||
Vector3i::new(local_x, local_y, local_z),
|
|
||||||
true,
|
|
||||||
self.chunk_size,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_base_height(&self, x: f32, z: f32) -> f32 {
|
|
||||||
// Main terrain with multiple octaves (FBm already applied)
|
|
||||||
let height_value = self.noise.get_noise_2d(x, z);
|
|
||||||
|
|
||||||
// Add detail noise for small features
|
|
||||||
let detail = self.noise_detail.get_noise_2d(x, z) * 0.15;
|
|
||||||
|
|
||||||
// Combine and scale - creates hills and valleys like Minecraft
|
|
||||||
let combined = height_value + detail;
|
|
||||||
let height = (combined + 1.0) * 0.5 * self.terrain_height as f32;
|
|
||||||
|
|
||||||
// Add some exponential scaling for more dramatic terrain
|
|
||||||
height.powf(1.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_be_solid(&self, density: f32, relative_height: f32) -> bool {
|
|
||||||
// Base terrain is solid below surface
|
|
||||||
if relative_height < 0.0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create overhangs and floating islands
|
|
||||||
let cave_threshold = 0.35; // Higher = fewer caves
|
|
||||||
if relative_height < 15.0 && density > cave_threshold {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_name(&self) -> &str {
|
|
||||||
"HeightmapGenerator"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
use fastnoise_lite::FastNoiseLite;
|
|
||||||
|
|
||||||
// HIGHTLY EXPERIMENTAL!!!
|
|
||||||
|
|
||||||
pub struct LayeredWorldGenerator {
|
|
||||||
base_noise: FastNoiseLite, // Large scale terrain
|
|
||||||
detail_noise: FastNoiseLite, // Medium details
|
|
||||||
cave_noise: FastNoiseLite, // Cave systems
|
|
||||||
biome_noise: FastNoiseLite, // Biome distribution
|
|
||||||
terrain_height: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayeredWorldGenerator {
|
|
||||||
pub fn new(seed: i64) -> Self {
|
|
||||||
let mut base_noise = FastNoiseLite::new();
|
|
||||||
base_noise.set_seed(Some(seed as i32));
|
|
||||||
base_noise.set_frequency(Some(0.001)); // Very low frequency for continents
|
|
||||||
|
|
||||||
let mut detail_noise = FastNoiseLite::new();
|
|
||||||
detail_noise.set_seed(Some(seed as i32 + 1));
|
|
||||||
detail_noise.set_frequency(Some(0.01)); // Medium frequency for hills
|
|
||||||
|
|
||||||
let mut cave_noise = FastNoiseLite::new();
|
|
||||||
cave_noise.set_seed(Some(seed as i32 + 2));
|
|
||||||
cave_noise.set_frequency(Some(0.05)); // High frequency for small caves
|
|
||||||
|
|
||||||
let mut biome_noise = FastNoiseLite::new();
|
|
||||||
biome_noise.set_seed(Some(seed as i32 + 3));
|
|
||||||
biome_noise.set_frequency(Some(0.0005)); // Very low for large biomes
|
|
||||||
|
|
||||||
Self {
|
|
||||||
base_noise,
|
|
||||||
detail_noise,
|
|
||||||
cave_noise,
|
|
||||||
biome_noise,
|
|
||||||
terrain_height: 64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod heightmap;
|
pub mod simple_heightmap;
|
||||||
pub mod layered_generator;
|
|
||||||
|
|
||||||
pub use generator::WorldGenerator;
|
pub use generator::WorldGenerator;
|
||||||
pub use heightmap::HeightmapGenerator;
|
pub use simple_heightmap::SimpleSurfaceGenerator;
|
||||||
|
|||||||
79
src/generation/simple_heightmap.rs
Normal file
79
src/generation/simple_heightmap.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use fastnoise_lite::{FastNoiseLite, FractalType};
|
||||||
|
use godot::{classes::class_macros::private::virtuals::Os::Vector3i, global::pow};
|
||||||
|
|
||||||
|
use crate::{chunk::ChunkColumn, generation::generator::SurfaceGenerator};
|
||||||
|
|
||||||
|
pub struct SimpleSurfaceGenerator {
|
||||||
|
noise: FastNoiseLite,
|
||||||
|
surface_height: u32,
|
||||||
|
chunk_size: Vector3i,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleSurfaceGenerator {
|
||||||
|
pub fn new(seed: i32, frequency: f32, surface_height: u32, chunk_size: Vector3i) -> Self {
|
||||||
|
let mut noise = FastNoiseLite::new();
|
||||||
|
noise.frequency = frequency;
|
||||||
|
noise.noise_type = fastnoise_lite::NoiseType::OpenSimplex2;
|
||||||
|
noise.seed = seed;
|
||||||
|
noise.set_fractal_type(Some(FractalType::Ridged));
|
||||||
|
noise.set_fractal_octaves(Some(4));
|
||||||
|
Self {
|
||||||
|
noise,
|
||||||
|
surface_height,
|
||||||
|
chunk_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn normalize_value(value: f32) -> f32 {
|
||||||
|
(value + 1.0) * 0.5 // [0, value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceGenerator for SimpleSurfaceGenerator {
|
||||||
|
fn generate(&self, column: &mut ChunkColumn) {
|
||||||
|
let size_x = self.chunk_size.x;
|
||||||
|
let size_z = self.chunk_size.z;
|
||||||
|
|
||||||
|
let base_x = column.world_position.0 * size_x;
|
||||||
|
let base_z = column.world_position.1 * size_z;
|
||||||
|
|
||||||
|
let freq = 1.0 / 32.0;
|
||||||
|
|
||||||
|
let column_height = self.chunk_size.y * self.surface_height as i32;
|
||||||
|
|
||||||
|
for x in 0..self.chunk_size.x {
|
||||||
|
for z in 0..self.chunk_size.z {
|
||||||
|
// world coordinates of this column
|
||||||
|
let world_x = base_x + x;
|
||||||
|
let world_z = base_z + z;
|
||||||
|
|
||||||
|
let noise = self
|
||||||
|
.noise
|
||||||
|
.get_noise_2d(world_x as f32 * freq, world_z as f32 * freq);
|
||||||
|
|
||||||
|
let noise = Self::normalize_value(noise);
|
||||||
|
|
||||||
|
let height = (noise.powi(2) * column_height as f32) as i32;
|
||||||
|
|
||||||
|
for y in 1..=height {
|
||||||
|
column.set_voxel(Vector3i::new(world_x, y, world_z), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn sample_voxel(&self, pos: Vector3i) -> bool {
|
||||||
|
let freq = 1.0 / 32.0;
|
||||||
|
|
||||||
|
let noise = self
|
||||||
|
.noise
|
||||||
|
.get_noise_2d(pos.x as f32 * freq, pos.z as f32 * freq);
|
||||||
|
|
||||||
|
let noise = Self::normalize_value(noise);
|
||||||
|
|
||||||
|
let column_height = self.chunk_size.y * self.surface_height as i32;
|
||||||
|
let height = (noise.powi(2) * column_height as f32) as i32;
|
||||||
|
|
||||||
|
pos.y <= height
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ impl BinaryGreedyMesher {
|
|||||||
|| nz < 0
|
|| nz < 0
|
||||||
|| nz >= size as i32
|
|| nz >= size as i32
|
||||||
{
|
{
|
||||||
true // Outside chunk = air
|
false // Outside chunk = air
|
||||||
} else {
|
} else {
|
||||||
!chunk.get_voxel(
|
!chunk.get_voxel(
|
||||||
Vector3i::new(nx, ny, nz),
|
Vector3i::new(nx, ny, nz),
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl CullingMesher {
|
|||||||
let pos = Vector3::new(x as f32, y as f32, z as f32);
|
let pos = Vector3::new(x as f32, y as f32, z as f32);
|
||||||
|
|
||||||
// Define the 4 vertices of the quad based on face direction
|
// Define the 4 vertices of the quad based on face direction
|
||||||
let (v0, v1, v2, v3) = Self::get_face_vertices(normal, pos);
|
let (v0, v1, v2, v3) = self.get_face_vertices(normal, pos);
|
||||||
|
|
||||||
// Add vertices
|
// Add vertices
|
||||||
mesh.vertices.extend_from_slice(&[v0, v1, v2, v3]);
|
mesh.vertices.extend_from_slice(&[v0, v1, v2, v3]);
|
||||||
@@ -153,8 +153,11 @@ impl CullingMesher {
|
|||||||
base_index,
|
base_index,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
fn get_face_vertices(
|
||||||
fn get_face_vertices(normal: Vector3, pos: Vector3) -> (Vector3, Vector3, Vector3, Vector3) {
|
&self,
|
||||||
|
normal: Vector3,
|
||||||
|
pos: Vector3,
|
||||||
|
) -> (Vector3, Vector3, Vector3, Vector3) {
|
||||||
let half = Vector3::new(0.5, 0.5, 0.5);
|
let half = Vector3::new(0.5, 0.5, 0.5);
|
||||||
let center = pos + half;
|
let center = pos + half;
|
||||||
|
|
||||||
|
|||||||
@@ -165,194 +165,6 @@ impl TexturedMesher {
|
|||||||
Vector2::new(u_min, v_min), // top-left
|
Vector2::new(u_min, v_min), // top-left
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_voxel_to_mesh(
|
|
||||||
&self,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
z: i32,
|
|
||||||
model_index: i32,
|
|
||||||
mesh: &mut ChunkMesh,
|
|
||||||
chunk: &Chunk,
|
|
||||||
registry: &VoxelRegistry,
|
|
||||||
) {
|
|
||||||
if let Some(model_gd) = registry.models.get(model_index as usize) {
|
|
||||||
let model = model_gd.bind();
|
|
||||||
|
|
||||||
// Skip empty models
|
|
||||||
if model.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only handle cube models for now
|
|
||||||
if model.is_cube() {
|
|
||||||
self.add_cube_voxel_to_mesh(x, y, z, &model, mesh, chunk);
|
|
||||||
} else {
|
|
||||||
godot_print!("No model found for index: {}", model_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_cube_voxel_to_mesh(
|
|
||||||
&self,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
z: i32,
|
|
||||||
model: &VoxelModel,
|
|
||||||
mesh: &mut ChunkMesh,
|
|
||||||
chunk: &Chunk,
|
|
||||||
) {
|
|
||||||
let directions = [
|
|
||||||
(Vector3i::new(1, 0, 0), Vector3::RIGHT, 1), // Right face, index 1
|
|
||||||
(Vector3i::new(-1, 0, 0), Vector3::LEFT, 0), // Left face, index 0
|
|
||||||
(Vector3i::new(0, 1, 0), Vector3::UP, 3), // Top face, index 3
|
|
||||||
(Vector3i::new(0, -1, 0), Vector3::DOWN, 2), // Bottom face, index 2
|
|
||||||
(Vector3i::new(0, 0, 1), Vector3::BACK, 4), // Back face, index 4
|
|
||||||
(Vector3i::new(0, 0, -1), Vector3::FORWARD, 5), // Front face, index 5
|
|
||||||
];
|
|
||||||
|
|
||||||
for (offset, normal, face_index) in directions.iter() {
|
|
||||||
let neighbor_pos = Vector3i::new(x + offset.x, y + offset.y, z + offset.z);
|
|
||||||
|
|
||||||
// If neighbor is out of bounds or not solid, create a face
|
|
||||||
if !chunk.is_voxel_within_bounds(
|
|
||||||
neighbor_pos,
|
|
||||||
Vector3i {
|
|
||||||
x: 32,
|
|
||||||
y: 32,
|
|
||||||
z: 32,
|
|
||||||
},
|
|
||||||
) || !chunk.get_voxel(
|
|
||||||
neighbor_pos,
|
|
||||||
Vector3i {
|
|
||||||
x: 32,
|
|
||||||
y: 32,
|
|
||||||
z: 32,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
self.add_textured_face(x, y, z, *normal, *face_index, model, mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_textured_face(
|
|
||||||
&self,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
z: i32,
|
|
||||||
normal: Vector3,
|
|
||||||
face_index: i32,
|
|
||||||
model: &VoxelModel,
|
|
||||||
mesh: &mut ChunkMesh,
|
|
||||||
) {
|
|
||||||
let base_index = mesh.vertices.len() as i32;
|
|
||||||
let pos = Vector3::new(x as f32, y as f32, z as f32);
|
|
||||||
|
|
||||||
// Get face vertices (same as before)
|
|
||||||
let (v0, v1, v2, v3) = self.get_face_vertices(normal, pos);
|
|
||||||
|
|
||||||
// Add vertices
|
|
||||||
mesh.vertices.extend_from_slice(&[v0, v1, v2, v3]);
|
|
||||||
|
|
||||||
// Add normals
|
|
||||||
let normal_vec = Vector3::new(normal.x as f32, normal.y as f32, normal.z as f32);
|
|
||||||
for _ in 0..4 {
|
|
||||||
mesh.normals.push(normal_vec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get texture coordinates for this face
|
|
||||||
let tile_coord = model.get_tile_for_face(face_index);
|
|
||||||
|
|
||||||
// Get atlas size from the model
|
|
||||||
let atlas_size = Vector2i::new(16, 16);
|
|
||||||
|
|
||||||
// Generate UV coordinates for this face
|
|
||||||
let uvs = self.generate_face_uvs(tile_coord, atlas_size);
|
|
||||||
mesh.uvs.extend_from_slice(&uvs);
|
|
||||||
|
|
||||||
// Add indices (two triangles)
|
|
||||||
mesh.indices.extend_from_slice(&[
|
|
||||||
base_index,
|
|
||||||
base_index + 1,
|
|
||||||
base_index + 2,
|
|
||||||
base_index + 2,
|
|
||||||
base_index + 3,
|
|
||||||
base_index,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_face_uvs(&self, tile_coord: Vector2i, atlas_size: Vector2i) -> [Vector2; 4] {
|
|
||||||
let tile_size = Vector2::new(1.0 / atlas_size.x as f32, 1.0 / atlas_size.y as f32);
|
|
||||||
|
|
||||||
let u_min = tile_coord.x as f32 * tile_size.x;
|
|
||||||
let u_max = (tile_coord.x + 1) as f32 * tile_size.x;
|
|
||||||
let v_min = tile_coord.y as f32 * tile_size.y;
|
|
||||||
let v_max = (tile_coord.y + 1) as f32 * tile_size.y;
|
|
||||||
|
|
||||||
// Standard quad UV mapping
|
|
||||||
// Adjust the order based on your vertex winding
|
|
||||||
[
|
|
||||||
Vector2::new(u_min, v_max), // bottom-left
|
|
||||||
Vector2::new(u_max, v_max), // bottom-right
|
|
||||||
Vector2::new(u_max, v_min), // top-right
|
|
||||||
Vector2::new(u_min, v_min), // top-left
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_face_vertices(
|
|
||||||
&self,
|
|
||||||
normal: Vector3,
|
|
||||||
pos: Vector3,
|
|
||||||
) -> (Vector3, Vector3, Vector3, Vector3) {
|
|
||||||
let half = Vector3::new(0.5, 0.5, 0.5);
|
|
||||||
let center = pos + half;
|
|
||||||
|
|
||||||
match (normal.x as i32, normal.y as i32, normal.z as i32) {
|
|
||||||
(1, 0, 0) => (
|
|
||||||
// RIGHT
|
|
||||||
center + Vector3::new(0.5, -0.5, -0.5),
|
|
||||||
center + Vector3::new(0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, 0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, -0.5),
|
|
||||||
),
|
|
||||||
(-1, 0, 0) => (
|
|
||||||
// LEFT
|
|
||||||
center + Vector3::new(-0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(-0.5, -0.5, -0.5),
|
|
||||||
center + Vector3::new(-0.5, 0.5, -0.5),
|
|
||||||
center + Vector3::new(-0.5, 0.5, 0.5),
|
|
||||||
),
|
|
||||||
(0, 1, 0) => (
|
|
||||||
// UP
|
|
||||||
center + Vector3::new(-0.5, 0.5, -0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, -0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, 0.5),
|
|
||||||
center + Vector3::new(-0.5, 0.5, 0.5),
|
|
||||||
),
|
|
||||||
(0, -1, 0) => (
|
|
||||||
// DOWN
|
|
||||||
center + Vector3::new(-0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(0.5, -0.5, -0.5),
|
|
||||||
center + Vector3::new(-0.5, -0.5, -0.5),
|
|
||||||
),
|
|
||||||
(0, 0, 1) => (
|
|
||||||
// BACK
|
|
||||||
center + Vector3::new(0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(-0.5, -0.5, 0.5),
|
|
||||||
center + Vector3::new(-0.5, 0.5, 0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, 0.5),
|
|
||||||
),
|
|
||||||
(0, 0, -1) => (
|
|
||||||
// FORWARD
|
|
||||||
center + Vector3::new(-0.5, -0.5, -0.5),
|
|
||||||
center + Vector3::new(0.5, -0.5, -0.5),
|
|
||||||
center + Vector3::new(0.5, 0.5, -0.5),
|
|
||||||
center + Vector3::new(-0.5, 0.5, -0.5),
|
|
||||||
),
|
|
||||||
_ => panic!("wrong face"), // fallback, shouldn't happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mesher for TexturedMesher {
|
impl Mesher for TexturedMesher {
|
||||||
@@ -368,11 +180,11 @@ impl Mesher for TexturedMesher {
|
|||||||
) {
|
) {
|
||||||
mesh.clear();
|
mesh.clear();
|
||||||
|
|
||||||
let estimated_faces = 4096 / 4 * 3; // ~3000 faces
|
const ESTIMATED_FACES: usize = (32 * 32 * 32) / 4 * 3; // ~24k faces
|
||||||
mesh.vertices.reserve(estimated_faces * 4);
|
mesh.vertices.reserve(ESTIMATED_FACES * 4);
|
||||||
mesh.normals.reserve(estimated_faces * 4);
|
mesh.normals.reserve(ESTIMATED_FACES * 4);
|
||||||
mesh.uvs.reserve(estimated_faces * 4);
|
mesh.uvs.reserve(ESTIMATED_FACES * 4);
|
||||||
mesh.indices.reserve(estimated_faces * 6);
|
mesh.indices.reserve(ESTIMATED_FACES * 6);
|
||||||
|
|
||||||
for x in 0..32 {
|
for x in 0..32 {
|
||||||
for y in 0..32 {
|
for y in 0..32 {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use godot::{
|
|||||||
geometry_instance_3d::ShadowCastingSetting,
|
geometry_instance_3d::ShadowCastingSetting,
|
||||||
mesh::{ArrayType, PrimitiveType},
|
mesh::{ArrayType, PrimitiveType},
|
||||||
},
|
},
|
||||||
|
global::abs,
|
||||||
obj::IndexEnum,
|
obj::IndexEnum,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
@@ -117,6 +118,12 @@ impl Renderer {
|
|||||||
// You can also add some basic properties
|
// You can also add some basic properties
|
||||||
material.set_roughness(0.8);
|
material.set_roughness(0.8);
|
||||||
material.set_metallic(0.0);
|
material.set_metallic(0.0);
|
||||||
|
material.set_albedo(Color {
|
||||||
|
r: (x as i32 % 2) as f32,
|
||||||
|
g: (y as i32 % 2) as f32,
|
||||||
|
b: (z as i32 % 2) as f32,
|
||||||
|
a: 255.0,
|
||||||
|
});
|
||||||
|
|
||||||
// Create mesh instance
|
// Create mesh instance
|
||||||
mesh_instance.set_material_override(&material);
|
mesh_instance.set_material_override(&material);
|
||||||
@@ -195,6 +202,14 @@ impl Renderer {
|
|||||||
|
|
||||||
// Apply material if available (do this without timing)
|
// Apply material if available (do this without timing)
|
||||||
if let Some(mat) = &self.terrain_material {
|
if let Some(mat) = &self.terrain_material {
|
||||||
|
// let mut materi = StandardMaterial3D::new_gd();
|
||||||
|
// materi.set_albedo(Color {
|
||||||
|
// r: (x as i32 % 2) as f32,
|
||||||
|
// g: (y as i32 % 2) as f32,
|
||||||
|
// b: (z as i32 % 2) as f32,
|
||||||
|
// a: 255.0,
|
||||||
|
// });
|
||||||
|
// let ca = materi.upcast::<Material>();
|
||||||
mesh_instance.set_material_override(mat);
|
mesh_instance.set_material_override(mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,8 +220,9 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
for (_, instance) in self.mesh_instances.drain() {
|
for (_, mut instance) in self.mesh_instances.drain() {
|
||||||
self.mesh_instance_pool.push(instance);
|
// self.mesh_instance_pool.push(instance);
|
||||||
|
instance.queue_free();
|
||||||
}
|
}
|
||||||
self.mesh_instances.clear();
|
self.mesh_instances.clear();
|
||||||
}
|
}
|
||||||
@@ -219,8 +235,9 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_chunk(&mut self, chunk_position: (i32, i32, i32)) {
|
pub fn remove_chunk(&mut self, chunk_position: (i32, i32, i32)) {
|
||||||
if let Some(instance) = self.mesh_instances.remove(&chunk_position) {
|
if let Some(mut instance) = self.mesh_instances.remove(&chunk_position) {
|
||||||
self.mesh_instance_pool.push(instance);
|
instance.queue_free();
|
||||||
|
// self.mesh_instance_pool.push(instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ pub mod registry;
|
|||||||
pub mod voxel;
|
pub mod voxel;
|
||||||
|
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
pub use voxel::Voxel;
|
|
||||||
|
|||||||
43
src/world.rs
43
src/world.rs
@@ -3,7 +3,9 @@ use godot::prelude::*;
|
|||||||
|
|
||||||
use crate::chunk::ChunkManager;
|
use crate::chunk::ChunkManager;
|
||||||
use crate::editor::VoxelRegistry;
|
use crate::editor::VoxelRegistry;
|
||||||
use crate::generation::{HeightmapGenerator, WorldGenerator};
|
use crate::generation::SimpleSurfaceGenerator;
|
||||||
|
use crate::generation::generator::SurfaceGenerator;
|
||||||
|
use crate::meshing::binary_greedy_mesher::BinaryGreedyMesher;
|
||||||
use crate::meshing::{Mesher, TexturedMesher};
|
use crate::meshing::{Mesher, TexturedMesher};
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
@@ -43,7 +45,7 @@ pub struct World {
|
|||||||
|
|
||||||
// World data (temporary)
|
// World data (temporary)
|
||||||
chunk_manager: ChunkManager,
|
chunk_manager: ChunkManager,
|
||||||
generator: Box<dyn WorldGenerator>,
|
surface_generator: Box<dyn SurfaceGenerator>,
|
||||||
mesher: Box<dyn Mesher>,
|
mesher: Box<dyn Mesher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +54,17 @@ impl INode3D for World {
|
|||||||
fn init(base: Base<Node3D>) -> Self {
|
fn init(base: Base<Node3D>) -> Self {
|
||||||
godot_print!("🧊 Hello from FastVoxel");
|
godot_print!("🧊 Hello from FastVoxel");
|
||||||
|
|
||||||
let generator = Box::new(HeightmapGenerator::new(1234, 0.03, 5));
|
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
||||||
|
1234,
|
||||||
|
0.3,
|
||||||
|
6,
|
||||||
|
Vector3i {
|
||||||
|
x: 32,
|
||||||
|
y: 32,
|
||||||
|
z: 32,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
let mesher = Box::new(TexturedMesher::new());
|
let mesher = Box::new(TexturedMesher::new());
|
||||||
let chunk_manager = ChunkManager::new();
|
let chunk_manager = ChunkManager::new();
|
||||||
|
|
||||||
@@ -61,9 +73,9 @@ impl INode3D for World {
|
|||||||
chunk_size: Vector3i::new(16, 16, 16),
|
chunk_size: Vector3i::new(16, 16, 16),
|
||||||
world_seed: 1234,
|
world_seed: 1234,
|
||||||
noise_frequency: 0.03,
|
noise_frequency: 0.03,
|
||||||
terrain_height: 5,
|
terrain_height: 8,
|
||||||
base,
|
base,
|
||||||
generator,
|
surface_generator,
|
||||||
mesher,
|
mesher,
|
||||||
chunk_manager,
|
chunk_manager,
|
||||||
workder_threads: 4,
|
workder_threads: 4,
|
||||||
@@ -89,12 +101,13 @@ impl INode3D for World {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
godot_print!("ChunkSize is {0}", self.chunk_size);
|
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
||||||
self.generator = Box::new(HeightmapGenerator::new(
|
self.world_seed,
|
||||||
self.get_world_seed() as i64,
|
|
||||||
self.noise_frequency,
|
self.noise_frequency,
|
||||||
self.terrain_height as i32,
|
self.terrain_height,
|
||||||
|
self.chunk_size,
|
||||||
));
|
));
|
||||||
|
self.surface_generator = surface_generator;
|
||||||
self.chunk_manager.chunk_size = self.chunk_size;
|
self.chunk_manager.chunk_size = self.chunk_size;
|
||||||
self.chunk_manager.terrain_height = self.terrain_height;
|
self.chunk_manager.terrain_height = self.terrain_height;
|
||||||
|
|
||||||
@@ -115,13 +128,13 @@ impl World {
|
|||||||
let distance = self.render_distance as i32;
|
let distance = self.render_distance as i32;
|
||||||
let library_ref = registry.bind();
|
let library_ref = registry.bind();
|
||||||
|
|
||||||
for x in -distance..=distance {
|
for index_x in -distance..=distance {
|
||||||
for z in -distance..=distance {
|
for index_z in -distance..=distance {
|
||||||
self.chunk_manager.generate_chunk_column(
|
self.chunk_manager.generate_chunk_column(
|
||||||
x,
|
index_x,
|
||||||
z,
|
index_z,
|
||||||
&library_ref,
|
&library_ref,
|
||||||
self.generator.as_ref(),
|
self.surface_generator.as_ref(),
|
||||||
self.mesher.as_ref(),
|
self.mesher.as_ref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -163,7 +176,7 @@ impl World {
|
|||||||
player_world_pos,
|
player_world_pos,
|
||||||
self.render_distance as i32,
|
self.render_distance as i32,
|
||||||
®istry_ref,
|
®istry_ref,
|
||||||
self.generator.as_ref(),
|
self.surface_generator.as_ref(),
|
||||||
self.mesher.as_ref(),
|
self.mesher.as_ref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/world/world_manager.rs
Normal file
3
src/world/world_manager.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub struct WorldManager {
|
||||||
|
chunks: HashMap<(i32, i32), Chunk>,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user