kitgit

tirbofish/dropbear · commit

751a9c98a9d3229315f086a1ae4095298f2948fd

fix: light cube rendering

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-18 07:30

view full diff

diff --git a/crates/dropbear-engine/src/animation.rs b/crates/dropbear-engine/src/animation.rs
index 05e9851..6acbbe7 100644
--- a/crates/dropbear-engine/src/animation.rs
+++ b/crates/dropbear-engine/src/animation.rs
@@ -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];
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index 1fccf7d..e451684 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -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>),
diff --git a/crates/dropbear-engine/src/pipelines/light_cube.rs b/crates/dropbear-engine/src/pipelines/light_cube.rs
index 578669f..bf25e48 100644
--- a/crates/dropbear-engine/src/pipelines/light_cube.rs
+++ b/crates/dropbear-engine/src/pipelines/light_cube.rs
@@ -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(),
         }
     }
-}
\ No newline at end of file
+}
diff --git a/crates/eucalyptus-core/src/animation/mod.rs b/crates/eucalyptus-core/src/animation/mod.rs
index 9623fe0..6fa3b19 100644
--- a/crates/eucalyptus-core/src/animation/mod.rs
+++ b/crates/eucalyptus-core/src/animation/mod.rs
@@ -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;
                 }
-            });
+            }
         });
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/lighting.rs b/crates/eucalyptus-core/src/lighting.rs
index 47c6ca4..185256f 100644
--- a/crates/eucalyptus-core/src/lighting.rs
+++ b/crates/eucalyptus-core/src/lighting.rs
@@ -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);
         }
     }
 
diff --git a/crates/eucalyptus-core/src/scene.rs b/crates/eucalyptus-core/src/scene.rs
index 1caf018..53c6ff5 100644
--- a/crates/eucalyptus-core/src/scene.rs
+++ b/crates/eucalyptus-core/src/scene.rs
@@ -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,
             ));
         }
 
diff --git a/crates/eucalyptus-editor/src/editor/viewport.rs b/crates/eucalyptus-editor/src/editor/viewport.rs
index fb3478c..d8b9801 100644
--- a/crates/eucalyptus-editor/src/editor/viewport.rs
+++ b/crates/eucalyptus-editor/src/editor/viewport.rs
@@ -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();
+                }
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/include/dropbear.h b/include/dropbear.h
index 630fb92..267f432 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -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);