From e515fa31566c47ff2597e55a716124bd6403353a Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 4 Jul 2026 19:52:56 +0300 Subject: [PATCH] feat(spline): draw fit-point splines via Catmull-Rom interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A spline stored by fit points (DWG R2013+ scenario 2) has no control points or knots, so the renderer — which built its curve from control points — produced nothing and the spline was invisible. When a spline has fewer than two control points but at least two fit points, interpolate a smooth Catmull-Rom curve through the fit points and emit it as a dense polyline. Open ends use reflected phantom points; closed/periodic splines wrap. Pairs with the acadrust bump (e8e422a) that decodes the R2013+ fit-point scenario correctly — together they make the fit-point spline in Annotation-2D-Mesh-Solid-BIM.dwg render. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 2 +- src/entities/spline.rs | 72 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a1d526c94..54efe5ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/HakanSeven12/acadrust?branch=main#4f0599a91458b39b608a81a9d9a146299ee8b195" +source = "git+https://github.com/HakanSeven12/acadrust?branch=main#e8e422a83742df747d2702f64cd87536b46e81fa" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/src/entities/spline.rs b/src/entities/spline.rs index 1b1cc9633..8a062a0d5 100644 --- a/src/entities/spline.rs +++ b/src/entities/spline.rs @@ -13,6 +13,23 @@ use crate::scene::model::object::{GripApply, GripDef, PropSection}; fn to_truck(spl: &Spline) -> TruckEntity { let n = spl.control_points.len(); if n < 2 { + // A fit-point spline (DWG scenario 2 / R2013+) stores only the points + // the curve passes through — no control points or knots. Interpolate a + // smooth Catmull-Rom curve through them and hand it back as a dense + // polyline so it still draws. + if spl.fit_points.len() >= 2 { + let closed = spl.flags.closed || spl.flags.periodic; + let pts = catmull_rom_polyline(&spl.fit_points, closed); + let key_vertices: Vec<[f64; 3]> = + spl.fit_points.iter().map(|p| [p.x, p.y, p.z]).collect(); + return TruckEntity { + object: TruckObject::Lines(pts), + snap_pts: vec![], + tangent_geoms: vec![], + key_vertices, + fill_tris: vec![], + }; + } return TruckEntity { object: TruckObject::Point(builder::vertex(Point3::new(0.0, 0.0, 0.0))), snap_pts: vec![], @@ -97,6 +114,60 @@ fn to_truck(spl: &Spline) -> TruckEntity { } } +/// Sample a Catmull-Rom spline through `pts` into a dense polyline. The curve +/// passes through every input point; open ends use reflected phantom points so +/// they don't kink, closed curves wrap around. +fn catmull_rom_polyline(pts: &[acadrust::types::Vector3], closed: bool) -> Vec<[f64; 3]> { + const STEPS: usize = 16; + let n = pts.len(); + if n < 2 { + return pts.iter().map(|p| [p.x, p.y, p.z]).collect(); + } + let get = |i: isize| -> [f64; 3] { + let j = if closed { + let m = n as isize; + (((i % m) + m) % m) as usize + } else { + i.clamp(0, n as isize - 1) as usize + }; + [pts[j].x, pts[j].y, pts[j].z] + }; + let seg_count = if closed { n } else { n - 1 }; + let mut out: Vec<[f64; 3]> = Vec::with_capacity(seg_count * STEPS + 1); + for seg in 0..seg_count { + let p1 = get(seg as isize); + let p2 = get(seg as isize + 1); + // Reflect at open ends so the tangent isn't pulled toward a clamped dup. + let p0 = if !closed && seg == 0 { + [2.0 * p1[0] - p2[0], 2.0 * p1[1] - p2[1], 2.0 * p1[2] - p2[2]] + } else { + get(seg as isize - 1) + }; + let p3 = if !closed && seg == seg_count - 1 { + [2.0 * p2[0] - p1[0], 2.0 * p2[1] - p1[1], 2.0 * p2[2] - p1[2]] + } else { + get(seg as isize + 2) + }; + // Emit t in [0, 1); the final segment also emits t = 1 so the curve + // closes onto the last point (no duplicate shared vertices otherwise). + let last = if seg == seg_count - 1 { STEPS } else { STEPS - 1 }; + for s in 0..=last { + let t = s as f64 / STEPS as f64; + let (t2, t3) = (t * t, t * t * t); + let mut q = [0.0f64; 3]; + for k in 0..3 { + q[k] = 0.5 + * (2.0 * p1[k] + + (-p0[k] + p2[k]) * t + + (2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k]) * t2 + + (-p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k]) * t3); + } + out.push(q); + } + } + out +} + fn grips(spline: &Spline) -> Vec { spline .control_points @@ -424,3 +495,4 @@ impl crate::entities::traits::Transformable for Spline { apply_transform(self, t); } } +