diff --git a/README.md b/README.md index d874cc0..79d3ef2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ with your favorite graphics APIs. ## Other -The gizmo exposes matrices and vectors as [mint](https://github.com/kvark/mint) types, which means it is easy to use with matrix types from various crates +The gizmo exposes mathematical types as [mint](https://github.com/kvark/mint) types, which means it is easy to use with types from various crates such as [nalgebra](https://github.com/dimforge/nalgebra), [glam](https://github.com/bitshifter/glam-rs) and [cgmath](https://github.com/rustgd/cgmath). You may need to enable a `mint` feature, depending on the math library. diff --git a/crates/transform-gizmo-bevy/src/lib.rs b/crates/transform-gizmo-bevy/src/lib.rs index 25c9eff..47bbe43 100644 --- a/crates/transform-gizmo-bevy/src/lib.rs +++ b/crates/transform-gizmo-bevy/src/lib.rs @@ -31,6 +31,7 @@ use bevy::prelude::*; use bevy::utils::{HashMap, Uuid}; use bevy::window::PrimaryWindow; +use bevy_math::{DQuat, DVec3}; use render::{DrawDataHandles, TransformGizmoRenderPlugin}; use transform_gizmo::config::{DEFAULT_SNAP_ANGLE, DEFAULT_SNAP_DISTANCE, DEFAULT_SNAP_SCALE}; @@ -221,7 +222,11 @@ fn update_gizmos( let gizmo_result = gizmo.update( gizmo_interaction, - &[target_transform.compute_matrix().as_dmat4().into()], + &[transform_gizmo::math::Transform { + translation: target_transform.translation.as_dvec3().into(), + rotation: target_transform.rotation.as_dquat().into(), + scale: target_transform.scale.as_dvec3().into(), + }], ); let is_focused = gizmo.is_focused(); @@ -235,8 +240,9 @@ fn update_gizmos( continue; }; - *target_transform = - Transform::from_matrix(bevy::math::DMat4::from(*result_transform).as_mat4()); + target_transform.translation = DVec3::from(result_transform.translation).as_vec3(); + target_transform.rotation = DQuat::from(result_transform.rotation).as_quat(); + target_transform.scale = DVec3::from(result_transform.scale).as_vec3(); } gizmo_target.latest_result = gizmo_result.map(|(result, _)| result); @@ -250,7 +256,11 @@ fn update_gizmos( gizmo_interaction, target_transforms .iter() - .map(|transform| transform.compute_matrix().as_dmat4().into()) + .map(|transform| transform_gizmo::math::Transform { + translation: transform.translation.as_dvec3().into(), + rotation: transform.rotation.as_dquat().into(), + scale: transform.scale.as_dvec3().into(), + }) .collect::>() .as_slice(), ); @@ -267,8 +277,9 @@ fn update_gizmos( continue; }; - *target_transform = - Transform::from_matrix(bevy::math::DMat4::from(*result_transform).as_mat4()); + target_transform.translation = DVec3::from(result_transform.translation).as_vec3(); + target_transform.rotation = DQuat::from(result_transform.rotation).as_quat(); + target_transform.scale = DVec3::from(result_transform.scale).as_vec3(); } gizmo_target.latest_result = gizmo_result.as_ref().map(|(result, _)| *result); diff --git a/crates/transform-gizmo-egui/src/lib.rs b/crates/transform-gizmo-egui/src/lib.rs index 575e651..a80a626 100644 --- a/crates/transform-gizmo-egui/src/lib.rs +++ b/crates/transform-gizmo-egui/src/lib.rs @@ -1,8 +1,7 @@ //! Provides a 3D transformation gizmo for the Egui library. //! -//! transform-gizmo-egui provides a feature-rich and configurable 3D transformation -//! gizmo that can be used to manipulate 4x4 transformation matrices (position, rotation, scale) -//! visually. +//! transform-gizmo-egui provides a feature-rich and configurable gizmo +//! that can be used for 3d transformations (translation, rotation, scale). //! //! # Usage //! @@ -14,7 +13,7 @@ //! let gizmo = Gizmo::default(); //! ``` //! -//! When drawing the gui, update the gizmo configuration. +//! Update the gizmo configuration as needed, for example, when the camera moves. //! //! ```ignore //! gizmo.update_config(GizmoConfig { @@ -26,20 +25,28 @@ //! }); //! ``` //! -//! Finally, interact with the gizmo. The function takes a slice of matrices as an +//! Finally, interact with the gizmo. The function takes a slice of transforms as an //! input. The result is [`Some`] if the gizmo was successfully interacted with this frame. -//! In the result you can find the modified matrices, in the same order as was given to the function +//! In the result you can find the modified transforms, in the same order as was given to the function //! as arguments. //! //! ```ignore -//! if let Some(result) = gizmo.interact(ui, &[model_matrix.into()]) { -//! model_matrix = result.targets.first().copied().unwrap().into(); +//! let mut transform = Transform::from_scale_rotation_translation(scale, rotation, translation); +//! +//! if let Some((result, new_transforms)) = gizmo.interact(ui, &[transform]) { +//! for (new_transform, transform) in +//! new_transforms.iter().zip(std::iter::once(&mut transform)) +//! { +//! // Apply the modified transforms +//! *transform = *new_transform; +//! } //! } //! ``` //! //! use egui::{epaint::Vertex, Mesh, PointerButton, Pos2, Rgba, Ui}; +use transform_gizmo::math::Transform; pub use transform_gizmo::*; pub mod prelude; @@ -47,19 +54,16 @@ pub trait GizmoExt { /// Interact with the gizmo and draw it to Ui. /// /// Returns result of the gizmo interaction. - fn interact( - &mut self, - ui: &Ui, - targets: &[mint::RowMatrix4], - ) -> Option<(GizmoResult, Vec>)>; + fn interact(&mut self, ui: &Ui, targets: &[Transform]) + -> Option<(GizmoResult, Vec)>; } impl GizmoExt for Gizmo { fn interact( &mut self, ui: &Ui, - targets: &[mint::RowMatrix4], - ) -> Option<(GizmoResult, Vec>)> { + targets: &[Transform], + ) -> Option<(GizmoResult, Vec)> { let config = self.config(); let egui_viewport = egui::Rect { diff --git a/crates/transform-gizmo/src/config.rs b/crates/transform-gizmo/src/config.rs index 7fbc00b..b601bde 100644 --- a/crates/transform-gizmo/src/config.rs +++ b/crates/transform-gizmo/src/config.rs @@ -5,7 +5,9 @@ pub use ecolor::Color32; use emath::Rect; use enumset::{enum_set, EnumSet, EnumSetType}; -use crate::math::{screen_to_world, world_to_screen, DMat4, DQuat, DVec3, DVec4, Vec4Swizzles}; +use crate::math::{ + screen_to_world, world_to_screen, DMat4, DQuat, DVec3, DVec4, Transform, Vec4Swizzles, +}; /// The default snapping distance for rotation in radians pub const DEFAULT_SNAP_ANGLE: f32 = std::f32::consts::PI / 32.0; @@ -149,19 +151,16 @@ impl PreparedGizmoConfig { } } - pub(crate) fn update_for_targets(&mut self, targets: &[DMat4]) { + pub(crate) fn update_for_targets(&mut self, targets: &[Transform]) { let mut scale = DVec3::ZERO; let mut translation = DVec3::ZERO; let mut rotation = DQuat::IDENTITY; let mut target_count = 0; for target in targets { - let (s, r, t) = target.to_scale_rotation_translation(); - - scale += s; - translation += t; - - rotation = r; + scale += DVec3::from(target.scale); + translation += DVec3::from(target.translation); + rotation = DQuat::from(target.rotation); target_count += 1; } diff --git a/crates/transform-gizmo/src/gizmo.rs b/crates/transform-gizmo/src/gizmo.rs index 3b52129..c3ac054 100644 --- a/crates/transform-gizmo/src/gizmo.rs +++ b/crates/transform-gizmo/src/gizmo.rs @@ -4,9 +4,9 @@ use enumset::EnumSet; use std::ops::{Add, AddAssign, Sub}; use crate::config::{GizmoConfig, GizmoDirection, GizmoMode, PreparedGizmoConfig}; -use crate::math::screen_to_world; +use crate::math::{screen_to_world, Transform}; use epaint::Mesh; -use glam::{DMat4, DVec3}; +use glam::{DQuat, DVec3}; use crate::subgizmo::rotation::RotationParams; use crate::subgizmo::scale::ScaleParams; @@ -16,7 +16,7 @@ use crate::subgizmo::{ SubGizmoControl, TranslationSubGizmo, }; -/// A transform gizmo for manipulating 4x4 matrices. +/// A 3D transformation gizmo. #[derive(Clone, Debug)] pub struct Gizmo { /// Prepared configuration of the gizmo. @@ -31,7 +31,7 @@ pub struct Gizmo { subgizmos: Vec, active_subgizmo_id: Option, - target_start_transforms: Vec, + target_start_transforms: Vec, } impl Default for Gizmo { @@ -70,14 +70,41 @@ impl Gizmo { /// Updates the gizmo based on given interaction information. /// + /// # Examples + /// + /// ``` + /// # // Dummy values + /// # use transform_gizmo::GizmoInteraction; + /// # let mut gizmo = transform_gizmo::Gizmo::default(); + /// # let cursor_pos = Default::default(); + /// # let drag_started = true; + /// # let dragging = true; + /// # let mut transforms = vec![]; + /// + /// let interaction = GizmoInteraction { + /// cursor_pos, + /// drag_started, + /// dragging + /// }; + /// + /// if let Some((_result, new_transforms)) = gizmo.update(interaction, &transforms) { + /// for (new_transform, transform) in + /// // Update transforms + /// new_transforms.iter().zip(&mut transforms) + /// { + /// *transform = *new_transform; + /// } + /// } + /// ``` + /// /// Returns the result of the interaction with the updated transformation. /// /// [`Some`] is returned when any of the subgizmos is being dragged, [`None`] otherwise. pub fn update( &mut self, interaction: GizmoInteraction, - targets: &[mint::RowMatrix4], - ) -> Option<(GizmoResult, Vec>)> { + targets: &[Transform], + ) -> Option<(GizmoResult, Vec)> { // Mode was changed. Update all subgizmos accordingly. if self.config.modes != self.last_modes { self.last_modes = self.config.modes; @@ -105,10 +132,8 @@ impl Gizmo { return None; } - let targets = targets.iter().copied().map(DMat4::from).collect::>(); - // Update the gizmo based on the given targets. - self.config.update_for_targets(&targets); + self.config.update_for_targets(targets); for subgizmo in &mut self.subgizmos { // Update current configuration to each subgizmo. @@ -128,7 +153,7 @@ impl Gizmo { // If we started dragging from one of the subgizmos, mark it as active. if interaction.drag_started { self.active_subgizmo_id = Some(subgizmo.id()); - self.target_start_transforms = targets.clone(); + self.target_start_transforms = targets.to_vec(); } } } @@ -158,47 +183,36 @@ impl Gizmo { return None; }; - let mut updated_targets = Vec::>::new(); + let mut updated_targets = Vec::::new(); for (target_start_transform, target_transform) in self.target_start_transforms.iter().zip(targets) { - let mut new_target_transform = target_transform; + let mut new_target_transform = *target_transform; match result { GizmoResult::Rotation { delta, total: _ } => { // Rotate around the target group origin - - let group_translation = DMat4::from_translation(self.config.translation); - - new_target_transform = - group_translation.inverse().mul_mat4(&new_target_transform); - - new_target_transform = - DMat4::from_quat(delta.into()).mul_mat4(&new_target_transform); - - new_target_transform = group_translation.mul_mat4(&new_target_transform); + let rotation_delta = DQuat::from(delta); + let origin = self.config.translation; + + new_target_transform.translation = (origin + + rotation_delta * (DVec3::from(target_transform.translation) - origin)) + .into(); + new_target_transform.rotation = + (rotation_delta * DQuat::from(target_transform.rotation)).into(); } GizmoResult::Translation { delta, total: _ } => { - new_target_transform = - DMat4::from_translation(delta.into()).mul_mat4(&new_target_transform); + new_target_transform.translation = + (DVec3::from(delta) + DVec3::from(new_target_transform.translation)).into(); } GizmoResult::Scale { total } => { - let (start_scale, _, _) = - target_start_transform.to_scale_rotation_translation(); - - let (_, target_rotation, target_translation) = - target_transform.to_scale_rotation_translation(); - - new_target_transform = DMat4::from_scale_rotation_translation( - start_scale * DVec3::from(total), - target_rotation, - target_translation, - ); + new_target_transform.scale = + (DVec3::from(target_start_transform.scale) * DVec3::from(total)).into(); } } - updated_targets.push(new_target_transform.into()); + updated_targets.push(new_target_transform); } Some((result, updated_targets)) @@ -472,7 +486,7 @@ pub enum GizmoResult { pub struct GizmoDrawData { /// Vertices in viewport space. pub vertices: Vec<[f32; 2]>, - /// RGBA colors. + /// Linear RGBA colors. pub colors: Vec<[f32; 4]>, /// Indices to the vertex data. pub indices: Vec, diff --git a/crates/transform-gizmo/src/lib.rs b/crates/transform-gizmo/src/lib.rs index c1fbe0b..d08b49d 100644 --- a/crates/transform-gizmo/src/lib.rs +++ b/crates/transform-gizmo/src/lib.rs @@ -1,6 +1,4 @@ -//! Provides a feature-rich and configurable 3D transformation -//! gizmo that can be used to manipulate 4x4 transformation matrices (position, rotation, scale) -//! visually. +//! Provides a feature-rich and configurable gizmo that can be used for 3d transformations (translation, rotation, scale). //! //! Such gizmos are commonly used in applications such as game engines and 3d modeling software. //! diff --git a/crates/transform-gizmo/src/math.rs b/crates/transform-gizmo/src/math.rs index a258828..d9831ae 100644 --- a/crates/transform-gizmo/src/math.rs +++ b/crates/transform-gizmo/src/math.rs @@ -1,6 +1,27 @@ pub use emath::{Pos2, Rect, Vec2}; pub use glam::{DMat3, DMat4, DQuat, DVec2, DVec3, DVec4, Mat4, Quat, Vec3, Vec4Swizzles}; +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct Transform { + pub scale: mint::Vector3, + pub rotation: mint::Quaternion, + pub translation: mint::Vector3, +} + +impl Transform { + pub fn from_scale_rotation_translation( + scale: impl Into>, + rotation: impl Into>, + translation: impl Into>, + ) -> Self { + Self { + scale: scale.into(), + rotation: rotation.into(), + translation: translation.into(), + } + } +} + /// Creates a matrix that represents rotation between two 3d vectors /// /// Credit: diff --git a/examples/egui/src/main.rs b/examples/egui/src/main.rs index 12b55df..65867bf 100644 --- a/examples/egui/src/main.rs +++ b/examples/egui/src/main.rs @@ -1,5 +1,5 @@ use eframe::{egui, NativeOptions}; -use transform_gizmo_egui::math::DQuat; +use transform_gizmo_egui::math::{DQuat, Transform}; use transform_gizmo_egui::{ math::{DMat4, DVec3}, *, @@ -11,7 +11,9 @@ struct ExampleApp { gizmo_modes: EnumSet, gizmo_orientation: GizmoOrientation, - model_matrix: DMat4, + scale: DVec3, + rotation: DQuat, + translation: DVec3, } impl ExampleApp { @@ -20,7 +22,9 @@ impl ExampleApp { gizmo: Gizmo::default(), gizmo_modes: enum_set!(GizmoMode::Rotate | GizmoMode::Translate), gizmo_orientation: GizmoOrientation::Local, - model_matrix: DMat4::IDENTITY, + scale: DVec3::ONE, + rotation: DQuat::IDENTITY, + translation: DVec3::ZERO, } } @@ -50,8 +54,19 @@ impl ExampleApp { ..Default::default() }); - if let Some((result, targets)) = self.gizmo.interact(ui, &[self.model_matrix.into()]) { - self.model_matrix = targets.first().copied().unwrap().into(); + let mut transform = + Transform::from_scale_rotation_translation(self.scale, self.rotation, self.translation); + + if let Some((result, new_transforms)) = self.gizmo.interact(ui, &[transform]) { + for (new_transform, transform) in + new_transforms.iter().zip(std::iter::once(&mut transform)) + { + *transform = *new_transform; + } + + self.scale = transform.scale.into(); + self.rotation = transform.rotation.into(); + self.translation = transform.translation.into(); match result { GizmoResult::Rotation { delta: _, total } => {