use std::collections::HashMap;
use crate::interaction::gizmo::{self, Gizmo, GizmoAxis, GizmoMode};
use crate::interaction::manipulation::GizmoInfo;
use crate::interaction::manipulation::{
ManipulationContext, ManipulationController, ManipResult, TransformDelta,
};
use crate::interaction::selection::{NodeId, Selection};
use crate::runtime::context::RuntimeFrameContext;
use crate::runtime::output::{RuntimeOutput, TransformWriteback};
use crate::scene::scene::Scene;
pub struct ManipulationSystem {
controller: ManipulationController,
transforms_snapshot: HashMap<NodeId, glam::Mat4>,
pub(crate) gizmo: Gizmo,
pub(crate) gizmo_center: Option<glam::Vec3>,
pub(crate) gizmo_scale: f32,
}
impl Default for ManipulationSystem {
fn default() -> Self {
Self::new()
}
}
impl ManipulationSystem {
pub fn new() -> Self {
Self {
controller: ManipulationController::new(),
transforms_snapshot: HashMap::new(),
gizmo: Gizmo::default(),
gizmo_center: None,
gizmo_scale: 1.0,
}
}
pub fn is_active(&self) -> bool {
self.controller.is_active()
}
pub fn gizmo_center(&self) -> Option<glam::Vec3> {
self.gizmo_center
}
pub fn gizmo_scale(&self) -> f32 {
self.gizmo_scale
}
pub fn gizmo_mode(&self) -> GizmoMode {
self.gizmo.mode
}
pub fn gizmo_hovered(&self) -> GizmoAxis {
self.gizmo.hovered_axis
}
pub fn gizmo_info(&self) -> Option<GizmoInfo> {
self.gizmo_center.map(|center| GizmoInfo {
center,
scale: self.gizmo_scale,
orientation: glam::Quat::IDENTITY,
mode: self.gizmo.mode,
})
}
pub(crate) fn step(
&mut self,
frame: &RuntimeFrameContext,
scene: &Scene,
selection: &Selection,
writeback: &mut TransformWriteback,
_output: &mut RuntimeOutput,
) {
self.gizmo_center = gizmo::gizmo_center_from_selection(selection, |id| {
scene.node(id).map(|n| {
let t = n.world_transform();
glam::Vec3::new(t.w_axis.x, t.w_axis.y, t.w_axis.z)
})
});
if let Some(center) = self.gizmo_center {
self.gizmo_scale = gizmo::compute_gizmo_scale(
center,
frame.camera.eye_position(),
frame.camera.fov_y,
frame.viewport_size.y,
);
}
if !self.controller.is_active() {
if let (Some(center), Some(cursor)) = (self.gizmo_center, frame.cursor_viewport) {
let w = frame.viewport_size.x.max(1.0);
let h = frame.viewport_size.y.max(1.0);
let view_proj = frame.camera.proj_matrix() * frame.camera.view_matrix();
let inv_vp = view_proj.inverse();
let ndc_x = (cursor.x / w) * 2.0 - 1.0;
let ndc_y = 1.0 - (cursor.y / h) * 2.0;
let far = inv_vp.project_point3(glam::Vec3::new(ndc_x, ndc_y, 1.0));
let ray_origin = frame.camera.eye_position();
let ray_dir = (far - ray_origin).normalize_or_zero();
self.gizmo.hovered_axis = self.gizmo.hit_test_oriented(
ray_origin,
ray_dir,
center,
self.gizmo_scale,
glam::Quat::IDENTITY,
);
} else {
self.gizmo.hovered_axis = GizmoAxis::None;
}
}
let gizmo_info = self.gizmo_center.map(|center| GizmoInfo {
center,
scale: self.gizmo_scale,
orientation: glam::Quat::IDENTITY,
mode: self.gizmo.mode,
});
let manip_ctx = ManipulationContext {
camera: frame.camera.clone(),
viewport_size: frame.viewport_size,
cursor_viewport: frame.cursor_viewport,
pointer_delta: frame.pointer_delta,
selection_center: self.gizmo_center,
gizmo: gizmo_info,
drag_started: frame.drag_started,
dragging: frame.dragging,
clicked: frame.clicked,
};
let result = self.controller.update(&frame.input, manip_ctx);
match result {
ManipResult::Update(delta) => {
self.apply_delta(&delta, scene, selection, writeback);
}
ManipResult::Commit => {
self.save_snapshots(scene, selection);
}
ManipResult::Cancel | ManipResult::ConstraintChanged => {
self.restore_snapshots(writeback);
}
ManipResult::None => {
if !self.controller.is_active() {
self.save_snapshots(scene, selection);
}
}
}
}
fn save_snapshots(&mut self, scene: &Scene, selection: &Selection) {
self.transforms_snapshot.clear();
for &id in selection.iter() {
if let Some(node) = scene.node(id) {
self.transforms_snapshot.insert(id, node.local_transform());
}
}
}
fn restore_snapshots(&self, writeback: &mut TransformWriteback) {
for (&id, &snap) in &self.transforms_snapshot {
writeback.set(id, glam::Affine3A::from_mat4(snap));
}
}
fn apply_delta(
&self,
delta: &TransformDelta,
scene: &Scene,
selection: &Selection,
writeback: &mut TransformWriteback,
) {
let Some(center) = self.gizmo_center else {
return;
};
let has_pos_override = delta.position_override.iter().any(|v| v.is_some());
let has_scale_override = delta.scale_override.iter().any(|v| v.is_some());
let rot_mat = glam::Mat4::from_quat(delta.rotation);
let scale_mat = glam::Mat4::from_scale(delta.scale);
let translate_mat = glam::Mat4::from_translation(delta.translation);
let to_pivot = glam::Mat4::from_translation(-center);
let from_pivot = glam::Mat4::from_translation(center);
for &id in selection.iter() {
let base = if has_pos_override || has_scale_override {
match self.transforms_snapshot.get(&id) {
Some(&snap) => snap,
None => continue,
}
} else {
match scene.node(id) {
Some(n) => n.local_transform(),
None => continue,
}
};
let mut new_t = translate_mat * from_pivot * rot_mat * scale_mat * to_pivot * base;
for (i, &ov) in delta.position_override.iter().enumerate() {
if let Some(v) = ov {
new_t.col_mut(3)[i] = v;
}
}
writeback.set(id, glam::Affine3A::from_mat4(new_t));
}
}
}