Skip to main content

mittens_engine/engine/ecs/component/
gizmo.rs

1use super::Component;
2use crate::engine::ecs::ComponentId;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum TransformGizmoAxis {
6    X,
7    Y,
8    Z,
9}
10
11impl TransformGizmoAxis {
12    pub fn unit_vec3(self) -> [f32; 3] {
13        match self {
14            TransformGizmoAxis::X => [1.0, 0.0, 0.0],
15            TransformGizmoAxis::Y => [0.0, 1.0, 0.0],
16            TransformGizmoAxis::Z => [0.0, 0.0, 1.0],
17        }
18    }
19}
20
21/// Handle marker: translate along an axis.
22///
23/// This component is intended to be an ancestor of the entire clickable handle subtree.
24#[derive(Debug, Clone, Copy)]
25pub struct TransformGizmoTranslateComponent {
26    pub axis: TransformGizmoAxis,
27}
28
29impl TransformGizmoTranslateComponent {
30    pub fn new(axis: TransformGizmoAxis) -> Self {
31        Self { axis }
32    }
33}
34
35impl Component for TransformGizmoTranslateComponent {
36    fn name(&self) -> &'static str {
37        "transform_gizmo_translate"
38    }
39
40    fn as_any(&self) -> &dyn std::any::Any {
41        self
42    }
43
44    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
45        self
46    }
47
48    fn to_mms_ast(
49        &self,
50        _world: &crate::engine::ecs::World,
51    ) -> crate::scripting::ast::ComponentExpression {
52        use crate::engine::ecs::component::ce_helpers::*;
53        ce_call("TransformGizmoTranslate", axis_ctor(self.axis), vec![])
54    }
55}
56
57fn axis_ctor(axis: TransformGizmoAxis) -> &'static str {
58    match axis {
59        TransformGizmoAxis::X => "x",
60        TransformGizmoAxis::Y => "y",
61        TransformGizmoAxis::Z => "z",
62    }
63}
64
65/// Handle marker: rotate around an axis.
66///
67/// This component is intended to be an ancestor of the entire clickable handle subtree.
68#[derive(Debug, Clone, Copy)]
69pub struct TransformGizmoRotateComponent {
70    pub axis: TransformGizmoAxis,
71}
72
73impl TransformGizmoRotateComponent {
74    pub fn new(axis: TransformGizmoAxis) -> Self {
75        Self { axis }
76    }
77}
78
79impl Component for TransformGizmoRotateComponent {
80    fn name(&self) -> &'static str {
81        "transform_gizmo_rotate"
82    }
83
84    fn as_any(&self) -> &dyn std::any::Any {
85        self
86    }
87
88    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
89        self
90    }
91
92    fn to_mms_ast(
93        &self,
94        _world: &crate::engine::ecs::World,
95    ) -> crate::scripting::ast::ComponentExpression {
96        use crate::engine::ecs::component::ce_helpers::*;
97        ce_call("TransformGizmoRotate", axis_ctor(self.axis), vec![])
98    }
99}
100
101/// Handle marker: scale along an axis.
102///
103/// This component is intended to be an ancestor of the entire clickable handle subtree.
104#[derive(Debug, Clone, Copy)]
105pub struct TransformGizmoScaleComponent {
106    pub axis: TransformGizmoAxis,
107}
108
109impl TransformGizmoScaleComponent {
110    pub fn new(axis: TransformGizmoAxis) -> Self {
111        Self { axis }
112    }
113}
114
115impl Component for TransformGizmoScaleComponent {
116    fn name(&self) -> &'static str {
117        "transform_gizmo_scale"
118    }
119
120    fn as_any(&self) -> &dyn std::any::Any {
121        self
122    }
123
124    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
125        self
126    }
127
128    fn to_mms_ast(
129        &self,
130        _world: &crate::engine::ecs::World,
131    ) -> crate::scripting::ast::ComponentExpression {
132        use crate::engine::ecs::component::ce_helpers::*;
133        ce_call("TransformGizmoScale", axis_ctor(self.axis), vec![])
134    }
135}
136
137/// A simple transform gizmo.
138///
139/// Attach this as a child of a TransformComponent you want to manipulate.
140/// On init, a 9-part visual subtree is spawned under the gizmo component.
141/// When a drag gesture is active on a gizmo renderable, TransformGizmoSystem applies the drag delta
142/// to the TransformComponent it is attached under.
143#[derive(Debug, Clone, Copy)]
144pub struct TransformGizmoComponent {
145    /// Visual scale applied to the gizmo's rendered/interactive subtree.
146    ///
147    /// This scales the gizmo visuals without affecting the target transform.
148    pub scale: f32,
149
150    /// Runtime: resolved target TransformComponent id.
151    ///
152    /// This is bound during `REGISTER_GIZMO` by walking up ancestry and finding the nearest
153    /// TransformComponent.
154    pub target_transform: Option<ComponentId>,
155
156    /// Runtime: raycaster currently driving this gizmo (single-pointer for now).
157    pub active_raycaster: Option<ComponentId>,
158
159    /// Runtime: accumulated slider angle (radians) since drag start.
160    pub active_drag_slider_last_angle: f32,
161
162    /// Runtime: drag-start hit point in world space for translation drags.
163    pub active_drag_start_hit_point_world: Option<[f32; 3]>,
164
165    /// Runtime: target local translation captured at drag start.
166    pub active_drag_start_target_translation: Option<[f32; 3]>,
167
168    /// Root TransformComponent id of the gizmo visual subtree (spawned on init).
169    pub visual_root: Option<ComponentId>,
170
171    /// Runtime: optional debug plane subtree root.
172    ///
173    /// When enabled, GizmoSystem spawns a thin quad/cube aligned to the drag plane captured at
174    /// DragStart to visualize the projection surface used by screen-space dragging.
175    pub debug_drag_plane_root: Option<ComponentId>,
176
177    component: Option<ComponentId>,
178}
179
180impl TransformGizmoComponent {
181    /// Create a gizmo.
182    ///
183    /// The target transform is resolved automatically from gizmo ancestry on init.
184    pub fn new() -> Self {
185        Self {
186            scale: 1.0,
187            target_transform: None,
188            active_raycaster: None,
189            active_drag_slider_last_angle: 0.0,
190            active_drag_start_hit_point_world: None,
191            active_drag_start_target_translation: None,
192            visual_root: None,
193            debug_drag_plane_root: None,
194            component: None,
195        }
196    }
197
198    pub fn with_scale(mut self, scale: f32) -> Self {
199        self.scale = scale;
200        self
201    }
202
203    /// Back-compat constructor name (gizmos are no longer mode-based).
204    pub fn translate() -> Self {
205        Self::new()
206    }
207
208    pub fn id(&self) -> Option<ComponentId> {
209        self.component
210    }
211}
212
213impl Component for TransformGizmoComponent {
214    fn set_id(&mut self, component: ComponentId) {
215        self.component = Some(component);
216    }
217
218    fn name(&self) -> &'static str {
219        "transform_gizmo"
220    }
221
222    fn as_any(&self) -> &dyn std::any::Any {
223        self
224    }
225
226    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
227        self
228    }
229
230    fn to_mms_ast(
231        &self,
232        _world: &crate::engine::ecs::World,
233    ) -> crate::scripting::ast::ComponentExpression {
234        use crate::engine::ecs::component::ce_helpers::*;
235        ce("TransformGizmo").with_call("scale", vec![num(self.scale as f64)])
236    }
237
238    fn init(&mut self, emit: &mut dyn crate::engine::ecs::SignalEmitter, component: ComponentId) {
239        emit.push_intent_now(
240            component,
241            crate::engine::ecs::IntentValue::RegisterTransformGizmo {
242                component_ids: vec![component],
243            },
244        );
245    }
246
247    fn cleanup(
248        &mut self,
249        emit: &mut dyn crate::engine::ecs::SignalEmitter,
250        _component: ComponentId,
251    ) {
252        if let Some(root) = self.visual_root.take() {
253            emit.push_intent_now(
254                root,
255                crate::engine::ecs::IntentValue::RemoveSubtree {
256                    component_ids: vec![root],
257                },
258            );
259        }
260
261        if let Some(root) = self.debug_drag_plane_root.take() {
262            emit.push_intent_now(
263                root,
264                crate::engine::ecs::IntentValue::RemoveSubtree {
265                    component_ids: vec![root],
266                },
267            );
268        }
269    }
270}