Skip to content

Commit

Permalink
Merge pull request #37 from urholaukkarinen/improvements
Browse files Browse the repository at this point in the history
Subgizmos fade out when unusable
  • Loading branch information
urholaukkarinen authored Jan 15, 2024
2 parents d288d22 + 2ad3798 commit 57215a0
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 41 deletions.
6 changes: 6 additions & 0 deletions demo/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ pub fn update_camera(
}
for ev in ev_scroll.read() {
scroll += ev.y;

scroll /= if cfg!(target_arch = "wasm32") {
100.0
} else {
2.0
};
}
if input_mouse.just_released(orbit_button) || input_mouse.just_pressed(orbit_button) {
orbit_button_changed = true;
Expand Down
2 changes: 1 addition & 1 deletion docs/egui-gizmo-demo.js

Large diffs are not rendered by default.

Binary file modified docs/egui-gizmo-demo_bg.wasm
Binary file not shown.
7 changes: 1 addition & 6 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
<title>egui-gizmo demo</title><style>body,canvas,html{margin:0;padding:0;width:100%;height:100%;overflow:hidden;position:absolute;background:black;z-index:0}</style><link as=fetch crossorigin href=./egui-gizmo-demo_bg.wasm integrity=sha384-_m4KZ3sk5EciNVPbvfL26vsm3GpfBwmRGPwBn6T8R8YDqeZ5FroLh2Z1Mg-zfPRM rel=preload type=application/wasm><link crossorigin href=./egui-gizmo-demo.js integrity=sha384-aAUtZnUcZxnAIx0Bo3HpSQU_yjPuzCT34MBzD2w6Dbtb5WzJCB41rHbuuZc3lDde rel=modulepreload></head><body oncontextmenu="return false;"><script type=module>
import init, * as bindings from './egui-gizmo-demo.js';
init('./egui-gizmo-demo_bg.wasm');
window.wasmBindings = bindings;

</script></body></html>
<title>egui-gizmo demo</title><style>body,canvas,html{margin:0;padding:0;width:100%;height:100%;overflow:hidden;position:absolute;background:black;z-index:0}</style><link as=fetch crossorigin href=./egui-gizmo-demo_bg.wasm integrity=sha384-0IsFCjRufCuGXGz07Y9s5KPkzzJ4lc9eStTf1slrcfK6T6qJV2o-APa6DgyIymyy rel=preload type=application/wasm><link crossorigin href=./egui-gizmo-demo.js integrity=sha384-AMlWz6FdyRln6WA_6U03u7Y2DAcOIc0eyT-OJ0UAQSSK7olp-CRXAUEivD4rH425 rel=modulepreload></head><body oncontextmenu="return false;"><script type=module>import a,*as b from"./egui-gizmo-demo.js";a(`./egui-gizmo-demo_bg.wasm`);window.wasmBindings=b</script></body></html>
42 changes: 21 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ use std::f32::consts::PI;
use std::hash::Hash;
use std::ops::Sub;

use crate::math::{screen_to_world, world_to_screen};
use egui::{Color32, Context, Id, PointerButton, Rect, Sense, Ui};
use glam::{DMat4, DQuat, DVec3, DVec4, Mat4, Quat, Vec3, Vec4Swizzles};
use glam::{DMat4, DQuat, DVec3, Mat4, Quat, Vec3, Vec4Swizzles};

use crate::subgizmo::{SubGizmo, SubGizmoKind};

Expand Down Expand Up @@ -355,29 +356,14 @@ impl Gizmo {
/// Calculate a world space ray from current mouse position
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 mat = self.config.view_projection.inverse();
let origin = screen_to_world(self.config.viewport, mat, hover, -1.0);
let target = screen_to_world(self.config.viewport, mat, hover, 1.0);

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);
let direction = target.sub(origin).normalize();

// w is zero when far plane is set to infinity
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,
})
Some(Ray { origin, direction })
}
}

