viewport_lib/runtime/systems/
manipulation.rs1use std::collections::HashMap;
4
5use crate::interaction::manipulation::GizmoInfo;
6use crate::interaction::manipulation::gizmo::{self, Gizmo, GizmoAxis, GizmoMode};
7use crate::interaction::manipulation::{
8 ManipResult, ManipulationContext, ManipulationController, TransformDelta,
9};
10use crate::interaction::select::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(scene, 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.world_transform());
185 }
186 }
187 }
188
189 fn restore_snapshots(&self, scene: &Scene, writeback: &mut TransformWriteback) {
190 for (&id, &world_snap) in &self.transforms_snapshot {
191 let parent_world = parent_world_of(scene, id);
192 let local = parent_world.inverse() * world_snap;
193 writeback.set(id, glam::Affine3A::from_mat4(local));
194 }
195 }
196
197 fn apply_delta(
198 &self,
199 delta: &TransformDelta,
200 scene: &Scene,
201 selection: &Selection,
202 writeback: &mut TransformWriteback,
203 ) {
204 let Some(center) = self.gizmo_center else {
205 return;
206 };
207
208 let has_pos_override = delta.position_override.iter().any(|v| v.is_some());
209 let has_scale_override = delta.scale_override.iter().any(|v| v.is_some());
210
211 let rot_mat = glam::Mat4::from_quat(delta.rotation);
212 let scale_mat = glam::Mat4::from_scale(delta.scale);
213 let translate_mat = glam::Mat4::from_translation(delta.translation);
214 let to_pivot = glam::Mat4::from_translation(-center);
215 let from_pivot = glam::Mat4::from_translation(center);
216
217 for &id in selection.iter() {
218 let world_base = if has_pos_override || has_scale_override {
223 match self.transforms_snapshot.get(&id) {
224 Some(&snap) => snap,
225 None => continue,
226 }
227 } else {
228 match scene.node(id) {
229 Some(n) => n.world_transform(),
230 None => continue,
231 }
232 };
233
234 let mut new_world =
235 translate_mat * from_pivot * rot_mat * scale_mat * to_pivot * world_base;
236
237 for (i, &ov) in delta.position_override.iter().enumerate() {
239 if let Some(v) = ov {
240 new_world.col_mut(3)[i] = v;
241 }
242 }
243
244 let parent_world = parent_world_of(scene, id);
251 let new_local = parent_world.inverse() * new_world;
252
253 writeback.set(id, glam::Affine3A::from_mat4(new_local));
254 }
255 }
256}
257
258fn parent_world_of(scene: &Scene, id: NodeId) -> glam::Mat4 {
262 let Some(parent_id) = scene.node(id).and_then(|n| n.parent()) else {
263 return glam::Mat4::IDENTITY;
264 };
265 scene
266 .node(parent_id)
267 .map(|n| n.world_transform())
268 .unwrap_or(glam::Mat4::IDENTITY)
269}