Kale
Loading...
Searching...
No Matches
Path.cpp
Go to the documentation of this file.
1/*
2 Copyright 2022 Rishi Challa
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15*/
16
17#include "Path.hpp"
18
20
21#include <limits>
22
23using namespace Kale;
24
29 // Empty Body
30}
31
36Path::Path(size_t n) : beziers(n) {
37 // Empty Body
38}
39
44Path::Path(const std::vector<CubicBezier>& beziers) : beziers(beziers) {
45 // Empty Body
46}
47
53Path::Path(const std::vector<Vector2f>& lines, float cornerRadius) {
54 std::vector<std::pair<Vector2f, float>> l(lines.size());
55 std::transform(lines.begin(), lines.end(), l.begin(), [&](const Vector2f& pos) -> std::pair<Vector2f, float> {
56 return std::make_pair(pos, cornerRadius);
57 });
58 roundCorners(l);
59}
60
67Path::Path(const std::vector<std::pair<Vector2f, float>>& lines) {
68 roundCorners(lines);
69}
70
75void Path::roundCorners(const std::vector<std::pair<Vector2f, float>>& lines) {
76
77 // Loop through all of the corners and attempt to round them
78 moveTo(lines[0].first);
79
80 for (size_t i = 0; i < lines.size(); i++) {
81
82 // Get the radius for this corner
83 float radius = lines[i].second;
84
85 // Get the corner, next, and previous points
86 Vector2f corner = lines[i].first;
87 Vector2f prev = i != 0 ? lines[i-1].first : lines.back().first;
88 Vector2f next = i+1 < lines.size() ? lines[i+1].first : lines.front().first;
89
90 // Calculate the cross product to see if our angle is acute or obtuse
91 float cp = (prev - corner).cross(next - corner);
92 if (isFloating0(cp) || radius <= 0.0f) {
93 lineTo(corner);
94 continue;
95 }
96
97 Vector2f prevOffset, nextOffset;
98 bool clockwise;
99
100 // Acute angle
101 if (cp < 0) {
102 clockwise = false;
103 prevOffset = (prev - corner).rotateClockwise().normalized() * radius;
104 nextOffset = (next - corner).rotateCounterClockwise().normalized() * radius;
105 }
106 // Obtuse angle
107 else {
108 clockwise = true;
109 prevOffset = (prev - corner).rotateCounterClockwise().normalized() * radius;
110 nextOffset = (next - corner).rotateClockwise().normalized() * radius;
111 }
112
113 // Create 2 parallel lines next to the previous - corner & next - corner lines
114 Vector2f prevLp0 = prev + prevOffset;
115 Vector2f prevLp1 = corner + prevOffset;
116 Vector2f nextLp0 = next + nextOffset;
117 Vector2f nextLp1 = corner + nextOffset;
118
119 Vector2f prevDist = prevLp1 - prevLp0;
120 Vector2f nextDist = nextLp1 - nextLp0;
121
122 // Calculate the times of their intersection
123 float prevT = (nextLp0 - prevLp0).cross(nextDist / prevDist.cross(nextDist));
124 float nextT = (nextLp0 - prevLp0).cross(prevDist / prevDist.cross(nextDist));
125
126 Vector2f center = prevLp0 + prevDist * prevT;
127 Vector2f truePrev = prev + prevDist * prevT;
128 Vector2f trueNext = next + nextDist * nextT;
129
130 lineTo(truePrev);
131 arcTo(trueNext, center, clockwise);
132 }
133
134 closePath();
135}
136
142 Vector2f topLeft = Vector2f::max();
143 Vector2f bottomRight = Vector2f::min();
144
145 for (const CubicBezier& bezier : beziers) {
146 for (const Vector2f& point : {bezier.start, bezier.controlPoint1, bezier.controlPoint2, bezier.end}) {
147 if (point.x < topLeft.x) topLeft.x = point.x;
148 if (point.y < topLeft.y) topLeft.y = point.y;
149 if (point.x > bottomRight.x) bottomRight.x = point.x;
150 if (point.y > bottomRight.y) bottomRight.y = point.y;
151 }
152 }
153
154 return {topLeft, bottomRight};
155}
156
161void Path::operator+=(const Path& other) {
162 klAssert(other.beziers.size() == beziers.size());
163 for (size_t i = 0; i < beziers.size(); i++) {
164 beziers[i].start += other.beziers[i].start;
165 beziers[i].controlPoint1 += other.beziers[i].controlPoint1;
166 beziers[i].controlPoint2 += other.beziers[i].controlPoint2;
167 beziers[i].end += other.beziers[i].end;
168 }
169}
170
175Path Path::operator*(float value) const {
176 Path path(*this);
177 for (CubicBezier& bezier : path.beziers) {
178 bezier.start *= value;
179 bezier.controlPoint1 *= value;
180 bezier.controlPoint2 *= value;
181 bezier.end *= value;
182 }
183 return path;
184}
185
193 beziers.clear();
195}
196
203 beziers.back().controlPoint1 = beziers.back().start;
204 beziers.back().controlPoint2 = pos;
205 beziers.back().end = pos;
207}
208
216void Path::bezierTo(Vector2f controlPoint1, Vector2f controlPoint2, Vector2f end) {
217 beziers.back().controlPoint1 = controlPoint1;
218 beziers.back().controlPoint2 = controlPoint2;
219 beziers.back().end = end;
221}
222
230void Path::arcTo(Vector2f end, Vector2f center, bool clockwise) {
231 // Calculate the number of segments we need to approximate the curve (1 - 0:90, 2 - 90:180, 3 - 180:270, 4 - 270:360)
232 float radius = (end - center).magnitude();
233 float angle = (beziers.back().start - center).signedAngle(end - center);
234 int numsegments;
235 if (angle > 0) numsegments = static_cast<int>(ceil(angle / (0.5f * PI)));
236 else numsegments = static_cast<int>(ceil((angle + 2.0f * PI) / (0.5f * PI)));
237
238 if (clockwise) numsegments = std::max(4 - numsegments, 1);
239
240 // Prep for loop
241 float a = angle * (1.0f / static_cast<float>(numsegments));
242 if (clockwise) a += 2.0f * PI;
243
244 // This is a constant needed to approximate a circle with beziers -
245 // https://stackoverflow.com/questions/1734745/how-to-create-circle-with-bézier-curves
246 float bezierArcApprox = (4.0f / 3.0f) * tan(PI / (2.0f * (static_cast<float>(numsegments) * 2.0f * PI / angle)));
247
248 // Loop through the segments
249 for (size_t i = 0; i < static_cast<size_t>(numsegments); i++) {
250
251 // Rotate the end of the last segment about the center
252 CubicBezier& last = beziers.back();
253 float c = cos(a);
254 float s = sin(a);
255 Vector2f n = Vector2f(
256 (c * (last.start.x - center.x)) - (s * (last.start.y - center.y)),
257 (s * (last.start.x - center.x)) + (c * (last.start.y - center.y))
258 ) + center;
259
260 // Create the bezier approximation
261 last.controlPoint1 = last.start + (center - last.start).rotateClockwise().normalized() * bezierArcApprox * radius;
262 last.controlPoint2 = n + (center - n).rotateCounterClockwise().normalized() * bezierArcApprox * radius;
263 last.end = n;
265 }
266}
267
273 if (beziers.front().controlPoint1 == beziers.front().start) beziers.front().controlPoint1 = beziers.back().start;
274 beziers.front().start = beziers.back().start;
275 beziers.pop_back();
276}
277
281std::ostream& Kale::operator<<(std::ostream& os, const CubicBezier& bezier) {
282 os << "CubicBezier(" << bezier.start << ", " << bezier.controlPoint1 << ", " << bezier.controlPoint2 << ", " << bezier.end << ")";
283 return os;
284}
285
289std::ostream& Kale::operator<<(std::ostream& os, const Path& path) {
290 os << "Path(\n";
291 for (const CubicBezier& bezier : path.beziers) os << bezier << "\n";
292 os << ")";
293 return os;
294}
#define klAssert(x)
Definition Logger.hpp:352
void bezierTo(Vector2f controlPoint1, Vector2f controlPoint2, Vector2f end)
Definition Path.cpp:216
void moveTo(Vector2f pos)
Definition Path.cpp:192
void roundCorners(const std::vector< std::pair< Vector2f, float > > &lines)
Definition Path.cpp:75
void closePath()
Definition Path.cpp:272
void operator+=(const Path &other)
Definition Path.cpp:161
void arcTo(Vector2f end, Vector2f center, bool clockwise)
Definition Path.cpp:230
void lineTo(Vector2f pos)
Definition Path.cpp:202
Path operator*(float value) const
Definition Path.cpp:175
std::vector< CubicBezier > beziers
Definition Path.hpp:61
Rect getBoundingBox() const
Definition Path.cpp:141
static Vector2< float > max()
Definition Vector.hpp:175
Vector2< T > normalized() const
Definition Vector.hpp:166
static Vector2< float > zero()
Definition Vector.hpp:172
static Vector2< float > min()
Definition Vector.hpp:176
T cross(Vector2< T > o) const
Definition Vector.hpp:122
bool isFloating0(T num, T epsilon=std::numeric_limits< T >::epsilon())
Definition Utils.hpp:58
constexpr float PI
Definition Constants.hpp:24
std::ostream & operator<<(std::ostream &os, const Kale::Matrix< w, h, T > &mat)
Definition Matrix.hpp:524
Vector2< float > Vector2f
Definition Vector.hpp:598
Vector2f start
Definition Path.hpp:41
Vector2f controlPoint1
Definition Path.hpp:41
Vector2f end
Definition Path.hpp:41
Vector2f controlPoint2
Definition Path.hpp:41