fix(render): preserve f64 precision until world_offset subtraction

Entity converters were casting WCS coordinates to f32 before the renderer
subtracted world_offset, which destroyed sub-unit precision on drawings
sitting at large coordinates (typical UTM-style CAD files). The multileader
case made it visible — glyphs warped under rotation — but every entity
that emits TruckObject::Lines/SegmentedLines/key_vertices/fill_tris was
affected.

Type changes (in src/scene/acad_to_truck.rs):
- TruckObject::Lines / SegmentedLines: Vec<[f32; 3]> → Vec<[f64; 3]>
- TruckEntity::key_vertices / fill_tris: f32 → f64

All ~17 producer files updated to keep WCS coordinates in f64 right up to
the TruckEntity boundary. The consumer in scene/tessellate.rs now performs
`(x - ox) as f32` (f64 subtract, then cast) for points, key_vertices, and
fill_tris — preserving precision near the camera origin even when the
drawing sits at million-unit coordinates.

catmull_rom_pts (multileader spline) is now f64; the scene path widens its
offset-relative f32 ctrl points to f64 around the call and narrows back at
the output.

Notes:
- Table layout still uses glam::Vec3 (f32) internally; widened to f64 only
  at the TruckEntity boundary for type compatibility. Separate cleanup to
  switch its internal math to f64 is tracked.
- snap_pts and tangent_geoms remain f32 — they are UI feedback (snap
  candidates, tangent hints) and the precision is sufficient there.
This commit is contained in:
Hakan Seven committed 2026-05-12 23:47:40 +03:00
1 parent 8bfa5088c4
commit 288ccff95a
20 files changed
+371 -360

No files matched your search

