21 Vec2 Normalise(Vec2 v) {
22 const double n = std::hypot(v.x, v.y);
23 return (n > 1e-12) ? Vec2{v.x / n, v.y / n} : Vec2{1.0, 0.0};
27 std::vector<Vec2> out;
28 for (
const geom::Point3 &p : line) {
29 if (out.empty() || std::hypot(p.x - out.back().x, p.y - out.back().y) > 1e-9) {
30 out.push_back(Vec2{p.x, p.y});
38 void HermiteSegment(
const Vec2 &p0,
const Vec2 &p1,
const Vec2 &t0,
const Vec2 &t1,
39 double &hdg,
double &chord, std::array<double, 8> &coeffs) {
40 const Vec2 delta{p1.x - p0.x, p1.y - p0.y};
41 chord = std::hypot(delta.x, delta.y);
42 hdg = std::atan2(t0.y, t0.x);
43 const double c = std::cos(hdg);
44 const double s = std::sin(hdg);
46 const Vec2 p1l{c * delta.x + s * delta.y, -s * delta.x + c * delta.y};
47 const Vec2 m0l{chord, 0.0};
48 const Vec2 m1l{(c * t1.x + s * t1.y) * chord, (-s * t1.x + c * t1.y) * chord};
49 const Vec2
cc{3.0 * p1l.x - 2.0 * m0l.x - m1l.x, 3.0 * p1l.y - 2.0 * m0l.y - m1l.y};
50 const Vec2 d{-2.0 * p1l.x + m0l.x + m1l.x, -2.0 * p1l.y + m0l.y + m1l.y};
51 coeffs = {0.0, m0l.x,
cc.x, d.x, 0.0, m0l.y,
cc.y, d.y};
54 void Evaluate(
const std::array<double, 8> &c,
double p,
double &u,
double &v) {
55 const double p2 = p * p;
56 const double p3 = p2 * p;
57 u = c[0] + c[1] * p + c[2] * p2 + c[3] * p3;
58 v = c[4] + c[5] * p + c[6] * p2 + c[7] * p3;
62 double ChordOffset(
const std::array<double, 8> &coeffs,
int samples = 33) {
65 Evaluate(coeffs, 1.0, ex, ey);
66 const double length = std::hypot(ex, ey);
68 for (
int i = 0; i < samples; ++i) {
69 const double p =
static_cast<double>(i) /
static_cast<double>(samples - 1);
72 Evaluate(coeffs, p, u, v);
74 worst = std::max(worst, std::hypot(u, v));
77 double t = (u * ex + v * ey) / (length * length);
78 t = std::clamp(t, 0.0, 1.0);
79 worst = std::max(worst, std::hypot(u - t * ex, v - t * ey));
84 double CubicLength(
const std::array<double, 8> &coeffs,
int steps) {
88 Evaluate(coeffs, 0.0, pu, pv);
89 for (
int i = 1; i <= steps; ++i) {
92 Evaluate(coeffs,
static_cast<double>(i) /
static_cast<double>(steps), u, v);
93 total += std::hypot(u - pu, v - pv);
103 std::vector<Geom> out;
104 const std::vector<Vec2> pts = Dedupe(line);
105 if (pts.size() < 2u) {
108 if (pts.size() == 2u) {
112 g.
hdg = std::atan2(pts[1].
y - pts[0].
y, pts[1].
x - pts[0].
x);
113 g.
length = std::hypot(pts[1].
x - pts[0].
x, pts[1].
y - pts[0].
y);
119 const std::size_t n = pts.size();
120 std::vector<Vec2> chord(n - 1u);
121 for (std::size_t i = 0u; i + 1u < n; ++i) {
122 chord[i] = Normalise(Vec2{pts[i + 1u].x - pts[i].x, pts[i + 1u].y - pts[i].y});
126 std::vector<Vec2> tangent(n);
127 tangent[0] = chord.front();
128 tangent[n - 1u] = chord.back();
129 for (std::size_t i = 1u; i + 1u < n; ++i) {
130 tangent[i] = Normalise(Vec2{pts[i + 1u].x - pts[i - 1u].x, pts[i + 1u].y - pts[i - 1u].y});
132 std::vector<Vec2> t_out = tangent;
133 std::vector<Vec2> t_in = tangent;
134 std::vector<char> corner(n, 0);
138 auto turn_at = [&](std::size_t k) {
139 const Vec2 &a = chord[k - 1u];
140 const Vec2 &b = chord[k];
141 return std::abs(std::atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y));
145 for (
int pass = 0; pass < 4; ++pass) {
147 for (std::size_t i = 0u; i + 1u < n; ++i) {
149 double chord_length = 0.0;
150 std::array<double, 8> coeffs{};
151 HermiteSegment(pts[i], pts[i + 1u], t_out[i], t_in[i + 1u], hdg, chord_length, coeffs);
152 if (ChordOffset(coeffs) <= max_offset) {
155 std::vector<std::size_t> candidates;
156 if (corner[i] == 0) candidates.push_back(i);
157 if (corner[i + 1u] == 0) candidates.push_back(i + 1u);
158 if (candidates.empty()) {
161 std::size_t pick = candidates.front();
162 for (std::size_t candidate : candidates) {
163 if (turn_at(candidate) > turn_at(pick)) {
168 t_out[pick] = chord[pick];
169 t_in[pick] = chord[pick - 1u];
178 for (std::size_t i = 0u; i + 1u < n; ++i) {
180 double chord_length = 0.0;
181 std::array<double, 8> coeffs{};
182 HermiteSegment(pts[i], pts[i + 1u], t_out[i], t_in[i + 1u], hdg, chord_length, coeffs);
189 if (corner[i] != 0 && corner[i + 1u] != 0) {
194 g.
length = CubicLength(coeffs, std::max(64,
static_cast<int>(chord_length / 0.05)));
std::vector< Point3 > Line3
std::vector< Geom > FitPlanView(const geom::Line3 &line, double max_offset)
Fit a polyline as a piecewise cubic paramPoly3 planview.
std::array< double, 8 > coeffs
aU bU cU dU aV bV cV dV, with aU = aV = bV = 0 by construction.