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 colour: [f32; 4],
40 pub line_width: f32,
42 pub handle_colour: [f32; 4],
44
45 hovered_endpoint: Option<usize>,
46 active_endpoint: Option<usize>,
47 drag_plane_normal: glam::Vec3,
49 drag_plane_d: f32,
50}
51
52impl LineProbeWidget {
53 pub fn new(start: glam::Vec3, end: glam::Vec3) -> Self {
55 Self {
56 start,
57 end,
58 colour: [1.0, 0.6, 0.1, 1.0],
59 line_width: 2.0,
60 handle_colour: [0.0; 4],
61 hovered_endpoint: None,
62 active_endpoint: None,
63 drag_plane_normal: glam::Vec3::Z,
64 drag_plane_d: 0.0,
65 }
66 }
67
68 pub fn hovered_endpoint(&self) -> Option<usize> {
70 self.hovered_endpoint
71 }
72
73 pub fn is_active(&self) -> bool {
75 self.active_endpoint.is_some()
76 }
77
78 pub fn update(&mut self, ctx: &WidgetContext) -> WidgetResult {
82 let (ro, rd) = ctx_ray(ctx);
83 let mut updated = false;
84
85 if self.active_endpoint.is_none() {
87 let hit = self.hit_test(ro, rd, ctx);
88 if hit.is_some() || !ctx.drag_started {
92 self.hovered_endpoint = hit;
93 }
94 }
95
96 if ctx.drag_started {
97 if let Some(ep) = self.hovered_endpoint {
98 let ep_world = self.endpoint_pos(ep);
99 let fwd = glam::Vec3::from(ctx.camera.forward);
100 let n = -fwd;
101 self.drag_plane_normal = n;
102 self.drag_plane_d = -n.dot(ep_world);
103 self.active_endpoint = Some(ep);
104 }
105 }
106
107 if let Some(ep) = self.active_endpoint {
108 if ctx.released || (!ctx.dragging && !ctx.drag_started) {
109 self.active_endpoint = None;
110 self.hovered_endpoint = None;
111 } else if let Some(hit) =
112 ray_plane_intersection(ro, rd, self.drag_plane_normal, self.drag_plane_d)
113 {
114 let prev = self.endpoint_pos(ep);
115 if (hit - prev).length_squared() > 1e-10 {
116 self.set_endpoint(ep, hit);
117 updated = true;
118 }
119 }
120 }
121
122 if updated {
123 WidgetResult::Updated
124 } else {
125 WidgetResult::None
126 }
127 }
128
129 pub fn polyline_item(&self, id: u64) -> PolylineItem {
133 PolylineItem {
134 positions: vec![self.start.to_array(), self.end.to_array()],
135 strip_lengths: vec![2],
136 default_colour: self.colour,
137 line_width: self.line_width,
138 id,
139 ..PolylineItem::default()
140 }
141 }
142
143 pub fn handle_glyphs(&self, id_base: u64, ctx: &WidgetContext) -> GlyphItem {
152 let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 10.0);
153 let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 10.0);
154
155 let s0 = if self.hovered_endpoint == Some(0) || self.active_endpoint == Some(0) {
156 1.0_f32
157 } else {
158 0.0
159 };
160 let s1 = if self.hovered_endpoint == Some(1) || self.active_endpoint == Some(1) {
161 1.0_f32
162 } else {
163 0.0
164 };
165
166 GlyphItem {
167 positions: vec![self.start.to_array(), self.end.to_array()],
168 vectors: vec![[r0, 0.0, 0.0], [r1, 0.0, 0.0]],
169 scale: 1.0,
170 scale_by_magnitude: true,
171 scalars: vec![s0, s1],
172 scalar_range: Some((0.0, 1.0)),
173 glyph_type: GlyphType::Sphere,
174 id: id_base,
175 default_colour: self.handle_colour,
176 use_default_colour: self.handle_colour[3] > 0.0,
177 ..GlyphItem::default()
178 }
179 }
180
181 fn endpoint_pos(&self, ep: usize) -> glam::Vec3 {
186 if ep == 0 { self.start } else { self.end }
187 }
188
189 fn set_endpoint(&mut self, ep: usize, pos: glam::Vec3) {
190 if ep == 0 {
191 self.start = pos;
192 } else {
193 self.end = pos;
194 }
195 }
196
197 fn hit_test(
198 &self,
199 ray_origin: glam::Vec3,
200 ray_dir: glam::Vec3,
201 ctx: &WidgetContext,
202 ) -> Option<usize> {
203 let r0 = handle_world_radius(self.start, &ctx.camera, ctx.viewport_size.y, 10.0);
204 let r1 = handle_world_radius(self.end, &ctx.camera, ctx.viewport_size.y, 10.0);
205
206 let d0 = ray_point_dist(ray_origin, ray_dir, self.start);
207 let d1 = ray_point_dist(ray_origin, ray_dir, self.end);
208
209 let h0 = d0 < r0;
210 let h1 = d1 < r1;
211
212 match (h0, h1) {
213 (true, true) => {
214 let t0 = (self.start - ray_origin).dot(ray_dir);
216 let t1 = (self.end - ray_origin).dot(ray_dir);
217 Some(if t0 <= t1 { 0 } else { 1 })
218 }
219 (true, false) => Some(0),
220 (false, true) => Some(1),
221 (false, false) => None,
222 }
223 }
224}