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,
)
}