viewport_lib/interaction/widgets/
line_probe.rs1use crate::interaction::clip_plane::ray_plane_intersection;
4use crate::renderer::{GlyphItem, GlyphType, PolylineItem};
5
6use super::{WidgetContext, WidgetResult, ctx_ray, handle_world_radius, ray_point_dist};
7
8pub struct LineProbeWidget {
34 pub start: glam::Vec3,
36 pub end: glam::Vec3,
38 pub color: [f32; 4],
40 pub line_width: f32,
42
43 hovered_endpoint: Option<usize>,
44 active_endpoint: Option<usize>,
45 drag_plane_normal: glam::Vec3,
47 drag_plane_d: f32,
48}
49
50impl LineProbeWidget {
51 pub fn new(start: glam::Vec3, end: glam::Vec3) -> Self {
53 Self {
54 start,
55 end,
56 color: [1.0, 0.6, 0.1, 1.0],
57 line_width: 2.0,
58 hovered_endpoint: None,
59 active_endpoint: None,
60 drag_plane_normal: glam::Vec3::Z,
61 drag_plane_d: 0.0,
62 }
63 }
64
65 pub fn hovered_endpoint(&self) -> Option<usize> {
67 self.hovered_endpoint
68 }
69
70 pub fn is_active(&self) -> bool {
72 self.active_endpoint.is_some()
73 }
74
75 pub fn update(&mut self, ctx: &WidgetContext) -> WidgetResult {
79 let (ro, rd) = ctx_ray(ctx);
80 let mut updated = false;
81
82 if self.active_endpoint.is_none() {
84 self.hovered_endpoint = self.hit_test(ro, rd, ctx);
85 }
86
87 if ctx.drag_started {
88 if let Some(ep) = self.hovered_endpoint {
89 let ep_world = self.endpoint_pos(ep);
90 let fwd = glam::Vec3::from(ctx.camera.forward);
91 let n = -fwd;
92 self.drag_plane_normal = n;
93 self.drag_plane_d = -n.dot(ep_world);
94 self.active_endpoint = Some(ep);
95 }
96 }
97
98 if let Some(ep) = self.active_endpoint {
99 if ctx.released || (!ctx.dragging && !ctx.drag_started) {
100 self.active_endpoint = None;
101 self.hovered_endpoint = None;
102 } else if let Some(hit) =
103 ray_plane_intersection(ro, rd, self.drag_plane_normal, self.drag_plane_d)
104 {
105 let prev = self.endpoint_pos(ep);
106 if (hit - prev).length_squared() > 1e-10 {
107 self.set_endpoint(ep, hit);
108 updated = true;
109 }
110 }
111 }
112
113 if updated { WidgetResult::Updated } else { WidgetResult::None }
114 }
115
116 pub fn polyline_item(&self, id: u64) -> PolylineItem {
120 PolylineItem {
121 positions: vec![self.start.to_array(), self.end.to_array()],
122 strip_lengths: vec![2],
123 default_color: self.color,
124 line_width: self.line_width,
125 id,
126 ..PolylineItem::default()
127 }
128 }
129
130 pub fn handle_glyphs(&self, id_base: u64, ctx: &WidgetContext) -> GlyphItem {
139 let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 10.0);
140 let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 10.0);
141
142 let s0 = if self.hovered_endpoint == Some(0) || self.active_endpoint == Some(0) {
143 1.0_f32
144 } else {
145 0.0
146 };
147 let s1 = if self.hovered_endpoint == Some(1) || self.active_endpoint == Some(1) {
148 1.0_f32
149 } else {
150 0.0
151 };
152
153 GlyphItem {
154 positions: vec![self.start.to_array(), self.end.to_array()],
155 vectors: vec![[r0, 0.0, 0.0], [r1, 0.0, 0.0]],
156 scale: 1.0,
157 scale_by_magnitude: true,
158 scalars: vec![s0, s1],
159 scalar_range: Some((0.0, 1.0)),
160 glyph_type: GlyphType::Sphere,
161 id: id_base,
162 ..GlyphItem::default()
163 }
164 }
165
166 fn endpoint_pos(&self, ep: usize) -> glam::Vec3 {
171 if ep == 0 { self.start } else { self.end }
172 }
173
174 fn set_endpoint(&mut self, ep: usize, pos: glam::Vec3) {
175 if ep == 0 { self.start = pos; } else { self.end = pos; }
176 }
177
178 fn hit_test(
179 &self,
180 ray_origin: glam::Vec3,
181 ray_dir: glam::Vec3,
182 ctx: &WidgetContext,
183 ) -> Option<usize> {
184 let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 12.0);
185 let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 12.0);
186
187 let d0 = ray_point_dist(ray_origin, ray_dir, self.start);
188 let d1 = ray_point_dist(ray_origin, ray_dir, self.end);
189
190 let h0 = d0 < r0;
191 let h1 = d1 < r1;
192
193 match (h0, h1) {
194 (true, true) => {
195 let t0 = (self.start - ray_origin).dot(ray_dir);
197 let t1 = (self.end - ray_origin).dot(ray_dir);
198 Some(if t0 <= t1 { 0 } else { 1 })
199 }
200 (true, false) => Some(0),
201 (false, true) => Some(1),
202 (false, false) => None,
203 }
204 }
205}