//! Shared spatial broad phase for cursor and area interactions. //! //! Exact snap and selection rules remain in their consumers. This index only //! narrows large resident wire sets to local wire/sub-primitive candidates: //! compact world-XY grids for plan views and projected 3D BVHs for tilted or //! perspective views. use crate::scene::model::wire_model::WireModel; use glam::{DVec3, Mat4}; use iced::Rectangle; use std::sync::Arc; const TARGET_ENTRIES_PER_CELL: usize = 8; const MAX_SPAN_CELLS: u64 = 64; const MAX_AXIS_CELLS: u32 = 8_192; const BVH_LEAF_SIZE: usize = 16; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct SegmentRef { pub wire: u32, pub start: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct SnapPointRef { pub wire: u32, pub index: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct KeyVertexRef { pub wire: u32, pub index: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct KeySegmentRef { pub wire: u32, pub start: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TriangleRef { pub wire: u32, pub start: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct GlyphRef { pub wire: u32, pub start: u32, } #[derive(Clone, Copy)] struct Entry3 { aabb: [f64; 6], value: T, } fn entry_aabb2(entry: &Entry3) -> [f64; 4] { [entry.aabb[0], entry.aabb[1], entry.aabb[3], entry.aabb[4]] } #[derive(Clone, Copy)] struct BvhNode2 { aabb: [f64; 4], left: u32, right: u32, start: u32, len: u32, } struct SpatialBvh2 { nodes: Vec, order: Vec, } impl SpatialBvh2 { fn build(entries: &[Entry3], mut order: Vec) -> Self { let mut nodes = Vec::new(); if !order.is_empty() { build_bvh2_node(entries, &mut order, 0, &mut nodes); } Self { nodes, order } } fn query(&self, entries: &[Entry3], query: [f64; 4], out: &mut Vec) { if self.nodes.is_empty() { return; } let mut stack = vec![0u32]; while let Some(node_idx) = stack.pop() { let node = self.nodes[node_idx as usize]; if !aabb_overlaps(node.aabb, query) { continue; } if node.left == u32::MAX { for &entry_idx in &self.order[node.start as usize..(node.start + node.len) as usize] { if aabb_overlaps(entry_aabb2(&entries[entry_idx as usize]), query) { out.push(entry_idx); } } } else { stack.push(node.left); stack.push(node.right); } } } } fn build_bvh2_node( entries: &[Entry3], order: &mut [u32], base: usize, nodes: &mut Vec, ) -> u32 { let mut bounds = [ f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, ]; for &idx in order.iter() { let aabb = entry_aabb2(&entries[idx as usize]); bounds[0] = bounds[0].min(aabb[0]); bounds[1] = bounds[1].min(aabb[1]); bounds[2] = bounds[2].max(aabb[2]); bounds[3] = bounds[3].max(aabb[3]); } let node_idx = nodes.len() as u32; nodes.push(BvhNode2 { aabb: bounds, left: u32::MAX, right: u32::MAX, start: base as u32, len: order.len() as u32, }); if order.len() <= BVH_LEAF_SIZE { return node_idx; } let axis = usize::from(bounds[3] - bounds[1] > bounds[2] - bounds[0]); order.sort_unstable_by(|&a, &b| { let aa = entry_aabb2(&entries[a as usize]); let bb = entry_aabb2(&entries[b as usize]); (aa[axis] + aa[axis + 2]).total_cmp(&(bb[axis] + bb[axis + 2])) }); let mid = order.len() / 2; let (left_order, right_order) = order.split_at_mut(mid); let left = build_bvh2_node(entries, left_order, base, nodes); let right = build_bvh2_node(entries, right_order, base + mid, nodes); nodes[node_idx as usize].left = left; nodes[node_idx as usize].right = right; node_idx } #[derive(Clone, Copy)] struct BvhNode3 { aabb: [f64; 6], left: u32, right: u32, start: u32, len: u32, } struct SpatialBvh3 { entries: Vec>, nodes: Vec, order: Vec, } impl SpatialBvh3 { fn build(entries: Vec>) -> Self { let mut order: Vec = (0..entries.len() as u32).collect(); let mut nodes = Vec::new(); if !order.is_empty() { build_bvh3_node(&entries, &mut order, 0, &mut nodes); } Self { entries, nodes, order, } } fn query_screen( &self, screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> Vec { if self.nodes.is_empty() { return Vec::new(); } let mut stack = vec![0u32]; let mut entry_indices = Vec::new(); while let Some(node_idx) = stack.pop() { let node = self.nodes[node_idx as usize]; if !aabb3_projects_into(node.aabb, screen_rect, view_rot, eye, bounds) { continue; } if node.left == u32::MAX { for &entry_idx in &self.order[node.start as usize..(node.start + node.len) as usize] { let entry = self.entries[entry_idx as usize]; if aabb3_projects_into(entry.aabb, screen_rect, view_rot, eye, bounds) { entry_indices.push(entry.value); } } } else { stack.push(node.left); stack.push(node.right); } } entry_indices.sort_unstable(); entry_indices.dedup(); entry_indices } } fn build_bvh3_node( entries: &[Entry3], order: &mut [u32], base: usize, nodes: &mut Vec, ) -> u32 { let mut bounds = [ f64::INFINITY, f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, ]; for &idx in order.iter() { let aabb = entries[idx as usize].aabb; for axis in 0..3 { bounds[axis] = bounds[axis].min(aabb[axis]); bounds[axis + 3] = bounds[axis + 3].max(aabb[axis + 3]); } } let node_idx = nodes.len() as u32; nodes.push(BvhNode3 { aabb: bounds, left: u32::MAX, right: u32::MAX, start: base as u32, len: order.len() as u32, }); if order.len() <= BVH_LEAF_SIZE { return node_idx; } let mut axis = 0; for candidate in 1..3 { if bounds[candidate + 3] - bounds[candidate] > bounds[axis + 3] - bounds[axis] { axis = candidate; } } order.sort_unstable_by(|&a, &b| { let aa = entries[a as usize].aabb; let bb = entries[b as usize].aabb; (aa[axis] + aa[axis + 3]).total_cmp(&(bb[axis] + bb[axis + 3])) }); let mid = order.len() / 2; let (left_order, right_order) = order.split_at_mut(mid); let left = build_bvh3_node(entries, left_order, base, nodes); let right = build_bvh3_node(entries, right_order, base + mid, nodes); nodes[node_idx as usize].left = left; nodes[node_idx as usize].right = right; node_idx } struct SpatialGrid { min: [f64; 2], cell: f64, cols: u32, rows: u32, cell_offsets: Vec, cell_entries: Vec, oversized: SpatialBvh2, } impl SpatialGrid { fn build(entries: &[Entry3]) -> Self { if entries.is_empty() { return Self { min: [0.0; 2], cell: 1.0, cols: 1, rows: 1, cell_offsets: vec![0, 0], cell_entries: Vec::new(), oversized: SpatialBvh2 { nodes: Vec::new(), order: Vec::new(), }, }; } let mut min = [f64::INFINITY; 2]; let mut max = [f64::NEG_INFINITY; 2]; for entry in entries { let aabb = entry_aabb2(entry); min[0] = min[0].min(aabb[0]); min[1] = min[1].min(aabb[1]); max[0] = max[0].max(aabb[2]); max[1] = max[1].max(aabb[3]); } let ext_x = (max[0] - min[0]).max(1e-9); let ext_y = (max[1] - min[1]).max(1e-9); let target_cells = ((entries.len() / TARGET_ENTRIES_PER_CELL).max(1)) as f64; let cell = ((ext_x * ext_y) / target_cells) .sqrt() .max(ext_x / (MAX_AXIS_CELLS - 1) as f64) .max(ext_y / (MAX_AXIS_CELLS - 1) as f64) .max(1e-9); let cols = (((ext_x / cell).ceil() as u64) + 1).clamp(1, MAX_AXIS_CELLS as u64) as u32; let rows = (((ext_y / cell).ceil() as u64) + 1).clamp(1, MAX_AXIS_CELLS as u64) as u32; let cell_count = cols as usize * rows as usize; let mut counts = vec![0u32; cell_count]; let mut oversized = Vec::new(); let col_of = |x: f64| (((x - min[0]) / cell).floor()).clamp(0.0, (cols - 1) as f64) as u32; let row_of = |y: f64| (((y - min[1]) / cell).floor()).clamp(0.0, (rows - 1) as f64) as u32; for (idx, entry) in entries.iter().enumerate() { let aabb = entry_aabb2(entry); let c0 = col_of(aabb[0]); let c1 = col_of(aabb[2]); let r0 = row_of(aabb[1]); let r1 = row_of(aabb[3]); let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64; if span > MAX_SPAN_CELLS { oversized.push(idx as u32); continue; } for row in r0..=r1 { let base = row as usize * cols as usize; for col in c0..=c1 { counts[base + col as usize] += 1; } } } let mut cell_offsets = Vec::with_capacity(cell_count + 1); cell_offsets.push(0); for count in counts { cell_offsets.push(cell_offsets.last().copied().unwrap_or(0) + count); } let mut cell_entries = vec![0u32; *cell_offsets.last().unwrap_or(&0) as usize]; let mut cursors = cell_offsets[..cell_count].to_vec(); for (idx, entry) in entries.iter().enumerate() { let aabb = entry_aabb2(entry); let c0 = col_of(aabb[0]); let c1 = col_of(aabb[2]); let r0 = row_of(aabb[1]); let r1 = row_of(aabb[3]); let span = (c1 - c0 + 1) as u64 * (r1 - r0 + 1) as u64; if span > MAX_SPAN_CELLS { continue; } for row in r0..=r1 { let base = row as usize * cols as usize; for col in c0..=c1 { let cell_idx = base + col as usize; let cursor = &mut cursors[cell_idx]; cell_entries[*cursor as usize] = idx as u32; *cursor += 1; } } } let oversized = SpatialBvh2::build(&entries, oversized); Self { min, cell, cols, rows, cell_offsets, cell_entries, oversized, } } fn query(&self, entries: &[Entry3], query: [f64; 4]) -> Vec { let mut entry_indices = Vec::new(); self.oversized.query(entries, query, &mut entry_indices); let grid_max_x = self.min[0] + self.cols as f64 * self.cell; let grid_max_y = self.min[1] + self.rows as f64 * self.cell; if query[2] >= self.min[0] && query[3] >= self.min[1] && query[0] <= grid_max_x && query[1] <= grid_max_y { let col = |x: f64| { (((x - self.min[0]) / self.cell).floor()).clamp(0.0, (self.cols - 1) as f64) as u32 }; let row = |y: f64| { (((y - self.min[1]) / self.cell).floor()).clamp(0.0, (self.rows - 1) as f64) as u32 }; for r in row(query[1])..=row(query[3]) { let base = r as usize * self.cols as usize; for c in col(query[0])..=col(query[2]) { let cell_idx = base + c as usize; let start = self.cell_offsets[cell_idx] as usize; let end = self.cell_offsets[cell_idx + 1] as usize; entry_indices.extend_from_slice(&self.cell_entries[start..end]); } } } entry_indices.sort_unstable(); entry_indices.dedup(); let mut out: Vec = entry_indices .into_iter() .filter_map(|idx| { let entry = entries.get(idx as usize)?; aabb_overlaps(entry_aabb2(entry), query).then_some(entry.value) }) .collect(); out.sort_unstable(); out.dedup(); out } } struct SpatialSet { xy: SpatialGrid, xyz: SpatialBvh3, } impl SpatialSet { fn build(entries: Vec>) -> Self { let xy = SpatialGrid::build(&entries); Self { xy, xyz: SpatialBvh3::build(entries), } } fn query_xy(&self, aabb: [f64; 4]) -> Vec { self.xy.query(&self.xyz.entries, aabb) } fn query_screen( &self, screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> Vec { self.xyz.query_screen(screen_rect, view_rot, eye, bounds) } } pub struct InteractionIndex { wires: SpatialSet, /// Stable entity handle for each source wire. Lets a stale-but-valid base /// index feed an incremental overlay after small edits without retaining /// the old heavyweight wire set or trusting shifted vector indices. wire_handles: Vec>, segments: SpatialSet, snap_points: SpatialSet, key_vertices: SpatialSet, key_segments: SpatialSet, fill_triangles: SpatialSet, pick_triangles: SpatialSet, glyphs: SpatialSet, unbounded_wires: Vec, max_line_half_width_px: f32, } pub struct InteractionHandleIndex { handles: SpatialSet, } impl InteractionHandleIndex { pub fn build(entries: impl IntoIterator) -> Self { Self { handles: SpatialSet::build( entries .into_iter() .filter(|(_, aabb)| { aabb.iter().all(|value| value.is_finite()) && aabb[0] <= aabb[3] && aabb[1] <= aabb[4] && aabb[2] <= aabb[5] }) .map(|(value, aabb)| Entry3 { aabb, value }) .collect(), ), } } pub fn query_xy(&self, aabb: [f64; 4]) -> Vec { self.handles.query_xy(aabb) } pub fn query_screen( &self, screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> Vec { self.handles .query_screen(screen_rect, view_rot, eye, bounds) } } impl InteractionIndex { pub fn estimated_work(wires: &[WireModel]) -> usize { wires.iter().fold(0usize, |total, wire| { total .saturating_add(wire.points.len()) .saturating_add(wire.key_vertices.len()) .saturating_add(wire.snap_pts.len() * 2) .saturating_add(wire.fill_tris.len()) .saturating_add(wire.pick_tris.len()) .saturating_add(wire.text_verts.len() / 2) }) } pub fn build(wires: &[WireModel]) -> Self { let wire_handles: Vec> = wires .iter() .map(|wire| wire.name.parse::().ok()) .collect(); let mut wire_entries = Vec::with_capacity(wires.len()); let mut segment_entries = Vec::new(); let mut snap_point_entries = Vec::new(); let mut key_vertex_entries = Vec::new(); let mut key_segment_entries = Vec::new(); let mut fill_triangle_entries = Vec::new(); let mut pick_triangle_entries = Vec::new(); let mut glyph_entries = Vec::new(); let mut unbounded_wires = Vec::new(); let mut max_line_half_width_px = 0.0f32; for (wire_idx, wire) in wires.iter().enumerate() { if wire.line_weight_px.is_finite() { max_line_half_width_px = max_line_half_width_px.max((wire.line_weight_px * 0.5).max(0.0)); } let wire_idx = wire_idx as u32; if let Some(aabb) = finite_wire_aabb3(wire) { wire_entries.push(Entry3 { aabb, value: wire_idx, }); } else { unbounded_wires.push(wire_idx); } for start in 0..wire.points.len().saturating_sub(1) { let a = wire_point(wire, start); let b = wire_point(wire, start + 1); let Some(aabb) = points_aabb3([a, b]) else { continue; }; segment_entries.push(Entry3 { aabb, value: SegmentRef { wire: wire_idx, start: start as u32, }, }); } for (index, (point, _)) in wire.snap_pts.iter().enumerate() { if !point.is_finite() { continue; } snap_point_entries.push(Entry3 { aabb: [point.x, point.y, point.z, point.x, point.y, point.z], value: SnapPointRef { wire: wire_idx, index: index as u32, }, }); } for (index, &point) in wire.key_vertices.iter().enumerate() { if !point.iter().all(|value| value.is_finite()) { continue; } key_vertex_entries.push(Entry3 { aabb: [point[0], point[1], point[2], point[0], point[1], point[2]], value: KeyVertexRef { wire: wire_idx, index: index as u32, }, }); } for start in 0..wire.key_vertices.len().saturating_sub(1) { let Some(aabb) = points_aabb3([wire.key_vertices[start], wire.key_vertices[start + 1]]) else { continue; }; key_segment_entries.push(Entry3 { aabb, value: KeySegmentRef { wire: wire_idx, start: start as u32, }, }); } append_triangle_entries( wire_idx, &wire.fill_tris, &wire.fill_tris_low, &mut fill_triangle_entries, ); append_triangle_entries( wire_idx, &wire.pick_tris, &wire.pick_tris_low, &mut pick_triangle_entries, ); for start in (0..wire.text_verts.len()).step_by(6) { let Some(quad) = wire.text_verts.get(start..start + 6) else { break; }; let Some(aabb) = points_aabb3(quad.iter().map(|vertex| { [ vertex.pos[0] as f64 + vertex.pos_low[0] as f64, vertex.pos[1] as f64 + vertex.pos_low[1] as f64, vertex.pos[2] as f64 + vertex.pos_low[2] as f64, ] })) else { continue; }; glyph_entries.push(Entry3 { aabb, value: GlyphRef { wire: wire_idx, start: start as u32, }, }); } } #[cfg(not(target_arch = "wasm32"))] let ( ((wires, segments), (snap_points, key_vertices)), ((key_segments, fill_triangles), (pick_triangles, glyphs)), ) = rayon::join( || { rayon::join( || { rayon::join( || SpatialSet::build(wire_entries), || SpatialSet::build(segment_entries), ) }, || { rayon::join( || SpatialSet::build(snap_point_entries), || SpatialSet::build(key_vertex_entries), ) }, ) }, || { rayon::join( || { rayon::join( || SpatialSet::build(key_segment_entries), || SpatialSet::build(fill_triangle_entries), ) }, || { rayon::join( || SpatialSet::build(pick_triangle_entries), || SpatialSet::build(glyph_entries), ) }, ) }, ); #[cfg(target_arch = "wasm32")] let ( wires, segments, snap_points, key_vertices, key_segments, fill_triangles, pick_triangles, glyphs, ) = ( SpatialSet::build(wire_entries), SpatialSet::build(segment_entries), SpatialSet::build(snap_point_entries), SpatialSet::build(key_vertex_entries), SpatialSet::build(key_segment_entries), SpatialSet::build(fill_triangle_entries), SpatialSet::build(pick_triangle_entries), SpatialSet::build(glyph_entries), ); Self { wires, wire_handles, segments, snap_points, key_vertices, key_segments, fill_triangles, pick_triangles, glyphs, unbounded_wires, max_line_half_width_px, } } pub fn pick_radius_px(&self, base_radius_px: f32) -> f32 { base_radius_px.max(self.max_line_half_width_px) } fn queried_wire_handles(&self, mut indices: Vec) -> Vec { indices.extend_from_slice(&self.unbounded_wires); let mut handles: Vec = indices .into_iter() .filter_map(|index| self.wire_handles.get(index as usize).copied().flatten()) .collect(); handles.sort_unstable(); handles.dedup(); handles } pub fn query_wire_handles_xy(&self, aabb: [f64; 4]) -> Vec { self.queried_wire_handles(self.wires.query_xy(aabb)) } pub fn query_wire_handles_screen( &self, screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> Vec { self.queried_wire_handles(self.wires.query_screen(screen_rect, view_rot, eye, bounds)) } pub fn query_xy(&self, wires: Arc>, aabb: [f64; 4]) -> InteractionCandidates { let mut wire_indices = self.wires.query_xy(aabb); wire_indices.extend_from_slice(&self.unbounded_wires); wire_indices.sort_unstable(); wire_indices.dedup(); InteractionCandidates { wires, wire_indices: Some(wire_indices), segments: Some(self.segments.query_xy(aabb)), snap_points: Some(self.snap_points.query_xy(aabb)), key_vertices: Some(self.key_vertices.query_xy(aabb)), key_segments: Some(self.key_segments.query_xy(aabb)), fill_triangles: Some(self.fill_triangles.query_xy(aabb)), pick_triangles: Some(self.pick_triangles.query_xy(aabb)), glyphs: Some(self.glyphs.query_xy(aabb)), query_aabb: Some(aabb), screen_rect: None, screen_view: None, } } pub fn query_screen( &self, wires: Arc>, screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> InteractionCandidates { let mut wire_indices = self.wires.query_screen(screen_rect, view_rot, eye, bounds); wire_indices.extend_from_slice(&self.unbounded_wires); wire_indices.sort_unstable(); wire_indices.dedup(); InteractionCandidates { wires, wire_indices: Some(wire_indices), segments: Some( self.segments .query_screen(screen_rect, view_rot, eye, bounds), ), snap_points: Some( self.snap_points .query_screen(screen_rect, view_rot, eye, bounds), ), key_vertices: Some( self.key_vertices .query_screen(screen_rect, view_rot, eye, bounds), ), key_segments: Some( self.key_segments .query_screen(screen_rect, view_rot, eye, bounds), ), fill_triangles: Some(self.fill_triangles.query_screen( screen_rect, view_rot, eye, bounds, )), pick_triangles: Some(self.pick_triangles.query_screen( screen_rect, view_rot, eye, bounds, )), glyphs: Some(self.glyphs.query_screen(screen_rect, view_rot, eye, bounds)), query_aabb: None, screen_rect: Some(screen_rect), screen_view: Some((view_rot, eye, bounds)), } } } pub struct InteractionCandidates { wires: Arc>, wire_indices: Option>, segments: Option>, snap_points: Option>, key_vertices: Option>, key_segments: Option>, fill_triangles: Option>, pick_triangles: Option>, glyphs: Option>, query_aabb: Option<[f64; 4]>, screen_rect: Option<[f32; 4]>, screen_view: Option<(Mat4, DVec3, Rectangle)>, } pub trait WireSource { fn iter(&self) -> WireIter<'_>; fn len(&self) -> usize; fn get(&self, index: usize) -> Option<&WireModel>; fn segments(&self) -> Option<&[SegmentRef]> { None } fn snap_points(&self) -> Option<&[SnapPointRef]> { None } fn key_vertices(&self) -> Option<&[KeyVertexRef]> { None } fn key_segments(&self) -> Option<&[KeySegmentRef]> { None } fn fill_triangles(&self) -> Option<&[TriangleRef]> { None } fn pick_triangles(&self) -> Option<&[TriangleRef]> { None } fn glyphs(&self) -> Option<&[GlyphRef]> { None } fn source_wire(&self, index: u32) -> Option<&WireModel>; } impl InteractionCandidates { pub fn all(wires: Arc>) -> Self { Self { wires, wire_indices: None, segments: None, snap_points: None, key_vertices: None, key_segments: None, fill_triangles: None, pick_triangles: None, glyphs: None, query_aabb: None, screen_rect: None, screen_view: None, } } pub fn iter(&self) -> WireIter<'_> { match &self.wire_indices { Some(indices) => WireIter::Indexed { wires: &self.wires, indices: indices.iter(), }, None => WireIter::All(self.wires.as_slice().iter()), } } pub fn len(&self) -> usize { self.wire_indices .as_ref() .map_or(self.wires.len(), Vec::len) } pub fn get(&self, index: usize) -> Option<&WireModel> { match &self.wire_indices { Some(indices) => indices .get(index) .and_then(|&wire| self.wires.get(wire as usize)), None => self.wires.get(index), } } pub fn segments(&self) -> Option<&[SegmentRef]> { self.segments.as_deref() } pub fn snap_points(&self) -> Option<&[SnapPointRef]> { self.snap_points.as_deref() } pub fn key_vertices(&self) -> Option<&[KeyVertexRef]> { self.key_vertices.as_deref() } pub fn key_segments(&self) -> Option<&[KeySegmentRef]> { self.key_segments.as_deref() } pub fn fill_triangles(&self) -> Option<&[TriangleRef]> { self.fill_triangles.as_deref() } pub fn pick_triangles(&self) -> Option<&[TriangleRef]> { self.pick_triangles.as_deref() } pub fn glyphs(&self) -> Option<&[GlyphRef]> { self.glyphs.as_deref() } pub fn source_wire(&self, index: u32) -> Option<&WireModel> { self.wires.get(index as usize) } pub fn query_aabb(&self) -> Option<[f64; 4]> { self.query_aabb } pub fn screen_query(&self) -> Option<([f32; 4], Mat4, DVec3, Rectangle)> { self.screen_rect .zip(self.screen_view) .map(|(rect, (view, eye, bounds))| (rect, view, eye, bounds)) } } impl WireSource for InteractionCandidates { fn iter(&self) -> WireIter<'_> { self.iter() } fn len(&self) -> usize { self.len() } fn get(&self, index: usize) -> Option<&WireModel> { self.get(index) } fn segments(&self) -> Option<&[SegmentRef]> { self.segments() } fn snap_points(&self) -> Option<&[SnapPointRef]> { self.snap_points() } fn key_vertices(&self) -> Option<&[KeyVertexRef]> { self.key_vertices() } fn key_segments(&self) -> Option<&[KeySegmentRef]> { self.key_segments() } fn fill_triangles(&self) -> Option<&[TriangleRef]> { self.fill_triangles() } fn pick_triangles(&self) -> Option<&[TriangleRef]> { self.pick_triangles() } fn glyphs(&self) -> Option<&[GlyphRef]> { self.glyphs() } fn source_wire(&self, index: u32) -> Option<&WireModel> { self.source_wire(index) } } impl WireSource for [WireModel] { fn iter(&self) -> WireIter<'_> { WireIter::All(<[WireModel]>::iter(self)) } fn len(&self) -> usize { <[WireModel]>::len(self) } fn get(&self, index: usize) -> Option<&WireModel> { <[WireModel]>::get(self, index) } fn source_wire(&self, index: u32) -> Option<&WireModel> { self.get(index as usize) } } impl WireSource for Vec { fn iter(&self) -> WireIter<'_> { WireIter::All(self.as_slice().iter()) } fn len(&self) -> usize { Vec::len(self) } fn get(&self, index: usize) -> Option<&WireModel> { self.as_slice().get(index) } fn source_wire(&self, index: u32) -> Option<&WireModel> { self.get(index as usize) } } impl WireSource for [WireModel; N] { fn iter(&self) -> WireIter<'_> { WireIter::All(self.as_slice().iter()) } fn len(&self) -> usize { N } fn get(&self, index: usize) -> Option<&WireModel> { self.as_slice().get(index) } fn source_wire(&self, index: u32) -> Option<&WireModel> { self.get(index as usize) } } #[derive(Clone)] pub enum WireIter<'a> { All(std::slice::Iter<'a, WireModel>), Indexed { wires: &'a [WireModel], indices: std::slice::Iter<'a, u32>, }, } impl<'a> Iterator for WireIter<'a> { type Item = &'a WireModel; fn next(&mut self) -> Option { match self { Self::All(iter) => iter.next(), Self::Indexed { wires, indices } => { indices.next().and_then(|&idx| wires.get(idx as usize)) } } } } fn finite_wire_aabb3(wire: &WireModel) -> Option<[f64; 6]> { let [min_x, min_y, max_x, max_y] = wire.aabb; if !min_x.is_finite() || !min_y.is_finite() || !max_x.is_finite() || !max_y.is_finite() { return None; } let mag = wire.aabb.iter().fold(0.0f32, |m, c| m.max(c.abs())); let pad = (mag * f32::EPSILON * 2.0) as f64; let mut aabb = [ min_x as f64 - pad, min_y as f64 - pad, f64::INFINITY, max_x as f64 + pad, max_y as f64 + pad, f64::NEG_INFINITY, ]; let mut include = |point: [f64; 3]| { if point.iter().all(|value| value.is_finite()) { aabb[0] = aabb[0].min(point[0]); aabb[1] = aabb[1].min(point[1]); aabb[2] = aabb[2].min(point[2]); aabb[3] = aabb[3].max(point[0]); aabb[4] = aabb[4].max(point[1]); aabb[5] = aabb[5].max(point[2]); } }; for index in 0..wire.points.len() { include(wire_point(wire, index)); } for &(point, _) in &wire.snap_pts { include(point.to_array()); } for &point in &wire.key_vertices { include(point); } for (index, &point) in wire.fill_tris.iter().enumerate() { include(point_with_low(point, &wire.fill_tris_low, index)); } for (index, &point) in wire.pick_tris.iter().enumerate() { include(point_with_low(point, &wire.pick_tris_low, index)); } for vertex in &wire.text_verts { include([ vertex.pos[0] as f64 + vertex.pos_low[0] as f64, vertex.pos[1] as f64 + vertex.pos_low[1] as f64, vertex.pos[2] as f64 + vertex.pos_low[2] as f64, ]); } if !aabb[2].is_finite() { aabb[2] = 0.0; aabb[5] = 0.0; } Some(aabb) } fn wire_point(wire: &WireModel, index: usize) -> [f64; 3] { let high = wire.points[index]; let low = wire.points_low.get(index).copied().unwrap_or([0.0; 3]); [ high[0] as f64 + low[0] as f64, high[1] as f64 + low[1] as f64, high[2] as f64 + low[2] as f64, ] } fn point_with_low(point: [f32; 3], low: &[[f32; 3]], index: usize) -> [f64; 3] { let residual = low.get(index).copied().unwrap_or([0.0; 3]); [ point[0] as f64 + residual[0] as f64, point[1] as f64 + residual[1] as f64, point[2] as f64 + residual[2] as f64, ] } fn points_aabb3(points: impl IntoIterator) -> Option<[f64; 6]> { let mut aabb = [ f64::INFINITY, f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, ]; let mut any = false; for point in points { if !point.iter().all(|value| value.is_finite()) { return None; } any = true; for axis in 0..3 { aabb[axis] = aabb[axis].min(point[axis]); aabb[axis + 3] = aabb[axis + 3].max(point[axis]); } } any.then_some(aabb) } fn append_triangle_entries( wire: u32, points: &[[f32; 3]], low: &[[f32; 3]], out: &mut Vec>, ) { for start in (0..points.len()).step_by(3) { if start + 2 >= points.len() { break; } let Some(aabb) = points_aabb3((start..start + 3).map(|index| point_with_low(points[index], low, index))) else { continue; }; out.push(Entry3 { aabb, value: TriangleRef { wire, start: start as u32, }, }); } } fn aabb3_projects_into( aabb: [f64; 6], screen_rect: [f32; 4], view_rot: Mat4, eye: DVec3, bounds: Rectangle, ) -> bool { let mut projected = [ f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY, ]; for x in [aabb[0], aabb[3]] { for y in [aabb[1], aabb[4]] { for z in [aabb[2], aabb[5]] { let clip = view_rot * (DVec3::new(x, y, z) - eye).as_vec3().extend(1.0); if !clip.is_finite() || clip.w <= 1e-6 { return true; } let ndc = clip.truncate() / clip.w; let sx = (ndc.x + 1.0) * 0.5 * bounds.width; let sy = (1.0 - ndc.y) * 0.5 * bounds.height; projected[0] = projected[0].min(sx); projected[1] = projected[1].min(sy); projected[2] = projected[2].max(sx); projected[3] = projected[3].max(sy); } } } projected[2] >= screen_rect[0] && projected[0] <= screen_rect[2] && projected[3] >= screen_rect[1] && projected[1] <= screen_rect[3] } fn aabb_overlaps(a: [f64; 4], b: [f64; 4]) -> bool { a[2] >= b[0] && a[0] <= b[2] && a[3] >= b[1] && a[1] <= b[3] }