+10 -22
View File
@@ -51,38 +51,26 @@ fn to_truck(arc: &Arc) -> TruckEntity {
let n = 32usize;
let ccw_end = if ea >= sa { ea } else { ea + TAU };
let (start_a, end_a) = (sa, ccw_end);
let mut pts: Vec<[f32; 3]> = Vec::with_capacity((n + 1) * 2 + 8);
let mut pts: Vec<[f64; 3]> = Vec::with_capacity((n + 1) * 2 + 8);
for i in 0..=n {
let a = start_a + (end_a - start_a) * (i as f64 / n as f64);
let p = arc_pt(a);
pts.push([p.x as f32, p.y as f32, p.z as f32]);
pts.push([p.x, p.y, p.z]);
}
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for i in 0..=n {
let a = start_a + (end_a - start_a) * (i as f64 / n as f64);
let p = arc_pt(a);
pts.push([
(p.x + t * nx) as f32,
(p.y + t * ny) as f32,
(p.z + t * nz) as f32,
]);
pts.push([p.x + t * nx, p.y + t * ny, p.z + t * nz]);
}
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
let ps = arc_pt(sa);
pts.push([ps.x as f32, ps.y as f32, ps.z as f32]);
pts.push([
(ps.x + t * nx) as f32,
(ps.y + t * ny) as f32,
(ps.z + t * nz) as f32,
]);
pts.push([f32::NAN; 3]);
pts.push([ps.x, ps.y, ps.z]);
pts.push([ps.x + t * nx, ps.y + t * ny, ps.z + t * nz]);
pts.push([f64::NAN; 3]);
let pe = arc_pt(ea);
pts.push([pe.x as f32, pe.y as f32, pe.z as f32]);
pts.push([
(pe.x + t * nx) as f32,
(pe.y + t * ny) as f32,
(pe.z + t * nz) as f32,
]);
pts.push([pe.x, pe.y, pe.z]);
pts.push([pe.x + t * nx, pe.y + t * ny, pe.z + t * nz]);
return TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(cv, SnapHint::Center)],
+8 -16
View File
@@ -69,31 +69,23 @@ fn to_truck(circle: &Circle) -> TruckEntity {
cwz + r * (c * ax.2 + s * ay.2),
)
};
let mut pts: Vec<[f32; 3]> = Vec::with_capacity((n + 1) * 2 + 4 * 3);
let mut pts: Vec<[f64; 3]> = Vec::with_capacity((n + 1) * 2 + 4 * 3);
for i in 0..=n {
let (x, y, z) = circ_pt(i as f64 * tau / n as f64);
pts.push([x as f32, y as f32, z as f32]);
pts.push([x, y, z]);
}
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for i in 0..=n {
let (x, y, z) = circ_pt(i as f64 * tau / n as f64);
pts.push([
(x + t * nx) as f32,
(y + t * ny) as f32,
(z + t * nz) as f32,
]);
pts.push([x + t * nx, y + t * ny, z + t * nz]);
}
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for i in 0..4usize {
let (x, y, z) = circ_pt(i as f64 * std::f64::consts::FRAC_PI_2);
pts.push([x as f32, y as f32, z as f32]);
pts.push([
(x + t * nx) as f32,
(y + t * ny) as f32,
(z + t * nz) as f32,
]);
pts.push([x, y, z]);
pts.push([x + t * nx, y + t * ny, z + t * nz]);
if i < 3 {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
}
}
return TruckEntity {
+3 -3
View File
@@ -125,9 +125,9 @@ fn to_truck(ell: &Ellipse) -> TruckEntity {
let edge = Edge::new(&v_start, &v_end, Curve::BSplineCurve(bspline));
let pt_start = ctrl_pts.first().unwrap();
let pt_end = ctrl_pts.last().unwrap();
let key_vertices = vec![
[pt_start.x as f32, pt_start.y as f32, pt_start.z as f32],
[pt_end.x as f32, pt_end.y as f32, pt_end.z as f32],
let key_vertices: Vec<[f64; 3]> = vec![
[pt_start.x, pt_start.y, pt_start.z],
[pt_end.x, pt_end.y, pt_end.z],
];
TruckEntity {
object: TruckObject::Curve(edge),
+16 -16
View File
@@ -13,12 +13,15 @@ use crate::scene::wire_model::TangentGeom;
fn to_truck(leader: &Leader) -> TruckEntity {
let verts = &leader.vertices;
let nan = [f32::NAN; 3];
let p3 = |v: &acadrust::types::Vector3| -> [f32; 3] { [v.x as f32, v.y as f32, v.z as f32] };
let nan = [f64::NAN; 3];
let p3 = |v: &acadrust::types::Vector3| -> [f64; 3] { [v.x, v.y, v.z] };
let p3f = |v: &acadrust::types::Vector3| -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
};
let mut points: Vec<[f32; 3]> = Vec::new();
let mut points: Vec<[f64; 3]> = Vec::new();
let mut tangents: Vec<TangentGeom> = Vec::new();
let mut key_verts: Vec<[f32; 3]> = Vec::new();
let mut key_verts: Vec<[f64; 3]> = Vec::new();
// Main leader path
for v in verts {
@@ -26,9 +29,10 @@ fn to_truck(leader: &Leader) -> TruckEntity {
key_verts.push(p3(v));
}
for i in 0..verts.len().saturating_sub(1) {
// TangentGeom uses f32 (UI-only); cast at construction.
tangents.push(TangentGeom::Line {
p1: p3(&verts[i]),
p2: p3(&verts[i + 1]),
p1: p3f(&verts[i]),
p2: p3f(&verts[i + 1]),
});
}
@@ -36,12 +40,12 @@ fn to_truck(leader: &Leader) -> TruckEntity {
if leader.arrow_enabled && verts.len() >= 2 {
let tip = &verts[0];
let next = &verts[1];
let dx = (next.x - tip.x) as f32;
let dy = (next.y - tip.y) as f32;
let dx = next.x - tip.x;
let dy = next.y - tip.y;
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
let (dx, dy) = (dx / len, dy / len);
let sz = (leader.text_height as f32).max(1.0) * 0.8;
let a = std::f32::consts::PI / 6.0;
let sz = (leader.text_height).max(1.0) * 0.8;
let a = std::f64::consts::PI / 6.0;
let (s, c) = a.sin_cos();
let tip_f = p3(tip);
points.push(nan);
@@ -62,12 +66,8 @@ fn to_truck(leader: &Leader) -> TruckEntity {
if leader.hookline_enabled && verts.len() >= 2 {
let last = verts.last().unwrap();
let prev = &verts[verts.len() - 2];
let sign = if (last.x - prev.x) >= 0.0 {
1.0_f32
} else {
-1.0_f32
};
let len = leader.text_height as f32 * 1.5;
let sign = if (last.x - prev.x) >= 0.0 { 1.0_f64 } else { -1.0_f64 };
let len = leader.text_height * 1.5;
let last_f = p3(last);
points.push(nan);
points.push(last_f);
+9 -20
View File
@@ -21,38 +21,27 @@ fn to_truck(line: &Line) -> TruckEntity {
crate::scene::transform::ocs_point_to_wcs((line.end.x, line.end.y, line.end.z), normal);
let p0 = Point3::new(sx, sy, sz);
let p1 = Point3::new(ex, ey, ez);
let kv = vec![
[p0.x as f32, p0.y as f32, p0.z as f32],
[p1.x as f32, p1.y as f32, p1.z as f32],
];
let kv: Vec<[f64; 3]> = vec![[p0.x, p0.y, p0.z], [p1.x, p1.y, p1.z]];
let tangent = TangentGeom::Line {
p1: kv[0],
p2: kv[1],
p1: [kv[0][0] as f32, kv[0][1] as f32, kv[0][2] as f32],
p2: [kv[1][0] as f32, kv[1][1] as f32, kv[1][2] as f32],
};
if line.thickness.abs() > 1e-10 {
let t = line.thickness;
let (nx, ny, nz) = normal;
let p0t = [
(sx + t * nx) as f32,
(sy + t * ny) as f32,
(sz + t * nz) as f32,
];
let p1t = [
(ex + t * nx) as f32,
(ey + t * ny) as f32,
(ez + t * nz) as f32,
];
let pts = vec![
let p0t = [sx + t * nx, sy + t * ny, sz + t * nz];
let p1t = [ex + t * nx, ey + t * ny, ez + t * nz];
let pts: Vec<[f64; 3]> = vec![
kv[0],
kv[1],
[f32::NAN; 3],
[f64::NAN; 3],
p0t,
p1t,
[f32::NAN; 3],
[f64::NAN; 3],
kv[0],
p0t,
[f32::NAN; 3],
[f64::NAN; 3],
kv[1],
p1t,
];
+33 -34
View File
@@ -74,44 +74,40 @@ fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option<f64>
/// Shared by LwPolyline and Polyline2D thickness paths.
fn thick_segments(
seg_data: &[(f64, f64, f64, f64)], // (x0, y0, x1, y1) per seg — or use run of (x,y,bulge)
path_pts: &[[f32; 3]],
path_pts: &[[f64; 3]],
thickness: f64,
normal: (f64, f64, f64),
key_verts: Vec<[f32; 3]>,
key_verts: Vec<[f64; 3]>,
tangents: Vec<TangentGeom>,
) -> TruckEntity {
let (nx, ny, nz) = normal;
let t = thickness;
let off = |p: [f32; 3]| -> [f32; 3] {
[
(p[0] as f64 + t * nx) as f32,
(p[1] as f64 + t * ny) as f32,
(p[2] as f64 + t * nz) as f32,
]
let off = |p: [f64; 3]| -> [f64; 3] {
[p[0] + t * nx, p[1] + t * ny, p[2] + t * nz]
};
let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path_pts.len() * 2 + seg_data.len() * 3 + 4);
let mut pts: Vec<[f64; 3]> = Vec::with_capacity(path_pts.len() * 2 + seg_data.len() * 3 + 4);
// Bottom path
pts.extend_from_slice(path_pts);
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
// Top path
for &p in path_pts {
pts.push(off(p));
}
// Walls at each vertex (seg_data.0/.1 = start x/y of each seg, last seg appends its end too)
if !seg_data.is_empty() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for (k, &(x0, y0, _x1, _y1)) in seg_data.iter().enumerate() {
let pb = key_verts[k];
let _ = (x0, y0); // key_verts already has correct WCS
pts.push(pb);
pts.push(off(pb));
if k + 1 < seg_data.len() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
}
}
// Last wall at the final vertex
if let Some(&last) = key_verts.last() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
pts.push(last);
pts.push(off(last));
}
@@ -143,7 +139,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let seg_count = if pline.is_closed { count } else { count - 1 };
let mut edges: Vec<Edge> = Vec::new();
let mut tangents: Vec<TangentGeom> = Vec::new();
let mut key_verts: Vec<[f32; 3]> = Vec::new();
let mut key_verts: Vec<[f64; 3]> = Vec::new();
// Convert OCS (x, y, elevation) to WCS Point3.
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
@@ -155,14 +151,14 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
};
if pline.thickness.abs() > 1e-10 {
let mut path: Vec<[f32; 3]> = Vec::new();
let mut kv: Vec<[f32; 3]> = Vec::new();
let mut path: Vec<[f64; 3]> = Vec::new();
let mut kv: Vec<[f64; 3]> = Vec::new();
let mut tgs: Vec<TangentGeom> = Vec::new();
let mut seg_data: Vec<(f64, f64, f64, f64)> = Vec::new();
// First vertex
let (w0x, w0y, w0z) = to_wcs(verts[0].location.x, verts[0].location.y);
path.push([w0x as f32, w0y as f32, w0z as f32]);
kv.push([w0x as f32, w0y as f32, w0z as f32]);
path.push([w0x, w0y, w0z]);
kv.push([w0x, w0y, w0z]);
for i in 0..seg_count {
let va = &verts[i];
let vb = &verts[(i + 1) % count];
@@ -171,10 +167,12 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let bulge = va.bulge;
if bulge.abs() < 1e-9 {
let (wx, wy, wz) = to_wcs(ox1, oy1);
path.push([wx as f32, wy as f32, wz as f32]);
path.push([wx, wy, wz]);
let p1_pt = path[path.len() - 2];
let p2_pt = *path.last().unwrap();
tgs.push(TangentGeom::Line {
p1: path[path.len() - 2],
p2: *path.last().unwrap(),
p1: [p1_pt[0] as f32, p1_pt[1] as f32, p1_pt[2] as f32],
p2: [p2_pt[0] as f32, p2_pt[1] as f32, p2_pt[2] as f32],
});
} else {
let angle = 4.0 * bulge.atan();
@@ -209,11 +207,11 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
path.push([wx as f32, wy as f32, wz as f32]);
path.push([wx, wy, wz]);
}
}
let (wbx, wby, wbz) = to_wcs(ox1, oy1);
kv.push([wbx as f32, wby as f32, wbz as f32]);
kv.push([wbx, wby, wbz]);
seg_data.push((ox0, oy0, ox1, oy1));
}
return thick_segments(&seg_data, &path, pline.thickness, normal, kv, tgs);
@@ -221,9 +219,10 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
// plinegen=false: NaN-separated segments so the linetype pattern restarts per vertex.
if !pline.plinegen {
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut tgs: Vec<TangentGeom> = Vec::new();
let mut kv: Vec<[f32; 3]> = Vec::new();
let mut kv: Vec<[f64; 3]> = Vec::new();
let to_f32 = |p: [f64; 3]| -> [f32; 3] { [p[0] as f32, p[1] as f32, p[2] as f32] };
for i in 0..seg_count {
let va = &verts[i];
let vb = &verts[(i + 1) % count];
@@ -231,19 +230,19 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let (ox1, oy1) = (vb.location.x, vb.location.y);
let bulge = va.bulge;
let (wx0, wy0, wz0) = to_wcs(ox0, oy0);
let p_start = [wx0 as f32, wy0 as f32, wz0 as f32];
let p_start = [wx0, wy0, wz0];
pts.push(p_start);
if i == 0 {
kv.push(p_start);
}
if bulge.abs() < 1e-9 {
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
let p_end = [wx1 as f32, wy1 as f32, wz1 as f32];
let p_end = [wx1, wy1, wz1];
pts.push(p_end);
kv.push(p_end);
tgs.push(TangentGeom::Line {
p1: p_start,
p2: p_end,
p1: to_f32(p_start),
p2: to_f32(p_end),
});
} else {
let angle = 4.0 * bulge.atan();
@@ -273,10 +272,10 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
pts.push([wx as f32, wy as f32, wz as f32]);
pts.push([wx, wy, wz]);
}
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
kv.push([wx1 as f32, wy1 as f32, wz1 as f32]);
kv.push([wx1, wy1, wz1]);
let (wcx, wcy, wcz) = to_wcs(ocx, ocy);
tgs.push(TangentGeom::Circle {
center: [wcx as f32, wcy as f32, wcz as f32],
@@ -284,7 +283,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
});
}
if i + 1 < seg_count {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
}
}
return TruckEntity {
@@ -351,9 +350,9 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
}
if i == 0 {
key_verts.push([p0.x as f32, p0.y as f32, p0.z as f32]);
key_verts.push([p0.x, p0.y, p0.z]);
}
key_verts.push([p1.x as f32, p1.y as f32, p1.z as f32]);
key_verts.push([p1.x, p1.y, p1.z]);
}
TruckEntity {
+33 -33
View File
@@ -10,7 +10,11 @@ use crate::scene::wire_model::SnapHint;
// ── Face3D ────────────────────────────────────────────────────────────────────
fn v3(v: &acadrust::types::Vector3) -> [f32; 3] {
fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
[v.x, v.y, v.z]
}
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
}
@@ -20,13 +24,17 @@ impl TruckConvertible for Face3D {
let p1 = v3(&self.second_corner);
let p2 = v3(&self.third_corner);
let p3 = v3(&self.fourth_corner);
let p0f = v3f32(&self.first_corner);
let p1f = v3f32(&self.second_corner);
let p2f = v3f32(&self.third_corner);
let p3f = v3f32(&self.fourth_corner);
let inv = self.invisible_edges;
// Add edge as a line segment (separated by NaN from previous edges).
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut add_edge = |a: [f32; 3], b: [f32; 3]| {
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut add_edge = |a: [f64; 3], b: [f64; 3]| {
if !pts.is_empty() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
}
pts.push(a);
pts.push(b);
@@ -50,17 +58,17 @@ impl TruckConvertible for Face3D {
let cx = (p0[0] + p1[0] + p2[0] + p3[0]) / 4.0;
let cy = (p0[1] + p1[1] + p2[1] + p3[1]) / 4.0;
let cz = (p0[2] + p1[2] + p2[2] + p3[2]) / 4.0;
let s = 0.1_f32;
let s = 0.1_f64;
pts = vec![[cx - s, cy, cz], [cx + s, cy, cz]];
}
Some(TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![
(Vec3::from(p0), SnapHint::Node),
(Vec3::from(p1), SnapHint::Node),
(Vec3::from(p2), SnapHint::Node),
(Vec3::from(p3), SnapHint::Node),
(Vec3::from(p0f), SnapHint::Node),
(Vec3::from(p1f), SnapHint::Node),
(Vec3::from(p2f), SnapHint::Node),
(Vec3::from(p3f), SnapHint::Node),
],
tangent_geoms: vec![],
key_vertices: vec![p0, p1, p2, p3],
@@ -72,10 +80,10 @@ impl TruckConvertible for Face3D {
impl Grippable for Face3D {
fn grips(&self) -> Vec<GripDef> {
vec![
square_grip(0, Vec3::from(v3(&self.first_corner))),
square_grip(1, Vec3::from(v3(&self.second_corner))),
square_grip(2, Vec3::from(v3(&self.third_corner))),
square_grip(3, Vec3::from(v3(&self.fourth_corner))),
square_grip(0, Vec3::from(v3f32(&self.first_corner))),
square_grip(1, Vec3::from(v3f32(&self.second_corner))),
square_grip(2, Vec3::from(v3f32(&self.third_corner))),
square_grip(3, Vec3::from(v3f32(&self.fourth_corner))),
]
}
@@ -178,21 +186,17 @@ impl TruckConvertible for PolygonMesh {
.flags
.contains(acadrust::entities::PolygonMeshFlags::CLOSED_N);
let pt = |i: usize, j: usize| -> [f32; 3] {
let pt = |i: usize, j: usize| -> [f64; 3] {
let v = &self.vertices[i * n + j];
[
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
]
[v.location.x, v.location.y, v.location.z]
};
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut fill_tris: Vec<[f32; 3]> = Vec::new();
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
// Rows (M direction).
for i in 0..m {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for j in 0..n {
pts.push(pt(i, j));
}
@@ -203,7 +207,7 @@ impl TruckConvertible for PolygonMesh {
// Columns (N direction).
for j in 0..n {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for i in 0..m {
pts.push(pt(i, j));
}
@@ -309,23 +313,19 @@ impl TruckConvertible for PolyfaceMesh {
return None;
}
let get_v = |idx: i16| -> Option<[f32; 3]> {
let get_v = |idx: i16| -> Option<[f64; 3]> {
let i = (idx.abs() as usize).checked_sub(1)?;
let v = self.vertices.get(i)?;
Some([
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
])
Some([v.location.x, v.location.y, v.location.z])
};
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut fill_tris: Vec<[f32; 3]> = Vec::new();
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
for face in &self.faces {
// Indices: 0 means unused. Negative = invisible edge (still render for wireframe).
let indices = [face.index1, face.index2, face.index3, face.index4];
let verts: Vec<[f32; 3]> = indices
let verts: Vec<[f64; 3]> = indices
.iter()
.filter(|&&i| i != 0)
.filter_map(|&i| get_v(i))
@@ -334,7 +334,7 @@ impl TruckConvertible for PolyfaceMesh {
if verts.len() < 2 {
continue;
}
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for &p in &verts {
pts.push(p);
}
+29 -39
View File
@@ -20,23 +20,19 @@ impl TruckConvertible for MLine {
// Spine: center line connecting all vertex positions.
// Also attempt to draw parallel offset lines (±scale/2 in miter direction)
// when scale_factor is non-zero.
let scale = self.scale_factor as f32;
let scale = self.scale_factor;
let mut pts: Vec<[f32; 3]> = Vec::new();
let mut pts: Vec<[f64; 3]> = Vec::new();
// Center spine.
for v in &self.vertices {
pts.push([
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]);
pts.push([v.position.x, v.position.y, v.position.z]);
}
if closed && n >= 2 {
pts.push([
self.vertices[0].position.x as f32,
self.vertices[0].position.y as f32,
self.vertices[0].position.z as f32,
self.vertices[0].position.x,
self.vertices[0].position.y,
self.vertices[0].position.z,
]);
}
@@ -44,28 +40,28 @@ impl TruckConvertible for MLine {
// along each vertex's miter direction.
if scale.abs() > 1e-6 {
let half = scale * 0.5;
for sign in [-1.0_f32, 1.0_f32] {
for sign in [-1.0_f64, 1.0_f64] {
let offset = half * sign;
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for v in &self.vertices {
let mx = v.miter.x as f32;
let my = v.miter.y as f32;
let mz = v.miter.z as f32;
let mx = v.miter.x;
let my = v.miter.y;
let mz = v.miter.z;
pts.push([
v.position.x as f32 + mx * offset,
v.position.y as f32 + my * offset,
v.position.z as f32 + mz * offset,
v.position.x + mx * offset,
v.position.y + my * offset,
v.position.z + mz * offset,
]);
}
if closed && n >= 2 {
let v0 = &self.vertices[0];
let mx = v0.miter.x as f32;
let my = v0.miter.y as f32;
let mz = v0.miter.z as f32;
let mx = v0.miter.x;
let my = v0.miter.y;
let mz = v0.miter.z;
pts.push([
v0.position.x as f32 + mx * offset,
v0.position.y as f32 + my * offset,
v0.position.z as f32 + mz * offset,
v0.position.x + mx * offset,
v0.position.y + my * offset,
v0.position.z + mz * offset,
]);
}
}
@@ -74,14 +70,14 @@ impl TruckConvertible for MLine {
// at the first and last vertex of an open MLine.
if !closed {
let cap_v = |v: &acadrust::entities::MLineVertex| {
let mx = v.miter.x as f32;
let my = v.miter.y as f32;
let mz = v.miter.z as f32;
let px = v.position.x as f32;
let py = v.position.y as f32;
let pz = v.position.z as f32;
let mx = v.miter.x;
let my = v.miter.y;
let mz = v.miter.z;
let px = v.position.x;
let py = v.position.y;
let pz = v.position.z;
[
[f32::NAN; 3],
[f64::NAN; 3],
[px + mx * (-half), py + my * (-half), pz + mz * (-half)],
[px + mx * half, py + my * half, pz + mz * half],
]
@@ -91,16 +87,10 @@ impl TruckConvertible for MLine {
}
}
let key_verts: Vec<[f32; 3]> = self
let key_verts: Vec<[f64; 3]> = self
.vertices
.iter()
.map(|v| {
[
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]
})
.map(|v| [v.position.x, v.position.y, v.position.z])
.collect();
let snap_pts = self
+33 -23
View File
@@ -39,7 +39,9 @@ use crate::scene::wire_model::{SnapHint, TangentGeom};
// ── TruckConvertible ────────────────────────────────────────────────────────
/// Catmull-Rom spline tessellation through `ctrl` points, `segs_per_span` segments each.
pub(crate) fn catmull_rom_pts(ctrl: &[[f32; 3]], segs_per_span: u32) -> Vec<[f32; 3]> {
/// Operates in f64 so it can be applied to either WCS-direct coordinates (entity path)
/// or offset-relative coordinates (scene path) without precision loss.
pub(crate) fn catmull_rom_pts(ctrl: &[[f64; 3]], segs_per_span: u32) -> Vec<[f64; 3]> {
let n = ctrl.len();
let mut out = Vec::new();
for i in 0..n.saturating_sub(1) {
@@ -48,10 +50,10 @@ pub(crate) fn catmull_rom_pts(ctrl: &[[f32; 3]], segs_per_span: u32) -> Vec<[f32
let p2 = ctrl[i + 1];
let p3 = if i + 2 < n { ctrl[i + 2] } else { ctrl[n - 1] };
for j in 0..=segs_per_span {
let t = j as f32 / segs_per_span as f32;
let t = j as f64 / segs_per_span as f64;
let t2 = t * t;
let t3 = t2 * t;
let mut pt = [0.0_f32; 3];
let mut pt = [0.0_f64; 3];
for k in 0..3 {
pt[k] = 0.5
* ((2.0 * p1[k])
@@ -66,20 +68,26 @@ pub(crate) fn catmull_rom_pts(ctrl: &[[f32; 3]], segs_per_span: u32) -> Vec<[f32
}
fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckEntity> {
let nan = [f32::NAN; 3];
let p3 = |v: &acadrust::types::Vector3| -> [f32; 3] { [v.x as f32, v.y as f32, v.z as f32] };
let nan = [f64::NAN; 3];
let p3 = |v: &acadrust::types::Vector3| -> [f64; 3] { [v.x, v.y, v.z] };
let arrow_size = ml.arrowhead_size as f32;
let arrow_size = ml.arrowhead_size;
let draw_arrow = arrow_size > 0.0;
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
let mut points: Vec<[f32; 3]> = Vec::new();
let mut points: Vec<[f64; 3]> = Vec::new();
let mut tangents: Vec<TangentGeom> = Vec::new();
let mut key_verts: Vec<[f32; 3]> = Vec::new();
let mut key_verts: Vec<[f64; 3]> = Vec::new();
let mut snap_pts: Vec<(Vec3, SnapHint)> = Vec::new();
let mut first = true;
let node = |arr: [f32; 3]| (Vec3::from(arr), SnapHint::Node);
// snap_pts uses f32 (UI-only); cast at construction.
let node = |arr: [f64; 3]| {
(
Vec3::new(arr[0] as f32, arr[1] as f32, arr[2] as f32),
SnapHint::Node,
)
};
for root in &ml.context.leader_roots {
let cp = &root.connection_point;
@@ -98,7 +106,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
first = false;
// Build the full control-point list: line.points + connection_point
let mut ctrl: Vec<[f32; 3]> = line.points.iter().map(|p| p3(p)).collect();
let mut ctrl: Vec<[f64; 3]> = line.points.iter().map(|p| p3(p)).collect();
let last_f = *ctrl.last().unwrap_or(&cp_f);
let dist = ((last_f[0] - cp_f[0]).powi(2) + (last_f[1] - cp_f[1]).powi(2)).sqrt();
if dist > 1e-9 {
@@ -122,9 +130,11 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
}
for i in 0..ctrl.len().saturating_sub(1) {
let a = ctrl[i];
let b = ctrl[i + 1];
tangents.push(TangentGeom::Line {
p1: ctrl[i],
p2: ctrl[i + 1],
p1: [a[0] as f32, a[1] as f32, a[2] as f32],
p2: [b[0] as f32, b[1] as f32, b[2] as f32],
});
}
}
@@ -138,11 +148,11 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
} else {
*cp
};
let dx = (next.x - tip.x) as f32;
let dy = (next.y - tip.y) as f32;
let dx = next.x - tip.x;
let dy = next.y - tip.y;
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
let (dx, dy) = (dx / dl, dy / dl);
let a = std::f32::consts::PI / 6.0;
let a = std::f64::consts::PI / 6.0;
let (s, c) = a.sin_cos();
points.push(nan);
points.push([
@@ -166,11 +176,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
let d = ml.dogleg_length;
points.push(nan);
points.push(cp_f);
points.push([
(cp.x + dir.x / dl * d) as f32,
(cp.y + dir.y / dl * d) as f32,
cp.z as f32,
]);
points.push([cp.x + dir.x / dl * d, cp.y + dir.y / dl * d, cp.z]);
}
}
@@ -192,7 +198,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
let ins = &ctx.text_location;
let ins_x = ins.x;
let ins_y = ins.y;
let z = ins.z as f32;
let z = ins.z;
// Prefer text_direction (carries through rotations/mirrors); fall back
// to text_rotation when no direction has been set.
let td = ctx.text_direction;
@@ -202,7 +208,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
ctx.text_rotation as f32
};
let (cos_r, sin_r) = (rot.cos(), rot.sin());
snap_pts.push(node([ins_x as f32, ins_y as f32, z]));
snap_pts.push(node([ins_x, ins_y, z]));
// Resolve text style via handle when available, falling back to STANDARD.
let style_name = ctx
@@ -267,6 +273,10 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
let h_shift_local = -line_w * h_anchor;
let wcs_dx = h_shift_local * cos_r - line_y_local * sin_r;
let wcs_dy = h_shift_local * sin_r + line_y_local * cos_r;
// tessellate_text_ex emits f32 glyph points around `origin`.
// For entity-level WCS output, we accept the f32 cast here since
// glyph offsets are small relative to the insertion point and the
// entity path doesn't apply a world_offset before render.
let origin = [ins_x as f32 + wcs_dx, ins_y as f32 + wcs_dy];
let strokes = crate::scene::cxf::tessellate_text_ex(
origin,
@@ -283,7 +293,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
}
points.push(nan);
for &[x, y] in stroke {
points.push([x, y, z]);
points.push([x as f64, y as f64, z]);
}
}
}
+9 -9
View File
@@ -9,14 +9,14 @@ use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
fn to_truck(ole: &Ole2Frame) -> TruckEntity {
let x0 = ole.upper_left_corner.x as f32;
let y0 = ole.lower_right_corner.y as f32;
let x1 = ole.lower_right_corner.x as f32;
let y1 = ole.upper_left_corner.y as f32;
let z = ole.upper_left_corner.z as f32;
let x0 = ole.upper_left_corner.x;
let y0 = ole.lower_right_corner.y;
let x1 = ole.lower_right_corner.x;
let y1 = ole.upper_left_corner.y;
let z = ole.upper_left_corner.z;
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
let s = 0.5_f32;
let s = 0.5_f64;
return TruckEntity {
object: TruckObject::Lines(vec![[-s, 0.0, z], [s, 0.0, z]]),
snap_pts: vec![],
@@ -28,7 +28,7 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
let cx = (x0 + x1) * 0.5;
let cy = (y0 + y1) * 0.5;
let pts = vec![
let pts: Vec<[f64; 3]> = vec![
// Rectangle
[x0, y0, z],
[x1, y0, z],
@@ -41,11 +41,11 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
// Diagonal X
[x0, y0, z],
[x1, y1, z],
[f32::NAN; 3],
[f64::NAN; 3],
[x1, y0, z],
[x0, y1, z],
];
let center = Vec3::new(cx, cy, z);
let center = Vec3::new(cx as f32, cy as f32, z as f32);
TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(center, SnapHint::Center)],
+30 -39
View File
@@ -14,16 +14,10 @@ use crate::scene::wire_model::TangentGeom;
// ── Polyline (old-style 3D heavy polyline) ────────────────────────────────────
fn tessellate_polyline(pl: &Polyline) -> TruckEntity {
let pts: Vec<[f32; 3]> = pl
let pts: Vec<[f64; 3]> = pl
.vertices
.iter()
.map(|v| {
[
v.location.x as f32,
v.location.y as f32,
v.location.z as f32,
]
})
.map(|v| [v.location.x, v.location.y, v.location.z])
.collect();
let mut points = pts.clone();
@@ -148,7 +142,7 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let seg_count = if pl.is_closed() { count } else { count - 1 };
let mut edges: Vec<Edge> = Vec::new();
let mut tangents: Vec<TangentGeom> = Vec::new();
let mut key_verts: Vec<[f32; 3]> = Vec::new();
let mut key_verts: Vec<[f64; 3]> = Vec::new();
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
crate::scene::transform::ocs_point_to_wcs((x, y, elev), normal)
@@ -161,19 +155,18 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
if pl.thickness.abs() > 1e-10 {
let (nx, ny, nz) = normal;
let t = pl.thickness;
let off = |p: [f32; 3]| -> [f32; 3] {
[
(p[0] as f64 + t * nx) as f32,
(p[1] as f64 + t * ny) as f32,
(p[2] as f64 + t * nz) as f32,
]
let off = |p: [f64; 3]| -> [f64; 3] {
[p[0] + t * nx, p[1] + t * ny, p[2] + t * nz]
};
let mut path: Vec<[f32; 3]> = Vec::new();
let mut kv: Vec<[f32; 3]> = Vec::new();
let to_f32 = |p: [f64; 3]| -> [f32; 3] {
[p[0] as f32, p[1] as f32, p[2] as f32]
};
let mut path: Vec<[f64; 3]> = Vec::new();
let mut kv: Vec<[f64; 3]> = Vec::new();
let mut tgs: Vec<TangentGeom> = Vec::new();
let (w0x, w0y, w0z) = to_wcs(verts[0].location.x, verts[0].location.y);
path.push([w0x as f32, w0y as f32, w0z as f32]);
kv.push([w0x as f32, w0y as f32, w0z as f32]);
path.push([w0x, w0y, w0z]);
kv.push([w0x, w0y, w0z]);
for i in 0..seg_count {
let va = &verts[i];
let vb = &verts[(i + 1) % count];
@@ -182,10 +175,12 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let bulge = va.bulge;
if bulge.abs() < 1e-9 {
let (wx, wy, wz) = to_wcs(ox1, oy1);
path.push([wx as f32, wy as f32, wz as f32]);
path.push([wx, wy, wz]);
let p1_pt = path[path.len() - 2];
let p2_pt = *path.last().unwrap();
tgs.push(TangentGeom::Line {
p1: path[path.len() - 2],
p2: *path.last().unwrap(),
p1: to_f32(p1_pt),
p2: to_f32(p2_pt),
});
} else {
let angle = 4.0 * bulge.atan();
@@ -219,25 +214,25 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
for j in 1..=16usize {
let a = a0 + (a1 - a0) * (j as f64 / 16.0);
let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin());
path.push([wx as f32, wy as f32, wz as f32]);
path.push([wx, wy, wz]);
}
}
let (wbx, wby, wbz) = to_wcs(ox1, oy1);
kv.push([wbx as f32, wby as f32, wbz as f32]);
kv.push([wbx, wby, wbz]);
}
let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path.len() * 2 + kv.len() * 3 + 4);
let mut pts: Vec<[f64; 3]> = Vec::with_capacity(path.len() * 2 + kv.len() * 3 + 4);
pts.extend_from_slice(&path);
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for &p in &path {
pts.push(off(p));
}
if !kv.is_empty() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
for (i, &pb) in kv.iter().enumerate() {
pts.push(pb);
pts.push(off(pb));
if i + 1 < kv.len() {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
}
}
}
@@ -304,9 +299,9 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
}
if i == 0 {
key_verts.push([p0.x as f32, p0.y as f32, p0.z as f32]);
key_verts.push([p0.x, p0.y, p0.z]);
}
key_verts.push([p1.x as f32, p1.y as f32, p1.z as f32]);
key_verts.push([p1.x, p1.y, p1.z]);
}
TruckEntity {
@@ -411,12 +406,8 @@ impl Transformable for Polyline2D {
// ── Polyline3D ────────────────────────────────────────────────────────────────
fn tessellate_polyline3d(pl: &Polyline3D) -> TruckEntity {
let to_pt = |v: &acadrust::entities::Vertex3DPolyline| -> [f32; 3] {
[
v.position.x as f32,
v.position.y as f32,
v.position.z as f32,
]
let to_pt = |v: &acadrust::entities::Vertex3DPolyline| -> [f64; 3] {
[v.position.x, v.position.y, v.position.z]
};
// DXF vertex flags: 8 = spline-fit curve point, 16 = spline frame control point.
@@ -426,11 +417,11 @@ fn tessellate_polyline3d(pl: &Polyline3D) -> TruckEntity {
let ctrl_pts: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 16 != 0).collect();
let (wire_pts, key_verts) = if !spline_curve.is_empty() {
let wire: Vec<[f32; 3]> = spline_curve.iter().map(|v| to_pt(v)).collect();
let ctrl: Vec<[f32; 3]> = ctrl_pts.iter().map(|v| to_pt(v)).collect();
let wire: Vec<[f64; 3]> = spline_curve.iter().map(|v| to_pt(v)).collect();
let ctrl: Vec<[f64; 3]> = ctrl_pts.iter().map(|v| to_pt(v)).collect();
(wire, ctrl)
} else {
let pts: Vec<[f32; 3]> = pl.vertices.iter().map(to_pt).collect();
let pts: Vec<[f64; 3]> = pl.vertices.iter().map(to_pt).collect();
(pts.clone(), pts)
};
+43 -39
View File
@@ -23,16 +23,16 @@ fn image_corners(
v: &acadrust::types::Vector3,
w: f64,
h: f64,
) -> [[f32; 3]; 4] {
let ox = origin.x as f32;
let oy = origin.y as f32;
let oz = origin.z as f32;
let ux = (u.x * w) as f32;
let uy = (u.y * w) as f32;
let uz = (u.z * w) as f32;
let vx = (v.x * h) as f32;
let vy = (v.y * h) as f32;
let vz = (v.z * h) as f32;
) -> [[f64; 3]; 4] {
let ox = origin.x;
let oy = origin.y;
let oz = origin.z;
let ux = u.x * w;
let uy = u.y * w;
let uz = u.z * w;
let vx = v.x * h;
let vy = v.y * h;
let vz = v.z * h;
[
[ox, oy, oz],
@@ -42,15 +42,19 @@ fn image_corners(
]
}
fn to_f32_3(p: [f64; 3]) -> [f32; 3] {
[p[0] as f32, p[1] as f32, p[2] as f32]
}
/// Rectangle border + X diagonals — used as a placeholder for images.
fn image_wire(corners: [[f32; 3]; 4], with_x: bool) -> Vec<[f32; 3]> {
fn image_wire(corners: [[f64; 3]; 4], with_x: bool) -> Vec<[f64; 3]> {
let [p0, p1, p2, p3] = corners;
let mut pts = vec![p0, p1, p2, p3, p0];
if with_x {
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
pts.push(p0);
pts.push(p2);
pts.push([f32::NAN; 3]);
pts.push([f64::NAN; 3]);
pts.push(p1);
pts.push(p3);
}
@@ -76,14 +80,14 @@ impl TruckConvertible for RasterImage {
);
// Helper: pixel-space → world-space point.
let ox = self.insertion_point.x as f32;
let oy = self.insertion_point.y as f32;
let oz = self.insertion_point.z as f32;
let px_to_world = |px: f64, py: f64| -> [f32; 3] {
let ox = self.insertion_point.x;
let oy = self.insertion_point.y;
let oz = self.insertion_point.z;
let px_to_world = |px: f64, py: f64| -> [f64; 3] {
[
ox + (self.u_vector.x * px + self.v_vector.x * py) as f32,
oy + (self.u_vector.y * px + self.v_vector.y * py) as f32,
oz + (self.u_vector.z * px + self.v_vector.z * py) as f32,
ox + self.u_vector.x * px + self.v_vector.x * py,
oy + self.u_vector.y * px + self.v_vector.y * py,
oz + self.u_vector.z * px + self.v_vector.z * py,
]
};
@@ -91,7 +95,7 @@ impl TruckConvertible for RasterImage {
let cb = &self.clip_boundary;
match cb.clip_type {
acadrust::entities::ClipType::Polygonal if cb.vertices.len() >= 3 => {
let mut poly: Vec<[f32; 3]> =
let mut poly: Vec<[f64; 3]> =
cb.vertices.iter().map(|v| px_to_world(v.x, v.y)).collect();
if let Some(&first) = poly.first() {
poly.push(first);
@@ -135,10 +139,10 @@ impl Grippable for RasterImage {
self.size.y,
);
vec![
square_grip(0, Vec3::from(corners[0])),
diamond_grip(1, Vec3::from(corners[1])),
diamond_grip(2, Vec3::from(corners[2])),
diamond_grip(3, Vec3::from(corners[3])),
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
diamond_grip(1, Vec3::from(to_f32_3(corners[1]))),
diamond_grip(2, Vec3::from(to_f32_3(corners[2]))),
diamond_grip(3, Vec3::from(to_f32_3(corners[3]))),
]
}
@@ -254,19 +258,19 @@ impl TruckConvertible for Wipeout {
) {
// Convert pixel-space boundary vertices to world space:
// world = insertion_point + u_vector * v.x * size.x + v_vector * v.y * size.y
let ox = self.insertion_point.x as f32;
let oy = self.insertion_point.y as f32;
let oz = self.insertion_point.z as f32;
let mut poly: Vec<[f32; 3]> = self
let ox = self.insertion_point.x;
let oy = self.insertion_point.y;
let oz = self.insertion_point.z;
let mut poly: Vec<[f64; 3]> = self
.clip_boundary_vertices
.iter()
.map(|v| {
let wx = (self.u_vector.x * v.x * self.size.x
+ self.v_vector.x * v.y * self.size.y) as f32;
let wy = (self.u_vector.y * v.x * self.size.x
+ self.v_vector.y * v.y * self.size.y) as f32;
let wz = (self.u_vector.z * v.x * self.size.x
+ self.v_vector.z * v.y * self.size.y) as f32;
let wx = self.u_vector.x * v.x * self.size.x
+ self.v_vector.x * v.y * self.size.y;
let wy = self.u_vector.y * v.x * self.size.x
+ self.v_vector.y * v.y * self.size.y;
let wz = self.u_vector.z * v.x * self.size.x
+ self.v_vector.z * v.y * self.size.y;
[ox + wx, oy + wy, oz + wz]
})
.collect();
@@ -330,10 +334,10 @@ impl Grippable for Wipeout {
self.size.y,
);
vec![
square_grip(0, Vec3::from(corners[0])),
diamond_grip(1, Vec3::from(corners[1])),
diamond_grip(2, Vec3::from(corners[2])),
diamond_grip(3, Vec3::from(corners[3])),
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
diamond_grip(1, Vec3::from(to_f32_3(corners[1]))),
diamond_grip(2, Vec3::from(to_f32_3(corners[2]))),
diamond_grip(3, Vec3::from(to_f32_3(corners[3]))),
]
}
}
+14 -14
View File
@@ -23,12 +23,12 @@ impl TruckConvertible for Ray {
return None;
}
let (nx, ny, nz) = (dir.x / len, dir.y / len, dir.z / len);
let far = [
(bp.x + nx * DISPLAY_EXTENT) as f32,
(bp.y + ny * DISPLAY_EXTENT) as f32,
(bp.z + nz * DISPLAY_EXTENT) as f32,
let far: [f64; 3] = [
bp.x + nx * DISPLAY_EXTENT,
bp.y + ny * DISPLAY_EXTENT,
bp.z + nz * DISPLAY_EXTENT,
];
let start = [bp.x as f32, bp.y as f32, bp.z as f32];
let start: [f64; 3] = [bp.x, bp.y, bp.z];
Some(TruckEntity {
object: TruckObject::Lines(vec![start, far]),
snap_pts: vec![],
@@ -159,21 +159,21 @@ impl TruckConvertible for XLine {
return None;
}
let (nx, ny, nz) = (dir.x / len, dir.y / len, dir.z / len);
let far_pos = [
(bp.x + nx * DISPLAY_EXTENT) as f32,
(bp.y + ny * DISPLAY_EXTENT) as f32,
(bp.z + nz * DISPLAY_EXTENT) as f32,
let far_pos: [f64; 3] = [
bp.x + nx * DISPLAY_EXTENT,
bp.y + ny * DISPLAY_EXTENT,
bp.z + nz * DISPLAY_EXTENT,
];
let far_neg = [
(bp.x - nx * DISPLAY_EXTENT) as f32,
(bp.y - ny * DISPLAY_EXTENT) as f32,
(bp.z - nz * DISPLAY_EXTENT) as f32,
let far_neg: [f64; 3] = [
bp.x - nx * DISPLAY_EXTENT,
bp.y - ny * DISPLAY_EXTENT,
bp.z - nz * DISPLAY_EXTENT,
];
Some(TruckEntity {
object: TruckObject::Lines(vec![far_neg, far_pos]),
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![[bp.x as f32, bp.y as f32, bp.z as f32]],
key_vertices: vec![[bp.x, bp.y, bp.z]],
fill_tris: vec![],
})
}
+7 -7
View File
@@ -18,7 +18,7 @@ use crate::scene::wire_model::SnapHint;
// ── Marker geometry ───────────────────────────────────────────────────────────
/// Small diamond marker at the shape insertion point.
fn shape_marker(ox: f32, oy: f32, oz: f32, size: f32) -> Vec<[f32; 3]> {
fn shape_marker(ox: f64, oy: f64, oz: f64, size: f64) -> Vec<[f64; 3]> {
let s = size * 0.5;
vec![
[ox, oy + s, oz], // top
@@ -26,7 +26,7 @@ fn shape_marker(ox: f32, oy: f32, oz: f32, size: f32) -> Vec<[f32; 3]> {
[ox, oy - s, oz], // bottom
[ox - s, oy, oz], // left
[ox, oy + s, oz], // close
[f32::NAN; 3],
[f64::NAN; 3],
]
}
@@ -34,12 +34,12 @@ fn shape_marker(ox: f32, oy: f32, oz: f32, size: f32) -> Vec<[f32; 3]> {
impl TruckConvertible for Shape {
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
let ox = self.insertion_point.x as f32;
let oy = self.insertion_point.y as f32;
let oz = self.insertion_point.z as f32;
let size = (self.size as f32).abs().max(0.5);
let ox = self.insertion_point.x;
let oy = self.insertion_point.y;
let oz = self.insertion_point.z;
let size = self.size.abs().max(0.5);
let snap_pt = Vec3::new(ox, oy, oz);
let snap_pt = Vec3::new(ox as f32, oy as f32, oz as f32);
let pts = shape_marker(ox, oy, oz, size);
Some(TruckEntity {
+16 -12
View File
@@ -14,7 +14,11 @@ use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
fn v3(v: &acadrust::types::Vector3) -> [f32; 3] {
fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
[v.x, v.y, v.z]
}
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
}
@@ -31,22 +35,22 @@ impl TruckConvertible for Solid {
let pts = vec![
p0,
p1,
[f32::NAN; 3],
[f64::NAN; 3],
p1,
p3,
[f32::NAN; 3],
[f64::NAN; 3],
p3,
p2,
[f32::NAN; 3],
[f64::NAN; 3],
p2,
p0,
];
let snap = vec![
(Vec3::from(p0), SnapHint::Node),
(Vec3::from(p1), SnapHint::Node),
(Vec3::from(p2), SnapHint::Node),
(Vec3::from(p3), SnapHint::Node),
(Vec3::from(v3f32(&self.first_corner)), SnapHint::Node),
(Vec3::from(v3f32(&self.second_corner)), SnapHint::Node),
(Vec3::from(v3f32(&self.third_corner)), SnapHint::Node),
(Vec3::from(v3f32(&self.fourth_corner)), SnapHint::Node),
];
Some(TruckEntity {
@@ -62,10 +66,10 @@ impl TruckConvertible for Solid {
impl Grippable for Solid {
fn grips(&self) -> Vec<GripDef> {
vec![
square_grip(0, Vec3::from(v3(&self.first_corner))),
square_grip(1, Vec3::from(v3(&self.second_corner))),
square_grip(2, Vec3::from(v3(&self.third_corner))),
square_grip(3, Vec3::from(v3(&self.fourth_corner))),
square_grip(0, Vec3::from(v3f32(&self.first_corner))),
square_grip(1, Vec3::from(v3f32(&self.second_corner))),
square_grip(2, Vec3::from(v3f32(&self.third_corner))),
square_grip(3, Vec3::from(v3f32(&self.fourth_corner))),
]
}
+2 -2
View File
@@ -64,9 +64,9 @@ fn to_truck(spl: &Spline) -> TruckEntity {
} else {
&spl.control_points
};
let key_vertices: Vec<[f32; 3]> = snap_source
let key_vertices: Vec<[f64; 3]> = snap_source
.iter()
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
.map(|p| [p.x, p.y, p.z])
.collect();
let is_closed = spl.flags.closed || spl.flags.periodic;
+14 -1
View File
@@ -139,8 +139,21 @@ impl TruckConvertible for Table {
}
}
// Table currently does its layout in glam::Vec3 (f32). The world_offset
// subtraction in tessellate.rs needs f64, so widen at the boundary —
// precision is already limited by the f32 math above (separate fix-up).
let pts_f64: Vec<[f64; 3]> = pts
.into_iter()
.map(|[x, y, z]| {
if x.is_nan() {
[f64::NAN, f64::NAN, f64::NAN]
} else {
[x as f64, y as f64, z as f64]
}
})
.collect();
Some(TruckEntity {
object: TruckObject::Lines(pts),
object: TruckObject::Lines(pts_f64),
snap_pts: vec![(origin, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
+14 -9
View File
@@ -16,17 +16,21 @@ use crate::scene::wire_model::SnapHint;
// ── Helpers ───────────────────────────────────────────────────────────────────
fn v3(v: &acadrust::types::Vector3) -> [f32; 3] {
fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
[v.x, v.y, v.z]
}
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
[v.x as f32, v.y as f32, v.z as f32]
}
/// Small cross marker at the insertion point (used when no clip boundary).
fn cross_wire(origin: [f32; 3], size: f32) -> Vec<[f32; 3]> {
fn cross_wire(origin: [f64; 3], size: f64) -> Vec<[f64; 3]> {
let [ox, oy, oz] = origin;
vec![
[ox - size, oy, oz],
[ox + size, oy, oz],
[f32::NAN; 3],
[f64::NAN; 3],
[ox, oy - size, oz],
[ox, oy + size, oz],
]
@@ -37,23 +41,24 @@ fn cross_wire(origin: [f32; 3], size: f32) -> Vec<[f32; 3]> {
impl TruckConvertible for Underlay {
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
let origin = v3(&self.insertion_point);
let origin_f32 = v3f32(&self.insertion_point);
if !self.clip_boundary_vertices.is_empty() {
// Draw clip boundary polygon + close it.
let world_verts = self.world_clip_boundary();
let mut pts: Vec<[f32; 3]> = world_verts
let mut pts: Vec<[f64; 3]> = world_verts
.iter()
.map(|v| [v.x as f32, v.y as f32, v.z as f32])
.map(|v| [v.x, v.y, v.z])
.collect();
// Close polygon.
if let Some(&first) = pts.first() {
pts.push(first);
}
// Insertion grip.
let key: Vec<[f32; 3]> = pts.clone();
let key: Vec<[f64; 3]> = pts.clone();
Some(TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(Vec3::from(origin), SnapHint::Node)],
snap_pts: vec![(Vec3::from(origin_f32), SnapHint::Node)],
tangent_geoms: vec![],
key_vertices: key,
fill_tris: vec![],
@@ -63,7 +68,7 @@ impl TruckConvertible for Underlay {
let pts = cross_wire(origin, 1.0);
Some(TruckEntity {
object: TruckObject::Lines(pts),
snap_pts: vec![(Vec3::from(origin), SnapHint::Node)],
snap_pts: vec![(Vec3::from(origin_f32), SnapHint::Node)],
tangent_geoms: vec![],
key_vertices: vec![origin],
fill_tris: vec![],
@@ -76,7 +81,7 @@ impl TruckConvertible for Underlay {
impl Grippable for Underlay {
fn grips(&self) -> Vec<GripDef> {
let origin = Vec3::from(v3(&self.insertion_point));
let origin = Vec3::from(v3f32(&self.insertion_point));
let mut grips = vec![square_grip(0, origin)];
if !self.clip_boundary_vertices.is_empty() {
+13 -6
View File
@@ -22,9 +22,13 @@ pub enum TruckObject {
Contour(Wire),
Text(Vec<TextStroke>),
/// Pre-computed NaN-separated 3-D point list (leader lines, arrowheads, etc.).
Lines(Vec<[f32; 3]>),
/// Points are stored in WCS as **f64** so the large world_offset can be
/// subtracted in full precision in tessellate.rs before the f32 cast.
/// Casting WCS coordinates to f32 in the entity converters used to wreck
/// rotated sub-glyph precision on drawings far from origin.
Lines(Vec<[f64; 3]>),
/// Like Lines but linetype pattern restarts at each NaN-separated segment (plinegen=false).
SegmentedLines(Vec<[f32; 3]>),
SegmentedLines(Vec<[f64; 3]>),
Volume(Solid),
}
@@ -32,10 +36,13 @@ pub struct TruckEntity {
pub object: TruckObject,
pub snap_pts: Vec<(Vec3, SnapHint)>,
pub tangent_geoms: Vec<TangentGeom>,
pub key_vertices: Vec<[f32; 3]>,
/// Pre-triangulated fill geometry: flat list of [f32;3] vertices, 3 per triangle.
/// Non-empty for mesh-like entities (PolyfaceMesh, PolygonMesh) that need solid fill.
pub fill_tris: Vec<[f32; 3]>,
/// Polyline vertex positions in WCS f64; converted to offset-relative f32
/// at the wire-model boundary.
pub key_vertices: Vec<[f64; 3]>,
/// Pre-triangulated fill geometry: flat list of WCS f64 vertices, 3 per
/// triangle. Non-empty for mesh-like entities (PolyfaceMesh, PolygonMesh)
/// that need solid fill.
pub fill_tris: Vec<[f64; 3]>,
}
pub fn convert(entity: &EntityType, document: &CadDocument) -> Option<TruckEntity> {
+35 -16
View File
@@ -142,6 +142,12 @@ pub fn tessellate(
}
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let [ox, oy, oz] = world_offset;
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
return WireModel {
name,
points,
@@ -153,7 +159,7 @@ pub fn tessellate(
snap_pts,
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
@@ -168,6 +174,14 @@ pub fn tessellate(
TruckTessResult::Point([x, y, z]) => {
let s = 0.1_f32;
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let [ox, oy, oz] = world_offset;
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[kx, ky, kz]| {
[(kx - ox) as f32, (ky - oy) as f32, (kz - oz) as f32]
})
.collect();
return WireModel {
name,
points: vec![
@@ -184,7 +198,7 @@ pub fn tessellate(
snap_pts,
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
@@ -202,7 +216,7 @@ pub fn tessellate(
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
return WireModel {
name,
@@ -231,7 +245,7 @@ pub fn tessellate(
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
return WireModel {
name,
@@ -254,17 +268,18 @@ pub fn tessellate(
}
TruckObject::Lines(points) => {
// Points are world-space f32 from entity converters (polyline,
// leader, mesh, solid2d, etc.). Subtract world_offset so the
// geometry lands in local space alongside Line/Arc/Circle.
// Points are world-space f64 from entity converters (polyline,
// leader, mesh, solid2d, etc.). Subtract world_offset in f64
// before casting to f32 so drawings at large UTM-style
// coordinates keep sub-unit precision in the wire model.
let [ox, oy, oz] = world_offset;
let local_pts: Vec<[f32; 3]> = points
.into_iter()
.map(|[x, y, z]| {
if x.is_nan() {
[x, y, z]
[f32::NAN, f32::NAN, f32::NAN]
} else {
[x - ox as f32, y - oy as f32, z - oz as f32]
[(x - ox) as f32, (y - oy) as f32, (z - oz) as f32]
}
})
.collect();
@@ -272,12 +287,12 @@ pub fn tessellate(
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
let fill_tris: Vec<[f32; 3]> = te
.fill_tris
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
return WireModel {
name,
@@ -304,9 +319,9 @@ pub fn tessellate(
.into_iter()
.map(|[x, y, z]| {
if x.is_nan() {
[x, y, z]
[f32::NAN, f32::NAN, f32::NAN]
} else {
[x - ox as f32, y - oy as f32, z - oz as f32]
[(x - ox) as f32, (y - oy) as f32, (z - oz) as f32]
}
})
.collect();
@@ -314,7 +329,7 @@ pub fn tessellate(
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.map(|[x, y, z]| [(x - ox) as f32, (y - oy) as f32, (z - oz) as f32])
.collect();
return WireModel {
name,
@@ -657,8 +672,12 @@ pub fn tessellate_multileader(
}
if ml.path_type == MultiLeaderPathType::Spline && ctrl.len() >= 2 {
for pt in catmull_rom_pts(&ctrl, 8) {
points.push(pt);
let ctrl_f64: Vec<[f64; 3]> = ctrl
.iter()
.map(|c| [c[0] as f64, c[1] as f64, c[2] as f64])
.collect();
for pt in catmull_rom_pts(&ctrl_f64, 8) {
points.push([pt[0] as f32, pt[1] as f32, pt[2] as f32]);
}
} else {
for &c in &ctrl {