forked from 1b-agent/opencadstudio-upstream
feat(tess): use angular sampling
Route curve, hatch, sweep, loft, surface, edge, and isoline tessellation through the shared kernel policy.
This commit is contained in:
1 parent
f607422c9d
commit
96be8214f9
21 files changed
+158
-767
No files matched your search
+5
-12
@@ -670,14 +670,7 @@ pub(crate) fn thick_band_tube(
|
||||
(fill_tris, lines)
|
||||
}
|
||||
|
||||
/// Build a continuous WCS point list + a per-point FULL band width for a
|
||||
/// tapered wide polyline, so the wire shader can interpolate each segment's two
|
||||
/// endpoint widths. Each `verts` entry is `(location_xy, bulge_to_next,
|
||||
/// start_width, end_width)` — the effective full widths at that vertex's segment
|
||||
/// start and end (already resolved against the polyline's constant width). Arcs
|
||||
/// are sampled in 16 steps with the width interpolated linearly along the arc.
|
||||
/// A shared vertex is emitted once (carrying the previous segment's end width),
|
||||
/// which is exact for the usual continuous taper.
|
||||
/// Build a continuous WCS point list and full width for a tapered polyline.
|
||||
pub(crate) fn tapered_band_points(
|
||||
verts: &[([f64; 2], f64, f64, f64)],
|
||||
is_closed: bool,
|
||||
@@ -701,9 +694,10 @@ pub(crate) fn tapered_band_points(
|
||||
if bulge.abs() < 1e-9 {
|
||||
push(p1[0], p1[1], ew0 as f32);
|
||||
} else if let Some(arc) = BulgeArc::from_bulge(p0, p1, bulge) {
|
||||
for j in 1..=16usize {
|
||||
let t = j as f64 / 16.0;
|
||||
let s = arc.sample(t);
|
||||
let samples = arc.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE);
|
||||
let segments = samples.len().saturating_sub(1).max(1);
|
||||
for (index, s) in samples.into_iter().enumerate().skip(1) {
|
||||
let t = index as f64 / segments as f64;
|
||||
push(s[0], s[1], (sw0 + (ew0 - sw0) * t) as f32);
|
||||
}
|
||||
}
|
||||
@@ -792,4 +786,3 @@ pub(crate) fn polyline_segment_fill(
|
||||
Some(boundary)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-46
@@ -331,53 +331,9 @@ pub fn entity_curve_xy(entity: &EntityType) -> Option<Curve> {
|
||||
})
|
||||
}
|
||||
|
||||
/// World-space wire points for a curve, cut to the render pass's current
|
||||
/// chord tolerance.
|
||||
///
|
||||
/// The density comes from how far a chord may sag from the curve, not from a
|
||||
/// segment count: an arc a metre across and one a kilometre across need
|
||||
/// different numbers of points to look equally round, and the tolerance the
|
||||
/// render pass carries is already zoom-adaptive.
|
||||
///
|
||||
/// Everything stays `f64` to the end. The narrowing belongs at the GPU
|
||||
/// boundary, where the residual is kept as the low half of a double-single
|
||||
/// pair; casting local coordinates on the way in throws away precision the
|
||||
/// shader was built to reconstruct.
|
||||
/// World-space wire points sampled by the kernel's angular policy.
|
||||
pub fn curve_points(curve: &PlanarCurve) -> Vec<[f64; 3]> {
|
||||
let tolerance = crate::scene::convert::curve_tol::curve_tol_for(curve_size(&curve.curve));
|
||||
curve.tessellate_within(tolerance)
|
||||
}
|
||||
|
||||
/// How big a curve is, for deciding how finely to sample it.
|
||||
///
|
||||
/// A radius where there is one, since that is what the chord-height relation
|
||||
/// is written in terms of. `0` for the kinds whose sampling density is not a
|
||||
/// question of curvature — a straight run needs its two ends and nothing in
|
||||
/// between — which leaves those on the frame's own tolerance.
|
||||
fn curve_size(curve: &Curve) -> f64 {
|
||||
match curve {
|
||||
Curve::Circle(circle) => circle.radius,
|
||||
Curve::Arc(arc) => arc.radius,
|
||||
Curve::Ellipse(arc) => arc.ellipse.major_radius,
|
||||
// A polyline's bulges and a spline's bends are the same question asked
|
||||
// of a chain, and its own extent is the nearest thing to a radius.
|
||||
Curve::Polyline(_) | Curve::Nurbs(_) => {
|
||||
let sampled = curve.tessellate(64.0);
|
||||
let (mut low, mut high) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
for point in &sampled {
|
||||
for axis in 0..2 {
|
||||
low[axis] = low[axis].min(point[axis]);
|
||||
high[axis] = high[axis].max(point[axis]);
|
||||
}
|
||||
}
|
||||
if low[0] > high[0] {
|
||||
0.0
|
||||
} else {
|
||||
(high[0] - low[0]).max(high[1] - low[1]) * 0.5
|
||||
}
|
||||
}
|
||||
Curve::Line(_) | Curve::Ray(_) | Curve::XLine(_) => 0.0,
|
||||
}
|
||||
curve.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
}
|
||||
|
||||
/// The snap candidates an entity's curve offers, in the two channels the
|
||||
|
||||
@@ -24,11 +24,7 @@ fn to_render(ell: &Ellipse) -> RenderEntity {
|
||||
.map(crate::entities::curve::snap_from)
|
||||
.unwrap_or_default();
|
||||
|
||||
// The points come from the entity's own curve, cut to the render pass's
|
||||
// chord tolerance. The two fixed counts this replaced — sixteen per half
|
||||
// for a full ellipse, thirty-two for an arc — faceted visibly on
|
||||
// anything large, and the local coordinates were built in `f32`, which
|
||||
// threw away precision the double-single vertex path exists to keep.
|
||||
// The points come from the entity's own kernel curve and angular policy.
|
||||
//
|
||||
// What does not change is the shape of the object. EXTRUDE, REVOLVE and
|
||||
// SWEEP read their profile out of `Contour` / `Curve` and have no arm for
|
||||
|
||||
+42
-143
@@ -11,7 +11,7 @@ use crate::command::EntityTransform;
|
||||
use crate::entities::common::{center_grip, circle_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::traits::{FallbackTess, Grippable, PropertyEditable, Transformable};
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::convert::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
|
||||
use crate::scene::convert::tess_util::FallbackGeometry;
|
||||
use crate::scene::model::wire_model::SnapHint;
|
||||
|
||||
/// The area the hatch's boundary paths enclose.
|
||||
@@ -49,19 +49,20 @@ fn boundary_area(h: &Hatch) -> f64 {
|
||||
}
|
||||
|
||||
/// A hatch boundary edge as a kernel curve, in the hatch's own OCS.
|
||||
fn edge_curve(edge: &BoundaryEdge) -> Option<KernelCurve> {
|
||||
pub(crate) fn edge_curve(edge: &BoundaryEdge) -> Option<KernelCurve> {
|
||||
Some(match edge {
|
||||
BoundaryEdge::Line(l) => KernelCurve::Line(KernelLine {
|
||||
start: [l.start.x, l.start.y],
|
||||
end: [l.end.x, l.end.y],
|
||||
}),
|
||||
BoundaryEdge::CircularArc(a) => {
|
||||
// A clockwise edge is the same arc walked the other way, so the
|
||||
// stored angles swap rather than the sweep going negative.
|
||||
let (start, end) = if a.counter_clockwise {
|
||||
(a.start_angle, a.end_angle)
|
||||
} else {
|
||||
(a.end_angle, a.start_angle)
|
||||
(
|
||||
std::f64::consts::TAU - a.end_angle,
|
||||
std::f64::consts::TAU - a.start_angle,
|
||||
)
|
||||
};
|
||||
KernelCurve::Arc(KernelArc {
|
||||
centre: [a.center.x, a.center.y],
|
||||
@@ -79,7 +80,10 @@ fn edge_curve(edge: &BoundaryEdge) -> Option<KernelCurve> {
|
||||
let (start, end) = if e.counter_clockwise {
|
||||
(e.start_angle, e.end_angle)
|
||||
} else {
|
||||
(e.end_angle, e.start_angle)
|
||||
(
|
||||
std::f64::consts::TAU - e.end_angle,
|
||||
std::f64::consts::TAU - e.start_angle,
|
||||
)
|
||||
};
|
||||
KernelCurve::Ellipse(KernelEllipseArc {
|
||||
ellipse: KernelEllipse {
|
||||
@@ -991,146 +995,41 @@ impl FallbackTess for Hatch {
|
||||
let mut snap_pts: Vec<(Vec3, SnapHint)> = Vec::new();
|
||||
for path in &self.paths {
|
||||
for edge in &path.edges {
|
||||
let Some(curve) = edge_curve(edge) else {
|
||||
continue;
|
||||
};
|
||||
let local = curve
|
||||
.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE);
|
||||
if local.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
let world: Vec<[f64; 3]> = local
|
||||
.into_iter()
|
||||
.map(|point| to_wcs(point[0], point[1]))
|
||||
.collect();
|
||||
match edge {
|
||||
BoundaryEdge::Polyline(poly) => {
|
||||
// Hatch-boundary polyline vertices encode bulge in
|
||||
// `Vector3.z`; straight segments emit just the
|
||||
// start vertex, bulged segments tessellate the arc
|
||||
// between v0 → v1.
|
||||
let verts = &poly.vertices;
|
||||
let count = verts.len();
|
||||
if count == 0 {
|
||||
continue;
|
||||
}
|
||||
// Break the wire between this polyline and whatever
|
||||
// preceded it — without the separator the renderer
|
||||
// draws a ghost segment from the previous edge / path
|
||||
// straight to this polyline's first vertex, which
|
||||
// shows up as a stray boundary line between hatch
|
||||
// regions.
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
let start_idx = pts.len();
|
||||
let seg_count = if poly.is_closed {
|
||||
count
|
||||
} else {
|
||||
count.saturating_sub(1)
|
||||
};
|
||||
for i in 0..seg_count {
|
||||
let v0 = &verts[i];
|
||||
let v1 = &verts[(i + 1) % count];
|
||||
let bulge = v0.z;
|
||||
let arc = if bulge.abs() < 1e-9 {
|
||||
None
|
||||
} else {
|
||||
crate::entities::common::BulgeArc::from_bulge(
|
||||
[v0.x, v0.y],
|
||||
[v1.x, v1.y],
|
||||
bulge,
|
||||
)
|
||||
};
|
||||
let Some(arc) = arc else {
|
||||
let p = to_wcs(v0.x, v0.y);
|
||||
pts.push(p);
|
||||
key_verts.push(p);
|
||||
continue;
|
||||
};
|
||||
let segs = arc_segments(
|
||||
arc.radius,
|
||||
arc.sweep.abs(),
|
||||
wire_chord_tol(arc.radius),
|
||||
);
|
||||
for j in 0..segs {
|
||||
let s = arc.sample(j as f64 / segs as f64);
|
||||
let p = to_wcs(s[0], s[1]);
|
||||
pts.push(p);
|
||||
if j == 0 {
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Close the loop visually for closed polylines by
|
||||
// returning to the first emitted point.
|
||||
if poly.is_closed {
|
||||
if let Some(first) = pts.get(start_idx).cloned() {
|
||||
if first[0].is_finite() {
|
||||
pts.push(first);
|
||||
}
|
||||
}
|
||||
} else if let Some(last) = verts.last() {
|
||||
let p = to_wcs(last.x, last.y);
|
||||
pts.push(p);
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
BoundaryEdge::Line(ln) => {
|
||||
let p0 = to_wcs(ln.start.x, ln.start.y);
|
||||
let p1 = to_wcs(ln.end.x, ln.end.y);
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
pts.push(p0);
|
||||
pts.push(p1);
|
||||
key_verts.push(p0);
|
||||
key_verts.push(p1);
|
||||
}
|
||||
BoundaryEdge::CircularArc(arc) => {
|
||||
let (sa, span) =
|
||||
arc_signed_span(arc.start_angle, arc.end_angle, arc.counter_clockwise);
|
||||
let segs = arc_segments(arc.radius, span.abs(), wire_chord_tol(arc.radius));
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
let p = to_wcs(
|
||||
arc.center.x + arc.radius * t.cos(),
|
||||
arc.center.y + arc.radius * t.sin(),
|
||||
);
|
||||
pts.push(p);
|
||||
if i == 0 || i == segs {
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
snap_pts.push((
|
||||
snap_at(to_wcs(arc.center.x, arc.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
BoundaryEdge::EllipticArc(ell) => {
|
||||
let r_maj = (ell.major_axis_endpoint.x * ell.major_axis_endpoint.x
|
||||
+ ell.major_axis_endpoint.y * ell.major_axis_endpoint.y)
|
||||
.sqrt();
|
||||
let r_min = r_maj * ell.minor_axis_ratio;
|
||||
let rot = ell.major_axis_endpoint.y.atan2(ell.major_axis_endpoint.x);
|
||||
let (sa, span) =
|
||||
arc_signed_span(ell.start_angle, ell.end_angle, ell.counter_clockwise);
|
||||
let segs = arc_segments(r_maj, span.abs(), wire_chord_tol(r_maj));
|
||||
if !pts.is_empty() {
|
||||
pts.push([f64::NAN; 3]);
|
||||
}
|
||||
let (cr, sr) = (rot.cos(), rot.sin());
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
let lx = r_maj * t.cos();
|
||||
let ly = r_min * t.sin();
|
||||
let p = to_wcs(
|
||||
ell.center.x + lx * cr - ly * sr,
|
||||
ell.center.y + lx * sr + ly * cr,
|
||||
);
|
||||
pts.push(p);
|
||||
if i == 0 || i == segs {
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
snap_pts.push((
|
||||
snap_at(to_wcs(ell.center.x, ell.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
BoundaryEdge::Polyline(poly) => key_verts.extend(
|
||||
poly.vertices
|
||||
.iter()
|
||||
.map(|point| to_wcs(point.x, point.y)),
|
||||
),
|
||||
_ => key_verts.extend([world[0], *world.last().unwrap()]),
|
||||
}
|
||||
match edge {
|
||||
BoundaryEdge::CircularArc(arc) => snap_pts.push((
|
||||
snap_at(to_wcs(arc.center.x, arc.center.y)),
|
||||
SnapHint::Center,
|
||||
)),
|
||||
BoundaryEdge::EllipticArc(ellipse) => snap_pts.push((
|
||||
snap_at(to_wcs(ellipse.center.x, ellipse.center.y)),
|
||||
SnapHint::Center,
|
||||
)),
|
||||
_ => {}
|
||||
}
|
||||
pts.extend(world);
|
||||
}
|
||||
}
|
||||
if pts.is_empty() {
|
||||
|
||||
@@ -308,8 +308,11 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
for j in 1..=16usize {
|
||||
let s = arc.sample(j as f64 / 16.0);
|
||||
for s in arc
|
||||
.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
{
|
||||
let (wx, wy, wz) = to_wcs(s[0], s[1]);
|
||||
path.push([wx, wy, wz]);
|
||||
}
|
||||
@@ -406,8 +409,11 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
} else if let Some(arc) =
|
||||
crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge)
|
||||
{
|
||||
for j in 1..=16usize {
|
||||
let s = arc.sample(j as f64 / 16.0);
|
||||
for s in arc
|
||||
.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
{
|
||||
let (wx, wy, wz) = to_wcs(s[0], s[1]);
|
||||
pts.push([wx, wy, wz]);
|
||||
}
|
||||
|
||||
@@ -258,8 +258,11 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
for j in 1..=16usize {
|
||||
let s = arc.sample(j as f64 / 16.0);
|
||||
for s in arc
|
||||
.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
{
|
||||
let (wx, wy, wz) = to_wcs(s[0], s[1]);
|
||||
path.push([wx, wy, wz]);
|
||||
}
|
||||
|
||||
+50
-40
@@ -157,25 +157,7 @@ pub(crate) fn measurement_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||
if degree == 0 || degree >= count {
|
||||
return spl.control_points.iter().map(|p| [p.x, p.y, p.z]).collect();
|
||||
}
|
||||
let mut min = [f64::INFINITY; 3];
|
||||
let mut max = [f64::NEG_INFINITY; 3];
|
||||
for point in &spl.control_points {
|
||||
min[0] = min[0].min(point.x);
|
||||
min[1] = min[1].min(point.y);
|
||||
min[2] = min[2].min(point.z);
|
||||
max[0] = max[0].max(point.x);
|
||||
max[1] = max[1].max(point.y);
|
||||
max[2] = max[2].max(point.z);
|
||||
}
|
||||
let diagonal = ((max[0] - min[0]).powi(2)
|
||||
+ (max[1] - min[1]).powi(2)
|
||||
+ (max[2] - min[2]).powi(2))
|
||||
.sqrt();
|
||||
let tolerance = crate::scene::convert::tess_util::fill_chord_tol(diagonal.max(1.0));
|
||||
|
||||
// The kernel's space curve holds the rational and the polynomial case
|
||||
// alike — weights absent means polynomial — and samples to a chord
|
||||
// tolerance, refined where the curve bends rather than evenly.
|
||||
// The kernel's space curve holds rational and polynomial curves alike.
|
||||
let controls: Vec<[f64; 3]> = spl
|
||||
.control_points
|
||||
.iter()
|
||||
@@ -188,7 +170,9 @@ pub(crate) fn measurement_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||
.collect()
|
||||
});
|
||||
match NurbsCurve3::new(degree, controls, spl.knots.clone(), weights) {
|
||||
Some(curve) => curve.tessellate_within(tolerance),
|
||||
Some(curve) => {
|
||||
curve.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
}
|
||||
None => spl.control_points.iter().map(|p| [p.x, p.y, p.z]).collect(),
|
||||
}
|
||||
}
|
||||
@@ -197,7 +181,6 @@ pub(crate) fn measurement_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||
/// 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();
|
||||
@@ -212,7 +195,7 @@ fn catmull_rom_polyline(pts: &[acadrust::types::Vector3], closed: bool) -> Vec<[
|
||||
[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);
|
||||
let mut out = Vec::new();
|
||||
for seg in 0..seg_count {
|
||||
let p1 = get(seg as isize);
|
||||
let p2 = get(seg as isize + 1);
|
||||
@@ -235,15 +218,7 @@ fn catmull_rom_polyline(pts: &[acadrust::types::Vector3], closed: bool) -> Vec<[
|
||||
} 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 point_at = |t: f64| {
|
||||
let (t2, t3) = (t * t, t * t * t);
|
||||
let mut q = [0.0f64; 3];
|
||||
for k in 0..3 {
|
||||
@@ -253,8 +228,24 @@ fn catmull_rom_polyline(pts: &[acadrust::types::Vector3], closed: bool) -> Vec<[
|
||||
+ (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);
|
||||
}
|
||||
q
|
||||
};
|
||||
let tangent_at = |t: f64| {
|
||||
let mut q = [0.0; 3];
|
||||
for k in 0..3 {
|
||||
let a = -p0[k] + p2[k];
|
||||
let b = 2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k];
|
||||
let c = -p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k];
|
||||
q[k] = 0.5 * (a + 2.0 * b * t + 3.0 * c * t * t);
|
||||
}
|
||||
q
|
||||
};
|
||||
let sampled = acadrust::kernel::tessellation::sample_curve3_angle(
|
||||
point_at,
|
||||
tangent_at,
|
||||
acadrust::kernel::tessellation::DEFAULT_ANGLE,
|
||||
);
|
||||
out.extend(sampled.into_iter().skip(usize::from(seg > 0)));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -340,12 +331,9 @@ fn fit_spline_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||
}
|
||||
|
||||
// Evaluate each segment as a cubic Hermite (dP/du = slope · h_i).
|
||||
const STEPS: usize = 32;
|
||||
let mut out = Vec::with_capacity((n - 1) * STEPS + 1);
|
||||
let mut out = Vec::new();
|
||||
for i in 0..n - 1 {
|
||||
let last = if i == n - 2 { STEPS } else { STEPS - 1 };
|
||||
for s in 0..=last {
|
||||
let u = s as f64 / STEPS as f64;
|
||||
let point_at = |u: f64| {
|
||||
let (u2, u3) = (u * u, u * u * u);
|
||||
let h00 = 2.0 * u3 - 3.0 * u2 + 1.0;
|
||||
let h10 = u3 - 2.0 * u2 + u;
|
||||
@@ -357,8 +345,30 @@ fn fit_spline_polyline(spl: &Spline) -> Vec<[f64; 3]> {
|
||||
let m1 = slopes[k][i + 1] * h[i];
|
||||
q[k] = h00 * p[i][k] + h10 * m0 + h01 * p[i + 1][k] + h11 * m1;
|
||||
}
|
||||
out.push(q);
|
||||
}
|
||||
q
|
||||
};
|
||||
let tangent_at = |u: f64| {
|
||||
let u2 = u * u;
|
||||
let (h00, h10, h01, h11) = (
|
||||
6.0 * u2 - 6.0 * u,
|
||||
3.0 * u2 - 4.0 * u + 1.0,
|
||||
-6.0 * u2 + 6.0 * u,
|
||||
3.0 * u2 - 2.0 * u,
|
||||
);
|
||||
let mut q = [0.0; 3];
|
||||
for k in 0..3 {
|
||||
let m0 = slopes[k][i] * h[i];
|
||||
let m1 = slopes[k][i + 1] * h[i];
|
||||
q[k] = h00 * p[i][k] + h10 * m0 + h01 * p[i + 1][k] + h11 * m1;
|
||||
}
|
||||
q
|
||||
};
|
||||
let sampled = acadrust::kernel::tessellation::sample_curve3_angle(
|
||||
point_at,
|
||||
tangent_at,
|
||||
acadrust::kernel::tessellation::DEFAULT_ANGLE,
|
||||
);
|
||||
out.extend(sampled.into_iter().skip(usize::from(i > 0)));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user