Expand Down Expand Up @@ -498,6 +484,7 @@ pub(crate) struct GizmoConfig {
pub scale: DVec3,
pub view_projection: DMat4,
pub mvp: DMat4,
pub gizmo_view_forward: DVec3,
pub scale_factor: f32,
/// How close the mouse pointer needs to be to a subgizmo before it is focused
pub focus_distance: f32,
Expand All @@ -524,6 +511,7 @@ impl Default for GizmoConfig {
scale: DVec3::ONE,
view_projection: DMat4::IDENTITY,
mvp: DMat4::IDENTITY,
gizmo_view_forward: DVec3::ONE,
scale_factor: 0.0,
focus_distance: 0.0,
left_handed: false,
Expand Down Expand Up @@ -559,6 +547,18 @@ impl GizmoConfig {
} else {
self.projection_matrix.z_axis.w > 0.0
};

let gizmo_screen_pos =
world_to_screen(self.viewport, self.mvp, self.translation).unwrap_or_default();

let gizmo_view_near = screen_to_world(
self.viewport,
self.view_projection.inverse(),
gizmo_screen_pos,
-1.0,
);

self.gizmo_view_forward = (gizmo_view_near - self.translation).normalize_or_zero();
}

/// Forward vector of the view camera
Expand Down
19 changes: 18 additions & 1 deletion src/math.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use egui::{Pos2, Rect};
use glam::{DMat3, DMat4, DVec3, DVec4};
use glam::{DMat3, DMat4, DVec3, DVec4, Vec4Swizzles};

/// Creates a matrix that represents rotation between two 3d vectors
///
Expand Down Expand Up @@ -173,3 +173,20 @@ pub fn world_to_screen(viewport: Rect, mvp: DMat4, pos: DVec3) -> Option<Pos2> {
(center.y as f64 + pos.y * viewport.height() as f64 / 2.0) as f32,
))
}

/// Calculates 3d world coordinates from 2d screen coordinates
pub fn screen_to_world(viewport: Rect, mat: DMat4, pos: Pos2, z: f64) -> DVec3 {
let x = (((pos.x - viewport.min.x) / viewport.width()) * 2.0 - 1.0) as f64;
let y = (((pos.y - viewport.min.y) / viewport.height()) * 2.0 - 1.0) as f64;

let mut world_pos = mat * DVec4::new(x, -y, z, 1.0);

// w is zero when far plane is set to infinity
if world_pos.w.abs() < 1e-7 {
world_pos.w = 1e-7;
}

world_pos /= world_pos.w;

return world_pos.xyz();
}
48 changes: 42 additions & 6 deletions src/scale.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use egui::{Stroke, Ui};
use glam::{DMat4, DVec3};
use std::ops::RangeInclusive;

use crate::math::{ray_to_plane_origin, round_to_interval, segment_to_segment, world_to_screen};
use crate::painter::Painter3d;
Expand All @@ -10,6 +11,9 @@ use crate::translation::{
};
use crate::{GizmoMode, GizmoResult, Ray, WidgetData};

const ARROW_FADE: RangeInclusive<f64> = (0.95)..=(0.99);
const PLANE_FADE: RangeInclusive<f64> = (0.70)..=(0.86);

