Blender V5.0
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
8
9#include <cmath>
10#include <cstdlib>
11#include <cstring>
12
13#include "MEM_guardedalloc.h"
14
15#include "BLI_listbase.h"
16#include "BLI_math_vector.h"
18#include "BLI_polyfill_2d.h"
19#include "BLI_span.hh"
20
22#include "DNA_mesh_types.h"
23#include "DNA_meshdata_types.h"
24
26#include "BKE_gpencil_legacy.h"
27
28using blender::float3;
29using blender::Span;
30
32 int totpoints,
33 float (*points2d)[2],
34 int *r_direction)
35{
36 BLI_assert(totpoints >= 2);
37
38 const bGPDspoint *pt0 = &points[0];
39 const bGPDspoint *pt1 = &points[1];
40 const bGPDspoint *pt3 = &points[int(totpoints * 0.75)];
41
42 float locx[3];
43 float locy[3];
44 float loc3[3];
45 float normal[3];
46
47 /* local X axis (p0 -> p1) */
48 sub_v3_v3v3(locx, &pt1->x, &pt0->x);
49
50 /* point vector at 3/4 */
51 float v3[3];
52 if (totpoints == 2) {
53 mul_v3_v3fl(v3, &pt3->x, 0.001f);
54 }
55 else {
56 copy_v3_v3(v3, &pt3->x);
57 }
58
59 sub_v3_v3v3(loc3, v3, &pt0->x);
60
61 /* vector orthogonal to polygon plane */
62 cross_v3_v3v3(normal, locx, loc3);
63
64 /* local Y axis (cross to normal/x axis) */
65 cross_v3_v3v3(locy, normal, locx);
66
67 /* Normalize vectors */
68 normalize_v3(locx);
69 normalize_v3(locy);
70
71 /* Calculate last point first. */
72 const bGPDspoint *pt_last = &points[totpoints - 1];
73 float tmp[3];
74 sub_v3_v3v3(tmp, &pt_last->x, &pt0->x);
75
76 points2d[totpoints - 1][0] = dot_v3v3(tmp, locx);
77 points2d[totpoints - 1][1] = dot_v3v3(tmp, locy);
78
79 /* Calculate the scalar cross product of the 2d points. */
80 float cross = 0.0f;
81 float *co_curr;
82 float *co_prev = (float *)&points2d[totpoints - 1];
83
84 /* Get all points in local space */
85 for (int i = 0; i < totpoints - 1; i++) {
86 const bGPDspoint *pt = &points[i];
87 float loc[3];
88
89 /* Get local space using first point as origin */
90 sub_v3_v3v3(loc, &pt->x, &pt0->x);
91
92 points2d[i][0] = dot_v3v3(loc, locx);
93 points2d[i][1] = dot_v3v3(loc, locy);
94
95 /* Calculate cross product. */
96 co_curr = (&points2d[i][0]);
97 cross += (co_curr[0] - co_prev[0]) * (co_curr[1] + co_prev[1]);
98 co_prev = (&points2d[i][0]);
99 }
100
101 /* Concave (-1), Convex (1) */
102 *r_direction = (cross >= 0.0f) ? 1 : -1;
103}
104
105/* Calc texture coordinates using flat projected points. */
106static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2],
107 bGPDstroke *gps,
108 const float minv[2],
109 const float maxv[2],
110 float (*r_uv)[2])
111{
112 const float s = sin(gps->uv_rotation);
113 const float c = cos(gps->uv_rotation);
114
115 /* Calc center for rotation. */
116 const float center[2] = {0.5f, 0.5f};
117 float d[2];
118 d[0] = maxv[0] - minv[0];
119 d[1] = maxv[1] - minv[1];
120 for (int i = 0; i < gps->totpoints; i++) {
121 r_uv[i][0] = (points2d[i][0] - minv[0]) / d[0];
122 r_uv[i][1] = (points2d[i][1] - minv[1]) / d[1];
123
124 /* Apply translation. */
125 add_v2_v2(r_uv[i], gps->uv_translation);
126
127 /* Apply Rotation. */
128 r_uv[i][0] -= center[0];
129 r_uv[i][1] -= center[1];
130
131 float x = r_uv[i][0] * c - r_uv[i][1] * s;
132 float y = r_uv[i][0] * s + r_uv[i][1] * c;
133
134 r_uv[i][0] = x + center[0];
135 r_uv[i][1] = y + center[1];
136
137 /* Apply scale. */
138 if (gps->uv_scale != 0.0f) {
139 mul_v2_fl(r_uv[i], 1.0f / gps->uv_scale);
140 }
141 }
142}
143
145
146/* -------------------------------------------------------------------- */
149
151{
152 BLI_assert(gps->totpoints >= 3);
153
154 /* allocate memory for temporary areas */
155 gps->tot_triangles = gps->totpoints - 2;
156 uint(*tmp_triangles)[3] = MEM_malloc_arrayN<uint[3]>(size_t(gps->tot_triangles),
157 "GP Stroke temp triangulation");
158 float (*points2d)[2] = MEM_malloc_arrayN<float[2]>(size_t(gps->totpoints),
159 "GP Stroke temp 2d points");
160 float (*uv)[2] = MEM_malloc_arrayN<float[2]>(size_t(gps->totpoints),
161 "GP Stroke temp 2d uv data");
162
163 int direction = 0;
164
165 /* convert to 2d and triangulate */
166 BKE_gpencil_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction);
167 BLI_polyfill_calc(points2d, uint(gps->totpoints), direction, tmp_triangles);
168
169 /* calc texture coordinates automatically */
170 float minv[2];
171 float maxv[2];
172 /* first needs bounding box data */
173 ARRAY_SET_ITEMS(minv, -1.0f, -1.0f);
174 ARRAY_SET_ITEMS(maxv, 1.0f, 1.0f);
175
176 /* calc uv data */
177 gpencil_calc_stroke_fill_uv(points2d, gps, minv, maxv, uv);
178
179 /* Save triangulation data. */
180 if (gps->tot_triangles > 0) {
183 "GP Stroke triangulation");
184
185 for (int i = 0; i < gps->tot_triangles; i++) {
186 memcpy(gps->triangles[i].verts, tmp_triangles[i], sizeof(uint[3]));
187 }
188
189 /* Copy UVs to bGPDspoint. */
190 for (int i = 0; i < gps->totpoints; i++) {
191 copy_v2_v2(gps->points[i].uv_fill, uv[i]);
192 }
193 }
194 else {
195 /* No triangles needed - Free anything allocated previously */
196 if (gps->triangles) {
197 MEM_freeN(gps->triangles);
198 }
199
200 gps->triangles = nullptr;
201 }
202
203 /* clear memory */
204 MEM_SAFE_FREE(tmp_triangles);
205 MEM_SAFE_FREE(points2d);
206 MEM_SAFE_FREE(uv);
207}
208
210{
211 if (gps == nullptr || gps->totpoints == 0) {
212 return;
213 }
214
215 bGPDspoint *pt = gps->points;
216 float totlen = 0.0f;
217 pt[0].uv_fac = totlen;
218 for (int i = 1; i < gps->totpoints; i++) {
219 totlen += len_v3v3(&pt[i - 1].x, &pt[i].x);
220 pt[i].uv_fac = totlen;
221 }
222}
223
225{
226 if (gps == nullptr) {
227 return;
228 }
229
230 if (gps->totpoints > 2) {
232 }
233 else {
234 gps->tot_triangles = 0;
236 }
237
238 /* calc uv data along the stroke */
240}
241
242/* Temp data for storing information about an "island" of points
243 * that should be kept when splitting up a stroke. Used in:
244 * gpencil_stroke_delete_tagged_points()
245 */
250
252 bGPDframe *gpf,
253 bGPDstroke *gps_first,
254 bGPDstroke *gps_last)
255{
256 bGPDspoint *pt = nullptr;
257 bGPDspoint *pt_final = nullptr;
258 const int totpoints = gps_first->totpoints + gps_last->totpoints;
259
260 /* create new stroke */
261 bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true);
262
263 join_stroke->points = MEM_calloc_arrayN<bGPDspoint>(totpoints, __func__);
264 join_stroke->totpoints = totpoints;
265 join_stroke->flag &= ~GP_STROKE_CYCLIC;
266
267 /* copy points (last before) */
268 int e1 = 0;
269 int e2 = 0;
270 float delta = 0.0f;
271
272 for (int i = 0; i < totpoints; i++) {
273 pt_final = &join_stroke->points[i];
274 if (i < gps_last->totpoints) {
275 pt = &gps_last->points[e1];
276 e1++;
277 }
278 else {
279 pt = &gps_first->points[e2];
280 e2++;
281 }
282
283 /* copy current point */
284 copy_v3_v3(&pt_final->x, &pt->x);
285 pt_final->pressure = pt->pressure;
286 pt_final->strength = pt->strength;
287 pt_final->time = delta;
288 pt_final->flag = pt->flag;
289 copy_v4_v4(pt_final->vert_color, pt->vert_color);
290
291 /* retiming with fixed time interval (we cannot determine real time) */
292 delta += 0.01f;
293 }
294
295 /* Copy over vertex weight data (if available) */
296 if ((gps_first->dvert != nullptr) || (gps_last->dvert != nullptr)) {
297 join_stroke->dvert = MEM_calloc_arrayN<MDeformVert>(totpoints, __func__);
298 MDeformVert *dvert_src = nullptr;
299 MDeformVert *dvert_dst = nullptr;
300
301 /* Copy weights (last before). */
302 e1 = 0;
303 e2 = 0;
304 for (int i = 0; i < totpoints; i++) {
305 dvert_dst = &join_stroke->dvert[i];
306 dvert_src = nullptr;
307 if (i < gps_last->totpoints) {
308 if (gps_last->dvert) {
309 dvert_src = &gps_last->dvert[e1];
310 e1++;
311 }
312 }
313 else {
314 if (gps_first->dvert) {
315 dvert_src = &gps_first->dvert[e2];
316 e2++;
317 }
318 }
319
320 if ((dvert_src) && (dvert_src->dw)) {
321 dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
322 }
323 }
324 }
325
326 /* add new stroke at head */
327 BLI_addhead(&gpf->strokes, join_stroke);
328 /* Calc geometry data. */
329 BKE_gpencil_stroke_geometry_update(gpd, join_stroke);
330
331 /* remove first stroke */
332 BLI_remlink(&gpf->strokes, gps_first);
333 BKE_gpencil_free_stroke(gps_first);
334
335 /* remove last stroke */
336 BLI_remlink(&gpf->strokes, gps_last);
337 BKE_gpencil_free_stroke(gps_last);
338}
339
341 bGPDframe *gpf,
342 bGPDstroke *gps,
343 bGPDstroke *next_stroke,
344 int tag_flags,
345 const bool select,
346 const bool flat_cap,
347 const int limit)
348{
349 /* The algorithm used here is as follows:
350 * 1) We firstly identify the number of "islands" of non-tagged points
351 * which will all end up being in new strokes.
352 * - In the most extreme case (i.e. every other vert is a 1-vert island),
353 * we have at most `n / 2` islands
354 * - Once we start having larger islands than that, the number required
355 * becomes much less
356 * 2) Each island gets converted to a new stroke
357 * If the number of points is <= limit, the stroke is deleted. */
358
360 "gp_point_islands");
361 bool in_island = false;
362 int num_islands = 0;
363
364 bGPDstroke *new_stroke = nullptr;
365 bGPDstroke *gps_first = nullptr;
366 const bool is_cyclic = bool(gps->flag & GP_STROKE_CYCLIC);
367
368 /* First Pass: Identify start/end of islands */
369 bGPDspoint *pt = gps->points;
370 for (int i = 0; i < gps->totpoints; i++, pt++) {
371 if (pt->flag & tag_flags) {
372 /* selected - stop accumulating to island */
373 in_island = false;
374 }
375 else {
376 /* unselected - start of a new island? */
377 int idx;
378
379 if (in_island) {
380 /* extend existing island */
381 idx = num_islands - 1;
382 islands[idx].end_idx = i;
383 }
384 else {
385 /* start of new island */
386 in_island = true;
387 num_islands++;
388
389 idx = num_islands - 1;
390 islands[idx].start_idx = islands[idx].end_idx = i;
391 }
392 }
393 }
394
395 /* Watch out for special case where No islands = All points selected = Delete Stroke only */
396 if (num_islands) {
397 /* There are islands, so create a series of new strokes,
398 * adding them before the "next" stroke. */
399 int idx;
400
401 /* Create each new stroke... */
402 for (idx = 0; idx < num_islands; idx++) {
403 tGPDeleteIsland *island = &islands[idx];
404 new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true);
405 if (flat_cap) {
406 new_stroke->caps[1 - (idx % 2)] = GP_STROKE_CAP_FLAT;
407 }
408
409 /* if cyclic and first stroke, save to join later */
410 if ((is_cyclic) && (gps_first == nullptr)) {
411 gps_first = new_stroke;
412 }
413
414 new_stroke->flag &= ~GP_STROKE_CYCLIC;
415
416 /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
417 new_stroke->totpoints = island->end_idx - island->start_idx + 1;
418
419 /* Copy over the relevant point data */
420 new_stroke->points = MEM_calloc_arrayN<bGPDspoint>(new_stroke->totpoints,
421 "gp delete stroke fragment");
422 memcpy(static_cast<void *>(new_stroke->points),
423 gps->points + island->start_idx,
424 sizeof(bGPDspoint) * new_stroke->totpoints);
425
426 /* Copy over vertex weight data (if available) */
427 if (gps->dvert != nullptr) {
428 /* Copy over the relevant vertex-weight points */
429 new_stroke->dvert = MEM_calloc_arrayN<MDeformVert>(new_stroke->totpoints,
430 "gp delete stroke fragment weight");
431 memcpy(new_stroke->dvert,
432 gps->dvert + island->start_idx,
433 sizeof(MDeformVert) * new_stroke->totpoints);
434
435 /* Copy weights */
436 int e = island->start_idx;
437 for (int i = 0; i < new_stroke->totpoints; i++) {
438 MDeformVert *dvert_src = &gps->dvert[e];
439 MDeformVert *dvert_dst = &new_stroke->dvert[i];
440 if (dvert_src->dw) {
441 dvert_dst->dw = (MDeformWeight *)MEM_dupallocN(dvert_src->dw);
442 }
443 e++;
444 }
445 }
446 /* Each island corresponds to a new stroke.
447 * We must adjust the timings of these new strokes:
448 *
449 * Each point's timing data is a delta from stroke's inittime, so as we erase some points
450 * from the start of the stroke, we have to offset this inittime and all remaining points'
451 * delta values. This way we get a new stroke with exactly the same timing as if user had
452 * started drawing from the first non-removed point.
453 */
454 {
455 bGPDspoint *pts;
456 float delta = gps->points[island->start_idx].time;
457 int j;
458
459 new_stroke->inittime += double(delta);
460
461 pts = new_stroke->points;
462 for (j = 0; j < new_stroke->totpoints; j++, pts++) {
463 /* Some points have time = 0, so check to not get negative time values. */
464 pts->time = max_ff(pts->time - delta, 0.0f);
465 /* set flag for select again later */
466 if (select == true) {
467 pts->flag &= ~GP_SPOINT_SELECT;
468 pts->flag |= GP_SPOINT_TAG;
469 }
470 }
471 }
472
473 /* Add new stroke to the frame or delete if below limit */
474 if ((limit > 0) && (new_stroke->totpoints <= limit)) {
475 if (gps_first == new_stroke) {
476 gps_first = nullptr;
477 }
478 BKE_gpencil_free_stroke(new_stroke);
479 }
480 else {
481 /* Calc geometry data. */
482 BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
483
484 if (next_stroke) {
485 BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
486 }
487 else {
488 BLI_addtail(&gpf->strokes, new_stroke);
489 }
490 }
491 }
492 /* if cyclic, need to join last stroke with first stroke */
493 if ((is_cyclic) && (gps_first != nullptr) && (gps_first != new_stroke)) {
494 gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke);
495 }
496 }
497
498 /* free islands */
499 MEM_freeN(islands);
500
501 /* Delete the old stroke */
502 BLI_remlink(&gpf->strokes, gps);
504
505 return new_stroke;
506}
struct bGPDstroke * BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, bool dup_points, bool dup_curve)
void BKE_gpencil_free_stroke(struct bGPDstroke *gps)
#define BLI_assert(a)
Definition BLI_assert.h:46
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
void BLI_addhead(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:91
void BLI_insertlinkbefore(ListBase *listbase, void *vnextlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:371
MINLINE float max_ff(float a, float b)
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
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void copy_v3_v3(float r[3], const float a[3])
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
MINLINE void cross_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f)
MINLINE float normalize_v3(float n[3])
void BLI_polyfill_calc(const float(*coords)[2], unsigned int coords_num, int coords_sign, unsigned int(*r_tris)[3])
unsigned int uint
#define ARRAY_SET_ITEMS(...)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
nullptr float
static bool is_cyclic(const Nurb *nu)
static void gpencil_stroke_join_islands(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last)
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)
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction)
void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
void BKE_gpencil_stroke_geometry_update(bGPdata *, bGPDstroke *gps)
#define sin
#define cos
#define select(A, B, C)
VecBase< float, 3 > cross(VecOp< float, 3 >, VecOp< float, 3 >) RET
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
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
VecBase< float, 3 > float3
struct MDeformWeight * dw
bGPDtriangle * triangles
struct MDeformVert * dvert
unsigned int verts[3]
i
Definition text_draw.cc:230