tirbofish/dropbear · diff
Merge pull request #2 from 4tkbytes/fix_up_guizmo_fuckup
fix: gizmo works now
Signature present but could not be verified.
Unverified
@@ -21,3 +21,4 @@ target .idea/ Cargo.lock dropbear-engine/src/resources/textures/Autism.png +.vscode @@ -1,7 +1,56 @@ [workspace] +package.version = "0.1.2" +package.edition = "2024" +package.license = "GPL-3.0-or-later" +package.repository = "https://github.com/4tkbytes/dropbear-engine" +package.readme = "README.md" + resolver = "3" members = ["dropbear-engine", "eucalyptus", "redback"] +[workspace.dependencies] +anyhow = "1.0" +app_dirs2 = "2.5" +bytemuck = { version = "1.23", features = ["derive"] } +chrono = "0.4" +dropbear-engine = { path = "dropbear-engine" } +egui = "0.32" +egui-toast-fork = "0.18" +egui-wgpu = { version = "0.32", features = ["winit"] } +egui-winit = "0.32" +egui_dnd = "0.13" +egui_dock-fork = { version = "0.17", features = ["serde"] } +egui_extras = { version = "0.32", features = ["all_loaders"] } +env_logger = "0.11" +futures = "0.3" +gilrs = "0.11" +git2 = "0.20" +glam = "0.30" +hecs = {version = "0.10", features = ["serde"]} +log = "0.4" +model_to_image = "0.1" +num-traits = "0.2" +once_cell = "1.21" +pollster = "0.4" +rfd = "0.15.4" +rhai = "1.22" +ron = "0.10.1" +russimp = { version = "3.2", features = ["static-link"] } +serde = { version = "1.0.219", features = ["derive"] } +spin_sleep = "1.3" +tokio = { version = "1", features = ["full"] } +transform-gizmo-egui = { git = "https://github.com/4tkbytes/transform-gizmo"} +wgpu = "25" +winit = { version = "0.30", features = [] } + +[workspace.dependencies.image] +version = "0.25" +default-features = false +features = ["png"] + +[patch.crates-io] +russimp-sys = { git = "https://github.com/4tkbytes/russimp-sys" } + [profile.dev] opt-level = 0 debug = true @@ -10,6 +59,7 @@ incremental = true lto = false [profile.release] +opt-level = 3 lto = true codegen-units = 1 strip = true @@ -11,7 +11,20 @@ If you might have not realised, all the crates/projects names are after Australi ## Build -To build, clone the repository, then build it. It will build in debug mode, and use a lot of packages, so if your CPU is not fast enough for building you should brew a cup of coffee during the build time. +To build, ensure build requirements, clone the repository, then build it. It will build in debug mode, and use a lot of packages, so if your CPU is not fast enough for building you should brew a cup of coffee during the build time. + +With Unix systems (macOS not tested), you will have to download a couple dependencies if building locally: +<!-- If you have a macOS system, please create a PR and add your own implementation. I know you need to use brew, but I don't know what dependencies to install. --> + +```bash +# ubuntu, adapt to your own OS +sudo apt install libudev-dev pkg-config libssl-dev clang + +# if on arm devices where russimp cannot compile +sudo apt install assimp-utils +``` + +After downloading the requirements, you are free to build it using cargo. ```bash git clone git@github.com:4tkbytes/dropbear @@ -1,39 +1,30 @@ [package] name = "dropbear-engine" -version = "0.1.2" -edition = "2024" -license = "GPL-3.0-or-later" description = "A game engine made by 4tkbytes. Thats really it..." -repository = "https://github.com/4tkbytes/dropbear-engine" -readme = "../README.md" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true [dependencies] -anyhow = "1.0.98" -winit = { version = "0.30", features = ["android-native-activity"] } -env_logger = "0.11" -log = "0.4" -wgpu = "25" -pollster = "0.4" -tokio = { version = "1", features = ["full"] } -futures = "0.3" -async-trait = "0.1" -bytemuck = { version = "1.23", features = ["derive"] } -spin_sleep = "1.3" -nalgebra = "0.33" -num-traits = "0.2" -egui = "0.32" -egui-winit = "0.32" -egui-wgpu = { version = "0.32", features = ["winit"] } -egui_extras = { version = "0.32", features = ["all_loaders"] } -gilrs = "0.11" -hecs = {version = "0.10", features = ["serde"]} - -[target.'cfg(any(target_arch = "aarch64", target_arch = "arm"))'.dependencies] -russimp = "3.2" - -[target.'cfg(not(any(target_arch = "aarch64", target_arch = "arm")))'.dependencies] -russimp = { version = "3.2", features = ["prebuilt"] } +anyhow.workspace = true +bytemuck.workspace = true +egui-wgpu.workspace = true +egui-winit.workspace = true +egui.workspace = true +env_logger.workspace = true +futures.workspace = true +gilrs.workspace = true +glam.workspace = true +log.workspace = true +# nalgebra.workspace = true +pollster.workspace = true +russimp.workspace = true +spin_sleep.workspace = true +wgpu.workspace = true +winit.workspace = true [dependencies.image] version = "0.25" @@ -1,4 +1,5 @@ -use nalgebra::{Matrix4, Perspective3, Point3, UnitQuaternion, Vector3}; +use glam::{DMat4, DQuat, DVec3, Mat4}; +// use nalgebra::{Matrix4, Perspective3, Point3, UnitQuaternion, Vector3}; use wgpu::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, ShaderStages, @@ -7,24 +8,24 @@ use wgpu::{ use crate::graphics::Graphics; #[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.0, 0.0, 0.5, 1.0, -); +pub const OPENGL_TO_WGPU_MATRIX: [[f64; 4]; 4] = [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.5, 0.0], + [0.0, 0.0, 0.5, 1.0], +]; #[derive(Default)] pub struct Camera { - pub eye: Point3<f32>, - pub target: Point3<f32>, - pub up: Vector3<f32>, - pub aspect: f32, - pub fov_y: f32, - pub znear: f32, - pub zfar: f32, - pub yaw: f32, - pub pitch: f32, + pub eye: DVec3, + pub target: DVec3, + pub up: DVec3, + pub aspect: f64, + pub fov_y: f64, + pub znear: f64, + pub zfar: f64, + pub yaw: f64, + pub pitch: f64, pub uniform: CameraUniform, buffer: Option<Buffer>, @@ -32,22 +33,25 @@ pub struct Camera { layout: Option<BindGroupLayout>, bind_group: Option<BindGroup>, - pub speed: f32, - pub sensitivity: f32, + pub speed: f64, + pub sensitivity: f64, + + pub view_mat: DMat4, + pub proj_mat: DMat4, } impl Camera { pub fn new( graphics: &Graphics, - eye: Point3<f32>, - target: Point3<f32>, - up: Vector3<f32>, - aspect: f32, - fov_y: f32, - znear: f32, - zfar: f32, - speed: f32, - sensitivity: f32, + eye: DVec3, + target: DVec3, + up: DVec3, + aspect: f64, + fov_y: f64, + znear: f64, + zfar: f64, + speed: f64, + sensitivity: f64, ) -> Self { let uniform = CameraUniform::new(); let mut camera = Self { @@ -79,10 +83,10 @@ impl Camera { pub fn predetermined(graphics: &Graphics) -> Self { Self::new( graphics, - Point3::new(0.0, 1.0, 2.0), - Point3::new(0.0, 0.0, 0.0), - Vector3::y(), - (graphics.state.config.width / graphics.state.config.height) as f32, + DVec3::new(0.0, 1.0, 2.0), + DVec3::new(0.0, 0.0, 0.0), + DVec3::Y, + (graphics.state.config.width / graphics.state.config.height).into(), 45.0, 0.1, 100.0, @@ -91,9 +95,9 @@ impl Camera { ) } - pub fn rotation(&self) -> UnitQuaternion<f32> { - let yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), self.yaw); - let pitch = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.pitch); + pub fn rotation(&self) -> DQuat { + let yaw = DQuat::from_axis_angle(DVec3::Y, self.yaw); + let pitch = DQuat::from_axis_angle(DVec3::X, self.pitch); yaw * pitch } @@ -109,18 +113,29 @@ impl Camera { self.bind_group.as_ref().unwrap() } - pub fn forward(&self) -> Vector3<f32> { + pub fn forward(&self) -> DVec3 { (self.target - self.eye).normalize() } - pub fn position(&self) -> Point3<f32> { + pub fn position(&self) -> DVec3 { self.eye } - fn build_vp(&self) -> Matrix4<f32> { - let view = Matrix4::<f32>::look_at_rh(&self.eye, &self.target, &self.up); - let proj = Perspective3::new(self.aspect, self.fov_y.to_radians(), self.znear, self.zfar); - return OPENGL_TO_WGPU_MATRIX * proj.to_homogeneous() * view; + fn build_vp(&mut self) -> DMat4 { + let view = DMat4::look_at_lh( + self.eye, self.target, self.up + ); + let proj = DMat4::perspective_infinite_reverse_lh( + self.fov_y, + self.aspect, + self.znear + ); + + self.view_mat = view.clone(); + self.proj_mat = proj.clone(); + + let result = DMat4::from_cols_array_2d(&OPENGL_TO_WGPU_MATRIX) * proj * view; + result } pub fn create_bind_group_layout(&mut self, graphics: &Graphics, camera_buffer: Buffer) { @@ -168,7 +183,7 @@ impl Camera { pub fn update_view_proj(&mut self) { let mvp = self.build_vp(); - self.uniform.view_proj = mvp.into(); + self.uniform.view_proj = mvp.as_mat4().to_cols_array_2d(); } pub fn move_forwards(&mut self) { @@ -185,14 +200,15 @@ impl Camera { pub fn move_right(&mut self) { let forward = (self.target - self.eye).normalize(); - let right = forward.cross(&self.up).normalize(); + // LH: right = up.cross(forward) + let right = self.up.cross(forward).normalize(); self.eye += right * self.speed; self.target += right * self.speed; } pub fn move_left(&mut self) { let forward = (self.target - self.eye).normalize(); - let right = forward.cross(&self.up).normalize(); + let right = self.up.cross(forward).normalize(); self.eye -= right * self.speed; self.target -= right * self.speed; } @@ -209,13 +225,13 @@ impl Camera { self.target -= up * self.speed; } - pub fn track_mouse_delta(&mut self, dx: f32, dy: f32) { + pub fn track_mouse_delta(&mut self, dx: f64, dy: f64) { let sensitivity = self.sensitivity; self.yaw += dx * sensitivity; self.pitch -= dy * sensitivity; - let max_pitch = std::f32::consts::FRAC_PI_2 - 0.01; + let max_pitch = std::f64::consts::FRAC_PI_2 - 0.01; self.pitch = self.pitch.clamp(-max_pitch, max_pitch); - let dir = Vector3::new( + let dir = DVec3::new( self.yaw.cos() * self.pitch.cos(), self.pitch.sin(), self.yaw.sin() * self.pitch.cos(), @@ -233,7 +249,7 @@ pub struct CameraUniform { impl CameraUniform { pub fn new() -> Self { Self { - view_proj: Matrix4::<f32>::identity().into(), + view_proj: Mat4::IDENTITY.to_cols_array_2d() } } } @@ -1,6 +1,7 @@ use std::path::PathBuf; -use nalgebra::{Matrix4, UnitQuaternion, Vector3}; +use glam::{DMat4, DQuat, DVec3, Mat4}; +// use nalgebra::{Matrix4, UnitQuaternion, Vector3}; use wgpu::{BindGroup, Buffer, RenderPass, util::DeviceExt}; use crate::{ @@ -22,54 +23,48 @@ pub struct AdoptedEntity { #[derive(Debug, Clone)] pub struct Transform { - pub position: Vector3<f32>, - pub rotation: UnitQuaternion<f32>, - pub scale: Vector3<f32>, + pub position: DVec3, + pub rotation: DQuat, + pub scale: DVec3, } impl Default for Transform { fn default() -> Self { Self { - position: Vector3::new(0.0, 0.0, 0.0), - rotation: UnitQuaternion::identity(), - scale: Vector3::new(1.0, 1.0, 1.0), + position: DVec3::new(0.0, 0.0, 0.0), + rotation: DQuat::IDENTITY, + scale: DVec3::new(1.0, 1.0, 1.0), } } } impl Transform { pub fn new() -> Self { - Self { - position: Vector3::new(0.0, 0.0, 0.0), - rotation: UnitQuaternion::identity(), - scale: Vector3::new(1.0, 1.0, 1.0), - } + Self::default() } - pub fn matrix(&self) -> Matrix4<f32> { - Matrix4::new_translation(&self.position) - * self.rotation.to_homogeneous() - * Matrix4::new_nonuniform_scaling(&self.scale) + pub fn matrix(&self) -> DMat4 { + DMat4::from_scale_rotation_translation(self.scale, self.rotation, self.position) } - pub fn rotate_x(&mut self, angle_rad: f32) { - self.rotation *= UnitQuaternion::from_euler_angles(angle_rad, 0.0, 0.0); + pub fn rotate_x(&mut self, angle_rad: f64) { + self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, angle_rad, 0.0, 0.0); } - pub fn rotate_y(&mut self, angle_rad: f32) { - self.rotation *= UnitQuaternion::from_euler_angles(0.0, angle_rad, 0.0); + pub fn rotate_y(&mut self, angle_rad: f64) { + self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, 0.0, angle_rad, 0.0); } - pub fn rotate_z(&mut self, angle_rad: f32) { - self.rotation *= UnitQuaternion::from_euler_angles(0.0, 0.0, angle_rad); + pub fn rotate_z(&mut self, angle_rad: f64) { + self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, 0.0, 0.0, angle_rad); } - pub fn translate(&mut self, translation: Vector3<f32>) { + pub fn translate(&mut self, translation: DVec3) { self.position += translation; } - pub fn scale(&mut self, scale: Vector3<f32>) { - self.scale.component_mul_assign(&scale); + pub fn scale(&mut self, scale: DVec3) { + self.scale *= scale; } } @@ -97,7 +92,7 @@ impl AdoptedEntity { }], }); - let instance = Instance::new(Vector3::identity(), UnitQuaternion::identity()); + let instance = Instance::new(DVec3::ONE, DQuat::IDENTITY); let instance_buffer = graphics @@ -123,7 +118,7 @@ impl AdoptedEntity { } pub fn update(&mut self, graphics: &Graphics, transform: &Transform) { - self.uniform.model = transform.matrix().into(); + self.uniform.model = transform.matrix().as_mat4().to_cols_array_2d(); if let Some(buffer) = &self.uniform_buffer { graphics @@ -166,7 +161,7 @@ pub struct ModelUniform { impl ModelUniform { pub fn new() -> Self { Self { - model: Matrix4::<f32>::identity().into(), + model: Mat4::IDENTITY.to_cols_array_2d(), } } } @@ -1,8 +1,9 @@ use std::{fs, path::PathBuf}; use egui::Context; +use glam::{DMat4, DQuat, DVec3}; use image::GenericImageView; -use nalgebra::{Matrix4, UnitQuaternion, Vector3}; +// use nalgebra::{Matrix4, UnitQuaternion, Vector3}; use wgpu::{ BindGroup, BindGroupLayout, Buffer, BufferAddress, BufferUsages, Color, CommandEncoder, CompareFunction, DepthBiasState, Device, Extent3d, LoadOp, Operations, RenderPass, @@ -18,7 +19,7 @@ use crate::{ }; pub struct Graphics<'a> { - pub state: &'a State, + pub state: &'a mut State, pub view: &'a TextureView, pub encoder: &'a mut CommandEncoder, pub screen_size: (f32, f32), @@ -27,15 +28,28 @@ pub struct Graphics<'a> { pub const NO_TEXTURE: &'static [u8] = include_bytes!("no-texture.png"); impl<'a> Graphics<'a> { - pub fn new(state: &'a State, view: &'a TextureView, encoder: &'a mut CommandEncoder) -> Self { + pub fn new( + state: &'a mut State, + view: &'a TextureView, + encoder: &'a mut CommandEncoder, + ) -> Self { + let screen_size = (state.config.width as f32, state.config.height as f32); Self { state, view, encoder, - screen_size: (state.config.width as f32, state.config.height as f32), + screen_size, } } + pub fn resize(&mut self, width: u32, height: u32) { + self.state.resize(width, height); + } + + pub fn texture_bind_group(&mut self) -> &wgpu::BindGroupLayout { + &self.state.texture_bind_layout + } + pub fn create_render_pipline( &mut self, shader: &Shader, @@ -84,7 +98,7 @@ impl<'a> Graphics<'a> { depth_stencil: Some(wgpu::DepthStencilState { format: Texture::DEPTH_FORMAT, depth_write_enabled: true, - depth_compare: CompareFunction::Less, + depth_compare: CompareFunction::Greater, stencil: StencilState::default(), bias: DepthBiasState::default(), }), @@ -119,7 +133,7 @@ impl<'a> Graphics<'a> { depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &self.state.depth_texture.view, depth_ops: Some(Operations { - load: LoadOp::Clear(1.0), + load: LoadOp::Clear(0.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, @@ -382,14 +396,14 @@ impl Texture { #[derive(Default)] pub struct Instance { - pub position: Vector3<f32>, - pub rotation: UnitQuaternion<f32>, + pub position: DVec3, + pub rotation: DQuat, buffer: Option<Buffer>, } impl Instance { - pub fn new(position: Vector3<f32>, rotation: UnitQuaternion<f32>) -> Self { + pub fn new(position: DVec3, rotation: DQuat) -> Self { Self { position, rotation, @@ -399,9 +413,9 @@ impl Instance { pub fn to_raw(&self) -> InstanceRaw { let rotation = self.rotation; - let model_matrix = Matrix4::new_translation(&self.position) * rotation.to_homogeneous(); + let model_matrix = DMat4::from_translation(self.position) * DMat4::from_quat(rotation); InstanceRaw { - model: model_matrix.into(), + model: model_matrix.as_mat4().to_cols_array_2d(), } } @@ -409,9 +423,8 @@ impl Instance { self.buffer.as_ref().unwrap() } - pub fn from_matrix(mat: Matrix4<f32>) -> Self { - let position = mat.fixed_view::<3, 1>(0, 3).into(); - let rotation = UnitQuaternion::from_matrix(&mat.fixed_view::<3, 3>(0, 0).into_owned()); + pub fn from_matrix(mat: DMat4) -> Self { + let (_, rotation, position) = mat.to_scale_rotation_translation(); Instance { position, rotation, @@ -8,24 +8,10 @@ pub mod model; pub mod resources; pub mod scene; -pub use async_trait; -pub use bytemuck; -pub use egui; use egui::TextureId; -pub use egui_extras; use egui_wgpu::ScreenDescriptor; use futures::FutureExt; -pub use gilrs; use gilrs::{Gilrs, GilrsBuilder}; -pub use hecs; -pub use log; -pub use nalgebra; -pub use num_traits; -pub use pollster::block_on; -pub use tokio; -pub use wgpu; -pub use winit; - use spin_sleep::SpinSleeper; use std::{ fmt::{self, Display, Formatter}, @@ -238,7 +224,7 @@ Hardware: label: Some("Render Encoder"), }); - let viewport_view = { &self.viewport_texture.view }; + let viewport_view = { &self.viewport_texture.view.clone() }; self.egui_renderer.begin_frame(&self.window); @@ -1,33 +1,43 @@ [package] name = "eucalyptus" -version = "0.1.0" -edition = "2024" -license = "GPL-3.0-or-later" description = "A visual game editor for the dropbear game engine" -repository = "https://github.com/4tkbytes/dropbear-engine" -readme = "README.md" default-run = "eucalyptus" exclude = ["resources"] +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + [dependencies] -serde = { version = "1.0.219", features = ["derive"] } -dropbear-engine = { path = "../dropbear-engine" } -# dropbear-engine = "0.1.2" -rfd = "0.15.4" -git2 = "0.20.2" -anyhow = "1.0" -ron = "0.10.1" -chrono = "0.4.41" -egui-toast-fork = "0.18.0" -egui_dock-fork = { version = "0.17", features = ["serde"] } -egui_dnd = "0.13" -once_cell = "1.21" -app_dirs2 = "2.5" -transform-gizmo-egui = "0.6" +anyhow.workspace = true +app_dirs2.workspace = true +chrono.workspace = true +dropbear-engine.workspace = true +egui-toast-fork.workspace = true +egui.workspace = true +egui_dnd.workspace = true +egui_dock-fork.workspace = true +egui_extras.workspace = true +gilrs.workspace = true +git2.workspace = true +glam.workspace = true +hecs.workspace = true +log.workspace = true +# model_to_image.workspace = true +once_cell.workspace = true +rfd.workspace = true +rhai.workspace = true +ron.workspace = true +serde.workspace = true +tokio.workspace = true +transform-gizmo-egui.workspace = true +wgpu.workspace = true +winit.workspace = true -[dependencies.rhai] -version = "1.22" -features = ["std"] +[package.metadata.cargo-machete] +ignored = ["rhai"] [build-dependencies] anyhow = "1.0" @@ -5,13 +5,18 @@ use std::{ sync::{LazyLock, Mutex}, }; -use dropbear_engine::{ - egui, egui_extras, entity::Transform, graphics::NO_TEXTURE, hecs::{self}, log -}; +use dropbear_engine::{entity::Transform, graphics::NO_TEXTURE}; +use egui; use egui_dock_fork::TabViewer; +use egui_extras; use egui_toast_fork::{Toast, ToastKind}; +use log; +// use nalgebra::{Matrix4, Perspective3, UnitQuaternion, Vector3}; use serde::{Deserialize, Serialize}; -use transform_gizmo_egui::Gizmo; +use transform_gizmo_egui::{ + Gizmo, GizmoConfig, GizmoExt, GizmoMode, + math::{DMat4, DVec3}, +}; use crate::{ APP_INFO, @@ -29,12 +34,14 @@ pub enum EditorTab { pub struct EditorTabViewer<'a> { pub view: egui::TextureId, pub nodes: Vec<EntityNode>, - pub world: &'a mut hecs::World, + pub tex_size: Extent3d, pub gizmo: &'a mut Gizmo, + pub camera: &'a mut Camera, + pub resize_signal: &'a mut (bool, u32, u32), + pub world: &'a mut hecs::World, + pub selected_entity: &'a mut Option<hecs::Entity>, } -pub const SELECTED: LazyLock<Mutex<Option<hecs::Entity>>> = LazyLock::new(|| Mutex::new(None)); - pub static TABS_GLOBAL: LazyLock<Mutex<INeedABetterNameForThisStruct>> = LazyLock::new(|| Mutex::new(INeedABetterNameForThisStruct::default())); @@ -86,40 +93,122 @@ impl<'a> TabViewer for EditorTabViewer<'a> { match tab { EditorTab::Viewport => { let size = ui.available_size(); - ui.image((self.view, size)); - - if let Some(selected) = SELECTED.lock().unwrap().as_ref() { - if let Ok((transform, camera)) = self.world.query_one_mut::<(&Transform, &Camera)>(*selected) { - self.gizmo.update_config(GizmoConfig { - view_matrix: camera.uniform.view_proj.into(), - projection_matrix: todo!(), - viewport: todo!(), - modes: todo!(), - mode_override: todo!(), - orientation: todo!(), - pivot_point: todo!(), - snapping: todo!(), - snap_angle: todo!(), - snap_distance: todo!(), - snap_scale: todo!(), - visuals: todo!(), - pixels_per_point: todo!(), - }); + let new_tex_width = size.x.max(1.0) as u32; + let new_tex_height = size.y.max(1.0) as u32; + + if self.tex_size.width != new_tex_width || self.tex_size.height != new_tex_height { + // log::debug!("Sending resize signal"); + *self.resize_signal = (true, new_tex_width, new_tex_height); + + self.tex_size.width = new_tex_width; + self.tex_size.height = new_tex_height; + + let new_aspect = new_tex_width as f64 / new_tex_height as f64; + self.camera.aspect = new_aspect; + } + + let image_response = ui.add( + egui::Image::new(( + self.view, + [self.tex_size.width as f32, self.tex_size.height as f32].into(), + )) + .sense(egui::Sense::click_and_drag()), + ); + log::debug!("Viewport rect: {:?}", image_response.rect); + log::debug!( + "Camera matrices - View: {:?}, Proj: {:?}", + self.camera.view_mat, + self.camera.proj_mat + ); + + // TODO: Figure out how to get the guizmos working because this is fucking annoying to deal with + // Note to self: fuck you >:( + // Note to self: ok wow thats pretty rude im trying my best >﹏< + // Note to self: finally holy shit i got it working + self.gizmo.update_config(GizmoConfig { + view_matrix: DMat4::look_at_lh( + DVec3::new( + self.camera.eye.x as f64, + self.camera.eye.y as f64, + self.camera.eye.z as f64, + ), + DVec3::new( + self.camera.target.x as f64, + self.camera.target.y as f64, + self.camera.target.z as f64, + ), + DVec3::new( + self.camera.up.x as f64, + self.camera.up.y as f64, + self.camera.up.z as f64, + ), + ) + .into(), + projection_matrix: DMat4::perspective_infinite_reverse_lh( + self.camera.fov_y as f64, + self.camera.aspect as f64, + self.camera.znear as f64, + ) + .into(), + viewport: image_response.rect, + modes: GizmoMode::all(), + orientation: transform_gizmo_egui::GizmoOrientation::Global, + ..Default::default() + }); + + println!("View Matrix: {:#?}", self.camera.view_mat); + println!("Projection Matrix: {:#?}", self.camera.proj_mat); + + if let Some(entity_id) = self.selected_entity { + log::debug!("Entity selected: {:?}", entity_id); + if let Ok(transform) = + self.world.query_one_mut::<&mut Transform>(*entity_id) + { + log::debug!( + "Transform - pos: {:?}, rot: {:?}, scale: {:?}", + transform.position, + transform.rotation, + transform.scale + ); + + let gizmo_transform = + transform_gizmo_egui::math::Transform::from_scale_rotation_translation( + transform.scale, + transform.rotation, + transform.position, + ); + + log::debug!("Gizmo transform created: {:?}", gizmo_transform); + log::debug!("Calling gizmo.interact..."); + + if let Some((result, new_transforms)) = + self.gizmo.interact(ui, &[gizmo_transform]) + { + log::debug!("Gizmo interaction successful: {:?}", result); + if let Some(new_transform) = new_transforms.first() { + transform.position = new_transform.translation.into(); + transform.rotation = new_transform.rotation.into(); + transform.scale = new_transform.scale.into(); + log::debug!("Gizmo updated entity {:?}: {:?}", entity_id, result); + } + } else { } + } else { + log::warn!("Unable to query entity: {:?}", entity_id); } + } else { + log::debug!("No entity selected"); } } EditorTab::ModelEntityList => { ui.label("Model/Entity List"); // TODO: deal with show_entity_tree and figure out how to convert hecs::World // to EntityNodes and to write it to file - { - show_entity_tree( - ui, - &mut self.nodes, - &mut SELECTED.lock().unwrap(), - "Model Entity Asset List", - ); - } + show_entity_tree( + ui, + &mut self.nodes, + &mut self.selected_entity, + "Model Entity Asset List", + ); } EditorTab::AssetViewer => { egui_extras::install_image_loaders(ui.ctx()); @@ -162,6 +251,24 @@ impl<'a> TabViewer for EditorTabViewer<'a> { file_name.clone(), fs::read(&model_thumbnail).unwrap(), ); + + // let mut model = match model_to_image::ModelToImageBuilder::new(&file.path) + // .with_size((800, 800)) + // .build() { + // Ok(v) => v, + // Err(e) => panic!("Error occurred while loading file from path: {}", e), + // }; + // let _ = model.render(); + // let buffer = model.output(); + // let bytes = buffer.bytes() + // .filter_map(Result::ok) + // .collect::<Vec<u8>>(); + // let file_name = + // file.name.clone(); + // let image = egui::Image::from_bytes( + // file_name, + // bytes, + // ); assets.push((image, file.name.clone())) } ResourceType::Texture => { @@ -1,30 +1,27 @@ use super::*; -use dropbear_engine::{ - input::{Controller, Keyboard, Mouse}, - log, - winit::{dpi::PhysicalPosition, keyboard::KeyCode}, +use dropbear_engine::input::{Controller, Keyboard, Mouse}; +use gilrs::{Button, GamepadId}; +use log; +use winit::{ + dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode, }; impl Keyboard for Editor { - fn key_down( - &mut self, - key: dropbear_engine::winit::keyboard::KeyCode, - _event_loop: &dropbear_engine::winit::event_loop::ActiveEventLoop, - ) { + fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) { match key { // KeyCode::Escape => event_loop.exit(), KeyCode::Escape => { - self.is_cursor_locked = !self.is_cursor_locked; - if !self.is_cursor_locked { - if let Some((surface_idx, node_idx, _)) = - self.dock_state.find_tab(&EditorTab::AssetViewer) - { - self.dock_state - .set_focused_node_and_surface((surface_idx, node_idx)); - } else { - self.dock_state.push_to_focused_leaf(EditorTab::AssetViewer); - } - } + // self.is_cursor_locked = !self.is_cursor_locked; + // if !self.is_cursor_locked { + // if let Some((surface_idx, node_idx, _)) = + // self.dock_state.find_tab(&EditorTab::AssetViewer) + // { + // self.dock_state + // .set_focused_node_and_surface((surface_idx, node_idx)); + // } else { + // self.dock_state.push_to_focused_leaf(EditorTab::AssetViewer); + // } + // } } KeyCode::KeyS => { #[cfg(not(target_os = "macos"))] @@ -73,62 +70,62 @@ impl Keyboard for Editor { } } - fn key_up( - &mut self, - key: dropbear_engine::winit::keyboard::KeyCode, - _event_loop: &dropbear_engine::winit::event_loop::ActiveEventLoop, - ) { + fn key_up(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) { self.pressed_keys.remove(&key); } } impl Mouse for Editor { - fn mouse_move(&mut self, position: PhysicalPosition<f64>) { - if self.is_cursor_locked { - if let Some(window) = &self.window { - let size = window.inner_size(); - let center = - PhysicalPosition::new(size.width as f64 / 2.0, size.height as f64 / 2.0); + fn mouse_move(&mut self, _position: PhysicalPosition<f64>) { + // // if self.is_cursor_locked { + // if let Some(window) = &self.window { + // let size = window.inner_size(); + // let center = + // PhysicalPosition::new(size.width as f64 / 2.0, size.height as f64 / 2.0); - let dx = position.x - center.x; - let dy = position.y - center.y; - self.camera.track_mouse_delta(dx as f32, dy as f32); + // let dx = position.x - center.x; + // let dy = position.y - center.y; + // self.camera.track_mouse_delta(dx as f32, dy as f32); - window.set_cursor_position(center).ok(); - window.set_cursor_visible(false); - } - } + // window.set_cursor_position(center).ok(); + // window.set_cursor_visible(false); + // } + // // } } - fn mouse_down(&mut self, _button: dropbear_engine::winit::event::MouseButton) {} + fn mouse_down(&mut self, _button: MouseButton) {} - fn mouse_up(&mut self, _button: dropbear_engine::winit::event::MouseButton) {} + fn mouse_up(&mut self, _button: MouseButton) {} } impl Controller for Editor { - fn button_down( - &mut self, - _button: dropbear_engine::gilrs::Button, - _id: dropbear_engine::gilrs::GamepadId, - ) { + fn button_down(&mut self, button: Button, _id: GamepadId) { + match button { + Button::South => { + self.camera.move_up(); + } + Button::East => { + self.camera.move_down(); + } + Button::LeftTrigger2 => { + self.camera.move_up(); + } + Button::RightTrigger2 => { + self.camera.move_down(); + } + _ => { + log::debug!("Controller button pressed: {:?}", button); + } + } } - fn button_up( - &mut self, - _button: dropbear_engine::gilrs::Button, - _id: dropbear_engine::gilrs::GamepadId, - ) { - } + fn button_up(&mut self, _button: Button, _id: GamepadId) {} - fn left_stick_changed(&mut self, _x: f32, _y: f32, _id: dropbear_engine::gilrs::GamepadId) { - // used for moving the camera - } + fn left_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {} - fn right_stick_changed(&mut self, _x: f32, _y: f32, _id: dropbear_engine::gilrs::GamepadId) { - // used for moving the player - } + fn right_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {} - fn on_connect(&mut self, _id: dropbear_engine::gilrs::GamepadId) {} + fn on_connect(&mut self, _id: GamepadId) {} - fn on_disconnect(&mut self, _id: dropbear_engine::gilrs::GamepadId) {} + fn on_disconnect(&mut self, _id: GamepadId) {} } @@ -10,19 +10,16 @@ use std::{ sync::{Arc, LazyLock, Mutex}, }; -use dropbear_engine::{ - camera::Camera, - egui, - hecs::{self, World}, - log, - scene::SceneCommand, - wgpu::{Color, Extent3d, RenderPipeline}, - winit::{keyboard::KeyCode, window::Window}, -}; +use dropbear_engine::{camera::Camera, scene::SceneCommand}; +use egui::{self, Context}; use egui_dock_fork::{DockArea, DockState, NodeIndex, Style}; use egui_toast_fork::{ToastOptions, Toasts}; +use hecs::World; +use log; use once_cell::sync::Lazy; -use transform_gizmo_egui::*; +use transform_gizmo_egui::Gizmo; +use wgpu::{Color, Extent3d, RenderPipeline}; +use winit::{keyboard::KeyCode, window::Window}; use crate::states::{EntityNode, PROJECT}; @@ -46,8 +43,7 @@ pub struct Editor { is_viewport_focused: bool, pressed_keys: HashSet<KeyCode>, - is_cursor_locked: bool, - + // is_cursor_locked: bool, window: Option<Arc<Window>>, show_new_project: bool, @@ -56,6 +52,9 @@ pub struct Editor { pending_scene_switch: bool, gizmo: Gizmo, + selected_entity: Option<hecs::Entity>, + + resize_signal: (bool, u32, u32), } impl Default for Editor { @@ -86,7 +85,7 @@ impl Editor { color: Color::default(), is_viewport_focused: false, pressed_keys: HashSet::new(), - is_cursor_locked: false, + // is_cursor_locked: false, window: None, world: World::new(), show_new_project: false, @@ -94,6 +93,8 @@ impl Editor { project_path: None, pending_scene_switch: false, gizmo: Gizmo::default(), + resize_signal: (false, 1, 1), + selected_entity: None, } } @@ -113,7 +114,7 @@ impl Editor { Ok(()) } - pub fn show_ui(&mut self, ctx: &egui::Context) { + pub fn show_ui(&mut self, ctx: &Context) { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { @@ -210,7 +211,7 @@ impl Editor { }); }); - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show(&ctx, |ui| { DockArea::new(&mut self.dock_state) .style(Style::from_egui(ui.style().as_ref())) .show_inside( @@ -218,8 +219,12 @@ impl Editor { &mut EditorTabViewer { view: self.texture_id.unwrap(), nodes: EntityNode::from_world(&self.world), - world: &mut self.world, gizmo: &mut self.gizmo, + tex_size: self.size, + camera: &mut self.camera, + resize_signal: &mut self.resize_signal, + world: &mut self.world, + selected_entity: &mut self.selected_entity, }, ); }); @@ -4,12 +4,13 @@ use dropbear_engine::{ camera::Camera, entity::{AdoptedEntity, Transform}, graphics::{Graphics, Shader}, - log, - nalgebra::{Point3, Vector3}, scene::{Scene, SceneCommand}, - wgpu::Color, - winit::{event_loop::ActiveEventLoop, keyboard::KeyCode}, }; +use glam::DVec3; +use log; +// use nalgebra::{Point3, Vector3}; +use wgpu::Color; +use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode}; use super::*; use crate::states::{Node, RESOURCES, ScriptComponent}; @@ -57,12 +58,12 @@ impl Scene for Editor { log::warn!("cube path is empty :(") } - let aspect = self.size.width as f32 / self.size.height as f32; + let aspect = self.size.width as f64 / self.size.height as f64; let camera = Camera::new( graphics, - Point3::new(0.0, 1.0, 2.0), - Point3::new(0.0, 0.0, 0.0), - Vector3::y(), + DVec3::new(0.0, 1.0, 2.0), + DVec3::new(0.0, 0.0, 0.0), + DVec3::Y, aspect, 45.0, 0.1, @@ -70,15 +71,12 @@ impl Scene for Editor { 0.125, 0.002, ); + let texture_bind_group = &graphics.texture_bind_group().clone(); let model_layout = graphics.create_model_uniform_bind_group_layout(); let pipeline = graphics.create_render_pipline( &shader, - vec![ - &graphics.state.texture_bind_layout, - camera.layout(), - &model_layout, - ], + vec![texture_bind_group, camera.layout(), &model_layout], ); self.camera = camera; @@ -93,29 +91,33 @@ impl Scene for Editor { self.is_viewport_focused = false; } - if self.is_viewport_focused { - self.is_cursor_locked = true; - } - - if self.is_cursor_locked { - for key in &self.pressed_keys { - match key { - KeyCode::KeyW => self.camera.move_forwards(), - KeyCode::KeyA => self.camera.move_left(), - KeyCode::KeyD => self.camera.move_right(), - KeyCode::KeyS => self.camera.move_back(), - KeyCode::ShiftLeft => self.camera.move_down(), - KeyCode::Space => self.camera.move_up(), - _ => {} - } + // if self.is_viewport_focused { + // self.is_cursor_locked = true; + // } + + // if self.is_cursor_locked { + for key in &self.pressed_keys { + match key { + KeyCode::KeyW => self.camera.move_forwards(), + KeyCode::KeyA => self.camera.move_left(), + KeyCode::KeyD => self.camera.move_right(), + KeyCode::KeyS => self.camera.move_back(), + KeyCode::ShiftLeft => self.camera.move_down(), + KeyCode::Space => self.camera.move_up(), + _ => {} } } + // } + + let new_size = graphics.state.viewport_texture.size; + let new_aspect = new_size.width as f64 / new_size.height as f64; + self.camera.aspect = new_aspect; self.camera.update(graphics); - if !self.is_cursor_locked { - self.window.as_mut().unwrap().set_cursor_visible(true); - } + // if !self.is_cursor_locked { + // self.window.as_mut().unwrap().set_cursor_visible(true); + // } let query = self.world.query_mut::<(&mut AdoptedEntity, &Transform)>(); for (_, (entity, transform)) in query { @@ -131,10 +133,15 @@ impl Scene for Editor { a: 1.0, }; self.color = color.clone(); - self.size = graphics.state.viewport_texture.size; - self.texture_id = Some(graphics.state.texture_id); - let ctx = graphics.get_egui_context(); - self.show_ui(ctx); + self.size = graphics.state.viewport_texture.size.clone(); + self.texture_id = Some(graphics.state.texture_id.clone()); + self.show_ui(&graphics.get_egui_context()); + + if self.resize_signal.0.clone() { + // graphics.state.resize(self.resize_signal.1, self.resize_signal.2); + // self.resize_signal.0 = false; + } + self.window = Some(graphics.state.window.clone()); if let Ok(mut toasts) = GLOBAL_TOASTS.lock() { toasts.show(graphics.get_egui_context()); @@ -1,12 +1,11 @@ mod editor; mod menu; -// mod scene1; pub(crate) mod states; pub mod utils; use std::{cell::RefCell, rc::Rc}; -use dropbear_engine::{WindowConfiguration, scene, tokio}; +use dropbear_engine::{WindowConfiguration, scene}; pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo { name: "Eucalyptus", @@ -1,21 +1,21 @@ use std::{ - fs, io, - path::Path, - process::Command, + fs, sync::mpsc::{self, Receiver}, }; use anyhow::anyhow; use dropbear_engine::{ - async_trait::async_trait, - egui::{self, FontId, Frame, RichText}, - gilrs, input::{Controller, Keyboard, Mouse}, - log::{self, debug}, scene::{Scene, SceneCommand}, }; +use egui::{self, FontId, Frame, RichText}; use egui_toast_fork::{ToastOptions, Toasts}; +use gilrs; use git2::Repository; +use log::{self, debug}; +use winit::{ + dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode, +}; use crate::states::{PROJECT, ProjectConfig}; @@ -56,49 +56,6 @@ impl MainMenu { } } - #[allow(dead_code)] - fn setup_poetry_project(project_path: &Path) -> anyhow::Result<()> { - if !Command::new("poetry").args(["--version"]).output().is_ok() { - return Err(anyhow!( - "Poetry is not installed. Please install it using pipx or the official poetry website" - )); - } - - let status = Command::new("poetry") - .args(&["new", "scripts"]) - .current_dir(project_path) - .status() - .expect("Failed to run poetry new"); - if !status.success() { - return Err(anyhow!(io::Error::new( - io::ErrorKind::Other, - "Poetry project creation failed", - ))); - } - - let scripts_path = project_path.join("scripts"); - if scripts_path.exists() && scripts_path.is_dir() { - for entry in fs::read_dir(&scripts_path)? { - let entry = entry?; - let path = entry.path(); - let file_name = path.file_name().unwrap(); - let dest = project_path.join(file_name); - fs::rename(&path, &dest)?; - } - } - - if scripts_path.exists() { - fs::remove_dir_all(&scripts_path)?; - } - - let tests_path = project_path.join("tests"); - if tests_path.exists() { - fs::remove_dir_all(&tests_path)?; - } - - Ok(()) - } - fn start_project_creation(&mut self) { let (tx, rx) = mpsc::channel(); let project_name = self.project_name.clone(); @@ -152,7 +109,6 @@ impl MainMenu { } else if folder == "src2" { if let Some(path) = &project_path { let mut config = ProjectConfig::new(project_name.clone(), &path); - // let _ = config.write_to(&path); let _ = config.write_to_all(); let mut global = PROJECT.write().unwrap(); *global = config; @@ -160,31 +116,7 @@ impl MainMenu { } else { Err(anyhow!("Project path not found")) } - } - // else if folder == "scripts" || folder == "deps" { - // if folder == "scripts" { - // Self::setup_poetry_project(path) - // } else { - // let status = Command::new("poetry") - // .args(["add", "3d-to-image"]) - // .current_dir(path) - // .status(); - // match status { - // Ok(_) => Ok(()), - // Err(e) => Err(anyhow!(e)), - // } - // } - // } else if folder == "scripts2" { - // if path.join("src/scripts").exists() { - // fs::write( - // &path.join("src/scripts/convert_model_to_image.py"), - // include_str!("scripts/convert_model_to_image.py"), - // ) - // .map_err(|e| anyhow!(e)) - // } else { - // Err(anyhow!("The src/scripts folder does not exist")) - // } - else { + } else { if !full_path.exists() { fs::create_dir_all(&full_path) .map_err(|e| anyhow!(e)) @@ -213,7 +145,6 @@ impl MainMenu { } } -#[async_trait] impl Scene for MainMenu { fn load(&mut self, _graphics: &mut dropbear_engine::graphics::Graphics) {} @@ -385,7 +316,7 @@ impl Scene for MainMenu { self.toast.show(graphics.get_egui_context()); } - fn exit(&mut self, _event_loop: &dropbear_engine::winit::event_loop::ActiveEventLoop) {} + fn exit(&mut self, _event_loop: &ActiveEventLoop) {} fn run_command(&mut self) -> SceneCommand { std::mem::replace(&mut self.scene_command, SceneCommand::None) @@ -393,30 +324,21 @@ impl Scene for MainMenu { } impl Keyboard for MainMenu { - fn key_down( - &mut self, - _key: dropbear_engine::winit::keyboard::KeyCode, - _event_loop: &dropbear_engine::winit::event_loop::ActiveEventLoop, - ) { + fn key_down(&mut self, _key: KeyCode, _event_loop: &ActiveEventLoop) { // if key == dropbear_engine::winit::keyboard::KeyCode::Escape { // event_loop.exit(); // } } - fn key_up( - &mut self, - _key: dropbear_engine::winit::keyboard::KeyCode, - _event_loop: &dropbear_engine::winit::event_loop::ActiveEventLoop, - ) { - } + fn key_up(&mut self, _key: KeyCode, _event_loop: &ActiveEventLoop) {} } impl Mouse for MainMenu { - fn mouse_move(&mut self, _position: dropbear_engine::winit::dpi::PhysicalPosition<f64>) {} + fn mouse_move(&mut self, _position: PhysicalPosition<f64>) {} - fn mouse_down(&mut self, _button: dropbear_engine::winit::event::MouseButton) {} + fn mouse_down(&mut self, _button: MouseButton) {} - fn mouse_up(&mut self, _button: dropbear_engine::winit::event::MouseButton) {} + fn mouse_up(&mut self, _button: MouseButton) {} } impl Controller for MainMenu { @@ -12,8 +12,9 @@ use std::{ }; use chrono::Utc; -use dropbear_engine::{hecs, log}; use egui_dock_fork::DockState; +use hecs; +use log; use once_cell::sync::Lazy; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; @@ -35,7 +36,7 @@ pub static SOURCE: Lazy<RwLock<SourceConfig>> = Lazy::new(|| RwLock::new(SourceC #[derive(Debug, Deserialize, Serialize, Default)] pub struct ProjectConfig { pub project_name: String, - pub project_path: String, + pub project_path: PathBuf, pub date_created: String, pub date_last_accessed: String, #[serde(default)] @@ -47,11 +48,10 @@ impl ProjectConfig { /// a new project, with it creating new defaults for everything. pub fn new(project_name: String, project_path: &PathBuf) -> Self { let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); - let project_path_str = project_path.to_str().unwrap().to_string(); let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); let mut result = Self { project_name, - project_path: project_path_str, + project_path: project_path.to_path_buf(), date_created, date_last_accessed, dock_layout: None, @@ -73,7 +73,7 @@ impl ProjectConfig { let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; let config_path = path.join(format!("{}.eucp", self.project_name.clone().to_lowercase())); - self.project_path = path.clone().to_str().unwrap().to_string(); + self.project_path = path.to_path_buf(); fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; Ok(()) @@ -87,11 +87,10 @@ impl ProjectConfig { pub fn read_from(path: &PathBuf) -> anyhow::Result<Self> { let ron_str = fs::read_to_string(path)?; let mut config: ProjectConfig = ron::de::from_str(&ron_str.as_str())?; + config.project_path = path.parent().unwrap().to_path_buf(); log::info!("Loaded project!"); - log::debug!("Loaded config info: {:#?}", config); + log::debug!("Loaded config info: {:?}", config); log::debug!("Updating with new content"); - // config.write_to(&path.parent().unwrap().to_path_buf())?; - // config.assets = Assets::walk(&path.parent().unwrap().to_path_buf()); config.load_config_to_memory()?; config.write_to_all()?; log::debug!("Successfully updated!"); @@ -5,8 +5,8 @@ use std::{ }; use anyhow::anyhow; -use dropbear_engine::egui::{self, Context}; use dropbear_engine::scene::SceneCommand; +use egui::Context; use egui_toast_fork::{Toast, ToastOptions, Toasts}; use git2::Repository; @@ -1,10 +1,11 @@ [package] name = "redback" -version = "0.1.1" -edition = "2024" -license = "GPL-3.0-or-later" description = "The build tool used by eucalyptus game engine" -repository = "https://github.com/4tkbytes/dropbear-engine" -readme = "README.md" + +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true [dependencies]