From 288ccff95a6523e9f03da8660972f760b44a5e33 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 12 May 2026 23:47:40 +0300 Subject: [PATCH] fix(render): preserve f64 precision until world_offset subtraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/entities/arc.rs | 32 +++++--------- src/entities/circle.rs | 24 ++++------- src/entities/ellipse.rs | 6 +-- src/entities/leader.rs | 32 +++++++------- src/entities/line.rs | 29 ++++--------- src/entities/lwpolyline.rs | 67 +++++++++++++++-------------- src/entities/mesh.rs | 66 ++++++++++++++--------------- src/entities/mline.rs | 68 +++++++++++++----------------- src/entities/multileader.rs | 56 ++++++++++++++---------- src/entities/ole2frame.rs | 18 ++++---- src/entities/polyline.rs | 69 +++++++++++++----------------- src/entities/raster_image.rs | 82 +++++++++++++++++++----------------- src/entities/ray.rs | 28 ++++++------ src/entities/shape.rs | 14 +++--- src/entities/solid.rs | 28 ++++++------ src/entities/spline.rs | 4 +- src/entities/table.rs | 15 ++++++- src/entities/underlay.rs | 23 ++++++---- src/scene/acad_to_truck.rs | 19 ++++++--- src/scene/tessellate.rs | 51 +++++++++++++++------- 20 files changed, 371 insertions(+), 360 deletions(-) diff --git a/src/entities/arc.rs b/src/entities/arc.rs index 4ead572bd..2a745a23b 100644 --- a/src/entities/arc.rs +++ b/src/entities/arc.rs @@ -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)], diff --git a/src/entities/circle.rs b/src/entities/circle.rs index 92e8745ec..b29ba42a7 100644 --- a/src/entities/circle.rs +++ b/src/entities/circle.rs @@ -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 { diff --git a/src/entities/ellipse.rs b/src/entities/ellipse.rs index 2fc918153..04c2dad1b 100644 --- a/src/entities/ellipse.rs +++ b/src/entities/ellipse.rs @@ -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), diff --git a/src/entities/leader.rs b/src/entities/leader.rs index e5571d817..8d643d7e2 100644 --- a/src/entities/leader.rs +++ b/src/entities/leader.rs @@ -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 = 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); diff --git a/src/entities/line.rs b/src/entities/line.rs index f7a972ed3..7147dfc00 100644 --- a/src/entities/line.rs +++ b/src/entities/line.rs @@ -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, ]; diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index 4c360445a..3a9fbf476 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -74,44 +74,40 @@ fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option /// 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, ) -> 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 = Vec::new(); let mut tangents: Vec = 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 = 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 = 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 { diff --git a/src/entities/mesh.rs b/src/entities/mesh.rs index 887d8d2b8..ac59966e9 100644 --- a/src/entities/mesh.rs +++ b/src/entities/mesh.rs @@ -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 { 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); } diff --git a/src/entities/mline.rs b/src/entities/mline.rs index 9ef04a50a..143e09e4b 100644 --- a/src/entities/mline.rs +++ b/src/entities/mline.rs @@ -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 diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index 7eb06a23f..c38c92493 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -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 { - 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 = 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 = 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 Option Option Option Option Option Option 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)], diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index d575d8682..22f63334b 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -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 = Vec::new(); let mut tangents: Vec = 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 = 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) }; diff --git a/src/entities/raster_image.rs b/src/entities/raster_image.rs index 7e9b5d6ec..83e7ff409 100644 --- a/src/entities/raster_image.rs +++ b/src/entities/raster_image.rs @@ -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]))), ] } } diff --git a/src/entities/ray.rs b/src/entities/ray.rs index a4ddbc2b7..bb60067f9 100644 --- a/src/entities/ray.rs +++ b/src/entities/ray.rs @@ -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![], }) } diff --git a/src/entities/shape.rs b/src/entities/shape.rs index e58c7ca58..f7e7f6642 100644 --- a/src/entities/shape.rs +++ b/src/entities/shape.rs @@ -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 { - 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 { diff --git a/src/entities/solid.rs b/src/entities/solid.rs index 6977f3345..557da0e28 100644 --- a/src/entities/solid.rs +++ b/src/entities/solid.rs @@ -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 { 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))), ] } diff --git a/src/entities/spline.rs b/src/entities/spline.rs index ea4fc4265..f41eccfa3 100644 --- a/src/entities/spline.rs +++ b/src/entities/spline.rs @@ -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; diff --git a/src/entities/table.rs b/src/entities/table.rs index 6bcb2e101..44f313398 100644 --- a/src/entities/table.rs +++ b/src/entities/table.rs @@ -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![], diff --git a/src/entities/underlay.rs b/src/entities/underlay.rs index 94c418ecb..5b16ceb83 100644 --- a/src/entities/underlay.rs +++ b/src/entities/underlay.rs @@ -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 { 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 { - 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() { diff --git a/src/scene/acad_to_truck.rs b/src/scene/acad_to_truck.rs index 5d3d04a48..977b4d733 100644 --- a/src/scene/acad_to_truck.rs +++ b/src/scene/acad_to_truck.rs @@ -22,9 +22,13 @@ pub enum TruckObject { Contour(Wire), Text(Vec), /// 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, - 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 { diff --git a/src/scene/tessellate.rs b/src/scene/tessellate.rs index 7338672d5..371ddc995 100644 --- a/src/scene/tessellate.rs +++ b/src/scene/tessellate.rs @@ -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 {