nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use super::{ComponentInspector, InspectorContext};
use crate::editor::undo::{begin_transform_change, commit_transform_change};
use crate::prelude::*;

pub struct TransformInspector {
    uniform_scaling: bool,
}

impl Default for TransformInspector {
    fn default() -> Self {
        Self {
            uniform_scaling: true,
        }
    }
}

impl ComponentInspector for TransformInspector {
    fn name(&self) -> &str {
        "Transform"
    }

    fn has_component(&self, world: &World, entity: Entity) -> bool {
        world.entity_has_local_transform(entity)
    }

    fn add_component(&self, world: &mut World, entity: Entity) {
        world.set_local_transform(entity, LocalTransform::default());
        world.set_global_transform(entity, GlobalTransform::default());
        mark_local_transform_dirty(world, entity);
    }

    fn remove_component(&self, world: &mut World, entity: Entity) {
        world.remove_local_transform(entity);
        world.remove_global_transform(entity);
    }

    fn ui(
        &mut self,
        world: &mut World,
        entity: Entity,
        ui: &mut egui::Ui,
        context: &mut InspectorContext,
    ) {
        if let Some(global_transform) = world.get_global_transform(entity) {
            let global_position = global_transform.translation();
            ui.label("Global Position (Read-only):");
            ui.horizontal(|ui| {
                ui.label(format!("X: {:.2}", global_position.x));
                ui.label(format!("Y: {:.2}", global_position.y));
                ui.label(format!("Z: {:.2}", global_position.z));
            });
            ui.separator();
        }

        if let Some(local_transform) = world.get_local_transform_mut(entity) {
            let initial_transform = *local_transform;
            let mut changed = false;
            let mut any_drag_started = false;
            let mut any_drag_released = false;

            ui.label("Local Position:");
            ui.horizontal(|ui| {
                ui.label("X:");
                let response =
                    ui.add(egui::DragValue::new(&mut local_transform.translation.x).speed(0.1));
                changed |= response.changed();
                any_drag_started |= response.drag_started();
                any_drag_released |= response.drag_stopped();

                ui.label("Y:");
                let response =
                    ui.add(egui::DragValue::new(&mut local_transform.translation.y).speed(0.1));
                changed |= response.changed();
                any_drag_started |= response.drag_started();
                any_drag_released |= response.drag_stopped();

                ui.label("Z:");
                let response =
                    ui.add(egui::DragValue::new(&mut local_transform.translation.z).speed(0.1));
                changed |= response.changed();
                any_drag_started |= response.drag_started();
                any_drag_released |= response.drag_stopped();
            });

            ui.label("Local Rotation:");
            ui.horizontal(|ui| {
                let (roll, pitch, yaw) = quat_to_euler(&local_transform.rotation);
                let mut roll_deg = roll.to_degrees();
                let mut pitch_deg = pitch.to_degrees();
                let mut yaw_deg = yaw.to_degrees();

                ui.label("X:");
                let response_x = ui.add(
                    egui::DragValue::new(&mut roll_deg)
                        .speed(1.0)
                        .suffix("\u{00b0}"),
                );
                any_drag_started |= response_x.drag_started();
                any_drag_released |= response_x.drag_stopped();

                ui.label("Y:");
                let response_y = ui.add(
                    egui::DragValue::new(&mut pitch_deg)
                        .speed(1.0)
                        .suffix("\u{00b0}"),
                );
                any_drag_started |= response_y.drag_started();
                any_drag_released |= response_y.drag_stopped();

                ui.label("Z:");
                let response_z = ui.add(
                    egui::DragValue::new(&mut yaw_deg)
                        .speed(1.0)
                        .suffix("\u{00b0}"),
                );
                any_drag_started |= response_z.drag_started();
                any_drag_released |= response_z.drag_stopped();

                if response_x.changed() || response_y.changed() || response_z.changed() {
                    local_transform.rotation = euler_to_quat(
                        roll_deg.to_radians(),
                        pitch_deg.to_radians(),
                        yaw_deg.to_radians(),
                    );
                    changed = true;
                }
            });

            ui.label("Local Scale:");
            ui.horizontal(|ui| {
                if self.uniform_scaling {
                    ui.label("XYZ:");
                    let mut uniform_scale = local_transform.scale.x;
                    let response = ui.add(egui::DragValue::new(&mut uniform_scale).speed(0.01));
                    if response.changed() {
                        local_transform.scale =
                            Vec3::new(uniform_scale, uniform_scale, uniform_scale);
                        changed = true;
                    }
                    any_drag_started |= response.drag_started();
                    any_drag_released |= response.drag_stopped();
                } else {
                    ui.label("X:");
                    let response =
                        ui.add(egui::DragValue::new(&mut local_transform.scale.x).speed(0.01));
                    changed |= response.changed();
                    any_drag_started |= response.drag_started();
                    any_drag_released |= response.drag_stopped();

                    ui.label("Y:");
                    let response =
                        ui.add(egui::DragValue::new(&mut local_transform.scale.y).speed(0.01));
                    changed |= response.changed();
                    any_drag_started |= response.drag_started();
                    any_drag_released |= response.drag_stopped();

                    ui.label("Z:");
                    let response =
                        ui.add(egui::DragValue::new(&mut local_transform.scale.z).speed(0.01));
                    changed |= response.changed();
                    any_drag_started |= response.drag_started();
                    any_drag_released |= response.drag_stopped();
                }
                ui.checkbox(&mut self.uniform_scaling, "Uniform");
            });

            if any_drag_started {
                begin_transform_change(context.transform_edit_pending, entity, initial_transform);
            }

            if any_drag_released {
                let final_transform = *local_transform;
                commit_transform_change(
                    context.transform_edit_pending,
                    context.undo_history,
                    entity,
                    final_transform,
                );
            }

            if changed {
                let _ = local_transform;
                mark_local_transform_dirty(world, entity);
            }
        }
    }
}

fn quat_to_euler(q: &Quat) -> (f32, f32, f32) {
    let sinr_cosp = 2.0 * (q.w * q.i + q.j * q.k);
    let cosr_cosp = 1.0 - 2.0 * (q.i * q.i + q.j * q.j);
    let roll = sinr_cosp.atan2(cosr_cosp);

    let sinp = 2.0 * (q.w * q.j - q.k * q.i);
    let pitch = if sinp.abs() >= 1.0 {
        std::f32::consts::FRAC_PI_2.copysign(sinp)
    } else {
        sinp.asin()
    };

    let siny_cosp = 2.0 * (q.w * q.k + q.i * q.j);
    let cosy_cosp = 1.0 - 2.0 * (q.j * q.j + q.k * q.k);
    let yaw = siny_cosp.atan2(cosy_cosp);

    (roll, pitch, yaw)
}

fn euler_to_quat(roll: f32, pitch: f32, yaw: f32) -> Quat {
    let cr = (roll * 0.5).cos();
    let sr = (roll * 0.5).sin();
    let cp = (pitch * 0.5).cos();
    let sp = (pitch * 0.5).sin();
    let cy = (yaw * 0.5).cos();
    let sy = (yaw * 0.5).sin();

    nalgebra_glm::quat(
        sr * cp * cy - cr * sp * sy,
        cr * sp * cy + sr * cp * sy,
        cr * cp * sy - sr * sp * cy,
        cr * cp * cy + sr * sp * sy,
    )
}