1use super::*;
2
3fn strip_for_node(node_idx: u32, strip_lengths: &[u32]) -> u32 {
9 let mut offset = 0u32;
10 for (i, &len) in strip_lengths.iter().enumerate() {
11 offset += len;
12 if node_idx < offset {
13 return i as u32;
14 }
15 }
16 strip_lengths.len().saturating_sub(1) as u32
17}
18
19fn pick_closest_polyline_segment(
26 click_pos: glam::Vec2,
27 viewport_size: glam::Vec2,
28 view_proj: glam::Mat4,
29 positions: &[[f32; 3]],
30 strip_lengths: &[u32],
31 threshold_px: f32,
32) -> Option<(u32, glam::Vec3)> {
33 let project = |p: [f32; 3]| -> Option<glam::Vec2> {
34 let clip = view_proj * glam::Vec4::new(p[0], p[1], p[2], 1.0);
35 if clip.w <= 0.0 {
36 return None;
37 }
38 Some(glam::Vec2::new(
39 (clip.x / clip.w + 1.0) * 0.5 * viewport_size.x,
40 (1.0 - clip.y / clip.w) * 0.5 * viewport_size.y,
41 ))
42 };
43
44 let mut best_dist = threshold_px;
45 let mut best: Option<(u32, glam::Vec3)> = None;
46
47 macro_rules! try_seg {
48 ($ai:expr, $bi:expr, $seg:expr) => {{
49 if let (Some(sa), Some(sb)) = (project(positions[$ai]), project(positions[$bi])) {
50 let ab = sb - sa;
51 let len_sq = ab.length_squared();
52 let t = if len_sq < 1e-6 {
53 0.0f32
54 } else {
55 ((click_pos - sa).dot(ab) / len_sq).clamp(0.0, 1.0)
56 };
57 let dist = (click_pos - (sa + ab * t)).length();
58 if dist < best_dist {
59 best_dist = dist;
60 let wa = glam::Vec3::from(positions[$ai]);
61 let wb = glam::Vec3::from(positions[$bi]);
62 best = Some(($seg as u32, wa.lerp(wb, t)));
63 }
64 }
65 }};
66 }
67
68 if strip_lengths.is_empty() {
69 for j in 0..positions.len().saturating_sub(1) {
70 try_seg!(j, j + 1, j);
71 }
72 } else {
73 let mut node_off = 0usize;
74 let mut seg_off = 0u32;
75 for &slen in strip_lengths {
76 let slen = slen as usize;
77 for j in 0..slen.saturating_sub(1) {
78 try_seg!(node_off + j, node_off + j + 1, seg_off + j as u32);
79 }
80 seg_off += slen.saturating_sub(1) as u32;
81 node_off += slen;
82 }
83 }
84
85 best
86}
87
88fn segment_in_rect(
90 a: glam::Vec2,
91 b: glam::Vec2,
92 rect_min: glam::Vec2,
93 rect_max: glam::Vec2,
94) -> bool {
95 if a.x.min(b.x) > rect_max.x
97 || a.x.max(b.x) < rect_min.x
98 || a.y.min(b.y) > rect_max.y
99 || a.y.max(b.y) < rect_min.y
100 {
101 return false;
102 }
103 let in_r = |p: glam::Vec2| {
105 p.x >= rect_min.x && p.x <= rect_max.x && p.y >= rect_min.y && p.y <= rect_max.y
106 };
107 if in_r(a) || in_r(b) {
108 return true;
109 }
110 let crosses = |p0: glam::Vec2, p1: glam::Vec2, q0: glam::Vec2, q1: glam::Vec2| -> bool {
112 let d = p1 - p0;
113 let e = q1 - q0;
114 let denom = d.x * e.y - d.y * e.x;
115 if denom.abs() < 1e-10 {
116 return false;
117 }
118 let diff = q0 - p0;
119 let t = (diff.x * e.y - diff.y * e.x) / denom;
120 let u = (diff.x * d.y - diff.y * d.x) / denom;
121 t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0
122 };
123 let tl = rect_min;
124 let tr = glam::Vec2::new(rect_max.x, rect_min.y);
125 let bl = glam::Vec2::new(rect_min.x, rect_max.y);
126 let br = rect_max;
127 crosses(a, b, tl, tr) || crosses(a, b, tr, br) || crosses(a, b, br, bl) || crosses(a, b, bl, tl)
128}
129
130fn strip_for_segment(seg_idx: u32, strip_lengths: &[u32]) -> u32 {
132 let mut offset = 0u32;
133 for (i, &len) in strip_lengths.iter().enumerate() {
134 let segs = len.saturating_sub(1);
135 offset += segs;
136 if seg_idx < offset {
137 return i as u32;
138 }
139 }
140 strip_lengths.len().saturating_sub(1) as u32
141}
142
143#[inline]
148fn ray_triangle(
149 ray_orig: glam::Vec3,
150 ray_dir: glam::Vec3,
151 v0: glam::Vec3,
152 v1: glam::Vec3,
153 v2: glam::Vec3,
154) -> Option<f32> {
155 let e1 = v1 - v0;
156 let e2 = v2 - v0;
157 let h = ray_dir.cross(e2);
158 let a = e1.dot(h);
159 if a.abs() < 1e-10 {
160 return None;
161 }
162 let f = 1.0 / a;
163 let s = ray_orig - v0;
164 let u = f * s.dot(h);
165 if u < 0.0 || u > 1.0 {
166 return None;
167 }
168 let q = s.cross(e1);
169 let v = f * ray_dir.dot(q);
170 if v < 0.0 || u + v > 1.0 {
171 return None;
172 }
173 let t = f * e2.dot(q);
174 if t > 0.0 { Some(t) } else { None }
175}
176
177fn ribbon_lateral_frames(
183 positions: &[[f32; 3]],
184 strip_lengths: &[u32],
185 width: f32,
186 width_attribute: Option<&[f32]>,
187 twist_attribute: Option<&[[f32; 3]]>,
188) -> Vec<(glam::Vec3, f32)> {
189 let n = positions.len();
190 let mut frames: Vec<(glam::Vec3, f32)> = vec![(glam::Vec3::X, 0.0); n];
192
193 let single;
194 let strips: &[u32] = if strip_lengths.is_empty() {
195 single = [positions.len() as u32];
196 &single
197 } else {
198 strip_lengths
199 };
200
201 let mut node_off = 0usize;
202 for &slen in strips {
203 let slen = slen as usize;
204 if slen < 2 {
205 node_off += slen;
206 continue;
207 }
208
209 let pts: Vec<glam::Vec3> = positions[node_off..node_off + slen]
210 .iter()
211 .map(|&p| glam::Vec3::from(p))
212 .collect();
213
214 let t0 = (pts[1] - pts[0]).normalize_or_zero();
215 if t0.length_squared() < 1e-10 {
216 node_off += slen;
217 continue;
218 }
219 let ref_v = if t0.x.abs() < 0.9 {
220 glam::Vec3::X
221 } else {
222 glam::Vec3::Y
223 };
224 let mut u = t0.cross(ref_v).normalize();
225
226 for k in 0..slen {
227 let tangent = if k + 1 < slen {
228 (pts[k + 1] - pts[k]).normalize_or_zero()
229 } else {
230 (pts[k] - pts[k - 1]).normalize_or_zero()
231 };
232
233 if k > 0 {
235 let t_prev = (pts[k] - pts[k - 1]).normalize_or_zero();
236 let axis = t_prev.cross(tangent);
237 let sin_a = axis.length().min(1.0);
238 if sin_a > 1e-6 {
239 let cos_a = t_prev.dot(tangent).clamp(-1.0, 1.0);
240 let ax = axis / sin_a;
241 u = u * cos_a + ax.cross(u) * sin_a + ax * ax.dot(u) * (1.0 - cos_a);
242 u = u.normalize_or_zero();
243 }
244 }
245
246 let mut lateral = u;
248 if let Some(twist) = twist_attribute {
249 if let Some(&tv) = twist.get(node_off + k) {
250 let tv = glam::Vec3::from(tv);
251 let proj = tv - tangent * tangent.dot(tv);
252 if proj.length_squared() > 1e-10 {
253 lateral = proj.normalize();
254 }
255 }
256 }
257
258 let half_w = width_attribute
259 .and_then(|wa| wa.get(node_off + k).copied())
260 .unwrap_or(width)
261 * 0.5;
262
263 frames[node_off + k] = (lateral, half_w);
264 }
265
266 node_off += slen;
267 }
268
269 frames
270}
271
272fn eval_implicit_primitive(p: glam::Vec3, prim: &crate::resources::ImplicitPrimitive) -> f32 {
278 match prim.kind {
279 1 => {
280 let center = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
282 (p - center).length() - prim.params[3]
283 }
284 2 => {
285 let center = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
287 let half = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
288 let q = (p - center).abs() - half;
289 q.max(glam::Vec3::ZERO).length() + q.x.max(q.y).max(q.z).min(0.0)
290 }
291 3 => {
292 let n =
294 glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]).normalize_or_zero();
295 p.dot(n) + prim.params[3]
296 }
297 4 => {
298 let a = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
300 let r = prim.params[3];
301 let b = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
302 let pa = p - a;
303 let ba = b - a;
304 let h = (pa.dot(ba) / ba.dot(ba).max(1e-10)).clamp(0.0, 1.0);
305 (pa - ba * h).length() - r
306 }
307 _ => f32::MAX,
308 }
309}
310
311#[inline]
313fn smin_implicit(a: f32, b: f32, k: f32) -> f32 {
314 let h = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
315 a * h + b * (1.0 - h) - k * h * (1.0 - h)
316}
317
318fn eval_implicit_sdf(p: glam::Vec3, item: &GpuImplicitPickItem) -> f32 {
320 use crate::resources::ImplicitBlendMode;
321 let mut d = item.max_distance;
322 for (i, prim) in item.primitives.iter().enumerate() {
323 let pd = eval_implicit_primitive(p, prim);
324 match item.blend_mode {
325 ImplicitBlendMode::Union => {
326 d = d.min(pd);
327 }
328 ImplicitBlendMode::SmoothUnion => {
329 let k = if prim.blend > 0.0 { prim.blend } else { 1e-5 };
330 d = smin_implicit(d, pd, k);
331 }
332 ImplicitBlendMode::Intersection => {
333 if i == 0 {
334 d = pd;
335 } else {
336 d = d.max(pd);
337 }
338 }
339 }
340 }
341 d
342}
343
344fn pick_implicit_sdf(
346 ray_origin: glam::Vec3,
347 ray_dir: glam::Vec3,
348 item: &GpuImplicitPickItem,
349) -> Option<(f32, glam::Vec3)> {
350 let max_steps = item.max_steps.min(512) as usize;
351 let scale = item.step_scale.clamp(0.01, 1.0);
352 let hit_thr = item.hit_threshold;
353 let max_dist = item.max_distance;
354 let min_step = hit_thr * 0.5;
355
356 let mut t = 0.0f32;
357 for _ in 0..max_steps {
358 if t > max_dist {
359 break;
360 }
361 let p = ray_origin + ray_dir * t;
362 let d = eval_implicit_sdf(p, item);
363 if d < hit_thr {
364 return Some((t, p));
365 }
366 t += d.abs().max(min_step) * scale;
367 }
368 None
369}
370
371fn ray_aabb_slab(
377 ray_orig: glam::Vec3,
378 ray_dir: glam::Vec3,
379 bbox_min: glam::Vec3,
380 bbox_max: glam::Vec3,
381) -> Option<(f32, f32)> {
382 let inv = glam::Vec3::new(
384 if ray_dir.x.abs() > 1e-30 {
385 1.0 / ray_dir.x
386 } else {
387 f32::INFINITY * ray_dir.x.signum()
388 },
389 if ray_dir.y.abs() > 1e-30 {
390 1.0 / ray_dir.y
391 } else {
392 f32::INFINITY * ray_dir.y.signum()
393 },
394 if ray_dir.z.abs() > 1e-30 {
395 1.0 / ray_dir.z
396 } else {
397 f32::INFINITY * ray_dir.z.signum()
398 },
399 );
400 let t1 = (bbox_min - ray_orig) * inv;
401 let t2 = (bbox_max - ray_orig) * inv;
402 let tmin = t1.min(t2);
403 let tmax = t1.max(t2);
404 let t_enter = tmin.x.max(tmin.y).max(tmin.z);
405 let t_exit = tmax.x.min(tmax.y).min(tmax.z);
406 if t_enter <= t_exit && t_exit >= 0.0 {
407 Some((t_enter, t_exit))
408 } else {
409 None
410 }
411}
412
413fn bisect_mc_crossing(
415 ray_orig: glam::Vec3,
416 ray_dir: glam::Vec3,
417 vol: &crate::geometry::marching_cubes::VolumeData,
418 isovalue: f32,
419 mut t_lo: f32,
420 mut t_hi: f32,
421) -> f32 {
422 let s0 = crate::geometry::marching_cubes::trilinear_sample(
423 vol,
424 (ray_orig + ray_dir * t_lo).to_array(),
425 ) - isovalue;
426 let mut lo_sign = s0 < 0.0;
427 for _ in 0..8 {
428 let mid = (t_lo + t_hi) * 0.5;
429 let s = crate::geometry::marching_cubes::trilinear_sample(
430 vol,
431 (ray_orig + ray_dir * mid).to_array(),
432 ) - isovalue;
433 if (s < 0.0) == lo_sign {
434 t_lo = mid;
435 } else {
436 t_hi = mid;
437 lo_sign = !lo_sign;
438 }
439 }
440 (t_lo + t_hi) * 0.5
441}
442
443fn pick_mc_volume(
448 ray_orig: glam::Vec3,
449 ray_dir: glam::Vec3,
450 item: &GpuMcPickItem,
451) -> Option<(f32, glam::Vec3)> {
452 use crate::geometry::marching_cubes::trilinear_sample;
453
454 let vol = &item.volume_data;
455 let isovalue = item.isovalue;
456 let [nx, ny, nz] = vol.dims;
457 let origin = glam::Vec3::from(vol.origin);
458 let spacing = glam::Vec3::from(vol.spacing);
459 let extent = spacing * glam::Vec3::new(nx as f32, ny as f32, nz as f32);
460
461 let (t_enter, t_exit) = ray_aabb_slab(ray_orig, ray_dir, origin, origin + extent)?;
462 let t_start = t_enter.max(0.0);
463 if t_start >= t_exit {
464 return None;
465 }
466
467 let step = spacing.min_element() * 0.5;
469 let mut t = t_start;
470 let mut prev = trilinear_sample(vol, (ray_orig + ray_dir * t).to_array()) - isovalue;
471
472 loop {
473 t += step;
474 if t > t_exit {
475 break;
476 }
477 let p = ray_orig + ray_dir * t;
478 let cur = trilinear_sample(vol, p.to_array()) - isovalue;
479 if prev * cur <= 0.0 {
480 let t_hit = bisect_mc_crossing(ray_orig, ray_dir, vol, isovalue, t - step, t);
482 let world_pos = ray_orig + ray_dir * t_hit;
483 return Some((t_hit, world_pos));
484 }
485 prev = cur;
486 }
487 None
488}
489
490#[derive(Clone, Debug, Default)]
496pub struct PickRectResult {
497 pub objects: Vec<u64>,
501 pub elements: Vec<(u64, crate::interaction::sub_object::SubObjectRef)>,
507}
508
509impl PickRectResult {
510 pub fn is_empty(&self) -> bool {
512 self.objects.is_empty() && self.elements.is_empty()
513 }
514}
515
516impl ViewportRenderer {
517 pub fn pick(
541 &self,
542 click_pos: glam::Vec2,
543 viewport_size: glam::Vec2,
544 view_proj: glam::Mat4,
545 mask: crate::interaction::pick_mask::PickMask,
546 ) -> Option<crate::interaction::picking::PickHit> {
547 use crate::interaction::pick_mask::PickMask;
548 use crate::interaction::picking::{
549 PickHit, pick_gaussian_splat_cpu, pick_point_cloud_cpu,
550 pick_transparent_volume_mesh_cpu, pick_volume_cpu, screen_to_ray,
551 };
552 use crate::interaction::sub_object::SubObjectRef;
553 use parry3d::math::{Pose, Vector};
554 use parry3d::query::{Ray, RayCast};
555
556 if viewport_size.x <= 0.0 || viewport_size.y <= 0.0 {
557 return None;
558 }
559
560 let view_proj_inv = view_proj.inverse();
561 let (ray_origin, ray_dir) = screen_to_ray(click_pos, viewport_size, view_proj_inv);
562
563 let wants_face = mask.intersects(PickMask::FACE);
564 let wants_vertex = mask.intersects(PickMask::VERTEX);
565 let wants_cell = mask.intersects(PickMask::CELL);
566 let wants_cloud = mask.intersects(PickMask::CLOUD_POINT);
567 let wants_splat = mask.intersects(PickMask::SPLAT);
568 let wants_object = mask.intersects(PickMask::OBJECT);
569 let wants_mesh_sub = wants_face || wants_vertex || mask.intersects(PickMask::EDGE);
570
571 let mut best: Option<(f32, PickHit)> = None;
573
574 let mut consider = |toi: f32, hit: PickHit| {
575 if best.as_ref().map_or(true, |(bt, _)| toi < *bt) {
576 best = Some((toi, hit));
577 }
578 };
579
580 let vm_cell_map: std::collections::HashMap<u64, &[u32]> = self
583 .pick_volume_mesh_items
584 .iter()
585 .filter(|item| item.settings.pick_id != PickId::NONE && !item.face_to_cell.is_empty())
586 .map(|item| (item.settings.pick_id.0, item.face_to_cell.as_slice()))
587 .collect();
588
589 if wants_mesh_sub || wants_cell || wants_object {
591 let ray = Ray::new(
592 Vector::new(ray_origin.x, ray_origin.y, ray_origin.z),
593 Vector::new(ray_dir.x, ray_dir.y, ray_dir.z),
594 );
595 for item in &self.pick_scene_items {
596 if item.settings.hidden || item.settings.pick_id == PickId::NONE {
597 continue;
598 }
599 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
600 continue;
601 };
602 let (Some(positions), Some(indices)) = (&mesh.cpu_positions, &mesh.cpu_indices)
603 else {
604 continue;
605 };
606
607 let model = glam::Mat4::from_cols_array_2d(&item.model);
608
609 let verts: Vec<Vector> = positions
612 .iter()
613 .map(|p| {
614 let wp = model.transform_point3(glam::Vec3::from(*p));
615 Vector::new(wp.x, wp.y, wp.z)
616 })
617 .collect();
618
619 let tri_indices: Vec<[u32; 3]> = indices
620 .chunks(3)
621 .filter(|c| c.len() == 3)
622 .map(|c| [c[0], c[1], c[2]])
623 .collect();
624
625 if tri_indices.is_empty() {
626 continue;
627 }
628
629 match parry3d::shape::TriMesh::new(verts, tri_indices) {
630 Ok(trimesh) => {
631 let identity = Pose::identity();
633 let Some(intersection) =
634 trimesh.cast_ray_and_get_normal(&identity, &ray, f32::MAX, true)
635 else {
636 continue;
637 };
638 let toi = intersection.time_of_impact;
639 let world_pos = ray_origin + ray_dir * toi;
640 let normal = intersection.normal;
641
642 let feature_sub = SubObjectRef::from_feature_id(intersection.feature);
643
644 let sub_object = if wants_face {
645 feature_sub
646 } else if wants_cell {
647 if let Some(f2c) = vm_cell_map.get(&item.settings.pick_id.0) {
649 match feature_sub {
650 Some(SubObjectRef::Face(face_raw)) => {
651 let n_tri = indices.len() / 3;
652 let face = if (face_raw as usize) >= n_tri {
653 face_raw as usize - n_tri
654 } else {
655 face_raw as usize
656 };
657 f2c.get(face).map(|&ci| SubObjectRef::Cell(ci))
658 }
659 other => other,
660 }
661 } else if wants_vertex {
662 match feature_sub {
666 Some(SubObjectRef::Face(face_raw)) => {
667 let n_tri = indices.len() / 3;
668 let face = if (face_raw as usize) >= n_tri {
669 face_raw as usize - n_tri
670 } else {
671 face_raw as usize
672 };
673 if face * 3 + 2 < indices.len() {
674 let vis = [
675 indices[face * 3] as usize,
676 indices[face * 3 + 1] as usize,
677 indices[face * 3 + 2] as usize,
678 ];
679 let (best_vi, _) = vis
680 .iter()
681 .map(|&i| {
682 let p = model.transform_point3(
683 glam::Vec3::from(positions[i]),
684 );
685 (i, p.distance(world_pos))
686 })
687 .fold((vis[0], f32::MAX), |acc, (i, d)| {
688 if d < acc.1 { (i, d) } else { acc }
689 });
690 Some(SubObjectRef::Vertex(best_vi as u32))
691 } else {
692 None
693 }
694 }
695 other => other,
696 }
697 } else {
698 None
700 }
701 } else if wants_vertex {
702 match feature_sub {
704 Some(SubObjectRef::Face(face_raw)) => {
705 let n_tri = indices.len() / 3;
706 let face = if (face_raw as usize) >= n_tri {
707 face_raw as usize - n_tri
708 } else {
709 face_raw as usize
710 };
711 if face * 3 + 2 < indices.len() {
712 let vis = [
713 indices[face * 3] as usize,
714 indices[face * 3 + 1] as usize,
715 indices[face * 3 + 2] as usize,
716 ];
717 let (best_vi, _) = vis
718 .iter()
719 .map(|&i| {
720 let p = model.transform_point3(glam::Vec3::from(
721 positions[i],
722 ));
723 (i, p.distance(world_pos))
724 })
725 .fold((vis[0], f32::MAX), |acc, (i, d)| {
726 if d < acc.1 { (i, d) } else { acc }
727 });
728 Some(SubObjectRef::Vertex(best_vi as u32))
729 } else {
730 None
731 }
732 }
733 other => other,
734 }
735 } else {
736 None
738 };
739
740 if sub_object.is_some() || wants_object {
746 #[allow(deprecated)]
747 let hit = PickHit {
748 id: item.settings.pick_id.0,
749 sub_object,
750 world_pos,
751 normal,
752 triangle_index: u32::MAX,
753 point_index: None,
754 scalar_value: None,
755 };
756 consider(toi, hit);
757 }
758 }
759 Err(e) => {
760 tracing::warn!(
761 pick_id = item.settings.pick_id.0,
762 error = %e,
763 "TriMesh build failed in renderer.pick()"
764 );
765 }
766 }
767 }
768 }
769
770 if wants_object {
776 for item in &self.pick_scatter_volume_items {
777 if item.settings.hidden || item.settings.pick_id == PickId::NONE {
778 continue;
779 }
780 if let Some((t_enter, _)) = crate::scene::scatter_volume::ray_intersect(
781 &item.volume.shape,
782 ray_origin,
783 ray_dir,
784 ) {
785 let world_pos = ray_origin + ray_dir * t_enter;
786 let normal = (world_pos
787 - match item.volume.shape {
788 crate::scene::scatter_volume::ScatterShape::Box(b) => {
789 (b.min + b.max) * 0.5
790 }
791 crate::scene::scatter_volume::ScatterShape::Sphere {
792 center, ..
793 } => glam::Vec3::from(center),
794 })
795 .try_normalize()
796 .unwrap_or(glam::Vec3::Z);
797 consider(
798 t_enter,
799 PickHit::object_hit(item.settings.pick_id.0, world_pos, normal),
800 );
801 }
802 }
803 }
804
805 if wants_cell || wants_object {
809 for item in &self.pick_volume_mesh_items {
810 if item.settings.pick_id == PickId::NONE
811 || item.transparency.is_none()
812 {
813 continue;
814 }
815 let Some(data) = item.volume_mesh_data.as_deref() else {
816 continue;
817 };
818 let model = glam::Mat4::from_cols_array_2d(&item.model);
819 if let Some(mut hit) = pick_transparent_volume_mesh_cpu(
820 ray_origin,
821 ray_dir,
822 item.settings.pick_id.0,
823 model,
824 data,
825 ) {
826 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
827 if !wants_cell {
828 hit.sub_object = None;
829 }
830 consider(toi, hit);
831 }
832 }
833 }
834
835 if wants_cloud || wants_object {
837 for item in &self.pick_point_cloud_items {
838 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
839 continue;
840 }
841 let radius_px = item.point_size.max(4.0);
842 if let Some(mut hit) = pick_point_cloud_cpu(
843 click_pos,
844 item.settings.pick_id.0,
845 item,
846 view_proj,
847 viewport_size,
848 radius_px,
849 ) {
850 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
851 if !wants_cloud {
852 hit.sub_object = None;
853 }
854 consider(toi, hit);
855 }
856 }
857 }
858
859 let wants_voxel = mask.intersects(PickMask::VOXEL);
861 if wants_voxel || wants_object {
862 for item in &self.pick_volume_items {
863 if item.settings.pick_id == PickId::NONE {
864 continue;
865 }
866 let Some(vol_data) = item.volume_data.as_deref() else {
867 continue;
868 };
869 if let Some(mut hit) =
870 pick_volume_cpu(ray_origin, ray_dir, item.settings.pick_id.0, item, vol_data)
871 {
872 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
873 if !wants_voxel {
874 hit.sub_object = None;
875 }
876 consider(toi, hit);
877 }
878 }
879 }
880
881 if wants_splat || wants_object {
883 for item in &self.pick_splat_items {
884 if item.settings.pick_id == PickId::NONE {
885 continue;
886 }
887 let Some(gpu_set) = self.resources.gaussian_splat_store.get(item.id.0) else {
888 continue;
889 };
890 if gpu_set.cpu_positions.is_empty() {
891 continue;
892 }
893 let model = glam::Mat4::from_cols_array_2d(&item.model);
894 let mean_max_scale: f32 = if gpu_set.cpu_scales.is_empty() {
897 0.05
898 } else {
899 gpu_set
900 .cpu_scales
901 .iter()
902 .map(|s| s[0].max(s[1]).max(s[2]))
903 .sum::<f32>()
904 / gpu_set.cpu_scales.len() as f32
905 };
906 let world_radius = mean_max_scale * 3.0;
907 let center_w = model.transform_point3(glam::Vec3::ZERO);
908 let p0_clip = view_proj * center_w.extend(1.0);
909 let p1_clip = view_proj * (center_w + glam::Vec3::X * world_radius).extend(1.0);
910 let radius_px = if p0_clip.w.abs() > 1e-6 && p1_clip.w.abs() > 1e-6 {
911 let p0_ndc = glam::Vec2::new(p0_clip.x, p0_clip.y) / p0_clip.w;
912 let p1_ndc = glam::Vec2::new(p1_clip.x, p1_clip.y) / p1_clip.w;
913 ((p1_ndc - p0_ndc).length() * 0.5 * viewport_size.x.max(viewport_size.y))
914 .max(4.0)
915 } else {
916 world_radius * 100.0
917 };
918 if let Some(mut hit) = pick_gaussian_splat_cpu(
919 click_pos,
920 item.settings.pick_id.0,
921 &gpu_set.cpu_positions,
922 model,
923 view_proj,
924 viewport_size,
925 radius_px,
926 ) {
927 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
929 if wants_splat {
930 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
931 hit.sub_object = Some(SubObjectRef::Splat(idx));
932 }
933 } else {
934 hit.sub_object = None;
935 }
936 consider(toi, hit);
937 }
938 }
939 }
940
941 let wants_instance = mask.intersects(PickMask::INSTANCE);
943 if wants_instance || wants_object {
944 let instance_radius_px = |world_center: glam::Vec3, world_r: f32| -> f32 {
948 let p0 = view_proj * world_center.extend(1.0);
949 let p1 = view_proj * (world_center + glam::Vec3::X * world_r).extend(1.0);
950 if p0.w.abs() > 1e-6 && p1.w.abs() > 1e-6 {
951 let n0 = glam::Vec2::new(p0.x, p0.y) / p0.w;
952 let n1 = glam::Vec2::new(p1.x, p1.y) / p1.w;
953 ((n1 - n0).length() * 0.5 * viewport_size.x.max(viewport_size.y)).max(4.0)
954 } else {
955 (world_r * 100.0_f32).max(4.0)
956 }
957 };
958
959 for item in &self.pick_glyph_items {
961 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
962 continue;
963 }
964 let model = glam::Mat4::from_cols_array_2d(&item.model);
965 let full_len = if item.scale_by_magnitude && !item.vectors.is_empty() {
966 let mean_mag = item
967 .vectors
968 .iter()
969 .map(|v| glam::Vec3::from(*v).length())
970 .sum::<f32>()
971 / item.vectors.len() as f32;
972 (mean_mag * item.scale).max(0.01)
973 } else {
974 item.scale.max(0.01)
975 };
976 let has_vecs = item.vectors.len() == item.positions.len();
980 let midpoints: Vec<[f32; 3]> = item
981 .positions
982 .iter()
983 .enumerate()
984 .map(|(i, pos)| {
985 if has_vecs {
986 let p = glam::Vec3::from(*pos);
987 let v = glam::Vec3::from(item.vectors[i]);
988 let len = if item.scale_by_magnitude {
989 v.length() * item.scale
990 } else {
991 item.scale
992 };
993 (p + v.normalize_or_zero() * len * 0.5).to_array()
994 } else {
995 *pos
996 }
997 })
998 .collect();
999 let n = midpoints.len() as f32;
1000 let centroid = model.transform_point3(
1001 midpoints
1002 .iter()
1003 .map(|p| glam::Vec3::from(*p))
1004 .sum::<glam::Vec3>()
1005 / n,
1006 );
1007 let radius_px = instance_radius_px(centroid, full_len * 0.5);
1008 if let Some(mut hit) = pick_gaussian_splat_cpu(
1009 click_pos,
1010 item.settings.pick_id.0,
1011 &midpoints,
1012 model,
1013 view_proj,
1014 viewport_size,
1015 radius_px,
1016 ) {
1017 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1019 if let Some(base) = item.positions.get(idx as usize) {
1020 hit.world_pos = model.transform_point3(glam::Vec3::from(*base));
1021 }
1022 if wants_instance {
1023 hit.sub_object = Some(SubObjectRef::Instance(idx));
1024 } else {
1025 hit.sub_object = None;
1026 }
1027 }
1028 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1029 consider(toi, hit);
1030 }
1031 }
1032
1033 for item in &self.pick_tensor_glyph_items {
1035 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1036 continue;
1037 }
1038 let model = glam::Mat4::from_cols_array_2d(&item.model);
1039 let world_r = if !item.eigenvalues.is_empty() {
1043 let max_ev = item
1044 .eigenvalues
1045 .iter()
1046 .map(|ev| ev[0].abs().max(ev[1].abs()).max(ev[2].abs()))
1047 .fold(0.0_f32, f32::max);
1048 (max_ev * item.scale).max(0.01)
1049 } else {
1050 item.scale.max(0.01)
1051 };
1052 let n = item.positions.len() as f32;
1053 let centroid = model.transform_point3(
1054 item.positions
1055 .iter()
1056 .map(|p| glam::Vec3::from(*p))
1057 .sum::<glam::Vec3>()
1058 / n,
1059 );
1060 let radius_px = instance_radius_px(centroid, world_r);
1061 if let Some(mut hit) = pick_gaussian_splat_cpu(
1062 click_pos,
1063 item.settings.pick_id.0,
1064 &item.positions,
1065 model,
1066 view_proj,
1067 viewport_size,
1068 radius_px,
1069 ) {
1070 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1071 if wants_instance {
1072 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1073 hit.sub_object = Some(SubObjectRef::Instance(idx));
1074 }
1075 } else {
1076 hit.sub_object = None;
1077 }
1078 consider(toi, hit);
1079 }
1080 }
1081
1082 for item in &self.pick_sprite_items {
1084 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1085 continue;
1086 }
1087 let model = glam::Mat4::from_cols_array_2d(&item.model);
1088 let radius_px = match item.size_mode {
1089 SpriteSizeMode::ScreenSpace => (item.default_size * 0.5).max(4.0),
1090 SpriteSizeMode::WorldSpace => {
1091 let n = item.positions.len() as f32;
1092 let centroid = model.transform_point3(
1093 item.positions
1094 .iter()
1095 .map(|p| glam::Vec3::from(*p))
1096 .sum::<glam::Vec3>()
1097 / n,
1098 );
1099 instance_radius_px(centroid, (item.default_size * 0.5).max(0.01))
1100 }
1101 };
1102 if let Some(mut hit) = pick_gaussian_splat_cpu(
1103 click_pos,
1104 item.settings.pick_id.0,
1105 &item.positions,
1106 model,
1107 view_proj,
1108 viewport_size,
1109 radius_px,
1110 ) {
1111 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1112 if wants_instance {
1113 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1114 hit.sub_object = Some(SubObjectRef::Instance(idx));
1115 }
1116 } else {
1117 hit.sub_object = None;
1118 }
1119 consider(toi, hit);
1120 }
1121 }
1122 }
1123
1124 let wants_poly_node = mask.intersects(PickMask::POLY_NODE);
1126 let wants_strip = mask.intersects(PickMask::STRIP);
1127 if wants_poly_node || wants_strip || wants_object {
1128 for item in &self.pick_polyline_items {
1129 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1130 continue;
1131 }
1132 let radius_px = (item.line_width + 4.0).max(8.0);
1133 if let Some(mut hit) = pick_gaussian_splat_cpu(
1134 click_pos,
1135 item.settings.pick_id.0,
1136 &item.positions,
1137 glam::Mat4::IDENTITY,
1138 view_proj,
1139 viewport_size,
1140 radius_px,
1141 ) {
1142 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1143 if wants_poly_node {
1144 } else if wants_strip {
1146 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1147 hit.sub_object = Some(SubObjectRef::Strip(strip_for_node(
1148 idx,
1149 &item.strip_lengths,
1150 )));
1151 }
1152 } else {
1153 hit.sub_object = None;
1154 }
1155 consider(toi, hit);
1156 }
1157 }
1158 }
1159
1160 let wants_segment = mask.intersects(PickMask::SEGMENT);
1164 if wants_segment || wants_strip || wants_object {
1165 for item in &self.pick_polyline_items {
1166 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1167 continue;
1168 }
1169 let threshold_px = (item.line_width / 2.0 + 4.0).max(4.0);
1171 let Some((seg_idx, world_pos)) = pick_closest_polyline_segment(
1172 click_pos,
1173 viewport_size,
1174 view_proj,
1175 &item.positions,
1176 &item.strip_lengths,
1177 threshold_px,
1178 ) else {
1179 continue;
1180 };
1181 let toi = (world_pos - ray_origin).dot(ray_dir).max(0.0);
1182 let sub_object = if wants_segment {
1183 Some(SubObjectRef::Segment(seg_idx))
1184 } else if wants_strip {
1185 Some(SubObjectRef::Strip(strip_for_segment(
1186 seg_idx,
1187 &item.strip_lengths,
1188 )))
1189 } else {
1190 None
1191 };
1192 #[allow(deprecated)]
1193 let hit = PickHit {
1194 id: item.settings.pick_id.0,
1195 sub_object,
1196 world_pos,
1197 normal: glam::Vec3::Z,
1198 triangle_index: u32::MAX,
1199 point_index: None,
1200 scalar_value: None,
1201 };
1202 consider(toi, hit);
1203 }
1204 }
1205
1206 if wants_poly_node || wants_segment || wants_strip || wants_object {
1213 let world_r_to_px = |ref_world: glam::Vec3, world_r: f32| -> f32 {
1215 let p0 = view_proj * ref_world.extend(1.0);
1216 let p1 = view_proj * (ref_world + glam::Vec3::X * world_r).extend(1.0);
1217 if p0.w.abs() > 1e-6 && p1.w.abs() > 1e-6 {
1218 let n0 = glam::Vec2::new(p0.x, p0.y) / p0.w;
1219 let n1 = glam::Vec2::new(p1.x, p1.y) / p1.w;
1220 ((n1 - n0).length() * 0.5 * viewport_size.x.max(viewport_size.y)).max(4.0)
1221 } else {
1222 (world_r * 100.0_f32).max(4.0)
1223 }
1224 };
1225
1226 if wants_poly_node || wants_strip || wants_object {
1228 for item in &self.pick_streamtube_items {
1229 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1230 continue;
1231 }
1232 let ref_pos = glam::Vec3::from(item.positions[0]);
1233 let radius_px = world_r_to_px(ref_pos, item.radius.max(0.01)).max(8.0);
1234 if let Some(mut hit) = pick_gaussian_splat_cpu(
1235 click_pos,
1236 item.settings.pick_id.0,
1237 &item.positions,
1238 glam::Mat4::IDENTITY,
1239 view_proj,
1240 viewport_size,
1241 radius_px,
1242 ) {
1243 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1244 if wants_poly_node {
1245 } else if wants_strip {
1247 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1248 hit.sub_object = Some(SubObjectRef::Strip(strip_for_node(
1249 idx,
1250 &item.strip_lengths,
1251 )));
1252 }
1253 } else {
1254 hit.sub_object = None;
1255 }
1256 consider(toi, hit);
1257 }
1258 }
1259 for item in &self.pick_tube_items {
1260 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1261 continue;
1262 }
1263 let ref_pos = glam::Vec3::from(item.positions[0]);
1264 let max_r = item
1265 .radius_attribute
1266 .as_ref()
1267 .and_then(|ra| ra.iter().copied().reduce(f32::max))
1268 .unwrap_or(0.0)
1269 .max(item.radius)
1270 .max(0.01);
1271 let radius_px = world_r_to_px(ref_pos, max_r).max(8.0);
1272 if let Some(mut hit) = pick_gaussian_splat_cpu(
1273 click_pos,
1274 item.settings.pick_id.0,
1275 &item.positions,
1276 glam::Mat4::IDENTITY,
1277 view_proj,
1278 viewport_size,
1279 radius_px,
1280 ) {
1281 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1282 if wants_poly_node {
1283 } else if wants_strip {
1285 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1286 hit.sub_object = Some(SubObjectRef::Strip(strip_for_node(
1287 idx,
1288 &item.strip_lengths,
1289 )));
1290 }
1291 } else {
1292 hit.sub_object = None;
1293 }
1294 consider(toi, hit);
1295 }
1296 }
1297 for item in &self.pick_ribbon_items {
1298 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1299 continue;
1300 }
1301 let ref_pos = glam::Vec3::from(item.positions[0]);
1302 let radius_px = world_r_to_px(ref_pos, item.width * 0.5).max(8.0);
1303 if let Some(mut hit) = pick_gaussian_splat_cpu(
1304 click_pos,
1305 item.settings.pick_id.0,
1306 &item.positions,
1307 glam::Mat4::IDENTITY,
1308 view_proj,
1309 viewport_size,
1310 radius_px,
1311 ) {
1312 let toi = (hit.world_pos - ray_origin).dot(ray_dir).max(0.0);
1313 if wants_poly_node {
1314 } else if wants_strip {
1316 if let Some(SubObjectRef::Point(idx)) = hit.sub_object {
1317 hit.sub_object = Some(SubObjectRef::Strip(strip_for_node(
1318 idx,
1319 &item.strip_lengths,
1320 )));
1321 }
1322 } else {
1323 hit.sub_object = None;
1324 }
1325 consider(toi, hit);
1326 }
1327 }
1328 }
1329
1330 if wants_segment || wants_strip || wants_object {
1332 for item in &self.pick_streamtube_items {
1335 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1336 continue;
1337 }
1338 let ref_pos = glam::Vec3::from(item.positions[0]);
1339 let threshold_px = world_r_to_px(ref_pos, item.radius.max(0.01));
1340 let Some((seg_idx, world_pos)) = pick_closest_polyline_segment(
1341 click_pos,
1342 viewport_size,
1343 view_proj,
1344 &item.positions,
1345 &item.strip_lengths,
1346 threshold_px,
1347 ) else {
1348 continue;
1349 };
1350 let toi = (world_pos - ray_origin).dot(ray_dir).max(0.0);
1351 let sub_object = if wants_segment {
1352 Some(SubObjectRef::Segment(seg_idx))
1353 } else if wants_strip {
1354 Some(SubObjectRef::Strip(strip_for_segment(
1355 seg_idx,
1356 &item.strip_lengths,
1357 )))
1358 } else {
1359 None
1360 };
1361 #[allow(deprecated)]
1362 consider(
1363 toi,
1364 PickHit {
1365 id: item.settings.pick_id.0,
1366 sub_object,
1367 world_pos,
1368 normal: glam::Vec3::Z,
1369 triangle_index: u32::MAX,
1370 point_index: None,
1371 scalar_value: None,
1372 },
1373 );
1374 }
1375
1376 for item in &self.pick_tube_items {
1379 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1380 continue;
1381 }
1382 let ref_pos = glam::Vec3::from(item.positions[0]);
1383 let max_r = item
1384 .radius_attribute
1385 .as_ref()
1386 .and_then(|ra| ra.iter().copied().reduce(f32::max))
1387 .unwrap_or(0.0)
1388 .max(item.radius)
1389 .max(0.01);
1390 let threshold_px = world_r_to_px(ref_pos, max_r);
1391 let Some((seg_idx, world_pos)) = pick_closest_polyline_segment(
1392 click_pos,
1393 viewport_size,
1394 view_proj,
1395 &item.positions,
1396 &item.strip_lengths,
1397 threshold_px,
1398 ) else {
1399 continue;
1400 };
1401 let toi = (world_pos - ray_origin).dot(ray_dir).max(0.0);
1402 let sub_object = if wants_segment {
1403 Some(SubObjectRef::Segment(seg_idx))
1404 } else if wants_strip {
1405 Some(SubObjectRef::Strip(strip_for_segment(
1406 seg_idx,
1407 &item.strip_lengths,
1408 )))
1409 } else {
1410 None
1411 };
1412 #[allow(deprecated)]
1413 consider(
1414 toi,
1415 PickHit {
1416 id: item.settings.pick_id.0,
1417 sub_object,
1418 world_pos,
1419 normal: glam::Vec3::Z,
1420 triangle_index: u32::MAX,
1421 point_index: None,
1422 scalar_value: None,
1423 },
1424 );
1425 }
1426
1427 for item in &self.pick_ribbon_items {
1430 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1431 continue;
1432 }
1433 let frames = ribbon_lateral_frames(
1434 &item.positions,
1435 &item.strip_lengths,
1436 item.width,
1437 item.width_attribute.as_deref(),
1438 item.twist_attribute.as_deref(),
1439 );
1440
1441 let single;
1442 let strips: &[u32] = if item.strip_lengths.is_empty() {
1443 single = [item.positions.len() as u32];
1444 &single
1445 } else {
1446 &item.strip_lengths
1447 };
1448
1449 let mut best_t = f32::MAX;
1450 let mut best_seg: Option<(u32, glam::Vec3)> = None;
1451 let mut node_off = 0usize;
1452 let mut seg_off = 0u32;
1453
1454 for &slen in strips {
1455 let slen = slen as usize;
1456 for k in 0..slen.saturating_sub(1) {
1457 let ia = node_off + k;
1458 let ib = node_off + k + 1;
1459 let pa = glam::Vec3::from(item.positions[ia]);
1460 let pb = glam::Vec3::from(item.positions[ib]);
1461 let (ua, wa) = frames[ia];
1462 let (ub, wb) = frames[ib];
1463 let c0 = pa + ua * wa; let c1 = pa - ua * wa; let c2 = pb + ub * wb; let c3 = pb - ub * wb; let t = ray_triangle(ray_origin, ray_dir, c0, c1, c2)
1470 .or_else(|| ray_triangle(ray_origin, ray_dir, c1, c3, c2))
1471 .or_else(|| ray_triangle(ray_origin, ray_dir, c2, c1, c0))
1472 .or_else(|| ray_triangle(ray_origin, ray_dir, c2, c3, c1));
1473 if let Some(t) = t {
1474 if t < best_t {
1475 best_t = t;
1476 best_seg = Some((seg_off + k as u32, ray_origin + ray_dir * t));
1477 }
1478 }
1479 }
1480 seg_off += slen.saturating_sub(1) as u32;
1481 node_off += slen;
1482 }
1483
1484 if let Some((seg_idx, world_pos)) = best_seg {
1485 let sub_object = if wants_segment {
1486 Some(SubObjectRef::Segment(seg_idx))
1487 } else if wants_strip {
1488 Some(SubObjectRef::Strip(strip_for_segment(
1489 seg_idx,
1490 &item.strip_lengths,
1491 )))
1492 } else {
1493 None
1494 };
1495 #[allow(deprecated)]
1496 consider(
1497 best_t,
1498 PickHit {
1499 id: item.settings.pick_id.0,
1500 sub_object,
1501 world_pos,
1502 normal: glam::Vec3::Z,
1503 triangle_index: u32::MAX,
1504 point_index: None,
1505 scalar_value: None,
1506 },
1507 );
1508 }
1509 }
1510 }
1511 }
1512
1513 if wants_object {
1515 for item in &self.pick_image_slice_items {
1517 if item.settings.pick_id == PickId::NONE {
1518 continue;
1519 }
1520 let [bmin, bmax] = [item.bbox_min, item.bbox_max];
1521 let t = item.offset;
1522 let (axis_idx, plane_pos) = match item.axis {
1524 SliceAxis::X => (0usize, bmin[0] + t * (bmax[0] - bmin[0])),
1525 SliceAxis::Y => (1usize, bmin[1] + t * (bmax[1] - bmin[1])),
1526 SliceAxis::Z => (2usize, bmin[2] + t * (bmax[2] - bmin[2])),
1527 };
1528 let plane_n = {
1529 let mut n = glam::Vec3::ZERO;
1530 n[axis_idx] = 1.0;
1531 n
1532 };
1533 let denom = plane_n.dot(ray_dir);
1534 if denom.abs() < 1e-6 {
1535 continue;
1536 }
1537 let toi = (plane_pos - ray_origin[axis_idx]) / denom;
1538 if toi <= 0.0 {
1539 continue;
1540 }
1541 let hit_pos = ray_origin + ray_dir * toi;
1542 let in_bounds = (0..3)
1544 .filter(|&i| i != axis_idx)
1545 .all(|i| hit_pos[i] >= bmin[i] - 1e-4 && hit_pos[i] <= bmax[i] + 1e-4);
1546 if in_bounds {
1547 #[allow(deprecated)]
1548 consider(
1549 toi,
1550 PickHit {
1551 id: item.settings.pick_id.0,
1552 sub_object: None,
1553 world_pos: hit_pos,
1554 normal: plane_n,
1555 triangle_index: u32::MAX,
1556 point_index: None,
1557 scalar_value: None,
1558 },
1559 );
1560 }
1561 }
1562
1563 for item in &self.pick_volume_surface_slice_items {
1565 if item.settings.pick_id == PickId::NONE {
1566 continue;
1567 }
1568 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
1569 continue;
1570 };
1571 let (Some(positions), Some(indices)) = (&mesh.cpu_positions, &mesh.cpu_indices)
1572 else {
1573 continue;
1574 };
1575 let model = glam::Mat4::from_cols_array_2d(&item.model);
1576 let verts: Vec<parry3d::math::Vector> = positions
1577 .iter()
1578 .map(|p| {
1579 let wp = model.transform_point3(glam::Vec3::from(*p));
1580 parry3d::math::Vector::new(wp.x, wp.y, wp.z)
1581 })
1582 .collect();
1583 let tri_indices: Vec<[u32; 3]> = indices
1584 .chunks(3)
1585 .filter(|c| c.len() == 3)
1586 .map(|c| [c[0], c[1], c[2]])
1587 .collect();
1588 if tri_indices.is_empty() {
1589 continue;
1590 }
1591 let ray = parry3d::query::Ray::new(
1592 parry3d::math::Vector::new(ray_origin.x, ray_origin.y, ray_origin.z),
1593 parry3d::math::Vector::new(ray_dir.x, ray_dir.y, ray_dir.z),
1594 );
1595 if let Ok(trimesh) = parry3d::shape::TriMesh::new(verts, tri_indices) {
1596 use parry3d::query::RayCast;
1597 if let Some(hit) = trimesh.cast_ray_and_get_normal(
1598 &parry3d::math::Pose::identity(),
1599 &ray,
1600 f32::MAX,
1601 true,
1602 ) {
1603 let world_pos = ray_origin + ray_dir * hit.time_of_impact;
1604 let n = hit.normal;
1605 #[allow(deprecated)]
1606 consider(
1607 hit.time_of_impact,
1608 PickHit {
1609 id: item.settings.pick_id.0,
1610 sub_object: None,
1611 world_pos,
1612 normal: glam::Vec3::new(n.x, n.y, n.z),
1613 triangle_index: u32::MAX,
1614 point_index: None,
1615 scalar_value: None,
1616 },
1617 );
1618 }
1619 }
1620 }
1621
1622 for item in &self.pick_screen_image_items {
1624 if item.settings.pick_id == PickId::NONE || item.width == 0 || item.height == 0 {
1625 continue;
1626 }
1627 let img_w = item.width as f32 * item.scale;
1628 let img_h = item.height as f32 * item.scale;
1629 let (sx, sy) = match item.anchor {
1630 ImageAnchor::TopLeft => (0.0, 0.0),
1631 ImageAnchor::TopRight => (viewport_size.x - img_w, 0.0),
1632 ImageAnchor::BottomLeft => (0.0, viewport_size.y - img_h),
1633 ImageAnchor::BottomRight => (viewport_size.x - img_w, viewport_size.y - img_h),
1634 ImageAnchor::Center => (
1635 (viewport_size.x - img_w) * 0.5,
1636 (viewport_size.y - img_h) * 0.5,
1637 ),
1638 };
1639 if click_pos.x >= sx
1640 && click_pos.x <= sx + img_w
1641 && click_pos.y >= sy
1642 && click_pos.y <= sy + img_h
1643 {
1644 let world_pos = ray_origin + ray_dir * 0.001;
1646 #[allow(deprecated)]
1647 consider(
1648 0.0,
1649 PickHit {
1650 id: item.settings.pick_id.0,
1651 sub_object: None,
1652 world_pos,
1653 normal: -ray_dir,
1654 triangle_index: u32::MAX,
1655 point_index: None,
1656 scalar_value: None,
1657 },
1658 );
1659 }
1660 }
1661 }
1662
1663 if wants_object {
1665 for item in &self.pick_implicit_items {
1666 if let Some((toi, world_pos)) = pick_implicit_sdf(ray_origin, ray_dir, item) {
1667 #[allow(deprecated)]
1668 consider(
1669 toi,
1670 PickHit {
1671 id: item.id,
1672 sub_object: None,
1673 world_pos,
1674 normal: glam::Vec3::Z,
1675 triangle_index: u32::MAX,
1676 point_index: None,
1677 scalar_value: None,
1678 },
1679 );
1680 }
1681 }
1682 }
1683
1684 if wants_object {
1686 for item in &self.pick_mc_items {
1687 if let Some((toi, world_pos)) = pick_mc_volume(ray_origin, ray_dir, item) {
1688 #[allow(deprecated)]
1689 consider(
1690 toi,
1691 PickHit {
1692 id: item.id,
1693 sub_object: None,
1694 world_pos,
1695 normal: glam::Vec3::Z,
1696 triangle_index: u32::MAX,
1697 point_index: None,
1698 scalar_value: None,
1699 },
1700 );
1701 }
1702 }
1703 }
1704
1705 if !self.item_type_plugins.is_empty() {
1709 let plugin_ray = crate::plugin_api::PickRay {
1710 origin: ray_origin,
1711 direction: ray_dir,
1712 };
1713 for plugin in self.item_type_plugins.values() {
1714 if let Some((t, hit)) = plugin.pick(&plugin_ray) {
1715 consider(t, hit);
1716 }
1717 }
1718 }
1719
1720 best.map(|(_, hit)| hit)
1721 }
1722
1723 pub fn pick_rect(
1739 &self,
1740 rect_min: glam::Vec2,
1741 rect_max: glam::Vec2,
1742 viewport_size: glam::Vec2,
1743 view_proj: glam::Mat4,
1744 mask: crate::interaction::pick_mask::PickMask,
1745 ) -> PickRectResult {
1746 use crate::interaction::pick_mask::PickMask;
1747 use crate::interaction::sub_object::SubObjectRef;
1748
1749 let mut result = PickRectResult::default();
1750
1751 if viewport_size.x <= 0.0 || viewport_size.y <= 0.0 {
1752 return result;
1753 }
1754
1755 let wants_face = mask.intersects(PickMask::FACE);
1756 let wants_vertex = mask.intersects(PickMask::VERTEX);
1757 let wants_cell = mask.intersects(PickMask::CELL);
1758 let wants_cloud = mask.intersects(PickMask::CLOUD_POINT);
1759 let wants_splat = mask.intersects(PickMask::SPLAT);
1760 let wants_object = mask.intersects(PickMask::OBJECT);
1761
1762 let vm_cell_map: std::collections::HashMap<u64, &[u32]> = self
1764 .pick_volume_mesh_items
1765 .iter()
1766 .filter(|item| item.settings.pick_id != PickId::NONE && !item.face_to_cell.is_empty())
1767 .map(|item| (item.settings.pick_id.0, item.face_to_cell.as_slice()))
1768 .collect();
1769
1770 let project = |mvp: glam::Mat4, local: glam::Vec3| -> Option<(f32, f32)> {
1773 let clip = mvp * local.extend(1.0);
1774 if clip.w <= 0.0 {
1775 return None;
1776 }
1777 let sx = (clip.x / clip.w + 1.0) * 0.5 * viewport_size.x;
1778 let sy = (1.0 - clip.y / clip.w) * 0.5 * viewport_size.y;
1779 Some((sx, sy))
1780 };
1781
1782 let in_rect = |sx: f32, sy: f32| -> bool {
1783 sx >= rect_min.x && sx <= rect_max.x && sy >= rect_min.y && sy <= rect_max.y
1784 };
1785
1786 if wants_face || wants_vertex || wants_cell || wants_object {
1788 for item in &self.pick_scene_items {
1789 if item.settings.hidden || item.settings.pick_id == PickId::NONE {
1790 continue;
1791 }
1792 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
1793 continue;
1794 };
1795 let (Some(positions), Some(indices)) = (&mesh.cpu_positions, &mesh.cpu_indices)
1796 else {
1797 continue;
1798 };
1799
1800 let model = glam::Mat4::from_cols_array_2d(&item.model);
1801 let mvp = view_proj * model;
1802 let id = item.settings.pick_id.0;
1803 let mut item_hit = false;
1804
1805 if wants_face {
1806 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
1807 if chunk.len() < 3 {
1808 continue;
1809 }
1810 let [i0, i1, i2] =
1811 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
1812 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
1813 continue;
1814 }
1815 let centroid = (glam::Vec3::from(positions[i0])
1816 + glam::Vec3::from(positions[i1])
1817 + glam::Vec3::from(positions[i2]))
1818 / 3.0;
1819 if let Some((sx, sy)) = project(mvp, centroid) {
1820 if in_rect(sx, sy) {
1821 result
1822 .elements
1823 .push((id, SubObjectRef::Face(tri_idx as u32)));
1824 item_hit = true;
1825 }
1826 }
1827 }
1828 } else if wants_cell {
1829 if let Some(f2c) = vm_cell_map.get(&id) {
1831 let mut seen = std::collections::HashSet::new();
1832 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
1833 if chunk.len() < 3 {
1834 continue;
1835 }
1836 let [i0, i1, i2] =
1837 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
1838 if i0 >= positions.len()
1839 || i1 >= positions.len()
1840 || i2 >= positions.len()
1841 {
1842 continue;
1843 }
1844 let centroid = (glam::Vec3::from(positions[i0])
1845 + glam::Vec3::from(positions[i1])
1846 + glam::Vec3::from(positions[i2]))
1847 / 3.0;
1848 if let Some((sx, sy)) = project(mvp, centroid) {
1849 if in_rect(sx, sy) {
1850 if let Some(&ci) = f2c.get(tri_idx) {
1851 if seen.insert(ci) {
1852 result.elements.push((id, SubObjectRef::Cell(ci)));
1853 }
1854 }
1855 item_hit = true;
1856 }
1857 }
1858 }
1859 } else if wants_vertex {
1860 for (vi, pos) in positions.iter().enumerate() {
1862 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
1863 if in_rect(sx, sy) {
1864 result.elements.push((id, SubObjectRef::Vertex(vi as u32)));
1865 item_hit = true;
1866 }
1867 }
1868 }
1869 }
1870 } else if wants_vertex {
1871 for (vi, pos) in positions.iter().enumerate() {
1872 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
1873 if in_rect(sx, sy) {
1874 result.elements.push((id, SubObjectRef::Vertex(vi as u32)));
1875 item_hit = true;
1876 }
1877 }
1878 }
1879 } else {
1880 'tri_scan: for chunk in indices.chunks(3) {
1882 if chunk.len() < 3 {
1883 continue;
1884 }
1885 let [i0, i1, i2] =
1886 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
1887 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
1888 continue;
1889 }
1890 let centroid = (glam::Vec3::from(positions[i0])
1891 + glam::Vec3::from(positions[i1])
1892 + glam::Vec3::from(positions[i2]))
1893 / 3.0;
1894 if let Some((sx, sy)) = project(mvp, centroid) {
1895 if in_rect(sx, sy) {
1896 item_hit = true;
1897 break 'tri_scan;
1898 }
1899 }
1900 }
1901 }
1902
1903 if wants_object && item_hit {
1904 result.objects.push(id);
1905 }
1906 }
1907 }
1908
1909 if wants_cell || wants_object {
1916 for item in &self.pick_volume_mesh_items {
1917 if item.settings.pick_id == PickId::NONE
1918 || item.transparency.is_none()
1919 {
1920 continue;
1921 }
1922 let Some(data) = item.volume_mesh_data.as_deref() else {
1923 continue;
1924 };
1925 use crate::resources::volume_mesh::CELL_SENTINEL;
1926 let id = item.settings.pick_id.0;
1927 let mvp = view_proj * glam::Mat4::from_cols_array_2d(&item.model);
1928 let mut item_hit = false;
1929
1930 for (cell_idx, cell) in data.cells.iter().enumerate() {
1931 let nv: usize = if cell[4] == CELL_SENTINEL {
1932 4
1933 } else if cell[5] == CELL_SENTINEL {
1934 5
1935 } else if cell[6] == CELL_SENTINEL {
1936 6
1937 } else {
1938 8
1939 };
1940 let centroid: glam::Vec3 = cell[..nv]
1941 .iter()
1942 .map(|&vi| glam::Vec3::from(data.positions[vi as usize]))
1943 .sum::<glam::Vec3>()
1944 / nv as f32;
1945 if let Some((sx, sy)) = project(mvp, centroid) {
1946 if in_rect(sx, sy) {
1947 if wants_cell {
1948 result
1949 .elements
1950 .push((id, SubObjectRef::Cell(cell_idx as u32)));
1951 }
1952 item_hit = true;
1953 }
1954 }
1955 }
1956
1957 if wants_object && item_hit {
1958 result.objects.push(id);
1959 }
1960 }
1961 }
1962
1963 if wants_cloud || wants_object {
1965 for item in &self.pick_point_cloud_items {
1966 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
1967 continue;
1968 }
1969 let model = glam::Mat4::from_cols_array_2d(&item.model);
1970 let mvp = view_proj * model;
1971 let id = item.settings.pick_id.0;
1972 let mut item_hit = false;
1973
1974 for (pt_idx, pos) in item.positions.iter().enumerate() {
1975 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
1976 if in_rect(sx, sy) {
1977 if wants_cloud {
1978 result
1979 .elements
1980 .push((id, SubObjectRef::Point(pt_idx as u32)));
1981 }
1982 item_hit = true;
1983 }
1984 }
1985 }
1986
1987 if wants_object && item_hit {
1988 result.objects.push(id);
1989 }
1990 }
1991 }
1992
1993 let wants_voxel = mask.intersects(PickMask::VOXEL);
1995 if wants_voxel || wants_object {
1996 for item in &self.pick_volume_items {
1997 if item.settings.pick_id == PickId::NONE {
1998 continue;
1999 }
2000 let Some(vol_data) = item.volume_data.as_deref() else {
2001 continue;
2002 };
2003 let [nx, ny, nz] = vol_data.dims;
2004 if nx == 0 || ny == 0 || nz == 0 || vol_data.data.is_empty() {
2005 continue;
2006 }
2007 let model = glam::Mat4::from_cols_array_2d(&item.model);
2008 let mvp = view_proj * model;
2009 let bbox_min = glam::Vec3::from(item.bbox_min);
2010 let bbox_max = glam::Vec3::from(item.bbox_max);
2011 let cell = (bbox_max - bbox_min) / glam::Vec3::new(nx as f32, ny as f32, nz as f32);
2012 let id = item.settings.pick_id.0;
2013 let mut item_hit = false;
2014
2015 for iz in 0..nz {
2016 for iy in 0..ny {
2017 for ix in 0..nx {
2018 let flat = (ix + iy * nx + iz * nx * ny) as usize;
2019 let scalar = vol_data.data[flat];
2020 if scalar.is_nan()
2021 || scalar < item.threshold_min
2022 || scalar > item.threshold_max
2023 {
2024 continue;
2025 }
2026 let center = bbox_min
2027 + cell
2028 * glam::Vec3::new(
2029 ix as f32 + 0.5,
2030 iy as f32 + 0.5,
2031 iz as f32 + 0.5,
2032 );
2033 if let Some((sx, sy)) = project(mvp, center) {
2034 if in_rect(sx, sy) {
2035 if wants_voxel {
2036 result
2037 .elements
2038 .push((id, SubObjectRef::Voxel(flat as u32)));
2039 }
2040 item_hit = true;
2041 }
2042 }
2043 }
2044 }
2045 }
2046
2047 if wants_object && item_hit {
2048 result.objects.push(id);
2049 }
2050 }
2051 }
2052
2053 if wants_splat || wants_object {
2055 for item in &self.pick_splat_items {
2056 if item.settings.pick_id == PickId::NONE {
2057 continue;
2058 }
2059 let Some(gpu_set) = self.resources.gaussian_splat_store.get(item.id.0) else {
2060 continue;
2061 };
2062 if gpu_set.cpu_positions.is_empty() {
2063 continue;
2064 }
2065 let model = glam::Mat4::from_cols_array_2d(&item.model);
2066 let mvp = view_proj * model;
2067 let id = item.settings.pick_id.0;
2068 let mut item_hit = false;
2069
2070 for (i, pos) in gpu_set.cpu_positions.iter().enumerate() {
2071 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
2072 if in_rect(sx, sy) {
2073 if wants_splat {
2074 result.elements.push((id, SubObjectRef::Splat(i as u32)));
2075 }
2076 item_hit = true;
2077 }
2078 }
2079 }
2080
2081 if wants_object && item_hit {
2082 result.objects.push(id);
2083 }
2084 }
2085 }
2086
2087 let wants_instance = mask.intersects(PickMask::INSTANCE);
2089 if wants_instance || wants_object {
2090 for item in &self.pick_glyph_items {
2092 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
2093 continue;
2094 }
2095 let model = glam::Mat4::from_cols_array_2d(&item.model);
2096 let mvp = view_proj * model;
2097 let id = item.settings.pick_id.0;
2098 let mut item_hit = false;
2099 for (i, pos) in item.positions.iter().enumerate() {
2100 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
2101 if in_rect(sx, sy) {
2102 if wants_instance {
2103 result.elements.push((id, SubObjectRef::Instance(i as u32)));
2104 }
2105 item_hit = true;
2106 }
2107 }
2108 }
2109 if wants_object && item_hit {
2110 result.objects.push(id);
2111 }
2112 }
2113
2114 for item in &self.pick_tensor_glyph_items {
2116 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
2117 continue;
2118 }
2119 let model = glam::Mat4::from_cols_array_2d(&item.model);
2120 let mvp = view_proj * model;
2121 let id = item.settings.pick_id.0;
2122 let mut item_hit = false;
2123 for (i, pos) in item.positions.iter().enumerate() {
2124 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
2125 if in_rect(sx, sy) {
2126 if wants_instance {
2127 result.elements.push((id, SubObjectRef::Instance(i as u32)));
2128 }
2129 item_hit = true;
2130 }
2131 }
2132 }
2133 if wants_object && item_hit {
2134 result.objects.push(id);
2135 }
2136 }
2137
2138 for item in &self.pick_sprite_items {
2140 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
2141 continue;
2142 }
2143 let model = glam::Mat4::from_cols_array_2d(&item.model);
2144 let mvp = view_proj * model;
2145 let id = item.settings.pick_id.0;
2146 let mut item_hit = false;
2147 for (i, pos) in item.positions.iter().enumerate() {
2148 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
2149 if in_rect(sx, sy) {
2150 if wants_instance {
2151 result.elements.push((id, SubObjectRef::Instance(i as u32)));
2152 }
2153 item_hit = true;
2154 }
2155 }
2156 }
2157 if wants_object && item_hit {
2158 result.objects.push(id);
2159 }
2160 }
2161 }
2162
2163 let wants_poly_node = mask.intersects(PickMask::POLY_NODE);
2165 let wants_segment = mask.intersects(PickMask::SEGMENT);
2166 let wants_strip = mask.intersects(PickMask::STRIP);
2167 if wants_poly_node || wants_segment || wants_strip || wants_object {
2168 for item in &self.pick_polyline_items {
2169 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
2170 continue;
2171 }
2172 let id = item.settings.pick_id.0;
2173 let mut item_hit = false;
2174 let mut strips_hit = std::collections::HashSet::<u32>::new();
2175
2176 if wants_poly_node || wants_strip || wants_object {
2178 for (node_idx, pos) in item.positions.iter().enumerate() {
2179 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
2180 if in_rect(sx, sy) {
2181 item_hit = true;
2182 if wants_poly_node {
2183 result
2184 .elements
2185 .push((id, SubObjectRef::Point(node_idx as u32)));
2186 } else if wants_strip {
2187 let s = strip_for_node(node_idx as u32, &item.strip_lengths);
2188 strips_hit.insert(s);
2189 }
2190 }
2191 }
2192 }
2193 }
2194
2195 if wants_segment || (wants_strip && !wants_poly_node) || wants_object {
2197 let mut node_off = 0usize;
2198 let mut seg_off = 0u32;
2199 macro_rules! try_seg_rect {
2200 ($ai:expr, $bi:expr, $seg:expr) => {{
2201 if let (Some((sax, say)), Some((sbx, sby))) = (
2202 project(view_proj, glam::Vec3::from(item.positions[$ai])),
2203 project(view_proj, glam::Vec3::from(item.positions[$bi])),
2204 ) {
2205 if segment_in_rect(
2206 glam::Vec2::new(sax, say),
2207 glam::Vec2::new(sbx, sby),
2208 rect_min,
2209 rect_max,
2210 ) {
2211 item_hit = true;
2212 if wants_segment {
2213 result.elements.push((id, SubObjectRef::Segment($seg)));
2214 } else if wants_strip {
2215 let s = strip_for_segment($seg, &item.strip_lengths);
2216 strips_hit.insert(s);
2217 }
2218 }
2219 }
2220 }};
2221 }
2222 if item.strip_lengths.is_empty() {
2223 for j in 0..item.positions.len().saturating_sub(1) {
2224 try_seg_rect!(j, j + 1, j as u32);
2225 }
2226 } else {
2227 for &slen in &item.strip_lengths {
2228 let slen = slen as usize;
2229 for j in 0..slen.saturating_sub(1) {
2230 try_seg_rect!(node_off + j, node_off + j + 1, seg_off + j as u32);
2231 }
2232 seg_off += slen.saturating_sub(1) as u32;
2233 node_off += slen;
2234 }
2235 }
2236 }
2237
2238 if wants_strip {
2239 for s in strips_hit {
2240 result.elements.push((id, SubObjectRef::Strip(s)));
2241 }
2242 }
2243 if wants_object && item_hit {
2244 result.objects.push(id);
2245 }
2246 }
2247 }
2248
2249 if wants_poly_node || wants_segment || wants_strip || wants_object {
2251 let st_tube_iter = self
2255 .pick_streamtube_items
2256 .iter()
2257 .map(|it| {
2258 (
2259 it.settings.pick_id.0,
2260 it.positions.as_slice(),
2261 it.strip_lengths.as_slice(),
2262 )
2263 })
2264 .chain(self.pick_tube_items.iter().map(|it| {
2265 (
2266 it.settings.pick_id.0,
2267 it.positions.as_slice(),
2268 it.strip_lengths.as_slice(),
2269 )
2270 }));
2271
2272 for (id, positions, strip_lengths) in st_tube_iter {
2273 if id == 0 || positions.is_empty() {
2274 continue;
2275 }
2276 let mut item_hit = false;
2277 let mut strips_hit = std::collections::HashSet::<u32>::new();
2278
2279 let single_st;
2280 let strips_st: &[u32] = if strip_lengths.is_empty() {
2281 single_st = [positions.len() as u32];
2282 &single_st
2283 } else {
2284 strip_lengths
2285 };
2286
2287 if wants_poly_node || wants_strip || wants_object {
2289 'st_nodes: for (ni, pos) in positions.iter().enumerate() {
2290 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
2291 if in_rect(sx, sy) {
2292 item_hit = true;
2293 if wants_poly_node {
2294 result.elements.push((id, SubObjectRef::Point(ni as u32)));
2295 } else if wants_strip {
2296 let s = strip_for_node(ni as u32, strip_lengths);
2297 strips_hit.insert(s);
2298 } else {
2299 break 'st_nodes;
2301 }
2302 }
2303 }
2304 }
2305 }
2306
2307 if wants_segment || wants_strip || wants_object {
2309 let mut node_off = 0usize;
2310 let mut seg_off = 0u32;
2311 'st_strips: for &slen in strips_st {
2312 let slen = slen as usize;
2313 for j in 0..slen.saturating_sub(1) {
2314 let seg_idx = seg_off + j as u32;
2315 let pa = glam::Vec3::from(positions[node_off + j]);
2316 let pb = glam::Vec3::from(positions[node_off + j + 1]);
2317 let hit = match (project(view_proj, pa), project(view_proj, pb)) {
2318 (Some((ax, ay)), Some((bx, by))) => segment_in_rect(
2319 glam::Vec2::new(ax, ay),
2320 glam::Vec2::new(bx, by),
2321 rect_min,
2322 rect_max,
2323 ),
2324 (Some((ax, ay)), None) => in_rect(ax, ay),
2325 (None, Some((bx, by))) => in_rect(bx, by),
2326 (None, None) => false,
2327 };
2328 if hit {
2329 item_hit = true;
2330 if wants_segment {
2331 result.elements.push((id, SubObjectRef::Segment(seg_idx)));
2332 } else if wants_strip {
2333 let s = strip_for_segment(seg_idx, strip_lengths);
2334 strips_hit.insert(s);
2335 } else {
2336 break 'st_strips;
2338 }
2339 }
2340 }
2341 seg_off += slen.saturating_sub(1) as u32;
2342 node_off += slen;
2343 }
2344 }
2345
2346 if wants_strip {
2347 for s in strips_hit {
2348 result.elements.push((id, SubObjectRef::Strip(s)));
2349 }
2350 }
2351 if wants_object && item_hit {
2352 result.objects.push(id);
2353 }
2354 }
2355
2356 for item in &self.pick_ribbon_items {
2361 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
2362 continue;
2363 }
2364
2365 let single_r;
2366 let strips_r: &[u32] = if item.strip_lengths.is_empty() {
2367 single_r = [item.positions.len() as u32];
2368 &single_r
2369 } else {
2370 &item.strip_lengths
2371 };
2372
2373 let mut item_hit = false;
2374 let mut strips_hit = std::collections::HashSet::<u32>::new();
2375
2376 let proj2 = |p: glam::Vec3| -> Option<glam::Vec2> {
2378 project(view_proj, p).map(|(x, y)| glam::Vec2::new(x, y))
2379 };
2380
2381 if wants_poly_node || wants_strip || wants_object {
2383 'rb_nodes: for (ni, pos) in item.positions.iter().enumerate() {
2384 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
2385 if in_rect(sx, sy) {
2386 item_hit = true;
2387 if wants_poly_node {
2388 result.elements.push((
2389 item.settings.pick_id.0,
2390 SubObjectRef::Point(ni as u32),
2391 ));
2392 } else if wants_strip {
2393 let s = strip_for_node(ni as u32, &item.strip_lengths);
2394 strips_hit.insert(s);
2395 } else {
2396 break 'rb_nodes;
2397 }
2398 }
2399 }
2400 }
2401 }
2402
2403 if wants_segment || wants_strip || wants_object {
2405 let frames = ribbon_lateral_frames(
2406 &item.positions,
2407 &item.strip_lengths,
2408 item.width,
2409 item.width_attribute.as_deref(),
2410 item.twist_attribute.as_deref(),
2411 );
2412 let mut node_off = 0usize;
2413 let mut seg_off = 0u32;
2414
2415 'rb_strips: for &slen in strips_r {
2416 let slen = slen as usize;
2417 for k in 0..slen.saturating_sub(1) {
2418 let seg_idx = seg_off + k as u32;
2419 let ia = node_off + k;
2420 let ib = node_off + k + 1;
2421 let pa = glam::Vec3::from(item.positions[ia]);
2422 let pb = glam::Vec3::from(item.positions[ib]);
2423 let (ua, wa) = frames[ia];
2424 let (ub, wb) = frames[ib];
2425 let c0 = pa + ua * wa; let c1 = pa - ua * wa; let c2 = pb + ub * wb; let c3 = pb - ub * wb; let sc0 = proj2(c0);
2430 let sc1 = proj2(c1);
2431 let sc2 = proj2(c2);
2432 let sc3 = proj2(c3);
2433 let edge_hit = |a: Option<glam::Vec2>, b: Option<glam::Vec2>| -> bool {
2434 match (a, b) {
2435 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
2436 (Some(a), None) => in_rect(a.x, a.y),
2437 (None, Some(b)) => in_rect(b.x, b.y),
2438 (None, None) => false,
2439 }
2440 };
2441 let hit = edge_hit(sc0, sc1)
2442 || edge_hit(sc2, sc3)
2443 || edge_hit(sc0, sc2)
2444 || edge_hit(sc1, sc3);
2445 if hit {
2446 item_hit = true;
2447 if wants_segment {
2448 result.elements.push((
2449 item.settings.pick_id.0,
2450 SubObjectRef::Segment(seg_idx),
2451 ));
2452 } else if wants_strip {
2453 let s = strip_for_segment(seg_idx, &item.strip_lengths);
2454 strips_hit.insert(s);
2455 } else {
2456 break 'rb_strips;
2457 }
2458 }
2459 }
2460 seg_off += slen.saturating_sub(1) as u32;
2461 node_off += slen;
2462 }
2463 }
2464
2465 if wants_strip {
2466 for s in strips_hit {
2467 result
2468 .elements
2469 .push((item.settings.pick_id.0, SubObjectRef::Strip(s)));
2470 }
2471 }
2472 if wants_object && item_hit {
2473 result.objects.push(item.settings.pick_id.0);
2474 }
2475 }
2476 }
2477
2478 if wants_object {
2480 for item in &self.pick_image_slice_items {
2482 if item.settings.pick_id == PickId::NONE {
2483 continue;
2484 }
2485 let [bmin, bmax] = [item.bbox_min, item.bbox_max];
2486 let t = item.offset;
2487 let corners: [[f32; 3]; 4] = match item.axis {
2488 SliceAxis::X => {
2489 let x = bmin[0] + t * (bmax[0] - bmin[0]);
2490 [
2491 [x, bmin[1], bmin[2]],
2492 [x, bmax[1], bmin[2]],
2493 [x, bmax[1], bmax[2]],
2494 [x, bmin[1], bmax[2]],
2495 ]
2496 }
2497 SliceAxis::Y => {
2498 let y = bmin[1] + t * (bmax[1] - bmin[1]);
2499 [
2500 [bmin[0], y, bmin[2]],
2501 [bmax[0], y, bmin[2]],
2502 [bmax[0], y, bmax[2]],
2503 [bmin[0], y, bmax[2]],
2504 ]
2505 }
2506 SliceAxis::Z => {
2507 let z = bmin[2] + t * (bmax[2] - bmin[2]);
2508 [
2509 [bmin[0], bmin[1], z],
2510 [bmax[0], bmin[1], z],
2511 [bmax[0], bmax[1], z],
2512 [bmin[0], bmax[1], z],
2513 ]
2514 }
2515 };
2516 let sc: Vec<Option<glam::Vec2>> = corners
2517 .iter()
2518 .map(|&c| {
2519 project(view_proj, glam::Vec3::from(c)).map(|(x, y)| glam::Vec2::new(x, y))
2520 })
2521 .collect();
2522 let hit = sc.iter().any(|p| p.map_or(false, |p| in_rect(p.x, p.y)))
2523 || (0..4).any(|i| {
2524 let a = sc[i];
2525 let b = sc[(i + 1) % 4];
2526 match (a, b) {
2527 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
2528 (Some(a), None) => in_rect(a.x, a.y),
2529 (None, Some(b)) => in_rect(b.x, b.y),
2530 (None, None) => false,
2531 }
2532 });
2533 if hit {
2534 result.objects.push(item.settings.pick_id.0);
2535 }
2536 }
2537
2538 for item in &self.pick_volume_surface_slice_items {
2540 if item.settings.pick_id == PickId::NONE {
2541 continue;
2542 }
2543 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
2544 continue;
2545 };
2546 let Some(positions) = &mesh.cpu_positions else {
2547 continue;
2548 };
2549 let model = glam::Mat4::from_cols_array_2d(&item.model);
2550 let hit = positions.iter().any(|&p| {
2551 let wp = model.transform_point3(glam::Vec3::from(p));
2552 project(view_proj, wp).map_or(false, |(sx, sy)| in_rect(sx, sy))
2553 });
2554 if hit {
2555 result.objects.push(item.settings.pick_id.0);
2556 }
2557 }
2558
2559 for item in &self.pick_screen_image_items {
2561 if item.settings.pick_id == PickId::NONE || item.width == 0 || item.height == 0 {
2562 continue;
2563 }
2564 let img_w = item.width as f32 * item.scale;
2565 let img_h = item.height as f32 * item.scale;
2566 let (sx, sy) = match item.anchor {
2567 ImageAnchor::TopLeft => (0.0, 0.0),
2568 ImageAnchor::TopRight => (viewport_size.x - img_w, 0.0),
2569 ImageAnchor::BottomLeft => (0.0, viewport_size.y - img_h),
2570 ImageAnchor::BottomRight => (viewport_size.x - img_w, viewport_size.y - img_h),
2571 ImageAnchor::Center => (
2572 (viewport_size.x - img_w) * 0.5,
2573 (viewport_size.y - img_h) * 0.5,
2574 ),
2575 };
2576 let overlap = sx <= rect_max.x
2578 && sx + img_w >= rect_min.x
2579 && sy <= rect_max.y
2580 && sy + img_h >= rect_min.y;
2581 if overlap {
2582 result.objects.push(item.settings.pick_id.0);
2583 }
2584 }
2585 }
2586
2587 if wants_object {
2594 for item in &self.pick_implicit_items {
2595 let mut hit = false;
2596 'prim_loop: for prim in &item.primitives {
2597 let (center, radius) = match prim.kind {
2599 1 => {
2600 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
2602 (c, prim.params[3].abs())
2603 }
2604 2 => {
2605 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
2607 let h = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
2608 (c, h.length())
2609 }
2610 3 => {
2611 continue;
2613 }
2614 4 => {
2615 let a = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
2617 let b = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
2618 let r = prim.params[3].abs();
2619 ((a + b) * 0.5, (b - a).length() * 0.5 + r)
2620 }
2621 _ => continue,
2622 };
2623 for dx in [-radius, radius] {
2625 for dy in [-radius, radius] {
2626 for dz in [-radius, radius] {
2627 let corner = center + glam::Vec3::new(dx, dy, dz);
2628 if let Some((sx, sy)) = project(view_proj, corner) {
2629 if in_rect(sx, sy) {
2630 hit = true;
2631 break 'prim_loop;
2632 }
2633 }
2634 }
2635 }
2636 }
2637 }
2638 if hit {
2639 result.objects.push(item.id);
2640 }
2641 }
2642 }
2643
2644 if wants_object {
2650 for item in &self.pick_mc_items {
2651 let vol = &item.volume_data;
2652 let isovalue = item.isovalue;
2653 let [nx, ny, nz] = vol.dims;
2654 let origin = glam::Vec3::from(vol.origin);
2655 let spacing = glam::Vec3::from(vol.spacing);
2656
2657 let mut hit = false;
2658 'mc_rect: for iz in 0..nz.saturating_sub(1) {
2659 for iy in 0..ny.saturating_sub(1) {
2660 for ix in 0..nx.saturating_sub(1) {
2661 let mut has_below = false;
2664 let mut has_above = false;
2665 'corners: for dz in 0u32..=1 {
2666 for dy in 0u32..=1 {
2667 for dx in 0u32..=1 {
2668 let s = vol.sample(ix + dx, iy + dy, iz + dz);
2669 if s < isovalue {
2670 has_below = true;
2671 } else {
2672 has_above = true;
2673 }
2674 if has_below && has_above {
2675 break 'corners;
2676 }
2677 }
2678 }
2679 }
2680 if !(has_below && has_above) {
2681 continue;
2682 }
2683 let cell_center = origin
2684 + spacing
2685 * glam::Vec3::new(
2686 ix as f32 + 0.5,
2687 iy as f32 + 0.5,
2688 iz as f32 + 0.5,
2689 );
2690 if let Some((sx, sy)) = project(view_proj, cell_center) {
2691 if in_rect(sx, sy) {
2692 hit = true;
2693 break 'mc_rect;
2694 }
2695 }
2696 }
2697 }
2698 }
2699 if hit {
2700 result.objects.push(item.id);
2701 }
2702 }
2703 }
2704
2705 result
2706 }
2707
2708 pub fn pick_scene_gpu(
2732 &mut self,
2733 device: &wgpu::Device,
2734 queue: &wgpu::Queue,
2735 cursor: glam::Vec2,
2736 frame: &FrameData,
2737 ) -> Option<crate::interaction::picking::GpuPickHit> {
2738 if self.runtime_mode == crate::renderer::stats::RuntimeMode::Playback
2741 && self.frame_counter % 4 != 0
2742 {
2743 return None;
2744 }
2745
2746 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
2748 SurfaceSubmission::Flat(items) => items.as_ref(),
2749 };
2750
2751 let ppp = frame.camera.pixels_per_point;
2752 let vp_w = (frame.camera.viewport_size[0] * ppp).round() as u32;
2753 let vp_h = (frame.camera.viewport_size[1] * ppp).round() as u32;
2754
2755 if cursor.x < 0.0
2757 || cursor.y < 0.0
2758 || cursor.x >= frame.camera.viewport_size[0]
2759 || cursor.y >= frame.camera.viewport_size[1]
2760 || vp_w == 0
2761 || vp_h == 0
2762 {
2763 return None;
2764 }
2765
2766 self.resources.ensure_pick_pipeline(device);
2768
2769 let pickable_items: Vec<&SceneRenderItem> = scene_items
2773 .iter()
2774 .filter(|item| !item.settings.hidden && item.settings.pick_id != PickId::NONE)
2775 .collect();
2776
2777 let pick_instances: Vec<PickInstance> = pickable_items
2778 .iter()
2779 .map(|item| {
2780 let m = item.model;
2781 PickInstance {
2782 model_c0: m[0],
2783 model_c1: m[1],
2784 model_c2: m[2],
2785 model_c3: m[3],
2786 object_id: item.settings.pick_id.0 as u32,
2787 _pad: [0; 3],
2788 }
2789 })
2790 .collect();
2791
2792 if pick_instances.is_empty() {
2793 return None;
2794 }
2795
2796 let pick_instance_bytes = bytemuck::cast_slice(&pick_instances);
2798 let pick_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
2799 label: Some("pick_instance_buf"),
2800 size: pick_instance_bytes.len().max(80) as u64,
2801 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2802 mapped_at_creation: false,
2803 });
2804 queue.write_buffer(&pick_instance_buf, 0, pick_instance_bytes);
2805
2806 let pick_instance_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
2807 label: Some("pick_instance_bg"),
2808 layout: self
2809 .resources
2810 .pick_bind_group_layout_1
2811 .as_ref()
2812 .expect("ensure_pick_pipeline must be called first"),
2813 entries: &[wgpu::BindGroupEntry {
2814 binding: 0,
2815 resource: pick_instance_buf.as_entire_binding(),
2816 }],
2817 });
2818
2819 let camera_uniform = frame.camera.render_camera.camera_uniform();
2821 let camera_bytes = bytemuck::bytes_of(&camera_uniform);
2822 let pick_camera_buf = device.create_buffer(&wgpu::BufferDescriptor {
2823 label: Some("pick_camera_buf"),
2824 size: std::mem::size_of::<CameraUniform>() as u64,
2825 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2826 mapped_at_creation: false,
2827 });
2828 queue.write_buffer(&pick_camera_buf, 0, camera_bytes);
2829
2830 let pick_camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
2831 label: Some("pick_camera_bg"),
2832 layout: self
2833 .resources
2834 .pick_camera_bgl
2835 .as_ref()
2836 .expect("ensure_pick_pipeline must be called first"),
2837 entries: &[
2838 wgpu::BindGroupEntry {
2839 binding: 0,
2840 resource: pick_camera_buf.as_entire_binding(),
2841 },
2842 wgpu::BindGroupEntry {
2843 binding: 6,
2844 resource: self.resources.clip_volume_uniform_buf.as_entire_binding(),
2845 },
2846 ],
2847 });
2848
2849 let pick_id_texture = device.create_texture(&wgpu::TextureDescriptor {
2851 label: Some("pick_id_texture"),
2852 size: wgpu::Extent3d {
2853 width: vp_w,
2854 height: vp_h,
2855 depth_or_array_layers: 1,
2856 },
2857 mip_level_count: 1,
2858 sample_count: 1,
2859 dimension: wgpu::TextureDimension::D2,
2860 format: wgpu::TextureFormat::R32Uint,
2861 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2862 view_formats: &[],
2863 });
2864 let pick_id_view = pick_id_texture.create_view(&wgpu::TextureViewDescriptor::default());
2865
2866 let pick_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
2867 label: Some("pick_depth_colour_texture"),
2868 size: wgpu::Extent3d {
2869 width: vp_w,
2870 height: vp_h,
2871 depth_or_array_layers: 1,
2872 },
2873 mip_level_count: 1,
2874 sample_count: 1,
2875 dimension: wgpu::TextureDimension::D2,
2876 format: wgpu::TextureFormat::R32Float,
2877 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2878 view_formats: &[],
2879 });
2880 let pick_depth_view =
2881 pick_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
2882
2883 let depth_stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
2884 label: Some("pick_ds_texture"),
2885 size: wgpu::Extent3d {
2886 width: vp_w,
2887 height: vp_h,
2888 depth_or_array_layers: 1,
2889 },
2890 mip_level_count: 1,
2891 sample_count: 1,
2892 dimension: wgpu::TextureDimension::D2,
2893 format: wgpu::TextureFormat::Depth24PlusStencil8,
2894 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2895 view_formats: &[],
2896 });
2897 let depth_stencil_view =
2898 depth_stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());
2899
2900 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
2902 label: Some("pick_pass_encoder"),
2903 });
2904 {
2905 let mut pick_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2906 label: Some("pick_pass"),
2907 color_attachments: &[
2908 Some(wgpu::RenderPassColorAttachment {
2909 view: &pick_id_view,
2910 resolve_target: None,
2911 depth_slice: None,
2912 ops: wgpu::Operations {
2913 load: wgpu::LoadOp::Clear(wgpu::Color {
2914 r: 0.0,
2915 g: 0.0,
2916 b: 0.0,
2917 a: 0.0,
2918 }),
2919 store: wgpu::StoreOp::Store,
2920 },
2921 }),
2922 Some(wgpu::RenderPassColorAttachment {
2923 view: &pick_depth_view,
2924 resolve_target: None,
2925 depth_slice: None,
2926 ops: wgpu::Operations {
2927 load: wgpu::LoadOp::Clear(wgpu::Color {
2928 r: 1.0,
2929 g: 0.0,
2930 b: 0.0,
2931 a: 0.0,
2932 }),
2933 store: wgpu::StoreOp::Store,
2934 },
2935 }),
2936 ],
2937 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2938 view: &depth_stencil_view,
2939 depth_ops: Some(wgpu::Operations {
2940 load: wgpu::LoadOp::Clear(1.0),
2941 store: wgpu::StoreOp::Store,
2942 }),
2943 stencil_ops: None,
2944 }),
2945 timestamp_writes: None,
2946 occlusion_query_set: None,
2947 });
2948
2949 pick_pass.set_pipeline(
2950 self.resources
2951 .pick_pipeline
2952 .as_ref()
2953 .expect("ensure_pick_pipeline must be called first"),
2954 );
2955 pick_pass.set_bind_group(0, &pick_camera_bg, &[]);
2956 pick_pass.set_bind_group(1, &pick_instance_bg, &[]);
2957
2958 for (instance_slot, item) in pickable_items.iter().enumerate() {
2961 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
2962 continue;
2963 };
2964 pick_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
2965 pick_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2966 let slot = instance_slot as u32;
2967 pick_pass.draw_indexed(0..mesh.index_count, 0, slot..slot + 1);
2968 }
2969 }
2970
2971 let bytes_per_row_aligned = 256u32; let id_staging = device.create_buffer(&wgpu::BufferDescriptor {
2976 label: Some("pick_id_staging"),
2977 size: bytes_per_row_aligned as u64,
2978 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2979 mapped_at_creation: false,
2980 });
2981 let depth_staging = device.create_buffer(&wgpu::BufferDescriptor {
2982 label: Some("pick_depth_staging"),
2983 size: bytes_per_row_aligned as u64,
2984 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2985 mapped_at_creation: false,
2986 });
2987
2988 let px = (cursor.x * ppp).round() as u32;
2990 let py = (cursor.y * ppp).round() as u32;
2991
2992 encoder.copy_texture_to_buffer(
2993 wgpu::TexelCopyTextureInfo {
2994 texture: &pick_id_texture,
2995 mip_level: 0,
2996 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
2997 aspect: wgpu::TextureAspect::All,
2998 },
2999 wgpu::TexelCopyBufferInfo {
3000 buffer: &id_staging,
3001 layout: wgpu::TexelCopyBufferLayout {
3002 offset: 0,
3003 bytes_per_row: Some(bytes_per_row_aligned),
3004 rows_per_image: Some(1),
3005 },
3006 },
3007 wgpu::Extent3d {
3008 width: 1,
3009 height: 1,
3010 depth_or_array_layers: 1,
3011 },
3012 );
3013 encoder.copy_texture_to_buffer(
3014 wgpu::TexelCopyTextureInfo {
3015 texture: &pick_depth_texture,
3016 mip_level: 0,
3017 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
3018 aspect: wgpu::TextureAspect::All,
3019 },
3020 wgpu::TexelCopyBufferInfo {
3021 buffer: &depth_staging,
3022 layout: wgpu::TexelCopyBufferLayout {
3023 offset: 0,
3024 bytes_per_row: Some(bytes_per_row_aligned),
3025 rows_per_image: Some(1),
3026 },
3027 },
3028 wgpu::Extent3d {
3029 width: 1,
3030 height: 1,
3031 depth_or_array_layers: 1,
3032 },
3033 );
3034
3035 queue.submit(std::iter::once(encoder.finish()));
3036
3037 let (tx_id, rx_id) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
3039 let (tx_dep, rx_dep) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
3040 id_staging
3041 .slice(..)
3042 .map_async(wgpu::MapMode::Read, move |r| {
3043 let _ = tx_id.send(r);
3044 });
3045 depth_staging
3046 .slice(..)
3047 .map_async(wgpu::MapMode::Read, move |r| {
3048 let _ = tx_dep.send(r);
3049 });
3050 device
3051 .poll(wgpu::PollType::Wait {
3052 submission_index: None,
3053 timeout: Some(std::time::Duration::from_secs(5)),
3054 })
3055 .unwrap();
3056 let _ = rx_id.recv().unwrap_or(Err(wgpu::BufferAsyncError));
3057 let _ = rx_dep.recv().unwrap_or(Err(wgpu::BufferAsyncError));
3058
3059 let object_id = {
3060 let data = id_staging.slice(..).get_mapped_range();
3061 u32::from_le_bytes([data[0], data[1], data[2], data[3]])
3062 };
3063 id_staging.unmap();
3064
3065 let depth = {
3066 let data = depth_staging.slice(..).get_mapped_range();
3067 f32::from_le_bytes([data[0], data[1], data[2], data[3]])
3068 };
3069 depth_staging.unmap();
3070
3071 if object_id == 0 {
3073 return None;
3074 }
3075
3076 Some(crate::interaction::picking::GpuPickHit {
3077 object_id: PickId(object_id as u64),
3078 depth,
3079 })
3080 }
3081}