diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2e8df7a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 FastVoxel contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cdf4a2b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,362 @@
+# FastVoxel
+
+
+
+
+
+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.
+
+This repo contains the engine/plugin 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.
+
+## Highlights
+
+- Rust-based Godot 4 GDExtension
+- chunked voxel terrain 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
+
+## Screenshots
+
+Right now the repo only contains branding assets. I haven't added gameplay screenshots yet.
+
+
+
+
+
+If you want to add screenshots that show directly on the README, the easiest thing is to just drop images somewhere like:
+
+```
+docs/screenshots/WHATEVER.png
+```
+
+Then embed them in the README like:
+
+```md
+
+```
+
+## What FastVoxel Actually Does
+
+The engine generates a voxel world using chunk columns.
+
+Rough pipeline looks like this:
+
+1. A `SurfaceGenerator` decides if a voxel is solid or not.
+2. A `ChunkColumn` holds a vertical stack of chunks.
+3. Each `Chunk` stores voxel occupancy in a compact bit-packed format.
+4. A `Mesher` converts visible voxel faces into triangle meshes.
+5. A `Renderer` turns those meshes into Godot `MeshInstance3D` nodes.
+6. A world node updates loaded terrain as the player moves around.
+
+The idea is to keep generation, storage, and meshing fairly modular so different strategies can be swapped in later.
+
+## Core Concepts
+
+### Chunked world layout
+
+Terrain is divided into columns of chunks.
+
+Current defaults:
+
+- `CHUNK_SIZE = 32`
+- each chunk = `32 × 32 × 32` voxels
+- columns stack multiple chunks vertically
+- chunk loading/unloading happens around the player based on render distance
+
+Relevant files:
+
+- `src/chunk/chunk.rs`
+- `src/chunk/column.rs`
+- `src/chunk/chunk_manager.rs`
+
+### Bit-packed voxel storage
+
+Instead of storing a struct per voxel, chunks store occupancy using packed `u32` blocks.
+
+So 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.
+
+Material differences are currently handled at the meshing/registry layer instead of inside the voxel storage itself.
+
+### Surface generation
+
+The default generator is `SimpleSurfaceGenerator`.
+
+It uses `fastnoise-lite` to create a heightmap and then fills voxels from the bottom up to that height.
+
+Important files:
+
+- `src/generation/generator.rs`
+- `src/generation/simple_heightmap.rs`
+
+The generation system is trait-based so other terrain approaches can be plugged in later.
+
+### Meshing
+
+There are a few different meshing implementations right now.
+
+- `CullingMesher` – basic visible-face meshing
+- `TexturedMesher` – cube meshing with texture atlas support
+- `BinaryGreedyMesher` – more optimization-focused experiment
+
+Relevant files:
+
+- `src/meshing/mesher.rs`
+- `src/meshing/textured_mesher.rs`
+- `src/meshing/binary_greedy_mesher.rs`
+
+The textured mesher is currently the most useful one if you're aiming for something Minecraft-like since it supports per-face UV lookup via a voxel registry.
+
+### Godot integration
+
+The engine exposes a few classes/resources to Godot via GDExtension.
+
+Main ones:
+
+- `WorldConfig` – terrain + generation settings
+- `VoxelRegistry` – describes voxel types and atlas tiles
+- `World` – runtime node responsible for generation and updates
+- `WorldPlugin` – editor-side plugin hooks
+
+Files:
+
+- `src/editor/world_config.rs`
+- `src/editor/voxel_registry.rs`
+- `src/editor/world_node.rs`
+- `src/editor/world_plugin.rs`
+
+## Project Structure
+
+```
+src/
+├── chunk/ # chunk data, columns, chunk manager, mesh buffers
+├── editor/ # Godot resources, world node, editor plugin
+├── generation/ # generator traits + procedural terrain generation
+├── meshing/ # meshing strategies
+├── rendering/ # converting mesh data to Godot meshes
+├── terrain/ # higher-level terrain experiments / refactor work
+└── voxel/ # voxel registry + earlier voxel abstractions
+```
+
+## Build and Install
+
+### Requirements
+
+You need:
+
+- Rust toolchain
+- a Godot 4 project set up for GDExtension
+- optionally a sibling Godot project if you want to use the provided `build.sh`
+
+### Build the extension
+
+```
+cargo build --release
+```
+
+### Helper build script
+
+There's a small `build.sh` script that builds the Rust library and copies it into a Godot project.
+
+By default it expects a project at:
+
+```
+../minekoloft
+```
+
+It copies the Linux shared library to:
+
+```
+../minekoloft/addons/fastvoxel/bin/linux
+```
+
+Run it with:
+
+```
+./build.sh
+```
+
+If your Godot project is somewhere else, just edit the `GODOT_PROJECT` path in the script.
+
+## Using It in Godot
+
+Typical workflow looks like this:
+
+1. build the Rust extension
+2. copy/install the addon into your Godot project
+3. create a `WorldConfig` resource
+4. create a `VoxelRegistry` resource
+5. add a `World` node to a scene
+6. assign the config and registry in the inspector
+7. trigger terrain generation
+8. call the update method each frame with the player position
+
+### WorldConfig
+
+`WorldConfig` stores generation settings like:
+
+- `chunk_size`
+- `render_distance`
+- `worker_threads`
+- `seed`
+- `frequency`
+- `terrain_height`
+
+Default values in the code are roughly:
+
+- chunk size: `Vector3i(32, 32, 32)`
+- render distance: `8`
+- worker threads: `4`
+- seed: `1234`
+- frequency: `0.03`
+- terrain height: `6`
+
+These are just meant for quick test worlds.
+
+### VoxelRegistry
+
+`VoxelRegistry` is a Godot `Resource` containing voxel model definitions.
+
+Each `VoxelModel` can specify:
+
+- voxel type
+- atlas size
+- tile index for each cube face
+
+Faces supported:
+
+- left
+- right
+- bottom
+- top
+- back
+- front
+
+The mesher uses this to assign UVs when building meshes.
+
+### Runtime API
+
+The intended `World` node API currently includes:
+
+- `generate_terrain()`
+- `regenerate_terrain()`
+- `clear_terrain()`
+- `update_from_gdscript(player_world_pos: Vector3)`
+
+Example usage in GDScript:
+
+```gdscript
+@onready var world = $World
+@onready var player = $Player
+
+func _ready() -> void:
+ world.generate_terrain()
+
+func _process(_delta: float) -> void:
+ world.update_from_gdscript(player.global_position)
+```
+
+## Engine-Level API Notes
+
+### Generation trait
+
+Terrain generation is abstracted behind `SurfaceGenerator`.
+
+```rust
+pub trait SurfaceGenerator: Send + Sync {
+ fn generate(&self, column: &mut ChunkColumn);
+ fn sample_voxel(&self, pos: Vector3i) -> bool;
+}
+```
+
+This makes it easier to experiment with:
+
+- different noise algorithms
+- caves
+- biome systems
+- deterministic seeds
+- threaded generation later on
+
+### Mesher trait
+
+Meshing also uses a trait interface.
+
+```rust
+pub trait Mesher: Send + Sync {
+ fn generate_mesh(&self, chunk: &Chunk, mesh: &mut ChunkMesh);
+ fn generate_mesh_with_registry(
+ &self,
+ chunk: &Chunk,
+ registry: &VoxelRegistry,
+ mesh: &mut ChunkMesh,
+ );
+ fn get_name(&self) -> &str;
+}
+```
+
+Some meshers only need occupancy data, while others need material/registry info.
+
+Keeping this separated makes it easier to experiment with meshing strategies without touching world management code.
+
+### ChunkManager
+
+`ChunkManager` is responsible for most of the runtime work:
+
+- managing chunk column lifetimes
+- generating terrain around the player
+- unloading distant chunks
+- triggering meshing
+- submitting meshes to the renderer
+- handing mesh instances back to the scene tree
+
+So it basically sits between generation, meshing, and rendering.
+
+## Current State
+
+The project is mid-development and some systems are being refactored (mainly around the world/terrain layers).
+
+But the overall direction is pretty clear:
+
+- compact chunk storage
+- trait-based generation
+- pluggable meshing
+- Godot editor resources
+- runtime chunk streaming
+
+## Why This README Exists
+
+Mostly so the repo has an actual front page explaining:
+
+- what the project is
+- how it works
+- how to build it
+- where to start reading the code
+
+Wiki pages tend to be less visible when someone first lands on the repository.
+
+## Possible Future Work
+
+Some things likely coming next:
+
+- finishing the current terrain/world refactor
+- adding real gameplay screenshots or gifs
+- stabilizing the Godot-facing API
+- multithreaded chunk generation (GOD HELP)
+- more voxel/material data in storage
+- improved greedy meshing and cross-chunk face handling
+- a small example Godot project using the addon
+
+## License
+
+This project is licensed under the MIT License.
+
+See the `LICENSE` file for the full text.
diff --git a/build.sh b/build.sh
index 1f70ed1..adf3661 100755
--- a/build.sh
+++ b/build.sh
@@ -17,7 +17,7 @@ TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
cp "target/release/lib${LIB_NAME}.so" "$TMP"
mv "$TMP" "$DEST_LINUX/lib${LIB_NAME}.so"
-# Optional: Cross‑compile for Windows (if needed)
+# Optional: Cross compile for Windows (if needed)
# Uncomment the following lines if you have the Windows target installed
# echo "Building for Windows (cross-compile)..."
# cargo build --release --target x86_64-pc-windows-gnu
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index cf130f8..63a0f15 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -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;
diff --git a/src/editor/voxel_registry.rs b/src/editor/voxel_registry.rs
index 1a798ce..af84ea3 100644
--- a/src/editor/voxel_registry.rs
+++ b/src/editor/voxel_registry.rs
@@ -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]
diff --git a/src/editor/world_config.rs b/src/editor/world_config.rs
new file mode 100644
index 0000000..cf8e17b
--- /dev/null
+++ b/src/editor/world_config.rs
@@ -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,
+
+ // ===== 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) -> 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,
+ }
+ }
+}
diff --git a/src/world.rs b/src/editor/world_node.rs
similarity index 70%
rename from src/world.rs
rename to src/editor/world_node.rs
index 131b633..7a82d79 100644
--- a/src/world.rs
+++ b/src/editor/world_node.rs
@@ -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,
- // ===== 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>,
#[export_group(name = "Voxel Registry")]
#[export]
registry: Option>,
- #[export_group(name = "GI")]
- #[export]
- voxelgi_node: Option>,
-
- // World data (temporary)
- chunk_manager: ChunkManager,
- surface_generator: Box,
- mesher: Box,
+ // runtime systems
+ terrain_manager: TerrainManager,
+ runtime_generator: Box,
+ runtime_mesher: Box,
}
#[godot_api]
impl INode3D for World {
fn init(base: Base) -> 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 {
diff --git a/src/editor/editor.rs b/src/editor/world_plugin.rs
similarity index 95%
rename from src/editor/editor.rs
rename to src/editor/world_plugin.rs
index 11d752f..624b911 100644
--- a/src/editor/editor.rs
+++ b/src/editor/world_plugin.rs
@@ -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");
diff --git a/src/lib.rs b/src/lib.rs
index 5f2e340..4a81006 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
diff --git a/src/terrain/mod.rs b/src/terrain/mod.rs
new file mode 100644
index 0000000..ddad7ed
--- /dev/null
+++ b/src/terrain/mod.rs
@@ -0,0 +1,3 @@
+pub mod terrain_manager;
+
+pub use terrain_manager::TerrainManager;
diff --git a/src/terrain/terrain_manager.rs b/src/terrain/terrain_manager.rs
new file mode 100644
index 0000000..ea219c3
--- /dev/null
+++ b/src/terrain/terrain_manager.rs
@@ -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),
+ }
+ }
+}
diff --git a/src/world/world_manager.rs b/src/world/world_manager.rs
deleted file mode 100644
index 8ce3121..0000000
--- a/src/world/world_manager.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub struct WorldManager {
- chunks: HashMap<(i32, i32), Chunk>,
-}