diff --git a/README.md b/README.md index ae7e8a7..c09d5ca 100644 --- a/README.md +++ b/README.md @@ -4,87 +4,94 @@ FastVoxel logo

-**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 - Rust-based Godot 4 GDExtension -- 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 +- 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) - 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

- Albedo Vertex color +

- Heightmap Surface Noise +

- Albedo Texture + Normal map +

-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 -![WHATEVER](docs/screenshots/WHATEVER.png) +``` +![image](docs/screenshots/my_face.png) ``` ## What FastVoxel Actually Does The engine generates a voxel world using chunk columns. -Rough pipeline looks like this: +The suspiciously‑simple pipeline: -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. +1. `SurfaceGenerator` decides if a voxel is solid. +2. `ChunkColumn` stacks chunks vertically. +3. Each `Chunk` stores voxels in bit‑packed `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. -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. -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). +1. No compute shaders or GPU meshing. This is an OS-Thread party. +2. No per‑vertex 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.) ## Core Concepts ### Chunked world layout -Terrain is divided into columns of chunks. +Terrain = columns of chunks. -Current defaults: +Defaults: -- `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 +- chunk size: `32 × 32 × 32` +- chunks load/unload based on render distance (default: 8) Relevant files: @@ -94,291 +101,32 @@ Relevant files: ### 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 -- `1` -> solid +- `0` => air +- `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. - -### 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 +This layout is amazing for meshing because checking solid/air is basically: ``` -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 +bit = (row >> x) & 1 ``` -## 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, and I'm not emotionally prepared for that. -### 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 +Materials are handled at the meshing/registry layer, not in voxel storage. ## License -This project is licensed under the MIT License. - -See the `LICENSE` file for the full text. +MIT License. Do whatever, just don't blame me. diff --git a/docs/INTRODUCTION.MD b/docs/INTRODUCTION.MD new file mode 100644 index 0000000..928a409 --- /dev/null +++ b/docs/INTRODUCTION.MD @@ -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