tirbofish/dropbear · commit
751a9c98a9d3229315f086a1ae4095298f2948fd
fix: light cube rendering
Signature present but could not be verified.
Unverified
@@ -3,7 +3,7 @@ use std::sync::Arc; use glam::Mat4; use wgpu::util::DeviceExt; use crate::graphics::SharedGraphicsContext; -use crate::model::{AnimationInterpolation, ChannelValues, Model, NodeTransform}; +use crate::model::{Animation, AnimationInterpolation, ChannelValues, Model, NodeTransform}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AnimationComponent { @@ -18,6 +18,9 @@ pub struct AnimationComponent { #[serde(default)] pub is_playing: bool, + #[serde(default)] + pub animation_settings: HashMap<usize, AnimationSettings>, + #[serde(skip)] pub local_pose: HashMap<usize, NodeTransform>, #[serde(skip)] @@ -27,6 +30,32 @@ pub struct AnimationComponent { pub bone_buffer: Option<wgpu::Buffer>, #[serde(skip)] pub bind_group: Option<wgpu::BindGroup>, + + #[serde(skip)] + pub available_animations: Vec<String>, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct AnimationSettings { + #[serde(default)] + pub time: f32, + #[serde(default)] + pub speed: f32, + #[serde(default)] + pub looping: bool, + #[serde(default)] + pub is_playing: bool, +} + +impl Default for AnimationSettings { + fn default() -> Self { + Self { + time: 0.0, + speed: 1.0, + looping: true, + is_playing: true, + } + } } impl Default for AnimationComponent { @@ -37,10 +66,12 @@ impl Default for AnimationComponent { speed: 1.0, looping: true, is_playing: true, + animation_settings: HashMap::new(), local_pose: HashMap::new(), skinning_matrices: Vec::new(), bone_buffer: None, bind_group: None, + available_animations: vec![], } } } @@ -52,39 +83,66 @@ impl AnimationComponent { pub fn update(&mut self, dt: f32, model: &Model) { puffin::profile_function!(&model.label); - if !self.is_playing || self.active_animation_index.is_none() { + self.available_animations = model.animations.iter().map(|v| v.name.clone()).collect::<Vec<_>>(); + + let Some(anim_idx) = self.active_animation_index else { + return; + }; + + if anim_idx >= model.animations.len() { return; } - let anim_idx = self.active_animation_index.unwrap(); + let settings = self + .animation_settings + .entry(anim_idx) + .or_insert_with(|| AnimationSettings { + time: self.time, + speed: self.speed, + looping: self.looping, + is_playing: self.is_playing, + }); + + if !settings.is_playing { + self.time = settings.time; + self.speed = settings.speed; + self.looping = settings.looping; + self.is_playing = settings.is_playing; + return; + } let animation = &model.animations[anim_idx]; - self.time += dt * self.speed; - if self.looping { + settings.time += dt * settings.speed; + if settings.looping { if animation.duration > 0.0 { - self.time %= animation.duration; + settings.time %= animation.duration; } } else { - self.time = self.time.clamp(0.0, animation.duration); - if self.time >= animation.duration { - self.is_playing = false; + settings.time = settings.time.clamp(0.0, animation.duration); + if settings.time >= animation.duration { + settings.is_playing = false; } } + self.time = settings.time; + self.speed = settings.speed; + self.looping = settings.looping; + self.is_playing = settings.is_playing; + for channel in &animation.channels { let count = channel.times.len(); if count == 0 { continue; } - if count == 1 || self.time <= channel.times[0] { + if count == 1 || settings.time <= channel.times[0] { Self::apply_single_keyframe(channel, 0, &mut self.local_pose, model); continue; } - if self.time >= channel.times[count - 1] { + if settings.time >= channel.times[count - 1] { Self::apply_single_keyframe(channel, count - 1, &mut self.local_pose, model); continue; } - let next_idx = channel.times.partition_point(|&t| t <= self.time); + let next_idx = channel.times.partition_point(|&t| t <= settings.time); let prev_idx = next_idx.saturating_sub(1); let start_time = channel.times[prev_idx]; @@ -92,7 +150,7 @@ impl AnimationComponent { let duration = end_time - start_time; let factor = if duration > 0.0 { - (self.time - start_time) / duration + (settings.time - start_time) / duration } else { 0.0 }; @@ -158,7 +216,7 @@ impl AnimationComponent { let idx0 = prev_idx * 3; let idx1 = next_idx * 3; - if idx1 + 1 >= values.len() { + if idx1 + 1 >= values.len() { values[idx0 + 1] } else { let p0 = values[idx0 + 1]; @@ -124,7 +124,7 @@ pub struct Skin { } /// An animation that can be played on a skeleton -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct Animation { pub name: String, pub channels: Vec<AnimationChannel>, @@ -132,7 +132,7 @@ pub struct Animation { } /// Describes how an animation affects a specific node -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct AnimationChannel { /// Target node index in the Model's nodes array pub target_node: usize, @@ -144,7 +144,7 @@ pub struct AnimationChannel { pub interpolation: AnimationInterpolation, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum ChannelValues { Translations(Vec<glam::Vec3>), Rotations(Vec<glam::Quat>), @@ -44,7 +44,7 @@ impl DropbearShaderPipeline for LightCubePipeline { compilation_options: Default::default(), buffers: &[ // model - ModelVertex::desc(), + LightCubeVertex::desc(), // instance InstanceInput::desc(), ], @@ -178,6 +178,24 @@ impl LightCubePipeline { } } +pub struct LightCubeVertex; + +impl LightCubeVertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: size_of::<ModelVertex>() as BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} + /// As mapped in `shaders/light.slang` as /// ```wgsl /// struct InstanceInput { @@ -234,4 +252,4 @@ impl Into<InstanceInput> for DMat4 { model_matrix: self.as_mat4().to_cols_array_2d(), } } -} +} @@ -1,7 +1,7 @@ use std::sync::Arc; -use egui::{CollapsingHeader, Ui}; +use egui::{CollapsingHeader, ComboBox, Ui}; use hecs::{Entity, World}; -use dropbear_engine::animation::AnimationComponent; +use dropbear_engine::animation::{AnimationComponent, AnimationSettings}; use dropbear_engine::asset::ASSET_REGISTRY; use dropbear_engine::entity::MeshRenderer; use dropbear_engine::graphics::SharedGraphicsContext; @@ -58,47 +58,84 @@ impl Component for AnimationComponent { impl InspectableComponent for AnimationComponent { fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| { - let mut enabled = self.active_animation_index.is_some(); - let mut value = self.active_animation_index.unwrap_or(0); + let has_animations = !self.available_animations.is_empty(); + let mut enabled = self.active_animation_index.is_some() && has_animations; + + let mut selected_index = self + .active_animation_index + .unwrap_or(0) + .min(self.available_animations.len().saturating_sub(1)); + + let selected_label = if has_animations { + self.available_animations + .get(selected_index) + .map(String::as_str) + .unwrap_or("Unnamed Animation") + } else { + "No Animations" + }; + + ComboBox::from_label("Animation") + .selected_text(selected_label) + .show_ui(ui, |ui| { + for (index, name) in self.available_animations.iter().enumerate() { + ui.selectable_value(&mut selected_index, index, name); + } + }); ui.horizontal(|ui| { - ui.label("Active Animation"); - if ui.checkbox(&mut enabled, "Enable").changed() { - self.active_animation_index = if enabled { Some(value) } else { None }; - } + ui.label("Active"); - let response = ui.add_enabled( - enabled, - egui::DragValue::new(&mut value).speed(1.0).range(0..=1_000_000), - ); - - if enabled && response.changed() { - self.active_animation_index = Some(value); + if ui.checkbox(&mut enabled, "Enable").changed() { + self.active_animation_index = if enabled && has_animations { + Some(selected_index) + } else { + None + }; } }); - ui.horizontal(|ui| { - ui.label("Playing"); - ui.checkbox(&mut self.is_playing, ""); - }); - - ui.horizontal(|ui| { - ui.label("Looping"); - ui.checkbox(&mut self.looping, ""); - }); - - ui.horizontal(|ui| { - ui.label("Speed"); - ui.add(egui::DragValue::new(&mut self.speed).speed(0.01).range(0.0..=10.0)); - }); - - ui.horizontal(|ui| { - ui.label("Time"); - ui.add(egui::DragValue::new(&mut self.time).speed(0.01).range(0.0..=1_000_000.0)); - if ui.button("Reset").clicked() { - self.time = 0.0; + if has_animations { + let settings = self + .animation_settings + .entry(selected_index) + .or_insert_with(|| AnimationSettings { + time: self.time, + speed: self.speed, + looping: self.looping, + is_playing: self.is_playing, + }); + + ui.horizontal(|ui| { + ui.label("Playing"); + ui.checkbox(&mut settings.is_playing, ""); + }); + + ui.horizontal(|ui| { + ui.label("Looping"); + ui.checkbox(&mut settings.looping, ""); + }); + + ui.horizontal(|ui| { + ui.label("Speed"); + ui.add(egui::DragValue::new(&mut settings.speed).speed(0.01).range(0.0..=10.0)); + }); + + ui.horizontal(|ui| { + ui.label("Start Time"); + ui.add(egui::DragValue::new(&mut settings.time).speed(0.01).range(0.0..=1_000_000.0)); + if ui.button("Reset").clicked() { + settings.time = 0.0; + } + }); + + if self.active_animation_index == Some(selected_index) { + self.time = settings.time; + self.speed = settings.speed; + self.looping = settings.looping; + self.is_playing = settings.is_playing; } - }); + } }); } } @@ -21,7 +21,7 @@ impl SerializedComponent for SerializedLight {} impl Component for Light { type SerializedForm = SerializedLight; - type RequiredComponentTypes = (Self, LightComponent); + type RequiredComponentTypes = (Self, LightComponent, Transform); fn descriptor() -> ComponentDescriptor { ComponentDescriptor { @@ -37,19 +37,31 @@ impl Component for Light { graphics: Arc<SharedGraphicsContext>, ) -> ComponentInitFuture<'a, Self> { Box::pin(async move { + let light_component = ser.light_component.clone(); let light = Light::new( graphics.clone(), - ser.light_component.clone(), + light_component.clone(), Some(ser.label.as_str()) ).await; + let transform = light_component.to_transform(); - Ok((light, ser.light_component.clone())) + Ok((light, light_component, transform)) }) } fn update_component(&mut self, world: &World, _physics: &mut crate::physics::PhysicsState, entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) { if let Ok(comp) = world.query_one::<&LightComponent>(entity).get() { - self.update(&graphics, comp); + let mut synced = comp.clone(); + if let Ok(entity_transform) = world.query_one::<&EntityTransform>(entity).get() { + let transform = entity_transform.sync(); + synced.position = transform.position; + synced.direction = (transform.rotation * DVec3::new(0.0, 0.0, -1.0)).normalize_or_zero(); + } else if let Ok(transform) = world.query_one::<&Transform>(entity).get() { + synced.position = transform.position; + synced.direction = (transform.rotation * DVec3::new(0.0, 0.0, -1.0)).normalize_or_zero(); + } + + self.update(&graphics, &synced); } } @@ -414,12 +414,15 @@ impl SceneConfig { entity_id: None, }; + let transform = comp.to_transform(); + world.spawn(( Label::from("Default Light"), comp, light, light_config, CustomProperties::default(), + transform, )); } @@ -1,9 +1,11 @@ use transform_gizmo_egui::{GizmoConfig, GizmoExt, GizmoOrientation}; use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, Transform}; +use dropbear_engine::lighting::LightComponent; use eucalyptus_core::camera::CameraComponent; use eucalyptus_core::utils::ViewportMode; use crate::editor::{EditorTabViewer, Signal, TABS_GLOBAL, UndoableAction}; +use glam::DVec3; impl<'a> EditorTabViewer<'a> { pub(crate) fn viewport_tab(&mut self, ui: &mut egui::Ui) { @@ -92,6 +94,7 @@ impl<'a> EditorTabViewer<'a> { && let Some(entity_id) = self.selected_entity { let mut handled = false; + let mut updated_light_transform: Option<Transform> = None; if let Ok(entity_transform) = self.world.query_one::<&mut EntityTransform>(*entity_id).get() { @@ -161,6 +164,8 @@ impl<'a> EditorTabViewer<'a> { local_transform.position = unrotated_delta / safe_world_scale; } } + + updated_light_transform = Some(entity_transform.sync()); } if was_focused && !cfg.is_focused { @@ -201,6 +206,7 @@ impl<'a> EditorTabViewer<'a> { transform.position = new_transform.translation.into(); transform.rotation = new_transform.rotation.into(); transform.scale = new_transform.scale.into(); + updated_light_transform = Some(*transform); } if was_focused && !cfg.is_focused { @@ -218,6 +224,14 @@ impl<'a> EditorTabViewer<'a> { } } } + + if let Some(updated_transform) = updated_light_transform { + if let Ok(mut light_component) = self.world.get::<&mut LightComponent>(*entity_id) { + let forward = DVec3::new(0.0, 0.0, -1.0); + light_component.position = updated_transform.position; + light_component.direction = (updated_transform.rotation * forward).normalize_or_zero(); + } + } } } } @@ -179,97 +179,17 @@ typedef struct NShapeCastStatusFfi { typedef NShapeCastStatusFfi NShapeCastStatus; -typedef struct NRange { - float start; - float end; -} NRange; +typedef void* AssetRegistryPtr; typedef struct IndexNative { uint32_t index; uint32_t generation; } IndexNative; -typedef struct NCollider { +typedef struct RigidBodyContext { IndexNative index; uint64_t entity_id; - uint32_t id; -} NCollider; - -typedef struct RayHit { - NCollider collider; - double distance; -} RayHit; - -typedef struct AxisLock { - bool x; - bool y; - bool z; -} AxisLock; - -typedef struct i32Array { - int32_t* values; - size_t length; - size_t capacity; -} i32Array; - -typedef struct f64ArrayArray { - double* values; - size_t length; - size_t capacity; -} f64ArrayArray; - -typedef struct NSkin { - const char* name; - i32Array joints; - f64ArrayArray inverse_bind_matrices; - const int32_t* skeleton_root; -} NSkin; - -typedef struct NSkinArray { - NSkin* values; - size_t length; - size_t capacity; -} NSkinArray; - -typedef void* AssetRegistryPtr; - -typedef struct NVector2 { - double x; - double y; -} NVector2; - -typedef void* InputStatePtr; - -typedef struct u64Array { - uint64_t* values; - size_t length; - size_t capacity; -} u64Array; - -typedef struct NAttenuation { - float constant; - float linear; - float quadratic; -} NAttenuation; - -typedef struct NColour { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -} NColour; - -typedef struct NTransform { - NVector3 position; - NQuaternion rotation; - NVector3 scale; -} NTransform; - -typedef struct Progress { - size_t current; - size_t total; - const char* message; -} Progress; +} RigidBodyContext; typedef struct IndexNativeArray { IndexNative* values; @@ -313,7 +233,11 @@ typedef struct NAnimationArray { size_t capacity; } NAnimationArray; -typedef void* WorldPtr; +typedef struct NCollider { + IndexNative index; + uint64_t entity_id; + uint32_t id; +} NCollider; typedef struct NShapeCastHit { NCollider collider; @@ -325,6 +249,16 @@ typedef struct NShapeCastHit { NShapeCastStatus status; } NShapeCastHit; +typedef struct u64Array { + uint64_t* values; + size_t length; + size_t capacity; +} u64Array; + +typedef struct ConnectedGamepadIds { + u64Array ids; +} ConnectedGamepadIds; + typedef struct NVector4 { double x; double y; @@ -332,6 +266,17 @@ typedef struct NVector4 { double w; } NVector4; +typedef struct NVector2 { + double x; + double y; +} NVector2; + +typedef struct i32Array { + int32_t* values; + size_t length; + size_t capacity; +} i32Array; + typedef struct NModelVertex { NVector3 position; NVector3 normal; @@ -386,27 +331,6 @@ typedef struct NMaterialArray { size_t capacity; } NMaterialArray; -typedef void* SceneLoaderPtr; - -typedef struct RigidBodyContext { - IndexNative index; - uint64_t entity_id; -} RigidBodyContext; - -typedef struct ConnectedGamepadIds { - u64Array ids; -} ConnectedGamepadIds; - -typedef struct NColliderArray { - NCollider* values; - size_t length; - size_t capacity; -} NColliderArray; - -typedef void* CommandBufferPtr; - -typedef void* GraphicsContextPtr; - typedef struct NNodeTransform { NVector3 translation; NQuaternion rotation; @@ -426,8 +350,84 @@ typedef struct NNodeArray { size_t capacity; } NNodeArray; +typedef void* SceneLoaderPtr; + +typedef void* CommandBufferPtr; + +typedef struct f64ArrayArray { + double* values; + size_t length; + size_t capacity; +} f64ArrayArray; + +typedef struct NSkin { + const char* name; + i32Array joints; + f64ArrayArray inverse_bind_matrices; + const int32_t* skeleton_root; +} NSkin; + +typedef struct NSkinArray { + NSkin* values; + size_t length; + size_t capacity; +} NSkinArray; + +typedef struct NAttenuation { + float constant; + float linear; + float quadratic; +} NAttenuation; + +typedef struct AxisLock { + bool x; + bool y; + bool z; +} AxisLock; + +typedef struct Progress { + size_t current; + size_t total; + const char* message; +} Progress; + +typedef void* WorldPtr; + +typedef struct NRange { + float start; + float end; +} NRange; + typedef void* PhysicsStatePtr; +typedef struct NTransform { + NVector3 position; + NQuaternion rotation; + NVector3 scale; +} NTransform; + +typedef void* GraphicsContextPtr; + +typedef struct NColour { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} NColour; + +typedef void* InputStatePtr; + +typedef struct NColliderArray { + NCollider* values; + size_t length; + size_t capacity; +} NColliderArray; + +typedef struct RayHit { + NCollider collider; + double distance; +} RayHit; + int32_t dropbear_gamepad_is_button_pressed(InputStatePtr input, uint64_t gamepad_id, int32_t button_ordinal, bool* out0); int32_t dropbear_gamepad_get_left_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0); int32_t dropbear_gamepad_get_right_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0);