forked from 1b-agent/opencadstudio-upstream
fix(linetype): keep complex linetypes precise at large coordinates
A circle with a complex linetype (embedded shapes/text, e.g. IZOLACE) drew its dashes and symbols scattered far off the entity at UTM-scale coordinates. Two defects compounded, both catastrophic-cancellation at a ~1.2M-unit origin: - The single-entity complex-linetype path fed apply_along the wire's HIGH double-single half only (`base.points`), an absolute f32 quantised to ~0.1 at 1.2M. Walking the pattern along those points blew the shape / dash positions up to ~1e308. Reconstruct the absolute f64 path from high+low, re-origin it at the first vertex, walk apply_along in that local frame, then lift each result wire back to world with shift_wire_to_world (now pub(crate)) — the same local-frame trick the MLINE complex-linetype path already uses. Fixes every entity, not just circles. - The circle was tessellated with truck's circle_arc (arc-through-three- points), whose fit cancels catastrophically at 1.2M and returned a ~3% radius wobble with uneven segments. Tessellate directly as a cos/sin polyline (adaptive segment count from the curve tolerance); adding a small ±r term to the centre stays precise, and the Lines path RTE-splits it. Arc and polyline bulges share the circle_arc root and remain to fix. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
2d3f33de10
commit
5e2b67c642
3 files changed
+70
-23
No files matched your search
+29
-21
@@ -1,5 +1,4 @@
|
||||
use acadrust::entities::Circle;
|
||||
use truck_modeling::{builder, Point3, Wire};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
@@ -20,20 +19,6 @@ fn to_truck(circle: &Circle) -> TruckEntity {
|
||||
let (ax, ay) = crate::scene::view::transform::ocs_axes(normal);
|
||||
let (cwx, cwy, cwz) = crate::scene::view::transform::ocs_point_to_wcs((cx, cy, cz), normal);
|
||||
|
||||
// Circle points in WCS: centre_wcs ± r * Ax and centre_wcs ± r * Ay
|
||||
let pt = |da: (f64, f64, f64), db: (f64, f64, f64), s: f64| {
|
||||
Point3::new(
|
||||
cwx + s * da.0 + s * db.0,
|
||||
cwy + s * da.1 + s * db.1,
|
||||
cwz + s * da.2 + s * db.2,
|
||||
)
|
||||
};
|
||||
// right: centre + r*Ax, left: centre - r*Ax, top: centre + r*Ay, bot: centre - r*Ay
|
||||
let p_right = pt(ax, (0.0, 0.0, 0.0), r);
|
||||
let p_left = pt((ax.0 * -1.0, ax.1 * -1.0, ax.2 * -1.0), (0.0, 0.0, 0.0), r);
|
||||
let p_top = pt(ay, (0.0, 0.0, 0.0), r);
|
||||
let p_bot = pt((ay.0 * -1.0, ay.1 * -1.0, ay.2 * -1.0), (0.0, 0.0, 0.0), r);
|
||||
|
||||
let cv = glam::DVec3::new(cwx, cwy, cwz);
|
||||
let rf = r as f32;
|
||||
let q = |d: (f64, f64, f64)| {
|
||||
@@ -95,14 +80,37 @@ fn to_truck(circle: &Circle) -> TruckEntity {
|
||||
};
|
||||
}
|
||||
|
||||
let right = builder::vertex(p_right);
|
||||
let left = builder::vertex(p_left);
|
||||
let upper = builder::circle_arc(&right, &left, p_top);
|
||||
let lower = builder::circle_arc(&left, &right, p_bot);
|
||||
let wire: Wire = [upper, lower].into_iter().collect();
|
||||
// Tessellate directly as a cos/sin polyline rather than a truck
|
||||
// `circle_arc` (arc-through-three-points). At large WCS coordinates
|
||||
// (e.g. −1.2M UTM) the three-point fit cancels catastrophically — the
|
||||
// circle comes back with a ~3% radius wobble and uneven segment lengths,
|
||||
// which then throws off a dashed linetype's dash spacing. Direct
|
||||
// evaluation only adds a small ±r term to the centre, so it stays precise;
|
||||
// the `Lines` path RTE-splits the absolute-f64 points into the
|
||||
// double-single the shader reconstructs.
|
||||
let tol = crate::scene::convert::truck_tess::current_curve_tol();
|
||||
// Chord-height tolerance → segment count: sag = r·(1 − cos(π/N)).
|
||||
let n = if r > tol {
|
||||
(std::f64::consts::PI / (1.0 - tol / r).clamp(-1.0, 1.0).acos())
|
||||
.ceil()
|
||||
.clamp(16.0, 4096.0) as usize
|
||||
} else {
|
||||
16
|
||||
};
|
||||
let tau = std::f64::consts::TAU;
|
||||
let mut pts: Vec<[f64; 3]> = Vec::with_capacity(n + 1);
|
||||
for i in 0..=n {
|
||||
let a = i as f64 * tau / n as f64;
|
||||
let (s, c) = a.sin_cos();
|
||||
pts.push([
|
||||
cwx + r * (c * ax.0 + s * ay.0),
|
||||
cwy + r * (c * ax.1 + s * ay.1),
|
||||
cwz + r * (c * ax.2 + s * ay.2),
|
||||
]);
|
||||
}
|
||||
TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: TruckObject::Contour(wire),
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts,
|
||||
tangent_geoms: vec![tangent],
|
||||
key_vertices: vec![],
|
||||
|
||||
@@ -1227,9 +1227,47 @@ pub(crate) fn tessellate_entity(
|
||||
// this path.
|
||||
if let Some(clt) = crate::io::linetypes::resolve_complex_lt(document, lt_name) {
|
||||
if let Some(base) = bases.first() {
|
||||
// Walk the dash / glyph layout in a local frame so `apply_along`'s
|
||||
// f32 math stays precise, then lift each wire back to world DS.
|
||||
// `base.points` is the double-single HIGH half; at UTM coordinates
|
||||
// (−1.2M) that alone quantises to ~0.1, which jitters the complex
|
||||
// linetype's shapes and dashes. Reconstruct the absolute f64 path
|
||||
// (high + low) and re-origin it at the first vertex before walking.
|
||||
let abs = |i: usize| -> [f64; 3] {
|
||||
let p = base.points[i];
|
||||
let l = base.points_low.get(i).copied().unwrap_or([0.0; 3]);
|
||||
[
|
||||
p[0] as f64 + l[0] as f64,
|
||||
p[1] as f64 + l[1] as f64,
|
||||
p[2] as f64 + l[2] as f64,
|
||||
]
|
||||
};
|
||||
let origin = base
|
||||
.points
|
||||
.iter()
|
||||
.position(|p| !p[0].is_nan())
|
||||
.map(&abs)
|
||||
.unwrap_or([0.0; 3]);
|
||||
let local: Vec<[f32; 3]> = base
|
||||
.points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| {
|
||||
if p[0].is_nan() {
|
||||
[f32::NAN; 3]
|
||||
} else {
|
||||
let a = abs(i);
|
||||
[
|
||||
(a[0] - origin[0]) as f32,
|
||||
(a[1] - origin[1]) as f32,
|
||||
(a[2] - origin[2]) as f32,
|
||||
]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut wires = text::complex_lt::apply_along(
|
||||
&base.name,
|
||||
&base.points,
|
||||
&local,
|
||||
&clt,
|
||||
(lt_scale * pslt_factor).max(1e-4),
|
||||
entity_color,
|
||||
@@ -1241,6 +1279,7 @@ pub(crate) fn tessellate_entity(
|
||||
);
|
||||
if !wires.is_empty() {
|
||||
for w in &mut wires {
|
||||
convert::tessellate::shift_wire_to_world(w, origin);
|
||||
set_wire_aabb(w, aabb);
|
||||
}
|
||||
return wires;
|
||||
|
||||
@@ -74,7 +74,7 @@ pub(crate) fn points_to_ds(
|
||||
/// The MLINE complex-linetype path uses this because `apply_along` lays out its
|
||||
/// dashes and glyphs in f32, which would otherwise quantise fine spacing far
|
||||
/// from the origin.
|
||||
fn shift_wire_to_world(w: &mut WireModel, origin: [f64; 3]) {
|
||||
pub(crate) fn shift_wire_to_world(w: &mut WireModel, origin: [f64; 3]) {
|
||||
if !w.points.is_empty() {
|
||||
let mut hi = Vec::with_capacity(w.points.len());
|
||||
let mut lo = Vec::with_capacity(w.points.len());
|
||||
|
||||
Reference in new issue
Block a user