forked from 1b-agent/opencadstudio-upstream
Merge PR #322: perf — responsive large copy/paste + point picking
External contributor sLuCHa (sLuCHaa). Gates O(k²) snap intersection passes, O(1) handle lookups in preview, bbox paste anchor/ghost, count-only properties panel for huge selections, incremental paste mesh.
This commit is contained in:
commit
17f46968d8
7 files changed
+143
-35
No files matched your search
@@ -22,15 +22,78 @@ use crate::command::{CadCommand, CmdResult};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
pub struct PasteCommand {
|
||||
/// Wire models of the clipboard entities (used for preview).
|
||||
/// Wire models of the clipboard entities (used for the ghost preview).
|
||||
/// Emptied when `bbox_wire` is used, so a huge clipboard isn't kept twice.
|
||||
wires: Vec<WireModel>,
|
||||
/// Lightweight bounding-box outline that replaces `wires` for very large
|
||||
/// clipboards. `on_preview_wires` runs every mouse move and translates every
|
||||
/// point of the ghost; for a whole-drawing paste that is O(hundreds of
|
||||
/// thousands) per frame and freezes placement. Above a point budget we ghost
|
||||
/// just this 5-point rectangle instead — O(1) per frame — the same way
|
||||
/// AutoCAD rubber-bands a box for a huge selection. `None` = ghost the full
|
||||
/// wires.
|
||||
bbox_wire: Option<WireModel>,
|
||||
/// Centroid of the clipboard entities (offset origin for translation).
|
||||
centroid: Vec3,
|
||||
}
|
||||
|
||||
impl PasteCommand {
|
||||
/// `on_preview_wires` clones and re-uploads the whole ghost every cursor
|
||||
/// move, so its cost scales with the wire count and the total point count.
|
||||
/// Past either budget the per-move work floods the event loop, so switch to
|
||||
/// a bounding-box outline; below them the full ghost still shows.
|
||||
const MAX_PREVIEW_WIRES: usize = 20_000;
|
||||
const MAX_PREVIEW_POINTS: usize = 300_000;
|
||||
|
||||
pub fn new(wires: Vec<WireModel>, centroid: Vec3) -> Self {
|
||||
Self { wires, centroid }
|
||||
let total_points: usize = wires.iter().map(|w| w.points.len()).sum();
|
||||
let too_heavy =
|
||||
wires.len() > Self::MAX_PREVIEW_WIRES || total_points > Self::MAX_PREVIEW_POINTS;
|
||||
if too_heavy {
|
||||
if let Some(bbox_wire) = Self::bbox_outline(&wires) {
|
||||
// Drop the full wires — the box is all the ghost needs now.
|
||||
return Self { wires: Vec::new(), bbox_wire: Some(bbox_wire), centroid };
|
||||
}
|
||||
}
|
||||
Self { wires, bbox_wire: None, centroid }
|
||||
}
|
||||
|
||||
/// Build a closed rectangle outline around the XY extent of `wires`, in the
|
||||
/// same world coordinates so the standard `translated(delta)` shift applies.
|
||||
/// Uses the double-single `points + points_low` sum so the box stays precise
|
||||
/// at UTM-scale coordinates. Returns `None` when there are no points.
|
||||
fn bbox_outline(wires: &[WireModel]) -> Option<WireModel> {
|
||||
let mut min = [f64::INFINITY; 3];
|
||||
let mut max = [f64::NEG_INFINITY; 3];
|
||||
let mut any = false;
|
||||
for w in wires {
|
||||
for (i, p) in w.points.iter().enumerate() {
|
||||
let lo = w.points_low.get(i).copied().unwrap_or([0.0; 3]);
|
||||
for k in 0..3 {
|
||||
let v = p[k] as f64 + lo[k] as f64;
|
||||
min[k] = min[k].min(v);
|
||||
max[k] = max[k].max(v);
|
||||
}
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
return None;
|
||||
}
|
||||
let z = min[2];
|
||||
let pts = vec![
|
||||
[min[0], min[1], z],
|
||||
[max[0], min[1], z],
|
||||
[max[0], max[1], z],
|
||||
[min[0], max[1], z],
|
||||
[min[0], min[1], z],
|
||||
];
|
||||
Some(WireModel::solid_f64(
|
||||
"paste_bbox".into(),
|
||||
pts,
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +118,13 @@ impl CadCommand for PasteCommand {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> { let pt = pt.as_vec3();
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
let pt = pt.as_vec3();
|
||||
let delta = pt - self.centroid;
|
||||
// Large clipboard: ghost just the bounding box — O(1) per frame.
|
||||
if let Some(bbox) = &self.bbox_wire {
|
||||
return vec![bbox.translated(delta)];
|
||||
}
|
||||
self.wires.iter().map(|w| w.translated(delta)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user