diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index b27c60613..07c9dab44 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -1335,15 +1335,8 @@ impl Scene { self.annotation_all_visible(), None, ); - // Ray / XLine tessellate as ±DISPLAY_EXTENT display segments - // (entities/ray.rs) — their endpoints are rendering artifacts, not - // drawing extent. A construction line through the drawing defeats - // both rejects below: its centroid sits at the base point (inside - // the consensus cluster), and in a fresh drawing `local_extent_max` - // is still the 1e9 default, so the far points pass the `lim` filter - // too (issue #284). Exclude them up front — an infinite construction - // line should never contribute to ZOOM Extents — and fall back to their - // base points if the drawing holds nothing else. + // Infinite display segments do not contribute to drawing extents. + // Preserve their base points as a fallback for otherwise empty drawings. let mut infinite_base_pts: Vec = Vec::new(); wires.retain(|w| { let is_infinite = Self::handle_from_wire_name(&w.name) @@ -1389,18 +1382,12 @@ impl Scene { return; } - // Per-wire centroid pass — used both for the absolute-magnitude reject - // (`local_extent_max`) and for the IQR-based outlier reject below. - // A wire whose centroid sits far outside the drawing's consensus - // cluster is an orphan (block-defn entity that leaked into MSPACE, - // bogus hatch boundary, Ray/XLine far point) and must not poison the - // bounding box. + // Collect wire centroids for robust outlier rejection. struct WireCent { idx: usize, cx: f32, cy: f32, } - let lim = self.local_extent_max; let mut cents: Vec = Vec::with_capacity(wires.len()); for (idx, wire) in wires.iter().enumerate() { let mut sx = 0.0_f64; @@ -1426,26 +1413,8 @@ impl Scene { return; } - // Robust drawing centre (median centroid). `lim` is a span RELATIVE to - // this centre, so every reject below is distance-from-centre — geometry - // now reaches fit_all as absolute coordinates (no world_offset), which - // at UTM scale are ~5.7e6; an absolute `|x| > lim` test would reject the - // entire drawing and make ZOOM Extents a no-op. - let (mcx, mcy) = { - let mut xs: Vec = cents.iter().map(|c| c.cx).collect(); - let mut ys: Vec = cents.iter().map(|c| c.cy).collect(); - if xs.is_empty() { - (0.0_f32, 0.0_f32) - } else { - xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - ys.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - (xs[xs.len() / 2], ys[ys.len() / 2]) - } - }; - - // IQR-based reject only kicks in with enough samples for the quartiles - // to be meaningful. Below that, the centre-relative `lim` filter is the - // only gate (legacy behavior). + // Quartile rejection needs enough samples to be meaningful. With fewer + // wires, every finite visible point belongs to the drawing extents. let (rx_lo, rx_hi, ry_lo, ry_hi) = if cents.len() >= 8 { let mut xs: Vec = cents.iter().map(|c| c.cx).collect(); let mut ys: Vec = cents.iter().map(|c| c.cy).collect(); @@ -1456,17 +1425,18 @@ impl Scene { let q3x = q(&xs, 0.75); let q1y = q(&ys, 0.25); let q3y = q(&ys, 0.75); - // k=10× the inter-quartile span is permissive enough to keep - // legitimate sparse outlying geometry (annotation labels, scattered - // dim leaders) but tight enough to drop a single wire stranded at - // -world_offset. `max(1.0)` guards against a degenerate IQR=0 - // (e.g. all wires at the same centroid). + // Keep sparse annotations while rejecting isolated corrupt wires. const K: f32 = 10.0; let dx = (q3x - q1x).max(1.0) * K; let dy = (q3y - q1y).max(1.0) * K; (q1x - dx, q3x + dx, q1y - dy, q3y + dy) } else { - (mcx - lim, mcx + lim, mcy - lim, mcy + lim) + ( + f32::NEG_INFINITY, + f32::INFINITY, + f32::NEG_INFINITY, + f32::INFINITY, + ) }; let mut min = glam::Vec3::splat(f32::MAX); @@ -1480,9 +1450,6 @@ impl Scene { if !x.is_finite() || !y.is_finite() || !z.is_finite() { continue; } - if (x - mcx).abs() > lim || (y - mcy).abs() > lim { - continue; - } min = min.min(glam::Vec3::new(x, y, z)); max = max.max(glam::Vec3::new(x, y, z)); } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index cf14edc61..f706fc99e 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1657,14 +1657,9 @@ pub struct Scene { pub bg_color: [f32; 4], /// Custom paper-space background fill color for Wipeout entities. pub paper_bg_color: [f32; 4], - /// Largest local-space coordinate expected from real geometry, derived from - /// EXTMIN/EXTMAX (10× safety margin). Used by fit_all() to ignore garbage - /// entity coordinates (origin-stuck entities, bad Ray/XLine direction vectors). + /// Dense model-space cluster half-span used for viewport recovery. pub local_extent_max: f32, - /// Robust centre (median of entity centroids) of the dense model-space - /// cluster. Used together with `local_extent_max` to frame a viewport whose - /// saved view is missing — aiming at the raw extents centre would land in - /// the empty gap when a drawing has a second, far cluster. + /// Dense model-space cluster median used for viewport recovery. pub local_center: [f64; 2], /// Current annotation scale (CANNOSCALE equivalent). /// Multiplier applied to Text/MText/Dimension sizes during tessellation.