viewport_lib/runtime/systems/
manipulation.rs1use std::collections::HashMap;
4
5use crate::interaction::gizmo::{self, Gizmo, GizmoAxis, GizmoMode};
6use crate::interaction::manipulation::GizmoInfo;
7use crate::interaction::manipulation::{
8 ManipulationContext, ManipulationController, ManipResult, TransformDelta,
9};
10use crate::interaction::selection::{NodeId, Selection};
11use crate::runtime::context::RuntimeFrameContext;
12use crate::runtime::output::{RuntimeOutput, TransformWriteback};
13use crate::scene::scene::Scene;
14
15pub struct ManipulationSystem {
26 controller: ManipulationController,
27 transforms_snapshot: HashMap<NodeId, glam::Mat4>,
28 pub(crate) gizmo: Gizmo,
29 pub(crate) gizmo_center: Option<glam::Vec3>,
30 pub(crate) gizmo_scale: f32,
31}
32
33impl Default for ManipulationSystem {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl ManipulationSystem {
40 pub fn new() -> Self {
42 Self {
43 controller: ManipulationController::new(),
44 transforms_snapshot: HashMap::new(),
45 gizmo: Gizmo::default(),
46 gizmo_center: None,
47 gizmo_scale: 1.0,
48 }
49 }
50
51 pub fn is_active(&self) -> bool {
53 self.controller.is_active()
54 }
55
56 pub fn gizmo_center(&self) -> Option<glam::Vec3> {
58 self.gizmo_center
59 }
60
61 pub fn gizmo_scale(&self) -> f32 {
63 self.gizmo_scale
64 }
65
66 pub fn gizmo_mode(&self) -> GizmoMode {
68 self.gizmo.mode
69 }
70
71 pub fn gizmo_hovered(&self) -> GizmoAxis {
73 self.gizmo.hovered_axis
74 }
75
76 pub fn gizmo_info(&self) -> Option<GizmoInfo> {
78 self.gizmo_center.map(|center| GizmoInfo {
79 center,
80 scale: self.gizmo_scale,
81 orientation: glam::Quat::IDENTITY,
82 mode: self.gizmo.mode,
83 })
84 }
85
86 pub(crate) fn step(
87 &mut self,
88 frame: &RuntimeFrameContext,
89 scene: &Scene,
90 selection: &Selection,
91 writeback: &mut TransformWriteback,
92 _output: &mut RuntimeOutput,
93 ) {
94 self.gizmo_center = gizmo::gizmo_center_from_selection(selection, |id| {
96 scene.node(id).map(|n| {
97 let t = n.world_transform();
98 glam::Vec3::new(t.w_axis.x, t.w_axis.y, t.w_axis.z)
99 })
100 });
101
102 if let Some(center) = self.gizmo_center {
104 self.gizmo_scale = gizmo::compute_gizmo_scale(
105 center,
106 frame.camera.eye_position(),
107 frame.camera.fov_y,
108 frame.viewport_size.y,
109 );
110 }
111
112 if !self.controller.is_active() {
114 if let (Some(center), Some(cursor)) = (self.gizmo_center, frame.cursor_viewport) {
115 let w = frame.viewport_size.x.max(1.0);
116 let h = frame.viewport_size.y.max(1.0);
117 let view_proj = frame.camera.proj_matrix() * frame.camera.view_matrix();
118 let inv_vp = view_proj.inverse();
119 let ndc_x = (cursor.x / w) * 2.0 - 1.0;
120 let ndc_y = 1.0 - (cursor.y / h) * 2.0;
121 let far = inv_vp.project_point3(glam::Vec3::new(ndc_x, ndc_y, 1.0));
122 let ray_origin = frame.camera.eye_position();
123 let ray_dir = (far - ray_origin).normalize_or_zero();
124 self.gizmo.hovered_axis = self.gizmo.hit_test_oriented(
125 ray_origin,
126 ray_dir,
127 center,
128 self.gizmo_scale,
129 glam::Quat::IDENTITY,
130 );
131 } else {
132 self.gizmo.hovered_axis = GizmoAxis::None;
133 }
134 }
135
136 let gizmo_info = self.gizmo_center.map(|center| GizmoInfo {
138 center,
139 scale: self.gizmo_scale,
140 orientation: glam::Quat::IDENTITY,
141 mode: self.gizmo.mode,
142 });
143 let manip_ctx = ManipulationContext {
144 camera: frame.camera.clone(),
145 viewport_size: frame.viewport_size,
146 cursor_viewport: frame.cursor_viewport,
147 pointer_delta: frame.pointer_delta,
148 selection_center: self.gizmo_center,
149 gizmo: gizmo_info,
150 drag_started: frame.drag_started,
151 dragging: frame.dragging,
152 clicked: frame.clicked,
153 };
154
155 let result = self.controller.update(&frame.input, manip_ctx);
156
157 match result {
158 ManipResult::Update(delta) => {
159 self.apply_delta(&delta, scene, selection, writeback);
160 }
161 ManipResult::Commit => {
162 self.save_snapshots(scene, selection);
163 }
164 ManipResult::Cancel | ManipResult::ConstraintChanged => {
165 self.restore_snapshots(writeback);
166 }
167 ManipResult::None => {
168 if !self.controller.is_active() {
169 self.save_snapshots(scene, selection);
170 }
171 }
172 }
173 }
174
175 fn save_snapshots(&mut self, scene: &Scene, selection: &Selection) {
176 self.transforms_snapshot.clear();
177 for &id in selection.iter() {
178 if let Some(node) = scene.node(id) {
179 self.transforms_snapshot.insert(id, node.local_transform());
180 }
181 }
182 }
183
184 fn restore_snapshots(&self, writeback: &mut TransformWriteback) {
185 for (&id, &snap) in &self.transforms_snapshot {
186 writeback.set(id, glam::Affine3A::from_mat4(snap));
187 }
188 }
189
190 fn apply_delta(
191 &self,
192 delta: &TransformDelta,
193 scene: &Scene,
194 selection: &Selection,
195 writeback: &mut TransformWriteback,
196 ) {
197 let Some(center) = self.gizmo_center else {
198 return;
199 };
200
201 let has_pos_override = delta.position_override.iter().any(|v| v.is_some());
202 let has_scale_override = delta.scale_override.iter().any(|v| v.is_some());
203
204 let rot_mat = glam::Mat4::from_quat(delta.rotation);
205 let scale_mat = glam::Mat4::from_scale(delta.scale);
206 let translate_mat = glam::Mat4::from_translation(delta.translation);
207 let to_pivot = glam::Mat4::from_translation(-center);
208 let from_pivot = glam::Mat4::from_translation(center);
209
210 for &id in selection.iter() {
211 let base = if has_pos_override || has_scale_override {
212 match self.transforms_snapshot.get(&id) {
213 Some(&snap) => snap,
214 None => continue,
215 }
216 } else {
217 match scene.node(id) {
218 Some(n) => n.local_transform(),
219 None => continue,
220 }
221 };
222
223 let mut new_t = translate_mat * from_pivot * rot_mat * scale_mat * to_pivot * base;
224
225 for (i, &ov) in delta.position_override.iter().enumerate() {
227 if let Some(v) = ov {
228 new_t.col_mut(3)[i] = v;
229 }
230 }
231
232 writeback.set(id, glam::Affine3A::from_mat4(new_t));
233 }
234 }
235}