use std::ops::{Deref, DerefMut};
pub use ecolor::Color32;
use emath::Rect;
use enumset::{EnumSet, EnumSetType, enum_set};
use crate::math::{
DMat4, DQuat, DVec3, DVec4, Transform, Vec4Swizzles, screen_to_world, world_to_screen,
};
pub const DEFAULT_SNAP_ANGLE: f32 = std::f32::consts::PI / 32.0;
pub const DEFAULT_SNAP_DISTANCE: f32 = 0.1;
pub const DEFAULT_SNAP_SCALE: f32 = 0.1;
#[derive(Debug, Copy, Clone)]
pub struct GizmoConfig {
pub view_matrix: mint::RowMatrix4<f64>,
pub projection_matrix: mint::RowMatrix4<f64>,
pub viewport: Rect,
pub modes: EnumSet<GizmoMode>,
pub mode_override: Option<GizmoMode>,
pub orientation: GizmoOrientation,
pub pivot_point: TransformPivotPoint,
pub snapping: bool,
pub snap_angle: f32,
pub snap_distance: f32,
pub snap_scale: f32,
pub visuals: GizmoVisuals,
pub pixels_per_point: f32,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
view_matrix: DMat4::IDENTITY.into(),
projection_matrix: DMat4::IDENTITY.into(),
viewport: Rect::NOTHING,
modes: GizmoMode::all(),
mode_override: None,
orientation: GizmoOrientation::default(),
pivot_point: TransformPivotPoint::default(),
snapping: false,
snap_angle: DEFAULT_SNAP_ANGLE,
snap_distance: DEFAULT_SNAP_DISTANCE,
snap_scale: DEFAULT_SNAP_SCALE,
visuals: GizmoVisuals::default(),
pixels_per_point: 1.0,
}
}
}
impl GizmoConfig {
pub(crate) fn view_forward(&self) -> DVec3 {
DVec4::from(self.view_matrix.z).xyz()
}
pub(crate) fn view_up(&self) -> DVec3 {
DVec4::from(self.view_matrix.y).xyz()
}
pub(crate) fn view_right(&self) -> DVec3 {
DVec4::from(self.view_matrix.x).xyz()
}
pub(crate) fn local_space(&self) -> bool {
self.orientation() == GizmoOrientation::Local
}
pub(crate) fn orientation(&self) -> GizmoOrientation {
self.orientation
}
pub(crate) fn modes_changed(&self, other: &Self) -> bool {
(self.modes != other.modes && self.mode_override.is_none())
|| (self.mode_override != other.mode_override)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub(crate) struct PreparedGizmoConfig {
config: GizmoConfig,
pub(crate) rotation: DQuat,
pub(crate) translation: DVec3,
pub(crate) scale: DVec3,
pub(crate) view_projection: DMat4,
pub(crate) model_matrix: DMat4,
pub(crate) mvp: DMat4,
pub(crate) scale_factor: f32,
pub(crate) focus_distance: f32,
pub(crate) left_handed: bool,
pub(crate) eye_to_model_dir: DVec3,
}
impl Deref for PreparedGizmoConfig {
type Target = GizmoConfig;
fn deref(&self) -> &Self::Target {
&self.config
}
}
impl DerefMut for PreparedGizmoConfig {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.config
}
}
impl PreparedGizmoConfig {
pub(crate) fn update_for_config(&mut self, config: GizmoConfig) {
let projection_matrix = DMat4::from(config.projection_matrix);
let view_matrix = DMat4::from(config.view_matrix);
let view_projection = projection_matrix * view_matrix;
let left_handed = if projection_matrix.z_axis.w == 0.0 {
projection_matrix.z_axis.z > 0.0
} else {
projection_matrix.z_axis.w > 0.0
};
self.config = config;
self.view_projection = view_projection;
self.left_handed = left_handed;
self.update_transform(Transform {
scale: self.scale.into(),
rotation: self.rotation.into(),
translation: self.translation.into(),
});
}
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 {
scale += DVec3::from(target.scale);
translation += DVec3::from(target.translation);
rotation = DQuat::from(target.rotation);
target_count += 1;
}
if target_count == 0 {
scale = DVec3::ONE;
} else {
translation /= target_count as f64;
scale /= target_count as f64;
}
self.update_transform(Transform {
scale: scale.into(),
rotation: rotation.into(),
translation: translation.into(),
});
}
pub(crate) fn update_transform(&mut self, transform: Transform) {
self.translation = transform.translation.into();
self.rotation = transform.rotation.into();
self.scale = transform.scale.into();
self.model_matrix =
DMat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation);
self.mvp = self.view_projection * self.model_matrix;
self.scale_factor = self.mvp.as_ref()[15] as f32
/ self.projection_matrix.x.x as f32
/ self.config.viewport.width()
* 2.0;
let gizmo_screen_pos =
world_to_screen(self.config.viewport, self.mvp, self.translation).unwrap_or_default();
let gizmo_view_near = screen_to_world(
self.config.viewport,
self.view_projection.inverse(),
gizmo_screen_pos,
-1.0,
);
self.focus_distance = self.scale_factor * (self.config.visuals.stroke_width / 2.0 + 5.0);
self.eye_to_model_dir = (gizmo_view_near - self.translation).normalize_or_zero();
}
pub(crate) fn as_transform(&self) -> Transform {
Transform {
scale: self.scale.into(),
rotation: self.rotation.into(),
translation: self.translation.into(),
}
}
}
#[derive(Debug, EnumSetType, Hash)]
pub enum GizmoMode {
RotateView,
RotateX,
RotateY,
RotateZ,
TranslateView,
TranslateX,
TranslateY,
TranslateZ,
TranslateXY,
TranslateXZ,
TranslateYZ,
ScaleUniform,
ScaleX,
ScaleY,
ScaleZ,
ScaleXY,
ScaleXZ,
ScaleYZ,
Arcball,
}
impl GizmoMode {
pub fn all() -> EnumSet<Self> {
EnumSet::all()
}
pub const fn all_rotate() -> EnumSet<Self> {
enum_set!(Self::RotateX | Self::RotateY | Self::RotateZ | Self::RotateView)
}
pub const fn all_translate() -> EnumSet<Self> {
enum_set!(
Self::TranslateX
| Self::TranslateY
| Self::TranslateZ
| Self::TranslateXY
| Self::TranslateXZ
| Self::TranslateYZ
| Self::TranslateView
)
}
pub const fn all_scale() -> EnumSet<Self> {
enum_set!(
Self::ScaleX
| Self::ScaleY
| Self::ScaleZ
| Self::ScaleXY
| Self::ScaleXZ
| Self::ScaleYZ
| Self::ScaleUniform
)
}
pub fn is_rotate(&self) -> bool {
self.kind() == GizmoModeKind::Rotate
}
pub fn is_translate(&self) -> bool {
self.kind() == GizmoModeKind::Translate
}
pub fn is_scale(&self) -> bool {
self.kind() == GizmoModeKind::Scale
}
pub fn axes(&self) -> EnumSet<GizmoDirection> {
match self {
Self::RotateX | Self::TranslateX | Self::ScaleX => {
enum_set!(GizmoDirection::X)
}
Self::RotateY | Self::TranslateY | Self::ScaleY => {
enum_set!(GizmoDirection::Y)
}
Self::RotateZ | Self::TranslateZ | Self::ScaleZ => {
enum_set!(GizmoDirection::Z)
}
Self::RotateView | Self::TranslateView => {
enum_set!(GizmoDirection::View)
}
Self::ScaleUniform | Self::Arcball => {
enum_set!(GizmoDirection::X | GizmoDirection::Y | GizmoDirection::Z)
}
Self::TranslateXY | Self::ScaleXY => {
enum_set!(GizmoDirection::X | GizmoDirection::Y)
}
Self::TranslateXZ | Self::ScaleXZ => {
enum_set!(GizmoDirection::X | GizmoDirection::Z)
}
Self::TranslateYZ | Self::ScaleYZ => {
enum_set!(GizmoDirection::Y | GizmoDirection::Z)
}
}
}
pub fn all_from_axes(axes: EnumSet<GizmoDirection>) -> EnumSet<Self> {
EnumSet::<Self>::all()
.iter()
.filter(|mode| mode.axes() == axes)
.collect()
}
pub fn kind(&self) -> GizmoModeKind {
match self {
Self::RotateX | Self::RotateY | Self::RotateZ | Self::RotateView => {
GizmoModeKind::Rotate
}
Self::TranslateX
| Self::TranslateY
| Self::TranslateZ
| Self::TranslateXY
| Self::TranslateXZ
| Self::TranslateYZ
| Self::TranslateView => GizmoModeKind::Translate,
Self::ScaleX
| Self::ScaleY
| Self::ScaleZ
| Self::ScaleXY
| Self::ScaleXZ
| Self::ScaleYZ
| Self::ScaleUniform => GizmoModeKind::Scale,
Self::Arcball => GizmoModeKind::Arcball,
}
}
pub fn all_from_kind(kind: GizmoModeKind) -> EnumSet<Self> {
EnumSet::<Self>::all()
.iter()
.filter(|mode| mode.kind() == kind)
.collect()
}
pub fn from_kind_and_axes(kind: GizmoModeKind, axes: EnumSet<GizmoDirection>) -> Option<Self> {
EnumSet::<Self>::all()
.iter()
.find(|mode| mode.kind() == kind && mode.axes() == axes)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
pub enum GizmoModeKind {
Rotate,
Translate,
Scale,
Arcball,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum TransformPivotPoint {
#[default]
MedianPoint,
IndividualOrigins,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum GizmoOrientation {
#[default]
Global,
Local,
}
#[derive(Debug, EnumSetType, Hash)]
pub enum GizmoDirection {
X,
Y,
Z,
View,
}
#[derive(Debug, Copy, Clone)]
pub struct GizmoVisuals {
pub x_color: Color32,
pub y_color: Color32,
pub z_color: Color32,
pub s_color: Color32,
pub inactive_alpha: f32,
pub highlight_alpha: f32,
pub highlight_color: Option<Color32>,
pub stroke_width: f32,
pub gizmo_size: f32,
}
impl Default for GizmoVisuals {
fn default() -> Self {
Self {
x_color: Color32::from_rgb(255, 0, 125),
y_color: Color32::from_rgb(0, 255, 125),
z_color: Color32::from_rgb(0, 125, 255),
s_color: Color32::from_rgb(255, 255, 255),
inactive_alpha: 0.7,
highlight_alpha: 1.0,
highlight_color: None,
stroke_width: 4.0,
gizmo_size: 75.0,
}
}
}