HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
bezier.hpp
1// Copyright Take Vos 2019-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../numeric/numeric.hpp"
8#include "../geometry/geometry.hpp"
9#include "../container/container.hpp"
10#include "../macros.hpp"
11#include <hikocpu/hikocpu.hpp>
12#include <array>
13#include <optional>
14#include <concepts>
15
16hi_export_module(hikogui.graphic_path.bezier);
17
18hi_export namespace hi { inline namespace v1 {
19
20namespace detail {
21
22template<typename T>
23[[nodiscard]] constexpr T bezier_broadcast(float x) noexcept
24{
25 return T::broadcast(x);
26}
27
28template<>
29[[nodiscard]] constexpr float bezier_broadcast(float x) noexcept
30{
31 return x;
32}
33
34}
35
36// B(t)=(P_{2}-P_{1})t+P_{1}
37template<typename T>
38constexpr std::array<T, 2> bezierToPolynomial(T P1, T P2) noexcept
39{
40 return {P2 - P1, P1};
41}
42
43// B(t)=(P_{1}-2C+P_{2})t^{2}+2(C-P_{1})t+P_{1}
44template<typename T>
45constexpr std::array<T, 3> bezierToPolynomial(T P1, T C, T P2) noexcept
46{
47 constexpr auto _2 = detail::bezier_broadcast<T>(2);
48 return {P1 - C * _2 + P2, (C - P1) * _2, P1};
49}
50
51// B(t)=(-P_{1}+3C_{1}-3C_{2}+P_{2})t^{3}+(3P_{1}-6_{1}+3C_{2})t^{2}+(-3P_{1}+3C_{1})t+P_{1}
52template<typename T>
53constexpr std::array<T, 4> bezierToPolynomial(T P1, T C1, T C2, T P2) noexcept
54{
55 constexpr auto _3 = detail::bezier_broadcast<T>(3);
56 constexpr auto _6 = detail::bezier_broadcast<T>(6);
57 constexpr auto min_3 = detail::bezier_broadcast<T>(-3);
58 return {-P1 + C1 * _3 - C2 * _3 + P2, P1 * _3 - C1 * _6 + C2 * _3, P1 * min_3 + C1 * _3, P1};
59}
60
61constexpr point2 bezierPointAt(point2 P1, point2 P2, float t) noexcept
62{
63 auto const t_ = f32x4::broadcast(t);
64 auto const[a, b] = bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(P2));
65 return point2{a * t_ + b};
66}
67
68constexpr point2 bezierPointAt(point2 P1, point2 C, point2 P2, float t) noexcept
69{
70 auto const t_ = f32x4::broadcast(t);
71 auto const[a, b, c] = bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(C), static_cast<f32x4>(P2));
72 return point2{a * t_ * t_ + b * t_ + c};
73}
74
75constexpr point2 bezierPointAt(point2 P1, point2 C1, point2 C2, point2 P2, float t) noexcept
76{
77 auto const t_ = f32x4::broadcast(t);
78 auto const tt_ = t_ * t_;
79 auto const ttt_ = tt_ * t_;
80
81 auto const[a, b, c, d] =
82 bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(C1), static_cast<f32x4>(C2), static_cast<f32x4>(P2));
83 return point2{a * ttt_ + b * tt_ + c * t_ + d};
84}
85
86constexpr vector2 bezierTangentAt(point2 P1, point2 P2, float t) noexcept
87{
88 return P2 - P1;
89}
90
91constexpr vector2 bezierTangentAt(point2 P1, point2 C, point2 P2, float t) noexcept
92{
93 constexpr auto _2 = f32x4::broadcast(2);
94 auto const t_ = f32x4::broadcast(t);
95 auto const P1_ = static_cast<f32x4>(P1);
96 auto const C_ = static_cast<f32x4>(C);
97 auto const P2_ = static_cast<f32x4>(P2);
98
99 return vector2{_2 * t_ * (P2_ - _2 * C_ + P1_) + _2 * (C_ - P1_)};
100}
101
102constexpr vector2 bezierTangentAt(point2 P1, point2 C1, point2 C2, point2 P2, float t) noexcept
103{
104 constexpr auto _2 = f32x4::broadcast(2);
105 constexpr auto _3 = f32x4::broadcast(3);
106 constexpr auto _6 = f32x4::broadcast(6);
107 auto const t_ = f32x4::broadcast(t);
108 auto const tt_ = t_ * t_;
109 auto const P1_ = static_cast<f32x4>(P1);
110 auto const C1_ = static_cast<f32x4>(C1);
111 auto const C2_ = static_cast<f32x4>(C2);
112 auto const P2_ = static_cast<f32x4>(P2);
113
114 return vector2{_3 * tt_ * (P2_ - _3 * C2_ + _3 * C1_ - P1_) + _6 * t_ * (C2_ - _2 * C1_ + P1_) + _3 * (C1_ - P1_)};
115}
116
117inline lean_vector<float> bezierFindT(float P1, float P2, float x) noexcept
118{
119 auto const[a, b] = bezierToPolynomial(P1, P2);
120 return solvePolynomial(a, b - x);
121}
122
123inline lean_vector<float> bezierFindT(float P1, float C, float P2, float x) noexcept
124{
125 auto const[a, b, c] = bezierToPolynomial(P1, C, P2);
126 return solvePolynomial(a, b, c - x);
127}
128
129inline lean_vector<float> bezierFindT(float P1, float C1, float C2, float P2, float x) noexcept
130{
131 auto const[a, b, c, d] = bezierToPolynomial(P1, C1, C2, P2);
132 return solvePolynomial(a, b, c, d - x);
133}
134
139inline lean_vector<float> bezierFindTForNormalsIntersectingPoint(point2 P1, point2 P2, point2 P) noexcept
140{
141 auto const t_above = dot(P - P1, P2 - P1);
142 auto const t_below = dot(P2 - P1, P2 - P1);
143 if (t_below == 0.0) {
144 [[unlikely]] return {};
145 } else {
146 return {t_above / t_below};
147 }
148}
149
154inline lean_vector<float> bezierFindTForNormalsIntersectingPoint(point2 P1, point2 C, point2 P2, point2 P) noexcept
155{
156 constexpr auto _2 = f32x4::broadcast(2);
157 auto const P1_ = static_cast<f32x4>(P1);
158 auto const P2_ = static_cast<f32x4>(P2);
159 auto const C_ = static_cast<f32x4>(C);
160
161 auto const p = P - P1;
162 auto const p1 = C - P1;
163 auto const p2 = vector2{P2_ - (_2 * C_) + P1_};
164
165 auto const a = dot(p2, p2);
166 auto const b = 3 * dot(p1, p2);
167 auto const c = dot(2 * p1, p1) - dot(p2, p);
168 auto const d = -dot(p1, p);
169 return solvePolynomial(a, b, c, d);
170}
171
179inline lean_vector<float> bezierFindX(point2 P1, point2 P2, float y) noexcept
180{
181 if (y < std::min({P1.y(), P2.y()}) || y > std::max({P1.y(), P2.y()})) {
182 return {};
183 }
184
185 auto r = lean_vector<float>{};
186 for (auto const t : bezierFindT(P1.y(), P2.y(), y)) {
187 if (t >= 0.0f && t < 1.0f) {
188 r.push_back(bezierPointAt(P1, P2, t).x());
189 }
190 }
191
192 return r;
193}
194
202inline lean_vector<float> bezierFindX(point2 P1, point2 C, point2 P2, float y) noexcept
203{
204 auto r = lean_vector<float>{};
205
206 if (y < std::min({P1.y(), C.y(), P2.y()}) || y > std::max({P1.y(), C.y(), P2.y()})) {
207 return r;
208 }
209
210 for (auto const t : bezierFindT(P1.y(), C.y(), P2.y(), y)) {
211 if (t >= 0.0f && t <= 1.0f) {
212 r.push_back(bezierPointAt(P1, C, P2, t).x());
213 }
214 }
215
216 return r;
217}
218
226inline lean_vector<float> bezierFindX(point2 P1, point2 C1, point2 C2, point2 P2, float y) noexcept
227{
228 auto r = lean_vector<float>{};
229
230 if (y < std::min({P1.y(), C1.y(), C2.y(), P2.y()}) || y > std::max({P1.y(), C1.y(), C2.y(), P2.y()})) {
231 return r;
232 }
233
234 for (auto const t : bezierFindT(P1.y(), C1.y(), C2.y(), P2.y(), y)) {
235 if (t >= 0.0f && t <= 1.0f) {
236 r.push_back(bezierPointAt(P1, C1, C2, P2, t).x());
237 }
238 }
239
240 return r;
241}
242
246inline float bezierFlatness(point2 P1, point2 P2) noexcept
247{
248 return 1.0f;
249}
250
254inline float bezierFlatness(point2 P1, point2 C, point2 P2) noexcept
255{
256 auto const P1P2 = hypot(P2 - P1);
257 if (P1P2 == 0.0f) {
258 return 1.0;
259 }
260
261 auto const P1C1 = hypot(C - P1);
262 auto const C1P2 = hypot(P2 - C);
263 return P1P2 / (P1C1 + C1P2);
264}
265
269inline float bezierFlatness(point2 P1, point2 C1, point2 C2, point2 P2) noexcept
270{
271 auto const P1P2 = hypot(P2 - P1);
272 if (P1P2 == 0.0f) {
273 return 1.0;
274 }
275
276 auto const P1C1 = hypot(C1 - P1);
277 auto const C1C2 = hypot(C2 - C1);
278 auto const C2P2 = hypot(P2 - C2);
279 return P1P2 / (P1C1 + C1C2 + C2P2);
280}
281
282inline std::pair<point2, point2> parallelLine(point2 P1, point2 P2, float distance) noexcept
283{
284 auto const v = P2 - P1;
285 auto const n = normal(v);
286 return {P1 + n * distance, P2 + n * distance};
287}
288
291inline std::optional<point2> getIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
292{
293 // convert points to vectors.
294 auto const p = A1;
295 auto const r = A2 - A1;
296 auto const q = B1;
297 auto const s = B2 - B1;
298
299 // find t and u in:
300 // p + t*r == q + us
301 auto const crossRS = cross(r, s);
302 if (crossRS == 0.0f) {
303 // Parallel, other non, or a range of points intersect.
304 return {};
305
306 } else {
307 auto const q_min_p = q - p;
308 auto const t = cross(q_min_p, s) / crossRS;
309 auto const u = cross(q_min_p, r) / crossRS;
310
311 if (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f) {
312 return bezierPointAt(A1, A2, t);
313 } else {
314 // The lines intersect outside of one or both of the segments.
315 return {};
316 }
317 }
318}
319
322inline std::optional<point2> getExtrapolatedIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
323{
324 // convert points to vectors.
325 auto const p = A1;
326 auto const r = A2 - A1;
327 auto const q = B1;
328 auto const s = B2 - B1;
329
330 // find t and u in:
331 // p + t*r == q + us
332 auto const crossRS = cross(r, s);
333 if (crossRS == 0.0f) {
334 // Parallel, other non, or a range of points intersect.
335 return {};
336
337 } else {
338 auto const q_min_p = q - p;
339 auto const t = cross(q_min_p, s) / crossRS;
340
341 return bezierPointAt(A1, A2, t);
342 }
343}
344
345}} // namespace hi::v1
The HikoGUI namespace.
Definition array_generic.hpp:20
lean_vector< float > bezierFindX(point2 P1, point2 P2, float y) noexcept
Definition bezier.hpp:179
std::optional< point2 > getIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
Definition bezier.hpp:291
lean_vector< float > bezierFindTForNormalsIntersectingPoint(point2 P1, point2 P2, point2 P) noexcept
Find t on the line P1->P2 which is closest to P.
Definition bezier.hpp:139
std::optional< point2 > getExtrapolatedIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
Definition bezier.hpp:322
float bezierFlatness(point2 P1, point2 P2) noexcept
Definition bezier.hpp:246
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
hi_force_inline constexpr lean_vector< T > solvePolynomial(T const &a, T const &b) noexcept
Definition polynomial.hpp:30
Definition simd_intf.hpp:18
Lean-vector with (SVO) short-vector-optimization.
Definition lean_vector.hpp:36
void push_back(value_type const &value)
Copy an item to the end of the vector.
Definition lean_vector.hpp:774
A high-level geometric vector Part of the high-level vector, point, mat and color types.
Definition vector2.hpp:27
T max(T... args)
T min(T... args)