forked from 1b-agent/opencadstudio-upstream
fix(render): UTM-precise UCS icon + fallback outlines (hatch etc.)
Two UTM-scale rendering defects. - UCS icon jittered: it projects only axis DIRECTIONS but went through the full view_proj, whose ~5.7e6 translation cancels catastrophically in f32. Feed it the rotation-only view_proj_rte instead (translation dropped). - Fallback outline geometry (hatch boundary, viewport/insert/ole2frame frames, Solid3D edge wires) built absolute f32 points with an empty points_low, so at UTM the outline sat ~0.5 m off its own fill (the hatch fill renders precisely via the world_origin-relative HatchModel pipeline). FallbackGeometry now carries absolute f64 points + key vertices; the tessellate dispatcher splits them into the double-single high/low pair the relative-to-eye wire pass needs, so outlines land exactly on their fills. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
ef55b49db5
commit
dd1f2e49bb
7 files changed
+72
-46
No files matched your search
+5
-1
@@ -167,7 +167,11 @@ impl OpenCADStudio {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
let (_, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
Some(overlay::UcsIconParams {
|
||||
view_proj: cam.view_proj(vp_bounds),
|
||||
// Rotation-only projection: the icon shows axis DIRECTIONS
|
||||
// only, so the full view_proj's huge UTM translation would
|
||||
// just cancel catastrophically in f32 and make the tripod
|
||||
// jitter. view_proj_rte drops the translation. (#utm-ucs)
|
||||
view_proj: cam.view_proj_rte(vp_bounds),
|
||||
bounds: vp_bounds,
|
||||
axes: (ux, uy, uz),
|
||||
})
|
||||
|
||||
+12
-10
@@ -348,13 +348,15 @@ impl FallbackTess for Hatch {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let normal = (self.normal.x, self.normal.y, self.normal.z);
|
||||
// Convert a 2D OCS hatch boundary point to WCS, then subtract world_offset.
|
||||
let to_wcs = |x: f64, y: f64| -> [f32; 3] {
|
||||
let to_wcs = |x: f64, y: f64| -> [f64; 3] {
|
||||
let (wx, wy, wz) =
|
||||
crate::scene::view::transform::ocs_point_to_wcs((x, y, self.elevation), normal);
|
||||
[(wx - ox) as f32, (wy - oy) as f32, (wz - oz) as f32]
|
||||
[wx - ox, wy - oy, wz - oz]
|
||||
};
|
||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||
let mut key_verts: Vec<[f32; 3]> = Vec::new();
|
||||
// Snap point at a world (f64) location, cast to the f32 snap buffer.
|
||||
let snap_at = |w: [f64; 3]| Vec3::new(w[0] as f32, w[1] as f32, w[2] as f32);
|
||||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||||
let mut key_verts: Vec<[f64; 3]> = Vec::new();
|
||||
let mut snap_pts: Vec<(Vec3, SnapHint)> = Vec::new();
|
||||
for path in &self.paths {
|
||||
for edge in &path.edges {
|
||||
@@ -376,7 +378,7 @@ impl FallbackTess for Hatch {
|
||||
// shows up as a stray boundary line between hatch
|
||||
// regions.
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
let start_idx = pts.len();
|
||||
let seg_count = if poly.is_closed {
|
||||
@@ -435,7 +437,7 @@ impl FallbackTess for Hatch {
|
||||
let p0 = to_wcs(ln.start.x, ln.start.y);
|
||||
let p1 = to_wcs(ln.end.x, ln.end.y);
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
pts.push(p0);
|
||||
pts.push(p1);
|
||||
@@ -447,7 +449,7 @@ impl FallbackTess for Hatch {
|
||||
arc_signed_span(arc.start_angle, arc.end_angle, arc.counter_clockwise);
|
||||
let segs = arc_segments(arc.radius, span.abs(), wire_chord_tol(arc.radius));
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
@@ -461,7 +463,7 @@ impl FallbackTess for Hatch {
|
||||
}
|
||||
}
|
||||
snap_pts.push((
|
||||
Vec3::from(to_wcs(arc.center.x, arc.center.y)),
|
||||
snap_at(to_wcs(arc.center.x, arc.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
@@ -475,7 +477,7 @@ impl FallbackTess for Hatch {
|
||||
arc_signed_span(ell.start_angle, ell.end_angle, ell.counter_clockwise);
|
||||
let segs = arc_segments(r_maj, span.abs(), wire_chord_tol(r_maj));
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
let (cr, sr) = (rot.cos(), rot.sin());
|
||||
for i in 0..=segs {
|
||||
@@ -492,7 +494,7 @@ impl FallbackTess for Hatch {
|
||||
}
|
||||
}
|
||||
snap_pts.push((
|
||||
Vec3::from(to_wcs(ell.center.x, ell.center.y)),
|
||||
snap_at(to_wcs(ell.center.x, ell.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
|
||||
+10
-9
@@ -104,17 +104,18 @@ impl crate::entities::traits::FallbackTess for Insert {
|
||||
world_offset: [f64; 3],
|
||||
) -> crate::scene::convert::tess_util::FallbackGeometry {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let ip = Vec3::new(
|
||||
(self.insert_point.x - ox) as f32,
|
||||
(self.insert_point.y - oy) as f32,
|
||||
(self.insert_point.z - oz) as f32,
|
||||
let (ipx, ipy, ipz) = (
|
||||
self.insert_point.x - ox,
|
||||
self.insert_point.y - oy,
|
||||
self.insert_point.z - oz,
|
||||
);
|
||||
let s = 0.1_f32;
|
||||
let ip = Vec3::new(ipx as f32, ipy as f32, ipz as f32);
|
||||
let s = 0.1_f64;
|
||||
let pts = vec![
|
||||
[ip.x - s, ip.y, ip.z],
|
||||
[ip.x + s, ip.y, ip.z],
|
||||
[ip.x, ip.y - s, ip.z],
|
||||
[ip.x, ip.y + s, ip.z],
|
||||
[ipx - s, ipy, ipz],
|
||||
[ipx + s, ipy, ipz],
|
||||
[ipx, ipy - s, ipz],
|
||||
[ipx, ipy + s, ipz],
|
||||
];
|
||||
(
|
||||
pts,
|
||||
|
||||
@@ -187,14 +187,14 @@ impl crate::entities::traits::FallbackTess for Ole2Frame {
|
||||
// OLE objects carry a bounding rectangle in model space.
|
||||
// Render a simple X-through-rectangle placeholder.
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let x0 = (self.upper_left_corner.x - ox) as f32;
|
||||
let y0 = (self.lower_right_corner.y - oy) as f32;
|
||||
let x1 = (self.lower_right_corner.x - ox) as f32;
|
||||
let y1 = (self.upper_left_corner.y - oy) as f32;
|
||||
let z = (self.upper_left_corner.z - oz) as f32;
|
||||
let x0 = self.upper_left_corner.x - ox;
|
||||
let y0 = self.lower_right_corner.y - oy;
|
||||
let x1 = self.lower_right_corner.x - ox;
|
||||
let y1 = self.upper_left_corner.y - oy;
|
||||
let z = self.upper_left_corner.z - oz;
|
||||
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
|
||||
// Degenerate / unknown size — show a small cross.
|
||||
let s = 0.5_f32;
|
||||
let s = 0.5_f64;
|
||||
return (vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]], vec![], vec![], vec![]);
|
||||
}
|
||||
let pts = vec![
|
||||
@@ -210,7 +210,7 @@ impl crate::entities::traits::FallbackTess for Ole2Frame {
|
||||
// Diagonal X
|
||||
[x0, y0, z],
|
||||
[x1, y1, z],
|
||||
[f32::NAN, f32::NAN, f32::NAN],
|
||||
[f64::NAN, f64::NAN, f64::NAN],
|
||||
[x1, y0, z],
|
||||
[x0, y1, z],
|
||||
];
|
||||
|
||||
@@ -551,11 +551,11 @@ impl crate::entities::traits::FallbackTess for Viewport {
|
||||
world_offset: [f64; 3],
|
||||
) -> crate::scene::convert::tess_util::FallbackGeometry {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let cx = (self.center.x - ox) as f32;
|
||||
let cy = (self.center.y - oy) as f32;
|
||||
let cz = (self.center.z - oz) as f32;
|
||||
let hw = (self.width / 2.0) as f32;
|
||||
let hh = (self.height / 2.0) as f32;
|
||||
let cx = self.center.x - ox;
|
||||
let cy = self.center.y - oy;
|
||||
let cz = self.center.z - oz;
|
||||
let hw = self.width / 2.0;
|
||||
let hh = self.height / 2.0;
|
||||
let pts = vec![
|
||||
[cx - hw, cy - hh, cz],
|
||||
[cx + hw, cy - hh, cz],
|
||||
|
||||
@@ -16,11 +16,16 @@ use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
|
||||
/// callsite when the dispatcher wraps these into a WireModel.
|
||||
///
|
||||
/// Layout: `(points, snap_pts, tangent_geoms, key_vertices)`.
|
||||
///
|
||||
/// `points` are ABSOLUTE world coordinates in f64 — the dispatcher splits them
|
||||
/// into the double-single high/low pair the relative-to-eye renderer needs, so
|
||||
/// fallback outlines (hatch boundary, viewport/insert/ole2frame frames) stay
|
||||
/// glued to their fills at UTM scale instead of quantizing ~0.5 m in f32.
|
||||
pub type FallbackGeometry = (
|
||||
Vec<[f32; 3]>,
|
||||
Vec<[f64; 3]>,
|
||||
Vec<(Vec3, SnapHint)>,
|
||||
Vec<TangentGeom>,
|
||||
Vec<[f32; 3]>,
|
||||
Vec<[f64; 3]>,
|
||||
);
|
||||
|
||||
// ── Arc tessellation helpers ─────────────────────────────────────────────
|
||||
|
||||
@@ -471,7 +471,7 @@ pub fn tessellate(
|
||||
// SOLVIEW output or when the SAT kernel cannot parse the
|
||||
// ACIS data).
|
||||
let wire_pts = solid_wire_fallback(entity, world_offset);
|
||||
let mut wm = WireModel::solid(name, wire_pts, color, selected);
|
||||
let mut wm = WireModel::solid_f64(name, wire_pts, color, selected);
|
||||
// Add insertion snap at point_of_reference.
|
||||
let [ox, oy, oz] = world_offset;
|
||||
if let Some(p) = crate::entities::solid3d::point_of_reference(entity) {
|
||||
@@ -484,19 +484,33 @@ pub fn tessellate(
|
||||
}
|
||||
|
||||
// ── Fallback for Viewport / Insert / Hatch / Ole2Frame ────────────────
|
||||
let (points, snap_pts, tangent_geoms, key_vertices) = fallback_geometry(entity, world_offset);
|
||||
let (points_f64, snap_pts, tangent_geoms, key_vertices) =
|
||||
fallback_geometry(entity, world_offset);
|
||||
// `points_f64` are absolute world coords; split into the double-single
|
||||
// high/low pair so the outline reconstructs to f64 precision at UTM scale
|
||||
// (a NaN separator stays NaN in both buffers).
|
||||
let mut points: Vec<[f32; 3]> = Vec::with_capacity(points_f64.len());
|
||||
let mut points_low: Vec<[f32; 3]> = Vec::with_capacity(points_f64.len());
|
||||
for [x, y, z] in &points_f64 {
|
||||
if !x.is_finite() || !y.is_finite() {
|
||||
points.push([f32::NAN, f32::NAN, f32::NAN]);
|
||||
points_low.push([0.0; 3]);
|
||||
continue;
|
||||
}
|
||||
let (hx, lx) = split_ds(*x);
|
||||
let (hy, ly) = split_ds(*y);
|
||||
let (hz, lz) = split_ds(*z);
|
||||
points.push([hx, hy, hz]);
|
||||
points_low.push([lx, ly, lz]);
|
||||
}
|
||||
// fallback_geometry still emits offset-relative f32 snap points; widen to
|
||||
// f64 for the WireModel's double-single-era snap buffer.
|
||||
let snap_pts: Vec<(glam::DVec3, SnapHint)> =
|
||||
snap_pts.into_iter().map(|(p, h)| (p.as_dvec3(), h)).collect();
|
||||
let key_vertices: Vec<[f64; 3]> = key_vertices
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x as f64, y as f64, z as f64])
|
||||
.collect();
|
||||
vec![WireModel {
|
||||
name,
|
||||
points,
|
||||
points_low: Vec::new(),
|
||||
points_low,
|
||||
color,
|
||||
selected,
|
||||
aci: 0,
|
||||
@@ -704,7 +718,7 @@ fn fallback_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
||||
(pts, snap, vec![], vec![])
|
||||
}
|
||||
_ => {
|
||||
let s = 0.5_f32;
|
||||
let s = 0.5_f64;
|
||||
(vec![[-s, 0.0, 0.0], [s, 0.0, 0.0]], vec![], vec![], vec![])
|
||||
}
|
||||
}
|
||||
@@ -715,7 +729,7 @@ fn fallback_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
||||
/// AutoCAD stores explicit wire geometry (from SOLVIEW / 3DPLOT) alongside the
|
||||
/// ACIS data. We use this as a visible fallback when the SAT tessellator
|
||||
/// produces no mesh (e.g. binary SAB data or unsupported geometry).
|
||||
fn solid_wire_fallback(entity: &EntityType, world_offset: [f64; 3]) -> Vec<[f32; 3]> {
|
||||
fn solid_wire_fallback(entity: &EntityType, world_offset: [f64; 3]) -> Vec<[f64; 3]> {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let Some(wires) = crate::entities::solid3d::fallback_wires(entity) else {
|
||||
return vec![];
|
||||
@@ -724,16 +738,16 @@ fn solid_wire_fallback(entity: &EntityType, world_offset: [f64; 3]) -> Vec<[f32;
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||||
for wire in wires {
|
||||
if wire.points.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
for v in &wire.points {
|
||||
pts.push([(v.x - ox) as f32, (v.y - oy) as f32, (v.z - oz) as f32]);
|
||||
pts.push([v.x - ox, v.y - oy, v.z - oz]);
|
||||
}
|
||||
// NaN sentinel separates distinct wire segments.
|
||||
pts.push([f32::NAN, f32::NAN, f32::NAN]);
|
||||
pts.push([f64::NAN, f64::NAN, f64::NAN]);
|
||||
}
|
||||
pts
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user