#![warn(clippy::all)]
use std::cmp::Ordering;
use std::f32::consts::PI;
use std::hash::Hash;
use std::ops::Sub;
use egui::{Color32, Context, Id, PointerButton, Rect, Sense, Ui};
use glam::{DMat4, DQuat, DVec3, DVec4, Mat4, Quat, Vec3, Vec4Swizzles};
use crate::subgizmo::{SubGizmo, SubGizmoKind};
mod math;
mod painter;
mod rotation;
mod scale;
mod subgizmo;
mod translation;
pub const DEFAULT_SNAP_ANGLE: f32 = PI / 32.0;
pub const DEFAULT_SNAP_DISTANCE: f32 = 0.1;
pub const DEFAULT_SNAP_SCALE: f32 = 0.1;
const MAX_SUBGIZMOS: usize = 6;
#[derive(Debug)]
pub struct Gizmo {
id: Id,
config: GizmoConfig,
subgizmos: [Option<SubGizmo>; MAX_SUBGIZMOS],
subgizmo_count: usize,
}
impl Gizmo {
pub fn new(id_source: impl Hash) -> Self {
Self {
id: Id::new(id_source),
config: GizmoConfig::default(),
subgizmos: Default::default(),
subgizmo_count: 0,
}
}
pub fn model_matrix(mut self, model_matrix: mint::ColumnMatrix4<f32>) -> Self {
self.config.model_matrix = Mat4::from(model_matrix).as_dmat4();
self
}
pub fn view_matrix(mut self, view_matrix: mint::ColumnMatrix4<f32>) -> Self {
self.config.view_matrix = Mat4::from(view_matrix).as_dmat4();
self
}
pub fn projection_matrix(mut self, projection_matrix: mint::ColumnMatrix4<f32>) -> Self {
self.config.projection_matrix = Mat4::from(projection_matrix).as_dmat4();
self
}
pub fn viewport(mut self, viewport: Rect) -> Self {
self.config.viewport = viewport;
self
}
pub fn mode(mut self, mode: GizmoMode) -> Self {
self.config.mode = mode;
self
}
pub fn orientation(mut self, orientation: GizmoOrientation) -> Self {
self.config.orientation = orientation;
self
}
pub fn snapping(mut self, snapping: bool) -> Self {
self.config.snapping = snapping;
self
}
pub fn snap_angle(mut self, snap_angle: f32) -> Self {
self.config.snap_angle = snap_angle;
self
}
pub fn snap_distance(mut self, snap_distance: f32) -> Self {
self.config.snap_distance = snap_distance;
self
}
pub fn snap_scale(mut self, snap_scale: f32) -> Self {
self.config.snap_scale = snap_scale;
self
}
pub fn visuals(mut self, visuals: GizmoVisuals) -> Self {
self.config.visuals = visuals;
self
}
pub fn interact(mut self, ui: &mut Ui) -> Option<GizmoResult> {
self.config.prepare(ui);
match self.config.mode {
GizmoMode::Rotate => self.add_subgizmos(self.new_rotation()),
GizmoMode::Translate => self.add_subgizmos(self.new_translation()),
GizmoMode::Scale => self.add_subgizmos(self.new_scale()),
};
let mut result = None;
let mut active_subgizmo = None;
let mut state = GizmoState::load(ui.ctx(), self.id);
if let Some(pointer_ray) = self.pointer_ray(ui) {
let viewport = self.config.viewport;
let id = self.id;
if state.active_subgizmo_id.is_none() {
if let Some(subgizmo) = self.pick_subgizmo(ui, pointer_ray) {
subgizmo.focused = true;
let interaction = ui.interact(viewport, id, Sense::click_and_drag());
let dragging = interaction.dragged_by(PointerButton::Primary);
if interaction.drag_started() && dragging {
state.active_subgizmo_id = Some(subgizmo.id);
}
}
}
active_subgizmo = state
.active_subgizmo_id
.and_then(|id| self.subgizmos_mut().find(|subgizmo| subgizmo.id == id));
if let Some(subgizmo) = active_subgizmo.as_mut() {
if ui.input(|i| i.pointer.primary_down()) {
subgizmo.active = true;
subgizmo.focused = true;
result = subgizmo.update(ui, pointer_ray);
} else {
state.active_subgizmo_id = None;
}
}
}
if let Some((subgizmo, result)) = active_subgizmo.zip(result) {
subgizmo.config.translation = Vec3::from(result.translation).as_dvec3();
subgizmo.config.rotation = Quat::from(result.rotation).as_f64();
subgizmo.config.scale = Vec3::from(result.scale).as_dvec3();
}
state.save(ui.ctx(), self.id);
self.draw_subgizmos(ui, &mut state);
result
}
fn draw_subgizmos(&self, ui: &mut Ui, state: &mut GizmoState) {
for subgizmo in self.subgizmos() {
if state.active_subgizmo_id.is_none() || subgizmo.active {
subgizmo.draw(ui);
}
}
}
fn pick_subgizmo(&mut self, ui: &Ui, ray: Ray) -> Option<&mut SubGizmo> {
self.subgizmos_mut()
.filter_map(|subgizmo| subgizmo.pick(ui, ray).map(|t| (t, subgizmo)))
.min_by(|(first, _), (second, _)| first.partial_cmp(second).unwrap_or(Ordering::Equal))
.map(|(_, subgizmo)| subgizmo)
}
fn subgizmos(&self) -> impl Iterator<Item = &SubGizmo> {
self.subgizmos.iter().flatten()
}
fn subgizmos_mut(&mut self) -> impl Iterator<Item = &mut SubGizmo> {
self.subgizmos.iter_mut().flatten()
}
fn new_rotation(&self) -> [SubGizmo; 4] {
[
SubGizmo::new(
self.id.with("rx"),
self.config,
GizmoDirection::X,
SubGizmoKind::RotationAxis,
),
SubGizmo::new(
self.id.with("ry"),
self.config,
GizmoDirection::Y,
SubGizmoKind::RotationAxis,
),
SubGizmo::new(
self.id.with("rz"),
self.config,
GizmoDirection::Z,
SubGizmoKind::RotationAxis,
),
SubGizmo::new(
self.id.with("rs"),
self.config,
GizmoDirection::Screen,
SubGizmoKind::RotationAxis,
),
]
}
fn new_translation(&self) -> [SubGizmo; 6] {
[
SubGizmo::new(
self.id.with("tx"),
self.config,
GizmoDirection::X,
SubGizmoKind::TranslationVector,
),
SubGizmo::new(
self.id.with("ty"),
self.config,
GizmoDirection::Y,
SubGizmoKind::TranslationVector,
),
SubGizmo::new(
self.id.with("tz"),
self.config,
GizmoDirection::Z,
SubGizmoKind::TranslationVector,
),
SubGizmo::new(
self.id.with("tyz"),
self.config,
GizmoDirection::X,
SubGizmoKind::TranslationPlane,
),
SubGizmo::new(
self.id.with("txz"),
self.config,
GizmoDirection::Y,
SubGizmoKind::TranslationPlane,
),
SubGizmo::new(
self.id.with("txy"),
self.config,
GizmoDirection::Z,
SubGizmoKind::TranslationPlane,
),
]
}
fn new_scale(&self) -> [SubGizmo; 6] {
[
SubGizmo::new(
self.id.with("sx"),
self.config,
GizmoDirection::X,
SubGizmoKind::ScaleVector,
),
SubGizmo::new(
self.id.with("sy"),
self.config,
GizmoDirection::Y,
SubGizmoKind::ScaleVector,
),
SubGizmo::new(
self.id.with("sz"),
self.config,
GizmoDirection::Z,
SubGizmoKind::ScaleVector,
),
SubGizmo::new(
self.id.with("syz"),
self.config,
GizmoDirection::X,
SubGizmoKind::ScalePlane,
),
SubGizmo::new(
self.id.with("sxz"),
self.config,
GizmoDirection::Y,
SubGizmoKind::ScalePlane,
),
SubGizmo::new(
self.id.with("sxy"),
self.config,
GizmoDirection::Z,
SubGizmoKind::ScalePlane,
),
]
}
fn add_subgizmos<const N: usize>(&mut self, subgizmos: [SubGizmo; N]) {
let mut i = self.subgizmo_count;
for subgizmo in subgizmos.into_iter() {
self.subgizmos[i] = Some(subgizmo);
i += 1;
}
self.subgizmo_count = i;
}
fn pointer_ray(&self, ui: &Ui) -> Option<Ray> {
let hover = ui.input(|i| i.pointer.hover_pos())?;
let viewport = self.config.viewport;
let x = (((hover.x - viewport.min.x) / viewport.width()) * 2.0 - 1.0) as f64;
let y = (((hover.y - viewport.min.y) / viewport.height()) * 2.0 - 1.0) as f64;
let screen_to_world: DMat4 = self.config.view_projection.inverse();
let mut origin = screen_to_world * DVec4::new(x, -y, -1.0, 1.0);
origin /= origin.w;
let mut target = screen_to_world * DVec4::new(x, -y, 1.0, 1.0);
if target.w.abs() < 1e-7 {
target.w = 1e-7;
}
target /= target.w;
let direction = target.sub(origin).xyz().normalize();
Some(Ray {
origin: origin.xyz(),
direction,
})
}
}
#[derive(Debug, Copy, Clone)]
pub struct GizmoResult {
pub scale: mint::Vector3<f32>,
pub rotation: mint::Quaternion<f32>,
pub translation: mint::Vector3<f32>,
pub mode: GizmoMode,
pub value: [f32; 3],
}
impl GizmoResult {
pub fn transform(&self) -> mint::ColumnMatrix4<f32> {
Mat4::from_scale_rotation_translation(
self.scale.into(),
self.rotation.into(),
self.translation.into(),
)
.into()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum GizmoMode {
Rotate,
Translate,
Scale,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum GizmoOrientation {
Global,
Local,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum GizmoDirection {
X,
Y,
Z,
Screen,
}
#[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, 50, 0),
y_color: Color32::from_rgb(50, 255, 0),
z_color: Color32::from_rgb(0, 50, 255),
s_color: Color32::from_rgb(255, 255, 255),
inactive_alpha: 0.5,
highlight_alpha: 0.9,
highlight_color: None,
stroke_width: 4.0,
gizmo_size: 75.0,
}
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct GizmoConfig {
pub view_matrix: DMat4,
pub projection_matrix: DMat4,
pub model_matrix: DMat4,
pub viewport: Rect,
pub mode: GizmoMode,
pub orientation: GizmoOrientation,
pub snapping: bool,
pub snap_angle: f32,
pub snap_distance: f32,
pub snap_scale: f32,
pub visuals: GizmoVisuals,
pub rotation: DQuat,
pub translation: DVec3,
pub scale: DVec3,
pub view_projection: DMat4,
pub mvp: DMat4,
pub scale_factor: f32,
pub focus_distance: f32,
pub left_handed: bool,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
view_matrix: DMat4::IDENTITY,
projection_matrix: DMat4::IDENTITY,
model_matrix: DMat4::IDENTITY,
viewport: Rect::NOTHING,
mode: GizmoMode::Rotate,
orientation: GizmoOrientation::Global,
snapping: false,
snap_angle: DEFAULT_SNAP_ANGLE,
snap_distance: DEFAULT_SNAP_DISTANCE,
snap_scale: DEFAULT_SNAP_SCALE,
visuals: GizmoVisuals::default(),
rotation: DQuat::IDENTITY,
translation: DVec3::ZERO,
scale: DVec3::ONE,
view_projection: DMat4::IDENTITY,
mvp: DMat4::IDENTITY,
scale_factor: 0.0,
focus_distance: 0.0,
left_handed: false,
}
}
}
impl GizmoConfig {
fn prepare(&mut self, ui: &Ui) {
if self.viewport.is_negative() {
self.viewport = ui.clip_rect();
}
let (scale, rotation, translation) = self.model_matrix.to_scale_rotation_translation();
self.rotation = rotation;
self.translation = translation;
self.scale = scale;
self.view_projection = self.projection_matrix * self.view_matrix;
self.mvp = self.projection_matrix * self.view_matrix * self.model_matrix;
self.scale_factor = self.mvp.as_ref()[15] as f32
/ self.projection_matrix.as_ref()[0] as f32
/ self.viewport.width()
* 2.0;
self.focus_distance = self.scale_factor * (self.visuals.stroke_width / 2.0 + 5.0);
self.left_handed = if self.projection_matrix.z_axis.w == 0.0 {
self.projection_matrix.z_axis.z > 0.0
} else {
self.projection_matrix.z_axis.w > 0.0
};
}
pub(crate) fn view_forward(&self) -> DVec3 {
self.view_matrix.row(2).xyz()
}
pub(crate) fn view_right(&self) -> DVec3 {
self.view_matrix.row(0).xyz()
}
pub(crate) fn local_space(&self) -> bool {
self.orientation == GizmoOrientation::Local
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct Ray {
origin: DVec3,
direction: DVec3,
}
#[derive(Default, Debug, Copy, Clone)]
struct GizmoState {
active_subgizmo_id: Option<Id>,
}
pub(crate) trait WidgetData: Sized + Default + Copy + Clone + Send + Sync + 'static {
fn load(ctx: &Context, gizmo_id: Id) -> Self {
ctx.memory_mut(|mem| *mem.data.get_temp_mut_or_default::<Self>(gizmo_id))
}
fn save(self, ctx: &Context, gizmo_id: Id) {
ctx.memory_mut(|mem| mem.data.insert_temp(gizmo_id, self));
}
}
impl WidgetData for GizmoState {}