Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/resources/curve.cpp
9903 views
1
/**************************************************************************/
2
/* curve.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "curve.h"
32
33
#include "core/math/math_funcs.h"
34
35
const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed";
36
const char *Curve::SIGNAL_DOMAIN_CHANGED = "domain_changed";
37
38
Curve::Curve() {
39
}
40
41
void Curve::set_point_count(int p_count) {
42
ERR_FAIL_COND(p_count < 0);
43
int old_size = _points.size();
44
if (old_size == p_count) {
45
return;
46
}
47
48
if (old_size > p_count) {
49
_points.resize(p_count);
50
mark_dirty();
51
} else {
52
for (int i = p_count - old_size; i > 0; i--) {
53
_add_point(Vector2());
54
}
55
}
56
notify_property_list_changed();
57
}
58
59
int Curve::_add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode, bool p_mark_dirty) {
60
// Add a point and preserve order.
61
62
// Points must remain within the given value and domain ranges.
63
p_position.x = CLAMP(p_position.x, _min_domain, _max_domain);
64
p_position.y = CLAMP(p_position.y, _min_value, _max_value);
65
66
int ret = -1;
67
68
if (_points.is_empty()) {
69
_points.push_back(Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode));
70
ret = 0;
71
72
} else if (_points.size() == 1) {
73
// TODO Is the `else` able to handle this block already?
74
75
real_t diff = p_position.x - _points[0].position.x;
76
77
if (diff > 0) {
78
_points.push_back(Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode));
79
ret = 1;
80
} else {
81
_points.insert(0, Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode));
82
ret = 0;
83
}
84
85
} else {
86
int i = get_index(p_position.x);
87
88
if (i == 0 && p_position.x < _points[0].position.x) {
89
// Insert before anything else.
90
_points.insert(0, Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode));
91
ret = 0;
92
} else {
93
// Insert between i and i+1.
94
++i;
95
_points.insert(i, Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode));
96
ret = i;
97
}
98
}
99
100
update_auto_tangents(ret);
101
102
if (p_mark_dirty) {
103
mark_dirty();
104
}
105
106
return ret;
107
}
108
109
int Curve::add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) {
110
int ret = _add_point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode);
111
notify_property_list_changed();
112
113
return ret;
114
}
115
116
// TODO: Needed to make the curve editor function properly until https://github.com/godotengine/godot/issues/76985 is fixed.
117
int Curve::add_point_no_update(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) {
118
int ret = _add_point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode);
119
120
return ret;
121
}
122
123
int Curve::get_index(real_t p_offset) const {
124
// Lower-bound float binary search.
125
126
int imin = 0;
127
int imax = _points.size() - 1;
128
129
while (imax - imin > 1) {
130
int m = (imin + imax) / 2;
131
132
real_t a = _points[m].position.x;
133
real_t b = _points[m + 1].position.x;
134
135
if (a < p_offset && b < p_offset) {
136
imin = m;
137
} else if (a > p_offset) {
138
imax = m;
139
} else {
140
return m;
141
}
142
}
143
144
// Will happen if the offset is out of bounds.
145
if (p_offset > _points[imax].position.x) {
146
return imax;
147
}
148
return imin;
149
}
150
151
void Curve::clean_dupes() {
152
bool dirty = false;
153
154
for (uint32_t i = 1; i < _points.size(); ++i) {
155
real_t diff = _points[i - 1].position.x - _points[i].position.x;
156
if (diff <= CMP_EPSILON) {
157
_points.remove_at(i);
158
--i;
159
dirty = true;
160
}
161
}
162
163
if (dirty) {
164
mark_dirty();
165
}
166
}
167
168
void Curve::set_point_left_tangent(int p_index, real_t p_tangent) {
169
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
170
_points[p_index].left_tangent = p_tangent;
171
_points[p_index].left_mode = TANGENT_FREE;
172
mark_dirty();
173
}
174
175
void Curve::set_point_right_tangent(int p_index, real_t p_tangent) {
176
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
177
_points[p_index].right_tangent = p_tangent;
178
_points[p_index].right_mode = TANGENT_FREE;
179
mark_dirty();
180
}
181
182
void Curve::set_point_left_mode(int p_index, TangentMode p_mode) {
183
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
184
_points[p_index].left_mode = p_mode;
185
if (p_index > 0) {
186
if (p_mode == TANGENT_LINEAR) {
187
Vector2 v = (_points[p_index - 1].position - _points[p_index].position).normalized();
188
_points[p_index].left_tangent = v.y / v.x;
189
}
190
}
191
mark_dirty();
192
}
193
194
void Curve::set_point_right_mode(int p_index, TangentMode p_mode) {
195
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
196
_points[p_index].right_mode = p_mode;
197
if ((uint32_t)p_index + 1 < _points.size()) {
198
if (p_mode == TANGENT_LINEAR) {
199
Vector2 v = (_points[p_index + 1].position - _points[p_index].position).normalized();
200
_points[p_index].right_tangent = v.y / v.x;
201
}
202
}
203
mark_dirty();
204
}
205
206
real_t Curve::get_point_left_tangent(int p_index) const {
207
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), 0);
208
return _points[p_index].left_tangent;
209
}
210
211
real_t Curve::get_point_right_tangent(int p_index) const {
212
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), 0);
213
return _points[p_index].right_tangent;
214
}
215
216
Curve::TangentMode Curve::get_point_left_mode(int p_index) const {
217
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), TANGENT_FREE);
218
return _points[p_index].left_mode;
219
}
220
221
Curve::TangentMode Curve::get_point_right_mode(int p_index) const {
222
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), TANGENT_FREE);
223
return _points[p_index].right_mode;
224
}
225
226
void Curve::_remove_point(int p_index, bool p_mark_dirty) {
227
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
228
_points.remove_at(p_index);
229
if (p_mark_dirty) {
230
mark_dirty();
231
}
232
}
233
234
void Curve::remove_point(int p_index) {
235
_remove_point(p_index);
236
notify_property_list_changed();
237
}
238
239
void Curve::clear_points() {
240
if (_points.is_empty()) {
241
return;
242
}
243
244
_points.clear();
245
mark_dirty();
246
notify_property_list_changed();
247
}
248
249
void Curve::set_point_value(int p_index, real_t p_position) {
250
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, _points.size());
251
_points[p_index].position.y = p_position;
252
update_auto_tangents(p_index);
253
mark_dirty();
254
}
255
256
int Curve::set_point_offset(int p_index, real_t p_offset) {
257
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), -1);
258
Point p = _points[p_index];
259
_remove_point(p_index, false);
260
int i = _add_point(Vector2(p_offset, p.position.y), p.left_tangent, p.right_tangent, p.left_mode, p.right_mode, false);
261
if (p_index != i) {
262
update_auto_tangents(p_index);
263
}
264
update_auto_tangents(i);
265
mark_dirty();
266
return i;
267
}
268
269
Vector2 Curve::get_point_position(int p_index) const {
270
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), Vector2(0, 0));
271
return _points[p_index].position;
272
}
273
274
Curve::Point Curve::get_point(int p_index) const {
275
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, _points.size(), Point());
276
return _points[p_index];
277
}
278
279
void Curve::update_auto_tangents(int p_index) {
280
Point &p = _points[p_index];
281
282
if (p_index > 0) {
283
if (p.left_mode == TANGENT_LINEAR) {
284
Vector2 v = (_points[p_index - 1].position - p.position).normalized();
285
p.left_tangent = v.y / v.x;
286
}
287
if (_points[p_index - 1].right_mode == TANGENT_LINEAR) {
288
Vector2 v = (_points[p_index - 1].position - p.position).normalized();
289
_points[p_index - 1].right_tangent = v.y / v.x;
290
}
291
}
292
293
if ((uint32_t)p_index + 1 < _points.size()) {
294
if (p.right_mode == TANGENT_LINEAR) {
295
Vector2 v = (_points[p_index + 1].position - p.position).normalized();
296
p.right_tangent = v.y / v.x;
297
}
298
if (_points[p_index + 1].left_mode == TANGENT_LINEAR) {
299
Vector2 v = (_points[p_index + 1].position - p.position).normalized();
300
_points[p_index + 1].left_tangent = v.y / v.x;
301
}
302
}
303
}
304
305
#define MIN_X_RANGE 0.01
306
#define MIN_Y_RANGE 0.01
307
308
Array Curve::get_limits() const {
309
Array output;
310
output.resize(4);
311
312
output[0] = _min_value;
313
output[1] = _max_value;
314
output[2] = _min_domain;
315
output[3] = _max_domain;
316
317
return output;
318
}
319
320
void Curve::set_limits(const Array &p_input) {
321
if (p_input.size() != 4) {
322
WARN_PRINT_ED(vformat(R"(Could not find Curve limit values when deserializing "%s". Resetting limits to default values.)", this->get_path()));
323
_min_value = 0;
324
_max_value = 1;
325
_min_domain = 0;
326
_max_domain = 1;
327
return;
328
}
329
330
// Do not use setters because we don't want to enforce their logical constraints during deserialization.
331
_min_value = p_input[0];
332
_max_value = p_input[1];
333
_min_domain = p_input[2];
334
_max_domain = p_input[3];
335
}
336
337
void Curve::set_min_value(real_t p_min) {
338
_min_value = MIN(p_min, _max_value - MIN_Y_RANGE);
339
340
for (const Point &p : _points) {
341
_min_value = MIN(_min_value, p.position.y);
342
}
343
344
emit_signal(SNAME(SIGNAL_RANGE_CHANGED));
345
}
346
347
void Curve::set_max_value(real_t p_max) {
348
_max_value = MAX(p_max, _min_value + MIN_Y_RANGE);
349
350
for (const Point &p : _points) {
351
_max_value = MAX(_max_value, p.position.y);
352
}
353
354
emit_signal(SNAME(SIGNAL_RANGE_CHANGED));
355
}
356
357
void Curve::set_min_domain(real_t p_min) {
358
_min_domain = MIN(p_min, _max_domain - MIN_X_RANGE);
359
360
if (_points.size() > 0 && _min_domain > _points[0].position.x) {
361
_min_domain = _points[0].position.x;
362
}
363
364
mark_dirty();
365
emit_signal(SNAME(SIGNAL_DOMAIN_CHANGED));
366
}
367
368
void Curve::set_max_domain(real_t p_max) {
369
_max_domain = MAX(p_max, _min_domain + MIN_X_RANGE);
370
371
if (_points.size() > 0 && _max_domain < _points[_points.size() - 1].position.x) {
372
_max_domain = _points[_points.size() - 1].position.x;
373
}
374
375
mark_dirty();
376
emit_signal(SNAME(SIGNAL_DOMAIN_CHANGED));
377
}
378
379
real_t Curve::sample(real_t p_offset) const {
380
if (_points.is_empty()) {
381
return 0;
382
}
383
if (_points.size() == 1) {
384
return _points[0].position.y;
385
}
386
387
uint32_t i = get_index(p_offset);
388
389
if (i == _points.size() - 1) {
390
return _points[i].position.y;
391
}
392
393
real_t local = p_offset - _points[i].position.x;
394
395
if (i == 0 && local <= 0) {
396
return _points[0].position.y;
397
}
398
399
return sample_local_nocheck(i, local);
400
}
401
402
real_t Curve::sample_local_nocheck(int p_index, real_t p_local_offset) const {
403
const Point a = _points[p_index];
404
const Point b = _points[p_index + 1];
405
406
/* Cubic bézier
407
*
408
* ac-----bc
409
* / \
410
* / \ Here with a.right_tangent > 0
411
* / \ and b.left_tangent < 0
412
* / \
413
* a b
414
*
415
* |-d1--|-d2--|-d3--|
416
*
417
* d1 == d2 == d3 == d / 3
418
*/
419
420
// Control points are chosen at equal distances.
421
real_t d = b.position.x - a.position.x;
422
if (Math::is_zero_approx(d)) {
423
return b.position.y;
424
}
425
p_local_offset /= d;
426
d /= 3.0;
427
real_t yac = a.position.y + d * a.right_tangent;
428
real_t ybc = b.position.y - d * b.left_tangent;
429
430
real_t y = Math::bezier_interpolate(a.position.y, yac, ybc, b.position.y, p_local_offset);
431
432
return y;
433
}
434
435
void Curve::mark_dirty() {
436
_baked_cache_dirty = true;
437
emit_changed();
438
}
439
440
Array Curve::get_data() const {
441
Array output;
442
const unsigned int ELEMS = 5;
443
output.resize(_points.size() * ELEMS);
444
445
for (uint32_t j = 0; j < _points.size(); ++j) {
446
const Point p = _points[j];
447
uint32_t i = j * ELEMS;
448
449
output[i] = p.position;
450
output[i + 1] = p.left_tangent;
451
output[i + 2] = p.right_tangent;
452
output[i + 3] = p.left_mode;
453
output[i + 4] = p.right_mode;
454
}
455
456
return output;
457
}
458
459
void Curve::set_data(const Array p_input) {
460
const unsigned int ELEMS = 5;
461
ERR_FAIL_COND(p_input.size() % ELEMS != 0);
462
463
// Validate input
464
for (int i = 0; i < p_input.size(); i += ELEMS) {
465
ERR_FAIL_COND(p_input[i].get_type() != Variant::VECTOR2);
466
ERR_FAIL_COND(!p_input[i + 1].is_num());
467
ERR_FAIL_COND(p_input[i + 2].get_type() != Variant::FLOAT);
468
469
ERR_FAIL_COND(p_input[i + 3].get_type() != Variant::INT);
470
int left_mode = p_input[i + 3];
471
ERR_FAIL_COND(left_mode < 0 || left_mode >= TANGENT_MODE_COUNT);
472
473
ERR_FAIL_COND(p_input[i + 4].get_type() != Variant::INT);
474
int right_mode = p_input[i + 4];
475
ERR_FAIL_COND(right_mode < 0 || right_mode >= TANGENT_MODE_COUNT);
476
}
477
int old_size = _points.size();
478
int new_size = p_input.size() / ELEMS;
479
if (old_size != new_size) {
480
_points.resize(new_size);
481
}
482
483
for (uint32_t j = 0; j < _points.size(); ++j) {
484
Point &p = _points[j];
485
int i = j * ELEMS;
486
487
p.position = p_input[i];
488
p.left_tangent = p_input[i + 1];
489
p.right_tangent = p_input[i + 2];
490
int left_mode = p_input[i + 3];
491
int right_mode = p_input[i + 4];
492
p.left_mode = (TangentMode)left_mode;
493
p.right_mode = (TangentMode)right_mode;
494
}
495
496
mark_dirty();
497
if (old_size != new_size) {
498
notify_property_list_changed();
499
}
500
}
501
502
void Curve::bake() {
503
_bake();
504
}
505
506
void Curve::_bake() const {
507
_baked_cache.clear();
508
509
_baked_cache.resize(_bake_resolution);
510
511
for (int i = 1; i < _bake_resolution - 1; ++i) {
512
real_t x = get_domain_range() * i / static_cast<real_t>(_bake_resolution - 1) + _min_domain;
513
real_t y = sample(x);
514
_baked_cache.write[i] = y;
515
}
516
517
if (_points.size() != 0) {
518
_baked_cache.write[0] = _points[0].position.y;
519
_baked_cache.write[_baked_cache.size() - 1] = _points[_points.size() - 1].position.y;
520
}
521
522
_baked_cache_dirty = false;
523
}
524
525
void Curve::set_bake_resolution(int p_resolution) {
526
ERR_FAIL_COND(p_resolution < 1);
527
ERR_FAIL_COND(p_resolution > 1000);
528
_bake_resolution = p_resolution;
529
_baked_cache_dirty = true;
530
}
531
532
real_t Curve::sample_baked(real_t p_offset) const {
533
// Make sure that p_offset is finite.
534
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), 0, "Offset is non-finite");
535
536
if (_baked_cache_dirty) {
537
// Last-second bake if not done already.
538
_bake();
539
}
540
541
// Special cases if the cache is too small.
542
if (_baked_cache.is_empty()) {
543
if (_points.is_empty()) {
544
return 0;
545
}
546
return _points[0].position.y;
547
} else if (_baked_cache.size() == 1) {
548
return _baked_cache[0];
549
}
550
551
// Get interpolation index.
552
real_t fi = (p_offset - _min_domain) / get_domain_range() * (_baked_cache.size() - 1);
553
int i = Math::floor(fi);
554
if (i < 0) {
555
i = 0;
556
fi = 0;
557
} else if (i >= _baked_cache.size()) {
558
i = _baked_cache.size() - 1;
559
fi = 0;
560
}
561
562
// Sample.
563
if (i + 1 < _baked_cache.size()) {
564
real_t t = fi - i;
565
return Math::lerp(_baked_cache[i], _baked_cache[i + 1], t);
566
} else {
567
return _baked_cache[_baked_cache.size() - 1];
568
}
569
}
570
571
void Curve::ensure_default_setup(real_t p_min, real_t p_max) {
572
if (_points.is_empty() && _min_value == 0 && _max_value == 1) {
573
add_point(Vector2(0, 1));
574
add_point(Vector2(1, 1));
575
set_min_value(p_min);
576
set_max_value(p_max);
577
}
578
}
579
580
bool Curve::_set(const StringName &p_name, const Variant &p_value) {
581
Vector<String> components = String(p_name).split("/", true, 2);
582
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
583
int point_index = components[0].trim_prefix("point_").to_int();
584
const String &property = components[1];
585
if (property == "position") {
586
Vector2 position = p_value.operator Vector2();
587
set_point_offset(point_index, position.x);
588
set_point_value(point_index, position.y);
589
return true;
590
} else if (property == "left_tangent") {
591
set_point_left_tangent(point_index, p_value);
592
return true;
593
} else if (property == "left_mode") {
594
int mode = p_value;
595
set_point_left_mode(point_index, (TangentMode)mode);
596
return true;
597
} else if (property == "right_tangent") {
598
set_point_right_tangent(point_index, p_value);
599
return true;
600
} else if (property == "right_mode") {
601
int mode = p_value;
602
set_point_right_mode(point_index, (TangentMode)mode);
603
return true;
604
}
605
}
606
return false;
607
}
608
609
bool Curve::_get(const StringName &p_name, Variant &r_ret) const {
610
Vector<String> components = String(p_name).split("/", true, 2);
611
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
612
int point_index = components[0].trim_prefix("point_").to_int();
613
const String &property = components[1];
614
if (property == "position") {
615
r_ret = get_point_position(point_index);
616
return true;
617
} else if (property == "left_tangent") {
618
r_ret = get_point_left_tangent(point_index);
619
return true;
620
} else if (property == "left_mode") {
621
r_ret = get_point_left_mode(point_index);
622
return true;
623
} else if (property == "right_tangent") {
624
r_ret = get_point_right_tangent(point_index);
625
return true;
626
} else if (property == "right_mode") {
627
r_ret = get_point_right_mode(point_index);
628
return true;
629
}
630
}
631
return false;
632
}
633
634
void Curve::_get_property_list(List<PropertyInfo> *p_list) const {
635
for (uint32_t i = 0; i < _points.size(); i++) {
636
PropertyInfo pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/position", i));
637
pi.usage &= ~PROPERTY_USAGE_STORAGE;
638
p_list->push_back(pi);
639
640
if (i != 0) {
641
pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/left_tangent", i));
642
pi.usage &= ~PROPERTY_USAGE_STORAGE;
643
p_list->push_back(pi);
644
645
pi = PropertyInfo(Variant::INT, vformat("point_%d/left_mode", i), PROPERTY_HINT_ENUM, "Free,Linear");
646
pi.usage &= ~PROPERTY_USAGE_STORAGE;
647
p_list->push_back(pi);
648
}
649
650
if (i != _points.size() - 1) {
651
pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/right_tangent", i));
652
pi.usage &= ~PROPERTY_USAGE_STORAGE;
653
p_list->push_back(pi);
654
655
pi = PropertyInfo(Variant::INT, vformat("point_%d/right_mode", i), PROPERTY_HINT_ENUM, "Free,Linear");
656
pi.usage &= ~PROPERTY_USAGE_STORAGE;
657
p_list->push_back(pi);
658
}
659
}
660
}
661
662
void Curve::_bind_methods() {
663
ClassDB::bind_method(D_METHOD("get_point_count"), &Curve::get_point_count);
664
ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve::set_point_count);
665
ClassDB::bind_method(D_METHOD("add_point", "position", "left_tangent", "right_tangent", "left_mode", "right_mode"), &Curve::add_point, DEFVAL(0), DEFVAL(0), DEFVAL(TANGENT_FREE), DEFVAL(TANGENT_FREE));
666
ClassDB::bind_method(D_METHOD("remove_point", "index"), &Curve::remove_point);
667
ClassDB::bind_method(D_METHOD("clear_points"), &Curve::clear_points);
668
ClassDB::bind_method(D_METHOD("get_point_position", "index"), &Curve::get_point_position);
669
ClassDB::bind_method(D_METHOD("set_point_value", "index", "y"), &Curve::set_point_value);
670
ClassDB::bind_method(D_METHOD("set_point_offset", "index", "offset"), &Curve::set_point_offset);
671
ClassDB::bind_method(D_METHOD("sample", "offset"), &Curve::sample);
672
ClassDB::bind_method(D_METHOD("sample_baked", "offset"), &Curve::sample_baked);
673
ClassDB::bind_method(D_METHOD("get_point_left_tangent", "index"), &Curve::get_point_left_tangent);
674
ClassDB::bind_method(D_METHOD("get_point_right_tangent", "index"), &Curve::get_point_right_tangent);
675
ClassDB::bind_method(D_METHOD("get_point_left_mode", "index"), &Curve::get_point_left_mode);
676
ClassDB::bind_method(D_METHOD("get_point_right_mode", "index"), &Curve::get_point_right_mode);
677
ClassDB::bind_method(D_METHOD("set_point_left_tangent", "index", "tangent"), &Curve::set_point_left_tangent);
678
ClassDB::bind_method(D_METHOD("set_point_right_tangent", "index", "tangent"), &Curve::set_point_right_tangent);
679
ClassDB::bind_method(D_METHOD("set_point_left_mode", "index", "mode"), &Curve::set_point_left_mode);
680
ClassDB::bind_method(D_METHOD("set_point_right_mode", "index", "mode"), &Curve::set_point_right_mode);
681
ClassDB::bind_method(D_METHOD("get_min_value"), &Curve::get_min_value);
682
ClassDB::bind_method(D_METHOD("set_min_value", "min"), &Curve::set_min_value);
683
ClassDB::bind_method(D_METHOD("get_max_value"), &Curve::get_max_value);
684
ClassDB::bind_method(D_METHOD("set_max_value", "max"), &Curve::set_max_value);
685
ClassDB::bind_method(D_METHOD("get_value_range"), &Curve::get_value_range);
686
ClassDB::bind_method(D_METHOD("get_min_domain"), &Curve::get_min_domain);
687
ClassDB::bind_method(D_METHOD("set_min_domain", "min"), &Curve::set_min_domain);
688
ClassDB::bind_method(D_METHOD("get_max_domain"), &Curve::get_max_domain);
689
ClassDB::bind_method(D_METHOD("set_max_domain", "max"), &Curve::set_max_domain);
690
ClassDB::bind_method(D_METHOD("get_domain_range"), &Curve::get_domain_range);
691
ClassDB::bind_method(D_METHOD("_get_limits"), &Curve::get_limits);
692
ClassDB::bind_method(D_METHOD("_set_limits", "data"), &Curve::set_limits);
693
ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes);
694
ClassDB::bind_method(D_METHOD("bake"), &Curve::bake);
695
ClassDB::bind_method(D_METHOD("get_bake_resolution"), &Curve::get_bake_resolution);
696
ClassDB::bind_method(D_METHOD("set_bake_resolution", "resolution"), &Curve::set_bake_resolution);
697
ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data);
698
ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data);
699
700
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_domain", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_min_domain", "get_min_domain");
701
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_domain", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_max_domain", "get_max_domain");
702
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_min_value", "get_min_value");
703
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_max_value", "get_max_value");
704
ADD_PROPERTY(PropertyInfo(Variant::NIL, "_limits", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_limits", "_get_limits");
705
ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution");
706
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
707
ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_");
708
709
ADD_SIGNAL(MethodInfo(SIGNAL_RANGE_CHANGED));
710
ADD_SIGNAL(MethodInfo(SIGNAL_DOMAIN_CHANGED));
711
712
BIND_ENUM_CONSTANT(TANGENT_FREE);
713
BIND_ENUM_CONSTANT(TANGENT_LINEAR);
714
BIND_ENUM_CONSTANT(TANGENT_MODE_COUNT);
715
}
716
717
int Curve2D::get_point_count() const {
718
return points.size();
719
}
720
721
void Curve2D::set_point_count(int p_count) {
722
ERR_FAIL_COND(p_count < 0);
723
int old_size = points.size();
724
if (old_size == p_count) {
725
return;
726
}
727
728
if (old_size > p_count) {
729
points.resize(p_count);
730
mark_dirty();
731
} else {
732
for (int i = p_count - old_size; i > 0; i--) {
733
_add_point(Vector2());
734
}
735
}
736
notify_property_list_changed();
737
}
738
739
void Curve2D::_add_point(const Vector2 &p_position, const Vector2 &p_in, const Vector2 &p_out, int p_atpos) {
740
Point n;
741
n.position = p_position;
742
n.in = p_in;
743
n.out = p_out;
744
if ((uint32_t)p_atpos < points.size()) {
745
points.insert(p_atpos, n);
746
} else {
747
points.push_back(n);
748
}
749
750
mark_dirty();
751
}
752
753
void Curve2D::add_point(const Vector2 &p_position, const Vector2 &p_in, const Vector2 &p_out, int p_atpos) {
754
_add_point(p_position, p_in, p_out, p_atpos);
755
notify_property_list_changed();
756
}
757
758
void Curve2D::set_point_position(int p_index, const Vector2 &p_position) {
759
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
760
761
points[p_index].position = p_position;
762
mark_dirty();
763
}
764
765
Vector2 Curve2D::get_point_position(int p_index) const {
766
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector2());
767
return points[p_index].position;
768
}
769
770
void Curve2D::set_point_in(int p_index, const Vector2 &p_in) {
771
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
772
773
points[p_index].in = p_in;
774
mark_dirty();
775
}
776
777
Vector2 Curve2D::get_point_in(int p_index) const {
778
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector2());
779
return points[p_index].in;
780
}
781
782
void Curve2D::set_point_out(int p_index, const Vector2 &p_out) {
783
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
784
785
points[p_index].out = p_out;
786
mark_dirty();
787
}
788
789
Vector2 Curve2D::get_point_out(int p_index) const {
790
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector2());
791
return points[p_index].out;
792
}
793
794
void Curve2D::_remove_point(int p_index) {
795
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
796
points.remove_at(p_index);
797
mark_dirty();
798
}
799
800
void Curve2D::remove_point(int p_index) {
801
_remove_point(p_index);
802
notify_property_list_changed();
803
}
804
805
void Curve2D::clear_points() {
806
if (!points.is_empty()) {
807
points.clear();
808
mark_dirty();
809
notify_property_list_changed();
810
}
811
}
812
813
Vector2 Curve2D::sample(int p_index, const real_t p_offset) const {
814
int pc = points.size();
815
ERR_FAIL_COND_V(pc == 0, Vector2());
816
817
if (p_index >= pc - 1) {
818
return points[pc - 1].position;
819
} else if (p_index < 0) {
820
return points[0].position;
821
}
822
823
Vector2 p0 = points[p_index].position;
824
Vector2 p1 = p0 + points[p_index].out;
825
Vector2 p3 = points[p_index + 1].position;
826
Vector2 p2 = p3 + points[p_index + 1].in;
827
828
return p0.bezier_interpolate(p1, p2, p3, p_offset);
829
}
830
831
Vector2 Curve2D::samplef(real_t p_findex) const {
832
if (p_findex < 0) {
833
p_findex = 0;
834
} else if (p_findex >= points.size()) {
835
p_findex = points.size();
836
}
837
838
return sample((int)p_findex, Math::fmod(p_findex, (real_t)1.0));
839
}
840
841
void Curve2D::mark_dirty() {
842
baked_cache_dirty = true;
843
emit_changed();
844
}
845
846
void Curve2D::_bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const {
847
real_t mp = p_begin + (p_end - p_begin) * 0.5;
848
Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
849
Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
850
Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
851
852
Vector2 na = (mid - beg).normalized();
853
Vector2 nb = (end - mid).normalized();
854
real_t dp = na.dot(nb);
855
856
if (dp < Math::cos(Math::deg_to_rad(p_tol))) {
857
r_bake[mp] = mid;
858
}
859
860
if (p_depth < p_max_depth) {
861
_bake_segment2d(r_bake, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_tol);
862
_bake_segment2d(r_bake, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_tol);
863
}
864
}
865
866
void Curve2D::_bake_segment2d_even_length(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_length) const {
867
Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
868
Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
869
870
real_t length = beg.distance_to(end);
871
872
if (length > p_length && p_depth < p_max_depth) {
873
real_t mp = (p_begin + p_end) * 0.5;
874
Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
875
r_bake[mp] = mid;
876
877
_bake_segment2d_even_length(r_bake, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length);
878
_bake_segment2d_even_length(r_bake, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length);
879
}
880
}
881
882
Vector2 Curve2D::_calculate_tangent(const Vector2 &p_begin, const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) {
883
// Handle corner cases.
884
if (Math::is_zero_approx(p_t - 0.0f)) {
885
if (p_control_1.is_equal_approx(p_begin)) {
886
if (p_control_1.is_equal_approx(p_control_2)) {
887
return (p_end - p_begin).normalized();
888
} else {
889
return (p_control_2 - p_begin).normalized();
890
}
891
}
892
} else if (Math::is_zero_approx(p_t - 1.0f)) {
893
if (p_control_2.is_equal_approx(p_end)) {
894
if (p_control_2.is_equal_approx(p_control_1)) {
895
return (p_end - p_begin).normalized();
896
} else {
897
return (p_end - p_control_1).normalized();
898
}
899
}
900
}
901
902
if (p_control_1.is_equal_approx(p_end) && p_control_2.is_equal_approx(p_begin)) {
903
return (p_end - p_begin).normalized();
904
}
905
906
return p_begin.bezier_derivative(p_control_1, p_control_2, p_end, p_t).normalized();
907
}
908
909
void Curve2D::_bake() const {
910
if (!baked_cache_dirty) {
911
return;
912
}
913
914
baked_max_ofs = 0;
915
baked_cache_dirty = false;
916
917
if (points.is_empty()) {
918
baked_point_cache.clear();
919
baked_dist_cache.clear();
920
baked_forward_vector_cache.clear();
921
return;
922
}
923
924
if (points.size() == 1) {
925
baked_point_cache.resize(1);
926
baked_point_cache.set(0, points[0].position);
927
baked_dist_cache.resize(1);
928
baked_dist_cache.set(0, 0.0);
929
baked_forward_vector_cache.resize(1);
930
baked_forward_vector_cache.set(0, Vector2(0.0, 0.1));
931
932
return;
933
}
934
935
// Tessellate curve to (almost) even length segments.
936
{
937
Vector<RBMap<real_t, Vector2>> midpoints = _tessellate_even_length(10, bake_interval);
938
939
int pc = 1;
940
for (uint32_t i = 0; i < points.size() - 1; i++) {
941
pc++;
942
pc += midpoints[i].size();
943
}
944
945
baked_point_cache.resize(pc);
946
baked_dist_cache.resize(pc);
947
baked_forward_vector_cache.resize(pc);
948
949
Vector2 *bpw = baked_point_cache.ptrw();
950
Vector2 *bfw = baked_forward_vector_cache.ptrw();
951
952
// Collect positions and sample tilts and tangents for each baked points.
953
bpw[0] = points[0].position;
954
bfw[0] = _calculate_tangent(points[0].position, points[0].position + points[0].out, points[1].position + points[1].in, points[1].position, 0.0);
955
int pidx = 0;
956
957
for (uint32_t i = 0; i < points.size() - 1; i++) {
958
for (const KeyValue<real_t, Vector2> &E : midpoints[i]) {
959
pidx++;
960
bpw[pidx] = E.value;
961
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, E.key);
962
}
963
964
pidx++;
965
bpw[pidx] = points[i + 1].position;
966
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, 1.0);
967
}
968
969
// Recalculate the baked distances.
970
real_t *bdw = baked_dist_cache.ptrw();
971
bdw[0] = 0.0;
972
for (int i = 0; i < pc - 1; i++) {
973
bdw[i + 1] = bdw[i] + bpw[i].distance_to(bpw[i + 1]);
974
}
975
baked_max_ofs = bdw[pc - 1];
976
}
977
}
978
979
real_t Curve2D::get_baked_length() const {
980
if (baked_cache_dirty) {
981
_bake();
982
}
983
984
return baked_max_ofs;
985
}
986
987
Curve2D::Interval Curve2D::_find_interval(real_t p_offset) const {
988
Interval interval = {
989
-1,
990
0.0
991
};
992
ERR_FAIL_COND_V_MSG(baked_cache_dirty, interval, "Backed cache is dirty");
993
994
int pc = baked_point_cache.size();
995
ERR_FAIL_COND_V_MSG(pc < 2, interval, "Less than two points in cache");
996
997
int start = 0;
998
int end = pc;
999
int idx = (end + start) / 2;
1000
// Binary search to find baked points.
1001
while (start < idx) {
1002
real_t offset = baked_dist_cache[idx];
1003
if (p_offset <= offset) {
1004
end = idx;
1005
} else {
1006
start = idx;
1007
}
1008
idx = (end + start) / 2;
1009
}
1010
1011
real_t offset_begin = baked_dist_cache[idx];
1012
real_t offset_end = baked_dist_cache[idx + 1];
1013
1014
real_t idx_interval = offset_end - offset_begin;
1015
ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, interval, "Offset out of range.");
1016
1017
interval.idx = idx;
1018
if (idx_interval < FLT_EPSILON) {
1019
interval.frac = 0.5; // For a very short interval, 0.5 is a reasonable choice.
1020
ERR_FAIL_V_MSG(interval, "Zero length interval.");
1021
}
1022
1023
interval.frac = (p_offset - offset_begin) / idx_interval;
1024
return interval;
1025
}
1026
1027
Vector2 Curve2D::_sample_baked(Interval p_interval, bool p_cubic) const {
1028
// Assuming p_interval is valid.
1029
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Vector2(), "Invalid interval");
1030
1031
int idx = p_interval.idx;
1032
real_t frac = p_interval.frac;
1033
1034
const Vector2 *r = baked_point_cache.ptr();
1035
int pc = baked_point_cache.size();
1036
1037
if (p_cubic) {
1038
Vector2 pre = idx > 0 ? r[idx - 1] : r[idx];
1039
Vector2 post = (idx < (pc - 2)) ? r[idx + 2] : r[idx + 1];
1040
return r[idx].cubic_interpolate(r[idx + 1], pre, post, frac);
1041
} else {
1042
return r[idx].lerp(r[idx + 1], frac);
1043
}
1044
}
1045
1046
Transform2D Curve2D::_sample_posture(Interval p_interval) const {
1047
// Assuming that p_interval is valid.
1048
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Transform2D(), "Invalid interval");
1049
1050
int idx = p_interval.idx;
1051
real_t frac = p_interval.frac;
1052
1053
Vector2 forward_begin = baked_forward_vector_cache[idx];
1054
Vector2 forward_end = baked_forward_vector_cache[idx + 1];
1055
1056
// Build frames at both ends of the interval, then interpolate.
1057
const Vector2 forward = forward_begin.slerp(forward_end, frac).normalized();
1058
const Vector2 side = Vector2(-forward.y, forward.x);
1059
1060
return Transform2D(forward, side, Vector2(0.0, 0.0));
1061
}
1062
1063
Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const {
1064
// Make sure that p_offset is finite.
1065
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector2(), "Offset is non-finite");
1066
1067
if (baked_cache_dirty) {
1068
_bake();
1069
}
1070
1071
// Validate: Curve may not have baked points.
1072
int pc = baked_point_cache.size();
1073
ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D.");
1074
1075
if (pc == 1) {
1076
return baked_point_cache[0];
1077
}
1078
1079
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
1080
1081
Curve2D::Interval interval = _find_interval(p_offset);
1082
return _sample_baked(interval, p_cubic);
1083
}
1084
1085
Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic) const {
1086
// Make sure that p_offset is finite.
1087
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Transform2D(), "Offset is non-finite");
1088
1089
if (baked_cache_dirty) {
1090
_bake();
1091
}
1092
1093
// Validate: Curve may not have baked points.
1094
const int point_count = baked_point_cache.size();
1095
ERR_FAIL_COND_V_MSG(point_count == 0, Transform2D(), "No points in Curve3D.");
1096
1097
if (point_count == 1) {
1098
Transform2D t;
1099
t.set_origin(baked_point_cache.get(0));
1100
ERR_FAIL_V_MSG(t, "Only 1 point in Curve2D.");
1101
}
1102
1103
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
1104
1105
// 0. Find interval for all sampling steps.
1106
Curve2D::Interval interval = _find_interval(p_offset);
1107
1108
// 1. Sample position.
1109
Vector2 pos = _sample_baked(interval, p_cubic);
1110
1111
// 2. Sample rotation frame.
1112
Transform2D frame = _sample_posture(interval);
1113
frame.set_origin(pos);
1114
1115
return frame;
1116
}
1117
1118
PackedVector2Array Curve2D::get_baked_points() const {
1119
if (baked_cache_dirty) {
1120
_bake();
1121
}
1122
1123
return baked_point_cache;
1124
}
1125
1126
void Curve2D::set_bake_interval(real_t p_tolerance) {
1127
bake_interval = p_tolerance;
1128
mark_dirty();
1129
}
1130
1131
real_t Curve2D::get_bake_interval() const {
1132
return bake_interval;
1133
}
1134
1135
PackedVector2Array Curve2D::get_points() const {
1136
return _get_data()["points"];
1137
}
1138
1139
Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const {
1140
// Brute force method.
1141
1142
if (baked_cache_dirty) {
1143
_bake();
1144
}
1145
1146
// Validate: Curve may not have baked points.
1147
int pc = baked_point_cache.size();
1148
ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D.");
1149
1150
if (pc == 1) {
1151
return baked_point_cache.get(0);
1152
}
1153
1154
const Vector2 *r = baked_point_cache.ptr();
1155
1156
Vector2 nearest;
1157
real_t nearest_dist = -1.0f;
1158
1159
for (int i = 0; i < pc - 1; i++) {
1160
const real_t interval = baked_dist_cache[i + 1] - baked_dist_cache[i];
1161
Vector2 origin = r[i];
1162
Vector2 direction = (r[i + 1] - origin) / interval;
1163
1164
real_t d = CLAMP((p_to_point - origin).dot(direction), 0.0f, interval);
1165
Vector2 proj = origin + direction * d;
1166
1167
real_t dist = proj.distance_squared_to(p_to_point);
1168
1169
if (nearest_dist < 0.0f || dist < nearest_dist) {
1170
nearest = proj;
1171
nearest_dist = dist;
1172
}
1173
}
1174
1175
return nearest;
1176
}
1177
1178
real_t Curve2D::get_closest_offset(const Vector2 &p_to_point) const {
1179
// Brute force method.
1180
1181
if (baked_cache_dirty) {
1182
_bake();
1183
}
1184
1185
// Validate: Curve may not have baked points.
1186
int pc = baked_point_cache.size();
1187
ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve2D.");
1188
1189
if (pc == 1) {
1190
return 0.0f;
1191
}
1192
1193
const Vector2 *r = baked_point_cache.ptr();
1194
1195
real_t nearest = 0.0f;
1196
real_t nearest_dist = -1.0f;
1197
real_t offset = 0.0f;
1198
1199
for (int i = 0; i < pc - 1; i++) {
1200
offset = baked_dist_cache[i];
1201
1202
const real_t interval = baked_dist_cache[i + 1] - baked_dist_cache[i];
1203
Vector2 origin = r[i];
1204
Vector2 direction = (r[i + 1] - origin) / interval;
1205
1206
real_t d = CLAMP((p_to_point - origin).dot(direction), 0.0f, interval);
1207
Vector2 proj = origin + direction * d;
1208
1209
real_t dist = proj.distance_squared_to(p_to_point);
1210
1211
if (nearest_dist < 0.0f || dist < nearest_dist) {
1212
nearest = offset + d;
1213
nearest_dist = dist;
1214
}
1215
}
1216
1217
return nearest;
1218
}
1219
1220
Dictionary Curve2D::_get_data() const {
1221
Dictionary dc;
1222
1223
PackedVector2Array d;
1224
d.resize(points.size() * 3);
1225
Vector2 *w = d.ptrw();
1226
1227
for (uint32_t i = 0; i < points.size(); i++) {
1228
w[i * 3 + 0] = points[i].in;
1229
w[i * 3 + 1] = points[i].out;
1230
w[i * 3 + 2] = points[i].position;
1231
}
1232
1233
dc["points"] = d;
1234
1235
return dc;
1236
}
1237
1238
void Curve2D::_set_data(const Dictionary &p_data) {
1239
ERR_FAIL_COND(!p_data.has("points"));
1240
1241
PackedVector2Array rp = p_data["points"];
1242
int pc = rp.size();
1243
ERR_FAIL_COND(pc % 3 != 0);
1244
int old_size = points.size();
1245
int new_size = pc / 3;
1246
if (old_size != new_size) {
1247
points.resize(new_size);
1248
}
1249
const Vector2 *r = rp.ptr();
1250
1251
for (uint32_t i = 0; i < points.size(); i++) {
1252
points[i].in = r[i * 3 + 0];
1253
points[i].out = r[i * 3 + 1];
1254
points[i].position = r[i * 3 + 2];
1255
}
1256
1257
mark_dirty();
1258
if (old_size != new_size) {
1259
notify_property_list_changed();
1260
}
1261
}
1262
1263
PackedVector2Array Curve2D::tessellate(int p_max_stages, real_t p_tolerance) const {
1264
PackedVector2Array tess;
1265
1266
if (points.is_empty()) {
1267
return tess;
1268
}
1269
1270
// The current implementation requires a sorted map.
1271
Vector<RBMap<real_t, Vector2>> midpoints;
1272
1273
midpoints.resize(points.size() - 1);
1274
1275
int pc = 1;
1276
for (uint32_t i = 0; i < points.size() - 1; i++) {
1277
_bake_segment2d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_tolerance);
1278
pc++;
1279
pc += midpoints[i].size();
1280
}
1281
1282
tess.resize(pc);
1283
Vector2 *bpw = tess.ptrw();
1284
bpw[0] = points[0].position;
1285
int pidx = 0;
1286
1287
for (uint32_t i = 0; i < points.size() - 1; i++) {
1288
for (const KeyValue<real_t, Vector2> &E : midpoints[i]) {
1289
pidx++;
1290
bpw[pidx] = E.value;
1291
}
1292
1293
pidx++;
1294
bpw[pidx] = points[i + 1].position;
1295
}
1296
1297
return tess;
1298
}
1299
1300
Vector<RBMap<real_t, Vector2>> Curve2D::_tessellate_even_length(int p_max_stages, real_t p_length) const {
1301
Vector<RBMap<real_t, Vector2>> midpoints;
1302
ERR_FAIL_COND_V_MSG(points.size() < 2, midpoints, "Curve must have at least 2 control point");
1303
1304
midpoints.resize(points.size() - 1);
1305
1306
for (uint32_t i = 0; i < points.size() - 1; i++) {
1307
_bake_segment2d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_length);
1308
}
1309
return midpoints;
1310
}
1311
1312
PackedVector2Array Curve2D::tessellate_even_length(int p_max_stages, real_t p_length) const {
1313
PackedVector2Array tess;
1314
1315
Vector<RBMap<real_t, Vector2>> midpoints = _tessellate_even_length(p_max_stages, p_length);
1316
if (midpoints.is_empty()) {
1317
return tess;
1318
}
1319
1320
int pc = 1;
1321
for (uint32_t i = 0; i < points.size() - 1; i++) {
1322
pc++;
1323
pc += midpoints[i].size();
1324
}
1325
1326
tess.resize(pc);
1327
Vector2 *bpw = tess.ptrw();
1328
bpw[0] = points[0].position;
1329
int pidx = 0;
1330
1331
for (uint32_t i = 0; i < points.size() - 1; i++) {
1332
for (const KeyValue<real_t, Vector2> &E : midpoints[i]) {
1333
pidx++;
1334
bpw[pidx] = E.value;
1335
}
1336
1337
pidx++;
1338
bpw[pidx] = points[i + 1].position;
1339
}
1340
1341
return tess;
1342
}
1343
1344
bool Curve2D::_set(const StringName &p_name, const Variant &p_value) {
1345
Vector<String> components = String(p_name).split("/", true, 2);
1346
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
1347
int point_index = components[0].trim_prefix("point_").to_int();
1348
const String &property = components[1];
1349
if (property == "position") {
1350
set_point_position(point_index, p_value);
1351
return true;
1352
} else if (property == "in") {
1353
set_point_in(point_index, p_value);
1354
return true;
1355
} else if (property == "out") {
1356
set_point_out(point_index, p_value);
1357
return true;
1358
}
1359
}
1360
return false;
1361
}
1362
1363
bool Curve2D::_get(const StringName &p_name, Variant &r_ret) const {
1364
Vector<String> components = String(p_name).split("/", true, 2);
1365
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
1366
int point_index = components[0].trim_prefix("point_").to_int();
1367
const String &property = components[1];
1368
if (property == "position") {
1369
r_ret = get_point_position(point_index);
1370
return true;
1371
} else if (property == "in") {
1372
r_ret = get_point_in(point_index);
1373
return true;
1374
} else if (property == "out") {
1375
r_ret = get_point_out(point_index);
1376
return true;
1377
}
1378
}
1379
return false;
1380
}
1381
1382
void Curve2D::_get_property_list(List<PropertyInfo> *p_list) const {
1383
for (uint32_t i = 0; i < points.size(); i++) {
1384
PropertyInfo pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/position", i));
1385
pi.usage &= ~PROPERTY_USAGE_STORAGE;
1386
p_list->push_back(pi);
1387
1388
if (i != 0) {
1389
pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/in", i));
1390
pi.usage &= ~PROPERTY_USAGE_STORAGE;
1391
p_list->push_back(pi);
1392
}
1393
1394
if (i != points.size() - 1) {
1395
pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/out", i));
1396
pi.usage &= ~PROPERTY_USAGE_STORAGE;
1397
p_list->push_back(pi);
1398
}
1399
}
1400
}
1401
1402
void Curve2D::_bind_methods() {
1403
ClassDB::bind_method(D_METHOD("get_point_count"), &Curve2D::get_point_count);
1404
ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve2D::set_point_count);
1405
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1));
1406
ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve2D::set_point_position);
1407
ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve2D::get_point_position);
1408
ClassDB::bind_method(D_METHOD("set_point_in", "idx", "position"), &Curve2D::set_point_in);
1409
ClassDB::bind_method(D_METHOD("get_point_in", "idx"), &Curve2D::get_point_in);
1410
ClassDB::bind_method(D_METHOD("set_point_out", "idx", "position"), &Curve2D::set_point_out);
1411
ClassDB::bind_method(D_METHOD("get_point_out", "idx"), &Curve2D::get_point_out);
1412
ClassDB::bind_method(D_METHOD("remove_point", "idx"), &Curve2D::remove_point);
1413
ClassDB::bind_method(D_METHOD("clear_points"), &Curve2D::clear_points);
1414
ClassDB::bind_method(D_METHOD("sample", "idx", "t"), &Curve2D::sample);
1415
ClassDB::bind_method(D_METHOD("samplef", "fofs"), &Curve2D::samplef);
1416
//ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve2D::bake,DEFVAL(10));
1417
ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve2D::set_bake_interval);
1418
ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve2D::get_bake_interval);
1419
1420
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length);
1421
ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(0.0), DEFVAL(false));
1422
ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic"), &Curve2D::sample_baked_with_rotation, DEFVAL(0.0), DEFVAL(false));
1423
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points);
1424
ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point);
1425
ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset);
1426
ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve2D::tessellate, DEFVAL(5), DEFVAL(4));
1427
ClassDB::bind_method(D_METHOD("tessellate_even_length", "max_stages", "tolerance_length"), &Curve2D::tessellate_even_length, DEFVAL(5), DEFVAL(20.0));
1428
1429
ClassDB::bind_method(D_METHOD("_get_data"), &Curve2D::_get_data);
1430
ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve2D::_set_data);
1431
1432
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
1433
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
1434
ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_");
1435
}
1436
1437
/***********************************************************************************/
1438
/***********************************************************************************/
1439
/***********************************************************************************/
1440
/***********************************************************************************/
1441
/***********************************************************************************/
1442
/***********************************************************************************/
1443
1444
int Curve3D::get_point_count() const {
1445
return points.size();
1446
}
1447
1448
void Curve3D::set_point_count(int p_count) {
1449
ERR_FAIL_COND(p_count < 0);
1450
int old_size = points.size();
1451
if (old_size == p_count) {
1452
return;
1453
}
1454
1455
if (old_size > p_count) {
1456
points.resize(p_count);
1457
mark_dirty();
1458
} else {
1459
for (int i = p_count - old_size; i > 0; i--) {
1460
_add_point(Vector3());
1461
}
1462
}
1463
notify_property_list_changed();
1464
}
1465
1466
void Curve3D::_add_point(const Vector3 &p_position, const Vector3 &p_in, const Vector3 &p_out, int p_atpos) {
1467
Point n;
1468
n.position = p_position;
1469
n.in = p_in;
1470
n.out = p_out;
1471
if ((uint32_t)p_atpos < points.size()) {
1472
points.insert(p_atpos, n);
1473
} else {
1474
points.push_back(n);
1475
}
1476
1477
mark_dirty();
1478
}
1479
1480
void Curve3D::add_point(const Vector3 &p_position, const Vector3 &p_in, const Vector3 &p_out, int p_atpos) {
1481
_add_point(p_position, p_in, p_out, p_atpos);
1482
notify_property_list_changed();
1483
}
1484
1485
void Curve3D::set_point_position(int p_index, const Vector3 &p_position) {
1486
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
1487
1488
points[p_index].position = p_position;
1489
mark_dirty();
1490
}
1491
1492
Vector3 Curve3D::get_point_position(int p_index) const {
1493
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector3());
1494
return points[p_index].position;
1495
}
1496
1497
void Curve3D::set_point_tilt(int p_index, real_t p_tilt) {
1498
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
1499
1500
points[p_index].tilt = p_tilt;
1501
mark_dirty();
1502
}
1503
1504
real_t Curve3D::get_point_tilt(int p_index) const {
1505
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), 0);
1506
return points[p_index].tilt;
1507
}
1508
1509
void Curve3D::set_point_in(int p_index, const Vector3 &p_in) {
1510
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
1511
1512
points[p_index].in = p_in;
1513
mark_dirty();
1514
}
1515
1516
Vector3 Curve3D::get_point_in(int p_index) const {
1517
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector3());
1518
return points[p_index].in;
1519
}
1520
1521
void Curve3D::set_point_out(int p_index, const Vector3 &p_out) {
1522
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
1523
1524
points[p_index].out = p_out;
1525
mark_dirty();
1526
}
1527
1528
Vector3 Curve3D::get_point_out(int p_index) const {
1529
ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_index, points.size(), Vector3());
1530
return points[p_index].out;
1531
}
1532
1533
void Curve3D::_remove_point(int p_index) {
1534
ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_index, points.size());
1535
points.remove_at(p_index);
1536
mark_dirty();
1537
}
1538
1539
void Curve3D::remove_point(int p_index) {
1540
_remove_point(p_index);
1541
if (closed && points.size() < 2) {
1542
set_closed(false);
1543
}
1544
notify_property_list_changed();
1545
}
1546
1547
void Curve3D::clear_points() {
1548
if (!points.is_empty()) {
1549
points.clear();
1550
mark_dirty();
1551
notify_property_list_changed();
1552
}
1553
}
1554
1555
Vector3 Curve3D::sample(int p_index, real_t p_offset) const {
1556
int pc = points.size();
1557
ERR_FAIL_COND_V(pc == 0, Vector3());
1558
1559
if (p_index >= pc - 1) {
1560
if (!closed) {
1561
return points[pc - 1].position;
1562
} else {
1563
p_index = pc - 1;
1564
}
1565
} else if (p_index < 0) {
1566
return points[0].position;
1567
}
1568
1569
Vector3 p0 = points[p_index].position;
1570
Vector3 p1 = p0 + points[p_index].out;
1571
Vector3 p3, p2;
1572
if (!closed || p_index < pc - 1) {
1573
p3 = points[p_index + 1].position;
1574
p2 = p3 + points[p_index + 1].in;
1575
} else {
1576
p3 = points[0].position;
1577
p2 = p3 + points[0].in;
1578
}
1579
1580
return p0.bezier_interpolate(p1, p2, p3, p_offset);
1581
}
1582
1583
Vector3 Curve3D::samplef(real_t p_findex) const {
1584
if (p_findex < 0) {
1585
p_findex = 0;
1586
} else if (p_findex >= points.size()) {
1587
p_findex = points.size();
1588
}
1589
1590
return sample((int)p_findex, Math::fmod(p_findex, (real_t)1.0));
1591
}
1592
1593
void Curve3D::mark_dirty() {
1594
baked_cache_dirty = true;
1595
emit_changed();
1596
}
1597
1598
void Curve3D::_bake_segment3d(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const {
1599
real_t mp = p_begin + (p_end - p_begin) * 0.5;
1600
Vector3 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
1601
Vector3 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
1602
Vector3 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
1603
1604
Vector3 na = (mid - beg).normalized();
1605
Vector3 nb = (end - mid).normalized();
1606
real_t dp = na.dot(nb);
1607
1608
if (dp < Math::cos(Math::deg_to_rad(p_tol))) {
1609
r_bake[mp] = mid;
1610
}
1611
if (p_depth < p_max_depth) {
1612
_bake_segment3d(r_bake, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_tol);
1613
_bake_segment3d(r_bake, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_tol);
1614
}
1615
}
1616
1617
void Curve3D::_bake_segment3d_even_length(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_length) const {
1618
Vector3 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
1619
Vector3 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
1620
1621
real_t length = beg.distance_to(end);
1622
1623
if (length > p_length && p_depth < p_max_depth) {
1624
real_t mp = (p_begin + p_end) * 0.5;
1625
Vector3 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
1626
r_bake[mp] = mid;
1627
1628
_bake_segment3d_even_length(r_bake, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length);
1629
_bake_segment3d_even_length(r_bake, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length);
1630
}
1631
}
1632
1633
Vector3 Curve3D::_calculate_tangent(const Vector3 &p_begin, const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) {
1634
// Handle corner cases.
1635
if (Math::is_zero_approx(p_t - 0.0f)) {
1636
if (p_control_1.is_equal_approx(p_begin)) {
1637
if (p_control_1.is_equal_approx(p_control_2)) {
1638
return (p_end - p_begin).normalized();
1639
} else {
1640
return (p_control_2 - p_begin).normalized();
1641
}
1642
}
1643
} else if (Math::is_zero_approx(p_t - 1.0f)) {
1644
if (p_control_2.is_equal_approx(p_end)) {
1645
if (p_control_2.is_equal_approx(p_control_1)) {
1646
return (p_end - p_begin).normalized();
1647
} else {
1648
return (p_end - p_control_1).normalized();
1649
}
1650
}
1651
}
1652
1653
if (p_control_1.is_equal_approx(p_end) && p_control_2.is_equal_approx(p_begin)) {
1654
return (p_end - p_begin).normalized();
1655
}
1656
1657
return p_begin.bezier_derivative(p_control_1, p_control_2, p_end, p_t).normalized();
1658
}
1659
1660
void Curve3D::_bake() const {
1661
if (!baked_cache_dirty) {
1662
return;
1663
}
1664
1665
baked_max_ofs = 0;
1666
baked_cache_dirty = false;
1667
1668
if (points.is_empty()) {
1669
#ifdef TOOLS_ENABLED
1670
points_in_cache.clear();
1671
#endif
1672
baked_point_cache.clear();
1673
baked_tilt_cache.clear();
1674
baked_dist_cache.clear();
1675
1676
baked_forward_vector_cache.clear();
1677
baked_up_vector_cache.clear();
1678
return;
1679
}
1680
1681
if (points.size() == 1) {
1682
#ifdef TOOLS_ENABLED
1683
points_in_cache.resize(1);
1684
points_in_cache.set(0, 0);
1685
#endif
1686
1687
baked_point_cache.resize(1);
1688
baked_point_cache.set(0, points[0].position);
1689
baked_tilt_cache.resize(1);
1690
baked_tilt_cache.set(0, points[0].tilt);
1691
baked_dist_cache.resize(1);
1692
baked_dist_cache.set(0, 0.0);
1693
baked_forward_vector_cache.resize(1);
1694
baked_forward_vector_cache.set(0, Vector3(0.0, 0.0, 1.0));
1695
1696
if (up_vector_enabled) {
1697
baked_up_vector_cache.resize(1);
1698
baked_up_vector_cache.set(0, Vector3(0.0, 1.0, 0.0));
1699
} else {
1700
baked_up_vector_cache.clear();
1701
}
1702
1703
return;
1704
}
1705
1706
// Step 1: Tessellate curve to (almost) even length segments.
1707
{
1708
Vector<RBMap<real_t, Vector3>> midpoints = _tessellate_even_length(10, bake_interval);
1709
1710
const int num_intervals = closed ? points.size() : points.size() - 1;
1711
1712
#ifdef TOOLS_ENABLED
1713
points_in_cache.resize(closed ? (points.size() + 1) : points.size());
1714
points_in_cache.set(0, 0);
1715
#endif
1716
1717
// Point Count: Begins at 1 to account for the last point.
1718
int pc = 1;
1719
for (int i = 0; i < num_intervals; i++) {
1720
pc++;
1721
pc += midpoints[i].size();
1722
#ifdef TOOLS_ENABLED
1723
points_in_cache.set(i + 1, pc - 1);
1724
#endif
1725
}
1726
1727
baked_point_cache.resize(pc);
1728
baked_tilt_cache.resize(pc);
1729
baked_dist_cache.resize(pc);
1730
baked_forward_vector_cache.resize(pc);
1731
1732
Vector3 *bpw = baked_point_cache.ptrw();
1733
real_t *btw = baked_tilt_cache.ptrw();
1734
Vector3 *bfw = baked_forward_vector_cache.ptrw();
1735
1736
// Collect positions and sample tilts and tangents for each baked points.
1737
bpw[0] = points[0].position;
1738
bfw[0] = _calculate_tangent(points[0].position, points[0].position + points[0].out, points[1].position + points[1].in, points[1].position, 0.0);
1739
btw[0] = points[0].tilt;
1740
int pidx = 0;
1741
1742
for (int i = 0; i < num_intervals; i++) {
1743
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
1744
pidx++;
1745
bpw[pidx] = E.value;
1746
if (!closed || i < num_intervals - 1) {
1747
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, E.key);
1748
btw[pidx] = Math::lerp(points[i].tilt, points[i + 1].tilt, E.key);
1749
} else {
1750
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[0].position + points[0].in, points[0].position, E.key);
1751
btw[pidx] = Math::lerp(points[i].tilt, points[0].tilt, E.key);
1752
}
1753
}
1754
1755
pidx++;
1756
if (!closed || i < num_intervals - 1) {
1757
bpw[pidx] = points[i + 1].position;
1758
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, 1.0);
1759
btw[pidx] = points[i + 1].tilt;
1760
} else {
1761
bpw[pidx] = points[0].position;
1762
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[0].position + points[0].in, points[0].position, 1.0);
1763
btw[pidx] = points[0].tilt;
1764
}
1765
}
1766
1767
// Recalculate the baked distances.
1768
real_t *bdw = baked_dist_cache.ptrw();
1769
bdw[0] = 0.0;
1770
for (int i = 0; i < pc - 1; i++) {
1771
bdw[i + 1] = bdw[i] + bpw[i].distance_to(bpw[i + 1]);
1772
}
1773
baked_max_ofs = bdw[pc - 1];
1774
}
1775
1776
if (!up_vector_enabled) {
1777
baked_up_vector_cache.resize(0);
1778
return;
1779
}
1780
1781
// Step 2: Calculate the up vectors and the whole local reference frame.
1782
//
1783
// See Dougan, Carl. "The parallel transport frame." Game Programming Gems 2 (2001): 215-219.
1784
// for an example discussing about why not the Frenet frame.
1785
{
1786
int point_count = baked_point_cache.size();
1787
1788
baked_up_vector_cache.resize(point_count);
1789
Vector3 *up_write = baked_up_vector_cache.ptrw();
1790
1791
const Vector3 *forward_ptr = baked_forward_vector_cache.ptr();
1792
const Vector3 *points_ptr = baked_point_cache.ptr();
1793
1794
Basis frame; // X-right, Y-up, -Z-forward.
1795
Basis frame_prev;
1796
1797
// Set the initial frame based on Y-up rule.
1798
{
1799
Vector3 forward = forward_ptr[0];
1800
1801
if (std::abs(forward.dot(Vector3(0, 1, 0))) > 1.0 - UNIT_EPSILON) {
1802
frame_prev = Basis::looking_at(forward, Vector3(1, 0, 0));
1803
} else {
1804
frame_prev = Basis::looking_at(forward, Vector3(0, 1, 0));
1805
}
1806
1807
up_write[0] = frame_prev.get_column(1);
1808
}
1809
1810
// Calculate the Parallel Transport Frame.
1811
for (int idx = 1; idx < point_count; idx++) {
1812
Vector3 forward = forward_ptr[idx];
1813
1814
Basis rotate;
1815
rotate.rotate_to_align(-frame_prev.get_column(2), forward);
1816
frame = rotate * frame_prev;
1817
frame.orthonormalize(); // Guard against float error accumulation.
1818
1819
up_write[idx] = frame.get_column(1);
1820
frame_prev = frame;
1821
}
1822
1823
bool is_loop = true;
1824
// Loop smoothing only applies when the curve is a loop, which means two ends meet, and share forward directions.
1825
{
1826
if (!points_ptr[0].is_equal_approx(points_ptr[point_count - 1])) {
1827
is_loop = false;
1828
}
1829
1830
real_t dot = forward_ptr[0].dot(forward_ptr[point_count - 1]);
1831
if (dot < 1.0 - UNIT_EPSILON) { // Alignment should not be too tight, or it doesn't work for coarse bake interval.
1832
is_loop = false;
1833
}
1834
}
1835
1836
// Twist up vectors, so that they align at two ends of the curve.
1837
if (is_loop) {
1838
const Vector3 up_start = up_write[0];
1839
const Vector3 up_end = up_write[point_count - 1];
1840
1841
real_t sign = SIGN(up_end.cross(up_start).dot(forward_ptr[0]));
1842
real_t full_angle = Quaternion(up_end, up_start).get_angle();
1843
1844
if (std::abs(full_angle) < CMP_EPSILON) {
1845
return;
1846
} else {
1847
const real_t *dists = baked_dist_cache.ptr();
1848
for (int idx = 1; idx < point_count; idx++) {
1849
const real_t frac = dists[idx] / baked_max_ofs;
1850
const real_t angle = Math::lerp((real_t)0.0, full_angle, frac);
1851
Basis twist(forward_ptr[idx] * sign, angle);
1852
1853
up_write[idx] = twist.xform(up_write[idx]);
1854
}
1855
}
1856
}
1857
}
1858
}
1859
1860
real_t Curve3D::get_baked_length() const {
1861
if (baked_cache_dirty) {
1862
_bake();
1863
}
1864
1865
return baked_max_ofs;
1866
}
1867
1868
Curve3D::Interval Curve3D::_find_interval(real_t p_offset) const {
1869
Interval interval = {
1870
-1,
1871
0.0
1872
};
1873
ERR_FAIL_COND_V_MSG(baked_cache_dirty, interval, "Backed cache is dirty");
1874
1875
int pc = baked_point_cache.size();
1876
ERR_FAIL_COND_V_MSG(pc < 2, interval, "Less than two points in cache");
1877
1878
int start = 0;
1879
int end = pc;
1880
int idx = (end + start) / 2;
1881
// Binary search to find baked points.
1882
while (start < idx) {
1883
real_t offset = baked_dist_cache[idx];
1884
if (p_offset <= offset) {
1885
end = idx;
1886
} else {
1887
start = idx;
1888
}
1889
idx = (end + start) / 2;
1890
}
1891
1892
real_t offset_begin = baked_dist_cache[idx];
1893
real_t offset_end = baked_dist_cache[idx + 1];
1894
1895
real_t idx_interval = offset_end - offset_begin;
1896
ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, interval, "Offset out of range.");
1897
1898
interval.idx = idx;
1899
if (idx_interval < FLT_EPSILON) {
1900
interval.frac = 0.5; // For a very short interval, 0.5 is a reasonable choice.
1901
ERR_FAIL_V_MSG(interval, "Zero length interval.");
1902
}
1903
1904
interval.frac = (p_offset - offset_begin) / idx_interval;
1905
return interval;
1906
}
1907
1908
Vector3 Curve3D::_sample_baked(Interval p_interval, bool p_cubic) const {
1909
// Assuming p_interval is valid.
1910
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Vector3(), "Invalid interval");
1911
1912
int idx = p_interval.idx;
1913
real_t frac = p_interval.frac;
1914
1915
const Vector3 *r = baked_point_cache.ptr();
1916
int pc = baked_point_cache.size();
1917
1918
if (p_cubic) {
1919
Vector3 pre = idx > 0 ? r[idx - 1] : r[idx];
1920
Vector3 post = (idx < (pc - 2)) ? r[idx + 2] : r[idx + 1];
1921
return r[idx].cubic_interpolate(r[idx + 1], pre, post, frac);
1922
} else {
1923
return r[idx].lerp(r[idx + 1], frac);
1924
}
1925
}
1926
1927
real_t Curve3D::_sample_baked_tilt(Interval p_interval) const {
1928
// Assuming that p_interval is valid.
1929
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_tilt_cache.size(), 0.0, "Invalid interval");
1930
1931
int idx = p_interval.idx;
1932
real_t frac = p_interval.frac;
1933
1934
const real_t *r = baked_tilt_cache.ptr();
1935
1936
return Math::lerp(r[idx], r[idx + 1], frac);
1937
}
1938
1939
// Internal method for getting posture at a baked point. Assuming caller
1940
// make all safety checks.
1941
Basis Curve3D::_compose_posture(int p_index) const {
1942
Vector3 forward = baked_forward_vector_cache[p_index];
1943
1944
Vector3 up;
1945
if (up_vector_enabled) {
1946
up = baked_up_vector_cache[p_index];
1947
} else {
1948
up = Vector3(0.0, 1.0, 0.0);
1949
}
1950
1951
const Basis frame = Basis::looking_at(forward, up);
1952
return frame;
1953
}
1954
1955
Basis Curve3D::_sample_posture(Interval p_interval, bool p_apply_tilt) const {
1956
// Assuming that p_interval is valid.
1957
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Basis(), "Invalid interval");
1958
if (up_vector_enabled) {
1959
ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_up_vector_cache.size(), Basis(), "Invalid interval");
1960
}
1961
1962
int idx = p_interval.idx;
1963
real_t frac = p_interval.frac;
1964
1965
// Get frames at both ends of the interval, then interpolate.
1966
const Basis frame_begin = _compose_posture(idx);
1967
const Basis frame_end = _compose_posture(idx + 1);
1968
const Basis frame = frame_begin.slerp(frame_end, frac).orthonormalized();
1969
1970
if (!p_apply_tilt) {
1971
return frame;
1972
}
1973
1974
// Applying tilt.
1975
const real_t tilt = _sample_baked_tilt(p_interval);
1976
Vector3 tangent = -frame.get_column(2);
1977
1978
const Basis twist(tangent, tilt);
1979
return twist * frame;
1980
}
1981
1982
#ifdef TOOLS_ENABLED
1983
// Get posture at a control point. Needed for Gizmo implementation.
1984
Basis Curve3D::get_point_baked_posture(int p_index, bool p_apply_tilt) const {
1985
if (baked_cache_dirty) {
1986
_bake();
1987
}
1988
1989
// Assuming that p_idx is valid.
1990
ERR_FAIL_INDEX_V_MSG(p_index, points_in_cache.size(), Basis(), "Invalid control point index");
1991
1992
int baked_idx = points_in_cache[p_index];
1993
Basis frame = _compose_posture(baked_idx);
1994
1995
if (!p_apply_tilt) {
1996
return frame;
1997
}
1998
1999
// Applying tilt.
2000
const real_t tilt = points[p_index].tilt;
2001
Vector3 tangent = -frame.get_column(2);
2002
const Basis twist(tangent, tilt);
2003
2004
return twist * frame;
2005
}
2006
#endif
2007
2008
Vector3 Curve3D::sample_baked(real_t p_offset, bool p_cubic) const {
2009
// Make sure that p_offset is finite.
2010
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector3(), "Offset is non-finite");
2011
2012
if (baked_cache_dirty) {
2013
_bake();
2014
}
2015
2016
// Validate: Curve may not have baked points.
2017
int pc = baked_point_cache.size();
2018
ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D.");
2019
2020
if (pc == 1) {
2021
return baked_point_cache[0];
2022
}
2023
2024
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
2025
2026
Curve3D::Interval interval = _find_interval(p_offset);
2027
return _sample_baked(interval, p_cubic);
2028
}
2029
2030
Transform3D Curve3D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_apply_tilt) const {
2031
// Make sure that p_offset is finite.
2032
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Transform3D(), "Offset is non-finite");
2033
2034
if (baked_cache_dirty) {
2035
_bake();
2036
}
2037
2038
// Validate: Curve may not have baked points.
2039
const int point_count = baked_point_cache.size();
2040
ERR_FAIL_COND_V_MSG(point_count == 0, Transform3D(), "No points in Curve3D.");
2041
2042
if (point_count == 1) {
2043
Transform3D t;
2044
t.origin = baked_point_cache.get(0);
2045
ERR_FAIL_V_MSG(t, "Only 1 point in Curve3D.");
2046
}
2047
2048
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
2049
2050
// 0. Find interval for all sampling steps.
2051
Curve3D::Interval interval = _find_interval(p_offset);
2052
2053
// 1. Sample position.
2054
Vector3 pos = _sample_baked(interval, p_cubic);
2055
2056
// 2. Sample rotation frame.
2057
Basis frame = _sample_posture(interval, p_apply_tilt);
2058
2059
return Transform3D(frame, pos);
2060
}
2061
2062
real_t Curve3D::sample_baked_tilt(real_t p_offset) const {
2063
// Make sure that p_offset is finite.
2064
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), 0, "Offset is non-finite");
2065
2066
if (baked_cache_dirty) {
2067
_bake();
2068
}
2069
2070
// Validate: Curve may not have baked tilts.
2071
int pc = baked_tilt_cache.size();
2072
ERR_FAIL_COND_V_MSG(pc == 0, 0, "No tilts in Curve3D.");
2073
2074
if (pc == 1) {
2075
return baked_tilt_cache.get(0);
2076
}
2077
2078
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
2079
2080
Curve3D::Interval interval = _find_interval(p_offset);
2081
return _sample_baked_tilt(interval);
2082
}
2083
2084
Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) const {
2085
// Make sure that p_offset is finite.
2086
ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector3(0, 1, 0), "Offset is non-finite");
2087
2088
if (baked_cache_dirty) {
2089
_bake();
2090
}
2091
2092
// Validate: Curve may not have baked up vectors.
2093
ERR_FAIL_COND_V_MSG(!up_vector_enabled, Vector3(0, 1, 0), "No up vectors in Curve3D.");
2094
2095
int count = baked_up_vector_cache.size();
2096
if (count == 1) {
2097
return baked_up_vector_cache.get(0);
2098
}
2099
2100
p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic.
2101
2102
Curve3D::Interval interval = _find_interval(p_offset);
2103
return _sample_posture(interval, p_apply_tilt).get_column(1);
2104
}
2105
2106
PackedVector3Array Curve3D::get_baked_points() const {
2107
if (baked_cache_dirty) {
2108
_bake();
2109
}
2110
2111
return baked_point_cache;
2112
}
2113
2114
Vector<real_t> Curve3D::get_baked_tilts() const {
2115
if (baked_cache_dirty) {
2116
_bake();
2117
}
2118
2119
return baked_tilt_cache;
2120
}
2121
2122
PackedVector3Array Curve3D::get_baked_up_vectors() const {
2123
if (baked_cache_dirty) {
2124
_bake();
2125
}
2126
2127
return baked_up_vector_cache;
2128
}
2129
2130
Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const {
2131
// Brute force method.
2132
2133
if (baked_cache_dirty) {
2134
_bake();
2135
}
2136
2137
// Validate: Curve may not have baked points.
2138
int pc = baked_point_cache.size();
2139
ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D.");
2140
2141
if (pc == 1) {
2142
return baked_point_cache.get(0);
2143
}
2144
2145
const Vector3 *r = baked_point_cache.ptr();
2146
2147
Vector3 nearest;
2148
real_t nearest_dist = -1.0f;
2149
2150
for (int i = 0; i < pc - 1; i++) {
2151
const real_t interval = baked_dist_cache[i + 1] - baked_dist_cache[i];
2152
Vector3 origin = r[i];
2153
Vector3 direction = (r[i + 1] - origin) / interval;
2154
2155
real_t d = CLAMP((p_to_point - origin).dot(direction), 0.0f, interval);
2156
Vector3 proj = origin + direction * d;
2157
2158
real_t dist = proj.distance_squared_to(p_to_point);
2159
2160
if (nearest_dist < 0.0f || dist < nearest_dist) {
2161
nearest = proj;
2162
nearest_dist = dist;
2163
}
2164
}
2165
2166
return nearest;
2167
}
2168
2169
PackedVector3Array Curve3D::get_points() const {
2170
return _get_data()["points"];
2171
}
2172
2173
real_t Curve3D::get_closest_offset(const Vector3 &p_to_point) const {
2174
// Brute force method.
2175
2176
if (baked_cache_dirty) {
2177
_bake();
2178
}
2179
2180
// Validate: Curve may not have baked points.
2181
int pc = baked_point_cache.size();
2182
ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve3D.");
2183
2184
if (pc == 1) {
2185
return 0.0f;
2186
}
2187
2188
const Vector3 *r = baked_point_cache.ptr();
2189
2190
real_t nearest = 0.0f;
2191
real_t nearest_dist = -1.0f;
2192
real_t offset;
2193
2194
for (int i = 0; i < pc - 1; i++) {
2195
offset = baked_dist_cache[i];
2196
2197
const real_t interval = baked_dist_cache[i + 1] - baked_dist_cache[i];
2198
Vector3 origin = r[i];
2199
Vector3 direction = (r[i + 1] - origin) / interval;
2200
2201
real_t d = CLAMP((p_to_point - origin).dot(direction), 0.0f, interval);
2202
Vector3 proj = origin + direction * d;
2203
2204
real_t dist = proj.distance_squared_to(p_to_point);
2205
2206
if (nearest_dist < 0.0f || dist < nearest_dist) {
2207
nearest = offset + d;
2208
nearest_dist = dist;
2209
}
2210
}
2211
2212
return nearest;
2213
}
2214
2215
void Curve3D::set_closed(bool p_closed) {
2216
if (closed == p_closed) {
2217
return;
2218
}
2219
2220
closed = p_closed;
2221
mark_dirty();
2222
notify_property_list_changed();
2223
}
2224
2225
bool Curve3D::is_closed() const {
2226
return closed;
2227
}
2228
2229
void Curve3D::set_bake_interval(real_t p_tolerance) {
2230
bake_interval = p_tolerance;
2231
mark_dirty();
2232
}
2233
2234
real_t Curve3D::get_bake_interval() const {
2235
return bake_interval;
2236
}
2237
2238
void Curve3D::set_up_vector_enabled(bool p_enable) {
2239
up_vector_enabled = p_enable;
2240
mark_dirty();
2241
}
2242
2243
bool Curve3D::is_up_vector_enabled() const {
2244
return up_vector_enabled;
2245
}
2246
2247
Dictionary Curve3D::_get_data() const {
2248
Dictionary dc;
2249
2250
PackedVector3Array d;
2251
d.resize(points.size() * 3);
2252
Vector3 *w = d.ptrw();
2253
Vector<real_t> t;
2254
t.resize(points.size());
2255
real_t *wt = t.ptrw();
2256
2257
for (uint32_t i = 0; i < points.size(); i++) {
2258
w[i * 3 + 0] = points[i].in;
2259
w[i * 3 + 1] = points[i].out;
2260
w[i * 3 + 2] = points[i].position;
2261
wt[i] = points[i].tilt;
2262
}
2263
2264
dc["points"] = d;
2265
dc["tilts"] = t;
2266
2267
return dc;
2268
}
2269
2270
void Curve3D::_set_data(const Dictionary &p_data) {
2271
ERR_FAIL_COND(!p_data.has("points"));
2272
ERR_FAIL_COND(!p_data.has("tilts"));
2273
2274
PackedVector3Array rp = p_data["points"];
2275
int pc = rp.size();
2276
ERR_FAIL_COND(pc % 3 != 0);
2277
int old_size = points.size();
2278
int new_size = pc / 3;
2279
if (old_size != new_size) {
2280
points.resize(new_size);
2281
}
2282
const Vector3 *r = rp.ptr();
2283
Vector<real_t> rtl = p_data["tilts"];
2284
const real_t *rt = rtl.ptr();
2285
2286
for (uint32_t i = 0; i < points.size(); i++) {
2287
points[i].in = r[i * 3 + 0];
2288
points[i].out = r[i * 3 + 1];
2289
points[i].position = r[i * 3 + 2];
2290
points[i].tilt = rt[i];
2291
}
2292
2293
mark_dirty();
2294
if (old_size != new_size) {
2295
notify_property_list_changed();
2296
}
2297
}
2298
2299
PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) const {
2300
PackedVector3Array tess;
2301
2302
if (points.is_empty()) {
2303
return tess;
2304
}
2305
Vector<RBMap<real_t, Vector3>> midpoints;
2306
2307
const int num_intervals = closed ? points.size() : points.size() - 1;
2308
midpoints.resize(num_intervals);
2309
2310
// Point Count: Begins at 1 to account for the last point.
2311
int pc = 1;
2312
for (int i = 0; i < num_intervals; i++) {
2313
if (!closed || i < num_intervals - 1) {
2314
_bake_segment3d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_tolerance);
2315
} else {
2316
_bake_segment3d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[0].position, points[0].in, 0, p_max_stages, p_tolerance);
2317
}
2318
pc++;
2319
pc += midpoints[i].size();
2320
}
2321
2322
tess.resize(pc);
2323
Vector3 *bpw = tess.ptrw();
2324
bpw[0] = points[0].position;
2325
int pidx = 0;
2326
2327
for (int i = 0; i < num_intervals; i++) {
2328
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
2329
pidx++;
2330
bpw[pidx] = E.value;
2331
}
2332
2333
pidx++;
2334
if (!closed || i < num_intervals - 1) {
2335
bpw[pidx] = points[i + 1].position;
2336
} else {
2337
bpw[pidx] = points[0].position;
2338
}
2339
}
2340
2341
return tess;
2342
}
2343
2344
Vector<RBMap<real_t, Vector3>> Curve3D::_tessellate_even_length(int p_max_stages, real_t p_length) const {
2345
Vector<RBMap<real_t, Vector3>> midpoints;
2346
ERR_FAIL_COND_V_MSG(points.size() < 2, midpoints, "Curve must have at least 2 control point");
2347
2348
const int num_intervals = closed ? points.size() : points.size() - 1;
2349
midpoints.resize(num_intervals);
2350
2351
for (int i = 0; i < num_intervals; i++) {
2352
if (!closed || i < num_intervals - 1) {
2353
_bake_segment3d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_length);
2354
} else {
2355
_bake_segment3d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[0].position, points[0].in, 0, p_max_stages, p_length);
2356
}
2357
}
2358
return midpoints;
2359
}
2360
2361
PackedVector3Array Curve3D::tessellate_even_length(int p_max_stages, real_t p_length) const {
2362
PackedVector3Array tess;
2363
2364
Vector<RBMap<real_t, Vector3>> midpoints = _tessellate_even_length(p_max_stages, p_length);
2365
if (midpoints.is_empty()) {
2366
return tess;
2367
}
2368
2369
const int num_intervals = closed ? points.size() : points.size() - 1;
2370
// Point Count: Begins at 1 to account for the last point.
2371
int pc = 1;
2372
for (int i = 0; i < num_intervals; i++) {
2373
pc++;
2374
pc += midpoints[i].size();
2375
}
2376
2377
tess.resize(pc);
2378
Vector3 *bpw = tess.ptrw();
2379
bpw[0] = points[0].position;
2380
int pidx = 0;
2381
2382
for (int i = 0; i < num_intervals; i++) {
2383
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
2384
pidx++;
2385
bpw[pidx] = E.value;
2386
}
2387
2388
pidx++;
2389
if (!closed || i < num_intervals - 1) {
2390
bpw[pidx] = points[i + 1].position;
2391
} else {
2392
bpw[pidx] = points[0].position;
2393
}
2394
}
2395
2396
return tess;
2397
}
2398
2399
bool Curve3D::_set(const StringName &p_name, const Variant &p_value) {
2400
Vector<String> components = String(p_name).split("/", true, 2);
2401
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
2402
int point_index = components[0].trim_prefix("point_").to_int();
2403
const String &property = components[1];
2404
if (property == "position") {
2405
set_point_position(point_index, p_value);
2406
return true;
2407
} else if (property == "in") {
2408
set_point_in(point_index, p_value);
2409
return true;
2410
} else if (property == "out") {
2411
set_point_out(point_index, p_value);
2412
return true;
2413
} else if (property == "tilt") {
2414
set_point_tilt(point_index, p_value);
2415
return true;
2416
}
2417
}
2418
return false;
2419
}
2420
2421
bool Curve3D::_get(const StringName &p_name, Variant &r_ret) const {
2422
Vector<String> components = String(p_name).split("/", true, 2);
2423
if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) {
2424
int point_index = components[0].trim_prefix("point_").to_int();
2425
const String &property = components[1];
2426
if (property == "position") {
2427
r_ret = get_point_position(point_index);
2428
return true;
2429
} else if (property == "in") {
2430
r_ret = get_point_in(point_index);
2431
return true;
2432
} else if (property == "out") {
2433
r_ret = get_point_out(point_index);
2434
return true;
2435
} else if (property == "tilt") {
2436
r_ret = get_point_tilt(point_index);
2437
return true;
2438
}
2439
}
2440
return false;
2441
}
2442
2443
void Curve3D::_get_property_list(List<PropertyInfo> *p_list) const {
2444
for (uint32_t i = 0; i < points.size(); i++) {
2445
PropertyInfo pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/position", i));
2446
pi.usage &= ~PROPERTY_USAGE_STORAGE;
2447
p_list->push_back(pi);
2448
2449
if (closed || i != 0) {
2450
pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/in", i));
2451
pi.usage &= ~PROPERTY_USAGE_STORAGE;
2452
p_list->push_back(pi);
2453
}
2454
2455
if (closed || i != points.size() - 1) {
2456
pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/out", i));
2457
pi.usage &= ~PROPERTY_USAGE_STORAGE;
2458
p_list->push_back(pi);
2459
}
2460
2461
pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/tilt", i), PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees");
2462
pi.usage &= ~PROPERTY_USAGE_STORAGE;
2463
p_list->push_back(pi);
2464
}
2465
}
2466
2467
void Curve3D::_bind_methods() {
2468
ClassDB::bind_method(D_METHOD("get_point_count"), &Curve3D::get_point_count);
2469
ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve3D::set_point_count);
2470
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1));
2471
ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve3D::set_point_position);
2472
ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve3D::get_point_position);
2473
ClassDB::bind_method(D_METHOD("set_point_tilt", "idx", "tilt"), &Curve3D::set_point_tilt);
2474
ClassDB::bind_method(D_METHOD("get_point_tilt", "idx"), &Curve3D::get_point_tilt);
2475
ClassDB::bind_method(D_METHOD("set_point_in", "idx", "position"), &Curve3D::set_point_in);
2476
ClassDB::bind_method(D_METHOD("get_point_in", "idx"), &Curve3D::get_point_in);
2477
ClassDB::bind_method(D_METHOD("set_point_out", "idx", "position"), &Curve3D::set_point_out);
2478
ClassDB::bind_method(D_METHOD("get_point_out", "idx"), &Curve3D::get_point_out);
2479
ClassDB::bind_method(D_METHOD("remove_point", "idx"), &Curve3D::remove_point);
2480
ClassDB::bind_method(D_METHOD("clear_points"), &Curve3D::clear_points);
2481
ClassDB::bind_method(D_METHOD("sample", "idx", "t"), &Curve3D::sample);
2482
ClassDB::bind_method(D_METHOD("samplef", "fofs"), &Curve3D::samplef);
2483
ClassDB::bind_method(D_METHOD("set_closed", "closed"), &Curve3D::set_closed);
2484
ClassDB::bind_method(D_METHOD("is_closed"), &Curve3D::is_closed);
2485
//ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10));
2486
ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval);
2487
ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval);
2488
ClassDB::bind_method(D_METHOD("set_up_vector_enabled", "enable"), &Curve3D::set_up_vector_enabled);
2489
ClassDB::bind_method(D_METHOD("is_up_vector_enabled"), &Curve3D::is_up_vector_enabled);
2490
2491
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length);
2492
ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve3D::sample_baked, DEFVAL(0.0), DEFVAL(false));
2493
ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "apply_tilt"), &Curve3D::sample_baked_with_rotation, DEFVAL(0.0), DEFVAL(false), DEFVAL(false));
2494
ClassDB::bind_method(D_METHOD("sample_baked_up_vector", "offset", "apply_tilt"), &Curve3D::sample_baked_up_vector, DEFVAL(false));
2495
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points);
2496
ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts);
2497
ClassDB::bind_method(D_METHOD("get_baked_up_vectors"), &Curve3D::get_baked_up_vectors);
2498
ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve3D::get_closest_point);
2499
ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve3D::get_closest_offset);
2500
ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve3D::tessellate, DEFVAL(5), DEFVAL(4));
2501
ClassDB::bind_method(D_METHOD("tessellate_even_length", "max_stages", "tolerance_length"), &Curve3D::tessellate_even_length, DEFVAL(5), DEFVAL(0.2));
2502
2503
ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data);
2504
ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve3D::_set_data);
2505
2506
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "is_closed");
2507
2508
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
2509
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
2510
ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_");
2511
2512
ADD_GROUP("Up Vector", "up_vector_");
2513
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled");
2514
}
2515
2516