re-working the editor nodes
de-coupling godot resources from internal runtime logic
This commit is contained in:
30
README.md
30
README.md
@@ -4,22 +4,26 @@
|
||||
<img src="./logo-cropped.svg" alt="FastVoxel logo" width="180" />
|
||||
</p>
|
||||
|
||||
FastVoxel is a voxel terrain engine for Godot written as a Rust GDExtension. The goal is basically: generate chunks quickly, keep voxel storage lightweight, and build meshes at runtime for blocky worlds with textured materials.
|
||||
**FastVoxel** is a **Voxel Engine** for Godot written as a Rust GDExtension. The goal is basically: generate chunks quickly, keep voxel storage lightweight, and build meshes at runtime for blocky worlds with textured materials.
|
||||
|
||||
This repo contains the engine/plugin side of the project.
|
||||
This repo contains the engine/GDExtension side of the project.
|
||||
|
||||
It's currently being refactored a bit, so some internal structure may change, but the main ideas and APIs are stable enough to explain here.
|
||||
It's currently being refactored constantly, so some internal structure may change, but the main ideas and APIs are stable enough to explain here. (or Are They?)
|
||||
|
||||
## Highlights
|
||||
|
||||
- Rust-based Godot 4 GDExtension
|
||||
- chunked voxel terrain pipeline
|
||||
- chunked voxel mesh pipeline
|
||||
- bit-packed voxel storage (solid / air)
|
||||
- procedural terrain using `fastnoise-lite`
|
||||
- chunk streaming around the player
|
||||
- runtime cube meshing with texture atlas support
|
||||
- Godot editor resources for config + voxel registry
|
||||
|
||||
## Soon:
|
||||
|
||||
- Multi-Threaded chunk meshing and world generation using a `Work Stealing` Thread pool with divide‑and‑conquer parallelism.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
@@ -63,6 +67,12 @@ Rough pipeline looks like this:
|
||||
|
||||
The idea is to keep generation, storage, and meshing fairly modular so different strategies can be swapped in later.
|
||||
|
||||
## What FastVoxel "Doesn't" do
|
||||
|
||||
1. The engine doesn't use compute shaders or any kind of GPU accelerated meshing algorithem.
|
||||
2. The engine doesn't bake per-vertex AO on greedy mesher (because it complicates things and I'm running on two brain cells at the moment)
|
||||
3. The engine doesn't cull the neighboring faces because it has no access to the data of adjacent chunks. (I'm working on it).
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Chunked world layout
|
||||
@@ -73,7 +83,7 @@ Current defaults:
|
||||
|
||||
- `CHUNK_SIZE = 32`
|
||||
- each chunk = `32 × 32 × 32` voxels
|
||||
- columns stack multiple chunks vertically
|
||||
- columns stack multiple chunks vertically like a hamburger
|
||||
- chunk loading/unloading happens around the player based on render distance
|
||||
|
||||
Relevant files:
|
||||
@@ -84,14 +94,18 @@ Relevant files:
|
||||
|
||||
### Bit-packed voxel storage
|
||||
|
||||
Instead of storing a struct per voxel, chunks store occupancy using packed `u32` blocks.
|
||||
Instead of storing a struct per voxel, chunks store occupancy using packed `u32` blocks. since a voxel is represented by a single **bit**, a single `u32` can store the state of `32 voxels`.
|
||||
|
||||
So right now a voxel is basically:
|
||||
right now a voxel is basically:
|
||||
|
||||
- `0` -> air
|
||||
- `1` -> solid
|
||||
|
||||
This keeps memory usage low and makes lookups very cheap. It works well for early terrain prototypes and simple block worlds.
|
||||
This keeps memory usage low and makes lookups very cheap.
|
||||
|
||||
For a 32 × 32 × 32 chunk, each horizontal row of 32 voxels fits into a single u32. Which means a layer of the chunk requires 32 u32s, and the entire chunk can be stored as a 32 × 32 array of u32s. Each u32 corresponds to one row of voxels along the X axis.
|
||||
|
||||
This compact memory layout works well for terrain meshing stage, where the main concern is to quickly check whether voxels are empty or solid. because we use a single bit to represent a voxel instead of a full struct or enum or whatever, the implementation is 32× more memory efficient (no sh- sherlock).
|
||||
|
||||
Material differences are currently handled at the meshing/registry layer instead of inside the voxel storage itself.
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ use godot::prelude::*;
|
||||
|
||||
pub const CHUNK_SIZE: i32 = 32;
|
||||
pub const ARR_SIZE: usize = CHUNK_SIZE.pow(3) as usize;
|
||||
|
||||
// 32x32x32 = 32768 bits, stored in u32s (32 bits each) = 1024 u32s
|
||||
pub const BITPACKED_SIZE: usize = ARR_SIZE / 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Chunk {
|
||||
pub struct SubChunk {
|
||||
// Bitpacked voxel data: 0 = air, 1 = solid
|
||||
pub voxels: Vec<u32>,
|
||||
// WORLD COORDINATES
|
||||
@@ -14,10 +15,10 @@ pub struct Chunk {
|
||||
modified: bool,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
impl SubChunk {
|
||||
/// 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) -> Self {
|
||||
Chunk {
|
||||
SubChunk {
|
||||
voxels: vec![0u32; BITPACKED_SIZE],
|
||||
world_position: (world_pos_x, world_pos_y, world_pos_z),
|
||||
modified: false,
|
||||
@@ -30,7 +31,7 @@ impl Chunk {
|
||||
// as usize
|
||||
// }
|
||||
#[inline(always)]
|
||||
fn get_voxel_index(local: Vector3i, chunk_size: Vector3i) -> usize {
|
||||
fn get_voxel_index(local: Vector3i) -> usize {
|
||||
((local.x << 10) | (local.y << 5) | local.z) as usize
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ impl Chunk {
|
||||
local_pos
|
||||
);
|
||||
|
||||
let index = Self::get_voxel_index(local_pos, chunk_size);
|
||||
let index = Self::get_voxel_index(local_pos);
|
||||
debug_assert!(
|
||||
index < BITPACKED_SIZE,
|
||||
"Voxel index {:?} out of chunk bounds",
|
||||
@@ -71,7 +72,7 @@ impl Chunk {
|
||||
"Voxel position {:?} out of chunk bounds",
|
||||
voxel_pos
|
||||
);
|
||||
let index = Self::get_voxel_index(voxel_pos, chunk_size);
|
||||
let index = Self::get_voxel_index(voxel_pos);
|
||||
|
||||
let word_index = index >> 5;
|
||||
let bit_index = index & 31;
|
||||
|
||||
@@ -3,17 +3,16 @@ use godot::classes::class_macros::private::virtuals::Os::{Vector3, Vector3i};
|
||||
use godot::global::godot_print;
|
||||
use godot::obj::Gd;
|
||||
|
||||
use crate::chunk::{Chunk, ChunkColumn, ChunkMesh};
|
||||
use crate::chunk::{Chunk, ChunkMesh, SubChunk};
|
||||
use crate::editor::voxel_registry::VoxelRegistry;
|
||||
use crate::generation::SimpleSurfaceGenerator;
|
||||
use crate::generation::generator::SurfaceGenerator;
|
||||
use crate::generation::generator::TerrainGenerator;
|
||||
use crate::meshing::Mesher;
|
||||
use crate::rendering::Renderer;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct ChunkManager {
|
||||
pub chunk_columns: HashMap<(i32, i32), ChunkColumn>,
|
||||
pub chunk_columns: HashMap<(i32, i32), Chunk>,
|
||||
pub renderer: Renderer,
|
||||
pub last_player_chunk: (i32, i32),
|
||||
|
||||
@@ -43,7 +42,7 @@ impl ChunkManager {
|
||||
player_world_pos: Vector3,
|
||||
render_distance: i32,
|
||||
registry: &VoxelRegistry,
|
||||
surface_generator: &dyn SurfaceGenerator,
|
||||
surface_generator: &dyn TerrainGenerator,
|
||||
mesher: &dyn Mesher,
|
||||
) -> bool {
|
||||
let player_chunk_index_x = (player_world_pos.x as i32).div_euclid(self.chunk_size.x);
|
||||
@@ -91,7 +90,7 @@ impl ChunkManager {
|
||||
center_z: i32,
|
||||
distance: i32,
|
||||
registry: &VoxelRegistry,
|
||||
generator: &dyn SurfaceGenerator,
|
||||
generator: &dyn TerrainGenerator,
|
||||
mesher: &dyn Mesher,
|
||||
) {
|
||||
use std::collections::HashSet;
|
||||
@@ -129,21 +128,21 @@ impl ChunkManager {
|
||||
index_x: i32,
|
||||
index_z: i32,
|
||||
registry: &VoxelRegistry,
|
||||
generator: &dyn SurfaceGenerator,
|
||||
generator: &dyn TerrainGenerator,
|
||||
mesher: &dyn Mesher,
|
||||
) {
|
||||
let mut column = ChunkColumn::new(index_x, index_z, self.chunk_size);
|
||||
let mut column = Chunk::new(index_x, index_z, self.chunk_size);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
generator.generate(&mut column);
|
||||
generator.sample_chunk(&mut column);
|
||||
|
||||
let generate_elapsed = start.elapsed().as_micros();
|
||||
godot_print!("Generation took: {}μs", generate_elapsed);
|
||||
|
||||
// Mesh and render each chunk
|
||||
for i in 0..self.terrain_height {
|
||||
if let Some(chunk) = column.get_chunk(i as usize) {
|
||||
if let Some(chunk) = column.get_sub_chunk(i as usize) {
|
||||
self.mesh_and_render_chunk(chunk, mesher, registry);
|
||||
}
|
||||
}
|
||||
@@ -154,7 +153,7 @@ impl ChunkManager {
|
||||
|
||||
fn mesh_and_render_chunk(
|
||||
&mut self,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
mesher: &dyn Mesher,
|
||||
registry: &VoxelRegistry,
|
||||
) {
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
use godot::classes::class_macros::private::virtuals::Os::Vector3i;
|
||||
|
||||
use super::chunk::{CHUNK_SIZE, Chunk};
|
||||
use super::chunk::{CHUNK_SIZE, SubChunk};
|
||||
|
||||
pub const CHUNKS_PER_COLUMN: usize = 8;
|
||||
|
||||
pub struct ChunkColumn {
|
||||
pub chunks: [Option<Chunk>; CHUNKS_PER_COLUMN],
|
||||
pub struct ChunkPos {
|
||||
pub x: i64,
|
||||
pub y: i64,
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub sub_chunks: [Option<SubChunk>; CHUNKS_PER_COLUMN],
|
||||
pub world_position: (i32, i32), // XZ
|
||||
pub chunk_size: Vector3i,
|
||||
}
|
||||
|
||||
impl ChunkColumn {
|
||||
impl Chunk {
|
||||
pub fn new(x: i32, z: i32, chunk_size: Vector3i) -> Self {
|
||||
ChunkColumn {
|
||||
chunks: [None, None, None, None, None, None, None, None],
|
||||
Chunk {
|
||||
sub_chunks: [None, None, None, None, None, None, None, None],
|
||||
world_position: (x, z),
|
||||
chunk_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_chunk_index(chunk_size_y: i32, world_y: i32) -> usize {
|
||||
pub fn get_sub_chunk_index(chunk_size_y: i32, world_y: i32) -> usize {
|
||||
(world_y / chunk_size_y) as usize
|
||||
}
|
||||
|
||||
@@ -34,31 +39,31 @@ impl ChunkColumn {
|
||||
(chunk_index * chunk_size_y) + local_y
|
||||
}
|
||||
|
||||
pub fn get_or_create_chunk(&mut self, chunk_y_index: i32) -> &mut Chunk {
|
||||
if self.chunks[chunk_y_index as usize].is_none() {
|
||||
pub fn get_or_create_sub_chunk(&mut self, chunk_y_index: i32) -> &mut SubChunk {
|
||||
if self.sub_chunks[chunk_y_index as usize].is_none() {
|
||||
let (world_x, world_z) = self.world_position;
|
||||
self.chunks[chunk_y_index as usize] = Some(Chunk::new(
|
||||
self.sub_chunks[chunk_y_index as usize] = Some(SubChunk::new(
|
||||
world_x as f64,
|
||||
(chunk_y_index) as f64,
|
||||
world_z as f64,
|
||||
));
|
||||
}
|
||||
self.chunks[chunk_y_index as usize].as_mut().unwrap()
|
||||
self.sub_chunks[chunk_y_index as usize].as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_chunk(&self, chunk_y_index: usize) -> Option<&Chunk> {
|
||||
self.chunks[chunk_y_index].as_ref()
|
||||
pub fn get_sub_chunk(&self, chunk_y_index: usize) -> Option<&SubChunk> {
|
||||
self.sub_chunks[chunk_y_index].as_ref()
|
||||
}
|
||||
|
||||
pub fn get_chunk_mut(&mut self, chunk_y_index: usize) -> Option<&mut Chunk> {
|
||||
self.chunks[chunk_y_index].as_mut()
|
||||
pub fn get_sub_chunk_mut(&mut self, chunk_y_index: usize) -> Option<&mut SubChunk> {
|
||||
self.sub_chunks[chunk_y_index].as_mut()
|
||||
}
|
||||
|
||||
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_sub_chunk_index(self.chunk_size.y, world_pos.y);
|
||||
if chunk_index < CHUNKS_PER_COLUMN {
|
||||
let size = self.chunk_size;
|
||||
let chunk = self.get_or_create_chunk(chunk_index as i32);
|
||||
let chunk = self.get_or_create_sub_chunk(chunk_index as i32);
|
||||
|
||||
let local_x = world_pos.x.rem_euclid(size.x);
|
||||
let local_y = Self::get_local_y(size.y, world_pos.y);
|
||||
@@ -72,8 +77,8 @@ impl ChunkColumn {
|
||||
}
|
||||
|
||||
pub fn get_voxel(&self, world_pos: Vector3i) -> bool {
|
||||
let chunk_index = Self::get_chunk_index(self.chunk_size.y, world_pos.y);
|
||||
if let Some(chunk) = self.get_chunk(chunk_index) {
|
||||
let chunk_index = Self::get_sub_chunk_index(self.chunk_size.y, world_pos.y);
|
||||
if let Some(chunk) = self.get_sub_chunk(chunk_index) {
|
||||
let local_x = world_pos.x.rem_euclid(CHUNK_SIZE);
|
||||
let local_y = Self::get_local_y(self.chunk_size.y, world_pos.y);
|
||||
let local_z = world_pos.z.rem_euclid(CHUNK_SIZE);
|
||||
|
||||
@@ -3,7 +3,7 @@ pub mod chunk_manager;
|
||||
pub mod column;
|
||||
pub mod mesh;
|
||||
|
||||
pub use chunk::Chunk;
|
||||
pub use chunk::SubChunk;
|
||||
pub use chunk_manager::ChunkManager;
|
||||
pub use column::ChunkColumn;
|
||||
pub use column::Chunk;
|
||||
pub use mesh::ChunkMesh;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod voxel_registry;
|
||||
pub mod world_config;
|
||||
pub mod world_generator;
|
||||
pub mod world_node;
|
||||
pub mod world_plugin;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use godot::{
|
||||
classes::{IResource, Resource, class_macros::private::virtuals::Os::Vector3i},
|
||||
obj::Base,
|
||||
classes::{IResource, Resource},
|
||||
obj::{Base, Gd},
|
||||
prelude::{GodotClass, godot_api},
|
||||
};
|
||||
|
||||
use crate::editor::world_generator::WorldGeneratorResource;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Resource)]
|
||||
pub struct WorldConfig {
|
||||
@@ -11,9 +13,6 @@ pub struct WorldConfig {
|
||||
|
||||
// ===== WORLD GROUP =====
|
||||
#[export_group(name = "World")]
|
||||
#[export]
|
||||
chunk_size: Vector3i,
|
||||
|
||||
#[export(range = (2.0,64.0))]
|
||||
render_distance: u8,
|
||||
|
||||
@@ -23,13 +22,10 @@ pub struct WorldConfig {
|
||||
// ===== GENERATION GROUP =====
|
||||
#[export_group(name = "Generation")]
|
||||
#[export]
|
||||
seed: i32,
|
||||
|
||||
#[export(range = (0.01, 4.0, 0.01))]
|
||||
frequency: f32,
|
||||
generator: Option<Gd<WorldGeneratorResource>>,
|
||||
|
||||
#[export(range = (1.0, 10.0))]
|
||||
terrain_height: u32,
|
||||
surface_height: u8,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
@@ -38,12 +34,11 @@ impl IResource for WorldConfig {
|
||||
// DEFAULT Values
|
||||
Self {
|
||||
base,
|
||||
chunk_size: Vector3i::new(32, 32, 32),
|
||||
render_distance: 8,
|
||||
worker_threads: 4,
|
||||
seed: 1234,
|
||||
frequency: 0.03,
|
||||
terrain_height: 6,
|
||||
worker_threads: 8,
|
||||
generator: None,
|
||||
// noise: None,
|
||||
surface_height: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
src/editor/world_generator.rs
Normal file
154
src/editor/world_generator.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use godot::{
|
||||
classes::{FastNoiseLite, Resource},
|
||||
obj::{Base, Gd},
|
||||
prelude::{Export, GodotClass, GodotConvert, Var},
|
||||
};
|
||||
|
||||
use crate::runtime::types::IntoRuntime;
|
||||
use fastnoise_lite::FastNoiseLite as RustNoise;
|
||||
|
||||
#[derive(GodotConvert, Var, Export, Debug, Clone, Copy, PartialEq)]
|
||||
#[godot(via = i64)]
|
||||
pub enum WorldGeneratorType {
|
||||
Noise2D,
|
||||
Noise3D,
|
||||
Graph,
|
||||
Flat,
|
||||
}
|
||||
|
||||
impl Default for WorldGeneratorType {
|
||||
fn default() -> Self {
|
||||
WorldGeneratorType::Noise2D
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GodotClass, Debug)]
|
||||
#[class(init, base=Resource)]
|
||||
pub struct WorldGeneratorResource {
|
||||
#[base]
|
||||
base: Base<Resource>,
|
||||
|
||||
#[export]
|
||||
pub noise2d: Option<Gd<FastNoiseLite>>,
|
||||
}
|
||||
impl WorldGeneratorResource {
|
||||
fn convert_noise_type(
|
||||
ty: godot::classes::fast_noise_lite::NoiseType,
|
||||
) -> fastnoise_lite::NoiseType {
|
||||
use fastnoise_lite::NoiseType as R;
|
||||
use godot::classes::fast_noise_lite::NoiseType as G;
|
||||
|
||||
match ty {
|
||||
G::SIMPLEX => R::OpenSimplex2,
|
||||
G::SIMPLEX_SMOOTH => R::OpenSimplex2S,
|
||||
G::PERLIN => R::Perlin,
|
||||
G::VALUE => R::Value,
|
||||
G::VALUE_CUBIC => R::ValueCubic,
|
||||
G::CELLULAR => R::Cellular,
|
||||
_ => R::OpenSimplex2S, // reasonable default
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_fractal_type(
|
||||
ft: godot::classes::fast_noise_lite::FractalType,
|
||||
dft: godot::classes::fast_noise_lite::DomainWarpFractalType,
|
||||
) -> fastnoise_lite::FractalType {
|
||||
use fastnoise_lite::FractalType as R;
|
||||
use godot::classes::fast_noise_lite::DomainWarpFractalType as D;
|
||||
use godot::classes::fast_noise_lite::FractalType as G;
|
||||
|
||||
// Priority: domain-warp fractals first
|
||||
let domain_fractal = match dft {
|
||||
D::PROGRESSIVE => Some(R::DomainWarpProgressive),
|
||||
D::INDEPENDENT => Some(R::DomainWarpIndependent),
|
||||
D::NONE => None,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(df) = domain_fractal {
|
||||
return df;
|
||||
}
|
||||
|
||||
// Then normal fractals
|
||||
match ft {
|
||||
G::NONE => R::None,
|
||||
G::FBM => R::FBm,
|
||||
G::RIDGED => R::Ridged,
|
||||
G::PING_PONG => R::PingPong,
|
||||
_ => R::None,
|
||||
}
|
||||
}
|
||||
fn convert_cellular_distance_function(
|
||||
fnc: godot::classes::fast_noise_lite::CellularDistanceFunction,
|
||||
) -> fastnoise_lite::CellularDistanceFunction {
|
||||
use fastnoise_lite::CellularDistanceFunction as R;
|
||||
use godot::classes::fast_noise_lite::CellularDistanceFunction as G;
|
||||
|
||||
match fnc {
|
||||
G::EUCLIDEAN => R::Euclidean,
|
||||
G::EUCLIDEAN_SQUARED => R::EuclideanSq,
|
||||
G::MANHATTAN => R::Manhattan,
|
||||
G::HYBRID => R::Hybrid,
|
||||
_ => R::Euclidean,
|
||||
}
|
||||
}
|
||||
fn convert_cellular_return_type(
|
||||
ret: godot::classes::fast_noise_lite::CellularReturnType,
|
||||
) -> fastnoise_lite::CellularReturnType {
|
||||
use fastnoise_lite::CellularReturnType as R;
|
||||
use godot::classes::fast_noise_lite::CellularReturnType as G;
|
||||
|
||||
match ret {
|
||||
G::CELL_VALUE => R::CellValue,
|
||||
G::DISTANCE => R::Distance,
|
||||
G::DISTANCE2 => R::Distance2,
|
||||
G::DISTANCE2_ADD => R::Distance2Add,
|
||||
G::DISTANCE2_SUB => R::Distance2Sub,
|
||||
G::DISTANCE2_MUL => R::Distance2Mul,
|
||||
G::DISTANCE2_DIV => R::Distance2Div,
|
||||
_ => R::CellValue,
|
||||
}
|
||||
}
|
||||
fn convert_domain_warp_type(
|
||||
warp: godot::classes::fast_noise_lite::DomainWarpType,
|
||||
) -> fastnoise_lite::DomainWarpType {
|
||||
use fastnoise_lite::DomainWarpType as R;
|
||||
use godot::classes::fast_noise_lite::DomainWarpType as G;
|
||||
|
||||
match warp {
|
||||
G::SIMPLEX => R::OpenSimplex2,
|
||||
G::SIMPLEX_REDUCED => R::OpenSimplex2Reduced,
|
||||
G::BASIC_GRID => R::BasicGrid,
|
||||
_ => R::OpenSimplex2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRuntime<RustNoise> for WorldGeneratorResource {
|
||||
fn into_runtime(&self) -> RustNoise {
|
||||
match &self.noise2d {
|
||||
Some(noise) => {
|
||||
let mut rn = RustNoise::new();
|
||||
rn.set_seed(Some(noise.get_seed()));
|
||||
rn.set_frequency(Some(noise.get_frequency()));
|
||||
rn.set_noise_type(Some(Self::convert_noise_type(noise.get_noise_type())));
|
||||
rn.set_fractal_type(Some(Self::convert_fractal_type(
|
||||
noise.get_fractal_type(),
|
||||
noise.get_domain_warp_fractal_type(),
|
||||
)));
|
||||
rn.set_cellular_distance_function(Some(Self::convert_cellular_distance_function(
|
||||
noise.get_cellular_distance_function(),
|
||||
)));
|
||||
rn.set_cellular_return_type(Some(Self::convert_cellular_return_type(
|
||||
noise.get_cellular_return_type(),
|
||||
)));
|
||||
rn.set_domain_warp_type(Some(Self::convert_domain_warp_type(
|
||||
noise.get_domain_warp_type(),
|
||||
)));
|
||||
|
||||
return rn;
|
||||
}
|
||||
None => return RustNoise::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
use godot::classes::{INode3D, VoxelGi};
|
||||
use godot::classes::INode3D;
|
||||
use godot::prelude::*;
|
||||
|
||||
use crate::editor::VoxelRegistry;
|
||||
use crate::editor::world_config::WorldConfig;
|
||||
use crate::generation::SimpleSurfaceGenerator;
|
||||
use crate::generation::generator::SurfaceGenerator;
|
||||
use crate::meshing::{Mesher, TexturedMesher};
|
||||
use crate::terrain::TerrainManager;
|
||||
use crate::runtime::Runtime;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Node3D,tool)]
|
||||
@@ -23,10 +20,12 @@ pub struct World {
|
||||
#[export]
|
||||
registry: Option<Gd<VoxelRegistry>>,
|
||||
|
||||
// runtime systems
|
||||
terrain_manager: TerrainManager,
|
||||
runtime_generator: Box<dyn SurfaceGenerator>,
|
||||
runtime_mesher: Box<dyn Mesher>,
|
||||
state: WorldState,
|
||||
}
|
||||
|
||||
pub enum WorldState {
|
||||
Uninitialized,
|
||||
Ready { runtime: Runtime },
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
@@ -37,36 +36,33 @@ impl INode3D for World {
|
||||
base,
|
||||
config: None,
|
||||
registry: None,
|
||||
terrain_manager: TerrainManager::new(),
|
||||
runtime_generator: Box::new(SimpleSurfaceGenerator::defaults()),
|
||||
runtime_mesher: Box::new(TexturedMesher::new()),
|
||||
state: WorldState::Uninitialized,
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, _delta: f64) {
|
||||
self.add_pending_mesh_instances_to_scene();
|
||||
// self.add_pending_mesh_instances_to_scene();
|
||||
}
|
||||
|
||||
fn ready(&mut self) {
|
||||
if self.registry.is_none() {
|
||||
godot_error!(
|
||||
"VoxelRegistry not assigned! Please assign a VoxelRegistry resource in the editor."
|
||||
);
|
||||
return;
|
||||
self.initialize_runtime();
|
||||
}
|
||||
}
|
||||
|
||||
impl World {
|
||||
fn initialize_runtime(&mut self) {
|
||||
match (&self.registry, &self.config) {
|
||||
(Some(reg), Some(cfg)) => {
|
||||
let cfg_ref = cfg.bind();
|
||||
let reg_ref = reg.bind();
|
||||
let runtime = Runtime::new(cfg_ref, reg_ref);
|
||||
self.state = WorldState::Ready { runtime };
|
||||
}
|
||||
_ => {
|
||||
godot_error!("World: missing resources (config or registry).");
|
||||
self.state = WorldState::Uninitialized;
|
||||
}
|
||||
}
|
||||
|
||||
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
||||
self.world_seed,
|
||||
self.noise_frequency,
|
||||
self.terrain_height,
|
||||
self.chunk_size,
|
||||
));
|
||||
self.surface_generator = surface_generator;
|
||||
self.chunk_manager.chunk_size = self.chunk_size;
|
||||
self.chunk_manager.terrain_height = self.terrain_height;
|
||||
|
||||
self.regenerate_terrain();
|
||||
godot_print!("World ready! building GI...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,100 +72,82 @@ impl INode3D for World {
|
||||
***/
|
||||
#[godot_api]
|
||||
impl World {
|
||||
fn apply_config(&mut self) {
|
||||
let Some(config) = &self.config else {
|
||||
godot_error!("VoxelWorld: Missing WorldConfig resource");
|
||||
return;
|
||||
};
|
||||
|
||||
let cfg = config.bind();
|
||||
|
||||
self.chunk_manager.chunk_size = cfg.chunk_size;
|
||||
self.chunk_manager.render_distance = cfg.render_distance;
|
||||
self.chunk_manager.terrain_height = cfg.height;
|
||||
|
||||
self.generator = Box::new(SimpleSurfaceGenerator::new(
|
||||
cfg.seed,
|
||||
cfg.frequency,
|
||||
cfg.height,
|
||||
cfg.chunk_size,
|
||||
));
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn generate_terrain(&mut self) {
|
||||
match &self.registry {
|
||||
None => {
|
||||
godot_print!("No Voxel registry is provided to the world.")
|
||||
}
|
||||
Some(registry) => {
|
||||
let distance = self.render_distance as i32;
|
||||
let library_ref = registry.bind();
|
||||
fn generate_world(&mut self, center_pos: Vector3i) {}
|
||||
// #[func]
|
||||
// fn generate_terrain(&mut self) {
|
||||
// match &self.registry {
|
||||
// None => {
|
||||
// godot_print!("No Voxel registry is provided to the world.")
|
||||
// }
|
||||
// Some(registry) => {
|
||||
// let distance = self.render_distance as i32;
|
||||
// let library_ref = registry.bind();
|
||||
|
||||
for index_x in -distance..=distance {
|
||||
for index_z in -distance..=distance {
|
||||
self.chunk_manager.generate_chunk_column(
|
||||
index_x,
|
||||
index_z,
|
||||
&library_ref,
|
||||
self.surface_generator.as_ref(),
|
||||
self.mesher.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
godot_print!("Textured terrain regenerated!");
|
||||
}
|
||||
}
|
||||
}
|
||||
// for index_x in -distance..=distance {
|
||||
// for index_z in -distance..=distance {
|
||||
// self.chunk_manager.generate_chunk_column(
|
||||
// index_x,
|
||||
// index_z,
|
||||
// &library_ref,
|
||||
// self.surface_generator.as_ref(),
|
||||
// self.mesher.as_ref(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// godot_print!("Textured terrain regenerated!");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[func]
|
||||
fn regenerate_terrain(&mut self) {
|
||||
godot_print!("Regenerating terrain...");
|
||||
// #[func]
|
||||
// fn regenerate_terrain(&mut self) {
|
||||
// godot_print!("Regenerating terrain...");
|
||||
|
||||
self.chunk_manager.clear();
|
||||
// self.chunk_manager.clear();
|
||||
|
||||
// Create textured mesher
|
||||
self.mesher = Box::new(TexturedMesher::new());
|
||||
// // Create textured mesher
|
||||
// self.mesher = Box::new(TexturedMesher::new());
|
||||
|
||||
self.generate_terrain();
|
||||
}
|
||||
// self.generate_terrain();
|
||||
// }
|
||||
|
||||
#[func]
|
||||
fn clear_terrain(&mut self) {
|
||||
godot_print!("Clearing Terrain...");
|
||||
// #[func]
|
||||
// fn clear_terrain(&mut self) {
|
||||
// godot_print!("Clearing Terrain...");
|
||||
|
||||
self.chunk_manager.clear();
|
||||
// self.chunk_manager.clear();
|
||||
|
||||
self.chunk_manager.pending_mesh_instances.clear();
|
||||
self.chunk_manager.renderer.clear_and_destroy();
|
||||
}
|
||||
// self.chunk_manager.pending_mesh_instances.clear();
|
||||
// self.chunk_manager.renderer.clear_and_destroy();
|
||||
// }
|
||||
|
||||
#[func]
|
||||
fn update_from_gdscript(&mut self, player_world_pos: Vector3) {
|
||||
match &self.registry {
|
||||
None => return,
|
||||
Some(registry) => {
|
||||
let registry_ref = registry.bind();
|
||||
self.chunk_manager.update_around_player(
|
||||
player_world_pos,
|
||||
self.render_distance as i32,
|
||||
®istry_ref,
|
||||
self.surface_generator.as_ref(),
|
||||
self.mesher.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #[func]
|
||||
// fn update_from_gdscript(&mut self, player_world_pos: Vector3) {
|
||||
// match &self.registry {
|
||||
// None => return,
|
||||
// Some(registry) => {
|
||||
// let registry_ref = registry.bind();
|
||||
// self.chunk_manager.update_around_player(
|
||||
// player_world_pos,
|
||||
// self.render_distance as i32,
|
||||
// ®istry_ref,
|
||||
// self.surface_generator.as_ref(),
|
||||
// self.mesher.as_ref(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn add_pending_mesh_instances_to_scene(&mut self) {
|
||||
let mesh_instances = self.chunk_manager.take_pending_mesh_instances();
|
||||
if mesh_instances.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut base = self.base_mut();
|
||||
// fn add_pending_mesh_instances_to_scene(&mut self) {
|
||||
// let mesh_instances = self.chunk_manager.take_pending_mesh_instances();
|
||||
// if mesh_instances.is_empty() {
|
||||
// return;
|
||||
// }
|
||||
// let mut base = self.base_mut();
|
||||
|
||||
for mesh_instance in mesh_instances {
|
||||
base.add_child(&mesh_instance.upcast::<Node3D>());
|
||||
}
|
||||
}
|
||||
// for mesh_instance in mesh_instances {
|
||||
// base.add_child(&mesh_instance.upcast::<Node3D>());
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
use godot::classes::class_macros::private::virtuals::Os::Vector3i;
|
||||
use crate::chunk::Chunk;
|
||||
|
||||
use crate::chunk::{Chunk, ChunkColumn, column};
|
||||
|
||||
pub trait WorldGenerator: Send + Sync {
|
||||
fn generate_chunk(&self, chunk: &mut Chunk);
|
||||
fn get_base_height(&self, x: f32, z: f32) -> f32;
|
||||
fn should_be_solid(&self, density: f32, relative_height: f32) -> bool;
|
||||
fn get_name(&self) -> &str;
|
||||
}
|
||||
|
||||
pub trait SurfaceGenerator: Send + Sync {
|
||||
fn generate(&self, column: &mut ChunkColumn);
|
||||
fn sample_voxel(&self, pos: Vector3i) -> bool;
|
||||
pub trait TerrainGenerator: Send + Sync {
|
||||
fn sample_chunk(&self, column: &mut Chunk);
|
||||
fn sample_height(&self, x: f32, z: f32) -> f32;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod generator;
|
||||
pub mod simple_heightmap;
|
||||
|
||||
pub use generator::WorldGenerator;
|
||||
pub use simple_heightmap::SimpleSurfaceGenerator;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use fastnoise_lite::{FastNoiseLite, FractalType};
|
||||
use godot::{classes::class_macros::private::virtuals::Os::Vector3i, global::pow};
|
||||
use godot::classes::class_macros::private::virtuals::Os::Vector3i;
|
||||
|
||||
use crate::{chunk::ChunkColumn, generation::generator::SurfaceGenerator};
|
||||
use crate::chunk::{Chunk, chunk::CHUNK_SIZE};
|
||||
|
||||
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 {
|
||||
pub fn new(seed: i32, frequency: f32, surface_height: u32) -> Self {
|
||||
let mut noise = FastNoiseLite::new();
|
||||
noise.frequency = frequency;
|
||||
noise.noise_type = fastnoise_lite::NoiseType::OpenSimplex2;
|
||||
@@ -20,7 +19,6 @@ impl SimpleSurfaceGenerator {
|
||||
Self {
|
||||
noise,
|
||||
surface_height,
|
||||
chunk_size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,20 +28,20 @@ impl SimpleSurfaceGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceGenerator for SimpleSurfaceGenerator {
|
||||
fn generate(&self, column: &mut ChunkColumn) {
|
||||
let size_x = self.chunk_size.x;
|
||||
let size_z = self.chunk_size.z;
|
||||
impl SimpleSurfaceGenerator {
|
||||
fn generate(&self, column: &mut Chunk) {
|
||||
let size_x = CHUNK_SIZE;
|
||||
let size_z = CHUNK_SIZE;
|
||||
|
||||
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;
|
||||
let column_height = CHUNK_SIZE * self.surface_height as i32;
|
||||
|
||||
for x in 0..self.chunk_size.x {
|
||||
for z in 0..self.chunk_size.z {
|
||||
for x in 0..CHUNK_SIZE {
|
||||
for z in 0..CHUNK_SIZE {
|
||||
// world coordinates of this column
|
||||
let world_x = base_x + x;
|
||||
let world_z = base_z + z;
|
||||
@@ -71,7 +69,7 @@ impl SurfaceGenerator for SimpleSurfaceGenerator {
|
||||
|
||||
let noise = Self::normalize_value(noise);
|
||||
|
||||
let column_height = self.chunk_size.y * self.surface_height as i32;
|
||||
let column_height = CHUNK_SIZE * self.surface_height as i32;
|
||||
let height = (noise.powi(2) * column_height as f32) as i32;
|
||||
|
||||
pos.y <= height
|
||||
|
||||
@@ -7,7 +7,7 @@ mod editor;
|
||||
mod generation;
|
||||
mod meshing;
|
||||
mod rendering;
|
||||
mod terrain;
|
||||
mod runtime;
|
||||
|
||||
struct FastVoxel;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::chunk::chunk::CHUNK_SIZE;
|
||||
use crate::chunk::{Chunk, ChunkMesh};
|
||||
use crate::chunk::{SubChunk, ChunkMesh};
|
||||
use crate::editor::voxel_registry::VoxelRegistry;
|
||||
use crate::meshing::Mesher;
|
||||
use godot::prelude::*;
|
||||
@@ -20,7 +20,7 @@ impl BinaryGreedyMesher {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh) {
|
||||
pub fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) {
|
||||
mesh.vertices.clear();
|
||||
mesh.normals.clear();
|
||||
mesh.indices.clear();
|
||||
@@ -34,7 +34,7 @@ impl BinaryGreedyMesher {
|
||||
self.mesh_face(chunk, mesh, 5); // +Z (forward)
|
||||
}
|
||||
|
||||
fn mesh_face(&self, chunk: &Chunk, mesh: &mut ChunkMesh, face_dir: usize) {
|
||||
fn mesh_face(&self, chunk: &SubChunk, mesh: &mut ChunkMesh, face_dir: usize) {
|
||||
let size = CHUNK_SIZE as usize;
|
||||
|
||||
// For each slice perpendicular to the face direction
|
||||
@@ -191,7 +191,7 @@ impl BinaryGreedyMesher {
|
||||
quad: GreedyQuad,
|
||||
face_dir: usize,
|
||||
axis: usize,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
) {
|
||||
let base_idx = mesh.vertices.len() as i32;
|
||||
|
||||
@@ -297,13 +297,13 @@ impl BinaryGreedyMesher {
|
||||
}
|
||||
|
||||
impl Mesher for BinaryGreedyMesher {
|
||||
fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh) {
|
||||
fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) {
|
||||
self.generate_mesh(chunk, mesh);
|
||||
}
|
||||
|
||||
fn generate_mesh_with_registry(
|
||||
&self,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
_registry: &VoxelRegistry,
|
||||
mesh: &mut ChunkMesh,
|
||||
) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::chunk::{Chunk, ChunkMesh};
|
||||
use crate::chunk::{ChunkMesh, SubChunk};
|
||||
use crate::editor::voxel_registry::VoxelRegistry;
|
||||
use godot::prelude::*;
|
||||
|
||||
pub trait Mesher: Send + Sync {
|
||||
fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh);
|
||||
fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh);
|
||||
fn generate_mesh_with_registry(
|
||||
&self,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
registry: &VoxelRegistry,
|
||||
mesh: &mut ChunkMesh,
|
||||
);
|
||||
@@ -24,7 +24,7 @@ impl CullingMesher {
|
||||
impl Mesher for CullingMesher {
|
||||
fn generate_mesh_with_registry(
|
||||
&self,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
registry: &VoxelRegistry,
|
||||
mesh: &mut ChunkMesh,
|
||||
) {
|
||||
@@ -32,7 +32,7 @@ impl Mesher for CullingMesher {
|
||||
self.generate_mesh(chunk, mesh);
|
||||
}
|
||||
|
||||
fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh) {
|
||||
fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) {
|
||||
mesh.clear();
|
||||
|
||||
// Simple CullingMesher - generate a quad for each solid voxel face
|
||||
@@ -72,7 +72,7 @@ impl CullingMesher {
|
||||
z: i32,
|
||||
voxel: bool,
|
||||
mesh: &mut ChunkMesh,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
) {
|
||||
let color = Color {
|
||||
r: 255.0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::chunk::chunk::CHUNK_SIZE;
|
||||
use crate::chunk::{Chunk, ChunkMesh};
|
||||
use crate::chunk::{SubChunk, ChunkMesh};
|
||||
use crate::editor::voxel_registry::{VoxelModel, VoxelRegistry};
|
||||
use crate::meshing::Mesher;
|
||||
use godot::prelude::*;
|
||||
@@ -74,7 +74,7 @@ impl TexturedMesher {
|
||||
]
|
||||
}
|
||||
#[inline(always)]
|
||||
fn is_face_visible(chunk: &Chunk, x: usize, y: usize, z: usize, face: usize) -> bool {
|
||||
fn is_face_visible(chunk: &SubChunk, x: usize, y: usize, z: usize, face: usize) -> bool {
|
||||
// Convert to i32 for neighbor calculation
|
||||
let (nx, ny, nz) = match face {
|
||||
0 => (x as i32 - 1, y as i32, z as i32), // LEFT
|
||||
@@ -108,7 +108,7 @@ impl TexturedMesher {
|
||||
z: usize,
|
||||
model: &VoxelModel, // Already borrowed, no binding needed
|
||||
mesh: &mut ChunkMesh,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
) {
|
||||
let pos = Vector3::new(x as f32, y as f32, z as f32);
|
||||
|
||||
@@ -168,13 +168,13 @@ impl TexturedMesher {
|
||||
}
|
||||
|
||||
impl Mesher for TexturedMesher {
|
||||
fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh) {
|
||||
fn generate_mesh(&self, chunk: &SubChunk, mesh: &mut ChunkMesh) {
|
||||
panic!("TexturedMesher requires a VoxelRegistry. Use generate_mesh_with_registry instead.");
|
||||
}
|
||||
|
||||
fn generate_mesh_with_registry(
|
||||
&self,
|
||||
chunk: &Chunk,
|
||||
chunk: &SubChunk,
|
||||
registry: &VoxelRegistry,
|
||||
mesh: &mut ChunkMesh,
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::chunk::{Chunk, ChunkMesh};
|
||||
use crate::chunk::{ChunkMesh, SubChunk};
|
||||
use godot::{
|
||||
classes::{
|
||||
ArrayMesh, Material, MeshInstance3D, ResourceLoader, StandardMaterial3D,
|
||||
@@ -40,7 +40,7 @@ impl Renderer {
|
||||
.and_then(|resource| resource.try_cast::<Material>().ok())
|
||||
}
|
||||
|
||||
pub fn render_chunk(&mut self, chunk: &Chunk, mesh: &ChunkMesh) -> Gd<MeshInstance3D> {
|
||||
pub fn render_chunk(&mut self, chunk: &SubChunk, mesh: &ChunkMesh) -> Gd<MeshInstance3D> {
|
||||
let chunk_key = chunk.world_position;
|
||||
let chunk_key_i32 = (chunk_key.0 as i32, chunk_key.1 as i32, chunk_key.2 as i32);
|
||||
|
||||
@@ -137,7 +137,11 @@ impl Renderer {
|
||||
mesh_instance
|
||||
}
|
||||
|
||||
pub fn render_chunk_with_uvs(&mut self, chunk: &Chunk, mesh: &ChunkMesh) -> Gd<MeshInstance3D> {
|
||||
pub fn render_chunk_with_uvs(
|
||||
&mut self,
|
||||
chunk: &SubChunk,
|
||||
mesh: &ChunkMesh,
|
||||
) -> Gd<MeshInstance3D> {
|
||||
let chunk_key = chunk.world_position;
|
||||
let chunk_key_i32 = (chunk_key.0 as i32, chunk_key.1 as i32, chunk_key.2 as i32);
|
||||
|
||||
|
||||
5
src/runtime/mod.rs
Normal file
5
src/runtime/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod runtime;
|
||||
pub mod types;
|
||||
|
||||
pub use runtime::Runtime;
|
||||
pub use runtime::*;
|
||||
@@ -1,17 +1,17 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{chunk::ChunkColumn, editor::world_config::WorldConfig, rendering::Renderer};
|
||||
use crate::{chunk::Chunk, runtime::types::WorldConfig};
|
||||
|
||||
pub struct TerrainManager {
|
||||
chunks: HashMap<(i32, i32), ChunkColumn>,
|
||||
pub struct Runtime {
|
||||
chunks: HashMap<(i32, i32), Chunk>,
|
||||
// renderer: Renderer,
|
||||
}
|
||||
|
||||
impl TerrainManager {
|
||||
pub fn new(config: WorldConfig) -> Self {
|
||||
impl Runtime {
|
||||
pub fn new(config: WorldConfig, registry: VoxelRegistry) -> Self {
|
||||
// Terrain Size calculation
|
||||
let width = config.get_render_distance() * 2 + 1;
|
||||
let height = config.get_terrain_height() as u8;
|
||||
let width = config.render_distance * 2 + 1;
|
||||
let height = config.surface_height as u8;
|
||||
let capacity = (width * width * height) as usize;
|
||||
|
||||
Self {
|
||||
@@ -28,7 +28,7 @@ impl TerrainManager {
|
||||
* We preallocate this capacity to minimize HashMap reallocations as the
|
||||
* visible terrain around the player is populated.
|
||||
***/
|
||||
chunks: HashMap::with_capacity(capacity),
|
||||
chunks: HashMap::<(i32, i32), Chunk>::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/runtime/types.rs
Normal file
17
src/runtime/types.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::generation::generator::TerrainGenerator;
|
||||
|
||||
pub trait IntoRuntime<T> {
|
||||
fn into_runtime(&self) -> T;
|
||||
}
|
||||
|
||||
pub struct WorldConfig {
|
||||
// ===== WORLD GROUP =====
|
||||
pub render_distance: u8,
|
||||
|
||||
pub worker_threads: u8,
|
||||
|
||||
pub surface_height: u8,
|
||||
|
||||
// ===== GENERATION GROUP =====
|
||||
generator: Box<dyn TerrainGenerator>,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod terrain_manager;
|
||||
|
||||
pub use terrain_manager::TerrainManager;
|
||||
Reference in New Issue
Block a user