+ atomic build.sh

+ implemented editor buttons
This commit is contained in:
2026-03-16 18:36:37 +03:30
parent a47f1f9818
commit 880362cc03
6 changed files with 160 additions and 18 deletions

View File

@@ -191,7 +191,7 @@ impl ChunkManager {
let start = Instant::now();
// Use render_chunk (with colors) instead of render_chunk_with_uvs
let mesh_instance = self.renderer.render_chunk(chunk, &mesh);
let mesh_instance = self.renderer.render_chunk_with_uvs(chunk, &mesh);
godot_print!("Rendering Took: {}μs", start.elapsed().as_micros());
self.pending_mesh_instances.push(mesh_instance);

125
src/editor/editor.rs Normal file
View File

@@ -0,0 +1,125 @@
use godot::classes::EditorInterface;
use godot::classes::EditorPlugin;
use godot::classes::IEditorPlugin;
use godot::classes::ImageTexture;
use godot::classes::MenuButton;
use godot::classes::ResourceLoader;
use godot::classes::Texture2D;
use godot::classes::editor_plugin::CustomControlContainer;
use godot::prelude::*;
use crate::world::World;
#[derive(GodotClass)]
#[class(init, base=EditorPlugin, tool)]
pub struct WorldPlugin {
#[base]
base: Base<EditorPlugin>,
toolbar_button: Option<Gd<MenuButton>>,
}
#[godot_api]
impl WorldPlugin {
// Called when a menu item is selected
#[func]
fn on_menu_item_pressed(&self, id: i32) {
let mut world = match self.find_world_node() {
Some(world) => world,
None => {
godot_error!(
"No World node found in the current scene. Make sure it's named 'World' and is a child of the scene root."
);
return;
}
};
match id {
0 => {
world.call("clear_terrain", &[]);
world.call("generate_terrain", &[]);
}
1 => {
world.call("clear_terrain", &[]);
}
_ => {
godot_print!("Unknown item");
}
}
// Here you can call your actual world management methods.
// For example:
// if let Some(world_manager) = self.get_world_manager() {
// match id {
// 0 => world_manager.call("rebuild_world", &[]),
// 1 => world_manager.call("clear_world", &[]),
// 2 => world_manager.call("reload_chunks", &[]),
// _ => (),
// }
// }
}
fn find_world_node(&self) -> Option<Gd<World>> {
let editor = EditorInterface::singleton();
let scene_root = editor.get_edited_scene_root()?;
// Assuming the World node is a direct child of the root and named "World"
scene_root.try_get_node_as::<World>("World")
}
}
#[godot_api]
impl IEditorPlugin for WorldPlugin {
fn enter_tree(&mut self) {
// Create the MenuButton
let mut menu_button = MenuButton::new_alloc();
menu_button.set_text("FastVoxel"); // Button label
// menu_button.set_flat(false); // Make it look like a normal button (optional)
// --- Load an SVG icon and set it ---
// Path to your SVG file (relative to the project, e.g., in addons/fastvoxel/fastvoxel.svg)
if let Some(icon) = ResourceLoader::singleton().load("res://addons/fastvoxel/fastvoxel.svg")
{
let icon_texture: Gd<Texture2D> = icon.cast(); // Cast to Texture2D
// Get image data, resize, create new texture
let mut image = icon_texture.get_image(); // Requires Texture2D to have image data
image
.as_mut()
.expect("failed to load the fastvoxel icon")
.resize(24, 24);
let small_texture = ImageTexture::create_from_image(image.as_ref());
menu_button.set_button_icon(small_texture.as_ref());
} else {
godot_error!("Failed to load plugin icon");
}
// --- Build the dropdown menu ---
let mut popup = menu_button.get_popup(); // Returns a PopupMenu
popup.as_mut().expect("no popup").add_item("Rebuild World"); // item text, id
popup.as_mut().expect("no popup").add_item("Clear World");
popup.as_mut().expect("no popup").add_item("Reload Chunks");
// You can also add separators, submenus, etc.
// Connect the popup's "id_pressed" signal to our handler
let callable = self.base().callable("on_menu_item_pressed");
popup.unwrap().connect("id_pressed", &callable);
// Add the button to the editor's main toolbar (top row, next to play buttons)
self.base_mut().add_control_to_container(
CustomControlContainer::SPATIAL_EDITOR_MENU,
menu_button.to_godot(),
);
// Store the button reference so we can remove it later
self.toolbar_button = Some(menu_button);
}
fn exit_tree(&mut self) {
// Clean up: remove the button from the toolbar
if let Some(button) = self.toolbar_button.take() {
self.base_mut().remove_control_from_container(
CustomControlContainer::SPATIAL_EDITOR_MENU,
button.to_godot(),
);
// The button will be freed automatically when no longer referenced
}
}
}

View File

@@ -1,3 +1,5 @@
pub mod editor;
pub mod voxel_registry;
pub use editor::WorldPlugin;
pub use voxel_registry::VoxelRegistry;

View File

@@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Instant};
use crate::chunk::{self, Chunk, ChunkMesh};
use godot::{
classes::{
ArrayMesh, Material, MeshInstance3D, ResourceLoader, RenderingServer, StandardMaterial3D,
ArrayMesh, Material, MeshInstance3D, RenderingServer, ResourceLoader, StandardMaterial3D,
base_material_3d::{CullMode, Flags, ShadingMode, Transparency},
geometry_instance_3d::ShadowCastingSetting,
mesh::{ArrayType, PrimitiveType},
@@ -183,7 +183,7 @@ impl Renderer {
instance.set_cast_shadows_setting(ShadowCastingSetting::ON);
instance
});
mesh_instance.set_mesh(&array_mesh);
// Position the mesh instance (chunk size is now 32)
@@ -212,6 +212,13 @@ impl Renderer {
self.mesh_instances.clear();
}
pub fn clear_and_destroy(&mut self) {
for (_, instance) in self.mesh_instances.drain() {
instance.free();
}
self.mesh_instances.clear();
}
pub fn remove_chunk(&mut self, chunk_position: (i32, i32, i32)) {
if let Some(instance) = self.mesh_instances.remove(&chunk_position) {
self.mesh_instance_pool.push(instance);

View File

@@ -2,15 +2,13 @@ use godot::classes::{INode3D, VoxelGi};
use godot::prelude::*;
use crate::chunk::ChunkManager;
use crate::chunk::chunk::CHUNK_SIZE;
use crate::editor::VoxelRegistry;
use crate::generation::{HeightmapGenerator, WorldGenerator};
use crate::meshing::mesher::CullingMesher;
use crate::meshing::{BinaryGreedyMesher, Mesher};
use crate::meshing::{Mesher, TexturedMesher};
#[derive(GodotClass)]
#[class(base=Node3D,tool)]
struct World {
pub struct World {
base: Base<Node3D>,
// ===== RENDERING GROUP =====
@@ -55,7 +53,7 @@ impl INode3D for World {
godot_print!("🧊 Hello from FastVoxel");
let generator = Box::new(HeightmapGenerator::new(1234, 0.03, 5));
let mesher = Box::new(CullingMesher::new());
let mesher = Box::new(TexturedMesher::new());
let chunk_manager = ChunkManager::new();
Self {
@@ -108,7 +106,6 @@ impl INode3D for World {
#[godot_api]
impl World {
#[func]
fn generate_terrain(&mut self) {
match &self.registry {
None => {
@@ -134,17 +131,28 @@ impl World {
}
}
#[func]
fn regenerate_terrain(&mut self) {
godot_print!("Regenerating terrain...");
self.chunk_manager.clear();
// Create textured mesher
self.mesher = Box::new(CullingMesher::new());
self.mesher = Box::new(TexturedMesher::new());
self.generate_terrain();
}
#[func]
fn clear_terrain(&mut self) {
godot_print!("Clearing Terrain...");
self.chunk_manager.clear();
self.chunk_manager.pending_mesh_instances.clear();
self.chunk_manager.renderer.clear_and_destroy();
}
#[func]
fn update_from_gdscript(&mut self, player_world_pos: Vector3) {
match &self.registry {