+ 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)..."
|
echo "Building for Linux (native)..."
|
||||||
cargo build --release
|
cargo build --release
|
||||||
# Copy the shared library
|
# Copy the shared library
|
||||||
mkdir -p "$DEST_LINUX"
|
TMP="$DEST_LINUX/lib${LIB_NAME}.so.tmp"
|
||||||
cp "target/release/lib${LIB_NAME}.so" "$DEST_LINUX/"
|
cp "target/release/lib${LIB_NAME}.so" "$TMP"
|
||||||
echo "Copied to $DEST_LINUX"
|
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
|
||||||
mkdir -p "$DEST_WINDOWS"
|
# mkdir -p "$DEST_WINDOWS"
|
||||||
cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
|
# cp "target/x86_64-pc-windows-gnu/release/${LIB_NAME}.dll" "$DEST_WINDOWS/"
|
||||||
echo "Copied to $DEST_WINDOWS"
|
# echo "Copied to $DEST_WINDOWS"
|
||||||
|
|
||||||
echo "Done."
|
echo "Done."
|
||||||
@@ -191,7 +191,7 @@ impl ChunkManager {
|
|||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
// Use render_chunk (with colors) instead of render_chunk_with_uvs
|
// 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());
|
godot_print!("Rendering Took: {}μs", start.elapsed().as_micros());
|
||||||
self.pending_mesh_instances.push(mesh_instance);
|
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 mod voxel_registry;
|
||||||
|
|
||||||
|
pub use editor::WorldPlugin;
|
||||||
pub use voxel_registry::VoxelRegistry;
|
pub use voxel_registry::VoxelRegistry;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Instant};
|
|||||||
use crate::chunk::{self, Chunk, ChunkMesh};
|
use crate::chunk::{self, Chunk, ChunkMesh};
|
||||||
use godot::{
|
use godot::{
|
||||||
classes::{
|
classes::{
|
||||||
ArrayMesh, Material, MeshInstance3D, ResourceLoader, RenderingServer, StandardMaterial3D,
|
ArrayMesh, Material, MeshInstance3D, RenderingServer, ResourceLoader, StandardMaterial3D,
|
||||||
base_material_3d::{CullMode, Flags, ShadingMode, Transparency},
|
base_material_3d::{CullMode, Flags, ShadingMode, Transparency},
|
||||||
geometry_instance_3d::ShadowCastingSetting,
|
geometry_instance_3d::ShadowCastingSetting,
|
||||||
mesh::{ArrayType, PrimitiveType},
|
mesh::{ArrayType, PrimitiveType},
|
||||||
@@ -212,6 +212,13 @@ impl Renderer {
|
|||||||
self.mesh_instances.clear();
|
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)) {
|
pub fn remove_chunk(&mut self, chunk_position: (i32, i32, i32)) {
|
||||||
if let Some(instance) = self.mesh_instances.remove(&chunk_position) {
|
if let Some(instance) = self.mesh_instances.remove(&chunk_position) {
|
||||||
self.mesh_instance_pool.push(instance);
|
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 godot::prelude::*;
|
||||||
|
|
||||||
use crate::chunk::ChunkManager;
|
use crate::chunk::ChunkManager;
|
||||||
use crate::chunk::chunk::CHUNK_SIZE;
|
|
||||||
use crate::editor::VoxelRegistry;
|
use crate::editor::VoxelRegistry;
|
||||||
use crate::generation::{HeightmapGenerator, WorldGenerator};
|
use crate::generation::{HeightmapGenerator, WorldGenerator};
|
||||||
use crate::meshing::mesher::CullingMesher;
|
use crate::meshing::{Mesher, TexturedMesher};
|
||||||
use crate::meshing::{BinaryGreedyMesher, Mesher};
|
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(base=Node3D,tool)]
|
#[class(base=Node3D,tool)]
|
||||||
struct World {
|
pub struct World {
|
||||||
base: Base<Node3D>,
|
base: Base<Node3D>,
|
||||||
|
|
||||||
// ===== RENDERING GROUP =====
|
// ===== RENDERING GROUP =====
|
||||||
@@ -55,7 +53,7 @@ impl INode3D for World {
|
|||||||
godot_print!("🧊 Hello from FastVoxel");
|
godot_print!("🧊 Hello from FastVoxel");
|
||||||
|
|
||||||
let generator = Box::new(HeightmapGenerator::new(1234, 0.03, 5));
|
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();
|
let chunk_manager = ChunkManager::new();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -108,7 +106,6 @@ impl INode3D for World {
|
|||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl World {
|
impl World {
|
||||||
#[func]
|
#[func]
|
||||||
|
|
||||||
fn generate_terrain(&mut self) {
|
fn generate_terrain(&mut self) {
|
||||||
match &self.registry {
|
match &self.registry {
|
||||||
None => {
|
None => {
|
||||||
@@ -134,17 +131,28 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[func]
|
||||||
fn regenerate_terrain(&mut self) {
|
fn regenerate_terrain(&mut self) {
|
||||||
godot_print!("Regenerating terrain...");
|
godot_print!("Regenerating terrain...");
|
||||||
|
|
||||||
self.chunk_manager.clear();
|
self.chunk_manager.clear();
|
||||||
|
|
||||||
// Create textured mesher
|
// Create textured mesher
|
||||||
self.mesher = Box::new(CullingMesher::new());
|
self.mesher = Box::new(TexturedMesher::new());
|
||||||
|
|
||||||
self.generate_terrain();
|
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]
|
#[func]
|
||||||
fn update_from_gdscript(&mut self, player_world_pos: Vector3) {
|
fn update_from_gdscript(&mut self, player_world_pos: Vector3) {
|
||||||
match &self.registry {
|
match &self.registry {
|
||||||
|
|||||||
Reference in New Issue
Block a user