Skip to main content

polyscope_rs/
ui_sync.rs

1use crate::{
2    GizmoSpace, Mat4, Vec3, add_slice_plane, deselect_structure, remove_slice_plane,
3    reset_selected_transform, with_context, with_context_mut,
4};
5use polyscope_core::gizmo::Transform;
6
7/// Syncs `CameraSettings` from UI to the actual Camera.
8pub fn apply_camera_settings(
9    camera: &mut polyscope_render::Camera,
10    settings: &polyscope_ui::CameraSettings,
11) {
12    camera.navigation_style = settings.navigation_style.into();
13    camera.projection_mode = settings.projection_mode.into();
14    camera.set_up_direction(settings.up_direction.into());
15    camera.set_fov_degrees(settings.fov_degrees);
16    camera.set_near(settings.near);
17    camera.set_far(settings.far);
18    camera.set_move_speed(settings.move_speed);
19    camera.set_ortho_scale(settings.ortho_scale);
20}
21
22/// Creates `CameraSettings` from the current Camera state.
23#[must_use]
24pub fn camera_to_settings(camera: &polyscope_render::Camera) -> polyscope_ui::CameraSettings {
25    polyscope_ui::CameraSettings {
26        navigation_style: camera.navigation_style.into(),
27        projection_mode: camera.projection_mode.into(),
28        up_direction: camera.up_direction.into(),
29        fov_degrees: camera.fov_degrees(),
30        near: camera.near,
31        far: camera.far,
32        move_speed: camera.move_speed,
33        ortho_scale: camera.ortho_scale,
34    }
35}
36
37/// Gets scene extents from the global context.
38#[must_use]
39pub fn get_scene_extents() -> polyscope_ui::SceneExtents {
40    polyscope_core::state::with_context(|ctx| polyscope_ui::SceneExtents {
41        auto_compute: ctx.options.auto_compute_scene_extents,
42        length_scale: ctx.length_scale,
43        bbox_min: ctx.bounding_box.0.to_array(),
44        bbox_max: ctx.bounding_box.1.to_array(),
45    })
46}
47
48/// Sets auto-compute scene extents option.
49///
50/// When re-enabling auto-compute, immediately recomputes extents from
51/// all registered structures (matching C++ Polyscope behavior).
52pub fn set_auto_compute_extents(auto: bool) {
53    polyscope_core::state::with_context_mut(|ctx| {
54        ctx.options.auto_compute_scene_extents = auto;
55        if auto {
56            ctx.recompute_extents();
57        }
58    });
59}
60
61// ============================================================================
62// Slice Plane UI Sync Functions
63// ============================================================================
64
65/// Gets all slice planes as UI settings.
66#[must_use]
67pub fn get_slice_plane_settings() -> Vec<polyscope_ui::SlicePlaneSettings> {
68    with_context(|ctx| {
69        let selected = ctx.selected_slice_plane();
70        ctx.slice_planes
71            .values()
72            .map(|plane| polyscope_ui::SlicePlaneSettings {
73                name: plane.name().to_string(),
74                enabled: plane.is_enabled(),
75                origin: plane.origin().to_array(),
76                normal: plane.normal().to_array(),
77                draw_plane: plane.draw_plane(),
78                draw_widget: plane.draw_widget(),
79                color: plane.color().truncate().to_array(),
80                transparency: plane.transparency(),
81                plane_size: plane.plane_size(),
82                is_selected: selected == Some(plane.name()),
83            })
84            .collect()
85    })
86}
87
88/// Applies UI settings to a slice plane.
89pub fn apply_slice_plane_settings(settings: &polyscope_ui::SlicePlaneSettings) {
90    with_context_mut(|ctx| {
91        if let Some(plane) = ctx.get_slice_plane_mut(&settings.name) {
92            plane.set_enabled(settings.enabled);
93            plane.set_origin(Vec3::from_array(settings.origin));
94            plane.set_normal(Vec3::from_array(settings.normal));
95            plane.set_draw_plane(settings.draw_plane);
96            plane.set_draw_widget(settings.draw_widget);
97            plane.set_color(Vec3::from_array(settings.color));
98            plane.set_transparency(settings.transparency);
99            plane.set_plane_size(settings.plane_size);
100        }
101    });
102}
103
104/// Handles a slice plane UI action.
105/// Returns the new list of settings after the action.
106pub fn handle_slice_plane_action(
107    action: polyscope_ui::SlicePlanesAction,
108    current_settings: &mut Vec<polyscope_ui::SlicePlaneSettings>,
109) {
110    match action {
111        polyscope_ui::SlicePlanesAction::None => {}
112        polyscope_ui::SlicePlanesAction::Add(name) => {
113            add_slice_plane(&name);
114            // Get the actual settings from the created plane (it has scene-relative values)
115            let settings = with_context(|ctx| {
116                if let Some(plane) = ctx.get_slice_plane(&name) {
117                    polyscope_ui::SlicePlaneSettings {
118                        name: plane.name().to_string(),
119                        enabled: plane.is_enabled(),
120                        origin: plane.origin().to_array(),
121                        normal: plane.normal().to_array(),
122                        draw_plane: plane.draw_plane(),
123                        draw_widget: plane.draw_widget(),
124                        color: plane.color().truncate().to_array(),
125                        transparency: plane.transparency(),
126                        plane_size: plane.plane_size(),
127                        is_selected: false,
128                    }
129                } else {
130                    polyscope_ui::SlicePlaneSettings::with_name(&name)
131                }
132            });
133            current_settings.push(settings);
134        }
135        polyscope_ui::SlicePlanesAction::Remove(idx) => {
136            if idx < current_settings.len() {
137                let name = &current_settings[idx].name;
138                remove_slice_plane(name);
139                current_settings.remove(idx);
140            }
141        }
142        polyscope_ui::SlicePlanesAction::Modified(idx) => {
143            if idx < current_settings.len() {
144                apply_slice_plane_settings(&current_settings[idx]);
145            }
146        }
147    }
148}
149
150// ============================================================================
151// Slice Plane Gizmo Functions
152// ============================================================================
153
154/// Gets slice plane selection info for gizmo rendering.
155#[must_use]
156pub fn get_slice_plane_selection_info() -> polyscope_ui::SlicePlaneSelectionInfo {
157    with_context(|ctx| {
158        if let Some(name) = ctx.selected_slice_plane() {
159            if let Some(plane) = ctx.get_slice_plane(name) {
160                let transform = plane.to_transform();
161                let (_, rotation, _) = transform.to_scale_rotation_translation();
162                let euler = rotation.to_euler(glam::EulerRot::XYZ);
163
164                polyscope_ui::SlicePlaneSelectionInfo {
165                    has_selection: true,
166                    name: name.to_string(),
167                    origin: plane.origin().to_array(),
168                    rotation_degrees: [
169                        euler.0.to_degrees(),
170                        euler.1.to_degrees(),
171                        euler.2.to_degrees(),
172                    ],
173                }
174            } else {
175                polyscope_ui::SlicePlaneSelectionInfo::default()
176            }
177        } else {
178            polyscope_ui::SlicePlaneSelectionInfo::default()
179        }
180    })
181}
182
183/// Selects a slice plane for gizmo manipulation.
184pub fn select_slice_plane_for_gizmo(name: &str) {
185    with_context_mut(|ctx| {
186        ctx.select_slice_plane(name);
187    });
188}
189
190/// Deselects the current slice plane from gizmo.
191pub fn deselect_slice_plane_gizmo() {
192    with_context_mut(|ctx| {
193        ctx.deselect_slice_plane();
194    });
195}
196
197/// Applies gizmo transform to the selected slice plane.
198pub fn apply_slice_plane_gizmo_transform(origin: [f32; 3], rotation_degrees: [f32; 3]) {
199    with_context_mut(|ctx| {
200        if let Some(name) = ctx.selected_slice_plane.clone() {
201            if let Some(plane) = ctx.get_slice_plane_mut(&name) {
202                // Reconstruct transform from origin + rotation
203                let rotation = glam::Quat::from_euler(
204                    glam::EulerRot::XYZ,
205                    rotation_degrees[0].to_radians(),
206                    rotation_degrees[1].to_radians(),
207                    rotation_degrees[2].to_radians(),
208                );
209                let transform =
210                    glam::Mat4::from_rotation_translation(rotation, Vec3::from_array(origin));
211                plane.set_from_transform(transform);
212            }
213        }
214    });
215}
216
217// ============================================================================
218// Group UI Sync Functions
219// ============================================================================
220
221/// Gets all groups as UI settings.
222#[must_use]
223pub fn get_group_settings() -> Vec<polyscope_ui::GroupSettings> {
224    with_context(|ctx| {
225        ctx.groups
226            .values()
227            .map(|group| polyscope_ui::GroupSettings {
228                name: group.name().to_string(),
229                enabled: group.is_enabled(),
230                show_child_details: group.show_child_details(),
231                parent_group: group.parent_group().map(std::string::ToString::to_string),
232                child_structures: group
233                    .child_structures()
234                    .map(|(t, n)| (t.to_string(), n.to_string()))
235                    .collect(),
236                child_groups: group
237                    .child_groups()
238                    .map(std::string::ToString::to_string)
239                    .collect(),
240            })
241            .collect()
242    })
243}
244
245/// Applies UI settings to a group.
246pub fn apply_group_settings(settings: &polyscope_ui::GroupSettings) {
247    with_context_mut(|ctx| {
248        if let Some(group) = ctx.get_group_mut(&settings.name) {
249            group.set_enabled(settings.enabled);
250            group.set_show_child_details(settings.show_child_details);
251        }
252    });
253}
254
255/// Handles a group UI action.
256pub fn handle_group_action(
257    action: polyscope_ui::GroupsAction,
258    current_settings: &mut [polyscope_ui::GroupSettings],
259) {
260    match action {
261        polyscope_ui::GroupsAction::None => {}
262        polyscope_ui::GroupsAction::SyncEnabled(indices) => {
263            for idx in indices {
264                if idx < current_settings.len() {
265                    apply_group_settings(&current_settings[idx]);
266                }
267            }
268        }
269    }
270}
271
272// ============================================================================
273// Gizmo UI Sync Functions
274// ============================================================================
275
276/// Gets gizmo settings for UI.
277#[must_use]
278pub fn get_gizmo_settings() -> polyscope_ui::GizmoSettings {
279    with_context(|ctx| {
280        let gizmo = ctx.gizmo();
281        polyscope_ui::GizmoSettings {
282            local_space: matches!(gizmo.space, GizmoSpace::Local),
283            visible: gizmo.visible,
284            snap_translate: gizmo.snap_translate,
285            snap_rotate: gizmo.snap_rotate,
286            snap_scale: gizmo.snap_scale,
287        }
288    })
289}
290
291/// Applies gizmo settings from UI.
292pub fn apply_gizmo_settings(settings: &polyscope_ui::GizmoSettings) {
293    with_context_mut(|ctx| {
294        let gizmo = ctx.gizmo_mut();
295        gizmo.space = if settings.local_space {
296            GizmoSpace::Local
297        } else {
298            GizmoSpace::World
299        };
300        gizmo.visible = settings.visible;
301        gizmo.snap_translate = settings.snap_translate;
302        gizmo.snap_rotate = settings.snap_rotate;
303        gizmo.snap_scale = settings.snap_scale;
304    });
305}
306
307/// Gets selection info for UI.
308#[must_use]
309pub fn get_selection_info() -> polyscope_ui::SelectionInfo {
310    with_context(|ctx| {
311        if let Some((type_name, name)) = ctx.selected_structure() {
312            // Get transform and bounding box from selected structure
313            let (transform, bbox) = ctx
314                .registry
315                .get(type_name, name)
316                .map_or((Mat4::IDENTITY, None), |s| {
317                    (s.transform(), s.bounding_box())
318                });
319
320            let t = Transform::from_matrix(transform);
321            let euler = t.euler_angles_degrees();
322
323            // Compute centroid from bounding box (world space)
324            let centroid = bbox.map_or(t.translation, |(min, max)| (min + max) * 0.5);
325
326            polyscope_ui::SelectionInfo {
327                has_selection: true,
328                type_name: type_name.to_string(),
329                name: name.to_string(),
330                translation: t.translation.to_array(),
331                rotation_degrees: euler.to_array(),
332                scale: t.scale.to_array(),
333                centroid: centroid.to_array(),
334            }
335        } else {
336            polyscope_ui::SelectionInfo::default()
337        }
338    })
339}
340
341/// Applies transform from selection info to the selected structure.
342pub fn apply_selection_transform(selection: &polyscope_ui::SelectionInfo) {
343    if !selection.has_selection {
344        return;
345    }
346
347    let translation = Vec3::from_array(selection.translation);
348    let rotation = glam::Quat::from_euler(
349        glam::EulerRot::XYZ,
350        selection.rotation_degrees[0].to_radians(),
351        selection.rotation_degrees[1].to_radians(),
352        selection.rotation_degrees[2].to_radians(),
353    );
354    let scale = Vec3::from_array(selection.scale);
355
356    let transform = Mat4::from_scale_rotation_translation(scale, rotation, translation);
357
358    with_context_mut(|ctx| {
359        if let Some((type_name, name)) = ctx.selected_structure.clone() {
360            if let Some(structure) = ctx.registry.get_mut(&type_name, &name) {
361                structure.set_transform(transform);
362            }
363        }
364    });
365}
366
367/// Handles a gizmo UI action.
368pub fn handle_gizmo_action(
369    action: polyscope_ui::GizmoAction,
370    settings: &polyscope_ui::GizmoSettings,
371    selection: &polyscope_ui::SelectionInfo,
372) {
373    match action {
374        polyscope_ui::GizmoAction::None => {}
375        polyscope_ui::GizmoAction::SettingsChanged => {
376            apply_gizmo_settings(settings);
377        }
378        polyscope_ui::GizmoAction::TransformChanged => {
379            apply_selection_transform(selection);
380        }
381        polyscope_ui::GizmoAction::Deselect => {
382            deselect_structure();
383        }
384        polyscope_ui::GizmoAction::ResetTransform => {
385            reset_selected_transform();
386        }
387    }
388}