CARLA
Loading...
Searching...
No Matches
PlanView.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "PlanView.h"
8
9#include <algorithm>
10#include <cmath>
11
12namespace twinmodel {
13
14 namespace {
15
16 struct Vec2 {
17 double x = 0.0;
18 double y = 0.0;
19 };
20
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};
24 }
25
26 std::vector<Vec2> Dedupe(const geom::Line3 &line) {
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});
31 }
32 }
33 return out;
34 }
35
36 /// Cubic Hermite from p0 (unit tangent t0) to p1 (unit tangent t1), tangent
37 /// magnitudes the chord length, in the frame rotated to t0.
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);
45 // world -> local, u along the start tangent
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};
52 }
53
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;
59 }
60
61 /// Largest distance of the fitted cubic from the chord it spans.
62 double ChordOffset(const std::array<double, 8> &coeffs, int samples = 33) {
63 double ex = 0.0;
64 double ey = 0.0;
65 Evaluate(coeffs, 1.0, ex, ey);
66 const double length = std::hypot(ex, ey);
67 double worst = 0.0;
68 for (int i = 0; i < samples; ++i) {
69 const double p = static_cast<double>(i) / static_cast<double>(samples - 1);
70 double u = 0.0;
71 double v = 0.0;
72 Evaluate(coeffs, p, u, v);
73 if (length < 1e-9) {
74 worst = std::max(worst, std::hypot(u, v));
75 continue;
76 }
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));
80 }
81 return worst;
82 }
83
84 double CubicLength(const std::array<double, 8> &coeffs, int steps) {
85 double total = 0.0;
86 double pu = 0.0;
87 double pv = 0.0;
88 Evaluate(coeffs, 0.0, pu, pv);
89 for (int i = 1; i <= steps; ++i) {
90 double u = 0.0;
91 double v = 0.0;
92 Evaluate(coeffs, static_cast<double>(i) / static_cast<double>(steps), u, v);
93 total += std::hypot(u - pu, v - pv);
94 pu = u;
95 pv = v;
96 }
97 return total;
98 }
99
100 } // namespace
101
102 std::vector<Geom> FitPlanView(const geom::Line3 &line, double max_offset) {
103 std::vector<Geom> out;
104 const std::vector<Vec2> pts = Dedupe(line);
105 if (pts.size() < 2u) {
106 return out;
107 }
108 if (pts.size() == 2u) {
109 Geom g;
110 g.x = pts[0].x;
111 g.y = pts[0].y;
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);
114 g.is_line = true;
115 out.push_back(g);
116 return out;
117 }
118
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});
123 }
124
125 // Catmull-Rom tangent directions; the ends follow their own chord
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});
131 }
132 std::vector<Vec2> t_out = tangent;
133 std::vector<Vec2> t_in = tangent;
134 std::vector<char> corner(n, 0);
135 corner.front() = 1;
136 corner.back() = 1;
137
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));
142 };
143
144 // lock the vertices whose rounding bows too far off the polyline
145 for (int pass = 0; pass < 4; ++pass) {
146 bool locked = false;
147 for (std::size_t i = 0u; i + 1u < n; ++i) {
148 double hdg = 0.0;
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) {
153 continue;
154 }
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()) {
159 continue;
160 }
161 std::size_t pick = candidates.front();
162 for (std::size_t candidate : candidates) {
163 if (turn_at(candidate) > turn_at(pick)) {
164 pick = candidate;
165 }
166 }
167 corner[pick] = 1;
168 t_out[pick] = chord[pick];
169 t_in[pick] = chord[pick - 1u];
170 locked = true;
171 }
172 if (!locked) {
173 break;
174 }
175 }
176
177 double s = 0.0;
178 for (std::size_t i = 0u; i + 1u < n; ++i) {
179 double hdg = 0.0;
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);
183 Geom g;
184 g.s = s;
185 g.x = pts[i].x;
186 g.y = pts[i].y;
187 g.hdg = hdg;
188 g.coeffs = coeffs;
189 if (corner[i] != 0 && corner[i + 1u] != 0) {
190 g.is_line = true;
191 g.length = chord_length;
192 } else {
193 g.is_line = false;
194 g.length = CubicLength(coeffs, std::max(64, static_cast<int>(chord_length / 0.05)));
195 }
196 s += g.length;
197 out.push_back(g);
198 }
199 return out;
200 }
201
202} // namespace twinmodel
float y
float x
std::vector< Point3 > Line3
std::vector< Geom > FitPlanView(const geom::Line3 &line, double max_offset)
Fit a polyline as a piecewise cubic paramPoly3 planview.
Definition PlanView.cpp:102
double length
Definition PlanView.h:25
std::array< double, 8 > coeffs
aU bU cU dU aV bV cV dV, with aU = aV = bV = 0 by construction.
Definition PlanView.h:28