feat(spline): draw fit-point splines via Catmull-Rom interpolation
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 <noreply@anthropic.com>
This commit is contained in:
parent
e6a961be6c
commit
e515fa3156
2 changed files with 73 additions and 1 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<GripDef> {
|
||||
spline
|
||||
.control_points
|
||||
|
|
@ -424,3 +495,4 @@ impl crate::entities::traits::Transformable for Spline {
|
|||
apply_transform(self, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue