perf(scene): grip drag previews the entity instead of re-tessellating
Phase 2.2 (partial). Dragging an entity's grip called scene.apply_grip on every mouse move, which bumped geometry and re-tessellated the whole model per move (plus an O(N) clone of every wire to exclude the edited one from snapping). On a large drawing that's the 30ms->400ms spike, per frame, for the duration of the drag. apply_grip no longer bumps. The drag flow now: - first move: hide the edited entity from the base tessellation (one re-tess) and record it in grip_preview_handle; - each move: apply the grip to the doc (no bump) and show the entity as a one-entity overlay preview (set_preview_wires) — the base is a cache hit; - commit on release / Esc: un-hide, drop the preview, one final re-tess. Because the edited entity is hidden, it's already absent from hit_test_wires, so snapping runs against the cached set directly — no per-move clone and no self-snap. Two re-tessellations per drag instead of one per move. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ccb9bb832d
commit
8df9bef6f8
4 changed files with 57 additions and 8 deletions
|
|
@ -132,6 +132,15 @@ handles; the render path re-tessellates only those, reusing the rest.
|
|||
Also useful on open: any partial cache (e.g. from block defns) can be
|
||||
re-used.
|
||||
|
||||
**Partial (landed): grip drag.** Dragging an entity's grip called
|
||||
`scene.apply_grip` every move, which `bump_geometry`'d → a full model
|
||||
re-tessellation per move (plus an O(N) clone of all wires for snapping). Now
|
||||
the first move hides the edited entity from the base (one re-tess) and shows
|
||||
it as a one-entity overlay preview; subsequent moves only re-tessellate that
|
||||
one entity (cheap) — the base stays a cache hit. Snapping runs against the
|
||||
set directly (the edited entity is already hidden, so no clone and no
|
||||
self-snap). The drag commits on release / Esc: un-hide + one final re-tess.
|
||||
|
||||
**Partial (landed): preview / interim overlays.** `set_preview_wires`,
|
||||
`clear_preview_wire` and `set_interim_wire` used to `bump_geometry`, so every
|
||||
rubber-band frame of a drawing command re-tessellated the whole model (the
|
||||
|
|
|
|||
|
|
@ -231,6 +231,12 @@ pub(super) struct OpenCADStudio {
|
|||
/// being placed (follows the cursor). `(entity handle, new-arrow grip id)`.
|
||||
/// Esc before the placement click removes it again.
|
||||
grip_add_provisional: Option<(acadrust::Handle, usize)>,
|
||||
/// Handle hidden from the base tessellation during an in-progress grip
|
||||
/// drag. While dragging, the edited entity is excluded from the cached
|
||||
/// wire set and shown as a cheap overlay preview instead, so each move
|
||||
/// updates only the overlay rather than re-tessellating the whole model.
|
||||
/// Committed (un-hidden + one re-tess) when the drag ends. `None` = idle.
|
||||
grip_preview_handle: Option<acadrust::Handle>,
|
||||
/// Open Quick Select panel state. `None` = panel closed. Filters are
|
||||
/// applied via `Message::QSelectApply`; the panel is dismissed on
|
||||
/// Apply / Cancel / Esc / outside-click.
|
||||
|
|
@ -1272,6 +1278,7 @@ impl OpenCADStudio {
|
|||
grip_popup: None,
|
||||
grip_pending: None,
|
||||
grip_add_provisional: None,
|
||||
grip_preview_handle: None,
|
||||
qselect: None,
|
||||
show_ucs_icon: true,
|
||||
show_viewcube: true,
|
||||
|
|
|
|||
|
|
@ -1313,6 +1313,14 @@ impl OpenCADStudio {
|
|||
self.tabs[i].scene.bump_geometry();
|
||||
self.refresh_selected_grips();
|
||||
}
|
||||
// Commit/cleanup an in-progress grip drag: un-hide the
|
||||
// edited entity, re-tessellate once, drop the preview.
|
||||
if let Some(h) = self.grip_preview_handle.take() {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.hidden.remove(&h);
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
}
|
||||
self.tabs[self.active_tab].snap_result = None;
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
|
|
@ -2100,14 +2108,24 @@ impl OpenCADStudio {
|
|||
drop(cam);
|
||||
let raw = self.tabs[i].scene.paper_to_model(raw_paper);
|
||||
|
||||
let edited_name = grip.handle.value().to_string();
|
||||
// First move of this drag: hide the edited entity from the
|
||||
// base tessellation (one re-tess) so subsequent moves only
|
||||
// refresh a cheap overlay preview instead of re-tessellating
|
||||
// the whole model on every move.
|
||||
if self.grip_preview_handle != Some(grip.handle) {
|
||||
if let Some(prev) = self.grip_preview_handle.take() {
|
||||
self.tabs[i].scene.hidden.remove(&prev);
|
||||
}
|
||||
self.tabs[i].scene.hidden.insert(grip.handle);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.grip_preview_handle = Some(grip.handle);
|
||||
}
|
||||
|
||||
// The edited entity is hidden, so it's already absent from
|
||||
// `hit_test_wires` — snap against the set directly, no clone
|
||||
// and no self-snap.
|
||||
let all_wires = self.tabs[i].scene.hit_test_wires();
|
||||
let snap_wires: Vec<_> = all_wires
|
||||
.iter()
|
||||
.filter(|w| w.name != edited_name)
|
||||
.cloned()
|
||||
.collect();
|
||||
let snap_hit = self.snapper.snap(raw, p, &snap_wires, vp_mat, bounds);
|
||||
let snap_hit = self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds);
|
||||
let mut snapped = snap_hit.map(|s| s.world).unwrap_or(raw);
|
||||
self.tabs[i].snap_result = snap_hit;
|
||||
if let Some(s) = self.tabs[i].snap_result.as_mut() {
|
||||
|
|
@ -2142,6 +2160,10 @@ impl OpenCADStudio {
|
|||
.apply_grip(grip.handle, grip.grip_id, apply);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
||||
// Overlay the moved entity (hidden from the base) — no base
|
||||
// re-tessellation, just a one-entity preview tessellation.
|
||||
let preview = self.tabs[i].scene.wire_models_for(&[grip.handle]);
|
||||
self.tabs[i].scene.set_preview_wires(preview);
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
|
|
@ -2611,6 +2633,14 @@ impl OpenCADStudio {
|
|||
return Task::none();
|
||||
}
|
||||
self.tabs[i].active_grip = None;
|
||||
// Commit the grip drag: un-hide the edited entity and
|
||||
// re-tessellate the base once with its final geometry,
|
||||
// dropping the overlay preview.
|
||||
if let Some(h) = self.grip_preview_handle.take() {
|
||||
self.tabs[i].scene.hidden.remove(&h);
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
}
|
||||
// Placement confirmed — keep the just-added leader.
|
||||
self.grip_add_provisional = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
|
|
|
|||
|
|
@ -4890,7 +4890,10 @@ impl Scene {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.bump_geometry();
|
||||
// NOTE: no `bump_geometry()` here. The grip-drag caller hides the
|
||||
// edited entity and previews it as an overlay during the drag (so a
|
||||
// move doesn't re-tessellate the whole model), then bumps once on
|
||||
// commit. Any other caller must bump geometry itself.
|
||||
}
|
||||
|
||||
// ── Hit-test convenience: wire name → Handle ──────────────────────────
|
||||
|
|
|
|||
Loading…
Reference in a new issue