Files
OneCAD-Draft/src/scene/pick/interaction_index.rs
T
Hakan Seven ea93e169ea perf: make edit history incremental
Replace full-document snapshots with Arc first-touch deltas and batch cache invalidation. Reuse interaction and GPU category caches across unrelated edits.
2026-07-24 23:25:33 +03:00

1228 lines
37 KiB
Rust

//! 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<T> {
aabb: [f64; 6],
value: T,
}
fn entry_aabb2<T>(entry: &Entry3<T>) -> [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<BvhNode2>,
order: Vec<u32>,
}
impl SpatialBvh2 {
fn build<T>(entries: &[Entry3<T>], mut order: Vec<u32>) -> Self {
let mut nodes = Vec::new();
if !order.is_empty() {
build_bvh2_node(entries, &mut order, 0, &mut nodes);
}
Self { nodes, order }
}
fn query<T>(&self, entries: &[Entry3<T>], query: [f64; 4], out: &mut Vec<u32>) {
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<T>(
entries: &[Entry3<T>],
order: &mut [u32],
base: usize,
nodes: &mut Vec<BvhNode2>,
) -> 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<T> {
entries: Vec<Entry3<T>>,
nodes: Vec<BvhNode3>,
order: Vec<u32>,
}
impl<T: Copy + Ord> SpatialBvh3<T> {
fn build(entries: Vec<Entry3<T>>) -> Self {
let mut order: Vec<u32> = (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<T> {
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<T>(
entries: &[Entry3<T>],
order: &mut [u32],
base: usize,
nodes: &mut Vec<BvhNode3>,
) -> 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<u32>,
cell_entries: Vec<u32>,
oversized: SpatialBvh2,
}
impl SpatialGrid {
fn build<T>(entries: &[Entry3<T>]) -> 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<T: Copy + Ord>(&self, entries: &[Entry3<T>], query: [f64; 4]) -> Vec<T> {
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<T> = 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<T> {
xy: SpatialGrid,
xyz: SpatialBvh3<T>,
}
impl<T: Copy + Ord> SpatialSet<T> {
fn build(entries: Vec<Entry3<T>>) -> Self {
let xy = SpatialGrid::build(&entries);
Self {
xy,
xyz: SpatialBvh3::build(entries),
}
}
fn query_xy(&self, aabb: [f64; 4]) -> Vec<T> {
self.xy.query(&self.xyz.entries, aabb)
}
fn query_screen(
&self,
screen_rect: [f32; 4],
view_rot: Mat4,
eye: DVec3,
bounds: Rectangle,
) -> Vec<T> {
self.xyz.query_screen(screen_rect, view_rot, eye, bounds)
}
}
pub struct InteractionIndex {
wires: SpatialSet<u32>,
/// 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<Option<u64>>,
segments: SpatialSet<SegmentRef>,
snap_points: SpatialSet<SnapPointRef>,
key_vertices: SpatialSet<KeyVertexRef>,
key_segments: SpatialSet<KeySegmentRef>,
fill_triangles: SpatialSet<TriangleRef>,
pick_triangles: SpatialSet<TriangleRef>,
glyphs: SpatialSet<GlyphRef>,
unbounded_wires: Vec<u32>,
max_line_half_width_px: f32,
}
pub struct InteractionHandleIndex {
handles: SpatialSet<u64>,
}
impl InteractionHandleIndex {
pub fn build(entries: impl IntoIterator<Item = (u64, [f64; 6])>) -> 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<u64> {
self.handles.query_xy(aabb)
}
pub fn query_screen(
&self,
screen_rect: [f32; 4],
view_rot: Mat4,
eye: DVec3,
bounds: Rectangle,
) -> Vec<u64> {
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<Option<u64>> = wires
.iter()
.map(|wire| wire.name.parse::<u64>().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<u32>) -> Vec<u64> {
indices.extend_from_slice(&self.unbounded_wires);
let mut handles: Vec<u64> = 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<u64> {
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<u64> {
self.queried_wire_handles(self.wires.query_screen(screen_rect, view_rot, eye, bounds))
}
pub fn query_xy(&self, wires: Arc<Vec<WireModel>>, 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<Vec<WireModel>>,
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<Vec<WireModel>>,
wire_indices: Option<Vec<u32>>,
segments: Option<Vec<SegmentRef>>,
snap_points: Option<Vec<SnapPointRef>>,
key_vertices: Option<Vec<KeyVertexRef>>,
key_segments: Option<Vec<KeySegmentRef>>,
fill_triangles: Option<Vec<TriangleRef>>,
pick_triangles: Option<Vec<TriangleRef>>,
glyphs: Option<Vec<GlyphRef>>,
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<Vec<WireModel>>) -> 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<WireModel> {
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<const N: usize> 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<Self::Item> {
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<Item = [f64; 3]>) -> 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<Entry3<TriangleRef>>,
) {
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]
}