Blender V5.0
curveprofile.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10
11#include "MEM_guardedalloc.h"
12
13#include "DNA_curve_types.h"
15
16#include "BLI_math_geom.h"
17#include "BLI_math_vector.h"
18#include "BLI_rect.h"
19#include "BLI_utildefines.h"
20
21#include "BKE_curve.hh"
22#include "BKE_curveprofile.h"
23
24#include "BLO_read_write.hh"
25
27#define PROF_TABLE_MAX 512
28
29/* -------------------------------------------------------------------- */
32
34{
35 CurveProfile *profile = MEM_callocN<CurveProfile>(__func__);
36
38 profile->preset = preset;
40 BKE_curveprofile_update(profile, 0);
41
42 return profile;
43}
44
46{
47 MEM_SAFE_FREE(profile->path);
48 MEM_SAFE_FREE(profile->table);
49 MEM_SAFE_FREE(profile->segments);
50}
51
53{
54 if (profile) {
56 MEM_freeN(profile);
57 }
58}
59
61{
62 *target = *profile;
63
64 target->path = (CurveProfilePoint *)MEM_dupallocN(profile->path);
65 target->table = (CurveProfilePoint *)MEM_dupallocN(profile->table);
66 target->segments = (CurveProfilePoint *)MEM_dupallocN(profile->segments);
67
68 /* Update the reference the points have to the profile. */
69 for (int i = 0; i < target->path_len; i++) {
70 target->path[i].profile = target;
71 }
72}
73
75{
76 if (profile) {
77 CurveProfile *new_prdgt = (CurveProfile *)MEM_dupallocN(profile);
78 BKE_curveprofile_copy_data(new_prdgt, profile);
79 return new_prdgt;
80 }
81 return nullptr;
82}
83
85{
86 BLO_write_struct(writer, CurveProfile, profile);
87 BLO_write_struct_array(writer, CurveProfilePoint, profile->path_len, profile->path);
88}
89
91{
92 BLO_read_struct_array(reader, CurveProfilePoint, profile->path_len, &profile->path);
93 profile->table = nullptr;
94 profile->segments = nullptr;
95
96 /* Reset the points' pointers to the profile. */
97 for (int i = 0; i < profile->path_len; i++) {
98 profile->path[i].profile = profile;
99 }
100
101 BKE_curveprofile_init(profile, profile->segments_len);
102}
103
105
106/* -------------------------------------------------------------------- */
109
111 const bool handle_1,
112 const bool snap,
113 const float delta[2])
114{
115 short handle_type = (handle_1) ? point->h1 : point->h2;
116 float *handle_location = (handle_1) ? &point->h1_loc[0] : &point->h2_loc[0];
117
118 float start_position[2];
119 copy_v2_v2(start_position, handle_location);
120
121 /* Don't move the handle if it's not a free handle type. */
122 if (!ELEM(handle_type, HD_FREE, HD_ALIGN)) {
123 return false;
124 }
125
126 /* Move the handle. */
127 handle_location[0] += delta ? delta[0] : 0.0f;
128 handle_location[1] += delta ? delta[1] : 0.0f;
129 if (snap) {
130 handle_location[0] = 0.125f * roundf(8.0f * handle_location[0]);
131 handle_location[1] = 0.125f * roundf(8.0f * handle_location[1]);
132 }
133
134 /* Move the other handle if they are aligned. */
135 if (handle_type == HD_ALIGN) {
136 short other_handle_type = (handle_1) ? point->h2 : point->h1;
137 if (other_handle_type == HD_ALIGN) {
138 float *other_handle_location = (handle_1) ? &point->h2_loc[0] : &point->h1_loc[0];
139 other_handle_location[0] = 2.0f * point->x - handle_location[0];
140 other_handle_location[1] = 2.0f * point->y - handle_location[1];
141 }
142 }
143
144 if (!equals_v2v2(handle_location, start_position)) {
145 return true;
146 }
147 return false;
148}
149
151 CurveProfilePoint *point,
152 const bool snap,
153 const float delta[2])
154{
155 /* Don't move the final point. */
156 if (point == &profile->path[profile->path_len - 1]) {
157 return false;
158 }
159 /* Don't move the first point. */
160 if (point == profile->path) {
161 return false;
162 }
163 float origx = point->x;
164 float origy = point->y;
165
166 point->x += delta[0];
167 point->y += delta[1];
168 if (snap) {
169 point->x = 0.125f * roundf(8.0f * point->x);
170 point->y = 0.125f * roundf(8.0f * point->y);
171 }
172
173 /* Clip here instead to test clipping here to stop handles from moving too. */
174 if (profile->flag & PROF_USE_CLIP) {
175 point->x = max_ff(point->x, profile->clip_rect.xmin);
176 point->x = min_ff(point->x, profile->clip_rect.xmax);
177 point->y = max_ff(point->y, profile->clip_rect.ymin);
178 point->y = min_ff(point->y, profile->clip_rect.ymax);
179 }
180
181 /* Also move free handles even when they aren't selected. */
182 if (ELEM(point->h1, HD_FREE, HD_ALIGN)) {
183 point->h1_loc[0] += point->x - origx;
184 point->h1_loc[1] += point->y - origy;
185 }
186 if (ELEM(point->h2, HD_FREE, HD_ALIGN)) {
187 point->h2_loc[0] += point->x - origx;
188 point->h2_loc[1] += point->y - origy;
189 }
190
191 if (point->x != origx || point->y != origy) {
192 return true;
193 }
194 return false;
195}
196
198{
199 /* Must have 2 points minimum. */
200 if (profile->path_len <= 2) {
201 return false;
202 }
203
204 /* Input point must be within the array. */
205 if (!(point > profile->path && point < profile->path + profile->path_len)) {
206 return false;
207 }
208
210 __func__);
211
212 int i_delete = int(point - profile->path);
213 BLI_assert(i_delete > 0);
214
215 /* Copy the before and after the deleted point. */
216 memcpy(new_path, profile->path, sizeof(CurveProfilePoint) * i_delete);
217 memcpy(new_path + i_delete,
218 profile->path + i_delete + 1,
219 sizeof(CurveProfilePoint) * (profile->path_len - i_delete - 1));
220
221 MEM_freeN(profile->path);
222 profile->path = new_path;
223 profile->path_len -= 1;
224 return true;
225}
226
228{
229 /* Copy every point without the flag into the new path. */
231 __func__);
232
233 /* Build the new list without any of the points with the flag. Keep the first and last points. */
234 int i_new = 1;
235 int i_old = 1;
236 int n_removed = 0;
237 new_path[0] = profile->path[0];
238 for (; i_old < profile->path_len - 1; i_old++) {
239 if (!(profile->path[i_old].flag & flag)) {
240 new_path[i_new] = profile->path[i_old];
241 i_new++;
242 }
243 else {
244 n_removed++;
245 }
246 }
247 new_path[i_new] = profile->path[i_old];
248
249 MEM_freeN(profile->path);
250 profile->path = new_path;
251 profile->path_len -= n_removed;
252}
253
257static void point_init(CurveProfilePoint *point, float x, float y, short flag, char h1, char h2)
258{
259 point->x = x;
260 point->y = y;
261 point->flag = flag;
262 point->h1 = h1;
263 point->h2 = h2;
264}
265
267{
268 const float new_loc[2] = {x, y};
269
270 /* Don't add more control points than the maximum size of the higher resolution table. */
271 if (profile->path_len == PROF_TABLE_MAX - 1) {
272 return nullptr;
273 }
274
275 /* Find the index at the line segment that's closest to the new position. */
276 float min_distance = FLT_MAX;
277 int i_insert = 0;
278 for (int i = 0; i < profile->path_len - 1; i++) {
279 const float loc1[2] = {profile->path[i].x, profile->path[i].y};
280 const float loc2[2] = {profile->path[i + 1].x, profile->path[i + 1].y};
281
282 float distance = dist_squared_to_line_segment_v2(new_loc, loc1, loc2);
283 if (distance < min_distance) {
284 min_distance = distance;
285 i_insert = i + 1;
286 }
287 }
288
289 /* Insert the new point at the location we found and copy all of the old points in as well. */
290 profile->path_len++;
292 __func__);
293 CurveProfilePoint *new_pt = nullptr;
294 for (int i_new = 0, i_old = 0; i_new < profile->path_len; i_new++) {
295 if (i_new != i_insert) {
296 /* Insert old points. */
297 new_path[i_new] = profile->path[i_old];
298 new_path[i_new].flag &= ~PROF_SELECT; /* Deselect old points. */
299 i_old++;
300 }
301 else {
302 /* Insert new point. */
303 /* Set handles of new point based on its neighbors. */
304 char new_handle_type = (new_path[i_new - 1].h2 == HD_VECT &&
305 profile->path[i_insert].h1 == HD_VECT) ?
306 HD_VECT :
307 HD_AUTO;
308 point_init(&new_path[i_new], x, y, PROF_SELECT, new_handle_type, new_handle_type);
309 new_pt = &new_path[i_new];
310 /* Give new point a reference to the profile. */
311 new_pt->profile = profile;
312 }
313 }
314
315 /* Free the old path and use the new one. */
316 MEM_freeN(profile->path);
317 profile->path = new_path;
318 return new_pt;
319}
320
321void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2)
322{
323 for (int i = 0; i < profile->path_len; i++) {
325 profile->path[i].h1 = type_1;
326 profile->path[i].h2 = type_2;
327
328 if (type_1 == HD_ALIGN && type_2 == HD_ALIGN) {
329 /* Align the handles. */
330 BKE_curveprofile_move_handle(&profile->path[i], true, false, nullptr);
331 }
332 }
333 }
334}
335
337{
338 CurveProfilePoint new_point = *point;
339 point_init(&new_point, point->y, point->x, point->flag, point->h2, point->h1);
340 return new_point;
341}
342
344{
345 /* When there are only two points, reversing shouldn't do anything. */
346 if (profile->path_len == 2) {
347 return;
348 }
350 __func__);
351 /* Mirror the new points across the y = x line */
352 for (int i = 0; i < profile->path_len; i++) {
353 int i_reversed = profile->path_len - i - 1;
354 BLI_assert(i_reversed >= 0);
355 new_path[i_reversed] = mirror_point(&profile->path[i]);
356 new_path[i_reversed].profile = profile;
357
358 /* Mirror free handles, they can't be recalculated. */
359 if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
360 new_path[i_reversed].h1_loc[0] = profile->path[i].h2_loc[1];
361 new_path[i_reversed].h1_loc[1] = profile->path[i].h2_loc[0];
362 }
363 if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
364 new_path[i_reversed].h2_loc[0] = profile->path[i].h1_loc[1];
365 new_path[i_reversed].h2_loc[1] = profile->path[i].h1_loc[0];
366 }
367 }
368
369 /* Free the old points and use the new ones */
370 MEM_freeN(profile->path);
371 profile->path = new_path;
372}
373
378{
379 int n = profile->path_len;
380
381 point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
382 point_init(&profile->path[1], 1.0f, 0.5f, 0, HD_VECT, HD_VECT);
383 for (int i = 1; i < n - 2; i++) {
384 const float x = 1.0f - (0.5f * (1.0f - cosf(float(i / float(n - 3)) * M_PI_2)));
385 const float y = 0.5f + 0.5f * sinf(float((i / float(n - 3)) * M_PI_2));
386 point_init(&profile->path[i], x, y, 0, HD_AUTO, HD_AUTO);
387 }
388 point_init(&profile->path[n - 2], 0.5f, 1.0f, 0, HD_VECT, HD_VECT);
389 point_init(&profile->path[n - 1], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
390}
391
397{
398 int n = profile->path_len;
399
400 /* Special case for two points to avoid dividing by zero later. */
401 if (n == 2) {
402 point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
403 point_init(&profile->path[0], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
404 return;
405 }
406
407 float n_steps_x = (n % 2 == 0) ? n : (n - 1);
408 float n_steps_y = (n % 2 == 0) ? (n - 2) : (n - 1);
409
410 for (int i = 0; i < n; i++) {
411 int step_x = (i + 1) / 2;
412 int step_y = i / 2;
413 const float x = 1.0f - (float(2 * step_x) / n_steps_x);
414 const float y = float(2 * step_y) / n_steps_y;
415 point_init(&profile->path[i], x, y, 0, HD_VECT, HD_VECT);
416 }
417}
418
420{
421 profile->view_rect = profile->clip_rect;
422}
423
425{
426 MEM_SAFE_FREE(profile->path);
427
428 eCurveProfilePresets preset = static_cast<eCurveProfilePresets>(profile->preset);
429 switch (preset) {
430 case PROF_PRESET_LINE:
431 profile->path_len = 2;
432 break;
434 /* Use a dynamic number of control points for the widget's profile. */
435 if (profile->segments_len < 4) {
436 /* But always use enough points to at least build the support points. */
437 profile->path_len = 5;
438 }
439 else {
440 profile->path_len = profile->segments_len + 1;
441 }
442 break;
444 profile->path_len = 13;
445 break;
447 profile->path_len = 11;
448 break;
450 /* Also use dynamic number of control points based on the set number of segments. */
451 if (profile->segments_len == 0) {
452 /* totsegments hasn't been set-- use the number of control points for 8 steps. */
453 profile->path_len = 17;
454 }
455 else {
456 profile->path_len = profile->segments_len + 1;
457 }
458 break;
459 }
460
461 profile->path = MEM_calloc_arrayN<CurveProfilePoint>(profile->path_len, __func__);
462
463 switch (preset) {
464 case PROF_PRESET_LINE:
465 point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_AUTO, HD_AUTO);
466 point_init(&profile->path[1], 0.0f, 1.0f, 0, HD_AUTO, HD_AUTO);
467 break;
470 break;
472 point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
473 point_init(&profile->path[1], 1.0f, 0.125f, 0, HD_VECT, HD_VECT);
474 point_init(&profile->path[2], 0.92f, 0.16f, 0, HD_AUTO, HD_AUTO);
475 point_init(&profile->path[3], 0.875f, 0.25f, 0, HD_VECT, HD_VECT);
476 point_init(&profile->path[4], 0.8f, 0.25f, 0, HD_VECT, HD_VECT);
477 point_init(&profile->path[5], 0.733f, 0.433f, 0, HD_AUTO, HD_AUTO);
478 point_init(&profile->path[6], 0.582f, 0.522f, 0, HD_AUTO, HD_AUTO);
479 point_init(&profile->path[7], 0.4f, 0.6f, 0, HD_AUTO, HD_AUTO);
480 point_init(&profile->path[8], 0.289f, 0.727f, 0, HD_AUTO, HD_AUTO);
481 point_init(&profile->path[9], 0.25f, 0.925f, 0, HD_VECT, HD_VECT);
482 point_init(&profile->path[10], 0.175f, 0.925f, 0, HD_VECT, HD_VECT);
483 point_init(&profile->path[11], 0.175f, 1.0f, 0, HD_VECT, HD_VECT);
484 point_init(&profile->path[12], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
485 break;
487 point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
488 point_init(&profile->path[1], 1.0f, 0.25f, 0, HD_VECT, HD_VECT);
489 point_init(&profile->path[2], 0.75f, 0.25f, 0, HD_VECT, HD_VECT);
490 point_init(&profile->path[3], 0.75f, 0.325f, 0, HD_VECT, HD_VECT);
491 point_init(&profile->path[4], 0.925f, 0.4f, 0, HD_AUTO, HD_AUTO);
492 point_init(&profile->path[5], 0.975f, 0.5f, 0, HD_AUTO, HD_AUTO);
493 point_init(&profile->path[6], 0.94f, 0.65f, 0, HD_AUTO, HD_AUTO);
494 point_init(&profile->path[7], 0.85f, 0.75f, 0, HD_AUTO, HD_AUTO);
495 point_init(&profile->path[8], 0.75f, 0.875f, 0, HD_AUTO, HD_AUTO);
496 point_init(&profile->path[9], 0.7f, 1.0f, 0, HD_VECT, HD_VECT);
497 point_init(&profile->path[10], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
498 break;
501 break;
502 }
503
504 profile->flag &= ~PROF_DIRTY_PRESET;
505
506 /* Ensure each point has a reference to the profile. */
507 for (int i = 0; i < profile->path_len; i++) {
508 profile->path[i].profile = profile;
509 }
510
511 MEM_SAFE_FREE(profile->table);
512 profile->table = nullptr;
513}
514
516
517/* -------------------------------------------------------------------- */
520
522{
524 const int resolution = 16;
525
526 /* Make sure there is always one sample, even if there are no control points. */
527 return std::clamp((profile->path_len - 1) * resolution + 1, 1, PROF_TABLE_MAX);
528}
529
534static bool is_curved_edge(CurveProfilePoint *path, int i)
535{
536 return (path[i].h2 != HD_VECT || path[i + 1].h1 != HD_VECT);
537}
538
544 const CurveProfilePoint *prev,
545 const CurveProfilePoint *next)
546{
547 if (point->h1 == HD_FREE && point->h2 == HD_FREE) {
548 return;
549 }
550
551 float *point_loc = &point->x;
552
553 float pt[2];
554 const float *prev_loc, *next_loc;
555 if (prev == nullptr) {
556 next_loc = &next->x;
557 pt[0] = 2.0f * point_loc[0] - next_loc[0];
558 pt[1] = 2.0f * point_loc[1] - next_loc[1];
559 prev_loc = pt;
560 }
561 else {
562 prev_loc = &prev->x;
563 }
564
565 if (next == nullptr) {
566 prev_loc = &prev->x;
567 pt[0] = 2.0f * point_loc[0] - prev_loc[0];
568 pt[1] = 2.0f * point_loc[1] - prev_loc[1];
569 next_loc = pt;
570 }
571 else {
572 next_loc = &next->x;
573 }
574
575 float dvec_a[2], dvec_b[2];
576 sub_v2_v2v2(dvec_a, point_loc, prev_loc);
577 sub_v2_v2v2(dvec_b, next_loc, point_loc);
578
579 float len_a = len_v2(dvec_a);
580 float len_b = len_v2(dvec_b);
581 if (len_a == 0.0f) {
582 len_a = 1.0f;
583 }
584 if (len_b == 0.0f) {
585 len_b = 1.0f;
586 }
587
588 if (point->h1 == HD_AUTO || point->h2 == HD_AUTO) {
589 float tvec[2];
590 tvec[0] = dvec_b[0] / len_b + dvec_a[0] / len_a;
591 tvec[1] = dvec_b[1] / len_b + dvec_a[1] / len_a;
592
593 float len = len_v2(tvec) * 2.5614f;
594 if (len != 0.0f) {
595 if (point->h1 == HD_AUTO) {
596 len_a /= len;
597 madd_v2_v2v2fl(point->h1_loc, point_loc, tvec, -len_a);
598 }
599 if (point->h2 == HD_AUTO) {
600 len_b /= len;
601 madd_v2_v2v2fl(point->h2_loc, point_loc, tvec, len_b);
602 }
603 }
604 }
605
606 if (point->h1 == HD_VECT) {
607 madd_v2_v2v2fl(point->h1_loc, point_loc, dvec_a, -1.0f / 3.0f);
608 }
609 if (point->h2 == HD_VECT) {
610 madd_v2_v2v2fl(point->h2_loc, point_loc, dvec_b, 1.0f / 3.0f);
611 }
612}
613
614static void calculate_path_handles(CurveProfilePoint *path, int path_len)
615{
616 point_calculate_handle(&path[0], nullptr, &path[1]);
617 for (int i = 1; i < path_len - 1; i++) {
618 point_calculate_handle(&path[i], &path[i - 1], &path[i + 1]);
619 }
620 point_calculate_handle(&path[path_len - 1], &path[path_len - 2], nullptr);
621}
622
629static float bezt_edge_handle_angle(const CurveProfilePoint *path, int i_edge)
630{
631 /* Find the direction of the handles that define this edge along the direction of the path. */
632 float start_handle_direction[2], end_handle_direction[2];
633 /* Handle 2 - point location. */
634 sub_v2_v2v2(start_handle_direction, path[i_edge].h2_loc, &path[i_edge].x);
635 /* Point location - handle 1. */
636 sub_v2_v2v2(end_handle_direction, &path[i_edge + 1].x, path[i_edge + 1].h1_loc);
637
638 return angle_v2v2(start_handle_direction, end_handle_direction);
639}
640
648
652static int sort_points_curvature(const void *in_a, const void *in_b)
653{
654 const CurvatureSortPoint *a = (const CurvatureSortPoint *)in_a;
655 const CurvatureSortPoint *b = (const CurvatureSortPoint *)in_b;
656
657 if (a->point_curvature > b->point_curvature) {
658 return 0;
659 }
660
661 return 1;
662}
663
677static void create_samples(CurveProfile *profile,
678 int n_segments,
679 bool sample_straight_edges,
680 CurveProfilePoint *r_samples)
681{
682 CurveProfilePoint *path = profile->path;
683 int totpoints = profile->path_len;
684 BLI_assert(n_segments > 0);
685
686 int totedges = totpoints - 1;
687
688 calculate_path_handles(path, totpoints);
689
690 /* Create a list of edge indices with the most curved at the start, least curved at the end. */
691 CurvatureSortPoint *curve_sorted = MEM_calloc_arrayN<CurvatureSortPoint>(totedges, __func__);
692 for (int i = 0; i < totedges; i++) {
693 curve_sorted[i].point_index = i;
694 /* Calculate the curvature of each edge once for use when sorting for curvature. */
695 curve_sorted[i].point_curvature = bezt_edge_handle_angle(path, i);
696 }
697 qsort(curve_sorted, totedges, sizeof(CurvatureSortPoint), sort_points_curvature);
698
699 /* Assign the number of sampled points for each edge. */
700 int16_t *n_samples = MEM_calloc_arrayN<int16_t>(totedges, "samples numbers");
701 int n_added = 0;
702 int n_left;
703 if (n_segments >= totedges) {
704 if (sample_straight_edges) {
705 /* Assign an even number to each edge if it's possible, then add the remainder of sampled
706 * points starting with the most curved edges. */
707 int n_common = n_segments / totedges;
708 n_left = n_segments % totedges;
709
710 /* Assign the points that fill fit evenly to the edges. */
711 if (n_common > 0) {
712 BLI_assert(n_common < INT16_MAX);
713 for (int i = 0; i < totedges; i++) {
714 n_samples[i] = n_common;
715 n_added += n_common;
716 }
717 }
718 }
719 else {
720 /* Count the number of curved edges */
721 int n_curved_edges = 0;
722 for (int i = 0; i < totedges; i++) {
723 if (is_curved_edge(path, i)) {
724 n_curved_edges++;
725 }
726 }
727 /* Just sample all of the edges if there are no curved edges. */
728 n_curved_edges = (n_curved_edges == 0) ? totedges : n_curved_edges;
729
730 /* Give all of the curved edges the same number of points and straight edges one point. */
731 n_left = n_segments - (totedges - n_curved_edges); /* Left after 1 for each straight edge. */
732 int n_common = n_left / n_curved_edges; /* Number assigned to all curved edges */
733 if (n_common > 0) {
734 for (int i = 0; i < totedges; i++) {
735 /* Add the common number if it's a curved edge or if edges are curved. */
736 if (is_curved_edge(path, i) || n_curved_edges == totedges) {
737 BLI_assert(n_common + n_samples[i] < INT16_MAX);
738 n_samples[i] += n_common;
739 n_added += n_common;
740 }
741 else {
742 n_samples[i] = 1;
743 n_added++;
744 }
745 }
746 }
747 n_left -= n_common * n_curved_edges;
748 }
749 }
750 else {
751 /* Not enough segments to give one to each edge, so just give them to the most curved edges. */
752 n_left = n_segments;
753 }
754 /* Assign the remainder of the points that couldn't be spread out evenly. */
755 BLI_assert(n_left < totedges);
756 for (int i = 0; i < n_left; i++) {
757 BLI_assert(n_samples[curve_sorted[i].point_index] < INT16_MAX);
758 n_samples[curve_sorted[i].point_index]++;
759 n_added++;
760 }
761
762 BLI_assert(n_added == n_segments); /* n_added is just used for this assert, could remove it. */
763 UNUSED_VARS_NDEBUG(n_added);
764
765 /* Sample the points and add them to the locations table. */
766 for (int i_sample = 0, i = 0; i < totedges; i++) {
767 if (n_samples[i] > 0) {
768 /* Carry over the handle types from the control point to its first corresponding sample. */
769 r_samples[i_sample].h1 = path[i].h1;
770 r_samples[i_sample].h2 = path[i].h2;
771 /* All extra sample points for this control point get "auto" handles. */
772 for (int j = i_sample + 1; j < i_sample + n_samples[i]; j++) {
773 r_samples[j].flag = 0;
774 r_samples[j].h1 = HD_AUTO;
775 r_samples[j].h2 = HD_AUTO;
776 BLI_assert(j < n_segments);
777 }
778
779 /* Sample from the bezier points. X then Y values. */
781 path[i].h2_loc[0],
782 path[i + 1].h1_loc[0],
783 path[i + 1].x,
784 &r_samples[i_sample].x,
785 n_samples[i],
786 sizeof(CurveProfilePoint));
788 path[i].h2_loc[1],
789 path[i + 1].h1_loc[1],
790 path[i + 1].y,
791 &r_samples[i_sample].y,
792 n_samples[i],
793 sizeof(CurveProfilePoint));
794 }
795 i_sample += n_samples[i]; /* Add the next set of points after the ones we just added. */
796 BLI_assert(i_sample <= n_segments);
797 }
798
799 MEM_freeN(curve_sorted);
800 MEM_freeN(n_samples);
801}
802
804{
805 profile->flag = PROF_USE_CLIP;
806
807 BLI_rctf_init(&profile->view_rect, 0.0f, 1.0f, 0.0f, 1.0f);
808 profile->clip_rect = profile->view_rect;
809
810 profile->path_len = 2;
811 profile->path = MEM_calloc_arrayN<CurveProfilePoint>(2, __func__);
812
813 profile->path[0].x = 1.0f;
814 profile->path[0].y = 0.0f;
815 profile->path[0].profile = profile;
816 profile->path[1].x = 1.0f;
817 profile->path[1].y = 1.0f;
818 profile->path[1].profile = profile;
819
820 profile->changed_timestamp = 0;
821}
822
823void BKE_curveprofile_init(CurveProfile *profile, short segments_len)
824{
825 if (segments_len != profile->segments_len) {
826 profile->flag |= PROF_DIRTY_PRESET;
827 }
828 profile->segments_len = segments_len;
829
830 /* Calculate the higher resolution / segments tables for display and evaluation. */
832}
833
841{
843
844 return len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
845}
846
852static float curveprofile_total_length(const CurveProfile *profile)
853{
854 float total_length = 0;
855 for (int i = 0; i < BKE_curveprofile_table_size(profile) - 1; i++) {
856 total_length += len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
857 }
858 return total_length;
859}
860
870 int n_segments,
871 CurveProfilePoint *r_samples)
872{
873 const float total_length = curveprofile_total_length(profile);
874 const float segment_length = total_length / n_segments;
875 float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0);
876 float distance_to_previous_table_point = 0.0f;
877 int i_table = 0;
878
879 /* Set the location for the first point. */
880 r_samples[0].x = profile->table[0].x;
881 r_samples[0].y = profile->table[0].y;
882
883 /* Travel along the path, recording the locations of segments as we pass them. */
884 float segment_left = segment_length;
885 for (int i = 1; i < n_segments; i++) {
886 /* Travel over all of the points that fit inside this segment. */
887 while (distance_to_next_table_point < segment_left) {
888 segment_left -= distance_to_next_table_point;
889 i_table++;
890 distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, i_table);
891 distance_to_previous_table_point = 0.0f;
892 }
893 /* We're at the last table point that fits inside the current segment, use interpolation. */
894 float factor = (distance_to_previous_table_point + segment_left) /
895 (distance_to_previous_table_point + distance_to_next_table_point);
896 r_samples[i].x = interpf(profile->table[i_table + 1].x, profile->table[i_table].x, factor);
897 r_samples[i].y = interpf(profile->table[i_table + 1].y, profile->table[i_table].y, factor);
898 BLI_assert(factor <= 1.0f && factor >= 0.0f);
899#ifdef DEBUG_CURVEPROFILE_EVALUATE
900 printf("segment_left: %.3f\n", segment_left);
901 printf("i_table: %d\n", i_table);
902 printf("distance_to_previous_table_point: %.3f\n", distance_to_previous_table_point);
903 printf("distance_to_next_table_point: %.3f\n", distance_to_next_table_point);
904 printf("Interpolating with factor %.3f from (%.3f, %.3f) to (%.3f, %.3f)\n\n",
905 factor,
906 profile->table[i_table].x,
907 profile->table[i_table].y,
908 profile->table[i_table + 1].x,
909 profile->table[i_table + 1].y);
910#endif
911
912 /* We sampled in between this table point and the next, so the next travel step is smaller. */
913 distance_to_next_table_point -= segment_left;
914 distance_to_previous_table_point += segment_left;
915 segment_left = segment_length;
916 }
917}
918
924{
925 int n_samples = BKE_curveprofile_table_size(profile);
926 CurveProfilePoint *new_table = MEM_calloc_arrayN<CurveProfilePoint>(size_t(n_samples) + 1,
927 __func__);
928
929 if (n_samples > 1) {
930 create_samples(profile, n_samples - 1, false, new_table);
931 }
932
933 /* Manually add last point at the end of the profile */
934 new_table[n_samples - 1].x = 0.0f;
935 new_table[n_samples - 1].y = 1.0f;
936
937 MEM_SAFE_FREE(profile->table);
938 profile->table = new_table;
939}
940
946{
947 int n_samples = profile->segments_len;
948 if (n_samples <= 0) {
949 return;
950 }
951 CurveProfilePoint *new_table = MEM_calloc_arrayN<CurveProfilePoint>(size_t(n_samples) + 1,
952 __func__);
953
954 if (profile->flag & PROF_SAMPLE_EVEN_LENGTHS) {
955 /* Even length sampling incompatible with only straight edge sampling for now. */
956 create_samples_even_spacing(profile, n_samples, new_table);
957 }
958 else {
959 create_samples(profile, n_samples, profile->flag & PROF_SAMPLE_STRAIGHT_EDGES, new_table);
960 }
961
962 MEM_SAFE_FREE(profile->segments);
963 profile->segments = new_table;
964}
965
966void BKE_curveprofile_update(CurveProfile *profile, const int update_flags)
967{
968 CurveProfilePoint *points = profile->path;
969 const rctf *clipr = &profile->clip_rect;
970
971 profile->changed_timestamp++;
972
973 /* Clamp with the clipping rect in case something got past. */
974 if (profile->flag & PROF_USE_CLIP) {
975 /* Move points inside the clip rectangle. */
976 if (update_flags & PROF_UPDATE_CLIP) {
977 for (int i = 0; i < profile->path_len; i++) {
978 points[i].x = clamp_f(points[i].x, clipr->xmin, clipr->xmax);
979 points[i].y = clamp_f(points[i].y, clipr->ymin, clipr->ymax);
980
981 /* Extra sanity assert to make sure the points have the right profile pointer. */
982 BLI_assert(points[i].profile == profile);
983 }
984 }
985 /* Ensure zoom-level respects clipping. */
986 if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) {
987 profile->view_rect.xmin = profile->clip_rect.xmin;
988 profile->view_rect.xmax = profile->clip_rect.xmax;
989 }
990 if (BLI_rctf_size_y(&profile->view_rect) > BLI_rctf_size_y(&profile->clip_rect)) {
991 profile->view_rect.ymin = profile->clip_rect.ymin;
992 profile->view_rect.ymax = profile->clip_rect.ymax;
993 }
994 }
995
996 /* Remove doubles with a threshold set at 1% of default range. */
997 float thresh = pow2f(0.01f * BLI_rctf_size_x(clipr));
998 if (update_flags & PROF_UPDATE_REMOVE_DOUBLES && profile->path_len > 2) {
999 for (int i = 0; i < profile->path_len - 1; i++) {
1000 if (len_squared_v2v2(&points[i].x, &points[i + 1].x) < thresh) {
1001 if (i == 0) {
1002 BKE_curveprofile_remove_point(profile, &points[1]);
1003 }
1004 else {
1005 BKE_curveprofile_remove_point(profile, &points[i]);
1006 }
1007 break; /* Assumes 1 deletion per update call is ok. */
1008 }
1009 }
1010 }
1011
1012 /* Create the high resolution table for drawing and some evaluation functions. */
1013 curveprofile_make_table(profile);
1014
1015 /* Store a table of samples for the segment locations for a preview and the table's user. */
1016 if (profile->segments_len > 0) {
1018 }
1019}
1020
1022 float length_portion,
1023 float *x_out,
1024 float *y_out)
1025{
1026 const float total_length = curveprofile_total_length(profile);
1027 const float requested_length = length_portion * total_length;
1028
1029 /* Find the last point along the path with a lower length portion than the input. */
1030 int i = 0;
1031 float length_traveled = 0.0f;
1032 while (length_traveled < requested_length) {
1033 /* Check if we reached the last point before the final one. */
1034 if (i == BKE_curveprofile_table_size(profile) - 2) {
1035 break;
1036 }
1037 float new_length = curveprofile_distance_to_next_table_point(profile, i);
1038 if (length_traveled + new_length >= requested_length) {
1039 break;
1040 }
1041 length_traveled += new_length;
1042 i++;
1043 }
1044
1045 /* Now travel the remaining distance of length portion down the path to the next point and
1046 * find the location where we stop. */
1047 float distance_to_next_point = curveprofile_distance_to_next_table_point(profile, i);
1048 float lerp_factor = (requested_length - length_traveled) / distance_to_next_point;
1049
1050#ifdef DEBUG_CURVEPROFILE_EVALUATE
1051 printf("CURVEPROFILE EVALUATE\n");
1052 printf(" length portion input: %f\n", double(length_portion));
1053 printf(" requested path length: %f\n", double(requested_length));
1054 printf(" distance to next point: %f\n", double(distance_to_next_point));
1055 printf(" length traveled: %f\n", double(length_traveled));
1056 printf(" lerp-factor: %f\n", double(lerp_factor));
1057 printf(" ith point (%f, %f)\n", double(profile->path[i].x), double(profile->path[i].y));
1058 printf(" next point(%f, %f)\n", double(profile->path[i + 1].x), double(profile->path[i + 1].y));
1059#endif
1060
1061 *x_out = interpf(profile->table[i].x, profile->table[i + 1].x, lerp_factor);
1062 *y_out = interpf(profile->table[i].y, profile->table[i + 1].y, lerp_factor);
1063}
1064
void BKE_curve_forward_diff_bezier(float q0, float q1, float q2, float q3, float *p, int it, int stride)
Definition curve.cc:1669
@ PROF_UPDATE_CLIP
@ PROF_UPDATE_REMOVE_DOUBLES
@ PROF_UPDATE_NONE
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE float max_ff(float a, float b)
MINLINE float pow2f(float x)
MINLINE float clamp_f(float value, float min, float max)
MINLINE float min_ff(float a, float b)
MINLINE float interpf(float target, float origin, float t)
#define M_PI_2
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:291
MINLINE float len_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float len_squared_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void madd_v2_v2v2fl(float r[2], const float a[2], const float b[2], float f)
float angle_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float len_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE bool equals_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
void BLI_rctf_init(struct rctf *rect, float xmin, float xmax, float ymin, float ymax)
Definition rct.cc:404
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
#define BLO_write_struct(writer, struct_name, data_ptr)
#define BLO_write_struct_array(writer, struct_name, array_size, data_ptr)
#define BLO_read_struct_array(reader, struct_name, array_size, ptr_p)
@ HD_VECT
@ HD_FREE
@ HD_AUTO
@ HD_ALIGN
@ PROF_PRESET_CROWN
@ PROF_PRESET_LINE
@ PROF_PRESET_CORNICE
@ PROF_PRESET_SUPPORTS
@ PROF_PRESET_STEPS
@ PROF_DIRTY_PRESET
@ PROF_SAMPLE_EVEN_LENGTHS
@ PROF_SAMPLE_STRAIGHT_EDGES
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
nullptr float
static float bezt_edge_handle_angle(const CurveProfilePoint *path, int i_edge)
static float curveprofile_total_length(const CurveProfile *profile)
void BKE_curveprofile_init(CurveProfile *profile, short segments_len)
static void create_samples_even_spacing(CurveProfile *profile, int n_segments, CurveProfilePoint *r_samples)
void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile, float length_portion, float *x_out, float *y_out)
bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *point)
CurveProfilePoint * BKE_curveprofile_insert(CurveProfile *profile, float x, float y)
void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profile)
void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2)
#define PROF_TABLE_MAX
static void curveprofile_build_steps(CurveProfile *profile)
bool BKE_curveprofile_move_handle(CurveProfilePoint *point, const bool handle_1, const bool snap, const float delta[2])
bool BKE_curveprofile_move_point(CurveProfile *profile, CurveProfilePoint *point, const bool snap, const float delta[2])
static void curveprofile_build_supports(CurveProfile *profile)
static void curveprofile_make_table(CurveProfile *profile)
void BKE_curveprofile_reset(CurveProfile *profile)
static void calculate_path_handles(CurveProfilePoint *path, int path_len)
static CurveProfilePoint mirror_point(const CurveProfilePoint *point)
static bool is_curved_edge(CurveProfilePoint *path, int i)
int BKE_curveprofile_table_size(const CurveProfile *profile)
void BKE_curveprofile_free_data(CurveProfile *profile)
void BKE_curveprofile_blend_write(BlendWriter *writer, const CurveProfile *profile)
void BKE_curveprofile_blend_read(BlendDataReader *reader, CurveProfile *profile)
void BKE_curveprofile_remove_by_flag(CurveProfile *profile, const short flag)
void BKE_curveprofile_reset_view(CurveProfile *profile)
static void point_calculate_handle(CurveProfilePoint *point, const CurveProfilePoint *prev, const CurveProfilePoint *next)
CurveProfile * BKE_curveprofile_add(eCurveProfilePresets preset)
void BKE_curveprofile_reverse(CurveProfile *profile)
static void point_init(CurveProfilePoint *point, float x, float y, short flag, char h1, char h2)
CurveProfile * BKE_curveprofile_copy(const CurveProfile *profile)
static void create_samples(CurveProfile *profile, int n_segments, bool sample_straight_edges, CurveProfilePoint *r_samples)
void BKE_curveprofile_free(CurveProfile *profile)
static float curveprofile_distance_to_next_table_point(const CurveProfile *profile, int i)
void BKE_curveprofile_update(CurveProfile *profile, const int update_flags)
static int sort_points_curvature(const void *in_a, const void *in_b)
static void curveprofile_make_segments_table(CurveProfile *profile)
void BKE_curveprofile_set_defaults(CurveProfile *profile)
#define roundf(x)
#define INT16_MAX
#define printf(...)
float distance(VecOp< float, D >, VecOp< float, D >) RET
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong * next
vector snap(vector a, vector b)
Definition node_math.h:97
#define sinf
#define cosf
#define FLT_MAX
Definition stdcycles.h:14
struct CurveProfile * profile
CurveProfilePoint * path
CurveProfilePoint * table
CurveProfilePoint * segments
float xmax
float xmin
float ymax
float ymin
i
Definition text_draw.cc:230
uint len
uint8_t flag
Definition wm_window.cc:145