use crate::interaction::clip_plane::ray_plane_intersection;
use crate::renderer::{GlyphItem, GlyphType, PolylineItem};
use super::{WidgetContext, WidgetResult, ctx_ray, handle_world_radius, ray_point_dist};
pub struct LineProbeWidget {
pub start: glam::Vec3,
pub end: glam::Vec3,
pub colour: [f32; 4],
pub line_width: f32,
pub handle_colour: [f32; 4],
hovered_endpoint: Option<usize>,
active_endpoint: Option<usize>,
drag_plane_normal: glam::Vec3,
drag_plane_d: f32,
}
impl LineProbeWidget {
pub fn new(start: glam::Vec3, end: glam::Vec3) -> Self {
Self {
start,
end,
colour: [1.0, 0.6, 0.1, 1.0],
line_width: 2.0,
handle_colour: [0.0; 4],
hovered_endpoint: None,
active_endpoint: None,
drag_plane_normal: glam::Vec3::Z,
drag_plane_d: 0.0,
}
}
pub fn hovered_endpoint(&self) -> Option<usize> {
self.hovered_endpoint
}
pub fn is_active(&self) -> bool {
self.active_endpoint.is_some()
}
pub fn update(&mut self, ctx: &WidgetContext) -> WidgetResult {
let (ro, rd) = ctx_ray(ctx);
let mut updated = false;
if self.active_endpoint.is_none() {
let hit = self.hit_test(ro, rd, ctx);
if hit.is_some() || !ctx.drag_started {
self.hovered_endpoint = hit;
}
}
if ctx.drag_started {
if let Some(ep) = self.hovered_endpoint {
let ep_world = self.endpoint_pos(ep);
let fwd = glam::Vec3::from(ctx.camera.forward);
let n = -fwd;
self.drag_plane_normal = n;
self.drag_plane_d = -n.dot(ep_world);
self.active_endpoint = Some(ep);
}
}
if let Some(ep) = self.active_endpoint {
if ctx.released || (!ctx.dragging && !ctx.drag_started) {
self.active_endpoint = None;
self.hovered_endpoint = None;
} else if let Some(hit) =
ray_plane_intersection(ro, rd, self.drag_plane_normal, self.drag_plane_d)
{
let prev = self.endpoint_pos(ep);
if (hit - prev).length_squared() > 1e-10 {
self.set_endpoint(ep, hit);
updated = true;
}
}
}
if updated {
WidgetResult::Updated
} else {
WidgetResult::None
}
}
pub fn polyline_item(&self, id: u64) -> PolylineItem {
PolylineItem {
positions: vec![self.start.to_array(), self.end.to_array()],
strip_lengths: vec![2],
default_colour: self.colour,
line_width: self.line_width,
id,
..PolylineItem::default()
}
}
pub fn handle_glyphs(&self, id_base: u64, ctx: &WidgetContext) -> GlyphItem {
let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 10.0);
let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 10.0);
let s0 = if self.hovered_endpoint == Some(0) || self.active_endpoint == Some(0) {
1.0_f32
} else {
0.0
};
let s1 = if self.hovered_endpoint == Some(1) || self.active_endpoint == Some(1) {
1.0_f32
} else {
0.0
};
GlyphItem {
positions: vec![self.start.to_array(), self.end.to_array()],
vectors: vec![[r0, 0.0, 0.0], [r1, 0.0, 0.0]],
scale: 1.0,
scale_by_magnitude: true,
scalars: vec![s0, s1],
scalar_range: Some((0.0, 1.0)),
glyph_type: GlyphType::Sphere,
id: id_base,
default_colour: self.handle_colour,
use_default_colour: self.handle_colour[3] > 0.0,
..GlyphItem::default()
}
}
fn endpoint_pos(&self, ep: usize) -> glam::Vec3 {
if ep == 0 { self.start } else { self.end }
}
fn set_endpoint(&mut self, ep: usize, pos: glam::Vec3) {
if ep == 0 {
self.start = pos;
} else {
self.end = pos;
}
}
fn hit_test(
&self,
ray_origin: glam::Vec3,
ray_dir: glam::Vec3,
ctx: &WidgetContext,
) -> Option<usize> {
let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 10.0);
let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 10.0);
let d0 = ray_point_dist(ray_origin, ray_dir, self.start);
let d1 = ray_point_dist(ray_origin, ray_dir, self.end);
let h0 = d0 < r0;
let h1 = d1 < r1;
match (h0, h1) {
(true, true) => {
let t0 = (self.start - ray_origin).dot(ray_dir);
let t1 = (self.end - ray_origin).dot(ray_dir);
Some(if t0 <= t1 { 0 } else { 1 })
}
(true, false) => Some(0),
(false, true) => Some(1),
(false, false) => None,
}
}
}