forked from 1b-agent/opencadstudio-upstream
Replace full-document snapshots with Arc first-touch deltas and batch cache invalidation. Reuse interaction and GPU category caches across unrelated edits.
1228 lines
37 KiB
Rust
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]
|
|
}
|