use crate::interaction::clip_plane::ray_plane_intersection;
use crate::renderer::{GlyphItem, GlyphType, PolylineItem, aabb_wireframe_polyline};
use crate::scene::aabb::Aabb;
use parry3d::math::{Pose, Vector};
use parry3d::query::{Ray, RayCast};
use super::{WidgetContext, WidgetResult, ctx_ray, handle_world_radius};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum BoxHandle {
Center,
Face(usize),
}
pub struct BoxWidget {
pub center: glam::Vec3,
pub half_extents: glam::Vec3,
pub color: [f32; 4],
hovered_handle: Option<BoxHandle>,
active_handle: Option<BoxHandle>,
drag_plane_normal: glam::Vec3,
drag_plane_d: f32,
drag_anchor_world: glam::Vec3,
}
impl BoxWidget {
pub fn new(center: glam::Vec3, half_extents: glam::Vec3) -> Self {
Self {
center,
half_extents: half_extents.max(glam::Vec3::splat(0.01)),
color: [0.3, 0.8, 0.4, 1.0],
hovered_handle: None,
active_handle: None,
drag_plane_normal: glam::Vec3::Z,
drag_plane_d: 0.0,
drag_anchor_world: glam::Vec3::ZERO,
}
}
pub fn is_active(&self) -> bool {
self.active_handle.is_some()
}
pub fn aabb(&self) -> Aabb {
Aabb {
min: self.center - self.half_extents,
max: self.center + self.half_extents,
}
}
pub fn update(&mut self, ctx: &WidgetContext) -> WidgetResult {
let (ro, rd) = ctx_ray(ctx);
let mut updated = false;
if self.active_handle.is_none() {
self.hovered_handle = self.hit_test(ro, rd, ctx);
}
if ctx.drag_started {
if let Some(handle) = self.hovered_handle {
let anchor = self.handle_pos(handle);
let n = -glam::Vec3::from(ctx.camera.forward);
self.drag_plane_normal = n;
self.drag_plane_d = -n.dot(anchor);
self.drag_anchor_world = anchor;
self.active_handle = Some(handle);
}
}
if let Some(handle) = self.active_handle {
if ctx.released || (!ctx.dragging && !ctx.drag_started) {
self.active_handle = None;
self.hovered_handle = None;
} else if let Some(hit) =
ray_plane_intersection(ro, rd, self.drag_plane_normal, self.drag_plane_d)
{
match handle {
BoxHandle::Center => {
let delta = hit - self.drag_anchor_world;
if delta.length_squared() > 1e-10 {
self.center += delta;
self.drag_anchor_world = hit;
updated = true;
}
}
BoxHandle::Face(i) => {
let normal = Self::face_normal(i);
let proj = (hit - self.drag_anchor_world).dot(normal);
if proj.abs() > 1e-5 {
let axis = i / 2; let new_he = (self.half_extents[axis] + proj).max(0.01);
let he_delta = new_he - self.half_extents[axis];
self.half_extents[axis] = new_he;
self.center += normal * (he_delta * 0.5);
self.drag_anchor_world = hit;
updated = true;
}
}
}
}
}
if updated { WidgetResult::Updated } else { WidgetResult::None }
}
pub fn wireframe_item(&self, id: u64) -> PolylineItem {
let mut item = aabb_wireframe_polyline(&self.aabb(), self.color);
item.id = id;
item
}
pub fn handle_glyphs(&self, id_base: u64, ctx: &WidgetContext) -> GlyphItem {
let handles = [
BoxHandle::Center,
BoxHandle::Face(0),
BoxHandle::Face(1),
BoxHandle::Face(2),
BoxHandle::Face(3),
BoxHandle::Face(4),
BoxHandle::Face(5),
];
let mut positions = Vec::with_capacity(7);
let mut vectors = Vec::with_capacity(7);
let mut scalars = Vec::with_capacity(7);
for handle in handles {
let pos = self.handle_pos(handle);
let r = handle_world_radius(pos, &ctx.camera, ctx.viewport_size.y, 9.0);
let s = if self.hovered_handle == Some(handle) || self.active_handle == Some(handle) {
1.0_f32
} else {
0.2
};
positions.push(pos.to_array());
vectors.push([r, 0.0, 0.0]);
scalars.push(s);
}
GlyphItem {
positions,
vectors,
scale: 1.0,
scale_by_magnitude: true,
scalars,
scalar_range: Some((0.0, 1.0)),
glyph_type: GlyphType::Sphere,
id: id_base,
..GlyphItem::default()
}
}
fn handle_pos(&self, handle: BoxHandle) -> glam::Vec3 {
match handle {
BoxHandle::Center => self.center,
BoxHandle::Face(i) => self.center + Self::face_normal(i) * self.half_extents[i / 2],
}
}
fn face_normal(i: usize) -> glam::Vec3 {
match i {
0 => glam::Vec3::X,
1 => glam::Vec3::NEG_X,
2 => glam::Vec3::Y,
3 => glam::Vec3::NEG_Y,
4 => glam::Vec3::Z,
_ => glam::Vec3::NEG_Z,
}
}
fn hit_test(
&self,
ray_origin: glam::Vec3,
ray_dir: glam::Vec3,
ctx: &WidgetContext,
) -> Option<BoxHandle> {
let ray = Ray::new(
Vector::new(ray_origin.x, ray_origin.y, ray_origin.z),
Vector::new(ray_dir.x, ray_dir.y, ray_dir.z),
);
let identity = glam::Quat::IDENTITY;
let handles = [
BoxHandle::Center,
BoxHandle::Face(0),
BoxHandle::Face(1),
BoxHandle::Face(2),
BoxHandle::Face(3),
BoxHandle::Face(4),
BoxHandle::Face(5),
];
let mut best: Option<(f32, BoxHandle)> = None;
for handle in handles {
let pos = self.handle_pos(handle);
let r = handle_world_radius(pos, &ctx.camera, ctx.viewport_size.y, 12.0);
let ball = parry3d::shape::Ball::new(r);
let pose = Pose::from_parts([pos.x, pos.y, pos.z].into(), identity);
if let Some(t) = ball.cast_ray(&pose, &ray, f32::MAX, true) {
if best.is_none() || t < best.unwrap().0 {
best = Some((t, handle));
}
}
}
best.map(|(_, h)| h)
}
}