Blender V4.3
gpencil_geom_legacy.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <algorithm>
10#include <cmath>
11#include <cstddef>
12#include <cstdio>
13#include <cstdlib>
14#include <cstring>
15
16#include "MEM_guardedalloc.h"
17
18#include "BLI_array_utils.h"
19#include "BLI_blenlib.h"
20#include "BLI_ghash.h"
21#include "BLI_hash.h"
22#include "BLI_heap.h"
23#include "BLI_math_geom.h"
24#include "BLI_math_matrix.h"
25#include "BLI_math_rotation.h"
26#include "BLI_math_vector.h"
27#include "BLI_math_vector.hh"
29#include "BLI_polyfill_2d.h"
30#include "BLI_span.hh"
31#include "BLI_string_utils.hh"
32
35#include "DNA_material_types.h"
36#include "DNA_mesh_types.h"
37#include "DNA_meshdata_types.h"
38#include "DNA_scene_types.h"
39
40#include "BKE_attribute.hh"
41#include "BKE_deform.hh"
44#include "BKE_gpencil_legacy.h"
45#include "BKE_material.h"
46#include "BKE_object.hh"
47#include "BKE_object_types.hh"
48
50
51using blender::float3;
52using blender::Span;
53
54/* -------------------------------------------------------------------- */
59 const bool use_select,
60 float r_min[3],
61 float r_max[3])
62{
63 if (gps == nullptr) {
64 return false;
65 }
66
67 bool changed = false;
68 if (use_select) {
69 for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
70 if (pt.flag & GP_SPOINT_SELECT) {
71 minmax_v3v3_v3(r_min, r_max, &pt.x);
72 changed = true;
73 }
74 }
75 }
76 else {
77 for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
78 minmax_v3v3_v3(r_min, r_max, &pt.x);
79 changed = true;
80 }
81 }
82
83 return changed;
84}
85
86std::optional<blender::Bounds<blender::float3>> BKE_gpencil_data_minmax(const bGPdata *gpd)
87{
88 bool changed = false;
89
90 float3 min;
91 float3 max;
92 INIT_MINMAX(min, max);
93 LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
94 bGPDframe *gpf = gpl->actframe;
95
96 if (gpf != nullptr) {
97 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
98 changed |= BKE_gpencil_stroke_minmax(gps, false, min, max);
99 }
100 }
101 }
102
103 if (!changed) {
104 return std::nullopt;
105 }
106
108}
109
110void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
111{
112 using namespace blender;
113 const Bounds<float3> bounds = BKE_gpencil_data_minmax(gpd).value_or(Bounds(float3(0)));
114 copy_v3_v3(r_centroid, math::midpoint(bounds.min, bounds.max));
115}
116
122
125/* -------------------------------------------------------------------- */
130 int point_index,
131 float influence,
132 int iterations,
133 const bool smooth_caps,
134 const bool keep_shape,
135 bGPDstroke *r_gps)
136{
137 /* If nothing to do, return early */
138 if (gps->totpoints <= 2 || iterations <= 0) {
139 return false;
140 }
141
142 /* - Overview of the algorithm here and in the following smooth functions:
143 *
144 * The smooth functions return the new attribute in question for a single point.
145 * The result is stored in r_gps->points[point_index], while the data is read from gps.
146 * To get a correct result, duplicate the stroke point data and read from the copy,
147 * while writing to the real stroke. Not doing that will result in acceptable, but
148 * asymmetric results.
149 *
150 * This algorithm works as long as all points are being smoothed. If there is
151 * points that should not get smoothed, use the old repeat smooth pattern with
152 * the parameter "iterations" set to 1 or 2. (2 matches the old algorithm).
153 */
154
155 const bGPDspoint *pt = &gps->points[point_index];
156 const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
157 /* If smooth_caps is false, the caps will not be translated by smoothing. */
158 if (!smooth_caps && !is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
159 copy_v3_v3(&r_gps->points[point_index].x, &pt->x);
160 return true;
161 }
162
163 /* This function uses a binomial kernel, which is the discrete version of gaussian blur.
164 * The weight for a vertex at the relative index point_index is
165 * `w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n`
166 * All weights together sum up to 1
167 * This is equivalent to doing multiple iterations of averaging neighbors,
168 * where n = iterations * 2 and -n/2 <= j <= n/2
169 *
170 * Now the problem is that `nCr(n, j + n/2)` is very hard to compute for `n > 500`, since even
171 * double precision isn't sufficient. A very good robust approximation for n > 20 is
172 * `nCr(n, j + n/2) / 2^n = sqrt(2/(pi*n)) * exp(-2*j*j/n)`
173 *
174 * There is one more problem left: The old smooth algorithm was doing a more aggressive
175 * smooth. To solve that problem, choose a different n/2, which does not match the range and
176 * normalize the weights on finish. This may cause some artifacts at low values.
177 *
178 * keep_shape is a new option to stop the stroke from severely deforming.
179 * It uses different partially negative weights.
180 * w = `2 * (nCr(n, j + n/2) / 2^n) - (nCr(3*n, j + n) / 2^(3*n))`
181 * ~ `2 * sqrt(2/(pi*n)) * exp(-2*j*j/n) - sqrt(2/(pi*3*n)) * exp(-2*j*j/(3*n))`
182 * All weights still sum up to 1.
183 * Note these weights only work because the averaging is done in relative coordinates.
184 */
185 float sco[3] = {0.0f, 0.0f, 0.0f};
186 float tmp[3];
187 const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
188 (iterations * iterations) / 4 + 2 * iterations + 12;
189 double w = keep_shape ? 2.0 : 1.0;
190 double w2 = keep_shape ?
191 (1.0 / M_SQRT3) * exp((2 * iterations * iterations) / double(n_half * 3)) :
192 0.0;
193 double total_w = 0.0;
194 for (int step = iterations; step > 0; step--) {
195 int before = point_index - step;
196 int after = point_index + step;
197 float w_before = float(w - w2);
198 float w_after = float(w - w2);
199
200 if (is_cyclic) {
201 before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
202 after = after % gps->totpoints;
203 }
204 else {
205 if (before < 0) {
206 if (!smooth_caps) {
207 w_before *= -before / float(point_index);
208 }
209 before = 0;
210 }
211 if (after > gps->totpoints - 1) {
212 if (!smooth_caps) {
213 w_after *= (after - (gps->totpoints - 1)) / float(gps->totpoints - 1 - point_index);
214 }
215 after = gps->totpoints - 1;
216 }
217 }
218
219 /* Add both these points in relative coordinates to the weighted average sum. */
220 sub_v3_v3v3(tmp, &gps->points[before].x, &pt->x);
221 madd_v3_v3fl(sco, tmp, w_before);
222 sub_v3_v3v3(tmp, &gps->points[after].x, &pt->x);
223 madd_v3_v3fl(sco, tmp, w_after);
224
225 total_w += w_before;
226 total_w += w_after;
227
228 w *= (n_half + step) / double(n_half + 1 - step);
229 w2 *= (n_half * 3 + step) / double(n_half * 3 + 1 - step);
230 }
231 total_w += w - w2;
232 /* The accumulated weight total_w should be
233 * `~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100`
234 * here, but sometimes not quite. */
235 mul_v3_fl(sco, float(1.0 / total_w));
236 /* Shift back to global coordinates. */
237 add_v3_v3(sco, &pt->x);
238
239 /* Based on influence factor, blend between original and optimal smoothed coordinate. */
240 interp_v3_v3v3(&r_gps->points[point_index].x, &pt->x, sco, influence);
241
242 return true;
243}
244
247/* -------------------------------------------------------------------- */
252 bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
253{
254 /* If nothing to do, return early */
255 if (gps->totpoints <= 2 || iterations <= 0) {
256 return false;
257 }
258
259 /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
260
261 const bGPDspoint *pt = &gps->points[point_index];
262 const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
263 float strength = 0.0f;
264 const int n_half = (iterations * iterations) / 4 + iterations;
265 double w = 1.0;
266 double total_w = 0.0;
267 for (int step = iterations; step > 0; step--) {
268 int before = point_index - step;
269 int after = point_index + step;
270 float w_before = float(w);
271 float w_after = float(w);
272
273 if (is_cyclic) {
274 before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
275 after = after % gps->totpoints;
276 }
277 else {
278 CLAMP_MIN(before, 0);
279 CLAMP_MAX(after, gps->totpoints - 1);
280 }
281
282 /* Add both these points in relative coordinates to the weighted average sum. */
283 strength += w_before * (gps->points[before].strength - pt->strength);
284 strength += w_after * (gps->points[after].strength - pt->strength);
285
286 total_w += w_before;
287 total_w += w_after;
288
289 w *= (n_half + step) / double(n_half + 1 - step);
290 }
291 total_w += w;
292 /* The accumulated weight total_w should be
293 * ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
294 * here, but sometimes not quite. */
295 strength /= total_w;
296
297 /* Based on influence factor, blend between original and optimal smoothed value. */
298 r_gps->points[point_index].strength = pt->strength + strength * influence;
299
300 return true;
301}
302
305/* -------------------------------------------------------------------- */
310 bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
311{
312 /* If nothing to do, return early */
313 if (gps->totpoints <= 2 || iterations <= 0) {
314 return false;
315 }
316
317 /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
318
319 const bGPDspoint *pt = &gps->points[point_index];
320 const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
321 float pressure = 0.0f;
322 const int n_half = (iterations * iterations) / 4 + iterations;
323 double w = 1.0;
324 double total_w = 0.0;
325 for (int step = iterations; step > 0; step--) {
326 int before = point_index - step;
327 int after = point_index + step;
328 float w_before = float(w);
329 float w_after = float(w);
330
331 if (is_cyclic) {
332 before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
333 after = after % gps->totpoints;
334 }
335 else {
336 CLAMP_MIN(before, 0);
337 CLAMP_MAX(after, gps->totpoints - 1);
338 }
339
340 /* Add both these points in relative coordinates to the weighted average sum. */
341 pressure += w_before * (gps->points[before].pressure - pt->pressure);
342 pressure += w_after * (gps->points[after].pressure - pt->pressure);
343
344 total_w += w_before;
345 total_w += w_after;
346
347 w *= (n_half + step) / double(n_half + 1 - step);
348 }
349 total_w += w;
350 /* The accumulated weight total_w should be
351 * ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
352 * here, but sometimes not quite. */
353 pressure /= total_w;
354
355 /* Based on influence factor, blend between original and optimal smoothed value. */
356 r_gps->points[point_index].pressure = pt->pressure + pressure * influence;
357
358 return true;
359}
360
363/* -------------------------------------------------------------------- */
368 bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
369{
370 /* If nothing to do, return early */
371 if (gps->totpoints <= 2 || iterations <= 0) {
372 return false;
373 }
374
375 /* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
376
377 const bGPDspoint *pt = &gps->points[point_index];
378 const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
379
380 /* If don't change the caps. */
381 if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
382 r_gps->points[point_index].uv_rot = pt->uv_rot;
383 r_gps->points[point_index].uv_fac = pt->uv_fac;
384 return true;
385 }
386
387 float uv_rot = 0.0f;
388 float uv_fac = 0.0f;
389 const int n_half = iterations * iterations + iterations;
390 double w = 1.0;
391 double total_w = 0.0;
392 for (int step = iterations; step > 0; step--) {
393 int before = point_index - step;
394 int after = point_index + step;
395 float w_before = float(w);
396 float w_after = float(w);
397
398 if (is_cyclic) {
399 before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
400 after = after % gps->totpoints;
401 }
402 else {
403 if (before < 0) {
404 w_before *= -before / float(point_index);
405 before = 0;
406 }
407 if (after > gps->totpoints - 1) {
408 w_after *= (after - (gps->totpoints - 1)) / float(gps->totpoints - 1 - point_index);
409 after = gps->totpoints - 1;
410 }
411 }
412
413 /* Add both these points in relative coordinates to the weighted average sum. */
414 uv_rot += w_before * (gps->points[before].uv_rot - pt->uv_rot);
415 uv_rot += w_after * (gps->points[after].uv_rot - pt->uv_rot);
416 uv_fac += w_before * (gps->points[before].uv_fac - pt->uv_fac);
417 uv_fac += w_after * (gps->points[after].uv_fac - pt->uv_fac);
418
419 total_w += w_before;
420 total_w += w_after;
421
422 w *= (n_half + step) / double(n_half + 1 - step);
423 }
424 total_w += w;
425 /* The accumulated weight total_w should be
426 * ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
427 * here, but sometimes not quite. */
428 uv_rot /= total_w;
429 uv_fac /= total_w;
430
431 /* Based on influence factor, blend between original and optimal smoothed value. */
432 r_gps->points[point_index].uv_rot = pt->uv_rot + uv_rot * influence;
433 r_gps->points[point_index].uv_fac = pt->uv_fac + uv_fac * influence;
434
435 return true;
436}
437
439 const float influence,
440 const int iterations,
441 const bool smooth_position,
442 const bool smooth_strength,
443 const bool smooth_thickness,
444 const bool smooth_uv,
445 const bool keep_shape,
446 const float *weights)
447{
448 if (influence <= 0 || iterations <= 0) {
449 return;
450 }
451
452 /* Make a copy of the point data to avoid directionality of the smooth operation. */
453 bGPDstroke gps_old = blender::dna::shallow_copy(*gps);
454 gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points);
455
456 /* Smooth stroke. */
457 for (int i = 0; i < gps->totpoints; i++) {
458 float val = influence;
459 if (weights != nullptr) {
460 val *= weights[i];
461 if (val <= 0.0f) {
462 continue;
463 }
464 }
465
466 /* TODO: Currently the weights only control the influence, but is would be much better if they
467 * would control the distribution used in smooth, similar to how the ends are handled. */
468
469 /* Perform smoothing. */
470 if (smooth_position) {
471 BKE_gpencil_stroke_smooth_point(&gps_old, i, val, iterations, false, keep_shape, gps);
472 }
473 if (smooth_strength) {
474 BKE_gpencil_stroke_smooth_strength(&gps_old, i, val, iterations, gps);
475 }
476 if (smooth_thickness) {
477 BKE_gpencil_stroke_smooth_thickness(&gps_old, i, val, iterations, gps);
478 }
479 if (smooth_uv) {
480 BKE_gpencil_stroke_smooth_uv(&gps_old, i, val, iterations, gps);
481 }
482 }
483
484 /* Free the copied points array. */
485 MEM_freeN(gps_old.points);
486}
487
489 int totpoints,
490 float (*points2d)[2],
491 int *r_direction)
492{
493 BLI_assert(totpoints >= 2);
494
495 const bGPDspoint *pt0 = &points[0];
496 const bGPDspoint *pt1 = &points[1];
497 const bGPDspoint *pt3 = &points[int(totpoints * 0.75)];
498
499 float locx[3];
500 float locy[3];
501 float loc3[3];
502 float normal[3];
503
504 /* local X axis (p0 -> p1) */
505 sub_v3_v3v3(locx, &pt1->x, &pt0->x);
506
507 /* point vector at 3/4 */
508 float v3[3];
509 if (totpoints == 2) {
510 mul_v3_v3fl(v3, &pt3->x, 0.001f);
511 }
512 else {
513 copy_v3_v3(v3, &pt3->x);
514 }
515
516 sub_v3_v3v3(loc3, v3, &pt0->x);
517
518 /* vector orthogonal to polygon plane */
519 cross_v3_v3v3(normal, locx, loc3);
520
521 /* local Y axis (cross to normal/x axis) */
522 cross_v3_v3v3(locy, normal, locx);
523
524 /* Normalize vectors */
525 normalize_v3(locx);
526 normalize_v3(locy);
527
528 /* Calculate last point first. */
529 const bGPDspoint *pt_last = &points[totpoints - 1];
530 float tmp[3];
531 sub_v3_v3v3(tmp, &pt_last->x, &pt0->x);
532
533 points2d[totpoints - 1][0] = dot_v3v3(tmp, locx);
534 points2d[totpoints - 1][1] = dot_v3v3(tmp, locy);
535
536 /* Calculate the scalar cross product of the 2d points. */
537 float cross = 0.0f;
538 float *co_curr;
539 float *co_prev = (float *)&points2d[totpoints - 1];
540
541 /* Get all points in local space */
542 for (int i = 0; i < totpoints - 1; i++) {
543 const bGPDspoint *pt = &points[i];
544 float loc[3];
545
546 /* Get local space using first point as origin */
547 sub_v3_v3v3(loc, &pt->x, &pt0->x);
548
549 points2d[i][0] = dot_v3v3(loc, locx);
550 points2d[i][1] = dot_v3v3(loc, locy);
551
552 /* Calculate cross product. */
553 co_curr = (float *)&points2d[i][0];
554 cross += (co_curr[0] - co_prev[0]) * (co_curr[1] + co_prev[1]);
555 co_prev = (float *)&points2d[i][0];
556 }
557
558 /* Concave (-1), Convex (1) */
559 *r_direction = (cross >= 0.0f) ? 1 : -1;
560}
561
563 int ref_totpoints,
564 const bGPDspoint *points,
565 int totpoints,
566 float (*points2d)[2],
567 const float scale,
568 int *r_direction)
569{
570 BLI_assert(totpoints >= 2);
571
572 const bGPDspoint *pt0 = &ref_points[0];
573 const bGPDspoint *pt1 = &ref_points[1];
574 const bGPDspoint *pt3 = &ref_points[int(ref_totpoints * 0.75)];
575
576 float locx[3];
577 float locy[3];
578 float loc3[3];
579 float normal[3];
580
581 /* local X axis (p0 -> p1) */
582 sub_v3_v3v3(locx, &pt1->x, &pt0->x);
583
584 /* point vector at 3/4 */
585 float v3[3];
586 if (totpoints == 2) {
587 mul_v3_v3fl(v3, &pt3->x, 0.001f);
588 }
589 else {
590 copy_v3_v3(v3, &pt3->x);
591 }
592
593 sub_v3_v3v3(loc3, v3, &pt0->x);
594
595 /* vector orthogonal to polygon plane */
596 cross_v3_v3v3(normal, locx, loc3);
597
598 /* local Y axis (cross to normal/x axis) */
599 cross_v3_v3v3(locy, normal, locx);
600
601 /* Normalize vectors */
602 normalize_v3(locx);
603 normalize_v3(locy);
604
605 /* Get all points in local space */
606 for (int i = 0; i < totpoints; i++) {
607 const bGPDspoint *pt = &points[i];
608 float loc[3];
609 float v1[3];
610 float vn[3] = {0.0f, 0.0f, 0.0f};
611
612 /* apply scale to extremes of the stroke to get better collision detection
613 * the scale is divided to get more control in the UI parameter
614 */
615 /* first point */
616 if (i == 0) {
617 const bGPDspoint *pt_next = &points[i + 1];
618 sub_v3_v3v3(vn, &pt->x, &pt_next->x);
619 normalize_v3(vn);
620 mul_v3_fl(vn, scale / 10.0f);
621 add_v3_v3v3(v1, &pt->x, vn);
622 }
623 /* last point */
624 else if (i == totpoints - 1) {
625 const bGPDspoint *pt_prev = &points[i - 1];
626 sub_v3_v3v3(vn, &pt->x, &pt_prev->x);
627 normalize_v3(vn);
628 mul_v3_fl(vn, scale / 10.0f);
629 add_v3_v3v3(v1, &pt->x, vn);
630 }
631 else {
632 copy_v3_v3(v1, &pt->x);
633 }
634
635 /* Get local space using first point as origin (ref stroke) */
636 sub_v3_v3v3(loc, v1, &pt0->x);
637
638 points2d[i][0] = dot_v3v3(loc, locx);
639 points2d[i][1] = dot_v3v3(loc, locy);
640 }
641
642 /* Concave (-1), Convex (1), or Auto-detect (0)? */
643 *r_direction = int(locy[2]);
644}
645
646/* Calc texture coordinates using flat projected points. */
647static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2],
648 bGPDstroke *gps,
649 const float minv[2],
650 const float maxv[2],
651 float (*r_uv)[2])
652{
653 const float s = sin(gps->uv_rotation);
654 const float c = cos(gps->uv_rotation);
655
656 /* Calc center for rotation. */
657 float center[2] = {0.5f, 0.5f};
658 float d[2];
659 d[0] = maxv[0] - minv[0];
660 d[1] = maxv[1] - minv[1];
661 for (int i = 0; i < gps->totpoints; i++) {
662 r_uv[i][0] = (points2d[i][0] - minv[0]) / d[0];
663 r_uv[i][1] = (points2d[i][1] - minv[1]) / d[1];
664
665 /* Apply translation. */
666 add_v2_v2(r_uv[i], gps->uv_translation);
667
668 /* Apply Rotation. */
669 r_uv[i][0] -= center[0];
670 r_uv[i][1] -= center[1];
671
672 float x = r_uv[i][0] * c - r_uv[i][1] * s;
673 float y = r_uv[i][0] * s + r_uv[i][1] * c;
674
675 r_uv[i][0] = x + center[0];
676 r_uv[i][1] = y + center[1];
677
678 /* Apply scale. */
679 if (gps->uv_scale != 0.0f) {
680 mul_v2_fl(r_uv[i], 1.0f / gps->uv_scale);
681 }
682 }
683}
684
687/* -------------------------------------------------------------------- */
692{
693 BLI_assert(gps->totpoints >= 3);
694
695 /* allocate memory for temporary areas */
696 gps->tot_triangles = gps->totpoints - 2;
697 uint(*tmp_triangles)[3] = (uint(*)[3])MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles,
698 "GP Stroke temp triangulation");
699 float(*points2d)[2] = (float(*)[2])MEM_mallocN(sizeof(*points2d) * gps->totpoints,
700 "GP Stroke temp 2d points");
701 float(*uv)[2] = (float(*)[2])MEM_mallocN(sizeof(*uv) * gps->totpoints,
702 "GP Stroke temp 2d uv data");
703
704 int direction = 0;
705
706 /* convert to 2d and triangulate */
707 BKE_gpencil_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction);
708 BLI_polyfill_calc(points2d, uint(gps->totpoints), direction, tmp_triangles);
709
710 /* calc texture coordinates automatically */
711 float minv[2];
712 float maxv[2];
713 /* first needs bounding box data */
714 ARRAY_SET_ITEMS(minv, -1.0f, -1.0f);
715 ARRAY_SET_ITEMS(maxv, 1.0f, 1.0f);
716
717 /* calc uv data */
718 gpencil_calc_stroke_fill_uv(points2d, gps, minv, maxv, uv);
719
720 /* Save triangulation data. */
721 if (gps->tot_triangles > 0) {
723 gps->triangles = (bGPDtriangle *)MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles,
724 "GP Stroke triangulation");
725
726 for (int i = 0; i < gps->tot_triangles; i++) {
727 memcpy(gps->triangles[i].verts, tmp_triangles[i], sizeof(uint[3]));
728 }
729
730 /* Copy UVs to bGPDspoint. */
731 for (int i = 0; i < gps->totpoints; i++) {
732 copy_v2_v2(gps->points[i].uv_fill, uv[i]);
733 }
734 }
735 else {
736 /* No triangles needed - Free anything allocated previously */
737 if (gps->triangles) {
738 MEM_freeN(gps->triangles);
739 }
740
741 gps->triangles = nullptr;
742 }
743
744 /* clear memory */
745 MEM_SAFE_FREE(tmp_triangles);
746 MEM_SAFE_FREE(points2d);
747 MEM_SAFE_FREE(uv);
748}
749
751{
752 if (gps == nullptr || gps->totpoints == 0) {
753 return;
754 }
755
756 bGPDspoint *pt = gps->points;
757 float totlen = 0.0f;
758 pt[0].uv_fac = totlen;
759 for (int i = 1; i < gps->totpoints; i++) {
760 totlen += len_v3v3(&pt[i - 1].x, &pt[i].x);
761 pt[i].uv_fac = totlen;
762 }
763}
764
766{
767 if (gps == nullptr) {
768 return;
769 }
770
771 if (gps->totpoints > 2) {
773 }
774 else {
775 gps->tot_triangles = 0;
777 }
778
779 /* calc uv data along the stroke */
781
782 /* Calc stroke bounding box. */
784}
785
786float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
787{
788 if (!gps->points || gps->totpoints < 2) {
789 return 0.0f;
790 }
791 float *last_pt = &gps->points[0].x;
792 float total_length = 0.0f;
793 for (int i = 1; i < gps->totpoints; i++) {
794 bGPDspoint *pt = &gps->points[i];
795 if (use_3d) {
796 total_length += len_v3v3(&pt->x, last_pt);
797 }
798 else {
799 total_length += len_v2v2(&pt->x, last_pt);
800 }
801 last_pt = &pt->x;
802 }
803 return total_length;
804}
805
807 const int start_index,
808 const int end_index,
809 bool use_3d)
810{
811 if (!gps->points || gps->totpoints < 2 || end_index <= start_index) {
812 return 0.0f;
813 }
814
815 int index = std::max(start_index, 0) + 1;
816 int last_index = std::min(end_index, gps->totpoints - 1) + 1;
817
818 float *last_pt = &gps->points[index - 1].x;
819 float total_length = 0.0f;
820 for (int i = index; i < last_index; i++) {
821 bGPDspoint *pt = &gps->points[i];
822 if (use_3d) {
823 total_length += len_v3v3(&pt->x, last_pt);
824 }
825 else {
826 total_length += len_v2v2(&pt->x, last_pt);
827 }
828 last_pt = &pt->x;
829 }
830 return total_length;
831}
832
834{
835 if (gps->totpoints < 4) {
836 return false;
837 }
838 bool intersect = false;
839 int start = 0;
840 int end = 0;
841 float point[3];
842 /* loop segments from start until we have an intersection */
843 for (int i = 0; i < gps->totpoints - 2; i++) {
844 start = i;
845 bGPDspoint *a = &gps->points[start];
846 bGPDspoint *b = &gps->points[start + 1];
847 for (int j = start + 2; j < gps->totpoints - 1; j++) {
848 end = j + 1;
849 bGPDspoint *c = &gps->points[j];
850 bGPDspoint *d = &gps->points[end];
851 float pointb[3];
852 /* get intersection */
853 if (isect_line_line_v3(&a->x, &b->x, &c->x, &d->x, point, pointb)) {
854 if (len_v3(point) > 0.0f) {
855 float closest[3];
856 /* check intersection is on both lines */
857 float lambda = closest_to_line_v3(closest, point, &a->x, &b->x);
858 if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
859 continue;
860 }
861 lambda = closest_to_line_v3(closest, point, &c->x, &d->x);
862 if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
863 continue;
864 }
865
866 intersect = true;
867 break;
868 }
869 }
870 }
871 if (intersect) {
872 break;
873 }
874 }
875
876 /* trim unwanted points */
877 if (intersect) {
878
879 /* save points */
880 bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
881 MDeformVert *old_dvert = nullptr;
882 MDeformVert *dvert_src = nullptr;
883
884 if (gps->dvert != nullptr) {
885 old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
886 }
887
888 /* resize gps */
889 int newtot = end - start + 1;
890
891 gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
892 if (gps->dvert != nullptr) {
893 gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
894 }
895
896 for (int i = 0; i < newtot; i++) {
897 int idx = start + i;
898 bGPDspoint *pt_src = &old_points[idx];
899 bGPDspoint *pt_new = &gps->points[i];
900 *pt_new = blender::dna::shallow_copy(*pt_src);
901 if (gps->dvert != nullptr) {
902 dvert_src = &old_dvert[idx];
903 MDeformVert *dvert = &gps->dvert[i];
904 memcpy(dvert, dvert_src, sizeof(MDeformVert));
905 if (dvert_src->dw) {
906 memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
907 }
908 }
909 if (ELEM(idx, start, end)) {
910 copy_v3_v3(&pt_new->x, point);
911 }
912 }
913
914 gps->totpoints = newtot;
915
916 MEM_SAFE_FREE(old_points);
917 MEM_SAFE_FREE(old_dvert);
918 }
919
921
922 return intersect;
923}
924
926{
927 bGPDspoint *pt1 = nullptr;
928 bGPDspoint *pt2 = nullptr;
929
930 /* Only can close a stroke with 3 points or more. */
931 if (gps->totpoints < 3) {
932 return false;
933 }
934
935 /* Calc average distance between points to get same level of sampling. */
936 float dist_tot = 0.0f;
937 for (int i = 0; i < gps->totpoints - 1; i++) {
938 pt1 = &gps->points[i];
939 pt2 = &gps->points[i + 1];
940 dist_tot += len_v3v3(&pt1->x, &pt2->x);
941 }
942 /* Calc the average distance. */
943 float dist_avg = dist_tot / (gps->totpoints - 1);
944
945 /* Calc distance between last and first point. */
946 pt1 = &gps->points[gps->totpoints - 1];
947 pt2 = &gps->points[0];
948 float dist_close = len_v3v3(&pt1->x, &pt2->x);
949
950 /* if the distance to close is very small, don't need add points and just enable cyclic. */
951 if (dist_close <= dist_avg) {
952 gps->flag |= GP_STROKE_CYCLIC;
953 return true;
954 }
955
956 /* Calc number of points required using the average distance. */
957 int tot_newpoints = std::max<int>(dist_close / dist_avg, 1);
958
959 /* Resize stroke array. */
960 int old_tot = gps->totpoints;
961 gps->totpoints += tot_newpoints;
962 gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
963 if (gps->dvert != nullptr) {
964 gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
965 }
966
967 /* Generate new points */
968 pt1 = &gps->points[old_tot - 1];
969 pt2 = &gps->points[0];
970 bGPDspoint *pt = &gps->points[old_tot];
971 for (int i = 1; i < tot_newpoints + 1; i++, pt++) {
972 float step = (tot_newpoints > 1) ? (float(i) / float(tot_newpoints)) : 0.99f;
973 /* Clamp last point to be near, but not on top of first point. */
974 if ((tot_newpoints > 1) && (i == tot_newpoints)) {
975 step *= 0.99f;
976 }
977
978 /* Average point. */
979 interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step);
980 pt->pressure = interpf(pt2->pressure, pt1->pressure, step);
981 pt->strength = interpf(pt2->strength, pt1->strength, step);
982 pt->flag = 0;
983 interp_v4_v4v4(pt->vert_color, pt1->vert_color, pt2->vert_color, step);
984 /* Set point as selected. */
985 if (gps->flag & GP_STROKE_SELECT) {
986 pt->flag |= GP_SPOINT_SELECT;
987 }
988
989 /* Set weights. */
990 if (gps->dvert != nullptr) {
991 MDeformVert *dvert1 = &gps->dvert[old_tot - 1];
992 MDeformWeight *dw1 = BKE_defvert_ensure_index(dvert1, 0);
993 float weight_1 = dw1 ? dw1->weight : 0.0f;
994
995 MDeformVert *dvert2 = &gps->dvert[0];
996 MDeformWeight *dw2 = BKE_defvert_ensure_index(dvert2, 0);
997 float weight_2 = dw2 ? dw2->weight : 0.0f;
998
999 MDeformVert *dvert_final = &gps->dvert[old_tot + i - 1];
1000 dvert_final->totweight = 0;
1001 MDeformWeight *dw = BKE_defvert_ensure_index(dvert_final, 0);
1002 if (dvert_final->dw) {
1003 dw->weight = interpf(weight_2, weight_1, step);
1004 }
1005 }
1006 }
1007
1008 /* Enable cyclic flag. */
1009 gps->flag |= GP_STROKE_CYCLIC;
1010
1011 return true;
1012}
1013
1016/* -------------------------------------------------------------------- */
1020void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3])
1021{
1022 if (gps->totpoints < 3) {
1023 zero_v3(r_normal);
1024 return;
1025 }
1026
1027 bGPDspoint *points = gps->points;
1028 int totpoints = gps->totpoints;
1029
1030 const bGPDspoint *pt0 = &points[0];
1031 const bGPDspoint *pt1 = &points[1];
1032 const bGPDspoint *pt3 = &points[int(totpoints * 0.75)];
1033
1034 float vec1[3];
1035 float vec2[3];
1036
1037 /* initial vector (p0 -> p1) */
1038 sub_v3_v3v3(vec1, &pt1->x, &pt0->x);
1039
1040 /* point vector at 3/4 */
1041 sub_v3_v3v3(vec2, &pt3->x, &pt0->x);
1042
1043 /* vector orthogonal to polygon plane */
1044 cross_v3_v3v3(r_normal, vec1, vec2);
1045
1046 /* Normalize vector */
1047 normalize_v3(r_normal);
1048}
1049
1052void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4])
1053{
1054 if (gpd == nullptr) {
1055 return;
1056 }
1057
1058 const float scalef = mat4_to_scale(mat);
1059 LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
1060 /* FIXME: For now, we just skip parented layers.
1061 * Otherwise, we have to update each frame to find
1062 * the current parent position/effects.
1063 */
1064 if (gpl->parent) {
1065 continue;
1066 }
1067
1068 LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
1069 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
1070 bGPDspoint *pt;
1071 int i;
1072
1073 for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
1074 mul_m4_v3(mat, &pt->x);
1075 pt->pressure *= scalef;
1076 }
1077
1078 /* Distortion may mean we need to re-triangulate. */
1080 }
1081 }
1082 }
1083}
1084
1086{
1087 int total_points = 0;
1088
1089 if (gpd == nullptr) {
1090 return 0;
1091 }
1092
1093 LISTBASE_FOREACH (const bGPDlayer *, gpl, &gpd->layers) {
1094 /* FIXME: For now, we just skip parented layers.
1095 * Otherwise, we have to update each frame to find
1096 * the current parent position/effects.
1097 */
1098 if (gpl->parent) {
1099 continue;
1100 }
1101
1102 LISTBASE_FOREACH (const bGPDframe *, gpf, &gpl->frames) {
1103 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
1104 total_points += gps->totpoints;
1105 }
1106 }
1107 }
1108 return total_points;
1109}
1110
1112{
1113 if (gpd == nullptr) {
1114 return;
1115 }
1116
1117 LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
1118 /* FIXME: For now, we just skip parented layers.
1119 * Otherwise, we have to update each frame to find
1120 * the current parent position/effects.
1121 */
1122 if (gpl->parent) {
1123 continue;
1124 }
1125
1126 LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
1127 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
1128 bGPDspoint *pt;
1129 int i;
1130
1131 for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
1132 copy_v3_v3(elem_data->co, &pt->x);
1133 elem_data->pressure = pt->pressure;
1134 elem_data++;
1135 }
1136 }
1137 }
1138 }
1139}
1140
1142{
1143 if (gpd == nullptr) {
1144 return;
1145 }
1146
1147 LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
1148 /* FIXME: For now, we just skip parented layers.
1149 * Otherwise, we have to update each frame to find
1150 * the current parent position/effects.
1151 */
1152 if (gpl->parent) {
1153 continue;
1154 }
1155
1156 LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
1157 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
1158 bGPDspoint *pt;
1159 int i;
1160
1161 for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
1162 copy_v3_v3(&pt->x, elem_data->co);
1163 pt->pressure = elem_data->pressure;
1164 elem_data++;
1165 }
1166
1167 /* Distortion may mean we need to re-triangulate. */
1169 }
1170 }
1171 }
1172}
1173
1175 const GPencilPointCoordinates *elem_data,
1176 const float mat[4][4])
1177{
1178 if (gpd == nullptr) {
1179 return;
1180 }
1181
1182 const float scalef = mat4_to_scale(mat);
1183 LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
1184 /* FIXME: For now, we just skip parented layers.
1185 * Otherwise, we have to update each frame to find
1186 * the current parent position/effects.
1187 */
1188 if (gpl->parent) {
1189 continue;
1190 }
1191
1192 LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
1193 LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
1194 bGPDspoint *pt;
1195 int i;
1196
1197 for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
1198 mul_v3_m4v3(&pt->x, mat, elem_data->co);
1199 pt->pressure = elem_data->pressure * scalef;
1200 elem_data++;
1201 }
1202
1203 /* Distortion may mean we need to re-triangulate. */
1205 }
1206 }
1207 }
1208}
1209
1211{
1212 BLI_assert(gps->totpoints > 0);
1213
1214 float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1215 bGPDspoint *pt = &gps->points[0];
1216 color[0] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints / 5, pt->x + pt->z));
1217 color[1] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints + pt->x, pt->y * pt->z + pt->x));
1218 color[2] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints - pt->x, pt->z * pt->x + pt->y));
1219 for (int i = 0; i < gps->totpoints; i++) {
1220 pt = &gps->points[i];
1221 copy_v4_v4(pt->vert_color, color);
1222 }
1223}
1224
1226{
1227 /* Reverse points. */
1229
1230 /* Reverse vertex groups if available. */
1231 if (gps->dvert) {
1232 BLI_array_reverse(gps->dvert, gps->totpoints);
1233 }
1234}
1235
1236/* Temp data for storing information about an "island" of points
1237 * that should be kept when splitting up a stroke. Used in:
1238 * gpencil_stroke_delete_tagged_points()
1239 */
1244
1246 bGPDframe *gpf,
1247 bGPDstroke *gps_first,
1248 bGPDstroke *gps_last)
1249{
1250 bGPDspoint *pt = nullptr;
1251 bGPDspoint *pt_final = nullptr;
1252 const int totpoints = gps_first->totpoints + gps_last->totpoints;
1253
1254 /* create new stroke */
1255 bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true);
1256
1257 join_stroke->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
1258 join_stroke->totpoints = totpoints;
1259 join_stroke->flag &= ~GP_STROKE_CYCLIC;
1260
1261 /* copy points (last before) */
1262 int e1 = 0;
1263 int e2 = 0;
1264 float delta = 0.0f;
1265
1266 for (int i = 0; i < totpoints; i++) {
1267 pt_final = &join_stroke->points[i];
1268 if (i < gps_last->totpoints) {
1269 pt = &gps_last->points[e1];
1270 e1++;
1271 }
1272 else {
1273 pt = &gps_first->points[e2];
1274 e2++;
1275 }
1276
1277 /* copy current point */
1278 copy_v3_v3(&pt_final->x, &pt->x);
1279 pt_final->pressure = pt->pressure;
1280 pt_final->strength = pt->strength;
1281 pt_final->time = delta;
1282 pt_final->flag = pt->flag;
1283 copy_v4_v4(pt_final->vert_color, pt->vert_color);
1284
1285 /* retiming with fixed time interval (we cannot determine real time) */
1286 delta += 0.01f;
1287 }
1288
1289 /* Copy over vertex weight data (if available) */
1290 if ((gps_first->dvert != nullptr) || (gps_last->dvert != nullptr)) {
1291 join_stroke->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
1292 MDeformVert *dvert_src = nullptr;
1293 MDeformVert *dvert_dst = nullptr;
1294
1295 /* Copy weights (last before). */
1296 e1 = 0;
1297 e2 = 0;
1298 for (int i = 0; i < totpoints; i++) {
1299 dvert_dst = &join_stroke->dvert[i];
1300 dvert_src = nullptr;
1301 if (i < gps_last->totpoints) {
1302 if (gps_last->dvert) {
1303 dvert_src = &gps_last->dvert[e1];
1304 e1++;
1305 }
1306 }
1307 else {
1308 if (gps_first->dvert) {
1309 dvert_src = &gps_first->dvert[e2];
1310 e2++;
1311 }
1312 }
1313
1314 if ((dvert_src) && (dvert_src->dw)) {
1315 dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
1316 }
1317 }
1318 }
1319
1320 /* add new stroke at head */
1321 BLI_addhead(&gpf->strokes, join_stroke);
1322 /* Calc geometry data. */
1323 BKE_gpencil_stroke_geometry_update(gpd, join_stroke);
1324
1325 /* remove first stroke */
1326 BLI_remlink(&gpf->strokes, gps_first);
1327 BKE_gpencil_free_stroke(gps_first);
1328
1329 /* remove last stroke */
1330 BLI_remlink(&gpf->strokes, gps_last);
1331 BKE_gpencil_free_stroke(gps_last);
1332}
1333
1335 bGPDframe *gpf,
1336 bGPDstroke *gps,
1337 bGPDstroke *next_stroke,
1338 int tag_flags,
1339 const bool select,
1340 const bool flat_cap,
1341 const int limit)
1342{
1343 /* The algorithm used here is as follows:
1344 * 1) We firstly identify the number of "islands" of non-tagged points
1345 * which will all end up being in new strokes.
1346 * - In the most extreme case (i.e. every other vert is a 1-vert island),
1347 * we have at most `n / 2` islands
1348 * - Once we start having larger islands than that, the number required
1349 * becomes much less
1350 * 2) Each island gets converted to a new stroke
1351 * If the number of points is <= limit, the stroke is deleted. */
1352
1354 sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
1355 bool in_island = false;
1356 int num_islands = 0;
1357
1358 bGPDstroke *new_stroke = nullptr;
1359 bGPDstroke *gps_first = nullptr;
1360 const bool is_cyclic = bool(gps->flag & GP_STROKE_CYCLIC);
1361
1362 /* First Pass: Identify start/end of islands */
1363 bGPDspoint *pt = gps->points;
1364 for (int i = 0; i < gps->totpoints; i++, pt++) {
1365 if (pt->flag & tag_flags) {
1366 /* selected - stop accumulating to island */
1367 in_island = false;
1368 }
1369 else {
1370 /* unselected - start of a new island? */
1371 int idx;
1372
1373 if (in_island) {
1374 /* extend existing island */
1375 idx = num_islands - 1;
1376 islands[idx].end_idx = i;
1377 }
1378 else {
1379 /* start of new island */
1380 in_island = true;
1381 num_islands++;
1382
1383 idx = num_islands - 1;
1384 islands[idx].start_idx = islands[idx].end_idx = i;
1385 }
1386 }
1387 }
1388
1389 /* Watch out for special case where No islands = All points selected = Delete Stroke only */
1390 if (num_islands) {
1391 /* There are islands, so create a series of new strokes,
1392 * adding them before the "next" stroke. */
1393 int idx;
1394
1395 /* Create each new stroke... */
1396 for (idx = 0; idx < num_islands; idx++) {
1397 tGPDeleteIsland *island = &islands[idx];
1398 new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true);
1399 if (flat_cap) {
1400 new_stroke->caps[1 - (idx % 2)] = GP_STROKE_CAP_FLAT;
1401 }
1402
1403 /* if cyclic and first stroke, save to join later */
1404 if ((is_cyclic) && (gps_first == nullptr)) {
1405 gps_first = new_stroke;
1406 }
1407
1408 new_stroke->flag &= ~GP_STROKE_CYCLIC;
1409
1410 /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
1411 new_stroke->totpoints = island->end_idx - island->start_idx + 1;
1412
1413 /* Copy over the relevant point data */
1414 new_stroke->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints,
1415 "gp delete stroke fragment");
1416 memcpy(static_cast<void *>(new_stroke->points),
1417 gps->points + island->start_idx,
1418 sizeof(bGPDspoint) * new_stroke->totpoints);
1419
1420 /* Copy over vertex weight data (if available) */
1421 if (gps->dvert != nullptr) {
1422 /* Copy over the relevant vertex-weight points */
1423 new_stroke->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints,
1424 "gp delete stroke fragment weight");
1425 memcpy(new_stroke->dvert,
1426 gps->dvert + island->start_idx,
1427 sizeof(MDeformVert) * new_stroke->totpoints);
1428
1429 /* Copy weights */
1430 int e = island->start_idx;
1431 for (int i = 0; i < new_stroke->totpoints; i++) {
1432 MDeformVert *dvert_src = &gps->dvert[e];
1433 MDeformVert *dvert_dst = &new_stroke->dvert[i];
1434 if (dvert_src->dw) {
1435 dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
1436 }
1437 e++;
1438 }
1439 }
1440 /* Each island corresponds to a new stroke.
1441 * We must adjust the timings of these new strokes:
1442 *
1443 * Each point's timing data is a delta from stroke's inittime, so as we erase some points
1444 * from the start of the stroke, we have to offset this inittime and all remaining points'
1445 * delta values. This way we get a new stroke with exactly the same timing as if user had
1446 * started drawing from the first non-removed point.
1447 */
1448 {
1449 bGPDspoint *pts;
1450 float delta = gps->points[island->start_idx].time;
1451 int j;
1452
1453 new_stroke->inittime += double(delta);
1454
1455 pts = new_stroke->points;
1456 for (j = 0; j < new_stroke->totpoints; j++, pts++) {
1457 /* Some points have time = 0, so check to not get negative time values. */
1458 pts->time = max_ff(pts->time - delta, 0.0f);
1459 /* set flag for select again later */
1460 if (select == true) {
1461 pts->flag &= ~GP_SPOINT_SELECT;
1462 pts->flag |= GP_SPOINT_TAG;
1463 }
1464 }
1465 }
1466
1467 /* Add new stroke to the frame or delete if below limit */
1468 if ((limit > 0) && (new_stroke->totpoints <= limit)) {
1469 if (gps_first == new_stroke) {
1470 gps_first = nullptr;
1471 }
1472 BKE_gpencil_free_stroke(new_stroke);
1473 }
1474 else {
1475 /* Calc geometry data. */
1476 BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
1477
1478 if (next_stroke) {
1479 BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
1480 }
1481 else {
1482 BLI_addtail(&gpf->strokes, new_stroke);
1483 }
1484 }
1485 }
1486 /* if cyclic, need to join last stroke with first stroke */
1487 if ((is_cyclic) && (gps_first != nullptr) && (gps_first != new_stroke)) {
1488 gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke);
1489 }
1490 }
1491
1492 /* free islands */
1493 MEM_freeN(islands);
1494
1495 /* Delete the old stroke */
1496 BLI_remlink(&gpf->strokes, gps);
1498
1499 return new_stroke;
1500}
1501
1502/* Helper: copy point between strokes */
1504 MDeformVert *dvert,
1505 bGPDspoint *point,
1506 const float delta[3],
1507 float pressure,
1508 float strength,
1509 float deltatime)
1510{
1511 bGPDspoint *newpoint;
1512
1513 gps->points = (bGPDspoint *)MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
1514 if (gps->dvert != nullptr) {
1515 gps->dvert = (MDeformVert *)MEM_reallocN(gps->dvert,
1516 sizeof(MDeformVert) * (gps->totpoints + 1));
1517 }
1518 else {
1519 /* If destination has weight add weight to origin. */
1520 if (dvert != nullptr) {
1521 gps->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1),
1522 __func__);
1523 }
1524 }
1525
1526 gps->totpoints++;
1527 newpoint = &gps->points[gps->totpoints - 1];
1528
1529 newpoint->x = point->x * delta[0];
1530 newpoint->y = point->y * delta[1];
1531 newpoint->z = point->z * delta[2];
1532 newpoint->flag = point->flag;
1533 newpoint->pressure = pressure;
1534 newpoint->strength = strength;
1535 newpoint->time = point->time + deltatime;
1536 copy_v4_v4(newpoint->vert_color, point->vert_color);
1537
1538 if (gps->dvert != nullptr) {
1539 MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1];
1540
1541 if (dvert != nullptr) {
1542 newdvert->totweight = dvert->totweight;
1543 newdvert->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
1544 }
1545 else {
1546 newdvert->totweight = 0;
1547 newdvert->dw = nullptr;
1548 }
1549 }
1550}
1551
1553 bGPDstroke *gps_b,
1554 const bool leave_gaps,
1555 const bool fit_thickness,
1556 const bool smooth,
1557 bool auto_flip)
1558{
1560 bGPDspoint *pt;
1561 int i;
1562 const float delta[3] = {1.0f, 1.0f, 1.0f};
1563 float deltatime = 0.0f;
1564
1565 /* sanity checks */
1566 if (ELEM(nullptr, gps_a, gps_b)) {
1567 return;
1568 }
1569
1570 if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) {
1571 return;
1572 }
1573
1574 if (auto_flip) {
1575 /* define start and end points of each stroke */
1576 float start_a[3], start_b[3], end_a[3], end_b[3];
1577 pt = &gps_a->points[0];
1578 copy_v3_v3(start_a, &pt->x);
1579
1580 pt = &gps_a->points[gps_a->totpoints - 1];
1581 copy_v3_v3(end_a, &pt->x);
1582
1583 pt = &gps_b->points[0];
1584 copy_v3_v3(start_b, &pt->x);
1585
1586 pt = &gps_b->points[gps_b->totpoints - 1];
1587 copy_v3_v3(end_b, &pt->x);
1588
1589 /* Check if need flip strokes. */
1590 float dist = len_squared_v3v3(end_a, start_b);
1591 bool flip_a = false;
1592 bool flip_b = false;
1593 float lowest = dist;
1594
1595 dist = len_squared_v3v3(end_a, end_b);
1596 if (dist < lowest) {
1597 lowest = dist;
1598 flip_a = false;
1599 flip_b = true;
1600 }
1601
1602 dist = len_squared_v3v3(start_a, start_b);
1603 if (dist < lowest) {
1604 lowest = dist;
1605 flip_a = true;
1606 flip_b = false;
1607 }
1608
1609 dist = len_squared_v3v3(start_a, end_b);
1610 if (dist < lowest) {
1611 lowest = dist;
1612 flip_a = true;
1613 flip_b = true;
1614 }
1615
1616 if (flip_a) {
1618 }
1619 if (flip_b) {
1621 }
1622 }
1623
1624 /* don't visibly link the first and last points? */
1625 if (leave_gaps) {
1626 /* 1st: add one tail point to start invisible area */
1627 point = blender::dna::shallow_copy(gps_a->points[gps_a->totpoints - 1]);
1628 deltatime = point.time;
1629
1630 gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, 0.0f);
1631
1632 /* 2nd: add one head point to finish invisible area */
1633 point = blender::dna::shallow_copy(gps_b->points[0]);
1634 gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, deltatime);
1635 }
1636
1637 /* Ratio to apply in the points to keep the same thickness in the joined stroke using the
1638 * destination stroke thickness. */
1639 const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ?
1640 float(gps_b->thickness) / float(gps_a->thickness) :
1641 1.0f;
1642
1643 /* 3rd: add all points */
1644 const int totpoints_a = gps_a->totpoints;
1645 for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
1646 MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : nullptr;
1648 gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime);
1649 }
1650 /* Smooth the join to avoid hard thickness changes. */
1651 if (smooth) {
1652 const int sample_points = 8;
1653 /* Get the segment to smooth using n points on each side of the join. */
1654 int start = std::max(0, totpoints_a - sample_points);
1655 int end = std::min(gps_a->totpoints - 1, start + (sample_points * 2));
1656 const int len = (end - start);
1657 float step = 1.0f / ((len / 2) + 1);
1658
1659 /* Calc the average pressure. */
1660 float avg_pressure = 0.0f;
1661 for (i = start; i < end; i++) {
1662 pt = &gps_a->points[i];
1663 avg_pressure += pt->pressure;
1664 }
1665 avg_pressure = avg_pressure / len;
1666
1667 /* Smooth segment thickness and position. */
1668 float ratio = step;
1669 for (i = start; i < end; i++) {
1670 pt = &gps_a->points[i];
1671 pt->pressure += (avg_pressure - pt->pressure) * ratio;
1672 BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f, 2, false, true, gps_a);
1673
1674 ratio += step;
1675 /* In the center, reverse the ratio. */
1676 if (ratio > 1.0f) {
1677 ratio = ratio - step - step;
1678 step *= -1.0f;
1679 }
1680 }
1681 }
1682}
1683
1686 float viewmat[4][4],
1687 const float diff_mat[4][4])
1688{
1689 for (int i = 0; i < gps->totpoints; i++) {
1690 bGPDspoint *pt = &gps->points[i];
1691 /* Point to parent space. */
1692 mul_v3_m4v3(&pt->x, diff_mat, &pt->x);
1693 /* point to view space */
1694 mul_m4_v3(viewmat, &pt->x);
1695 }
1696}
1697
1699 float viewinv[4][4],
1700 const float diff_mat[4][4])
1701{
1702 float inverse_diff_mat[4][4];
1703 invert_m4_m4(inverse_diff_mat, diff_mat);
1704
1705 for (int i = 0; i < gps->totpoints; i++) {
1706 bGPDspoint *pt = &gps->points[i];
1707 mul_v3_m4v3(&pt->x, viewinv, &pt->x);
1708 mul_m4_v3(inverse_diff_mat, &pt->x);
1709 }
1710}
1711
1715{
1716
1717 if (gps->totpoints == 1) {
1718 return gps->points[0].pressure;
1719 }
1720
1721 float tot = 0.0f;
1722 for (int i = 0; i < gps->totpoints; i++) {
1723 const bGPDspoint *pt = &gps->points[i];
1724 tot += pt->pressure;
1725 }
1726
1727 return tot / float(gps->totpoints);
1728}
1729
1731{
1732 if (gps->totpoints == 1) {
1733 return true;
1734 }
1735
1736 const float first_pressure = gps->points[0].pressure;
1737 for (int i = 0; i < gps->totpoints; i++) {
1738 const bGPDspoint *pt = &gps->points[i];
1739 if (pt->pressure != first_pressure) {
1740 return false;
1741 }
1742 }
1743
1744 return true;
1745}
1746
support for deformation groups and hooks.
MDeformWeight * BKE_defvert_ensure_index(MDeformVert *dv, int defgroup)
Definition deform.cc:814
struct bGPDstroke * BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, bool dup_points, bool dup_curve)
void BKE_gpencil_free_stroke(struct bGPDstroke *gps)
General operations, lookup, etc. for materials.
General operations, lookup, etc. for blender objects.
Generic array manipulation API.
#define BLI_array_reverse(arr, arr_len)
#define BLI_assert(a)
Definition BLI_assert.h:50
BLI_INLINE float BLI_hash_int_01(unsigned int k)
Definition BLI_hash.h:96
BLI_INLINE unsigned int BLI_hash_int_2d(unsigned int kx, unsigned int ky)
Definition BLI_hash.h:55
A min-heap / priority queue ADT.
void BLI_addhead(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:90
#define LISTBASE_FOREACH(type, var, list)
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
void BLI_insertlinkbefore(struct ListBase *listbase, void *vnextlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:370
#define M_SQRT3
MINLINE float max_ff(float a, float b)
MINLINE float interpf(float target, float origin, float t)
int isect_line_line_v3(const float v1[3], const float v2[3], const float v3[3], const float v4[3], float r_i1[3], float r_i2[3])
float closest_to_line_v3(float r_close[3], const float p[3], const float l1[3], const float l2[3])
float mat4_to_scale(const float mat[4][4])
void mul_m4_v3(const float M[4][4], float r[3])
void mul_v3_m4v3(float r[3], const float mat[4][4], const float vec[3])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
void minmax_v3v3_v3(float min[3], float max[3], const float vec[3])
MINLINE void madd_v3_v3fl(float r[3], const float a[3], float f)
MINLINE float len_squared_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE float len_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
void interp_v4_v4v4(float r[4], const float a[4], const float b[4], float t)
Definition math_vector.c:45
MINLINE void add_v2_v2(float r[2], const float a[2])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
Definition math_vector.c:36
MINLINE void add_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void cross_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void zero_v3(float r[3])
MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f)
MINLINE void add_v3_v3(float r[3], const float a[3])
MINLINE float normalize_v3(float n[3])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
void BLI_polyfill_calc(const float(*coords)[2], unsigned int coords_num, int coords_sign, unsigned int(*r_tris)[3])
unsigned int uint
#define CLAMP_MAX(a, c)
#define INIT_MINMAX(min, max)
#define ARRAY_SET_ITEMS(...)
#define ELEM(...)
#define CLAMP_MIN(a, b)
typedef double(DMatrix)[4][4]
Read Guarded memory(de)allocation.
#define MEM_recallocN(vmemh, len)
#define MEM_SAFE_FREE(v)
#define MEM_reallocN(vmemh, len)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
__forceinline BoundBox intersect(const BoundBox &a, const BoundBox &b)
Definition boundbox.h:178
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
local_group_size(16, 16) .push_constant(Type b
int len
static bool is_cyclic(const Nurb *nu)
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void BKE_gpencil_stroke_flip(bGPDstroke *gps)
float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps)
static void gpencil_stroke_join_islands(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last)
void BKE_gpencil_stroke_to_view_space(bGPDstroke *gps, float viewmat[4][4], const float diff_mat[4][4])
void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points, int ref_totpoints, const bGPDspoint *points, int totpoints, float(*points2d)[2], const float scale, int *r_direction)
bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
static void gpencil_calc_stroke_fill_uv(const float(*points2d)[2], bGPDstroke *gps, const float minv[2], const float maxv[2], float(*r_uv)[2])
bGPDstroke * BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, const bool select, const bool flat_cap, const int limit)
float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
void BKE_gpencil_stroke_join(bGPDstroke *gps_a, bGPDstroke *gps_b, const bool leave_gaps, const bool fit_thickness, const bool smooth, bool auto_flip)
bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps)
void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3])
void BKE_gpencil_stroke_from_view_space(bGPDstroke *gps, float viewinv[4][4], const float diff_mat[4][4])
bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps)
void BKE_gpencil_point_coords_get(bGPdata *gpd, GPencilPointCoordinates *elem_data)
bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps, const bool use_select, float r_min[3], float r_max[3])
void BKE_gpencil_stroke_smooth(bGPDstroke *gps, const float influence, const int iterations, const bool smooth_position, const bool smooth_strength, const bool smooth_thickness, const bool smooth_uv, const bool keep_shape, const float *weights)
void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, const GPencilPointCoordinates *elem_data, const float mat[4][4])
bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
float BKE_gpencil_stroke_segment_length(const bGPDstroke *gps, const int start_index, const int end_index, bool use_3d)
void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4])
void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps)
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction)
static void gpencil_stroke_copy_point(bGPDstroke *gps, MDeformVert *dvert, bGPDspoint *point, const float delta[3], float pressure, float strength, float deltatime)
void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
int BKE_gpencil_stroke_point_count(const bGPdata *gpd)
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int point_index, float influence, int iterations, const bool smooth_caps, const bool keep_shape, bGPDstroke *r_gps)
void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps)
bool BKE_gpencil_stroke_close(bGPDstroke *gps)
void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates *elem_data)
std::optional< blender::Bounds< blender::float3 > > BKE_gpencil_data_minmax(const bGPdata *gpd)
void BKE_gpencil_stroke_geometry_update(bGPdata *, bGPDstroke *gps)
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
void *(* MEM_dupallocN)(const void *vmemh)
Definition mallocn.cc:39
ccl_device_inline float cross(const float2 a, const float2 b)
ccl_device_inline float3 exp(float3 v)
ccl_device_inline float3 cos(float3 v)
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
T midpoint(const T &a, const T &b)
#define min(a, b)
Definition sort.c:32
struct MDeformWeight * dw
bGPDtriangle * triangles
struct MDeformVert * dvert
float max