1 parent
5165e428cd
commit
daf618c344
6 files changed
+243
-15
No files matched your search
@@ -0,0 +1,159 @@
|
|||||||
|
//! The vendored edge division evaluates each parameter once (the ends of a piece are carried
|
||||||
|
//! along, its midpoint is the probe and an end of both halves). Regressions against the division
|
||||||
|
//! as it was, which evaluated both ends of every piece again: the polylines must be the same, bit
|
||||||
|
//! for bit, including the fallbacks for samples that do not evaluate.
|
||||||
|
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use solidcraft_geom::Vec3;
|
||||||
|
use truck_meshalgo::prelude::PolylineCurve;
|
||||||
|
use truck_meshalgo::tessellation::{Deflection, TrySubs, bounded_polyline};
|
||||||
|
use truck_modeling::{BoundedCurve, Curve, EuclideanSpace, MetricSpace, ParameterDivision1D, ParameterRange, ParametricCurve, Point3, Vector3};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// A curve that counts its evaluations.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Counted {
|
||||||
|
curve: Curve,
|
||||||
|
calls: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Counted {
|
||||||
|
fn new(curve: &Curve) -> Self {
|
||||||
|
Counted { curve: curve.clone(), calls: Arc::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calls(&self) -> usize {
|
||||||
|
self.calls.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParametricCurve for Counted {
|
||||||
|
type Point = Point3;
|
||||||
|
type Vector = Vector3;
|
||||||
|
fn subs(&self, t: f64) -> Point3 {
|
||||||
|
self.calls.fetch_add(1, Ordering::Relaxed);
|
||||||
|
self.curve.subs(t)
|
||||||
|
}
|
||||||
|
fn der(&self, t: f64) -> Vector3 {
|
||||||
|
self.curve.der(t)
|
||||||
|
}
|
||||||
|
fn der2(&self, t: f64) -> Vector3 {
|
||||||
|
self.curve.der2(t)
|
||||||
|
}
|
||||||
|
fn parameter_range(&self) -> ParameterRange {
|
||||||
|
self.curve.parameter_range()
|
||||||
|
}
|
||||||
|
fn period(&self) -> Option<f64> {
|
||||||
|
self.curve.period()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoundedCurve for Counted {}
|
||||||
|
|
||||||
|
/// The division samples through `TrySubs`: count those evaluations.
|
||||||
|
impl TrySubs for Counted {
|
||||||
|
fn try_subs(&self, t: f64) -> Option<Point3> {
|
||||||
|
self.calls.fetch_add(1, Ordering::Relaxed);
|
||||||
|
TrySubs::try_subs(&self.curve, t)
|
||||||
|
}
|
||||||
|
fn approx_subs(&self, t: f64) -> Point3 {
|
||||||
|
self.calls.fetch_add(1, Ordering::Relaxed);
|
||||||
|
TrySubs::approx_subs(&self.curve, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParameterDivision1D for Counted {
|
||||||
|
type Point = Point3;
|
||||||
|
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Point3>) {
|
||||||
|
self.curve.parameter_division(range, tol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `bounded_polyline` before it carried the end points: 4 evaluations per piece (both ends, the
|
||||||
|
/// probe and the midpoint), with the same fallbacks for samples that do not evaluate.
|
||||||
|
fn reference(curve: &Counted, range: (f64, f64), d: Deflection) -> PolylineCurve<Point3> {
|
||||||
|
const MAX_EDGE_POINTS: usize = 20_000;
|
||||||
|
let (tol, turn2) = (d.chord, d.turn2());
|
||||||
|
let end = |t: f64| curve.try_subs(t).unwrap_or_else(|| curve.approx_subs(t));
|
||||||
|
let probe = |t: f64| curve.try_subs(t).unwrap_or(Point3::new(f64::NAN, f64::NAN, f64::NAN));
|
||||||
|
let mut out = vec![end(range.0)];
|
||||||
|
let mut stack: Vec<(f64, f64, usize)> = vec![(range.0, range.1, 0)];
|
||||||
|
while let Some((a, b, depth)) = stack.pop() {
|
||||||
|
let (pa, pb) = (end(a), end(b));
|
||||||
|
let t = a + (b - a) * 0.4729;
|
||||||
|
let chord = pa + (pb - pa) * 0.4729;
|
||||||
|
let d2 = probe(t).distance2(chord);
|
||||||
|
let tm = 0.5 * (a + b);
|
||||||
|
let dm = probe(tm).distance2(pa.midpoint(pb));
|
||||||
|
let flat = !(d2 >= tol * tol || dm >= tol * tol);
|
||||||
|
let flat = flat && !d.turns(turn2, d2.max(dm), pa.distance2(pb));
|
||||||
|
if flat || depth >= 24 || out.len() + stack.len() >= MAX_EDGE_POINTS {
|
||||||
|
out.push(pb);
|
||||||
|
} else {
|
||||||
|
stack.push((tm, b, depth + 1));
|
||||||
|
stack.push((a, tm, depth + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PolylineCurve(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Divides `curve` both ways and checks the points, their order and the evaluations; returns
|
||||||
|
/// the polyline.
|
||||||
|
fn same_division(curve: &Curve, d: Deflection) -> Vec<Point3> {
|
||||||
|
let range = curve.range_tuple();
|
||||||
|
let (before, after) = (Counted::new(curve), Counted::new(curve));
|
||||||
|
let old = reference(&before, range, d).0;
|
||||||
|
let new = bounded_polyline(&after, range, d).0;
|
||||||
|
let bits = |p: &Point3| [p.x.to_bits(), p.y.to_bits(), p.z.to_bits()];
|
||||||
|
assert_eq!(old.len(), new.len(), "{d:?}");
|
||||||
|
for (i, (p, q)) in old.iter().zip(&new).enumerate() {
|
||||||
|
assert_eq!(bits(p), bits(q), "point {i} of {}: {p:?} before, {q:?} now ({d:?})", new.len());
|
||||||
|
}
|
||||||
|
// Every piece is kept (one point) or halved: n points come from 2n - 3 pieces.
|
||||||
|
let pieces = 2 * new.len() - 3;
|
||||||
|
assert_eq!(before.calls(), 1 + 4 * pieces);
|
||||||
|
assert_eq!(after.calls(), 2 + 2 * pieces);
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The chord tolerance alone (booleans), then with the display's facet angle.
|
||||||
|
fn deflections(tol: f64) -> [Deflection; 2] {
|
||||||
|
[Deflection::chord(tol), Deflection { chord: tol, angle: DISPLAY_ANGLE, floor: tol * 0.05, rulings: true }]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersection_curves_of_a_drilled_cylinder_divide_as_before() {
|
||||||
|
// The radial hole of a clamping hub: both cylinders are NURBS, so each point of the edges
|
||||||
|
// is two grid searches.
|
||||||
|
let hub = cylinder(Vec3::new(0.0, 0.0, -10.0), Vec3::Z, 8.0, 20.0).unwrap();
|
||||||
|
let drill = cylinder(Vec3::new(-12.0, 0.0, 1.5), Vec3::X, 1.25, 24.0).unwrap();
|
||||||
|
let drilled = boolean(&hub, &drill, BoolOp::Cut).unwrap().unwrap();
|
||||||
|
let curves: Vec<Curve> = drilled.solid.edge_iter().map(|e| e.curve()).filter(|c| matches!(c, Curve::IntersectionCurve(_))).collect();
|
||||||
|
assert!(curves.len() >= 2, "{} intersection curves", curves.len());
|
||||||
|
// One curve with each deflection (each division takes about half a second here).
|
||||||
|
for (curve, d) in curves.iter().zip(deflections(0.01)) {
|
||||||
|
let points = same_division(curve, d);
|
||||||
|
assert!(points.len() > 8, "{} points ({d:?})", points.len());
|
||||||
|
// On the drill's wall.
|
||||||
|
for p in &points {
|
||||||
|
let r = (p.y * p.y + (p.z - 1.5) * (p.z - 1.5)).sqrt();
|
||||||
|
assert!((r - 1.25).abs() < 1e-3, "{p:?} at {r} from the drill's axis");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn circle_divides_as_before() {
|
||||||
|
let body = cylinder(Vec3::ZERO, Vec3::Z, 5.0, 10.0).unwrap();
|
||||||
|
let circle = body.solid.edge_iter().map(|e| e.curve()).find(|c| !matches!(c, Curve::Line(_))).unwrap();
|
||||||
|
for d in deflections(0.001) {
|
||||||
|
let points = same_division(&circle, d);
|
||||||
|
assert!(points.len() > 18, "{} points ({d:?})", points.len());
|
||||||
|
for p in &points {
|
||||||
|
assert!(((p.x * p.x + p.y * p.y).sqrt() - 5.0).abs() < 1e-9, "{p:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ mod clearance;
|
|||||||
mod coplanar;
|
mod coplanar;
|
||||||
mod curveblend;
|
mod curveblend;
|
||||||
mod delete_face;
|
mod delete_face;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod edge_polyline_tests;
|
||||||
mod emboss;
|
mod emboss;
|
||||||
mod face_params;
|
mod face_params;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
# Design loading
|
# Design loading
|
||||||
|
|
||||||
|
## Edges made by booleans: each point evaluated once (2026-10-16)
|
||||||
|
|
||||||
|
A boolean meshes both operands with truck-meshalgo, which first divides every edge into a
|
||||||
|
polyline (`bounded_polyline`, vendored). An edge that an earlier boolean made is an
|
||||||
|
intersection curve. Each of its points is found by a grid search on each of the two surfaces,
|
||||||
|
51 × 51 evaluations of a NURBS surface each, before Newton. The division evaluated both ends of
|
||||||
|
every piece again, so 2 of its 4 evaluations per piece repeated a point it already had.
|
||||||
|
Profiles of `main` (01b1b705) on the machine below put the division at 42 % of the CPU time
|
||||||
|
of opening the rover, 58 % of `bench-agent` and 82-85 % of a drilled hub or a union of tubes.
|
||||||
|
|
||||||
|
Each piece now carries the points of its ends, and its midpoint is both the probe and an end of
|
||||||
|
both halves: 2 evaluations per piece. The fallbacks for samples that do not evaluate (#1364) are
|
||||||
|
kept: a midpoint that fails is NaN as a probe and the curve's approximation as an end, exactly as
|
||||||
|
before. The polyline is the same, bit for bit: `edge_polyline_tests` compares it with the
|
||||||
|
previous division (points, order and number of evaluations) on the intersection curves of a
|
||||||
|
drilled cylinder and on a circle.
|
||||||
|
|
||||||
|
Release `solidcraft-cli` from `main` (73493b94) and the same with the change, 3 alternating
|
||||||
|
pairs (before/after, after/before, before/after), each run a new process timed with
|
||||||
|
`/usr/bin/time -v`, started once the 1-minute load average was below 2.5. Medians, with the
|
||||||
|
range of the 3 runs. 2018 laptop: Intel Core i7-8565U (4 cores / 8 threads, 15 W), 7.4 GB,
|
||||||
|
Ubuntu (Linux 7.0), on mains power:
|
||||||
|
|
||||||
|
| Run | Evaluation | Meshes | Wall time | CPU time | Peak RSS |
|
||||||
|
|---|---:|---:|---:|---:|---:|
|
||||||
|
| `bench-open`, `examples/acceptance/sensor_enclosure.json` | 3,203 → 3,089 ms | 6 → 6 ms | 3.2 → 3.1 s | 8.7 → 8.5 s | 91 → 91 MB |
|
||||||
|
| `bench-open`, a hub barrel with one radial hole cut through it | 966 → 817 ms | 878 → 481 ms | 1.9 → 1.4 s (-24 %) | 4.4 → 2.8 s (-37 %) | 27 → 30 MB |
|
||||||
|
| `bench-open`, the frame of the `bench-agent` bicycle (one union of tilted tubes) | 8,204 → 6,277 ms | 1,711 → 893 ms | 9.9 → 7.2 s (-28 %) | 33.3 → 20.6 s (-38 %) | 44 → 46 MB |
|
||||||
|
| `bench-open`, a rover (3,324 features, 1,395 bodies) | 26,783 → 22,792 ms | 7,960 → 6,046 ms | 35.7 → 29.8 s (-17 %) | 129.0 → 101.9 s (-21 %) | 2,076 → 2,079 MB |
|
||||||
|
| `bench-agent` (build 33.5 → 30.2 s, -10 %) | 10,553 → 8,565 ms | 2,778 → 2,081 ms | 47.6 → 41.1 s (-14 %) | 124.0 → 91.0 s (-27 %) | 1,428 → 1,432 MB |
|
||||||
|
|
||||||
|
`bench-agent`'s evaluation and meshes are those of reopening the saved design. CPU time is the
|
||||||
|
steadier measure on this 15 W processor: its before and after ranges are apart for the four
|
||||||
|
cases with boolean edges (the hub 3.0-4.4 s before, 2.5-2.9 s after; the rover 113.6-133.5 s,
|
||||||
|
101.0-102.5 s), while the first pair's "before" run, which started on a cool processor, overlaps
|
||||||
|
the "after" wall times of the two short cases. The sensor enclosure does not change: its time goes
|
||||||
|
to grid searches in face meshing, which this does not touch. An earlier series on `main`
|
||||||
|
(8535d229), on a quieter machine, gave -38 %, -35 %, -18 % and -16 % of wall time for the same
|
||||||
|
four cases. The CPU time saved is close to half the division's share in the profile. The mesh
|
||||||
|
phase gains as well, so these display meshes also call the reference mesher somewhere (where is
|
||||||
|
not traced here; `tess::edges`, the display plans' own edge division, already evaluated each
|
||||||
|
point once). In the rover, the second shell (which names every body's faces, so meshes them all)
|
||||||
|
goes from 7.8 s to 5.7 s.
|
||||||
|
|
||||||
|
No model changed. `solidcraft-cli eval` of the five designs (the bicycle as `bench-agent` saved
|
||||||
|
it) is the same with both binaries apart from the per-feature milliseconds: bodies, volumes,
|
||||||
|
areas, centres, bounding boxes, topology counts and warnings. `bench-open` meshes the same
|
||||||
|
triangle counts, and the bicycles both binaries save differ only in the document's name.
|
||||||
|
|
||||||
## STEP assemblies: a repeated part is meshed once (2026-10-14)
|
## STEP assemblies: a repeated part is meshed once (2026-10-14)
|
||||||
|
|
||||||
The reader already converts each B-rep item once and keeps it for the instances that list it
|
The reader already converts each B-rep item once and keeps it for the instances that list it
|
||||||
|
|||||||
Vendored
+5
@@ -108,6 +108,11 @@ Changes:
|
|||||||
point that does not evaluate counts as flat instead of recursing). Surface grids refine only
|
point that does not evaluate counts as flat instead of recursing). Surface grids refine only
|
||||||
the direction that bends, and a ruling left undivided gets cells comparable to the other
|
the direction that bends, and a ruling left undivided gets cells comparable to the other
|
||||||
direction (long triangles from a trim loop to the grid's far ends cut through cylinders).
|
direction (long triangles from a trim loop to the grid's far ends cut through cylinders).
|
||||||
|
- `tessellation/triangulation.rs`: an edge's division evaluates each parameter once. A piece
|
||||||
|
carries the points of its ends and its midpoint is an end of both halves: 2 evaluations per
|
||||||
|
piece instead of 4, and the same polyline (a point of an intersection curve costs two grid
|
||||||
|
searches of its surfaces). `bounded_polyline` and `Deflection::turn2`/`turns` are public but
|
||||||
|
hidden, for the kernel's regression against the old division.
|
||||||
- `tessellation/mod.rs`, `tessellation/triangulation.rs`, for display: a `Deflection` is the
|
- `tessellation/mod.rs`, `tessellation/triangulation.rs`, for display: a `Deflection` is the
|
||||||
chord tolerance plus an optional bound on the angle one facet spans, and
|
chord tolerance plus an optional bound on the angle one facet spans, and
|
||||||
`RoundMeshableShape::robust_triangulation_within` divides by it: an edge or a grid direction
|
`RoundMeshableShape::robust_triangulation_within` divides by it: an edge or a grid direction
|
||||||
|
|||||||
+13
-4
@@ -25,6 +25,13 @@ pub fn preview_face<S: RobustMeshableSurface>(surface: &S, loops: &[Vec<Point3>]
|
|||||||
triangulation::preview_face(surface, loops, steps)
|
triangulation::preview_face(surface, loops, steps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SolidCraft: an edge's polyline as the meshers divide it (at most 20 000 points). Exposed for
|
||||||
|
/// the kernel's regressions in the workspace CI.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn bounded_polyline<C: PolylineableCurve>(curve: &C, range: (f64, f64), d: Deflection) -> PolylineCurve {
|
||||||
|
triangulation::bounded_polyline(curve, range, d)
|
||||||
|
}
|
||||||
|
|
||||||
/// SolidCraft: sampling a curve that may fail to evaluate. truck's `IntersectionCurve::subs`
|
/// SolidCraft: sampling a curve that may fail to evaluate. truck's `IntersectionCurve::subs`
|
||||||
/// unwraps the projection of its leader's point onto both surfaces, which does not converge
|
/// unwraps the projection of its leader's point onto both surfaces, which does not converge
|
||||||
/// everywhere; the mesher samples through this trait so that a failed sample is data, not a panic.
|
/// everywhere; the mesher samples through this trait so that a failed sample is data, not a panic.
|
||||||
@@ -145,8 +152,9 @@ impl Deflection {
|
|||||||
|
|
||||||
/// The squared sag over squared length above which a facet turns more than `angle`: a
|
/// The squared sag over squared length above which a facet turns more than `angle`: a
|
||||||
/// chord spanning `a` of a circle sags `tan(a / 4) / 2` of its length. Infinite without
|
/// chord spanning `a` of a circle sags `tan(a / 4) / 2` of its length. Infinite without
|
||||||
/// an angle bound.
|
/// an angle bound. SolidCraft: public for the kernel's regressions.
|
||||||
pub(super) fn turn2(&self) -> f64 {
|
#[doc(hidden)]
|
||||||
|
pub fn turn2(&self) -> f64 {
|
||||||
match self.angular() && self.angle < std::f64::consts::PI {
|
match self.angular() && self.angle < std::f64::consts::PI {
|
||||||
true => (f64::tan(self.angle / 4.0) / 2.0).powi(2),
|
true => (f64::tan(self.angle / 4.0) / 2.0).powi(2),
|
||||||
false => f64::INFINITY,
|
false => f64::INFINITY,
|
||||||
@@ -154,8 +162,9 @@ impl Deflection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the angle bound splits a facet of squared sag `sag2` and squared length `len2`
|
/// Whether the angle bound splits a facet of squared sag `sag2` and squared length `len2`
|
||||||
/// (`turn2` from [`Deflection::turn2`]).
|
/// (`turn2` from [`Deflection::turn2`]). SolidCraft: public for the kernel's regressions.
|
||||||
pub(super) fn turns(&self, turn2: f64, sag2: f64, len2: f64) -> bool {
|
#[doc(hidden)]
|
||||||
|
pub fn turns(&self, turn2: f64, sag2: f64, len2: f64) -> bool {
|
||||||
turn2.is_finite() && sag2 > self.floor * self.floor && sag2 > turn2 * len2
|
turn2.is_finite() && sag2 > self.floor * self.floor && sag2 > turn2 * len2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-11
@@ -861,30 +861,34 @@ pub(super) fn face_cap(faces: usize) -> usize {
|
|||||||
/// bisection depth of 24; a midpoint that does not evaluate (NaN) counts as flat (truck's
|
/// bisection depth of 24; a midpoint that does not evaluate (NaN) counts as flat (truck's
|
||||||
/// version recursed on it 100 levels deep, which never finishes). A piece within the chord
|
/// version recursed on it 100 levels deep, which never finishes). A piece within the chord
|
||||||
/// tolerance is still halved while it turns more than the deflection's angle.
|
/// tolerance is still halved while it turns more than the deflection's angle.
|
||||||
fn bounded_polyline<C: PolylineableCurve>(curve: &C, range: (f64, f64), d: Deflection) -> PolylineCurve {
|
pub(super) fn bounded_polyline<C: PolylineableCurve>(curve: &C, range: (f64, f64), d: Deflection) -> PolylineCurve {
|
||||||
let (tol, turn2) = (d.chord, d.turn2());
|
let (tol, turn2) = (d.chord, d.turn2());
|
||||||
// A sample that does not evaluate (an intersection curve whose projection fails): an interval
|
// A sample that does not evaluate (an intersection curve whose projection fails): an interval
|
||||||
// end falls back to the curve's approximation so the polyline stays finite; an interior probe
|
// end falls back to the curve's approximation so the polyline stays finite; an interior probe
|
||||||
// is NaN, which counts as flat.
|
// is NaN, which counts as flat. Each parameter is evaluated once: a piece carries the points
|
||||||
|
// of its ends, and its midpoint, an end of both halves, serves as probe and as end (a point of
|
||||||
|
// an intersection curve costs two grid searches).
|
||||||
let end = |t: f64| curve.try_subs(t).unwrap_or_else(|| curve.approx_subs(t));
|
let end = |t: f64| curve.try_subs(t).unwrap_or_else(|| curve.approx_subs(t));
|
||||||
let probe = |t: f64| curve.try_subs(t).unwrap_or(Point3::new(f64::NAN, f64::NAN, f64::NAN));
|
let nan = Point3::new(f64::NAN, f64::NAN, f64::NAN);
|
||||||
let mut out = vec![end(range.0)];
|
let (p0, p1) = (end(range.0), end(range.1));
|
||||||
let mut stack: Vec<(f64, f64, usize)> = vec![(range.0, range.1, 0)];
|
let mut out = vec![p0];
|
||||||
while let Some((a, b, depth)) = stack.pop() {
|
let mut stack: Vec<(f64, f64, Point3, Point3, usize)> = vec![(range.0, range.1, p0, p1, 0)];
|
||||||
let (pa, pb) = (end(a), end(b));
|
while let Some((a, b, pa, pb, depth)) = stack.pop() {
|
||||||
let t = a + (b - a) * 0.4729;
|
let t = a + (b - a) * 0.4729;
|
||||||
let chord = pa + (pb - pa) * 0.4729;
|
let chord = pa + (pb - pa) * 0.4729;
|
||||||
let d2 = probe(t).distance2(chord);
|
let d2 = curve.try_subs(t).unwrap_or(nan).distance2(chord);
|
||||||
let tm = 0.5 * (a + b);
|
let tm = 0.5 * (a + b);
|
||||||
let dm = probe(tm).distance2(pa.midpoint(pb));
|
let sm = curve.try_subs(tm);
|
||||||
|
let dm = sm.unwrap_or(nan).distance2(pa.midpoint(pb));
|
||||||
let flat = !(d2 >= tol * tol || dm >= tol * tol);
|
let flat = !(d2 >= tol * tol || dm >= tol * tol);
|
||||||
let flat = flat && !d.turns(turn2, d2.max(dm), pa.distance2(pb));
|
let flat = flat && !d.turns(turn2, d2.max(dm), pa.distance2(pb));
|
||||||
if flat || depth >= 24 || out.len() + stack.len() >= MAX_EDGE_POINTS {
|
if flat || depth >= 24 || out.len() + stack.len() >= MAX_EDGE_POINTS {
|
||||||
out.push(pb);
|
out.push(pb);
|
||||||
} else {
|
} else {
|
||||||
|
let pm = sm.unwrap_or_else(|| curve.approx_subs(tm));
|
||||||
// Right half after the left one (stack order).
|
// Right half after the left one (stack order).
|
||||||
stack.push((tm, b, depth + 1));
|
stack.push((tm, b, pm, pb, depth + 1));
|
||||||
stack.push((a, tm, depth + 1));
|
stack.push((a, tm, pa, pm, depth + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PolylineCurve(out)
|
PolylineCurve(out)
|
||||||
|
|||||||
Reference in new issue
Block a user