1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | // LOVE |
22 | #include "BezierCurve.h" |
23 | #include "common/Exception.h" |
24 | |
25 | #include <cmath> |
26 | #include <algorithm> |
27 | |
28 | using namespace std; |
29 | |
30 | namespace |
31 | { |
32 | |
33 | /** |
34 | * Subdivide Bezier polygon. |
35 | **/ |
36 | void subdivide(vector<love::Vector2> &points, int k) |
37 | { |
38 | if (k <= 0) |
39 | return; |
40 | |
41 | // subdivision using de casteljau - subdivided control polygons are |
42 | // on the 'edges' of the computation scheme, e.g: |
43 | // |
44 | // ------LEFT-------> |
45 | // b00 b10 b20 b30 |
46 | // b01 b11 b21 .--- |
47 | // b02 b12 .---' |
48 | // b03 .---'RIGHT |
49 | // <--' |
50 | // |
51 | // the subdivided control polygon is: |
52 | // b00, b10, b20, b30, b21, b12, b03 |
53 | vector<love::Vector2> left, right; |
54 | left.reserve(points.size()); |
55 | right.reserve(points.size()); |
56 | |
57 | for (size_t step = 1; step < points.size(); ++step) |
58 | { |
59 | left.push_back(points[0]); |
60 | right.push_back(points[points.size() - step]); |
61 | for (size_t i = 0; i < points.size() - step; ++i) |
62 | points[i] = (points[i] + points[i+1]) * .5; |
63 | } |
64 | left.push_back(points[0]); |
65 | right.push_back(points[0]); |
66 | |
67 | // recurse |
68 | subdivide(left, k-1); |
69 | subdivide(right, k-1); |
70 | |
71 | // merge (right is in reversed order) |
72 | points.resize(left.size() + right.size() - 1); |
73 | for (size_t i = 0; i < left.size(); ++i) |
74 | points[i] = left[i]; |
75 | for (size_t i = 1; i < right.size(); ++i) |
76 | points[i-1 + left.size()] = right[right.size() - i - 1]; |
77 | } |
78 | |
79 | } |
80 | |
81 | namespace love |
82 | { |
83 | namespace math |
84 | { |
85 | |
86 | love::Type BezierCurve::type("BezierCurve" , &Object::type); |
87 | |
88 | BezierCurve::BezierCurve(const vector<Vector2> &pts) |
89 | : controlPoints(pts) |
90 | { |
91 | } |
92 | |
93 | |
94 | BezierCurve BezierCurve::getDerivative() const |
95 | { |
96 | if (getDegree() < 1) |
97 | throw Exception("Cannot derive a curve of degree < 1." ); |
98 | // actually we can, it just doesn't make any sense. |
99 | |
100 | vector<Vector2> forward_differences(controlPoints.size()-1); |
101 | float degree = float(getDegree()); |
102 | for (size_t i = 0; i < forward_differences.size(); ++i) |
103 | forward_differences[i] = (controlPoints[i+1] - controlPoints[i]) * degree; |
104 | |
105 | return BezierCurve(forward_differences); |
106 | } |
107 | |
108 | const Vector2 &BezierCurve::getControlPoint(int i) const |
109 | { |
110 | if (controlPoints.size() == 0) |
111 | throw Exception("Curve contains no control points." ); |
112 | |
113 | while (i < 0) |
114 | i += controlPoints.size(); |
115 | |
116 | while ((size_t) i >= controlPoints.size()) |
117 | i -= controlPoints.size(); |
118 | |
119 | return controlPoints[i]; |
120 | } |
121 | |
122 | void BezierCurve::setControlPoint(int i, const Vector2 &point) |
123 | { |
124 | if (controlPoints.size() == 0) |
125 | throw Exception("Curve contains no control points." ); |
126 | |
127 | while (i < 0) |
128 | i += controlPoints.size(); |
129 | |
130 | while ((size_t) i >= controlPoints.size()) |
131 | i -= controlPoints.size(); |
132 | |
133 | controlPoints[i] = point; |
134 | } |
135 | |
136 | void BezierCurve::insertControlPoint(const Vector2 &point, int i) |
137 | { |
138 | if (controlPoints.size() == 0) |
139 | i = 0; |
140 | |
141 | while (i < 0) |
142 | i += controlPoints.size(); |
143 | |
144 | while ((size_t) i > controlPoints.size()) |
145 | i -= controlPoints.size(); |
146 | |
147 | controlPoints.insert(controlPoints.begin() + i, point); |
148 | } |
149 | |
150 | void BezierCurve::removeControlPoint(int i) |
151 | { |
152 | if (controlPoints.size() == 0) |
153 | throw Exception("No control points to remove." ); |
154 | |
155 | while (i < 0) |
156 | i += controlPoints.size(); |
157 | |
158 | while ((size_t) i >= controlPoints.size()) |
159 | i -= controlPoints.size(); |
160 | |
161 | controlPoints.erase(controlPoints.begin() + i); |
162 | } |
163 | |
164 | void BezierCurve::translate(const Vector2 &t) |
165 | { |
166 | for (size_t i = 0; i < controlPoints.size(); ++i) |
167 | controlPoints[i] += t; |
168 | } |
169 | |
170 | void BezierCurve::rotate(double phi, const Vector2 ¢er) |
171 | { |
172 | float c = cos(phi), s = sin(phi); |
173 | for (size_t i = 0; i < controlPoints.size(); ++i) |
174 | { |
175 | Vector2 v = controlPoints[i] - center; |
176 | controlPoints[i].x = c * v.x - s * v.y + center.x; |
177 | controlPoints[i].y = s * v.x + c * v.y + center.y; |
178 | } |
179 | } |
180 | |
181 | void BezierCurve::scale(double s, const Vector2 ¢er) |
182 | { |
183 | for (size_t i = 0; i < controlPoints.size(); ++i) |
184 | controlPoints[i] = (controlPoints[i] - center) * s + center; |
185 | } |
186 | |
187 | Vector2 BezierCurve::evaluate(double t) const |
188 | { |
189 | if (t < 0 || t > 1) |
190 | throw Exception("Invalid evaluation parameter: must be between 0 and 1" ); |
191 | if (controlPoints.size() < 2) |
192 | throw Exception("Invalid Bezier curve: Not enough control points." ); |
193 | |
194 | // de casteljau |
195 | vector<Vector2> points(controlPoints); |
196 | for (size_t step = 1; step < controlPoints.size(); ++step) |
197 | for (size_t i = 0; i < controlPoints.size() - step; ++i) |
198 | points[i] = points[i] * (1-t) + points[i+1] * t; |
199 | |
200 | return points[0]; |
201 | } |
202 | |
203 | BezierCurve* BezierCurve::getSegment(double t1, double t2) const |
204 | { |
205 | if (t1 < 0 || t2 > 1) |
206 | throw Exception("Invalid segment parameters: must be between 0 and 1" ); |
207 | if (t2 <= t1) |
208 | throw Exception("Invalid segment parameters: t1 must be smaller than t2" ); |
209 | |
210 | // First, sudivide the curve at t2, then subdivide the "left" |
211 | // sub-curve at t1/t2. The "right" curve is the segment. |
212 | vector<Vector2> points(controlPoints); |
213 | vector<Vector2> left, right; |
214 | left.reserve(points.size()); |
215 | right.reserve(points.size()); |
216 | |
217 | // first subdivision at t2 (take only the left curve) |
218 | for (size_t step = 1; step < points.size(); ++step) |
219 | { |
220 | left.push_back(points[0]); |
221 | for (size_t i = 0; i < points.size() - step; ++i) |
222 | points[i] += (points[i+1] - points[i]) * t2; // p_i <- (1-t2)*p_i + t2*p_{i+1} |
223 | } |
224 | left.push_back(points[0]); |
225 | |
226 | // second subdivion at t1/t2 (take only the right curve) |
227 | double s = t1/t2; |
228 | for (size_t step = 1; step < left.size(); ++step) |
229 | { |
230 | right.push_back(left[left.size() - step]); |
231 | for (size_t i = 0; i < left.size() - step; ++i) |
232 | left[i] += (left[i+1] - left[i]) * s; |
233 | } |
234 | right.push_back(left[0]); |
235 | |
236 | // control points for right curve were added in reversed order |
237 | std::reverse(right.begin(), right.end()); |
238 | return new BezierCurve(right); |
239 | } |
240 | |
241 | vector<Vector2> BezierCurve::render(int accuracy) const |
242 | { |
243 | if (controlPoints.size() < 2) |
244 | throw Exception("Invalid Bezier curve: Not enough control points." ); |
245 | vector<Vector2> vertices(controlPoints); |
246 | subdivide(vertices, accuracy); |
247 | return vertices; |
248 | } |
249 | |
250 | vector<Vector2> BezierCurve::renderSegment(double start, double end, int accuracy) const |
251 | { |
252 | if (controlPoints.size() < 2) |
253 | throw Exception("Invalid Bezier curve: Not enough control points." ); |
254 | vector<Vector2> vertices(controlPoints); |
255 | subdivide(vertices, accuracy); |
256 | if (start == end) |
257 | { |
258 | vertices.clear(); |
259 | } |
260 | else if (start < end) |
261 | { |
262 | size_t start_idx = size_t(start * vertices.size()); |
263 | size_t end_idx = size_t(end * vertices.size() + 0.5); |
264 | return std::vector<Vector2>(vertices.begin() + start_idx, vertices.begin() + end_idx); |
265 | } |
266 | else if (end > start) |
267 | { |
268 | size_t start_idx = size_t(end * vertices.size() + 0.5); |
269 | size_t end_idx = size_t(start * vertices.size()); |
270 | return std::vector<Vector2>(vertices.begin() + start_idx, vertices.begin() + end_idx); |
271 | } |
272 | return vertices; |
273 | } |
274 | |
275 | } // namespace math |
276 | } // namespace love |
277 | |