+ atomic build.sh
+ implemented editor buttons
This commit is contained in:
16
build.sh
16
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."
|
||||
@@ -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
125
src/editor/editor.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod editor;
|
||||
pub mod voxel_registry;
|
||||
|
||||
pub use editor::WorldPlugin;
|
||||
pub use voxel_registry::VoxelRegistry;
|
||||
|
||||
@@ -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},
|
||||
@@ -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);
|
||||
|
||||
22
src/world.rs
22
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<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 {
|
||||
|
||||
Reference in New Issue
Block a user