diff --git a/build.sh b/build.sh index e12a397..1f70ed1 100755 --- a/build.sh +++ b/build.sh @@ -13,16 +13,16 @@ DEST_WINDOWS="$GODOT_PROJECT/addons/fastvoxel/bin/windows" echo "Building for Linux (native)..." cargo build --release # Copy the shared library -mkdir -p "$DEST_LINUX" -cp "target/release/lib${LIB_NAME}.so" "$DEST_LINUX/" -echo "Copied to $DEST_LINUX" +TMP="$DEST_LINUX/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." \ No newline at end of file diff --git a/src/chunk/chunk_manager.rs b/src/chunk/chunk_manager.rs index 963fffc..7d0d569 100644 --- a/src/chunk/chunk_manager.rs +++ b/src/chunk/chunk_manager.rs @@ -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); diff --git a/src/editor/editor.rs b/src/editor/editor.rs new file mode 100644 index 0000000..11d752f --- /dev/null +++ b/src/editor/editor.rs @@ -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, + toolbar_button: Option>, +} + +#[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> { + 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") + } +} + +#[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 = 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 + } + } +} diff --git a/src/editor/mod.rs b/src/editor/mod.rs index f136d11..21346e9 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,3 +1,5 @@ +pub mod editor; pub mod voxel_registry; +pub use editor::WorldPlugin; pub use voxel_registry::VoxelRegistry; diff --git a/src/rendering/renderer.rs b/src/rendering/renderer.rs index 69a8b59..901726a 100644 --- a/src/rendering/renderer.rs +++ b/src/rendering/renderer.rs @@ -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); diff --git a/src/world.rs b/src/world.rs index 3bc7fc9..b9eedf0 100644 --- a/src/world.rs +++ b/src/world.rs @@ -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, // ===== 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 {