HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
bezier.hpp
1// Copyright Take Vos 2019-2021.
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 "polynomial.hpp"
8#include "rapid/numeric_array.hpp"
9#include "geometry/point.hpp"
10#include <array>
11#include <optional>
12
13namespace hi::inline v1 {
14
15// B(t)=(P_{2}-P_{1})t+P_{1}
16template<typename T>
17constexpr std::array<T, 2> bezierToPolynomial(T P1, T P2) noexcept
18{
19 return {P2 - P1, P1};
20}
21
22// B(t)=(P_{1}-2C+P_{2})t^{2}+2(C-P_{1})t+P_{1}
23template<typename T>
24constexpr std::array<T, 3> bezierToPolynomial(T P1, T C, T P2) noexcept
25{
26 return {P1 - C * 2 + P2, (C - P1) * 2, P1};
27}
28
29// 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}
30template<typename T>
31constexpr std::array<T, 4> bezierToPolynomial(T P1, T C1, T C2, T P2) noexcept
32{
33 return {-P1 + C1 * 3 - C2 * 3 + P2, P1 * 3 - C1 * 6 + C2 * 3, P1 * -3 + C1 * 3, P1};
34}
35
36template<int D>
37constexpr geo::point<D> bezierPointAt(geo::point<D> P1, geo::point<D> P2, float t) noexcept
38{
39 hilet[a, b] = bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(P2));
40 return geo::point<D>{a * t + b};
41}
42
43template<int D>
44constexpr geo::point<D> bezierPointAt(geo::point<D> P1, geo::point<D> C, geo::point<D> P2, float t) noexcept
45{
46 hilet[a, b, c] = bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(C), static_cast<f32x4>(P2));
47 return geo::point<D>{a * t * t + b * t + c};
48}
49
50template<int D>
51constexpr geo::point<D> bezierPointAt(geo::point<D> P1, geo::point<D> C1, geo::point<D> C2, geo::point<D> P2, float t) noexcept
52{
53 hilet[a, b, c, d] =
54 bezierToPolynomial(static_cast<f32x4>(P1), static_cast<f32x4>(C1), static_cast<f32x4>(C2), static_cast<f32x4>(P2));
55 return geo::point<D>{a * t * t * t + b * t * t + c * t + d};
56}
57
58template<int D>
59constexpr geo::vector<D> bezierTangentAt(geo::point<D> P1, geo::point<D> P2, float t) noexcept
60{
61 return P2 - P1;
62}
63
64template<int D>
65constexpr geo::vector<D> bezierTangentAt(geo::point<D> P1, geo::point<D> C, geo::point<D> P2, float t) noexcept
66{
67 hilet P1_ = static_cast<f32x4>(P1);
68 hilet C_ = static_cast<f32x4>(C);
69 hilet P2_ = static_cast<f32x4>(P2);
70
71 return geo::vector<D>{2 * t * (P2_ - 2 * C_ + P1_) + 2 * (C_ - P1_)};
72}
73
74template<int D>
75constexpr geo::vector<D> bezierTangentAt(geo::point<D> P1, geo::point<D> C1, geo::point<D> C2, geo::point<D> P2, float t) noexcept
76{
77 hilet P1_ = static_cast<f32x4>(P1);
78 hilet C1_ = static_cast<f32x4>(C1);
79 hilet C2_ = static_cast<f32x4>(C2);
80 hilet P2_ = static_cast<f32x4>(P2);
81
82 return geo::vector<D>{3 * t * t * (P2_ - 3 * C2_ + 3 * C1_ - P1_) + 6 * t * (C2_ - 2 * C1_ + P1_) + 3 * (C1_ - P1_)};
83}
84
85constexpr results<float, 1> bezierFindT(float P1, float P2, float x) noexcept
86{
87 hilet[a, b] = bezierToPolynomial(P1, P2);
88 return solvePolynomial(a, b - x);
89}
90
91constexpr results<float, 2> bezierFindT(float P1, float C, float P2, float x) noexcept
92{
93 hilet[a, b, c] = bezierToPolynomial(P1, C, P2);
94 return solvePolynomial(a, b, c - x);
95}
96
97hi_force_inline constexpr results<float, 3> bezierFindT(float P1, float C1, float C2, float P2, float x) noexcept
98{
99 hilet[a, b, c, d] = bezierToPolynomial(P1, C1, C2, P2);
100 return solvePolynomial(a, b, c, d - x);
101}
102
107hi_force_inline constexpr results<float, 1> bezierFindTForNormalsIntersectingPoint(point2 P1, point2 P2, point2 P) noexcept
108{
109 hilet t_above = dot(P - P1, P2 - P1);
110 hilet t_below = dot(P2 - P1, P2 - P1);
111 if (t_below == 0.0) {
112 [[unlikely]] return {};
113 } else {
114 return {t_above / t_below};
115 }
116}
117
122hi_force_inline constexpr results<float, 3>
123bezierFindTForNormalsIntersectingPoint(point2 P1, point2 C, point2 P2, point2 P) noexcept
124{
125 hilet P1_ = static_cast<f32x4>(P1);
126 hilet P2_ = static_cast<f32x4>(P2);
127 hilet C_ = static_cast<f32x4>(C);
128
129 hilet p = P - P1;
130 hilet p1 = C - P1;
131 hilet p2 = vector2{P2_ - (2 * C_) + P1_};
132
133 hilet a = dot(p2, p2);
134 hilet b = 3 * dot(p1, p2);
135 hilet c = dot(2 * p1, p1) - dot(p2, p);
136 hilet d = -dot(p1, p);
137 return solvePolynomial(a, b, c, d);
138}
139
147constexpr results<float, 1> bezierFindX(point2 P1, point2 P2, float y) noexcept
148{
149 if (y < std::min({P1.y(), P2.y()}) || y > std::max({P1.y(), P2.y()})) {
150 return {};
151 }
152
153 results<float, 1> r;
154 for (hilet t : bezierFindT(P1.y(), P2.y(), y)) {
155 if (t >= 0.0f && t < 1.0f) {
156 r.add(bezierPointAt(P1, P2, t).x());
157 }
158 }
159
160 return r;
161}
162
170constexpr results<float, 2> bezierFindX(point2 P1, point2 C, point2 P2, float y) noexcept
171{
172 results<float, 2> r{};
173
174 if (y < std::min({P1.y(), C.y(), P2.y()}) || y > std::max({P1.y(), C.y(), P2.y()})) {
175 return r;
176 }
177
178 for (hilet t : bezierFindT(P1.y(), C.y(), P2.y(), y)) {
179 if (t >= 0.0f && t <= 1.0f) {
180 r.add(bezierPointAt(P1, C, P2, t).x());
181 }
182 }
183
184 return r;
185}
186
194constexpr results<float, 3> bezierFindX(point2 P1, point2 C1, point2 C2, point2 P2, float y) noexcept
195{
196 results<float, 3> r{};
197
198 if (y < std::min({P1.y(), C1.y(), C2.y(), P2.y()}) || y > std::max({P1.y(), C1.y(), C2.y(), P2.y()})) {
199 return r;
200 }
201
202 for (hilet t : bezierFindT(P1.y(), C1.y(), C2.y(), P2.y(), y)) {
203 if (t >= 0.0f && t <= 1.0f) {
204 r.add(bezierPointAt(P1, C1, C2, P2, t).x());
205 }
206 }
207
208 return r;
209}
210
214inline float bezierFlatness(point2 P1, point2 P2) noexcept
215{
216 return 1.0f;
217}
218
223inline float bezierFlatness(point2 P1, point2 C, point2 P2) noexcept
224{
225 hilet P1P2 = hypot(P2 - P1);
226 if (P1P2 == 0.0f) {
227 return 1.0;
228 }
229
230 hilet P1C1 = hypot(C - P1);
231 hilet C1P2 = hypot(P2 - C);
232 return P1P2 / (P1C1 + C1P2);
233}
234
239inline float bezierFlatness(point2 P1, point2 C1, point2 C2, point2 P2) noexcept
240{
241 hilet P1P2 = hypot(P2 - P1);
242 if (P1P2 == 0.0f) {
243 return 1.0;
244 }
245
246 hilet P1C1 = hypot(C1 - P1);
247 hilet C1C2 = hypot(C2 - C1);
248 hilet C2P2 = hypot(P2 - C2);
249 return P1P2 / (P1C1 + C1C2 + C2P2);
250}
251
252inline std::pair<point2, point2> parallelLine(point2 P1, point2 P2, float distance) noexcept
253{
254 hilet v = P2 - P1;
255 hilet n = normal(v);
256 return {P1 + n * distance, P2 + n * distance};
257}
258
261inline std::optional<point2> getIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
262{
263 // convert points to vectors.
264 hilet p = A1;
265 hilet r = A2 - A1;
266 hilet q = B1;
267 hilet s = B2 - B1;
268
269 // find t and u in:
270 // p + t*r == q + us
271 hilet crossRS = cross(r, s);
272 if (crossRS == 0.0f) {
273 // Parallel, other non, or a range of points intersect.
274 return {};
275
276 } else {
277 hilet q_min_p = q - p;
278 hilet t = cross(q_min_p, s) / crossRS;
279 hilet u = cross(q_min_p, r) / crossRS;
280
281 if (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f) {
282 return bezierPointAt(A1, A2, t);
283 } else {
284 // The lines intersect outside of one or both of the segments.
285 return {};
286 }
287 }
288}
289
292inline std::optional<point2> getExtrapolatedIntersectionPoint(point2 A1, point2 A2, point2 B1, point2 B2) noexcept
293{
294 // convert points to vectors.
295 hilet p = A1;
296 hilet r = A2 - A1;
297 hilet q = B1;
298 hilet s = B2 - B1;
299
300 // find t and u in:
301 // p + t*r == q + us
302 hilet crossRS = cross(r, s);
303 if (crossRS == 0.0f) {
304 // Parallel, other non, or a range of points intersect.
305 return {};
306
307 } else {
308 hilet q_min_p = q - p;
309 hilet t = cross(q_min_p, s) / crossRS;
310
311 return bezierPointAt(A1, A2, t);
312 }
313}
314
315} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
T distance(T... args)
T hypot(T... args)
T max(T... args)
T min(T... args)