re-working editor resource, chunk_manager and more.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
pub mod editor;
|
||||
pub mod voxel_registry;
|
||||
pub mod world_config;
|
||||
pub mod world_node;
|
||||
pub mod world_plugin;
|
||||
|
||||
pub use voxel_registry::VoxelRegistry;
|
||||
|
||||
@@ -7,7 +7,7 @@ use godot::{
|
||||
prelude::{Export, GodotClass, GodotConvert, Var},
|
||||
};
|
||||
|
||||
#[derive(GodotConvert, Var, Export, Debug, Clone, Copy)]
|
||||
#[derive(GodotConvert, Var, Export, Debug, Clone, Copy, PartialEq)]
|
||||
#[godot(via = i64)]
|
||||
pub enum VoxelType {
|
||||
Empty,
|
||||
@@ -58,26 +58,17 @@ pub struct VoxelModel {
|
||||
impl VoxelModel {
|
||||
#[inline]
|
||||
pub fn is_solid(&self) -> bool {
|
||||
match self.voxel_type {
|
||||
VoxelType::Empty => false,
|
||||
_ => true,
|
||||
}
|
||||
self.voxel_type != VoxelType::Empty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self.voxel_type {
|
||||
VoxelType::Empty => true,
|
||||
_ => false,
|
||||
}
|
||||
self.voxel_type == VoxelType::Empty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_cube(&self) -> bool {
|
||||
match self.voxel_type {
|
||||
VoxelType::Cube => true,
|
||||
_ => false,
|
||||
}
|
||||
self.voxel_type == VoxelType::Cube
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
49
src/editor/world_config.rs
Normal file
49
src/editor/world_config.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use godot::{
|
||||
classes::{IResource, Resource, class_macros::private::virtuals::Os::Vector3i},
|
||||
obj::Base,
|
||||
prelude::{GodotClass, godot_api},
|
||||
};
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Resource)]
|
||||
pub struct WorldConfig {
|
||||
base: Base<Resource>,
|
||||
|
||||
// ===== WORLD GROUP =====
|
||||
#[export_group(name = "World")]
|
||||
#[export]
|
||||
chunk_size: Vector3i,
|
||||
|
||||
#[export(range = (2.0,64.0))]
|
||||
render_distance: u8,
|
||||
|
||||
#[export(range = (1.0, 64.0, 1.0))]
|
||||
worker_threads: u8,
|
||||
|
||||
// ===== GENERATION GROUP =====
|
||||
#[export_group(name = "Generation")]
|
||||
#[export]
|
||||
seed: i32,
|
||||
|
||||
#[export(range = (0.01, 4.0, 0.01))]
|
||||
frequency: f32,
|
||||
|
||||
#[export(range = (1.0, 10.0))]
|
||||
terrain_height: u32,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl IResource for WorldConfig {
|
||||
fn init(base: Base<Resource>) -> Self {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,45 @@
|
||||
use godot::classes::{INode3D, VoxelGi};
|
||||
use godot::prelude::*;
|
||||
|
||||
use crate::chunk::ChunkManager;
|
||||
use crate::editor::VoxelRegistry;
|
||||
use crate::editor::world_config::WorldConfig;
|
||||
use crate::generation::SimpleSurfaceGenerator;
|
||||
use crate::generation::generator::SurfaceGenerator;
|
||||
use crate::meshing::binary_greedy_mesher::BinaryGreedyMesher;
|
||||
use crate::meshing::{Mesher, TexturedMesher};
|
||||
use crate::terrain::TerrainManager;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Node3D,tool)]
|
||||
pub struct World {
|
||||
base: Base<Node3D>,
|
||||
|
||||
// ===== RENDERING GROUP =====
|
||||
#[export_group(name = "Rendering")]
|
||||
#[export_group(name = "Config")]
|
||||
|
||||
/// World generation and rendering settings
|
||||
#[export]
|
||||
chunk_size: Vector3i,
|
||||
|
||||
#[export(range = (2.0,64.0))]
|
||||
render_distance: u8,
|
||||
|
||||
// ===== GENERATION GROUP =====
|
||||
#[export_group(name = "Generation")]
|
||||
#[export]
|
||||
world_seed: i32,
|
||||
|
||||
#[export(range = (0.01, 4.0, 0.01))]
|
||||
noise_frequency: f32,
|
||||
|
||||
#[export(range = (1.0, 10.0))]
|
||||
terrain_height: u32,
|
||||
|
||||
#[export(range = (1.0, 64.0, 1.0))]
|
||||
workder_threads: u8,
|
||||
config: Option<Gd<WorldConfig>>,
|
||||
|
||||
#[export_group(name = "Voxel Registry")]
|
||||
#[export]
|
||||
registry: Option<Gd<VoxelRegistry>>,
|
||||
|
||||
#[export_group(name = "GI")]
|
||||
#[export]
|
||||
voxelgi_node: Option<Gd<VoxelGi>>,
|
||||
|
||||
// World data (temporary)
|
||||
chunk_manager: ChunkManager,
|
||||
surface_generator: Box<dyn SurfaceGenerator>,
|
||||
mesher: Box<dyn Mesher>,
|
||||
// runtime systems
|
||||
terrain_manager: TerrainManager,
|
||||
runtime_generator: Box<dyn SurfaceGenerator>,
|
||||
runtime_mesher: Box<dyn Mesher>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode3D for World {
|
||||
fn init(base: Base<Node3D>) -> Self {
|
||||
godot_print!("🧊 Hello from FastVoxel");
|
||||
|
||||
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 chunk_manager = ChunkManager::new();
|
||||
|
||||
godot_print!("🧊 FastVoxel Init...");
|
||||
Self {
|
||||
render_distance: 8,
|
||||
chunk_size: Vector3i::new(16, 16, 16),
|
||||
world_seed: 1234,
|
||||
noise_frequency: 0.03,
|
||||
terrain_height: 8,
|
||||
base,
|
||||
surface_generator,
|
||||
mesher,
|
||||
chunk_manager,
|
||||
workder_threads: 4,
|
||||
config: None,
|
||||
registry: None,
|
||||
voxelgi_node: None,
|
||||
terrain_manager: TerrainManager::new(),
|
||||
runtime_generator: Box::new(SimpleSurfaceGenerator::defaults()),
|
||||
runtime_mesher: Box::new(TexturedMesher::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +55,6 @@ impl INode3D for World {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.voxelgi_node.is_none() {
|
||||
godot_error!("VoxelGI Node is not selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
||||
self.world_seed,
|
||||
self.noise_frequency,
|
||||
@@ -116,8 +70,32 @@ impl INode3D for World {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Public API is below here. function that can be called by GDScript
|
||||
* or other gdextensions in-order to manage and do all sorts of things
|
||||
***/
|
||||
#[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 {
|
||||
@@ -8,7 +8,7 @@ use godot::classes::Texture2D;
|
||||
use godot::classes::editor_plugin::CustomControlContainer;
|
||||
use godot::prelude::*;
|
||||
|
||||
use crate::world::World;
|
||||
use crate::editor::world_node::World;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(init, base=EditorPlugin, tool)]
|
||||
@@ -93,6 +93,11 @@ impl IEditorPlugin for WorldPlugin {
|
||||
|
||||
// --- Build the dropdown menu ---
|
||||
let mut popup = menu_button.get_popup(); // Returns a PopupMenu
|
||||
popup
|
||||
.as_mut()
|
||||
.expect("no popup")
|
||||
.add_check_item("Follow Editor Camera");
|
||||
popup.as_mut().expect("no popup").add_separator();
|
||||
popup.as_mut().expect("no popup").add_item("Rebuild World"); // item text, id
|
||||
popup.as_mut().expect("no popup").add_item("Clear World");
|
||||
popup.as_mut().expect("no popup").add_item("Reload Chunks");
|
||||
@@ -1,13 +1,13 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
mod voxel;
|
||||
mod world;
|
||||
|
||||
mod chunk;
|
||||
mod editor;
|
||||
mod generation;
|
||||
mod meshing;
|
||||
mod rendering;
|
||||
mod terrain;
|
||||
|
||||
struct FastVoxel;
|
||||
|
||||
|
||||
3
src/terrain/mod.rs
Normal file
3
src/terrain/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod terrain_manager;
|
||||
|
||||
pub use terrain_manager::TerrainManager;
|
||||
34
src/terrain/terrain_manager.rs
Normal file
34
src/terrain/terrain_manager.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{chunk::ChunkColumn, editor::world_config::WorldConfig, rendering::Renderer};
|
||||
|
||||
pub struct TerrainManager {
|
||||
chunks: HashMap<(i32, i32), ChunkColumn>,
|
||||
// renderer: Renderer,
|
||||
}
|
||||
|
||||
impl TerrainManager {
|
||||
pub fn new(config: WorldConfig) -> Self {
|
||||
// Terrain Size calculation
|
||||
let width = config.get_render_distance() * 2 + 1;
|
||||
let height = config.get_terrain_height() as u8;
|
||||
let capacity = (width * width * height) as usize;
|
||||
|
||||
Self {
|
||||
/***
|
||||
* The render distance defines a square area around the player in chunks.
|
||||
*
|
||||
* Example:
|
||||
* With a render distance of 8, the player stands on the "center" chunk.
|
||||
* There are 8 chunks in extended in each direction (left/right and forward/backward),
|
||||
* forming a (8 * 2 + 1) = 17 chunk wide grid.
|
||||
*
|
||||
* Total chunks = (render_distance * 2 + 1).pow(2) * height
|
||||
*
|
||||
* We preallocate this capacity to minimize HashMap reallocations as the
|
||||
* visible terrain around the player is populated.
|
||||
***/
|
||||
chunks: HashMap::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub struct WorldManager {
|
||||
chunks: HashMap<(i32, i32), Chunk>,
|
||||
}
|
||||
Reference in New Issue
Block a user