Compare commits
3 Commits
8a635640c5
...
world_mana
| Author | SHA1 | Date | |
|---|---|---|---|
| c1f240dd32 | |||
| ae7d82a542 | |||
| a1acd221a0 |
370
README.md
370
README.md
@@ -4,87 +4,94 @@
|
|||||||
<img src="./logo-cropped.svg" alt="FastVoxel logo" width="180" />
|
<img src="./logo-cropped.svg" alt="FastVoxel logo" width="180" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**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.
|
**FastVoxel** is a voxel engine for Godot written in Rust using GDExtension.
|
||||||
|
Main goals are:
|
||||||
|
|
||||||
This repo contains the engine/GDExtension side of the project.
|
- 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.
|
||||||
|
|
||||||
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?)
|
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.
|
||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
- Rust-based Godot 4 GDExtension
|
- Rust-based Godot 4 GDExtension
|
||||||
- chunked voxel mesh pipeline
|
- Chunked voxel mesh pipeline
|
||||||
- bit-packed voxel storage (solid / air)
|
- Bit-packed voxel storage (solid/air, liquid, whatever)
|
||||||
- procedural terrain using `fastnoise-lite`
|
- Procedural terrain via `fastnoise-lite`
|
||||||
- chunk streaming around the player
|
- Chunk streaming around the player
|
||||||
- runtime cube meshing with texture atlas support
|
- Runtime cube meshing with texture atlas support (up to 1024x1024)
|
||||||
- Godot editor resources for config + voxel registry
|
- Godot editor resources for config + voxel registry
|
||||||
|
|
||||||
## Soon:
|
## Soon
|
||||||
|
|
||||||
- Multi-Threaded chunk meshing and world generation using a `Work Stealing` Thread pool with divide‑and‑conquer parallelism.
|
- Multi-threaded chunk meshing & generation using a **work stealing** thread pool with divide‑and‑conquer parallelism.
|
||||||
|
(Some C dev just segfaulted reading that.)
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/screenshots/colors.png" alt="Albedo Vertex color" width="960" />
|
<img src="docs/screenshots/colors.png" width="960" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/screenshots/heightmap-noise.png" alt="Heightmap Surface Noise" width="960" />
|
<img src="docs/screenshots/heightmap-noise.png" width="960" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/screenshots/normal-map.png" alt="Albedo Texture + Normal map" width="960" />
|
<img src="docs/screenshots/normal-map.png" width="960" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Right now the repo only contains branding assets. I haven't added gameplay screenshots yet.
|
The repo mostly contains branding assets. More gameplay screenshots coming soon, assuming I stop rewriting the engine every Tuesday.
|
||||||
|
|
||||||
If you want to add screenshots that show directly on the README, the easiest thing is to just drop images somewhere like:
|
To add your own: put them in
|
||||||
|
|
||||||
```
|
```
|
||||||
docs/screenshots/WHATEVER.png
|
docs/screenshots
|
||||||
```
|
```
|
||||||
|
|
||||||
Then embed them in the README like:
|
then embed with
|
||||||
|
|
||||||
```md
|
```
|
||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
## What FastVoxel Actually Does
|
## What FastVoxel Actually Does
|
||||||
|
|
||||||
The engine generates a voxel world using chunk columns.
|
The engine generates a voxel world using chunk columns.
|
||||||
|
|
||||||
Rough pipeline looks like this:
|
The surprisingly simple pipeline:
|
||||||
|
|
||||||
1. A `SurfaceGenerator` decides if a voxel is solid or not.
|
1. `SurfaceGenerator` decides if a voxel is solid.
|
||||||
2. A `ChunkColumn` holds a vertical stack of chunks.
|
2. `ChunkColumn` stacks chunks vertically.
|
||||||
3. Each `Chunk` stores voxel occupancy in a compact bit-packed format.
|
3. Each `Chunk` stores voxels in bit‑packed `u32`s.
|
||||||
4. A `Mesher` converts visible voxel faces into triangle meshes.
|
4. `Mesher` converts only the exposed voxel faces into triangles.
|
||||||
5. A `Renderer` turns those meshes into Godot `MeshInstance3D` nodes.
|
5. `Renderer` throws those triangles into Godot's scene tree.
|
||||||
6. A world node updates loaded terrain as the player moves around.
|
6. The world updates chunks as the player moves around.
|
||||||
|
|
||||||
The idea is to keep generation, storage, and meshing fairly modular so different strategies can be swapped in later.
|
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.
|
||||||
|
|
||||||
## What FastVoxel "Doesn't" do
|
## What FastVoxel _Doesn't_ Do (Yet)
|
||||||
|
|
||||||
1. The engine doesn't use compute shaders or any kind of GPU accelerated meshing algorithem.
|
1. No compute shaders or GPU meshing. This is an OS-Thread party.
|
||||||
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)
|
2. No per‑vertex AO on the greedy mesher.
|
||||||
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).
|
(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.)
|
||||||
|
|
||||||
## Core Concepts
|
## Core Concepts
|
||||||
|
|
||||||
### Chunked world layout
|
### Chunked world layout
|
||||||
|
|
||||||
Terrain is divided into columns of chunks.
|
Terrain = columns of chunks.
|
||||||
|
|
||||||
Current defaults:
|
Defaults:
|
||||||
|
|
||||||
- `CHUNK_SIZE = 32`
|
- chunk size: `32 × 32 × 32`
|
||||||
- each chunk = `32 × 32 × 32` voxels
|
- chunks load/unload based on render distance (default: 8)
|
||||||
- columns stack multiple chunks vertically like a hamburger
|
|
||||||
- chunk loading/unloading happens around the player based on render distance
|
|
||||||
|
|
||||||
Relevant files:
|
Relevant files:
|
||||||
|
|
||||||
@@ -94,291 +101,32 @@ Relevant files:
|
|||||||
|
|
||||||
### Bit-packed voxel storage
|
### Bit-packed voxel storage
|
||||||
|
|
||||||
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`.
|
Instead of storing a big struct per voxel, we cram voxels into `u32`s.
|
||||||
|
|
||||||
right now a voxel is basically:
|
Each voxel = **1 bit**:
|
||||||
|
|
||||||
- `0` -> air
|
- `0` => air
|
||||||
- `1` -> solid
|
- `1` => solid
|
||||||
|
|
||||||
This keeps memory usage low and makes lookups very cheap.
|
A single `u32` stores 32 voxels (very memory-friendly).
|
||||||
|
|
||||||
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.
|
A 32×32×32 chunk:
|
||||||
|
|
||||||
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).
|
- each row (32 voxels) = `u32`
|
||||||
|
- each layer = 32 rows = 32 `u32`s
|
||||||
|
- whole chunk = 32 × 32 = **1024 `u32`s**
|
||||||
|
|
||||||
Material differences are currently handled at the meshing/registry layer instead of inside the voxel storage itself.
|
This layout is amazing for meshing because checking solid/air is basically:
|
||||||
|
|
||||||
### 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/
|
bit = (row >> x) & 1
|
||||||
├── 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
|
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.
|
||||||
|
|
||||||
### Requirements
|
Materials are handled at the meshing/registry layer, not in voxel storage.
|
||||||
|
|
||||||
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
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
MIT License. Do whatever, just don't blame me.
|
||||||
|
|
||||||
See the `LICENSE` file for the full text.
|
|
||||||
|
|||||||
18
build.sh
18
build.sh
@@ -4,25 +4,25 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
GODOT_PROJECT="../minekoloft" # relative path to Godot project
|
GODOT_PROJECT="../minekoloft"
|
||||||
LIB_NAME="fastvoxel" # your library name
|
LIB_NAME="fastvoxel"
|
||||||
DEST_LINUX="$GODOT_PROJECT/addons/fastvoxel/bin/linux"
|
DEST_LINUX="$GODOT_PROJECT/addons/fastvoxel/bin/linux"
|
||||||
DEST_WINDOWS="$GODOT_PROJECT/addons/fastvoxel/bin/windows"
|
DEST_WINDOWS="$GODOT_PROJECT/addons/fastvoxel/bin/windows"
|
||||||
|
|
||||||
# Build for Linux (native)
|
# Build for Linux (native)
|
||||||
echo "Building for Linux (native)..."
|
echo "Building for Linux (native)..."
|
||||||
cargo build --release
|
cargo build --release --target x86_64-unknown-linux-gnu
|
||||||
# Copy the shared library
|
# Copy the shared library
|
||||||
TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
|
TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
|
||||||
cp "target/release/lib${LIB_NAME}.so" "$TMP"
|
cp "target/x86_64-unknown-linux-gnu/release/lib${LIB_NAME}.so" "$TMP"
|
||||||
mv "$TMP" "$DEST_LINUX/lib${LIB_NAME}.so"
|
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
|
# Uncomment the following lines if you have the Windows target installed
|
||||||
# echo "Building for Windows (cross-compile)..."
|
echo "Building for Windows (cross-compile)..."
|
||||||
# cargo build --release --target x86_64-pc-windows-gnu
|
cargo build --release --target x86_64-pc-windows-gnu
|
||||||
# mkdir -p "$DEST_WINDOWS"
|
mkdir -p "$DEST_WINDOWS"
|
||||||
# cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
|
cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
|
||||||
# echo "Copied to $DEST_WINDOWS"
|
echo "Copied to $DEST_WINDOWS"
|
||||||
|
|
||||||
echo "Done."
|
echo "Done."
|
||||||
220
docs/INTRODUCTION.MD
Normal file
220
docs/INTRODUCTION.MD
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# 📄 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
|
||||||
@@ -2,6 +2,14 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::{chunk::Chunk, runtime::types::WorldConfig};
|
use crate::{chunk::Chunk, runtime::types::WorldConfig};
|
||||||
|
|
||||||
|
pub enum RuntimeState {
|
||||||
|
Idle,
|
||||||
|
ReadingStorage,
|
||||||
|
Generating,
|
||||||
|
Meshing,
|
||||||
|
SavingToStorage,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
chunks: HashMap<(i32, i32), Chunk>,
|
chunks: HashMap<(i32, i32), Chunk>,
|
||||||
// renderer: Renderer,
|
// renderer: Renderer,
|
||||||
|
|||||||
Reference in New Issue
Block a user