rev |
line source |
nuclear@16
|
1 /*
|
nuclear@16
|
2 curvedraw - a simple program to draw curves
|
nuclear@16
|
3 Copyright (C) 2015 John Tsiombikas <nuclear@member.fsf.org>
|
nuclear@16
|
4
|
nuclear@16
|
5 This program is free software: you can redistribute it and/or modify
|
nuclear@16
|
6 it under the terms of the GNU General Public License as published by
|
nuclear@16
|
7 the Free Software Foundation, either version 3 of the License, or
|
nuclear@16
|
8 (at your option) any later version.
|
nuclear@16
|
9
|
nuclear@16
|
10 This program is distributed in the hope that it will be useful,
|
nuclear@16
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nuclear@16
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nuclear@16
|
13 GNU General Public License for more details.
|
nuclear@16
|
14
|
nuclear@16
|
15 You should have received a copy of the GNU General Public License
|
nuclear@16
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
nuclear@16
|
17 */
|
nuclear@0
|
18 #include <float.h>
|
nuclear@12
|
19 #include <assert.h>
|
nuclear@5
|
20 #include <algorithm>
|
nuclear@0
|
21 #include "curve.h"
|
nuclear@0
|
22
|
nuclear@0
|
23 Curve::Curve(CurveType type)
|
nuclear@0
|
24 {
|
nuclear@0
|
25 this->type = type;
|
nuclear@12
|
26 bbvalid = true;
|
nuclear@12
|
27 }
|
nuclear@12
|
28
|
nuclear@12
|
29 Curve::Curve(const Vector4 *cp, int numcp, CurveType type)
|
nuclear@12
|
30 : Curve(type)
|
nuclear@12
|
31 {
|
nuclear@12
|
32 this->cp.resize(numcp);
|
nuclear@12
|
33 for(int i=0; i<numcp; i++) {
|
nuclear@12
|
34 this->cp[i] = cp[i];
|
nuclear@12
|
35 }
|
nuclear@12
|
36 }
|
nuclear@12
|
37
|
nuclear@12
|
38 Curve::Curve(const Vector3 *cp, int numcp, CurveType type)
|
nuclear@12
|
39 : Curve(type)
|
nuclear@12
|
40 {
|
nuclear@12
|
41 this->cp.resize(numcp);
|
nuclear@12
|
42 for(int i=0; i<numcp; i++) {
|
nuclear@12
|
43 this->cp[i] = Vector4(cp[i].x, cp[i].y, cp[i].z, 1.0f);
|
nuclear@12
|
44 }
|
nuclear@12
|
45 }
|
nuclear@12
|
46
|
nuclear@12
|
47 Curve::Curve(const Vector2 *cp, int numcp, CurveType type)
|
nuclear@12
|
48 : Curve(type)
|
nuclear@12
|
49 {
|
nuclear@12
|
50 this->cp.resize(numcp);
|
nuclear@12
|
51 for(int i=0; i<numcp; i++) {
|
nuclear@12
|
52 this->cp[i] = Vector4(cp[i].x, cp[i].y, 0.0f, 1.0f);
|
nuclear@12
|
53 }
|
nuclear@0
|
54 }
|
nuclear@0
|
55
|
nuclear@0
|
56 void Curve::set_type(CurveType type)
|
nuclear@0
|
57 {
|
nuclear@0
|
58 this->type = type;
|
nuclear@0
|
59 }
|
nuclear@0
|
60
|
nuclear@0
|
61 CurveType Curve::get_type() const
|
nuclear@0
|
62 {
|
nuclear@0
|
63 return type;
|
nuclear@0
|
64 }
|
nuclear@0
|
65
|
nuclear@12
|
66 void Curve::add_point(const Vector4 &p)
|
nuclear@12
|
67 {
|
nuclear@12
|
68 cp.push_back(p);
|
nuclear@12
|
69 inval_bounds();
|
nuclear@12
|
70 }
|
nuclear@12
|
71
|
nuclear@12
|
72 void Curve::add_point(const Vector3 &p, float weight)
|
nuclear@12
|
73 {
|
nuclear@12
|
74 add_point(Vector4(p.x, p.y, p.z, weight));
|
nuclear@12
|
75 }
|
nuclear@12
|
76
|
nuclear@0
|
77 void Curve::add_point(const Vector2 &p, float weight)
|
nuclear@0
|
78 {
|
nuclear@12
|
79 add_point(Vector4(p.x, p.y, 0.0f, weight));
|
nuclear@0
|
80 }
|
nuclear@0
|
81
|
nuclear@0
|
82 bool Curve::remove_point(int idx)
|
nuclear@0
|
83 {
|
nuclear@0
|
84 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@0
|
85 return false;
|
nuclear@0
|
86 }
|
nuclear@0
|
87 cp.erase(cp.begin() + idx);
|
nuclear@12
|
88 inval_bounds();
|
nuclear@0
|
89 return true;
|
nuclear@0
|
90 }
|
nuclear@0
|
91
|
nuclear@12
|
92 void Curve::clear()
|
nuclear@0
|
93 {
|
nuclear@12
|
94 cp.clear();
|
nuclear@12
|
95 inval_bounds();
|
nuclear@0
|
96 }
|
nuclear@0
|
97
|
nuclear@2
|
98 bool Curve::empty() const
|
nuclear@2
|
99 {
|
nuclear@2
|
100 return cp.empty();
|
nuclear@2
|
101 }
|
nuclear@2
|
102
|
nuclear@2
|
103 int Curve::size() const
|
nuclear@0
|
104 {
|
nuclear@0
|
105 return (int)cp.size();
|
nuclear@0
|
106 }
|
nuclear@0
|
107
|
nuclear@12
|
108 Vector4 &Curve::operator [](int idx)
|
nuclear@12
|
109 {
|
nuclear@12
|
110 inval_bounds();
|
nuclear@12
|
111 return cp[idx];
|
nuclear@12
|
112 }
|
nuclear@12
|
113
|
nuclear@12
|
114 const Vector4 &Curve::operator [](int idx) const
|
nuclear@2
|
115 {
|
nuclear@2
|
116 return cp[idx];
|
nuclear@2
|
117 }
|
nuclear@2
|
118
|
nuclear@12
|
119 const Vector4 &Curve::get_point(int idx) const
|
nuclear@2
|
120 {
|
nuclear@2
|
121 return cp[idx];
|
nuclear@2
|
122 }
|
nuclear@2
|
123
|
nuclear@12
|
124 Vector3 Curve::get_point3(int idx) const
|
nuclear@0
|
125 {
|
nuclear@12
|
126 return Vector3(cp[idx].x, cp[idx].y, cp[idx].z);
|
nuclear@0
|
127 }
|
nuclear@0
|
128
|
nuclear@12
|
129 Vector2 Curve::get_point2(int idx) const
|
nuclear@0
|
130 {
|
nuclear@0
|
131 return Vector2(cp[idx].x, cp[idx].y);
|
nuclear@0
|
132 }
|
nuclear@0
|
133
|
nuclear@0
|
134 float Curve::get_weight(int idx) const
|
nuclear@0
|
135 {
|
nuclear@12
|
136 return cp[idx].w;
|
nuclear@12
|
137 }
|
nuclear@12
|
138
|
nuclear@12
|
139 bool Curve::set_point(int idx, const Vector3 &p, float weight)
|
nuclear@12
|
140 {
|
nuclear@12
|
141 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@12
|
142 return false;
|
nuclear@12
|
143 }
|
nuclear@12
|
144 cp[idx] = Vector4(p.x, p.y, p.z, weight);
|
nuclear@12
|
145 inval_bounds();
|
nuclear@12
|
146 return true;
|
nuclear@0
|
147 }
|
nuclear@0
|
148
|
nuclear@0
|
149 bool Curve::set_point(int idx, const Vector2 &p, float weight)
|
nuclear@0
|
150 {
|
nuclear@0
|
151 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@0
|
152 return false;
|
nuclear@0
|
153 }
|
nuclear@12
|
154 cp[idx] = Vector4(p.x, p.y, 0.0, weight);
|
nuclear@12
|
155 inval_bounds();
|
nuclear@0
|
156 return true;
|
nuclear@0
|
157 }
|
nuclear@0
|
158
|
nuclear@0
|
159 bool Curve::set_weight(int idx, float weight)
|
nuclear@0
|
160 {
|
nuclear@0
|
161 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@0
|
162 return false;
|
nuclear@0
|
163 }
|
nuclear@12
|
164 cp[idx].w = weight;
|
nuclear@12
|
165 return true;
|
nuclear@12
|
166 }
|
nuclear@12
|
167
|
nuclear@12
|
168 bool Curve::move_point(int idx, const Vector3 &p)
|
nuclear@12
|
169 {
|
nuclear@12
|
170 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@12
|
171 return false;
|
nuclear@12
|
172 }
|
nuclear@12
|
173 cp[idx] = Vector4(p.x, p.y, p.z, cp[idx].w);
|
nuclear@12
|
174 inval_bounds();
|
nuclear@0
|
175 return true;
|
nuclear@0
|
176 }
|
nuclear@0
|
177
|
nuclear@2
|
178 bool Curve::move_point(int idx, const Vector2 &p)
|
nuclear@2
|
179 {
|
nuclear@2
|
180 if(idx < 0 || idx >= (int)cp.size()) {
|
nuclear@2
|
181 return false;
|
nuclear@2
|
182 }
|
nuclear@12
|
183 cp[idx] = Vector4(p.x, p.y, 0.0f, cp[idx].w);
|
nuclear@12
|
184 inval_bounds();
|
nuclear@2
|
185 return true;
|
nuclear@2
|
186 }
|
nuclear@2
|
187
|
nuclear@12
|
188
|
nuclear@12
|
189 int Curve::nearest_point(const Vector3 &p) const
|
nuclear@0
|
190 {
|
nuclear@12
|
191 int res = -1;
|
nuclear@12
|
192 float bestsq = FLT_MAX;
|
nuclear@12
|
193
|
nuclear@12
|
194 for(size_t i=0; i<cp.size(); i++) {
|
nuclear@12
|
195 float d = (get_point3(i) - p).length_sq();
|
nuclear@12
|
196 if(d < bestsq) {
|
nuclear@12
|
197 bestsq = d;
|
nuclear@12
|
198 res = i;
|
nuclear@12
|
199 }
|
nuclear@12
|
200 }
|
nuclear@12
|
201 return res;
|
nuclear@12
|
202 }
|
nuclear@12
|
203
|
nuclear@12
|
204 int Curve::nearest_point(const Vector2 &p) const
|
nuclear@12
|
205 {
|
nuclear@12
|
206 int res = -1;
|
nuclear@12
|
207 float bestsq = FLT_MAX;
|
nuclear@12
|
208
|
nuclear@12
|
209 for(size_t i=0; i<cp.size(); i++) {
|
nuclear@12
|
210 float d = (get_point2(i) - p).length_sq();
|
nuclear@12
|
211 if(d < bestsq) {
|
nuclear@12
|
212 bestsq = d;
|
nuclear@12
|
213 res = i;
|
nuclear@12
|
214 }
|
nuclear@12
|
215 }
|
nuclear@12
|
216 return res;
|
nuclear@12
|
217 }
|
nuclear@12
|
218
|
nuclear@12
|
219
|
nuclear@12
|
220 void Curve::inval_bounds() const
|
nuclear@12
|
221 {
|
nuclear@12
|
222 bbvalid = false;
|
nuclear@12
|
223 }
|
nuclear@12
|
224
|
nuclear@12
|
225 void Curve::calc_bounds() const
|
nuclear@12
|
226 {
|
nuclear@12
|
227 calc_bbox(&bbmin, &bbmax);
|
nuclear@12
|
228 bbvalid = true;
|
nuclear@12
|
229 }
|
nuclear@12
|
230
|
nuclear@12
|
231 void Curve::get_bbox(Vector3 *bbmin, Vector3 *bbmax) const
|
nuclear@12
|
232 {
|
nuclear@12
|
233 if(!bbvalid) {
|
nuclear@12
|
234 calc_bounds();
|
nuclear@12
|
235 }
|
nuclear@12
|
236 *bbmin = this->bbmin;
|
nuclear@12
|
237 *bbmax = this->bbmax;
|
nuclear@12
|
238 }
|
nuclear@12
|
239
|
nuclear@12
|
240 void Curve::calc_bbox(Vector3 *bbmin, Vector3 *bbmax) const
|
nuclear@12
|
241 {
|
nuclear@12
|
242 if(empty()) {
|
nuclear@12
|
243 *bbmin = *bbmax = Vector3(0, 0, 0);
|
nuclear@12
|
244 return;
|
nuclear@12
|
245 }
|
nuclear@12
|
246
|
nuclear@12
|
247 Vector3 bmin = cp[0];
|
nuclear@12
|
248 Vector3 bmax = bmin;
|
nuclear@12
|
249 for(size_t i=1; i<cp.size(); i++) {
|
nuclear@12
|
250 const Vector4 &v = cp[i];
|
nuclear@12
|
251 for(int j=0; j<3; j++) {
|
nuclear@12
|
252 if(v[j] < bmin[j]) bmin[j] = v[j];
|
nuclear@12
|
253 if(v[j] > bmax[j]) bmax[j] = v[j];
|
nuclear@12
|
254 }
|
nuclear@12
|
255 }
|
nuclear@12
|
256 *bbmin = bmin;
|
nuclear@12
|
257 *bbmax = bmax;
|
nuclear@12
|
258 }
|
nuclear@12
|
259
|
nuclear@12
|
260 void Curve::normalize()
|
nuclear@12
|
261 {
|
nuclear@12
|
262 if(!bbvalid) {
|
nuclear@12
|
263 calc_bounds();
|
nuclear@12
|
264 }
|
nuclear@12
|
265
|
nuclear@12
|
266 Vector3 bsize = bbmax - bbmin;
|
nuclear@12
|
267 Vector3 boffs = (bbmin + bbmax) * 0.5;
|
nuclear@12
|
268
|
nuclear@12
|
269 Vector3 bscale;
|
nuclear@12
|
270 bscale.x = bsize.x == 0.0f ? 1.0f : 1.0f / bsize.x;
|
nuclear@12
|
271 bscale.y = bsize.y == 0.0f ? 1.0f : 1.0f / bsize.y;
|
nuclear@12
|
272 bscale.z = bsize.z == 0.0f ? 1.0f : 1.0f / bsize.z;
|
nuclear@12
|
273
|
nuclear@12
|
274 for(size_t i=0; i<cp.size(); i++) {
|
nuclear@12
|
275 cp[i].x = (cp[i].x - boffs.x) * bscale.x;
|
nuclear@12
|
276 cp[i].y = (cp[i].y - boffs.y) * bscale.y;
|
nuclear@12
|
277 cp[i].z = (cp[i].z - boffs.z) * bscale.z;
|
nuclear@12
|
278 }
|
nuclear@12
|
279 inval_bounds();
|
nuclear@12
|
280 }
|
nuclear@12
|
281
|
nuclear@13
|
282 Vector3 Curve::proj_point(const Vector3 &p, float refine_thres) const
|
nuclear@12
|
283 {
|
nuclear@13
|
284 // first step through the curve a few times and find the nearest of them
|
nuclear@12
|
285 int num_cp = size();
|
nuclear@13
|
286 int num_steps = num_cp * 5; // arbitrary number; sounds ok
|
nuclear@13
|
287 float dt = 1.0f / (float)(num_steps - 1);
|
nuclear@12
|
288
|
nuclear@13
|
289 float best_distsq = FLT_MAX;
|
nuclear@13
|
290 float best_t = 0.0f;
|
nuclear@13
|
291 Vector3 best_pp;
|
nuclear@12
|
292
|
nuclear@13
|
293 float t = 0.0f;
|
nuclear@13
|
294 for(int i=0; i<num_steps; i++) {
|
nuclear@13
|
295 Vector3 pp = interpolate(t);
|
nuclear@13
|
296 float distsq = (pp - p).length_sq();
|
nuclear@13
|
297 if(distsq < best_distsq) {
|
nuclear@13
|
298 best_distsq = distsq;
|
nuclear@13
|
299 best_pp = pp;
|
nuclear@13
|
300 best_t = t;
|
nuclear@13
|
301 }
|
nuclear@13
|
302 t += dt;
|
nuclear@13
|
303 }
|
nuclear@12
|
304
|
nuclear@13
|
305 // refine by gradient descent
|
nuclear@13
|
306 float dist = best_distsq;
|
nuclear@13
|
307 t = best_t;
|
nuclear@13
|
308 dt *= 0.05;
|
nuclear@13
|
309 for(;;) {
|
nuclear@13
|
310 float tn = t + dt;
|
nuclear@13
|
311 float tp = t - dt;
|
nuclear@12
|
312
|
nuclear@13
|
313 float dn = (interpolate(tn) - p).length_sq();
|
nuclear@13
|
314 float dp = (interpolate(tp) - p).length_sq();
|
nuclear@12
|
315
|
nuclear@13
|
316 if(fabs(dn - dp) < refine_thres * refine_thres) {
|
nuclear@12
|
317 break;
|
nuclear@12
|
318 }
|
nuclear@12
|
319
|
nuclear@13
|
320 if(dn < dist) {
|
nuclear@13
|
321 t = tn;
|
nuclear@13
|
322 dist = dn;
|
nuclear@13
|
323 } else if(dp < dist) {
|
nuclear@13
|
324 t = tp;
|
nuclear@13
|
325 dist = dp;
|
nuclear@12
|
326 } else {
|
nuclear@13
|
327 break; // found the minimum
|
nuclear@12
|
328 }
|
nuclear@12
|
329 }
|
nuclear@13
|
330
|
nuclear@13
|
331 return interpolate(t);
|
nuclear@12
|
332 }
|
nuclear@12
|
333
|
nuclear@13
|
334 float Curve::distance(const Vector3 &p) const
|
nuclear@13
|
335 {
|
nuclear@13
|
336 return (proj_point(p) - p).length();
|
nuclear@13
|
337 }
|
nuclear@13
|
338
|
nuclear@13
|
339 float Curve::distance_sq(const Vector3 &p) const
|
nuclear@13
|
340 {
|
nuclear@13
|
341 return fabs((proj_point(p) - p).length_sq());
|
nuclear@13
|
342 }
|
nuclear@13
|
343
|
nuclear@13
|
344
|
nuclear@12
|
345 Vector3 Curve::interpolate_segment(int a, int b, float t) const
|
nuclear@12
|
346 {
|
nuclear@12
|
347 int num_cp = size();
|
nuclear@12
|
348
|
nuclear@12
|
349 if(t < 0.0) t = 0.0;
|
nuclear@12
|
350 if(t > 1.0) t = 1.0;
|
nuclear@12
|
351
|
nuclear@12
|
352 Vector4 res;
|
nuclear@12
|
353 if(type == CURVE_LINEAR || num_cp == 2) {
|
nuclear@12
|
354 res = lerp(cp[a], cp[b], t);
|
nuclear@12
|
355 } else {
|
nuclear@12
|
356 int prev = a <= 0 ? a : a - 1;
|
nuclear@12
|
357 int next = b >= num_cp - 1 ? b : b + 1;
|
nuclear@12
|
358
|
nuclear@12
|
359 if(type == CURVE_HERMITE) {
|
nuclear@12
|
360 res = catmull_rom_spline(cp[prev], cp[a], cp[b], cp[next], t);
|
nuclear@12
|
361 } else {
|
nuclear@12
|
362 res = bspline(cp[prev], cp[a], cp[b], cp[next], t);
|
nuclear@12
|
363 if(res.w != 0.0f) {
|
nuclear@12
|
364 res.x /= res.w;
|
nuclear@12
|
365 res.y /= res.w;
|
nuclear@13
|
366 res.z /= res.w;
|
nuclear@12
|
367 }
|
nuclear@12
|
368 }
|
nuclear@12
|
369 }
|
nuclear@12
|
370
|
nuclear@12
|
371 return Vector3(res.x, res.y, res.z);
|
nuclear@12
|
372 }
|
nuclear@12
|
373
|
nuclear@12
|
374 Vector3 Curve::interpolate(float t) const
|
nuclear@12
|
375 {
|
nuclear@12
|
376 if(empty()) {
|
nuclear@12
|
377 return Vector3(0, 0, 0);
|
nuclear@0
|
378 }
|
nuclear@2
|
379
|
nuclear@2
|
380 int num_cp = (int)cp.size();
|
nuclear@0
|
381 if(num_cp == 1) {
|
nuclear@12
|
382 return Vector3(cp[0].x, cp[0].y, cp[0].z);
|
nuclear@0
|
383 }
|
nuclear@0
|
384
|
nuclear@13
|
385 if(t < 0.0) t = 0.0;
|
nuclear@13
|
386 if(t > 1.0) t = 1.0;
|
nuclear@13
|
387
|
nuclear@2
|
388 int idx0 = std::min((int)floor(t * (num_cp - 1)), num_cp - 2);
|
nuclear@0
|
389 int idx1 = idx0 + 1;
|
nuclear@0
|
390
|
nuclear@0
|
391 float dt = 1.0 / (float)(num_cp - 1);
|
nuclear@0
|
392 float t0 = (float)idx0 * dt;
|
nuclear@0
|
393 float t1 = (float)idx1 * dt;
|
nuclear@0
|
394
|
nuclear@0
|
395 t = (t - t0) / (t1 - t0);
|
nuclear@0
|
396
|
nuclear@12
|
397 return interpolate_segment(idx0, idx1, t);
|
nuclear@12
|
398 }
|
nuclear@0
|
399
|
nuclear@12
|
400 Vector2 Curve::interpolate2(float t) const
|
nuclear@12
|
401 {
|
nuclear@12
|
402 Vector3 res = interpolate(t);
|
nuclear@0
|
403 return Vector2(res.x, res.y);
|
nuclear@0
|
404 }
|
nuclear@0
|
405
|
nuclear@12
|
406 Vector3 Curve::operator ()(float t) const
|
nuclear@0
|
407 {
|
nuclear@0
|
408 return interpolate(t);
|
nuclear@0
|
409 }
|