1use super::*;
5
6impl ViewportRenderer {
7 pub fn pick_rect(
23 &self,
24 rect_min: glam::Vec2,
25 rect_max: glam::Vec2,
26 viewport_size: glam::Vec2,
27 view_proj: glam::Mat4,
28 mask: crate::interaction::pick_mask::PickMask,
29 ) -> PickRectResult {
30 use crate::interaction::pick_mask::PickMask;
31 use crate::interaction::sub_object::SubObjectRef;
32
33 let mut result = PickRectResult::default();
34
35 if !self.cpu_pick_cache_enabled {
36 warn_pick_cache_disabled();
37 return result;
38 }
39
40 if viewport_size.x <= 0.0 || viewport_size.y <= 0.0 {
41 return result;
42 }
43
44 let wants_face = mask.intersects(PickMask::FACE);
45 let wants_vertex = mask.intersects(PickMask::VERTEX);
46 let wants_cell = mask.intersects(PickMask::CELL);
47 let wants_cloud = mask.intersects(PickMask::CLOUD_POINT);
48 let wants_splat = mask.intersects(PickMask::SPLAT);
49 let wants_object = mask.intersects(PickMask::OBJECT);
50
51 let vm_cell_map: std::collections::HashMap<u64, &[u32]> = self
53 .pick_volume_mesh_items
54 .iter()
55 .filter(|item| item.settings.pick_id != PickId::NONE && !item.face_to_cell.is_empty())
56 .map(|item| (item.settings.pick_id.0, item.face_to_cell.as_slice()))
57 .collect();
58
59 let project = |mvp: glam::Mat4, local: glam::Vec3| -> Option<(f32, f32)> {
62 let clip = mvp * local.extend(1.0);
63 if clip.w <= 0.0 {
64 return None;
65 }
66 let sx = (clip.x / clip.w + 1.0) * 0.5 * viewport_size.x;
67 let sy = (1.0 - clip.y / clip.w) * 0.5 * viewport_size.y;
68 Some((sx, sy))
69 };
70
71 let in_rect = |sx: f32, sy: f32| -> bool {
72 sx >= rect_min.x && sx <= rect_max.x && sy >= rect_min.y && sy <= rect_max.y
73 };
74
75 if wants_face || wants_vertex || wants_cell || wants_object {
77 for item in &self.pick_scene_items {
78 if item.settings.hidden || item.settings.pick_id == PickId::NONE {
79 continue;
80 }
81 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
82 continue;
83 };
84 let (Some(positions), Some(indices)) = (&mesh.cpu_positions, &mesh.cpu_indices)
85 else {
86 continue;
87 };
88
89 let model = glam::Mat4::from_cols_array_2d(&item.model);
90 let mvp = view_proj * model;
91 let id = item.settings.pick_id.0;
92 let mut item_hit = false;
93
94 if wants_face {
95 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
96 if chunk.len() < 3 {
97 continue;
98 }
99 let [i0, i1, i2] =
100 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
101 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
102 continue;
103 }
104 let centroid = (glam::Vec3::from(positions[i0])
105 + glam::Vec3::from(positions[i1])
106 + glam::Vec3::from(positions[i2]))
107 / 3.0;
108 if let Some((sx, sy)) = project(mvp, centroid) {
109 if in_rect(sx, sy) {
110 result
111 .elements
112 .push((id, SubObjectRef::Face(tri_idx as u32)));
113 item_hit = true;
114 }
115 }
116 }
117 } else if wants_cell {
118 if let Some(f2c) = vm_cell_map.get(&id) {
120 let mut seen = std::collections::HashSet::new();
121 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
122 if chunk.len() < 3 {
123 continue;
124 }
125 let [i0, i1, i2] =
126 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
127 if i0 >= positions.len()
128 || i1 >= positions.len()
129 || i2 >= positions.len()
130 {
131 continue;
132 }
133 let centroid = (glam::Vec3::from(positions[i0])
134 + glam::Vec3::from(positions[i1])
135 + glam::Vec3::from(positions[i2]))
136 / 3.0;
137 if let Some((sx, sy)) = project(mvp, centroid) {
138 if in_rect(sx, sy) {
139 if let Some(&ci) = f2c.get(tri_idx) {
140 if seen.insert(ci) {
141 result.elements.push((id, SubObjectRef::Cell(ci)));
142 }
143 }
144 item_hit = true;
145 }
146 }
147 }
148 } else if wants_vertex {
149 for (vi, pos) in positions.iter().enumerate() {
151 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
152 if in_rect(sx, sy) {
153 result.elements.push((id, SubObjectRef::Vertex(vi as u32)));
154 item_hit = true;
155 }
156 }
157 }
158 }
159 } else if wants_vertex {
160 for (vi, pos) in positions.iter().enumerate() {
161 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
162 if in_rect(sx, sy) {
163 result.elements.push((id, SubObjectRef::Vertex(vi as u32)));
164 item_hit = true;
165 }
166 }
167 }
168 } else {
169 'tri_scan: for chunk in indices.chunks(3) {
171 if chunk.len() < 3 {
172 continue;
173 }
174 let [i0, i1, i2] =
175 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize];
176 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
177 continue;
178 }
179 let centroid = (glam::Vec3::from(positions[i0])
180 + glam::Vec3::from(positions[i1])
181 + glam::Vec3::from(positions[i2]))
182 / 3.0;
183 if let Some((sx, sy)) = project(mvp, centroid) {
184 if in_rect(sx, sy) {
185 item_hit = true;
186 break 'tri_scan;
187 }
188 }
189 }
190 }
191
192 if wants_object && item_hit {
193 result.objects.push(id);
194 }
195 }
196 }
197
198 if wants_cell || wants_object {
205 for item in &self.pick_volume_mesh_items {
206 if item.settings.pick_id == PickId::NONE || item.transparency.is_none() {
207 continue;
208 }
209 let Some(data) = item.volume_mesh_data.as_deref() else {
210 continue;
211 };
212 use crate::resources::volume_mesh::CELL_SENTINEL;
213 let id = item.settings.pick_id.0;
214 let mvp = view_proj * glam::Mat4::from_cols_array_2d(&item.model);
215 let mut item_hit = false;
216
217 for (cell_idx, cell) in data.cells.iter().enumerate() {
218 let nv: usize = if cell[4] == CELL_SENTINEL {
219 4
220 } else if cell[5] == CELL_SENTINEL {
221 5
222 } else if cell[6] == CELL_SENTINEL {
223 6
224 } else {
225 8
226 };
227 let centroid: glam::Vec3 = cell[..nv]
228 .iter()
229 .map(|&vi| glam::Vec3::from(data.positions[vi as usize]))
230 .sum::<glam::Vec3>()
231 / nv as f32;
232 if let Some((sx, sy)) = project(mvp, centroid) {
233 if in_rect(sx, sy) {
234 if wants_cell {
235 result
236 .elements
237 .push((id, SubObjectRef::Cell(cell_idx as u32)));
238 }
239 item_hit = true;
240 }
241 }
242 }
243
244 if wants_object && item_hit {
245 result.objects.push(id);
246 }
247 }
248 }
249
250 if wants_cloud || wants_object {
252 for item in &self.pick_point_cloud_items {
253 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
254 continue;
255 }
256 let model = glam::Mat4::from_cols_array_2d(&item.model);
257 let mvp = view_proj * model;
258 let id = item.settings.pick_id.0;
259 let mut item_hit = false;
260
261 for (pt_idx, pos) in item.positions.iter().enumerate() {
262 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
263 if in_rect(sx, sy) {
264 if wants_cloud {
265 result
266 .elements
267 .push((id, SubObjectRef::Point(pt_idx as u32)));
268 }
269 item_hit = true;
270 }
271 }
272 }
273
274 if wants_object && item_hit {
275 result.objects.push(id);
276 }
277 }
278 }
279
280 let wants_voxel = mask.intersects(PickMask::VOXEL);
282 if wants_voxel || wants_object {
283 for item in &self.pick_volume_items {
284 if item.settings.pick_id == PickId::NONE {
285 continue;
286 }
287 let Some(vol_data) = item.volume_data.as_deref() else {
288 continue;
289 };
290 let [nx, ny, nz] = vol_data.dims;
291 if nx == 0 || ny == 0 || nz == 0 || vol_data.data.is_empty() {
292 continue;
293 }
294 let model = glam::Mat4::from_cols_array_2d(&item.model);
295 let mvp = view_proj * model;
296 let bbox_min = glam::Vec3::from(item.bbox_min);
297 let bbox_max = glam::Vec3::from(item.bbox_max);
298 let cell = (bbox_max - bbox_min) / glam::Vec3::new(nx as f32, ny as f32, nz as f32);
299 let id = item.settings.pick_id.0;
300 let mut item_hit = false;
301
302 for iz in 0..nz {
303 for iy in 0..ny {
304 for ix in 0..nx {
305 let flat = (ix + iy * nx + iz * nx * ny) as usize;
306 let scalar = vol_data.data[flat];
307 if scalar.is_nan()
308 || scalar < item.threshold_min
309 || scalar > item.threshold_max
310 {
311 continue;
312 }
313 let center = bbox_min
314 + cell
315 * glam::Vec3::new(
316 ix as f32 + 0.5,
317 iy as f32 + 0.5,
318 iz as f32 + 0.5,
319 );
320 if let Some((sx, sy)) = project(mvp, center) {
321 if in_rect(sx, sy) {
322 if wants_voxel {
323 result
324 .elements
325 .push((id, SubObjectRef::Voxel(flat as u32)));
326 }
327 item_hit = true;
328 }
329 }
330 }
331 }
332 }
333
334 if wants_object && item_hit {
335 result.objects.push(id);
336 }
337 }
338 }
339
340 if wants_splat || wants_object {
342 for item in &self.pick_splat_items {
343 if item.settings.pick_id == PickId::NONE {
344 continue;
345 }
346 let Some(gpu_set) = self.resources.gaussian_splat_store.get(item.id.0) else {
347 continue;
348 };
349 if gpu_set.cpu_positions.is_empty() {
350 continue;
351 }
352 let model = glam::Mat4::from_cols_array_2d(&item.model);
353 let mvp = view_proj * model;
354 let id = item.settings.pick_id.0;
355 let mut item_hit = false;
356
357 for (i, pos) in gpu_set.cpu_positions.iter().enumerate() {
358 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
359 if in_rect(sx, sy) {
360 if wants_splat {
361 result.elements.push((id, SubObjectRef::Splat(i as u32)));
362 }
363 item_hit = true;
364 }
365 }
366 }
367
368 if wants_object && item_hit {
369 result.objects.push(id);
370 }
371 }
372 }
373
374 let wants_instance = mask.intersects(PickMask::INSTANCE);
376 if wants_instance || wants_object {
377 for item in &self.pick_glyph_items {
379 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
380 continue;
381 }
382 let model = glam::Mat4::from_cols_array_2d(&item.model);
383 let mvp = view_proj * model;
384 let id = item.settings.pick_id.0;
385 let mut item_hit = false;
386 for (i, pos) in item.positions.iter().enumerate() {
387 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
388 if in_rect(sx, sy) {
389 if wants_instance {
390 result.elements.push((id, SubObjectRef::Instance(i as u32)));
391 }
392 item_hit = true;
393 }
394 }
395 }
396 if wants_object && item_hit {
397 result.objects.push(id);
398 }
399 }
400
401 for item in &self.pick_tensor_glyph_items {
403 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
404 continue;
405 }
406 let model = glam::Mat4::from_cols_array_2d(&item.model);
407 let mvp = view_proj * model;
408 let id = item.settings.pick_id.0;
409 let mut item_hit = false;
410 for (i, pos) in item.positions.iter().enumerate() {
411 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
412 if in_rect(sx, sy) {
413 if wants_instance {
414 result.elements.push((id, SubObjectRef::Instance(i as u32)));
415 }
416 item_hit = true;
417 }
418 }
419 }
420 if wants_object && item_hit {
421 result.objects.push(id);
422 }
423 }
424
425 for item in &self.pick_sprite_items {
427 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
428 continue;
429 }
430 let model = glam::Mat4::from_cols_array_2d(&item.model);
431 let mvp = view_proj * model;
432 let id = item.settings.pick_id.0;
433 let mut item_hit = false;
434 for (i, pos) in item.positions.iter().enumerate() {
435 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
436 if in_rect(sx, sy) {
437 if wants_instance {
438 result.elements.push((id, SubObjectRef::Instance(i as u32)));
439 }
440 item_hit = true;
441 }
442 }
443 }
444 if wants_object && item_hit {
445 result.objects.push(id);
446 }
447 }
448 }
449
450 let wants_poly_node = mask.intersects(PickMask::POLY_NODE);
452 let wants_segment = mask.intersects(PickMask::SEGMENT);
453 let wants_strip = mask.intersects(PickMask::STRIP);
454 if wants_poly_node || wants_segment || wants_strip || wants_object {
455 for item in &self.pick_polyline_items {
456 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
457 continue;
458 }
459 let id = item.settings.pick_id.0;
460 let mut item_hit = false;
461 let mut strips_hit = std::collections::HashSet::<u32>::new();
462
463 if wants_poly_node || wants_strip || wants_object {
465 for (node_idx, pos) in item.positions.iter().enumerate() {
466 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
467 if in_rect(sx, sy) {
468 item_hit = true;
469 if wants_poly_node {
470 result
471 .elements
472 .push((id, SubObjectRef::Point(node_idx as u32)));
473 } else if wants_strip {
474 let s = strip_for_node(node_idx as u32, &item.strip_lengths);
475 strips_hit.insert(s);
476 }
477 }
478 }
479 }
480 }
481
482 if wants_segment || (wants_strip && !wants_poly_node) || wants_object {
484 let mut node_off = 0usize;
485 let mut seg_off = 0u32;
486 macro_rules! try_seg_rect {
487 ($ai:expr, $bi:expr, $seg:expr) => {{
488 if let (Some((sax, say)), Some((sbx, sby))) = (
489 project(view_proj, glam::Vec3::from(item.positions[$ai])),
490 project(view_proj, glam::Vec3::from(item.positions[$bi])),
491 ) {
492 if segment_in_rect(
493 glam::Vec2::new(sax, say),
494 glam::Vec2::new(sbx, sby),
495 rect_min,
496 rect_max,
497 ) {
498 item_hit = true;
499 if wants_segment {
500 result.elements.push((id, SubObjectRef::Segment($seg)));
501 } else if wants_strip {
502 let s = strip_for_segment($seg, &item.strip_lengths);
503 strips_hit.insert(s);
504 }
505 }
506 }
507 }};
508 }
509 if item.strip_lengths.is_empty() {
510 for j in 0..item.positions.len().saturating_sub(1) {
511 try_seg_rect!(j, j + 1, j as u32);
512 }
513 } else {
514 for &slen in &item.strip_lengths {
515 let slen = slen as usize;
516 for j in 0..slen.saturating_sub(1) {
517 try_seg_rect!(node_off + j, node_off + j + 1, seg_off + j as u32);
518 }
519 seg_off += slen.saturating_sub(1) as u32;
520 node_off += slen;
521 }
522 }
523 }
524
525 if wants_strip {
526 for s in strips_hit {
527 result.elements.push((id, SubObjectRef::Strip(s)));
528 }
529 }
530 if wants_object && item_hit {
531 result.objects.push(id);
532 }
533 }
534 }
535
536 if wants_poly_node || wants_segment || wants_strip || wants_object {
538 let st_tube_iter = self
542 .pick_streamtube_items
543 .iter()
544 .map(|it| {
545 (
546 it.settings.pick_id.0,
547 it.positions.as_slice(),
548 it.strip_lengths.as_slice(),
549 )
550 })
551 .chain(self.pick_tube_items.iter().map(|it| {
552 (
553 it.settings.pick_id.0,
554 it.positions.as_slice(),
555 it.strip_lengths.as_slice(),
556 )
557 }));
558
559 for (id, positions, strip_lengths) in st_tube_iter {
560 if id == 0 || positions.is_empty() {
561 continue;
562 }
563 let mut item_hit = false;
564 let mut strips_hit = std::collections::HashSet::<u32>::new();
565
566 let single_st;
567 let strips_st: &[u32] = if strip_lengths.is_empty() {
568 single_st = [positions.len() as u32];
569 &single_st
570 } else {
571 strip_lengths
572 };
573
574 if wants_poly_node || wants_strip || wants_object {
576 'st_nodes: for (ni, pos) in positions.iter().enumerate() {
577 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
578 if in_rect(sx, sy) {
579 item_hit = true;
580 if wants_poly_node {
581 result.elements.push((id, SubObjectRef::Point(ni as u32)));
582 } else if wants_strip {
583 let s = strip_for_node(ni as u32, strip_lengths);
584 strips_hit.insert(s);
585 } else {
586 break 'st_nodes;
588 }
589 }
590 }
591 }
592 }
593
594 if wants_segment || wants_strip || wants_object {
596 let mut node_off = 0usize;
597 let mut seg_off = 0u32;
598 'st_strips: for &slen in strips_st {
599 let slen = slen as usize;
600 for j in 0..slen.saturating_sub(1) {
601 let seg_idx = seg_off + j as u32;
602 let pa = glam::Vec3::from(positions[node_off + j]);
603 let pb = glam::Vec3::from(positions[node_off + j + 1]);
604 let hit = match (project(view_proj, pa), project(view_proj, pb)) {
605 (Some((ax, ay)), Some((bx, by))) => segment_in_rect(
606 glam::Vec2::new(ax, ay),
607 glam::Vec2::new(bx, by),
608 rect_min,
609 rect_max,
610 ),
611 (Some((ax, ay)), None) => in_rect(ax, ay),
612 (None, Some((bx, by))) => in_rect(bx, by),
613 (None, None) => false,
614 };
615 if hit {
616 item_hit = true;
617 if wants_segment {
618 result.elements.push((id, SubObjectRef::Segment(seg_idx)));
619 } else if wants_strip {
620 let s = strip_for_segment(seg_idx, strip_lengths);
621 strips_hit.insert(s);
622 } else {
623 break 'st_strips;
625 }
626 }
627 }
628 seg_off += slen.saturating_sub(1) as u32;
629 node_off += slen;
630 }
631 }
632
633 if wants_strip {
634 for s in strips_hit {
635 result.elements.push((id, SubObjectRef::Strip(s)));
636 }
637 }
638 if wants_object && item_hit {
639 result.objects.push(id);
640 }
641 }
642
643 for item in &self.pick_ribbon_items {
648 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
649 continue;
650 }
651
652 let single_r;
653 let strips_r: &[u32] = if item.strip_lengths.is_empty() {
654 single_r = [item.positions.len() as u32];
655 &single_r
656 } else {
657 &item.strip_lengths
658 };
659
660 let mut item_hit = false;
661 let mut strips_hit = std::collections::HashSet::<u32>::new();
662
663 let proj2 = |p: glam::Vec3| -> Option<glam::Vec2> {
665 project(view_proj, p).map(|(x, y)| glam::Vec2::new(x, y))
666 };
667
668 if wants_poly_node || wants_strip || wants_object {
670 'rb_nodes: for (ni, pos) in item.positions.iter().enumerate() {
671 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
672 if in_rect(sx, sy) {
673 item_hit = true;
674 if wants_poly_node {
675 result.elements.push((
676 item.settings.pick_id.0,
677 SubObjectRef::Point(ni as u32),
678 ));
679 } else if wants_strip {
680 let s = strip_for_node(ni as u32, &item.strip_lengths);
681 strips_hit.insert(s);
682 } else {
683 break 'rb_nodes;
684 }
685 }
686 }
687 }
688 }
689
690 if wants_segment || wants_strip || wants_object {
692 let frames = ribbon_lateral_frames(
693 &item.positions,
694 &item.strip_lengths,
695 item.width,
696 item.width_attribute.as_deref(),
697 item.twist_attribute.as_deref(),
698 );
699 let mut node_off = 0usize;
700 let mut seg_off = 0u32;
701
702 'rb_strips: for &slen in strips_r {
703 let slen = slen as usize;
704 for k in 0..slen.saturating_sub(1) {
705 let seg_idx = seg_off + k as u32;
706 let ia = node_off + k;
707 let ib = node_off + k + 1;
708 let pa = glam::Vec3::from(item.positions[ia]);
709 let pb = glam::Vec3::from(item.positions[ib]);
710 let (ua, wa) = frames[ia];
711 let (ub, wb) = frames[ib];
712 let c0 = pa + ua * wa; let c1 = pa - ua * wa; let c2 = pb + ub * wb; let c3 = pb - ub * wb; let sc0 = proj2(c0);
717 let sc1 = proj2(c1);
718 let sc2 = proj2(c2);
719 let sc3 = proj2(c3);
720 let edge_hit = |a: Option<glam::Vec2>, b: Option<glam::Vec2>| -> bool {
721 match (a, b) {
722 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
723 (Some(a), None) => in_rect(a.x, a.y),
724 (None, Some(b)) => in_rect(b.x, b.y),
725 (None, None) => false,
726 }
727 };
728 let hit = edge_hit(sc0, sc1)
729 || edge_hit(sc2, sc3)
730 || edge_hit(sc0, sc2)
731 || edge_hit(sc1, sc3);
732 if hit {
733 item_hit = true;
734 if wants_segment {
735 result.elements.push((
736 item.settings.pick_id.0,
737 SubObjectRef::Segment(seg_idx),
738 ));
739 } else if wants_strip {
740 let s = strip_for_segment(seg_idx, &item.strip_lengths);
741 strips_hit.insert(s);
742 } else {
743 break 'rb_strips;
744 }
745 }
746 }
747 seg_off += slen.saturating_sub(1) as u32;
748 node_off += slen;
749 }
750 }
751
752 if wants_strip {
753 for s in strips_hit {
754 result
755 .elements
756 .push((item.settings.pick_id.0, SubObjectRef::Strip(s)));
757 }
758 }
759 if wants_object && item_hit {
760 result.objects.push(item.settings.pick_id.0);
761 }
762 }
763 }
764
765 if wants_object {
767 for item in &self.pick_image_slice_items {
769 if item.settings.pick_id == PickId::NONE {
770 continue;
771 }
772 let [bmin, bmax] = [item.bbox_min, item.bbox_max];
773 let t = item.offset;
774 let corners: [[f32; 3]; 4] = match item.axis {
775 SliceAxis::X => {
776 let x = bmin[0] + t * (bmax[0] - bmin[0]);
777 [
778 [x, bmin[1], bmin[2]],
779 [x, bmax[1], bmin[2]],
780 [x, bmax[1], bmax[2]],
781 [x, bmin[1], bmax[2]],
782 ]
783 }
784 SliceAxis::Y => {
785 let y = bmin[1] + t * (bmax[1] - bmin[1]);
786 [
787 [bmin[0], y, bmin[2]],
788 [bmax[0], y, bmin[2]],
789 [bmax[0], y, bmax[2]],
790 [bmin[0], y, bmax[2]],
791 ]
792 }
793 SliceAxis::Z => {
794 let z = bmin[2] + t * (bmax[2] - bmin[2]);
795 [
796 [bmin[0], bmin[1], z],
797 [bmax[0], bmin[1], z],
798 [bmax[0], bmax[1], z],
799 [bmin[0], bmax[1], z],
800 ]
801 }
802 };
803 let sc: Vec<Option<glam::Vec2>> = corners
804 .iter()
805 .map(|&c| {
806 project(view_proj, glam::Vec3::from(c)).map(|(x, y)| glam::Vec2::new(x, y))
807 })
808 .collect();
809 let hit = sc.iter().any(|p| p.map_or(false, |p| in_rect(p.x, p.y)))
810 || (0..4).any(|i| {
811 let a = sc[i];
812 let b = sc[(i + 1) % 4];
813 match (a, b) {
814 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
815 (Some(a), None) => in_rect(a.x, a.y),
816 (None, Some(b)) => in_rect(b.x, b.y),
817 (None, None) => false,
818 }
819 });
820 if hit {
821 result.objects.push(item.settings.pick_id.0);
822 }
823 }
824
825 for item in &self.pick_volume_surface_slice_items {
827 if item.settings.pick_id == PickId::NONE {
828 continue;
829 }
830 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
831 continue;
832 };
833 let Some(positions) = &mesh.cpu_positions else {
834 continue;
835 };
836 let model = glam::Mat4::from_cols_array_2d(&item.model);
837 let hit = positions.iter().any(|&p| {
838 let wp = model.transform_point3(glam::Vec3::from(p));
839 project(view_proj, wp).map_or(false, |(sx, sy)| in_rect(sx, sy))
840 });
841 if hit {
842 result.objects.push(item.settings.pick_id.0);
843 }
844 }
845
846 for item in &self.pick_screen_image_items {
848 if item.settings.pick_id == PickId::NONE || item.width == 0 || item.height == 0 {
849 continue;
850 }
851 let img_w = item.width as f32 * item.scale;
852 let img_h = item.height as f32 * item.scale;
853 let (sx, sy) = match item.anchor {
854 ImageAnchor::TopLeft => (0.0, 0.0),
855 ImageAnchor::TopRight => (viewport_size.x - img_w, 0.0),
856 ImageAnchor::BottomLeft => (0.0, viewport_size.y - img_h),
857 ImageAnchor::BottomRight => (viewport_size.x - img_w, viewport_size.y - img_h),
858 ImageAnchor::Center => (
859 (viewport_size.x - img_w) * 0.5,
860 (viewport_size.y - img_h) * 0.5,
861 ),
862 };
863 let overlap = sx <= rect_max.x
865 && sx + img_w >= rect_min.x
866 && sy <= rect_max.y
867 && sy + img_h >= rect_min.y;
868 if overlap {
869 result.objects.push(item.settings.pick_id.0);
870 }
871 }
872 }
873
874 if wants_object {
881 for item in &self.pick_implicit_items {
882 let mut hit = false;
883 'prim_loop: for prim in &item.primitives {
884 let (center, radius) = match prim.kind {
886 1 => {
887 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
889 (c, prim.params[3].abs())
890 }
891 2 => {
892 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
894 let h = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
895 (c, h.length())
896 }
897 3 => {
898 continue;
900 }
901 4 => {
902 let a = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
904 let b = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
905 let r = prim.params[3].abs();
906 ((a + b) * 0.5, (b - a).length() * 0.5 + r)
907 }
908 _ => continue,
909 };
910 for dx in [-radius, radius] {
912 for dy in [-radius, radius] {
913 for dz in [-radius, radius] {
914 let corner = center + glam::Vec3::new(dx, dy, dz);
915 if let Some((sx, sy)) = project(view_proj, corner) {
916 if in_rect(sx, sy) {
917 hit = true;
918 break 'prim_loop;
919 }
920 }
921 }
922 }
923 }
924 }
925 if hit {
926 result.objects.push(item.id);
927 }
928 }
929 }
930
931 if wants_object {
937 for item in &self.pick_mc_items {
938 let vol = &item.volume_data;
939 let isovalue = item.isovalue;
940 let [nx, ny, nz] = vol.dims;
941 let origin = glam::Vec3::from(vol.origin);
942 let spacing = glam::Vec3::from(vol.spacing);
943
944 let mut hit = false;
945 'mc_rect: for iz in 0..nz.saturating_sub(1) {
946 for iy in 0..ny.saturating_sub(1) {
947 for ix in 0..nx.saturating_sub(1) {
948 let mut has_below = false;
951 let mut has_above = false;
952 'corners: for dz in 0u32..=1 {
953 for dy in 0u32..=1 {
954 for dx in 0u32..=1 {
955 let s = vol.sample(ix + dx, iy + dy, iz + dz);
956 if s < isovalue {
957 has_below = true;
958 } else {
959 has_above = true;
960 }
961 if has_below && has_above {
962 break 'corners;
963 }
964 }
965 }
966 }
967 if !(has_below && has_above) {
968 continue;
969 }
970 let cell_center = origin
971 + spacing
972 * glam::Vec3::new(
973 ix as f32 + 0.5,
974 iy as f32 + 0.5,
975 iz as f32 + 0.5,
976 );
977 if let Some((sx, sy)) = project(view_proj, cell_center) {
978 if in_rect(sx, sy) {
979 hit = true;
980 break 'mc_rect;
981 }
982 }
983 }
984 }
985 }
986 if hit {
987 result.objects.push(item.id);
988 }
989 }
990 }
991
992 result
993 }
994}