/// Picks given scale subgizmo. If the subgizmo is close enough to
/// the mouse pointer, distance from camera to the subgizmo is returned.
pub(crate) fn pick_scale(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) -> Option<f64> {
Expand All @@ -33,19 +37,36 @@ pub(crate) fn pick_scale(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) -> Option<f64>

let start_delta = distance_from_origin_2d(subgizmo, ui)?;

let dot = subgizmo
.config
.gizmo_view_forward
.dot(subgizmo.normal())
.abs();
let visibility =
(1.0 - (dot - *ARROW_FADE.start()) / (*ARROW_FADE.end() - *ARROW_FADE.start())).min(1.0);

subgizmo.update_state_with(ui, |state: &mut ScaleState| {
state.start_scale = subgizmo.config.scale;
state.start_delta = start_delta;
state.visibility = visibility as _;
});

if dist <= subgizmo.config.focus_distance as f64 {
if visibility > 0.0 && dist <= subgizmo.config.focus_distance as f64 {
Some(ray.origin.distance(ray_point))
} else {
None
}
}

pub(crate) fn draw_scale(subgizmo: &SubGizmo, ui: &Ui) {
let state = subgizmo.state::<ScaleState>(ui);

if state.visibility <= 0.0001 {
return;
}

let color = subgizmo.color().gamma_multiply(state.visibility);

let painter = Painter3d::new(
ui.painter().clone(),
subgizmo.config.view_projection * scale_transform(subgizmo),
Expand All @@ -54,8 +75,6 @@ pub(crate) fn draw_scale(subgizmo: &SubGizmo, ui: &Ui) {

let direction = subgizmo.local_normal();

let color = subgizmo.color();

let width = subgizmo.config.scale_factor * subgizmo.config.visuals.stroke_width;
let length = subgizmo.config.scale_factor * subgizmo.config.visuals.gizmo_size;
let end_stroke_width = subgizmo.config.visuals.stroke_width * 2.5;
Expand Down Expand Up @@ -110,12 +129,22 @@ pub(crate) fn pick_scale_plane(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) -> Option

let start_delta = distance_from_origin_2d(subgizmo, ui)?;

let dot = subgizmo
.config
.gizmo_view_forward
.dot(subgizmo.normal())
.abs();
let visibility = (1.0
- ((1.0 - dot) - *PLANE_FADE.start()) / (*PLANE_FADE.end() - *PLANE_FADE.start()))
.min(1.0);

subgizmo.update_state_with(ui, |state: &mut ScaleState| {
state.start_scale = subgizmo.config.scale;
state.start_delta = start_delta;
state.visibility = visibility as _;
});

if dist_from_origin <= translation_plane_size(subgizmo) {
if visibility > 0.0 && dist_from_origin <= translation_plane_size(subgizmo) {
Some(t)
} else {
None
Expand Down Expand Up @@ -153,14 +182,20 @@ pub(crate) fn update_scale_plane(subgizmo: &SubGizmo, ui: &Ui, _ray: Ray) -> Opt
}

pub(crate) fn draw_scale_plane(subgizmo: &SubGizmo, ui: &Ui) {
let state = subgizmo.state::<ScaleState>(ui);

if state.visibility <= 0.0001 {
return;
}

let color = subgizmo.color().gamma_multiply(state.visibility);

let painter = Painter3d::new(
ui.painter().clone(),
subgizmo.config.view_projection * scale_transform(subgizmo),
subgizmo.config.viewport,
);

let color = subgizmo.color();

let scale = translation_plane_size(subgizmo) * 0.5;
let a = translation_plane_binormal(subgizmo.direction) * scale;
let b = translation_plane_tangent(subgizmo.direction) * scale;
Expand All @@ -183,6 +218,7 @@ pub(crate) fn draw_scale_plane(subgizmo: &SubGizmo, ui: &Ui) {
pub(crate) struct ScaleState {
start_scale: DVec3,
start_delta: f64,
visibility: f32,
}

impl WidgetData for ScaleState {}
Expand Down
48 changes: 42 additions & 6 deletions src/translation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use egui::{Stroke, Ui};
use glam::{DMat4, DVec3};
use std::ops::RangeInclusive;

use crate::math::{
intersect_plane, ray_to_plane_origin, ray_to_ray, round_to_interval, segment_to_segment,
Expand All @@ -8,6 +9,9 @@ use crate::painter::Painter3d;
use crate::subgizmo::SubGizmo;
use crate::{GizmoDirection, GizmoMode, GizmoResult, Ray, WidgetData};

const ARROW_FADE: RangeInclusive<f64> = (0.95)..=(0.99);
const PLANE_FADE: RangeInclusive<f64> = (0.70)..=(0.86);

/// Picks given translation subgizmo. If the subgizmo is close enough to
/// the mouse pointer, distance from camera to the subgizmo is returned.
pub(crate) fn pick_translation(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) -> Option<f64> {
Expand All @@ -29,20 +33,37 @@ pub(crate) fn pick_translation(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) -> Option
let subgizmo_point = origin + dir * length * subgizmo_t;
let dist = (ray_point - subgizmo_point).length();

let dot = subgizmo
.config
.gizmo_view_forward
.dot(subgizmo.normal())
.abs();
let visibility =
(1.0 - (dot - *ARROW_FADE.start()) / (*ARROW_FADE.end() - *ARROW_FADE.start())).min(1.0);

subgizmo.update_state_with(ui, |state: &mut TranslationState| {
state.start_point = subgizmo_point;
state.last_point = subgizmo_point;
state.current_delta = DVec3::ZERO;
state.visibility = visibility as _;
});

if dist <= subgizmo.config.focus_distance as f64 {
if visibility > 0.0 && dist <= subgizmo.config.focus_distance as f64 {
Some(ray.origin.distance(ray_point))
} else {
None
}
}

pub(crate) fn draw_translation(subgizmo: &SubGizmo, ui: &Ui) {
let state = subgizmo.state::<TranslationState>(ui);

if state.visibility <= 0.0001 {
return;
}

let color = subgizmo.color().gamma_multiply(state.visibility);

let painter = Painter3d::new(
ui.painter().clone(),
subgizmo.config.view_projection * translation_transform(subgizmo),
Expand All @@ -51,8 +72,6 @@ pub(crate) fn draw_translation(subgizmo: &SubGizmo, ui: &Ui) {

let direction = subgizmo.local_normal();

let color = subgizmo.color();

let width = subgizmo.config.scale_factor * subgizmo.config.visuals.stroke_width;
let length = subgizmo.config.scale_factor * subgizmo.config.visuals.gizmo_size;
let arrow_length = width * 2.4;
Expand Down Expand Up @@ -119,28 +138,44 @@ pub(crate) fn pick_translation_plane(subgizmo: &SubGizmo, ui: &Ui, ray: Ray) ->

let ray_point = ray.origin + ray.direction * t;

let dot = subgizmo
.config
.gizmo_view_forward
.dot(subgizmo.normal())
.abs();
let visibility = (1.0
- ((1.0 - dot) - *PLANE_FADE.start()) / (*PLANE_FADE.end() - *PLANE_FADE.start()))
.min(1.0);

subgizmo.update_state_with(ui, |state: &mut TranslationState| {
state.start_point = ray_point;
state.last_point = ray_point;
state.current_delta = DVec3::ZERO;
state.visibility = visibility as _;
});

if dist_from_origin <= translation_plane_size(subgizmo) {
if visibility > 0.0 && dist_from_origin <= translation_plane_size(subgizmo) {
Some(t)
} else {
None
}
}

pub(crate) fn draw_translation_plane(subgizmo: &SubGizmo, ui: &Ui) {
let state = subgizmo.state::<TranslationState>(ui);

if state.visibility <= 0.0001 {
return;
}

let color = subgizmo.color().gamma_multiply(state.visibility);

let painter = Painter3d::new(
ui.painter().clone(),
subgizmo.config.view_projection * translation_transform(subgizmo),
subgizmo.config.viewport,
);

let color = subgizmo.color();

let scale = translation_plane_size(subgizmo) * 0.5;
let a = translation_plane_binormal(subgizmo.direction) * scale;
let b = translation_plane_tangent(subgizmo.direction) * scale;
Expand Down Expand Up @@ -224,6 +259,7 @@ pub(crate) struct TranslationState {
start_point: DVec3,
last_point: DVec3,
current_delta: DVec3,
visibility: f32,
}

impl WidgetData for TranslationState {}
Expand Down

0 comments on commit 57215a0

Please sign in to comment.