forked from 1b-agent/opencadstudio-upstream
perf(render): batch solid meshes, fix per-pick freezes on heavy 3D models
Opening / navigating a heavy 3D DWG (e.g. a BIM model with ~10k ACIS solids) was effectively frozen. Several independent costs, all addressed here: - Face3D edge/fill buffers were re-uploaded on every camera move (keyed on camera_generation). They are world-space and selection-independent, so gate them on (geometry_epoch, fill_mode) instead — a pan no longer re-walks every wire. - Solid meshes were drawn one buffer + one draw call per solid (~10k draws/ frame), which strangled the GPU front end. Concatenate every solid's LOD0 into a few large buffers (split only to stay under the 256 MB buffer cap) and draw the whole set in a handful of calls. Built once per geometry epoch, so selection / hover never re-pack it. - Selection / hover highlight is now a small tinted overlay of just the picked solids, drawn last with an Always-depth pipeline so it shows on top even when the solid is occluded — instead of re-tinting and re-uploading all meshes. - Hover / pick hit-testing scanned every triangle of every solid each move (seconds on a heavy model). Add a 3D-AABB screen-rect broad phase (MeshLodSet now carries z_aabb) and reuse the renderer's cached expanded mesh set, so picking is O(solids) cheap projections plus the few solids actually under the cursor. - Block-internal hatch hit-testing exploded every INSERT on every hover. Cache the result per geometry epoch and skip blocks that contain no hatch. - Curved-surface (cone/sphere/torus) tessellation tolerance is now radius- relative (CURVE_REL_TOL) so a cylinder's facet count is size-independent and matches the circle/arc wire tessellation, instead of exploding on large radii. Also adds docs/tessellation.md mapping every EntityType to its tessellation path (truck B-rep vs truck curve topology vs direct). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
27896e3401
commit
a994a148fe
10 files changed
+572
-146
No files matched your search
@@ -138,6 +138,51 @@ pub fn click_hits_all<'a>(
|
||||
/// mesh triangle to screen space and test whether the cursor lands inside it.
|
||||
/// Returns the front-most hit (smallest projected depth). Lets meshed solids
|
||||
/// (whose only wire geometry is thin edges) be selected on their faces.
|
||||
/// Conservative broad-phase: does the solid's 3D AABB project to a screen rect
|
||||
/// that the cursor falls within (± `CLICK_THRESHOLD_PX`)? Lets the caller skip
|
||||
/// ray-testing the triangles of solids whose footprint isn't under the cursor —
|
||||
/// O(solids) cheap projections instead of O(triangles) per hover. Returns
|
||||
/// `true` (don't cull) whenever any corner is at/behind the camera, where the
|
||||
/// projected rect can't be trusted.
|
||||
///
|
||||
/// `world_aabb` is `[min_x, min_y, max_x, max_y]`, `z_aabb` is `[min_z, max_z]`,
|
||||
/// and `view_proj` is the same matrix `mesh_click_hit` projects vertices with.
|
||||
pub fn aabb_under_cursor(
|
||||
world_aabb: [f32; 4],
|
||||
z_aabb: [f32; 2],
|
||||
cursor: Point,
|
||||
view_proj: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
) -> bool {
|
||||
let [min_x, min_y, max_x, max_y] = world_aabb;
|
||||
let [min_z, max_z] = z_aabb;
|
||||
if !min_x.is_finite() || !min_z.is_finite() {
|
||||
return true; // degenerate bound — don't cull
|
||||
}
|
||||
let (mut sx0, mut sy0, mut sx1, mut sy1) =
|
||||
(f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
|
||||
for &z in &[min_z, max_z] {
|
||||
for &(x, y) in &[(min_x, min_y), (max_x, min_y), (min_x, max_y), (max_x, max_y)] {
|
||||
let rel = glam::DVec3::new(x as f64 - eye.x, y as f64 - eye.y, z as f64 - eye.z)
|
||||
.as_vec3();
|
||||
let clip = view_proj * rel.extend(1.0);
|
||||
if clip.w <= 1e-6 {
|
||||
return true; // corner at/behind the camera — can't bound; keep it
|
||||
}
|
||||
let ndc = clip.truncate() / clip.w;
|
||||
let px = (ndc.x + 1.0) * 0.5 * bounds.width;
|
||||
let py = (1.0 - ndc.y) * 0.5 * bounds.height;
|
||||
sx0 = sx0.min(px);
|
||||
sy0 = sy0.min(py);
|
||||
sx1 = sx1.max(px);
|
||||
sy1 = sy1.max(py);
|
||||
}
|
||||
}
|
||||
let m = CLICK_THRESHOLD_PX;
|
||||
cursor.x >= sx0 - m && cursor.x <= sx1 + m && cursor.y >= sy0 - m && cursor.y <= sy1 + m
|
||||
}
|
||||
|
||||
pub fn mesh_click_hit<'a>(
|
||||
cursor: Point,
|
||||
meshes: impl Iterator<Item = (Handle, &'a MeshModel)>,
|
||||
|
||||
Reference in new issue
Block a user