re-working editor resource, chunk_manager and more.
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||||
362
README.md
Normal file
362
README.md
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# FastVoxel
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="./logo-cropped.svg" alt="FastVoxel logo" width="180" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="./logo-cropped.svg" alt="FastVoxel mark" width="220" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
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.
|
||||||
2
build.sh
2
build.sh
@@ -17,7 +17,7 @@ TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
|
|||||||
cp "target/release/lib${LIB_NAME}.so" "$TMP"
|
cp "target/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
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
pub mod editor;
|
|
||||||
pub mod voxel_registry;
|
pub mod voxel_registry;
|
||||||
|
pub mod world_config;
|
||||||
|
pub mod world_node;
|
||||||
|
pub mod world_plugin;
|
||||||
|
|
||||||
pub use voxel_registry::VoxelRegistry;
|
pub use voxel_registry::VoxelRegistry;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use godot::{
|
|||||||
prelude::{Export, GodotClass, GodotConvert, Var},
|
prelude::{Export, GodotClass, GodotConvert, Var},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(GodotConvert, Var, Export, Debug, Clone, Copy)]
|
#[derive(GodotConvert, Var, Export, Debug, Clone, Copy, PartialEq)]
|
||||||
#[godot(via = i64)]
|
#[godot(via = i64)]
|
||||||
pub enum VoxelType {
|
pub enum VoxelType {
|
||||||
Empty,
|
Empty,
|
||||||
@@ -58,26 +58,17 @@ pub struct VoxelModel {
|
|||||||
impl VoxelModel {
|
impl VoxelModel {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_solid(&self) -> bool {
|
pub fn is_solid(&self) -> bool {
|
||||||
match self.voxel_type {
|
self.voxel_type != VoxelType::Empty
|
||||||
VoxelType::Empty => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self.voxel_type {
|
self.voxel_type == VoxelType::Empty
|
||||||
VoxelType::Empty => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_cube(&self) -> bool {
|
pub fn is_cube(&self) -> bool {
|
||||||
match self.voxel_type {
|
self.voxel_type == VoxelType::Cube
|
||||||
VoxelType::Cube => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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::classes::{INode3D, VoxelGi};
|
||||||
use godot::prelude::*;
|
use godot::prelude::*;
|
||||||
|
|
||||||
use crate::chunk::ChunkManager;
|
|
||||||
use crate::editor::VoxelRegistry;
|
use crate::editor::VoxelRegistry;
|
||||||
|
use crate::editor::world_config::WorldConfig;
|
||||||
use crate::generation::SimpleSurfaceGenerator;
|
use crate::generation::SimpleSurfaceGenerator;
|
||||||
use crate::generation::generator::SurfaceGenerator;
|
use crate::generation::generator::SurfaceGenerator;
|
||||||
use crate::meshing::binary_greedy_mesher::BinaryGreedyMesher;
|
|
||||||
use crate::meshing::{Mesher, TexturedMesher};
|
use crate::meshing::{Mesher, TexturedMesher};
|
||||||
|
use crate::terrain::TerrainManager;
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(base=Node3D,tool)]
|
#[class(base=Node3D,tool)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
base: Base<Node3D>,
|
base: Base<Node3D>,
|
||||||
|
|
||||||
// ===== RENDERING GROUP =====
|
#[export_group(name = "Config")]
|
||||||
#[export_group(name = "Rendering")]
|
|
||||||
|
/// World generation and rendering settings
|
||||||
#[export]
|
#[export]
|
||||||
chunk_size: Vector3i,
|
config: Option<Gd<WorldConfig>>,
|
||||||
|
|
||||||
#[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,
|
|
||||||
|
|
||||||
#[export_group(name = "Voxel Registry")]
|
#[export_group(name = "Voxel Registry")]
|
||||||
#[export]
|
#[export]
|
||||||
registry: Option<Gd<VoxelRegistry>>,
|
registry: Option<Gd<VoxelRegistry>>,
|
||||||
|
|
||||||
#[export_group(name = "GI")]
|
// runtime systems
|
||||||
#[export]
|
terrain_manager: TerrainManager,
|
||||||
voxelgi_node: Option<Gd<VoxelGi>>,
|
runtime_generator: Box<dyn SurfaceGenerator>,
|
||||||
|
runtime_mesher: Box<dyn Mesher>,
|
||||||
// World data (temporary)
|
|
||||||
chunk_manager: ChunkManager,
|
|
||||||
surface_generator: Box<dyn SurfaceGenerator>,
|
|
||||||
mesher: Box<dyn Mesher>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl INode3D for World {
|
impl INode3D for World {
|
||||||
fn init(base: Base<Node3D>) -> Self {
|
fn init(base: Base<Node3D>) -> Self {
|
||||||
godot_print!("🧊 Hello from FastVoxel");
|
godot_print!("🧊 FastVoxel Init...");
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
render_distance: 8,
|
|
||||||
chunk_size: Vector3i::new(16, 16, 16),
|
|
||||||
world_seed: 1234,
|
|
||||||
noise_frequency: 0.03,
|
|
||||||
terrain_height: 8,
|
|
||||||
base,
|
base,
|
||||||
surface_generator,
|
config: None,
|
||||||
mesher,
|
|
||||||
chunk_manager,
|
|
||||||
workder_threads: 4,
|
|
||||||
registry: 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.voxelgi_node.is_none() {
|
|
||||||
godot_error!("VoxelGI Node is not selected.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
let surface_generator = Box::new(SimpleSurfaceGenerator::new(
|
||||||
self.world_seed,
|
self.world_seed,
|
||||||
self.noise_frequency,
|
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]
|
#[godot_api]
|
||||||
impl World {
|
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]
|
#[func]
|
||||||
fn generate_terrain(&mut self) {
|
fn generate_terrain(&mut self) {
|
||||||
match &self.registry {
|
match &self.registry {
|
||||||
@@ -8,7 +8,7 @@ use godot::classes::Texture2D;
|
|||||||
use godot::classes::editor_plugin::CustomControlContainer;
|
use godot::classes::editor_plugin::CustomControlContainer;
|
||||||
use godot::prelude::*;
|
use godot::prelude::*;
|
||||||
|
|
||||||
use crate::world::World;
|
use crate::editor::world_node::World;
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(init, base=EditorPlugin, tool)]
|
#[class(init, base=EditorPlugin, tool)]
|
||||||
@@ -93,6 +93,11 @@ impl IEditorPlugin for WorldPlugin {
|
|||||||
|
|
||||||
// --- Build the dropdown menu ---
|
// --- Build the dropdown menu ---
|
||||||
let mut popup = menu_button.get_popup(); // Returns a PopupMenu
|
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("Rebuild World"); // item text, id
|
||||||
popup.as_mut().expect("no popup").add_item("Clear World");
|
popup.as_mut().expect("no popup").add_item("Clear World");
|
||||||
popup.as_mut().expect("no popup").add_item("Reload Chunks");
|
popup.as_mut().expect("no popup").add_item("Reload Chunks");
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use godot::prelude::*;
|
use godot::prelude::*;
|
||||||
|
|
||||||
mod voxel;
|
mod voxel;
|
||||||
mod world;
|
|
||||||
|
|
||||||
mod chunk;
|
mod chunk;
|
||||||
mod editor;
|
mod editor;
|
||||||
mod generation;
|
mod generation;
|
||||||
mod meshing;
|
mod meshing;
|
||||||
mod rendering;
|
mod rendering;
|
||||||
|
mod terrain;
|
||||||
|
|
||||||
struct FastVoxel;
|
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