summaryrefslogtreecommitdiff
path: root/engine/src/ui
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-09-14 15:58:13 +0200
committerHampusM <hampus@hampusmat.com>2026-09-14 15:58:13 +0200
commit36c5e8407e332b9f72f9e116acfbc3a35c218245 (patch)
tree2c01136226e6af9e2d90661adf1add35ca5c481f /engine/src/ui
parenta55f4d3aa9db4eef4eaf329f921c60cba6f89a2c (diff)
feat(engine): add 3D transforming ui view
Diffstat (limited to 'engine/src/ui')
-rw-r--r--engine/src/ui/view.rs1
-rw-r--r--engine/src/ui/view/transform_3d.rs128
2 files changed, 129 insertions, 0 deletions
diff --git a/engine/src/ui/view.rs b/engine/src/ui/view.rs
index ed714d6..42601a3 100644
--- a/engine/src/ui/view.rs
+++ b/engine/src/ui/view.rs
@@ -1 +1,2 @@
+pub mod transform_3d;
pub mod world;
diff --git a/engine/src/ui/view/transform_3d.rs b/engine/src/ui/view/transform_3d.rs
new file mode 100644
index 0000000..a426b2a
--- /dev/null
+++ b/engine/src/ui/view/transform_3d.rs
@@ -0,0 +1,128 @@
+use dear_imguizmo::GuizmoExt;
+
+use crate::camera::{Active as ActiveCamera, Camera};
+use crate::data_types::dimens::Dimens3;
+use crate::ecs::pair::ChildOf;
+use crate::ecs::query::term::{Traverse, TraverseUp, With, Without};
+use crate::ecs::sole::Single;
+use crate::ecs::Query;
+use crate::projection::ClipVolume as ProjectionClipVolume;
+use crate::scene::{Active as ActiveScene, Scene};
+use crate::transform::Transform;
+use crate::ui::dear_imgui::{
+ Context as DearImguiContext,
+ TargetWindow as DearImguiTargetWindow,
+};
+use crate::vector::Vec3;
+use crate::windowing::window::{Closed as WindowClosed, Window};
+
+pub fn show(
+ subject_query: Query<(
+ &mut Transform,
+ Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>,
+ Without<ActiveCamera>,
+ )>,
+ camera_query: Query<(
+ &Camera,
+ &Transform,
+ Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>,
+ With<ActiveCamera>,
+ )>,
+ target_window_query: Query<(
+ &Window,
+ With<DearImguiTargetWindow>,
+ Without<WindowClosed>,
+ )>,
+ mut dear_imgui_context: Single<DearImguiContext>,
+) -> Result<(), crate::Error>
+{
+ let dear_imgui_context = dear_imgui_context.get_mut()?;
+
+ let Some(frame) = dear_imgui_context.frame() else {
+ return Ok(());
+ };
+
+ let Some((camera, camera_world_pos)) = camera_query.iter().next() else {
+ return Ok(());
+ };
+
+ let Some((target_window,)) = target_window_query.iter().next() else {
+ return Ok(());
+ };
+
+ let gizmo = frame.guizmo();
+
+ let [display_width, display_height] = frame.io().display_size();
+
+ gizmo.set_rect(0.0, 0.0, display_width, display_height);
+
+ // The widgets should appear under dear imgui windows
+ gizmo.set_drawlist_background();
+
+ let col_major_view = flatten_matrix(
+ camera
+ .to_view_matrix(camera_world_pos.position)
+ .to_column_major(),
+ );
+
+ let col_major_projection = flatten_matrix(
+ camera
+ .projection
+ .to_matrix_rh(target_window.inner_size, ProjectionClipVolume::NegOneToOne)
+ .to_column_major(),
+ );
+
+ for (subject_index, (mut subject_transform,)) in subject_query.iter().enumerate() {
+ let subject_index = match i32::try_from(subject_index) {
+ Ok(subject_index) => subject_index,
+ Err(err) => {
+ tracing::error!("Invalid subject index {subject_index}: {err}");
+ break;
+ }
+ };
+
+ // No rotation
+ let rotation = [0.0, 0.0, 0.0];
+
+ let mut col_major_model_mat = dear_imguizmo::recompose_matrix(
+ &subject_transform.position.into(),
+ &rotation,
+ &subject_transform.scale.into(),
+ );
+
+ let _id_token = gizmo.push_id(subject_index);
+
+ let used = gizmo
+ .manipulate_config(
+ &col_major_view,
+ &col_major_projection,
+ &mut col_major_model_mat,
+ )
+ .operation(
+ dear_imguizmo::Operation::TRANSLATE | dear_imguizmo::Operation::SCALE,
+ )
+ .mode(dear_imguizmo::Mode::World)
+ .build();
+
+ if used {
+ let (translation, _rotation, scale) =
+ dear_imguizmo::decompose_matrix(&col_major_model_mat);
+
+ subject_transform.position = Vec3::from(translation);
+ subject_transform.scale = Dimens3::from(scale);
+ }
+ }
+
+ Ok(())
+}
+
+fn flatten_matrix(mat: [[f32; 4]; 4]) -> [f32; 4 * 4]
+{
+ let flattened = mat.as_flattened();
+
+ let Some(flattened) = flattened.as_array::<{ 4 * 4 }>() else {
+ unreachable!();
+ };
+
+ flattened.clone()
+}