tirbofish/dropbear · commit
2643d1f2239e87d68b747c7bdedff2e1d4c44341
fix: magna-carta parser
fix: reset animation state on animation change so its legs don't look like its snapped
fix: collider wireframe finally renders now (fixes #90)
fix: kcc contains a CharacterMovementResult now
refactor: included a world + entity in all InspectableComponents
jarvis, run github actions
Signature present but could not be verified.
Unverified
@@ -33,6 +33,9 @@ pub struct AnimationComponent { #[serde(skip)] pub available_animations: Vec<String>, + + #[serde(skip)] + pub last_animation_index: Option<usize>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -72,6 +75,7 @@ impl Default for AnimationComponent { bone_buffer: None, bind_group: None, available_animations: vec![], + last_animation_index: None, } } } @@ -89,6 +93,11 @@ impl AnimationComponent { .map(|v| v.name.clone()) .collect::<Vec<_>>(); + if self.active_animation_index != self.last_animation_index { + self.local_pose.clear(); + self.last_animation_index = self.active_animation_index; + } + let Some(anim_idx) = self.active_animation_index else { self.reset_to_bind_pose(model); return; @@ -66,7 +66,13 @@ impl Component for AnimationComponent { } impl InspectableComponent for AnimationComponent { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Animation") .default_open(true) .show(ui, |ui| { @@ -75,7 +75,13 @@ impl Component for Camera { } impl InspectableComponent for Camera { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Camera3D") .default_open(true) .show(ui, |ui| { @@ -302,7 +308,7 @@ pub mod shared { )] fn exists_for_entity( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<bool> { Ok(shared::camera_exists_for_entity(world, entity)) } @@ -313,7 +319,7 @@ fn exists_for_entity( )] fn get_eye( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<NVector3> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.eye.into()), @@ -327,7 +333,7 @@ fn get_eye( )] fn set_eye( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, eye: &NVector3, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -348,7 +354,7 @@ fn set_eye( )] fn get_target( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<NVector3> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.target.into()), @@ -365,7 +371,7 @@ fn get_target( )] fn set_target( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, target: &NVector3, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -383,7 +389,7 @@ fn set_target( )] fn get_up( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<NVector3> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.up.into()), @@ -397,7 +403,7 @@ fn get_up( )] fn set_up( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, up: &NVector3, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -418,7 +424,7 @@ fn set_up( )] fn get_aspect( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.aspect.into()), @@ -432,7 +438,7 @@ fn get_aspect( )] fn get_fovy( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.settings.fov_y.into()), @@ -446,7 +452,7 @@ fn get_fovy( )] fn set_fovy( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, fovy: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -467,7 +473,7 @@ fn set_fovy( )] fn get_znear( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.znear.into()), @@ -484,7 +490,7 @@ fn get_znear( )] fn set_znear( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, znear: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -502,7 +508,7 @@ fn set_znear( )] fn get_zfar( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.zfar.into()), @@ -516,7 +522,7 @@ fn get_zfar( )] fn set_zfar( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, zfar: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -534,7 +540,7 @@ fn set_zfar( )] fn get_yaw( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.yaw.into()), @@ -548,7 +554,7 @@ fn get_yaw( )] fn set_yaw( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, yaw: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -569,7 +575,7 @@ fn set_yaw( )] fn get_pitch( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.pitch.into()), @@ -586,7 +592,7 @@ fn get_pitch( )] fn set_pitch( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, pitch: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -607,7 +613,7 @@ fn set_pitch( )] fn get_speed( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.settings.speed.into()), @@ -624,7 +630,7 @@ fn get_speed( )] fn set_speed( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, speed: f64, ) -> DropbearNativeResult<()> { match world.get::<&mut Camera>(entity) { @@ -645,7 +651,7 @@ fn set_speed( )] fn get_sensitivity( #[dropbear_macro::define(WorldPtr)] world: &hecs::World, - #[dropebear_macro::entity] entity: hecs::Entity, + #[dropbear_macro::entity] entity: hecs::Entity, ) -> DropbearNativeResult<f64> { match world.get::<&Camera>(entity) { Ok(camera) => Ok(camera.settings.sensitivity.into()), @@ -90,7 +90,7 @@ type InspectFn = Box< dyn Fn(&mut hecs::World, hecs::Entity, &mut egui::Ui, Arc<SharedGraphicsContext>) + Send + Sync, >; -// fn inspect(&mut self, ui: &mut egui::Ui); +// fn inspect(&mut self, world: &hecs::World, entity: hecs::Entity, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>); impl ComponentRegistry { pub fn new() -> Self { @@ -196,8 +196,10 @@ impl ComponentRegistry { self.inspectors.insert( type_id, Box::new(|world, entity, ui, graphics| { + let world_ptr = world as *const hecs::World; if let Ok(mut comp) = world.get::<&mut T>(entity) { - comp.inspect(ui, graphics); + let world_ref = unsafe { &*world_ptr }; + comp.inspect(world_ref, entity, ui, graphics); } }), ); @@ -492,7 +494,13 @@ pub trait Component: Sync + Send { pub trait InspectableComponent: Send + Sync { /// In the editor, how the component will be represented in the `Resource Viewer` dock. - fn inspect(&mut self, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>); + fn inspect( + &mut self, + world: &hecs::World, + entity: hecs::Entity, + ui: &mut egui::Ui, + graphics: Arc<SharedGraphicsContext>, + ); } #[typetag::serde] @@ -701,7 +709,13 @@ impl Component for MeshRenderer { } impl InspectableComponent for MeshRenderer { - fn inspect(&mut self, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &hecs::World, + _entity: hecs::Entity, + ui: &mut egui::Ui, + graphics: Arc<SharedGraphicsContext>, + ) { fn is_probably_model_uri(uri: &str) -> bool { let uri = uri.to_ascii_lowercase(); uri.ends_with(".glb") @@ -85,7 +85,13 @@ impl Component for Light { } impl InspectableComponent for Light { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Light") .default_open(true) .show(ui, |ui| { @@ -102,7 +102,13 @@ impl Component for ColliderGroup { } impl InspectableComponent for ColliderGroup { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Colliders") .default_open(true) .show(ui, |ui| { @@ -398,9 +404,9 @@ impl From<&ColliderShape> for ColliderShapeKey { match *shape { ColliderShape::Box { half_extents } => Self::Box { half_extents_bits: [ - half_extents.x.to_bits() as u32, - half_extents.y.to_bits() as u32, - half_extents.z.to_bits() as u32, + (half_extents.x as f32).to_bits(), + (half_extents.y as f32).to_bits(), + (half_extents.z as f32).to_bits(), ], }, ColliderShape::Sphere { radius } => Self::Sphere { @@ -1,6 +1,9 @@ struct CameraUniform { view_pos: vec4<f32>, + view: mat4x4<f32>, view_proj: mat4x4<f32>, + inv_proj: mat4x4<f32>, + inv_view: mat4x4<f32>, }; @group(0) @binding(0) @@ -5,7 +5,7 @@ pub mod character_collision; use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent}; use crate::physics::PhysicsState; -use crate::ptr::WorldPtr; +use crate::ptr::{WorldPtr}; use crate::scripting::jni::utils::ToJObject; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; @@ -16,9 +16,7 @@ use egui::{ComboBox, DragValue, Ui}; use hecs::{Entity, World}; use jni::JNIEnv; use jni::objects::{JObject, JValue}; -use rapier3d::control::{ - CharacterAutostep, CharacterCollision, CharacterLength, KinematicCharacterController, -}; +use rapier3d::control::{CharacterAutostep, CharacterCollision, CharacterLength, KinematicCharacterController}; use rapier3d::dynamics::RigidBodyType; use rapier3d::math::Rotation; use rapier3d::na::{UnitVector3, Vector3}; @@ -33,6 +31,42 @@ pub struct KCC { pub controller: KinematicCharacterController, #[serde(skip)] pub collisions: Vec<CharacterCollision>, + #[serde(skip)] + pub movement: Option<CharacterMovementResult>, +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct CharacterMovementResult { + pub translation: NVector3, + pub grounded: bool, + pub is_sliding_down_slope: bool, +} + +impl ToJObject for CharacterMovementResult { + fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { + let class = env + .find_class("com/dropbear/physics/CharacterMovementResult") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + + let translation_obj = self.translation.to_jobject(env)?; + + let args = [ + JValue::Object(&translation_obj), + JValue::Bool(self.grounded as u8), + JValue::Bool(self.is_sliding_down_slope as u8), + ]; + + let obj = env + .new_object( + &class, + "(Lcom/dropbear/math/Vector3d;ZZ)V", + &args, + ) + .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; + + Ok(obj) + } } #[typetag::serde] @@ -51,10 +85,10 @@ impl Component for KCC { } } - fn init<'a>( - ser: &'a Self::SerializedForm, + fn init( + ser: &'_ Self::SerializedForm, _: Arc<SharedGraphicsContext>, - ) -> crate::component::ComponentInitFuture<'a, Self> { + ) -> crate::component::ComponentInitFuture<'_, Self> { Box::pin(async move { Ok((ser.clone(),)) }) } @@ -74,7 +108,13 @@ impl Component for KCC { } impl InspectableComponent for KCC { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { egui::CollapsingHeader::new("Kinematic Character Controller") .default_open(true) .show(ui, |ui| { @@ -223,6 +263,7 @@ impl KCC { entity: label.clone(), controller: KinematicCharacterController::default(), collisions: vec![], + movement: None, } } } @@ -301,7 +342,7 @@ fn move_character( translation: &NVector3, delta_time: f64, ) -> DropbearNativeResult<()> { - if let Ok((label, kcc)) = world.query_one::<(&Label, &KCC)>(entity).get() { + if let Ok((label, kcc)) = world.query_one::<(&Label, &mut KCC)>(entity).get() { let rigid_body_handle = physics_state .bodies_entity_map .get(label) @@ -337,7 +378,7 @@ fn move_character( *collider.position() }; - let filter = QuprintlneryFilter::default().exclude_rigid_body(*rigid_body_handle); + let filter = QueryFilter::default().exclude_rigid_body(*rigid_body_handle); let query_pipeline = physics_state.broad_phase.as_query_pipeline( physics_state.narrow_phase.query_dispatcher(), &physics_state.bodies, @@ -374,6 +415,12 @@ fn move_character( body.set_next_kinematic_translation(new_pos); } + kcc.movement = Some(CharacterMovementResult { + translation: movement.translation.into(), + grounded: movement.grounded, + is_sliding_down_slope: movement.is_sliding_down_slope, + }); + Ok(()) } else { Err(DropbearNativeError::MissingComponent) @@ -467,3 +514,17 @@ fn get_hit( collisions, }) } + +#[dropbear_macro::export( + kotlin( + class = "com.dropbear.physics.KinematicCharacterControllerNative", + func = "getMovementResult" + ), + c +)] +fn get_movement_result( + #[dropbear_macro::define(WorldPtr)] world: &hecs::World, + #[dropbear_macro::entity] entity: hecs::Entity, +) -> DropbearNativeResult<Option<CharacterMovementResult>> { + world.get::<&KCC>(entity).map(|kcc| kcc.movement.clone()).map(Ok).unwrap_or(Ok(None)) +} @@ -233,7 +233,13 @@ impl Component for RigidBody { } impl InspectableComponent for RigidBody { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("RigidBody") .default_open(true) .show(ui, |ui| { @@ -61,7 +61,13 @@ impl Component for CustomProperties { } impl InspectableComponent for CustomProperties { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Custom Properties") .default_open(true) .show(ui, |ui| { @@ -196,7 +196,13 @@ impl Component for Script { } impl InspectableComponent for Script { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Scripting") .default_open(true) .show(ui, |ui| { @@ -53,7 +53,13 @@ impl Component for EntityTransform { } impl InspectableComponent for EntityTransform { - fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { + fn inspect( + &mut self, + _world: &World, + _entity: Entity, + ui: &mut Ui, + _graphics: Arc<SharedGraphicsContext>, + ) { CollapsingHeader::new("Entity Transform") .default_open(true) .show(ui, |ui| { @@ -18,15 +18,6 @@ use crate::editor::{ }; use eucalyptus_core::component::DRAGGED_ASSET_ID; -#[derive(Clone, Copy, Debug)] -enum TextureSlot { - Diffuse, - Normal, - Emissive, - MetallicRoughness, - Occlusion, -} - impl<'a> EditorTabViewer<'a> { pub(crate) fn show_asset_viewer(&mut self, ui: &mut egui::Ui) { let mut cfg = TABS_GLOBAL.lock(); @@ -229,6 +220,7 @@ impl<'a> EditorTabViewer<'a> { reference_for_menu.clone(), entry_name.clone(), ); + info!("Loading model {}", entry_name); } } @@ -239,46 +231,8 @@ impl<'a> EditorTabViewer<'a> { reference_for_menu.clone(), entry_name.clone(), ); + info!("Loading texture {}", entry_name); } - - ui.separator(); - ui.menu_button("Choose", |ui| { - if ui.button("Diffuse").clicked() { - ui.close(); - self.apply_texture_slot( - reference_for_menu.clone(), - TextureSlot::Diffuse, - ); - } - if ui.button("Normal").clicked() { - ui.close(); - self.apply_texture_slot( - reference_for_menu.clone(), - TextureSlot::Normal, - ); - } - if ui.button("Emissive").clicked() { - ui.close(); - self.apply_texture_slot( - reference_for_menu.clone(), - TextureSlot::Emissive, - ); - } - if ui.button("Metal/Rough").clicked() { - ui.close(); - self.apply_texture_slot( - reference_for_menu.clone(), - TextureSlot::MetallicRoughness, - ); - } - if ui.button("Occlusion").clicked() { - ui.close(); - self.apply_texture_slot( - reference_for_menu.clone(), - TextureSlot::Occlusion, - ); - } - }); } }); builder.node(menu); @@ -1265,7 +1219,7 @@ impl<'a> EditorTabViewer<'a> { { Ok(v) => v, Err(e) => { - warn!("Unable to load model {}: {}", reference, e); + eucalyptus_core::warn!("Unable to load model {}: {}", reference, e); return Err(e); } }; @@ -1308,47 +1262,6 @@ impl<'a> EditorTabViewer<'a> { }); } - fn apply_texture_slot(&mut self, reference: ResourceReference, slot: TextureSlot) { - let Some(entity) = *self.selected_entity else { - warn!("Unable to apply texture: no entity selected"); - return; - }; - - let Some(handle) = ASSET_REGISTRY - .read() - .get_texture_handle_by_reference(&reference) - else { - warn!("Texture not loaded in memory, load it first"); - return; - }; - - let texture = { - let registry = ASSET_REGISTRY.read(); - registry.get_texture(handle).cloned() - }; - - let Some(texture) = texture else { - warn!("Texture handle missing from registry"); - return; - }; - - if let Ok(renderer) = self.world.query_one::<&mut MeshRenderer>(entity).get() { - for material in renderer.material_snapshot.values_mut() { - match slot { - TextureSlot::Diffuse => material.diffuse_texture = texture.clone(), - TextureSlot::Normal => material.normal_texture = texture.clone(), - TextureSlot::Emissive => material.emissive_texture = Some(texture.clone()), - TextureSlot::MetallicRoughness => { - material.metallic_roughness_texture = Some(texture.clone()) - } - TextureSlot::Occlusion => material.occlusion_texture = Some(texture.clone()), - } - } - } else { - warn!("Selected entity has no MeshRenderer"); - } - } - pub(crate) fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) { for node_id in items { self.resolve_tree_node(cfg, *node_id); @@ -1,1377 +0,0 @@ -//! This module should describe the different components that are editable in the resource inspector. - -use crate::editor::{Signal, StaticallyKept, UndoableAction}; -use dropbear_engine::asset::{ASSET_REGISTRY}; -use dropbear_engine::attenuation::ATTENUATION_PRESETS; -use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; -use dropbear_engine::lighting::LightType; -use dropbear_engine::texture::TextureWrapMode; -use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; -use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui, UiBuilder}; -use eucalyptus_core::camera::CameraType; -use eucalyptus_core::states::{Camera3D, Light, Property, Script}; -use eucalyptus_core::{fatal, warn}; -use glam::{DVec3, Vec3}; -use hecs::Entity; -use std::time::Instant; -use dropbear_engine::procedural::ProcObj; -use eucalyptus_core::properties::{CustomProperties, Value}; - -/// A trait that can added to any component that allows you to inspect the value in the editor. -pub trait InspectableComponent { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - signal: &mut Signal, - _label: &mut String, - ); -} - - -fn wrap_angle_degrees(angle: f64) -> f64 { - (angle + 180.0).rem_euclid(360.0) - 180.0 -} - -fn reconcile_angle(angle: f64, reference: f64) -> f64 { - let delta = wrap_angle_degrees(angle - reference); - wrap_angle_degrees(reference + delta) -} - -impl InspectableComponent for CustomProperties { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - - } -} - -impl InspectableComponent for EntityTransform { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - signal: &mut Signal, - _label: &mut String, - ) { - self.local_mut().inspect( - entity, - cfg, - ui, - undo_stack, - signal, - &mut "Local Transform".to_string(), - ); - self.world_mut().inspect( - entity, - cfg, - ui, - undo_stack, - signal, - &mut "World Transform".to_string(), - ); - } -} - -fn inspect_light_transform( - transform: &mut Transform, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - light_type: &LightType, -) { - let show_position = matches!(light_type, LightType::Point | LightType::Spot); - let show_rotation = !matches!(light_type, LightType::Point); - - if show_position { - ui.label(RichText::new("Position").strong()); - - ui.horizontal_wrapped(|ui| { - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.x) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Y:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.y) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Z:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.z) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - }); - - if ui.button("Reset Position").clicked() { - transform.position = DVec3::ZERO; - } - ui.add_space(5.0); - } - - if show_rotation { - ui.label(RichText::new("Rotation").strong()); - - let mut rotation_deg = *cfg - .transform_rotation_cache - .entry(*entity) - .or_insert_with(|| { - let (x, y, z) = transform.rotation.to_euler(glam::EulerRot::XYZ); - DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()) - }); - - if let Some(prev) = cfg.transform_rotation_cache.get(entity) { - let mut degrees = rotation_deg; - let (x, y, z) = transform.rotation.to_euler(glam::EulerRot::XYZ); - degrees.x = x.to_degrees(); - degrees.y = y.to_degrees(); - degrees.z = z.to_degrees(); - - degrees.x = reconcile_angle(degrees.x, prev.x); - degrees.y = reconcile_angle(degrees.y, prev.y); - degrees.z = reconcile_angle(degrees.z, prev.z); - - degrees.x = wrap_angle_degrees(degrees.x); - degrees.y = wrap_angle_degrees(degrees.y); - degrees.z = wrap_angle_degrees(degrees.z); - - cfg.transform_rotation_cache.insert(*entity, degrees); - rotation_deg = degrees; - }; - - let mut rotation_changed = false; - - ui.horizontal(|ui| { - ui.label("Pitch (X):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.x) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Yaw (Y):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.y) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Roll (Z):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.z) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - } - cfg.transform_in_progress = false; - } - }); - - if rotation_changed { - let rot_x = glam::DQuat::from_rotation_x(rotation_deg.x.to_radians()); - let rot_y = glam::DQuat::from_rotation_y(rotation_deg.y.to_radians()); - let rot_z = glam::DQuat::from_rotation_z(rotation_deg.z.to_radians()); - transform.rotation = rot_y * rot_x * rot_z; - cfg.transform_rotation_cache.insert(*entity, rotation_deg); - } - - if ui.button("Reset Rotation").clicked() { - transform.rotation = glam::DQuat::IDENTITY; - cfg.transform_rotation_cache.insert(*entity, DVec3::NEG_Y); - } - ui.add_space(5.0); - } -} - -fn inspect_transform( - transform: &mut Transform, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - label: &str, - show_position: bool, - show_rotation: bool, - show_scale: bool, -) { - ui.vertical(|ui| { - CollapsingHeader::new(label) - .default_open(true) - .show(ui, |ui| { - if show_position { - ui.horizontal(|ui| { - ui.label("Position:"); - }); - - ui.horizontal_wrapped(|ui| { - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.x) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X transform change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - ui.horizontal(|ui| { - ui.label("Y:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.y) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y transform change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Z:"); - let response = ui.add( - egui::DragValue::new(&mut transform.position.z) - .speed(0.1) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z transform change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - }); - - if ui.button("Reset Position").clicked() { - transform.position = DVec3::ZERO; - } - - ui.add_space(5.0); - } - - if show_rotation { - ui.label("Rotation:"); - - ui.horizontal_wrapped(|ui| { - let cached_rotation = cfg.transform_rotation_cache.get(entity).copied(); - - let mut rotation_deg: DVec3 = if cfg.transform_in_progress { - cached_rotation.unwrap_or_else(|| { - let (x, y, z) = transform.rotation.to_euler(glam::EulerRot::YXZ); - DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()) - }) - } else { - let (x, y, z) = transform.rotation.to_euler(glam::EulerRot::YXZ); - let mut degrees = - DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()); - - if let Some(prev) = cached_rotation { - degrees.x = reconcile_angle(degrees.x, prev.x); - degrees.y = reconcile_angle(degrees.y, prev.y); - degrees.z = reconcile_angle(degrees.z, prev.z); - } - - degrees.x = wrap_angle_degrees(degrees.x); - degrees.y = wrap_angle_degrees(degrees.y); - degrees.z = wrap_angle_degrees(degrees.z); - - cfg.transform_rotation_cache.insert(*entity, degrees); - degrees - }; - - let mut rotation_changed = false; - - ui.horizontal(|ui| { - ui.label("Pitch (X):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.x) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X rotation change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Yaw (Y):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.y) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y rotation change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Roll (Z):"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.z) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - rotation_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z rotation change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - if rotation_changed { - rotation_deg.x = wrap_angle_degrees(rotation_deg.x); - rotation_deg.y = wrap_angle_degrees(rotation_deg.y); - rotation_deg.z = wrap_angle_degrees(rotation_deg.z); - - cfg.transform_rotation_cache.insert(*entity, rotation_deg); - transform.rotation = glam::DQuat::from_euler( - glam::EulerRot::YXZ, - rotation_deg.x.to_radians(), - rotation_deg.y.to_radians(), - rotation_deg.z.to_radians(), - ); - } - }); - - if ui.button("Reset Rotation").clicked() { - transform.rotation = glam::DQuat::IDENTITY; - cfg.transform_rotation_cache.insert(*entity, DVec3::ZERO); - } - ui.add_space(5.0); - } - - if show_scale { - ui.horizontal(|ui| { - ui.label("Scale:"); - let lock_icon = if cfg.scale_locked { "🔒" } else { "🔓" }; - if ui - .button(lock_icon) - .on_hover_text("Lock uniform scaling") - .clicked() - { - cfg.scale_locked = !cfg.scale_locked; - } - }); - - let mut scale_changed = false; - let mut new_scale = transform.scale; - - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - DragValue::new(&mut new_scale.x) - .speed(0.01) - .fixed_decimals(3), - ); - - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - scale_changed = true; - if cfg.scale_locked { - let scale_factor = new_scale.x / transform.scale.x; - new_scale.y = transform.scale.y * scale_factor; - new_scale.z = transform.scale.z * scale_factor; - } - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X scale change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Y:"); - let y_slider = egui::DragValue::new(&mut new_scale.y) - .speed(0.01) - .fixed_decimals(3); - - let response = ui.add_enabled(!cfg.scale_locked, y_slider); - - if response.drag_started() && !cfg.scale_locked { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - scale_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y scale change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - ui.horizontal(|ui| { - ui.label("Z:"); - let z_slider = egui::DragValue::new(&mut new_scale.z) - .speed(0.01) - .fixed_decimals(3); - - let response = ui.add_enabled(!cfg.scale_locked, z_slider); - - if response.drag_started() && !cfg.scale_locked { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*transform); - cfg.transform_in_progress = true; - } - - if response.changed() { - scale_changed = true; - } - - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z scale change to undo stack"); - } - cfg.transform_in_progress = false; - } - }); - - if scale_changed { - transform.scale = new_scale; - } - - if ui.button("Reset Scale").clicked() { - transform.scale = DVec3::ONE; - } - ui.add_space(5.0); - } - }); - }); - ui.separator(); -} - -impl InspectableComponent for Transform { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - label: &mut String, - ) { - inspect_transform(self, entity, cfg, ui, undo_stack, label, true, true, true); - } -} - -impl InspectableComponent for Script { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - - } -} - -impl InspectableComponent for eucalyptus_core::states::Label { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - ui.horizontal(|ui| { - ui.label("Name: "); - - let resp = ui.text_edit_singleline(self.as_mut_string()); - - if resp.changed() { - if cfg.old_label_entity.is_none() { - cfg.old_label_entity = Some(*entity); - cfg.label_original = Some(self.to_string()); - } - cfg.label_last_edit = Some(Instant::now()); - } - - if resp.lost_focus() { - if let Some(ent) = cfg.old_label_entity.take() { - if ent == *entity { - if let Some(orig) = cfg.label_original.take() { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Label(ent, orig), - ); - log::debug!("Pushed label change to undo stack (immediate)"); - } - } else { - cfg.label_original = None; - } - } - cfg.label_last_edit = None; - } - }); - } -} - -impl InspectableComponent for MeshRenderer { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - signal: &mut Signal, - _label: &mut String, - ) { - fn is_probably_model_uri(uri: &str) -> bool { - let uri = uri.to_ascii_lowercase(); - uri.ends_with(".glb") - || uri.ends_with(".gltf") - || uri.ends_with(".obj") - || uri.ends_with(".fbx") - } - - fn is_probably_texture_uri(uri: &str) -> bool { - let uri = uri.to_ascii_lowercase(); - uri.ends_with(".png") - || uri.ends_with(".jpg") - || uri.ends_with(".jpeg") - || uri.ends_with(".tga") - || uri.ends_with(".bmp") - } - - let model_reference = self.model().path.clone(); - let model_title = match &model_reference.ref_type { - ResourceReferenceType::Unassigned { .. } => "None".to_string(), - ResourceReferenceType::ProcObj(obj) => { - match obj { - ProcObj::Cuboid { .. } => "Cuboid".to_string(), - } - } - _ => self.handle().label.clone(), - }; - - ui.vertical(|ui| { - let expand_id = ui.make_persistent_id(format!("mesh_renderer_expand_{:?}", entity)); - let mut expanded = ui.data_mut(|d| d.get_temp::<bool>(expand_id).unwrap_or(false)); - - let mut selected_model: Option<AssetHandle> = None; - let mut choose_proc_cuboid = false; - let mut choose_none = false; - - let (rect, response) = ui.allocate_exact_size( - egui::vec2(ui.available_width(), 72.0), - egui::Sense::click(), - ); - - let fill = if response.hovered() { - ui.visuals().widgets.hovered.bg_fill - } else { - ui.visuals().widgets.inactive.bg_fill - }; - - ui.painter() - .rect_filled(rect, 4.0, fill); - ui.painter() - .rect_stroke( - rect, - 4.0, - ui.visuals().widgets.inactive.bg_stroke, - egui::StrokeKind::Inside, - ); - - let mut card_ui = ui.new_child( - UiBuilder::new() - .layout(egui::Layout::top_down(egui::Align::Min)) - .max_rect(rect), - ); - - card_ui.horizontal(|ui| { - let arrow = if expanded { "🔽" } else { "▶️" }; - if ui.button(arrow).clicked() { - expanded = !expanded; - } - - ui.vertical(|ui| { - ui.label(RichText::new(&model_title).strong()); - ui.label( - RichText::new("Drop a model from the Asset Viewer") - .small() - .color(ui.visuals().weak_text_color()), - ); - }); - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ComboBox::from_id_salt("mesh_renderer_model_picker") - .selected_text(&model_title) - .show_ui(ui, |ui| { - if ui - .selectable_label( - matches!( - model_reference.ref_type, - ResourceReferenceType::Unassigned { .. } - ), - "None", - ) - .clicked() - { - choose_none = true; - } - - ui.separator(); - - if ui - .selectable_label( - matches!(model_reference.ref_type, ResourceReferenceType::ProcObj(ProcObj::Cuboid { .. })), - "Cuboid", - ) - .clicked() - { - choose_proc_cuboid = true; - } - - ui.separator(); - - for i in ASSET_REGISTRY.iter_model() { - if i.path.as_uri().is_none() { - continue; - } - if i.label.eq_ignore_ascii_case("light cube") { - continue; - } - - let is_selected = self.asset_handle() == *i.key(); - if ui - .selectable_label(is_selected, i.label.clone()) - .clicked() - { - selected_model = Some(*i.key()); - } - } - }); - }); - }); - - let pointer_released = ui.input(|i| i.pointer.any_released()); - if pointer_released && response.hovered() { - if let Some(asset) = cfg.dragged_asset.clone() { - if let Some(uri) = asset.path.as_uri() { - if is_probably_model_uri(uri) { - *signal = Signal::ReplaceModel(*entity, uri.to_string()); - cfg.dragged_asset = None; - } - } - } - } - - ui.data_mut(|d| d.insert_temp(expand_id, expanded)); - - if choose_proc_cuboid { - let default_size = match &model_reference.ref_type { - ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) => [ - f32::from_bits(size_bits[0]), - f32::from_bits(size_bits[1]), - f32::from_bits(size_bits[2]), - ], - _ => [1.0, 1.0, 1.0], - }; - *signal = Signal::SetProceduralCuboid(*entity, default_size); - } else if choose_none { - *signal = Signal::ClearModel(*entity); - } else if let Some(model) = selected_model { - if let Err(e) = self.set_asset_handle(model) { - fatal!("Unable to swap model: {}", e); - } - } - - if expanded { - ui.add_space(6.0); - - if let ResourceReferenceType::File(uri) = &model_reference.ref_type { - if is_probably_model_uri(uri) { - let mut import_scale = self.import_scale(); - ui.horizontal(|ui| { - ui.label("Import Scale"); - let resp = ui.add( - egui::DragValue::new(&mut import_scale) - .speed(0.01) - .range(0.0001..=10_000.0), - ); - - if resp.changed() { - self.set_import_scale(import_scale); - } - - if ui.button("Reset").clicked() { - self.set_import_scale(1.0); - } - }); - ui.add_space(6.0); - } - } - - if let ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) = &self.model().path.ref_type { - let mut size = [ - f32::from_bits(size_bits[0]), - f32::from_bits(size_bits[1]), - f32::from_bits(size_bits[2]), - ]; - - ui.label(RichText::new("Cuboid").strong()); - ui.horizontal(|ui| { - ui.label("Extents:"); - let mut changed = false; - ui.label("X"); - changed |= ui - .add(DragValue::new(&mut size[0]).speed(0.05).range(0.01..=10_000.0)) - .changed(); - ui.label("Y"); - changed |= ui - .add(DragValue::new(&mut size[1]).speed(0.05).range(0.01..=10_000.0)) - .changed(); - ui.label("Z"); - changed |= ui - .add(DragValue::new(&mut size[2]).speed(0.05).range(0.01..=10_000.0)) - .changed(); - - if changed { - *signal = Signal::UpdateProceduralCuboid(*entity, size); - } - }); - - ui.separator(); - } - - ui.label(RichText::new("Textures").strong()); - - for material in self.model().materials.iter() { - let material_name = material.name.clone(); - let mut tint_rgb = [material.tint[0], material.tint[1], material.tint[2]]; - let mut wrap_mode = material.wrap_mode; - let mut uv_tiling = material.uv_tiling; - let texture_label = material - .texture_tag - .clone() - .and_then(|tag| if is_probably_texture_uri(&tag) { Some(tag) } else { None }) - .unwrap_or_else(|| "(embedded)".to_string()); - - ui.horizontal(|ui| { - ui.label(RichText::new(&material_name).strong()); - - let (slot_rect, slot_resp) = ui.allocate_exact_size( - egui::vec2(160.0, 22.0), - egui::Sense::click(), - ); - - let slot_fill = if slot_resp.hovered() { - ui.visuals().widgets.hovered.bg_fill - } else { - ui.visuals().widgets.inactive.bg_fill - }; - ui.painter().rect_filled(slot_rect, 3.0, slot_fill); - ui.painter().rect_stroke( - slot_rect, - 3.0, - ui.visuals().widgets.inactive.bg_stroke, - egui::StrokeKind::Inside, - ); - - let mut slot_ui = ui.new_child( - UiBuilder::new() - .layout(egui::Layout::left_to_right(egui::Align::Center)) - .max_rect(slot_rect), - ); - slot_ui.add_space(4.0); - slot_ui.label( - RichText::new(texture_label) - .small() - .color(slot_ui.visuals().weak_text_color()), - ); - - if ui.button("Remove").clicked() { - *signal = Signal::ClearMaterialTexture(*entity, material_name.clone()); - } - - let mut wrap_changed = false; - egui::ComboBox::from_id_salt(format!( - "mesh_renderer_wrap_{:?}_{}", - entity, - material_name - )) - .selected_text(match wrap_mode { - TextureWrapMode::Repeat => "Repeat", - TextureWrapMode::Clamp => "Clamp", - }) - .show_ui(ui, |ui| { - wrap_changed |= ui - .selectable_value( - &mut wrap_mode, - TextureWrapMode::Repeat, - "Repeat", - ) - .changed(); - wrap_changed |= ui - .selectable_value( - &mut wrap_mode, - TextureWrapMode::Clamp, - "Clamp", - ) - .changed(); - }); - - if wrap_changed { - *signal = Signal::SetMaterialWrapMode( - *entity, - material_name.clone(), - wrap_mode, - ); - } - - if matches!(wrap_mode, TextureWrapMode::Repeat) { - ui.label("Repeat"); - let mut tiling_changed = ui - .add(DragValue::new(&mut uv_tiling[0]).speed(0.05).range(0.01..=10_000.0)) - .changed(); - ui.label("x"); - tiling_changed |= ui - .add(DragValue::new(&mut uv_tiling[1]).speed(0.05).range(0.01..=10_000.0)) - .changed(); - - if tiling_changed { - *signal = Signal::SetMaterialUvTiling( - *entity, - material_name.clone(), - uv_tiling, - ); - } - } - - let colour_changed = - egui::color_picker::color_edit_button_rgb(ui, &mut tint_rgb) - .changed(); - if colour_changed { - *signal = Signal::SetMaterialTint( - *entity, - material_name.clone(), - [tint_rgb[0], tint_rgb[1], tint_rgb[2], 1.0], - ); - } - - let pointer_released = ui.input(|i| i.pointer.any_released()); - if pointer_released && slot_resp.hovered() { - if let Some(asset) = cfg.dragged_asset.clone() { - if let Some(uri) = asset.path.as_uri() { - if is_probably_texture_uri(uri) { - *signal = Signal::SetMaterialTexture( - *entity, - material_name.clone(), - uri.to_string(), - wrap_mode, - ); - cfg.dragged_asset = None; - } - } - } - } - }); - } - } - }); - - ui.separator(); - } -} - -impl InspectableComponent for Light { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - CollapsingHeader::new("Light").default_open(true).show(ui, |ui| { - ui.horizontal(|ui| { - ComboBox::new("light_type", "Light Type") - .selected_text(self.light_component.light_type.to_string()) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.light_component.light_type, - LightType::Directional, - "Directional", - ); - ui.selectable_value(&mut self.light_component.light_type, LightType::Point, "Point"); - ui.selectable_value(&mut self.light_component.light_type, LightType::Spot, "Spot"); - }); - }); - - ui.separator(); - - inspect_light_transform( - &mut self.transform, - entity, - cfg, - ui, - undo_stack, - &self.light_component.light_type, - ); - - let is_point = matches!(self.light_component.light_type, LightType::Point); - let is_spot = matches!(self.light_component.light_type, LightType::Spot); - - ui.separator(); - let mut colour = self.light_component.colour.clone().as_vec3().to_array(); - ui.horizontal(|ui| { - ui.label("Colour"); - egui::color_picker::color_edit_button_rgb(ui, &mut colour) - }); - self.light_component.colour = Vec3::from_array(colour).as_dvec3(); - - ui.separator(); - ui.horizontal(|ui| { - ui.label("Intensity"); - ui.add(egui::Slider::new(&mut self.light_component.intensity, 0.0..=10.0)); - }); - - ui.separator(); - ui.horizontal(|ui| { - ui.checkbox(&mut self.light_component.enabled, "Enabled"); - ui.checkbox(&mut self.light_component.visible, "Visible"); - }); - - if is_spot || is_point { - ui.separator(); - ui.horizontal(|ui| { - ComboBox::new("attenuation_range", "Range") - .selected_text(format!("Range {}", self.light_component.attenuation.range)) - .show_ui(ui, |ui| { - for (preset, label) in ATTENUATION_PRESETS { - ui.selectable_value(&mut self.light_component.attenuation, *preset, *label); - } - }); - }); - } - - if is_spot { - ui.separator(); - ui.horizontal(|ui| { - ui.add( - egui::Slider::new(&mut self.light_component.cutoff_angle, 1.0..=89.0) - .text("Inner") - .suffix("°") - .step_by(0.1), - ); - }); - - ui.horizontal(|ui| { - ui.add( - egui::Slider::new(&mut self.light_component.outer_cutoff_angle, 1.0..=90.0) - .text("Outer") - .suffix("°") - .step_by(0.1), - ); - }); - - if self.light_component.outer_cutoff_angle <= self.light_component.cutoff_angle { - self.light_component.outer_cutoff_angle = self.light_component.cutoff_angle + 1.0; - } - - let cone_softness = self.light_component.outer_cutoff_angle - self.light_component.cutoff_angle; - ui.label(format!("Soft edge: {:.1}°", cone_softness)); - } - - ui.separator(); - - ui.label("Shadows"); - ui.checkbox(&mut self.light_component.cast_shadows, "Cast Shadows"); - ui.horizontal(|ui| { - ui.label("Depth"); - ui.add(egui::DragValue::new(&mut self.light_component.depth.start).speed(0.1)); - ui.label(".."); - ui.add(egui::DragValue::new(&mut self.light_component.depth.end).speed(0.1)); - }); - - if self.light_component.depth.end < self.light_component.depth.start { - self.light_component.depth.end = self.light_component.depth.start; - } - - ui.separator(); - }); - } -} - -impl InspectableComponent for Camera3D { - fn inspect( - &mut self, - entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - undo_stack: &mut Vec<UndoableAction>, - signal: &mut Signal, - label: &mut String, - ) { - self.transform - .inspect(entity, cfg, ui, undo_stack, signal, label); - - ui.vertical(|ui| { - CollapsingHeader::new("Camera Settings") - .default_open(true) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Type"); - ComboBox::from_id_salt("camera_type") - .selected_text(format!("{:?}", self.camera_type)) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.camera_type, - CameraType::Normal, - "Normal", - ); - ui.selectable_value( - &mut self.camera_type, - CameraType::Debug, - "Debug", - ); - ui.selectable_value( - &mut self.camera_type, - CameraType::Player, - "Player", - ); - }); - }); - - ui.horizontal(|ui| { - ui.label("FOV"); - ui.add(egui::Slider::new(&mut self.fov, 1.0..=179.0).suffix("°")); - }); - - ui.horizontal(|ui| { - ui.label("Near Plane"); - ui.add( - egui::DragValue::new(&mut self.near) - .speed(0.1) - .range(0.01..=1000.0), - ); - }); - - ui.horizontal(|ui| { - ui.label("Far Plane"); - ui.add( - egui::DragValue::new(&mut self.far) - .speed(1.0) - .range(0.1..=10000.0), - ); - }); - - ui.separator(); - - ui.horizontal(|ui| { - ui.label("Speed"); - ui.add(egui::DragValue::new(&mut self.speed).speed(0.1)); - }); - - ui.horizontal(|ui| { - ui.label("Sensitivity"); - ui.add(egui::DragValue::new(&mut self.sensitivity).speed(0.01)); - }); - - ui.separator(); - - ui.checkbox(&mut self.starting_camera, "Starting Camera"); - }); - }); - ui.separator(); - } -} @@ -38,7 +38,7 @@ impl<'a> EditorTabViewer<'a> { let mut is_starting = comp.starting_camera; let is_starting_label = if comp.camera_type == CameraType::Debug { - is_starting = false; + is_starting = true; "Cannot set a Debug camera as starting" } else if is_starting { "Already set as starting" @@ -17,7 +17,7 @@ use eucalyptus_core::physics::collider::ColliderShapeKey; use eucalyptus_core::physics::collider::shader::{ColliderInstanceRaw, create_wireframe_geometry}; use eucalyptus_core::properties::CustomProperties; use eucalyptus_core::states::{Label, WorldLoadingStatus}; -use glam::{DMat4, Mat4}; +use glam::{Mat4}; use log; use parking_lot::Mutex; use std::collections::HashMap; @@ -506,7 +506,9 @@ impl Scene for Editor { let environment_bind_group = &sky.bind_group; let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); - if self.scene_globals_bind_group.is_none() { + if let Some(scene_globals) = &mut self.scene_globals_bind_group { + scene_globals.update(&graphics, &globals.buffer, camera.buffer()); + } else { self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( &graphics, &globals.buffer, @@ -724,19 +726,11 @@ impl Scene for Editor { > = HashMap::new(); let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>(); - let mut entity_count = 0; - let mut collider_count = 0; for (entity_transform, group) in q.iter() { - entity_count += 1; for collider in &group.colliders { - collider_count += 1; let world_tf = entity_transform.sync(); - let entity_matrix = DMat4::from_rotation_translation( - world_tf.rotation, - world_tf.position, - ) - .as_mat4(); + let entity_matrix = world_tf.matrix().as_mat4(); let offset_transform = Transform::new() .with_offset(collider.translation, collider.rotation); @@ -4,7 +4,6 @@ pub mod camera; pub mod debug; pub mod editor; pub mod menu; -pub mod physics; pub mod plugin; pub mod process; pub mod signal; @@ -1,3 +0,0 @@ -// pub mod rigidbody; -// pub mod collider; -// pub mod kcc; @@ -1,19 +0,0 @@ -use egui::{ComboBox, Ui}; -use hecs::Entity; -use eucalyptus_core::physics::collider::{Collider, ColliderShape}; -use eucalyptus_core::states::Label; -use crate::editor::{Signal, StaticallyKept, UndoableAction}; - -impl InspectableComponent for Collider { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - label: &mut String - ) { - - } -} @@ -1,140 +0,0 @@ -use egui::{ComboBox, DragValue, Ui}; -use hecs::Entity; -use eucalyptus_core::physics::kcc::KCC; -use eucalyptus_core::rapier3d::control::{CharacterAutostep, CharacterLength}; -use eucalyptus_core::rapier3d::na::{UnitVector3, Vector3}; -use eucalyptus_core::states::Label; -use crate::editor::component::InspectableComponent; -use crate::editor::{Signal, StaticallyKept, UndoableAction}; - -impl InspectableComponent for KCC { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - label: &mut String - ) { - self.entity = Label::new(label.clone()); - - fn edit_character_length(ui: &mut Ui, id_salt: impl std::hash::Hash, value: &mut CharacterLength, text: &str) { - ui.horizontal(|ui| { - ui.label(text); - - let (mut kind, mut v) = match *value { - CharacterLength::Absolute(x) => (0, x), - CharacterLength::Relative(x) => (1, x), - }; - - ComboBox::from_id_salt(id_salt) - .selected_text(match kind { - 0 => "Absolute", - _ => "Relative", - }) - .show_ui(ui, |ui| { - ui.selectable_value(&mut kind, 0, "Absolute"); - ui.selectable_value(&mut kind, 1, "Relative"); - }); - - ui.add(DragValue::new(&mut v).speed(0.01)); - - *value = if kind == 0 { - CharacterLength::Absolute(v) - } else { - CharacterLength::Relative(v) - }; - }); - } - - ui.vertical(|ui| { - ui.label("Up Vector:"); - let up = self.controller.up; - let mut up_v = Vector3::new(up.x, up.y, up.z); - - ui.horizontal(|ui| { - ui.label("X:"); - ui.add(DragValue::new(&mut up_v.x).speed(0.01)); - ui.label("Y:"); - ui.add(DragValue::new(&mut up_v.y).speed(0.01)); - ui.label("Z:"); - ui.add(DragValue::new(&mut up_v.z).speed(0.01)); - }); - - if up_v.norm_squared() > 0.0 { - self.controller.up = UnitVector3::new_normalize(up_v).into(); - } - - ui.add_space(8.0); - - edit_character_length(ui, "kcc_offset", &mut self.controller.offset, "Offset:"); - - ui.add_space(8.0); - ui.separator(); - - ui.checkbox(&mut self.controller.slide, "Slide against floor?"); - - ui.label("Slope Angles (degrees):"); - ui.horizontal(|ui| { - ui.label("Max climb:"); - let mut deg = self.controller.max_slope_climb_angle.to_degrees(); - if ui.add(DragValue::new(&mut deg).speed(1.0)).changed() { - self.controller.max_slope_climb_angle = deg.to_radians(); - } - }); - ui.horizontal(|ui| { - ui.label("Min slide:"); - let mut deg = self.controller.min_slope_slide_angle.to_degrees(); - if ui.add(DragValue::new(&mut deg).speed(1.0)).changed() { - self.controller.min_slope_slide_angle = deg.to_radians(); - } - }); - - ui.add_space(8.0); - - ui.horizontal(|ui| { - ui.label("Normal nudge:"); - ui.add( - egui::Slider::new(&mut self.controller.normal_nudge_factor, 0.001..=0.5) - .logarithmic(true) - .smallest_positive(0.001) - .largest_finite(0.5) - .suffix(" m") - ); - }); - - ui.add_space(8.0); - ui.separator(); - - let mut enable_snap = self.controller.snap_to_ground.is_some(); - ui.checkbox(&mut enable_snap, "Snap to ground"); - if enable_snap && self.controller.snap_to_ground.is_none() { - self.controller.snap_to_ground = Some(CharacterLength::Absolute(0.2)); - } else if !enable_snap { - self.controller.snap_to_ground = None; - } - - if let Some(ref mut snap) = self.controller.snap_to_ground { - edit_character_length(ui, "kcc_snap_to_ground", snap, "Snap distance:"); - } - - ui.add_space(8.0); - ui.separator(); - - let mut enable_autostep = self.controller.autostep.is_some(); - ui.checkbox(&mut enable_autostep, "Enable autostep"); - if enable_autostep && self.controller.autostep.is_none() { - self.controller.autostep = Some(CharacterAutostep::default()); - } else if !enable_autostep { - self.controller.autostep = None; - } - - if let Some(step) = &mut self.controller.autostep { - edit_character_length(ui, "kcc_autostep_max_height", &mut step.max_height, "Max height:"); - edit_character_length(ui, "kcc_autostep_min_width", &mut step.min_width, "Min width:"); - ui.checkbox(&mut step.include_dynamic_bodies, "Include dynamic bodies"); - } - }); - } -} @@ -1,106 +0,0 @@ -use egui::{ComboBox, Ui}; -use hecs::Entity; -use eucalyptus_core::physics::rigidbody::{RigidBody, RigidBodyMode}; -use eucalyptus_core::states::Label; -use crate::editor::component::InspectableComponent; -use crate::editor::{Signal, StaticallyKept, UndoableAction}; - -impl InspectableComponent for RigidBody { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - label: &mut String - ) { - ui.vertical(|ui| { - self.entity = Label::new(label.clone()); - - let mut selected = self.mode.clone(); - ComboBox::from_id_salt("rigidbody") - .selected_text(format!("{:?}", self.mode)) - .show_ui(ui, |ui| { - ui.selectable_value(&mut selected, RigidBodyMode::Dynamic, "Dynamic"); - ui.selectable_value(&mut selected, RigidBodyMode::Fixed, "Fixed"); - ui.selectable_value(&mut selected, RigidBodyMode::KinematicPosition, "Kinematic Position"); - ui.selectable_value(&mut selected, RigidBodyMode::KinematicVelocity, "Kinematic Velocity"); - }); - - if selected != self.mode { - self.mode = selected; - } - - ui.add_space(8.0); - - ui.horizontal(|ui| { - ui.label("Gravity Scale:"); - ui.add(egui::DragValue::new(&mut self.gravity_scale) - .speed(0.1) - .range(0.0..=10.0)); - }); - - - ui.checkbox(&mut self.can_sleep, "Can Sleep"); - - ui.checkbox(&mut self.sleeping, "Initially sleeping?"); - - ui.checkbox(&mut self.ccd_enabled, "CCD Enabled"); - - ui.add_space(8.0); - - ui.label("Linear Velocity:"); - ui.horizontal(|ui| { - ui.label("X:"); - ui.add(egui::DragValue::new(&mut self.linvel[0]).speed(0.1)); - ui.label("Y:"); - ui.add(egui::DragValue::new(&mut self.linvel[1]).speed(0.1)); - ui.label("Z:"); - ui.add(egui::DragValue::new(&mut self.linvel[2]).speed(0.1)); - }); - - ui.label("Angular Velocity:"); - ui.horizontal(|ui| { - ui.label("X:"); - ui.add(egui::DragValue::new(&mut self.angvel[0]).speed(0.1)); - ui.label("Y:"); - ui.add(egui::DragValue::new(&mut self.angvel[1]).speed(0.1)); - ui.label("Z:"); - ui.add(egui::DragValue::new(&mut self.angvel[2]).speed(0.1)); - }); - - ui.add_space(8.0); - - ui.horizontal(|ui| { - ui.label("Linear Damping:"); - ui.add(egui::DragValue::new(&mut self.linear_damping) - .speed(0.01) - .range(0.0..=10.0)); - }); - - ui.horizontal(|ui| { - ui.label("Angular Damping:"); - ui.add(egui::DragValue::new(&mut self.angular_damping) - .speed(0.01) - .range(0.0..=10.0)) - }); - - ui.add_space(8.0); - - ui.label("Lock Translation:"); - ui.horizontal(|ui| { - ui.checkbox(&mut self.lock_translation.x, "X"); - ui.checkbox(&mut self.lock_translation.y, "Y"); - ui.checkbox(&mut self.lock_translation.z, "Z"); - }); - - ui.label("Lock Rotation:"); - ui.horizontal(|ui| { - ui.checkbox(&mut self.lock_rotation.x, "X"); - ui.checkbox(&mut self.lock_rotation.y, "Y"); - ui.checkbox(&mut self.lock_rotation.z, "Z"); - }); - }); - } -} @@ -8,6 +8,12 @@ use std::fs; use std::path::{Path, PathBuf}; use tree_sitter::{Parser, Query, QueryCursor}; +struct AnnotationInfo<'a> { + node: tree_sitter::Node<'a>, + name: Option<String>, + value_args: Option<tree_sitter::Node<'a>>, +} + /// A group of manifests. #[derive(Debug, Clone)] pub struct ScriptManifest { @@ -127,6 +133,16 @@ impl KotlinProcessor { return Ok(Some(ManifestItem::new(fqcn, class_name, tags, file_path))); } + if std::env::var("MAGNA_CARTA_DEBUG").is_ok() && source_code.contains("@Runnable") { + let class_names = self.collect_class_names(root_node, source_code)?; + eprintln!( + "magna-carta: @Runnable found but no manifest item for {}. Classes seen: {:?}", + file_path.display(), + class_names + ); + self.debug_dump_annotations(root_node, source_code, &file_path)?; + } + Ok(None) } @@ -181,10 +197,10 @@ impl KotlinProcessor { (annotation (constructor_invocation (user_type - (type_identifier) @annotation_name2) - (value_arguments)? @value_args) - (#eq? @annotation_name2 "Runnable"))) - (type_identifier) @class_name2) + (type_identifier) @annotation_name2) + (value_arguments)? @value_args) + (#eq? @annotation_name2 "Runnable"))) + (type_identifier) @class_name2) "#, )?; @@ -253,9 +269,333 @@ impl KotlinProcessor { } } + if let Some(result) = self.extract_class_info_fallback(root_node, source)? { + return Ok(Some(result)); + } + + self.extract_class_info_loose(root_node, source) + } + + fn extract_class_info_fallback( + &self, + root_node: tree_sitter::Node, + source: &str, + ) -> anyhow::Result<Option<(String, Vec<String>)>> { + let mut stack = vec![root_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "class_declaration" { + if let Some(class_name) = self.class_name_from_node(node, source)? { + if let Some(modifiers) = self.first_child_by_kind(node, "modifiers") { + let mut mod_cursor = modifiers.walk(); + for modifier in modifiers.children(&mut mod_cursor) { + if modifier.kind() != "annotation" { + continue; + } + + let (annotation_name, value_args) = + self.annotation_name_and_args(modifier, source)?; + if annotation_name.as_deref() == Some("Runnable") { + let tags = if let Some(args) = value_args { + self.extract_tags_from_value_args(args, source)? + } else { + let raw = modifier.utf8_text(source.as_bytes())?; + self.extract_tags_from_raw_annotation(raw) + }; + + return Ok(Some((class_name, tags))); + } + } + } + } + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + Ok(None) + } + + fn class_name_from_node( + &self, + class_node: tree_sitter::Node, + source: &str, + ) -> anyhow::Result<Option<String>> { + let mut cursor = class_node.walk(); + for child in class_node.children(&mut cursor) { + if child.kind() == "type_identifier" { + let text = child.utf8_text(source.as_bytes())?; + return Ok(Some(text.to_string())); + } + } + + Ok(None) + } + + fn annotation_name_and_args<'a>( + &self, + annotation_node: tree_sitter::Node<'a>, + source: &str, + ) -> anyhow::Result<(Option<String>, Option<tree_sitter::Node<'a>>)> { + let mut name: Option<String> = None; + let mut value_args: Option<tree_sitter::Node> = None; + let mut stack = vec![annotation_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "value_arguments" && value_args.is_none() { + value_args = Some(node); + } + + if node.kind() == "type_identifier" { + let text = node.utf8_text(source.as_bytes())?; + name = Some(text.to_string()); + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + Ok((name, value_args)) + } + + fn first_child_by_kind<'a>( + &self, + node: tree_sitter::Node<'a>, + kind: &str, + ) -> Option<tree_sitter::Node<'a>> { + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + if child.kind() == kind { + return Some(child); + } + } + + None + } + + fn extract_class_info_loose<'a>( + &self, + root_node: tree_sitter::Node<'a>, + source: &str, + ) -> anyhow::Result<Option<(String, Vec<String>)>> { + let annotations = self.collect_annotation_nodes(root_node, source)?; + let classes = self.collect_class_nodes(root_node); + + for annotation in annotations { + if annotation.name.as_deref() != Some("Runnable") { + continue; + } + + let Some((class_node, _)) = classes + .iter() + .filter(|(node, _)| node.start_byte() > annotation.node.end_byte()) + .min_by_key(|(node, _)| node.start_byte().saturating_sub(annotation.node.end_byte())) + else { + continue; + }; + + if let Some(class_name) = self.class_name_from_node(*class_node, source)? { + let tags = if let Some(args) = annotation.value_args { + self.extract_tags_from_value_args(args, source)? + } else { + let raw = annotation.node.utf8_text(source.as_bytes())?; + let mut tags = self.extract_tags_from_raw_annotation(raw); + if tags.is_empty() { + tags = self.extract_tags_from_source_slice( + source, + annotation.node.start_byte(), + class_node.start_byte(), + ); + } + tags + }; + + return Ok(Some((class_name, tags))); + } + } + Ok(None) } + fn collect_class_nodes<'a>( + &self, + root_node: tree_sitter::Node<'a>, + ) -> Vec<(tree_sitter::Node<'a>, usize)> { + let mut nodes = Vec::new(); + let mut stack = vec![root_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "class_declaration" { + nodes.push((node, node.start_byte())); + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + nodes + } + + fn collect_annotation_nodes<'a>( + &self, + root_node: tree_sitter::Node<'a>, + source: &str, + ) -> anyhow::Result<Vec<AnnotationInfo<'a>>> { + let mut nodes = Vec::new(); + let mut stack = vec![root_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "annotation" { + let (name, value_args) = self.annotation_name_and_args(node, source)?; + nodes.push(AnnotationInfo { + node, + name, + value_args, + }); + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + Ok(nodes) + } + + fn extract_tags_from_raw_annotation(&self, raw: &str) -> Vec<String> { + let mut tags = Vec::new(); + let mut current = String::new(); + let mut in_string = false; + let mut quote_char = '\0'; + + for ch in raw.chars() { + if !in_string { + if ch == '"' || ch == '\'' { + in_string = true; + quote_char = ch; + current.clear(); + } + continue; + } + + if ch == quote_char { + if !current.is_empty() { + tags.push(current.clone()); + } + in_string = false; + quote_char = '\0'; + current.clear(); + continue; + } + + current.push(ch); + } + + tags + } + + fn extract_tags_from_source_slice( + &self, + source: &str, + start: usize, + end: usize, + ) -> Vec<String> { + let Some(slice) = source.get(start..end) else { + return Vec::new(); + }; + + self.extract_tags_from_raw_annotation(slice) + } + + fn collect_class_names( + &self, + root_node: tree_sitter::Node, + source: &str, + ) -> anyhow::Result<Vec<String>> { + let mut names = Vec::new(); + let mut stack = vec![root_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "class_declaration" { + if let Some(name) = self.class_name_from_node(node, source)? { + names.push(name); + } + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + Ok(names) + } + + fn debug_dump_annotations( + &self, + root_node: tree_sitter::Node, + source: &str, + file_path: &Path, + ) -> anyhow::Result<()> { + let mut stack = vec![root_node]; + + while let Some(node) = stack.pop() { + if node.kind() == "class_declaration" { + let class_name = self + .class_name_from_node(node, source)? + .unwrap_or_else(|| "<unknown>".to_string()); + let annotations = self.collect_annotation_debug(node, source)?; + eprintln!( + "magna-carta: {} class {} annotations: {:?}", + file_path.display(), + class_name, + annotations + ); + } + + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + stack.push(child); + } + } + + Ok(()) + } + + fn collect_annotation_debug( + &self, + class_node: tree_sitter::Node, + source: &str, + ) -> anyhow::Result<Vec<String>> { + let mut results = Vec::new(); + + let Some(modifiers) = self.first_child_by_kind(class_node, "modifiers") else { + return Ok(results); + }; + + let mut mod_cursor = modifiers.walk(); + for modifier in modifiers.children(&mut mod_cursor) { + if modifier.kind() != "annotation" { + continue; + } + + let raw = modifier.utf8_text(source.as_bytes())?; + let (annotation_name, _) = self.annotation_name_and_args(modifier, source)?; + let name = annotation_name.unwrap_or_else(|| "<unknown>".to_string()); + results.push(format!("{} => {}", name, raw.trim())); + } + + Ok(results) + } + fn extract_tags_from_value_args( &self, value_args_node: tree_sitter::Node, @@ -25,10 +25,10 @@ use eucalyptus_core::rapier3d::prelude::QueryFilter; use eucalyptus_core::scene::loading::{IsSceneLoaded, SCENE_LOADER, SceneLoadResult}; use eucalyptus_core::states::SCENES; use eucalyptus_core::states::{Label, PROJECT}; -use glam::{DMat4, DQuat, DVec3, Mat4, Quat, Vec2, vec2}; +use glam::{DQuat, DVec3, Mat4, Quat, Vec2}; use hecs::Entity; -use kino_ui::widgets::rect::Rectangle; -use kino_ui::widgets::{Border, Fill}; +// use kino_ui::widgets::rect::Rectangle; +// use kino_ui::widgets::{Border, Fill}; use std::collections::HashMap; use wgpu::util::DeviceExt; use winit::event::WindowEvent; @@ -55,7 +55,7 @@ impl Scene for PlayMode { } } - fn physics_update(&muprintlnt self, dt: f32, _graphics: Arc<SharedGraphicsContext>) { + fn physics_update(&mut self, dt: f32, _graphics: Arc<SharedGraphicsContext>) { if self.scripts_ready { let _ = self .script_manager @@ -816,7 +816,9 @@ impl Scene for PlayMode { let environment_bind_group = &sky.bind_group; let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); - if self.scene_globals_bind_group.is_none() { + if let Some(scene_globals) = &mut self.scene_globals_bind_group { + scene_globals.update(&graphics, &globals.buffer, camera.buffer()); + } else { self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( &graphics, &globals.buffer, @@ -955,37 +957,6 @@ impl Scene for PlayMode { } } - // collider pipeline - { - let show_hitboxes = self - .current_scene - .as_ref() - .and_then(|scene_name| { - let scenes = SCENES.read(); - scenes - .iter() - .find(|scene| &scene.scene_name == scene_name) - .map(|scene| scene.settings.show_hitboxes) - }) - .unwrap_or(false); - - // UI_CONTEXT.with(|v| { - // let commands = graphics.yakui_renderer.lock().paint( - // &mut v.borrow().yakui_state.lock(), - // &graphics.device, - // &graphics.queue, - // SurfaceInfo { - // format: Texture::TEXTURE_FORMAT, - // sample_count: 1, - // color_attachment: &graphics.viewport_texture.view, - // resolve_target: None, - // } - // ); - // - // graphics.queue.submit([commands]); - // }); - } - if let Some(sky) = &self.sky_pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("sky render pass"), @@ -1067,11 +1038,7 @@ impl Scene for PlayMode { for collider in &group.colliders { let world_tf = entity_transform.sync(); - let entity_matrix = DMat4::from_rotation_translation( - world_tf.rotation, - world_tf.position, - ) - .as_mat4(); + let entity_matrix = world_tf.matrix().as_mat4(); let offset_transform = Transform::new() .with_offset(collider.translation, collider.rotation); @@ -1161,6 +1128,8 @@ impl Scene for PlayMode { } } + fn exit(&mut self, _event_loop: &ActiveEventLoop) {} + fn handle_event(&mut self, event: &WindowEvent) { // UI_CONTEXT.with(|yakui_cell| { // let yak = yakui_cell.borrow(); @@ -1175,8 +1144,6 @@ impl Scene for PlayMode { } } - fn exit(&mut self, _event_loop: &ActiveEventLoop) {} - fn run_command(&mut self) -> SceneCommand { std::mem::replace(&mut self.scene_command, SceneCommand::None) } @@ -203,6 +203,12 @@ typedef struct CharacterCollisionArray { IndexNativeArray collisions; } CharacterCollisionArray; +typedef struct CharacterMovementResult { + NVector3 translation; + bool grounded; + bool is_sliding_down_slope; +} CharacterMovementResult; + typedef void* CommandBufferPtr; typedef struct u64Array { @@ -531,6 +537,7 @@ int32_t dropbear_input_print_input_state(InputStatePtr input); int32_t dropbear_input_set_cursor_hidden(CommandBufferPtr command_buffer, InputStatePtr input, bool hidden); int32_t dropbear_input_set_cursor_locked(CommandBufferPtr command_buffer, InputStatePtr input, bool locked); int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0); +int32_t dropbear_kcc_get_movement_result(WorldPtr world, uint64_t entity, CharacterMovementResult* out0, bool* out0_present); int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time); int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation); @@ -14,4 +14,4 @@ package com.dropbear */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) -annotation class Runnable(val tags: Array<String> = []) +annotation class Runnable(vararg val tags: String) @@ -0,0 +1,9 @@ +package com.dropbear.physics + +import com.dropbear.math.Vector3d + +data class CharacterMovementResult( + val translation: Vector3d, + val grounded: Boolean, + val isSlidingDownSlope: Boolean, +) @@ -11,6 +11,9 @@ import kotlin.math.cos class KinematicCharacterController( val entity: EntityId, ) : Component(entity, "KCC") { + val movementResult: CharacterMovementResult? + get() = getMovementResult() + /** * Moves the character by a translation (displacement) for this tick. * @@ -53,14 +56,9 @@ class KinematicCharacterController( * Returns true if the character is currently in contact with a "floor-like" surface. * * This uses the cached character-collision hits and checks if any collision normal points upward. - * - * @param minUpwardNormalY Minimum Y component of the contact normal to be considered floor. - * Default is ~45° slope limit (cos(45°) ~= 0.707). */ - fun isOnFloor(minUpwardNormalY: Double = cos(degreesToRadians(45.0))): Boolean { - return getHits().any { hit -> - hit.status != ShapeCastStatus.Failed && hit.normal1.y >= minUpwardNormalY - } + fun isGrounded(): Boolean { + return movementResult?.grounded == true } companion object : ComponentType<KinematicCharacterController> { @@ -74,4 +72,5 @@ internal expect fun kccExistsForEntity(entityId: EntityId): Boolean internal expect fun KinematicCharacterController.moveCharacter(dt: Double, translation: Vector3d) internal expect fun KinematicCharacterController.setRotationNative(rotation: Quaterniond) -internal expect fun KinematicCharacterController.getHitsNative(): List<CharacterCollision> +internal expect fun KinematicCharacterController.getHitsNative(): List<CharacterCollision> +internal expect fun KinematicCharacterController.getMovementResult(): CharacterMovementResult? @@ -15,4 +15,5 @@ public class KinematicCharacterControllerNative { public static native void moveCharacter(long worldHandle, long physicsHandle, long entityHandle, Vector3d translation, double deltaTime); public static native void setRotation(long worldHandle, long physicsHandle, long entityHandle, Quaterniond rotation); public static native CharacterCollision[] getHit(long worldHandle, long entity); + public static native CharacterMovementResult getMovementResult(long worldHandle, long entity); } @@ -1,12 +0,0 @@ -package com.dropbear.ui.widgets; - -import com.dropbear.EucalyptusCoreLoader; - -public class ButtonNative { - static { - new EucalyptusCoreLoader().ensureLoaded(); - } - - public static native boolean getClicked(long uiBufferHandle, long id); - public static native boolean getHovering(long uiBufferHandle, long id); -} @@ -1,12 +0,0 @@ -package com.dropbear.ui.widgets; - -import com.dropbear.EucalyptusCoreLoader; - -public class CheckboxNative { - static { - new EucalyptusCoreLoader().ensureLoaded(); - } - - public static native boolean getChecked(long uiBufferHandle, long id); - public static native boolean hasCheckedState(long uiBufferHandle, long id); -} @@ -32,4 +32,11 @@ internal actual fun KinematicCharacterController.getHitsNative(): List<Character DropbearEngine.native.worldHandle, entity.raw, ).toList() +} + +internal actual fun KinematicCharacterController.getMovementResult(): CharacterMovementResult? { + return KinematicCharacterControllerNative.getMovementResult( + DropbearEngine.native.worldHandle, + entity.raw + ) } @@ -17,4 +17,8 @@ internal actual fun kccExistsForEntity(entityId: EntityId): Boolean { internal actual fun KinematicCharacterController.getHitsNative(): List<CharacterCollision> { TODO("Not yet implemented") +} + +internal actual fun KinematicCharacterController.getMovementResult(): CharacterMovementResult? { + TODO("Not yet implemented") }