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::select::pick_mask::PickMask,
29 ) -> PickRectResult {
30 use crate::interaction::select::pick_mask::PickMask;
31 use crate::interaction::select::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::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.content.gaussian_splat_store.get(item.source)
347 else {
348 continue;
349 };
350 if gpu_set.cpu_positions.is_empty() {
351 continue;
352 }
353 let model = glam::Mat4::from_cols_array_2d(&item.model);
354 let mvp = view_proj * model;
355 let id = item.settings.pick_id.0;
356 let mut item_hit = false;
357
358 for (i, pos) in gpu_set.cpu_positions.iter().enumerate() {
359 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
360 if in_rect(sx, sy) {
361 if wants_splat {
362 result.elements.push((id, SubObjectRef::Splat(i as u32)));
363 }
364 item_hit = true;
365 }
366 }
367 }
368
369 if wants_object && item_hit {
370 result.objects.push(id);
371 }
372 }
373 }
374
375 let wants_instance = mask.intersects(PickMask::INSTANCE);
377 if wants_instance || wants_object {
378 for item in &self.pick_glyph_items {
380 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
381 continue;
382 }
383 let model = glam::Mat4::from_cols_array_2d(&item.model);
384 let mvp = view_proj * model;
385 let id = item.settings.pick_id.0;
386 let mut item_hit = false;
387 for (i, pos) in item.positions.iter().enumerate() {
388 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
389 if in_rect(sx, sy) {
390 if wants_instance {
391 result.elements.push((id, SubObjectRef::Instance(i as u32)));
392 }
393 item_hit = true;
394 }
395 }
396 }
397 if wants_object && item_hit {
398 result.objects.push(id);
399 }
400 }
401
402 for item in &self.pick_tensor_glyph_items {
404 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
405 continue;
406 }
407 let model = glam::Mat4::from_cols_array_2d(&item.model);
408 let mvp = view_proj * model;
409 let id = item.settings.pick_id.0;
410 let mut item_hit = false;
411 for (i, pos) in item.positions.iter().enumerate() {
412 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
413 if in_rect(sx, sy) {
414 if wants_instance {
415 result.elements.push((id, SubObjectRef::Instance(i as u32)));
416 }
417 item_hit = true;
418 }
419 }
420 }
421 if wants_object && item_hit {
422 result.objects.push(id);
423 }
424 }
425
426 for item in &self.pick_sprite_items {
428 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
429 continue;
430 }
431 let model = glam::Mat4::from_cols_array_2d(&item.model);
432 let mvp = view_proj * model;
433 let id = item.settings.pick_id.0;
434 let mut item_hit = false;
435 for (i, pos) in item.positions.iter().enumerate() {
436 if let Some((sx, sy)) = project(mvp, glam::Vec3::from(*pos)) {
437 if in_rect(sx, sy) {
438 if wants_instance {
439 result.elements.push((id, SubObjectRef::Instance(i as u32)));
440 }
441 item_hit = true;
442 }
443 }
444 }
445 if wants_object && item_hit {
446 result.objects.push(id);
447 }
448 }
449 }
450
451 let wants_poly_node = mask.intersects(PickMask::POLY_NODE);
453 let wants_segment = mask.intersects(PickMask::SEGMENT);
454 let wants_strip = mask.intersects(PickMask::STRIP);
455 if wants_poly_node || wants_segment || wants_strip || wants_object {
456 for item in &self.pick_polyline_items {
457 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
458 continue;
459 }
460 let id = item.settings.pick_id.0;
461 let mut item_hit = false;
462 let mut strips_hit = std::collections::HashSet::<u32>::new();
463
464 if wants_poly_node || wants_strip || wants_object {
466 for (node_idx, pos) in item.positions.iter().enumerate() {
467 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
468 if in_rect(sx, sy) {
469 item_hit = true;
470 if wants_poly_node {
471 result
472 .elements
473 .push((id, SubObjectRef::Point(node_idx as u32)));
474 } else if wants_strip {
475 let s = strip_for_node(node_idx as u32, &item.strip_lengths);
476 strips_hit.insert(s);
477 }
478 }
479 }
480 }
481 }
482
483 if wants_segment || (wants_strip && !wants_poly_node) || wants_object {
485 let mut node_off = 0usize;
486 let mut seg_off = 0u32;
487 macro_rules! try_seg_rect {
488 ($ai:expr, $bi:expr, $seg:expr) => {{
489 if let (Some((sax, say)), Some((sbx, sby))) = (
490 project(view_proj, glam::Vec3::from(item.positions[$ai])),
491 project(view_proj, glam::Vec3::from(item.positions[$bi])),
492 ) {
493 if segment_in_rect(
494 glam::Vec2::new(sax, say),
495 glam::Vec2::new(sbx, sby),
496 rect_min,
497 rect_max,
498 ) {
499 item_hit = true;
500 if wants_segment {
501 result.elements.push((id, SubObjectRef::Segment($seg)));
502 } else if wants_strip {
503 let s = strip_for_segment($seg, &item.strip_lengths);
504 strips_hit.insert(s);
505 }
506 }
507 }
508 }};
509 }
510 if item.strip_lengths.is_empty() {
511 for j in 0..item.positions.len().saturating_sub(1) {
512 try_seg_rect!(j, j + 1, j as u32);
513 }
514 } else {
515 for &slen in &item.strip_lengths {
516 let slen = slen as usize;
517 for j in 0..slen.saturating_sub(1) {
518 try_seg_rect!(node_off + j, node_off + j + 1, seg_off + j as u32);
519 }
520 seg_off += slen.saturating_sub(1) as u32;
521 node_off += slen;
522 }
523 }
524 }
525
526 if wants_strip {
527 for s in strips_hit {
528 result.elements.push((id, SubObjectRef::Strip(s)));
529 }
530 }
531 if wants_object && item_hit {
532 result.objects.push(id);
533 }
534 }
535 }
536
537 if wants_poly_node || wants_segment || wants_strip || wants_object {
539 let st_tube_iter = self
543 .pick_streamtube_items
544 .iter()
545 .map(|it| {
546 (
547 it.settings.pick_id.0,
548 it.positions.as_slice(),
549 it.strip_lengths.as_slice(),
550 )
551 })
552 .chain(self.pick_tube_items.iter().map(|it| {
553 (
554 it.settings.pick_id.0,
555 it.positions.as_slice(),
556 it.strip_lengths.as_slice(),
557 )
558 }));
559
560 for (id, positions, strip_lengths) in st_tube_iter {
561 if id == 0 || positions.is_empty() {
562 continue;
563 }
564 let mut item_hit = false;
565 let mut strips_hit = std::collections::HashSet::<u32>::new();
566
567 let single_st;
568 let strips_st: &[u32] = if strip_lengths.is_empty() {
569 single_st = [positions.len() as u32];
570 &single_st
571 } else {
572 strip_lengths
573 };
574
575 if wants_poly_node || wants_strip || wants_object {
577 'st_nodes: for (ni, pos) in positions.iter().enumerate() {
578 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
579 if in_rect(sx, sy) {
580 item_hit = true;
581 if wants_poly_node {
582 result.elements.push((id, SubObjectRef::Point(ni as u32)));
583 } else if wants_strip {
584 let s = strip_for_node(ni as u32, strip_lengths);
585 strips_hit.insert(s);
586 } else {
587 break 'st_nodes;
589 }
590 }
591 }
592 }
593 }
594
595 if wants_segment || wants_strip || wants_object {
597 let mut node_off = 0usize;
598 let mut seg_off = 0u32;
599 'st_strips: for &slen in strips_st {
600 let slen = slen as usize;
601 for j in 0..slen.saturating_sub(1) {
602 let seg_idx = seg_off + j as u32;
603 let pa = glam::Vec3::from(positions[node_off + j]);
604 let pb = glam::Vec3::from(positions[node_off + j + 1]);
605 let hit = match (project(view_proj, pa), project(view_proj, pb)) {
606 (Some((ax, ay)), Some((bx, by))) => segment_in_rect(
607 glam::Vec2::new(ax, ay),
608 glam::Vec2::new(bx, by),
609 rect_min,
610 rect_max,
611 ),
612 (Some((ax, ay)), None) => in_rect(ax, ay),
613 (None, Some((bx, by))) => in_rect(bx, by),
614 (None, None) => false,
615 };
616 if hit {
617 item_hit = true;
618 if wants_segment {
619 result.elements.push((id, SubObjectRef::Segment(seg_idx)));
620 } else if wants_strip {
621 let s = strip_for_segment(seg_idx, strip_lengths);
622 strips_hit.insert(s);
623 } else {
624 break 'st_strips;
626 }
627 }
628 }
629 seg_off += slen.saturating_sub(1) as u32;
630 node_off += slen;
631 }
632 }
633
634 if wants_strip {
635 for s in strips_hit {
636 result.elements.push((id, SubObjectRef::Strip(s)));
637 }
638 }
639 if wants_object && item_hit {
640 result.objects.push(id);
641 }
642 }
643
644 for item in &self.pick_ribbon_items {
649 if item.settings.pick_id == PickId::NONE || item.positions.is_empty() {
650 continue;
651 }
652
653 let single_r;
654 let strips_r: &[u32] = if item.strip_lengths.is_empty() {
655 single_r = [item.positions.len() as u32];
656 &single_r
657 } else {
658 &item.strip_lengths
659 };
660
661 let mut item_hit = false;
662 let mut strips_hit = std::collections::HashSet::<u32>::new();
663
664 let proj2 = |p: glam::Vec3| -> Option<glam::Vec2> {
666 project(view_proj, p).map(|(x, y)| glam::Vec2::new(x, y))
667 };
668
669 if wants_poly_node || wants_strip || wants_object {
671 'rb_nodes: for (ni, pos) in item.positions.iter().enumerate() {
672 if let Some((sx, sy)) = project(view_proj, glam::Vec3::from(*pos)) {
673 if in_rect(sx, sy) {
674 item_hit = true;
675 if wants_poly_node {
676 result.elements.push((
677 item.settings.pick_id.0,
678 SubObjectRef::Point(ni as u32),
679 ));
680 } else if wants_strip {
681 let s = strip_for_node(ni as u32, &item.strip_lengths);
682 strips_hit.insert(s);
683 } else {
684 break 'rb_nodes;
685 }
686 }
687 }
688 }
689 }
690
691 if wants_segment || wants_strip || wants_object {
693 let frames = ribbon_lateral_frames(
694 &item.positions,
695 &item.strip_lengths,
696 item.width,
697 item.width_attribute.as_deref(),
698 item.twist_attribute.as_deref(),
699 );
700 let mut node_off = 0usize;
701 let mut seg_off = 0u32;
702
703 'rb_strips: for &slen in strips_r {
704 let slen = slen as usize;
705 for k in 0..slen.saturating_sub(1) {
706 let seg_idx = seg_off + k as u32;
707 let ia = node_off + k;
708 let ib = node_off + k + 1;
709 let pa = glam::Vec3::from(item.positions[ia]);
710 let pb = glam::Vec3::from(item.positions[ib]);
711 let (ua, wa) = frames[ia];
712 let (ub, wb) = frames[ib];
713 let c0 = pa + ua * wa; let c1 = pa - ua * wa; let c2 = pb + ub * wb; let c3 = pb - ub * wb; let sc0 = proj2(c0);
718 let sc1 = proj2(c1);
719 let sc2 = proj2(c2);
720 let sc3 = proj2(c3);
721 let edge_hit = |a: Option<glam::Vec2>, b: Option<glam::Vec2>| -> bool {
722 match (a, b) {
723 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
724 (Some(a), None) => in_rect(a.x, a.y),
725 (None, Some(b)) => in_rect(b.x, b.y),
726 (None, None) => false,
727 }
728 };
729 let hit = edge_hit(sc0, sc1)
730 || edge_hit(sc2, sc3)
731 || edge_hit(sc0, sc2)
732 || edge_hit(sc1, sc3);
733 if hit {
734 item_hit = true;
735 if wants_segment {
736 result.elements.push((
737 item.settings.pick_id.0,
738 SubObjectRef::Segment(seg_idx),
739 ));
740 } else if wants_strip {
741 let s = strip_for_segment(seg_idx, &item.strip_lengths);
742 strips_hit.insert(s);
743 } else {
744 break 'rb_strips;
745 }
746 }
747 }
748 seg_off += slen.saturating_sub(1) as u32;
749 node_off += slen;
750 }
751 }
752
753 if wants_strip {
754 for s in strips_hit {
755 result
756 .elements
757 .push((item.settings.pick_id.0, SubObjectRef::Strip(s)));
758 }
759 }
760 if wants_object && item_hit {
761 result.objects.push(item.settings.pick_id.0);
762 }
763 }
764 }
765
766 if wants_object {
768 for item in &self.pick_image_slice_items {
770 if item.settings.pick_id == PickId::NONE {
771 continue;
772 }
773 let [bmin, bmax] = [item.bbox_min, item.bbox_max];
774 let t = item.offset;
775 let corners: [[f32; 3]; 4] = match item.axis {
776 SliceAxis::X => {
777 let x = bmin[0] + t * (bmax[0] - bmin[0]);
778 [
779 [x, bmin[1], bmin[2]],
780 [x, bmax[1], bmin[2]],
781 [x, bmax[1], bmax[2]],
782 [x, bmin[1], bmax[2]],
783 ]
784 }
785 SliceAxis::Y => {
786 let y = bmin[1] + t * (bmax[1] - bmin[1]);
787 [
788 [bmin[0], y, bmin[2]],
789 [bmax[0], y, bmin[2]],
790 [bmax[0], y, bmax[2]],
791 [bmin[0], y, bmax[2]],
792 ]
793 }
794 SliceAxis::Z => {
795 let z = bmin[2] + t * (bmax[2] - bmin[2]);
796 [
797 [bmin[0], bmin[1], z],
798 [bmax[0], bmin[1], z],
799 [bmax[0], bmax[1], z],
800 [bmin[0], bmax[1], z],
801 ]
802 }
803 };
804 let sc: Vec<Option<glam::Vec2>> = corners
805 .iter()
806 .map(|&c| {
807 project(view_proj, glam::Vec3::from(c)).map(|(x, y)| glam::Vec2::new(x, y))
808 })
809 .collect();
810 let hit = sc.iter().any(|p| p.map_or(false, |p| in_rect(p.x, p.y)))
811 || (0..4).any(|i| {
812 let a = sc[i];
813 let b = sc[(i + 1) % 4];
814 match (a, b) {
815 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
816 (Some(a), None) => in_rect(a.x, a.y),
817 (None, Some(b)) => in_rect(b.x, b.y),
818 (None, None) => false,
819 }
820 });
821 if hit {
822 result.objects.push(item.settings.pick_id.0);
823 }
824 }
825
826 for item in &self.pick_volume_surface_slice_items {
828 if item.settings.pick_id == PickId::NONE {
829 continue;
830 }
831 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
832 continue;
833 };
834 let Some(positions) = &mesh.cpu_positions else {
835 continue;
836 };
837 let model = glam::Mat4::from_cols_array_2d(&item.model);
838 let hit = positions.iter().any(|&p| {
839 let wp = model.transform_point3(glam::Vec3::from(p));
840 project(view_proj, wp).map_or(false, |(sx, sy)| in_rect(sx, sy))
841 });
842 if hit {
843 result.objects.push(item.settings.pick_id.0);
844 }
845 }
846
847 for item in &self.pick_screen_image_items {
849 if item.settings.pick_id == PickId::NONE || item.width == 0 || item.height == 0 {
850 continue;
851 }
852 let img_w = item.width as f32 * item.scale;
853 let img_h = item.height as f32 * item.scale;
854 let (sx, sy) = match item.anchor {
855 ImageAnchor::TopLeft => (0.0, 0.0),
856 ImageAnchor::TopRight => (viewport_size.x - img_w, 0.0),
857 ImageAnchor::BottomLeft => (0.0, viewport_size.y - img_h),
858 ImageAnchor::BottomRight => (viewport_size.x - img_w, viewport_size.y - img_h),
859 ImageAnchor::Center => (
860 (viewport_size.x - img_w) * 0.5,
861 (viewport_size.y - img_h) * 0.5,
862 ),
863 };
864 let overlap = sx <= rect_max.x
866 && sx + img_w >= rect_min.x
867 && sy <= rect_max.y
868 && sy + img_h >= rect_min.y;
869 if overlap {
870 result.objects.push(item.settings.pick_id.0);
871 }
872 }
873 }
874
875 if wants_object {
882 for item in &self.pick_implicit_items {
883 let mut hit = false;
884 'prim_loop: for prim in &item.primitives {
885 let (center, radius) = match prim.kind {
887 1 => {
888 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
890 (c, prim.params[3].abs())
891 }
892 2 => {
893 let c = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
895 let h = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
896 (c, h.length())
897 }
898 3 => {
899 continue;
901 }
902 4 => {
903 let a = glam::Vec3::new(prim.params[0], prim.params[1], prim.params[2]);
905 let b = glam::Vec3::new(prim.params[4], prim.params[5], prim.params[6]);
906 let r = prim.params[3].abs();
907 ((a + b) * 0.5, (b - a).length() * 0.5 + r)
908 }
909 _ => continue,
910 };
911 for dx in [-radius, radius] {
913 for dy in [-radius, radius] {
914 for dz in [-radius, radius] {
915 let corner = center + glam::Vec3::new(dx, dy, dz);
916 if let Some((sx, sy)) = project(view_proj, corner) {
917 if in_rect(sx, sy) {
918 hit = true;
919 break 'prim_loop;
920 }
921 }
922 }
923 }
924 }
925 }
926 if hit {
927 result.objects.push(item.id);
928 }
929 }
930 }
931
932 if wants_object {
938 for item in &self.pick_mc_items {
939 let vol = &item.volume_data;
940 let isovalue = item.isovalue;
941 let [nx, ny, nz] = vol.dims;
942 let origin = glam::Vec3::from(vol.origin);
943 let spacing = glam::Vec3::from(vol.spacing);
944
945 let mut hit = false;
946 'mc_rect: for iz in 0..nz.saturating_sub(1) {
947 for iy in 0..ny.saturating_sub(1) {
948 for ix in 0..nx.saturating_sub(1) {
949 let mut has_below = false;
952 let mut has_above = false;
953 'corners: for dz in 0u32..=1 {
954 for dy in 0u32..=1 {
955 for dx in 0u32..=1 {
956 let s = vol.sample(ix + dx, iy + dy, iz + dz);
957 if s < isovalue {
958 has_below = true;
959 } else {
960 has_above = true;
961 }
962 if has_below && has_above {
963 break 'corners;
964 }
965 }
966 }
967 }
968 if !(has_below && has_above) {
969 continue;
970 }
971 let cell_center = origin
972 + spacing
973 * glam::Vec3::new(
974 ix as f32 + 0.5,
975 iy as f32 + 0.5,
976 iz as f32 + 0.5,
977 );
978 if let Some((sx, sy)) = project(view_proj, cell_center) {
979 if in_rect(sx, sy) {
980 hit = true;
981 break 'mc_rect;
982 }
983 }
984 }
985 }
986 }
987 if hit {
988 result.objects.push(item.id);
989 }
990 }
991 }
992
993 if wants_object {
998 const CORNERS: [[f32; 3]; 8] = [
1001 [-0.5, -0.5, -0.5],
1002 [0.5, -0.5, -0.5],
1003 [-0.5, 0.5, -0.5],
1004 [0.5, 0.5, -0.5],
1005 [-0.5, -0.5, 0.5],
1006 [0.5, -0.5, 0.5],
1007 [-0.5, 0.5, 0.5],
1008 [0.5, 0.5, 0.5],
1009 ];
1010 const EDGES: [(usize, usize); 12] = [
1011 (0, 1),
1012 (0, 2),
1013 (0, 4),
1014 (1, 3),
1015 (1, 5),
1016 (2, 3),
1017 (2, 6),
1018 (3, 7),
1019 (4, 5),
1020 (4, 6),
1021 (5, 7),
1022 (6, 7),
1023 ];
1024 for item in &self.pick_decal_items {
1025 if item.settings.hidden || item.settings.pick_id == PickId::NONE {
1026 continue;
1027 }
1028 let model = glam::Mat4::from_cols_array_2d(&item.transform);
1029 if model.determinant().abs() < 1e-12 {
1030 continue;
1031 }
1032 let mvp = view_proj * model;
1033 let sc: [Option<glam::Vec2>; 8] = std::array::from_fn(|i| {
1034 project(mvp, glam::Vec3::from(CORNERS[i])).map(|(x, y)| glam::Vec2::new(x, y))
1035 });
1036 let hit = sc.iter().any(|p| p.map_or(false, |p| in_rect(p.x, p.y)))
1037 || EDGES.iter().any(|&(a, b)| match (sc[a], sc[b]) {
1038 (Some(a), Some(b)) => segment_in_rect(a, b, rect_min, rect_max),
1039 (Some(a), None) => in_rect(a.x, a.y),
1040 (None, Some(b)) => in_rect(b.x, b.y),
1041 (None, None) => false,
1042 });
1043 if hit {
1044 result.objects.push(item.settings.pick_id.0);
1045 }
1046 }
1047 }
1048
1049 result
1050 }
1051}