Compare commits

1 Commits

Author SHA1 Message Date
8a635640c5 Merge pull request 'Major Rewrite' (#1) from world_manager-impl into master
Reviewed-on: #1
2026-03-27 23:47:50 +00:00
4 changed files with 320 additions and 296 deletions

370
README.md
View File

@@ -4,94 +4,87 @@
<img src="./logo-cropped.svg" alt="FastVoxel logo" width="180" />
</p>
**FastVoxel** is a voxel engine for Godot written in Rust using GDExtension.
Main goals are:
**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.
- Generate chunks fast enough (It's already faster than minecraft, but not because of my good code, it's actually minecraft's fault)
- store voxels using almost no memory (because RAM prices blah blah blah.. and also I enjoy suffering so why not spend 1k hours optimizing this thing?),
- and build meshes at runtime for blocky worlds with textured materials.
This repo contains the engine/GDExtension side of the project.
This repo contains the engine side of the project.
Yes, things are constantly being refactored.
And yes, I still have no idea what I'm doing, so take that for granted and pray to the compiler.
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 mesh pipeline
- Bit-packed voxel storage (solid/air, liquid, whatever)
- Procedural terrain via `fastnoise-lite`
- Chunk streaming around the player
- Runtime cube meshing with texture atlas support (up to 1024x1024)
- 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
## Soon:
- Multi-threaded chunk meshing & generation using a **work stealing** thread pool with divideandconquer parallelism.
(Some C dev just segfaulted reading that.)
- Multi-Threaded chunk meshing and world generation using a `Work Stealing` Thread pool with divideandconquer parallelism.
## Screenshots
<p align="center">
<img src="docs/screenshots/colors.png" width="960" />
<img src="docs/screenshots/colors.png" alt="Albedo Vertex color" width="960" />
</p>
<p align="center">
<img src="docs/screenshots/heightmap-noise.png" width="960" />
<img src="docs/screenshots/heightmap-noise.png" alt="Heightmap Surface Noise" width="960" />
</p>
<p align="center">
<img src="docs/screenshots/normal-map.png" width="960" />
<img src="docs/screenshots/normal-map.png" alt="Albedo Texture + Normal map" width="960" />
</p>
The repo mostly contains branding assets. More gameplay screenshots coming soon, assuming I stop rewriting the engine every Tuesday.
Right now the repo only contains branding assets. I haven't added gameplay screenshots yet.
To add your own: put them in
If you want to add screenshots that show directly on the README, the easiest thing is to just drop images somewhere like:
```
docs/screenshots
docs/screenshots/WHATEVER.png
```
then embed with
Then embed them in the README like:
```
![image](docs/screenshots/my_face.png)
```md
![WHATEVER](docs/screenshots/WHATEVER.png)
```
## What FastVoxel Actually Does
The engine generates a voxel world using chunk columns.
The surprisingly simple pipeline:
Rough pipeline looks like this:
1. `SurfaceGenerator` decides if a voxel is solid.
2. `ChunkColumn` stacks chunks vertically.
3. Each `Chunk` stores voxels in bitpacked `u32`s.
4. `Mesher` converts only the exposed voxel faces into triangles.
5. `Renderer` throws those triangles into Godot's scene tree.
6. The world updates chunks as the player moves around.
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.
Everything is modular because future "me", will absolutely regret today's design decisions and might decide to get back into the cave and rewrite the engine again.
The idea is to keep generation, storage, and meshing fairly modular so different strategies can be swapped in later.
## What FastVoxel _Doesn't_ Do (Yet)
## What FastVoxel "Doesn't" do
1. No compute shaders or GPU meshing. This is an OS-Thread party.
2. No pervertex AO on the greedy mesher.
(Because it's complicated and my last functioning brain cell is currently writing this.)
3. Doesn't cull neighboring chunk faces yet because chunks don't talk to each other.
(They're socially anxious.)
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
Terrain = columns of chunks.
Terrain is divided into columns of chunks.
Defaults:
Current defaults:
- chunk size: `32 × 32 × 32`
- chunks load/unload based on render distance (default: 8)
- `CHUNK_SIZE = 32`
- each chunk = `32 × 32 × 32` voxels
- columns stack multiple chunks vertically like a hamburger
- chunk loading/unloading happens around the player based on render distance
Relevant files:
@@ -101,32 +94,291 @@ Relevant files:
### Bit-packed voxel storage
Instead of storing a big struct per voxel, we cram voxels into `u32`s.
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`.
Each voxel = **1 bit**:
right now a voxel is basically:
- `0` => air
- `1` => solid
- `0` -> air
- `1` -> solid
A single `u32` stores 32 voxels (very memory-friendly).
This keeps memory usage low and makes lookups very cheap.
A 32×32×32 chunk:
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.
- each row (32 voxels) = `u32`
- each layer = 32 rows = 32 `u32`s
- whole chunk = 32 × 32 = **1024 `u32`s**
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).
This layout is amazing for meshing because checking solid/air is basically:
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
```
bit = (row >> x) & 1
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
```
Since we use 1 bit instead of a whole struct, one could argu the memory usage is ~32× lower.
Important, because RAM now costs $800 for 16 GB. that's like fifty bucks per gigabyte.
## Build and Install
Materials are handled at the meshing/registry layer, not in voxel storage.
### 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
MIT License. Do whatever, just don't blame me.
This project is licensed under the MIT License.
See the `LICENSE` file for the full text.

View File

@@ -4,25 +4,25 @@
set -e
# Configuration
GODOT_PROJECT="../minekoloft"
LIB_NAME="fastvoxel"
GODOT_PROJECT="../minekoloft" # relative path to Godot project
LIB_NAME="fastvoxel" # your library name
DEST_LINUX="$GODOT_PROJECT/addons/fastvoxel/bin/linux"
DEST_WINDOWS="$GODOT_PROJECT/addons/fastvoxel/bin/windows"
# Build for Linux (native)
echo "Building for Linux (native)..."
cargo build --release --target x86_64-unknown-linux-gnu
cargo build --release
# Copy the shared library
TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
cp "target/x86_64-unknown-linux-gnu/release/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)
# 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
mkdir -p "$DEST_WINDOWS"
cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
echo "Copied to $DEST_WINDOWS"
# echo "Building for Windows (cross-compile)..."
# cargo build --release --target x86_64-pc-windows-gnu
# mkdir -p "$DEST_WINDOWS"
# cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
# echo "Copied to $DEST_WINDOWS"
echo "Done."

View File

@@ -1,220 +0,0 @@
# 📄 Introduction into FastVoxel
## Surface generation
Default generator: `SimpleSurfaceGenerator`
It uses `fastnoise-lite` to create a heightmap, then fills voxel columns from the bottom up.
Files:
- `src/generation/generator.rs`
- `src/generation/simple_heightmap.rs`
Generation uses traits, so you can plug in whatever mumbo jumbo nonsense you want later: caves, biomes, multiverse, whatever.
## Meshing
Multiple meshers exist because one was simply not enough to satisfy my unhealthy obsession with "doing it right this time."
[engine development perfectionism disorder. there's no cure.]
- `CullingMesher` basic visible-face meshing
- `TexturedMesher` UV-aware cube meshing (Minecraft basically)
- `BinaryGreedyMesher` experimental optimization attempt
Files:
- `src/meshing/mesher.rs`
- `src/meshing/textured_mesher.rs`
- `src/meshing/binary_greedy_mesher.rs`
The textured mesher is currently the most practical one because it respects UVs and etc. simply because you can define the material for it.
It's not optimized though.
## Godot integration
The engine exposes several handy APIs for the editor:
- `WorldConfig` generation settings
- `VoxelRegistry` voxel definitions and atlas stuff
- `World` main terrain node
- `WorldPlugin` editor 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, manager
├── editor/ # Godot resources + editor plugin
├── generation/ # terrain generators
├── meshing/ # meshing algorithems
├── rendering/ # mesh to Godot conversion
├── terrain/ # I'm not so sure
└── voxel/ # registry + older voxel logic
```
## Build and Install
Requirements:
- Rust toolchain
- Godot 4 project with GDExtension
- Optional sibling project for `build.sh` to work.
### Build the extension
```
cargo build --release
```
### Helper script
`build.sh` builds & copies the extension to:
```
../minekoloft/addons/fastvoxel/bin/linux
```
Run with:
```
./build.sh
```
If your project is elsewhere, edit the path.
## Using It in Godot
Typical use:
1. build the Rust extension
2. install addon in your Godot project
3. create `WorldConfig`
4. create `VoxelRegistry`
5. add `World` node
6. assign config + registry
7. generate terrain
8. Call the "update with player position" API every frame. Spam it. It returns early and won't yell at you.
### WorldConfig
Contains:
- chunk_size
- render_distance
- worker_threads
- seed
- frequency
- terrain_height
Defaults:
- chunk: `32,32,32`
- render dist: `8`
- threads: `4`
- seed: `1234`
- freq: `0.03`
- terrain height: `6`
### VoxelRegistry
Each voxel defines:
- type
- atlas size
- tile index per face (top, bottom, left, right, front, back)
Mesher uses this for UVs.
### Runtime API
World node provides:
- `generate_terrain()`
- `regenerate_terrain()`
- `clear_terrain()`
- `update_from_gdscript(player_world_pos)`
GDScript example:
```gdscript
@onready var world = $World
@onready var player = $Player
func _ready():
world.generate_terrain()
func _process(_delta):
world.update_from_gdscript(player.global_position)
```
## Engine-Level API Notes
### Generation trait
```rust
pub trait SurfaceGenerator: Send + Sync {
fn generate(&self, column: &mut ChunkColumn);
fn sample_voxel(&self, pos: Vector3i) -> bool;
}
```
### Mesher trait
```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;
}
```
### ChunkManager
Handles:
- chunk lifetime
- generation around player
- unloading distant chunks
- meshing
- sending meshes to renderer
- attaching instances to scene
Basically the project's overworked intern.
## Current State
Mid-development, active refactors, might panic on unwrap.
Direction is clear though:
- compact chunk storage
- trait-based generation
- modular meshing
- Godot resource workflow
- runtime streaming
## Why This Document Exists
Because why not? If you actually wanted to read the code, you wouldn't be here.
## Future Work
- finish terrain/world refactor
- more screenshots
- stable API for godot
- multithreaded chunk gen (**GOD HELP**)
- Better Godot material integration for voxel meshes (so things look less like programmer art).
- fix the greedy meshing
- sample Godot project

View File

@@ -2,14 +2,6 @@ use std::collections::HashMap;
use crate::{chunk::Chunk, runtime::types::WorldConfig};
pub enum RuntimeState {
Idle,
ReadingStorage,
Generating,
Meshing,
SavingToStorage,
}
pub struct Runtime {
chunks: HashMap<(i32, i32), Chunk>,
// renderer: Renderer,