1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 */
16
17 /** \file
18 * \ingroup bke
19 *
20 * Functions for accessing mesh connectivity data.
21 * eg: polys connected to verts, UV's connected to verts.
22 */
23
24 #include "MEM_guardedalloc.h"
25
26 #include "DNA_meshdata_types.h"
27 #include "DNA_vec_types.h"
28
29 #include "BLI_bitmap.h"
30 #include "BLI_buffer.h"
31 #include "BLI_math.h"
32 #include "BLI_utildefines.h"
33
34 #include "BKE_customdata.h"
35 #include "BKE_mesh_mapping.h"
36 #include "BLI_memarena.h"
37
38 #include "BLI_strict_flags.h"
39
40 /* -------------------------------------------------------------------- */
41 /** \name Mesh Connectivity Mapping
42 * \{ */
43
44 /* ngon version wip, based on BM_uv_vert_map_create */
45 /* this replaces the non bmesh function (in trunk) which takes MTFace's,
46 * if we ever need it back we could but for now this replaces it because its unused. */
47
BKE_mesh_uv_vert_map_create(const MPoly * mpoly,const MLoop * mloop,const MLoopUV * mloopuv,unsigned int totpoly,unsigned int totvert,const float limit[2],const bool selected,const bool use_winding)48 UvVertMap *BKE_mesh_uv_vert_map_create(const MPoly *mpoly,
49 const MLoop *mloop,
50 const MLoopUV *mloopuv,
51 unsigned int totpoly,
52 unsigned int totvert,
53 const float limit[2],
54 const bool selected,
55 const bool use_winding)
56 {
57 UvVertMap *vmap;
58 UvMapVert *buf;
59 const MPoly *mp;
60 unsigned int a;
61 int i, totuv, nverts;
62
63 bool *winding = NULL;
64 BLI_buffer_declare_static(vec2f, tf_uv_buf, BLI_BUFFER_NOP, 32);
65
66 totuv = 0;
67
68 /* generate UvMapVert array */
69 mp = mpoly;
70 for (a = 0; a < totpoly; a++, mp++) {
71 if (!selected || (!(mp->flag & ME_HIDE) && (mp->flag & ME_FACE_SEL))) {
72 totuv += mp->totloop;
73 }
74 }
75
76 if (totuv == 0) {
77 return NULL;
78 }
79
80 vmap = (UvVertMap *)MEM_callocN(sizeof(*vmap), "UvVertMap");
81 buf = vmap->buf = (UvMapVert *)MEM_callocN(sizeof(*vmap->buf) * (size_t)totuv, "UvMapVert");
82 vmap->vert = (UvMapVert **)MEM_callocN(sizeof(*vmap->vert) * totvert, "UvMapVert*");
83 if (use_winding) {
84 winding = MEM_callocN(sizeof(*winding) * totpoly, "winding");
85 }
86
87 if (!vmap->vert || !vmap->buf) {
88 BKE_mesh_uv_vert_map_free(vmap);
89 return NULL;
90 }
91
92 mp = mpoly;
93 for (a = 0; a < totpoly; a++, mp++) {
94 if (!selected || (!(mp->flag & ME_HIDE) && (mp->flag & ME_FACE_SEL))) {
95 float(*tf_uv)[2] = NULL;
96
97 if (use_winding) {
98 tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, (size_t)mp->totloop);
99 }
100
101 nverts = mp->totloop;
102
103 for (i = 0; i < nverts; i++) {
104 buf->loop_of_poly_index = (unsigned short)i;
105 buf->poly_index = a;
106 buf->separate = 0;
107 buf->next = vmap->vert[mloop[mp->loopstart + i].v];
108 vmap->vert[mloop[mp->loopstart + i].v] = buf;
109
110 if (use_winding) {
111 copy_v2_v2(tf_uv[i], mloopuv[mpoly[a].loopstart + i].uv);
112 }
113
114 buf++;
115 }
116
117 if (use_winding) {
118 winding[a] = cross_poly_v2(tf_uv, (unsigned int)nverts) > 0;
119 }
120 }
121 }
122
123 /* sort individual uvs for each vert */
124 for (a = 0; a < totvert; a++) {
125 UvMapVert *newvlist = NULL, *vlist = vmap->vert[a];
126 UvMapVert *iterv, *v, *lastv, *next;
127 const float *uv, *uv2;
128 float uvdiff[2];
129
130 while (vlist) {
131 v = vlist;
132 vlist = vlist->next;
133 v->next = newvlist;
134 newvlist = v;
135
136 uv = mloopuv[mpoly[v->poly_index].loopstart + v->loop_of_poly_index].uv;
137 lastv = NULL;
138 iterv = vlist;
139
140 while (iterv) {
141 next = iterv->next;
142
143 uv2 = mloopuv[mpoly[iterv->poly_index].loopstart + iterv->loop_of_poly_index].uv;
144 sub_v2_v2v2(uvdiff, uv2, uv);
145
146 if (fabsf(uv[0] - uv2[0]) < limit[0] && fabsf(uv[1] - uv2[1]) < limit[1] &&
147 (!use_winding || winding[iterv->poly_index] == winding[v->poly_index])) {
148 if (lastv) {
149 lastv->next = next;
150 }
151 else {
152 vlist = next;
153 }
154 iterv->next = newvlist;
155 newvlist = iterv;
156 }
157 else {
158 lastv = iterv;
159 }
160
161 iterv = next;
162 }
163
164 newvlist->separate = 1;
165 }
166
167 vmap->vert[a] = newvlist;
168 }
169
170 if (use_winding) {
171 MEM_freeN(winding);
172 }
173
174 BLI_buffer_free(&tf_uv_buf);
175
176 return vmap;
177 }
178
BKE_mesh_uv_vert_map_get_vert(UvVertMap * vmap,unsigned int v)179 UvMapVert *BKE_mesh_uv_vert_map_get_vert(UvVertMap *vmap, unsigned int v)
180 {
181 return vmap->vert[v];
182 }
183
BKE_mesh_uv_vert_map_free(UvVertMap * vmap)184 void BKE_mesh_uv_vert_map_free(UvVertMap *vmap)
185 {
186 if (vmap) {
187 if (vmap->vert) {
188 MEM_freeN(vmap->vert);
189 }
190 if (vmap->buf) {
191 MEM_freeN(vmap->buf);
192 }
193 MEM_freeN(vmap);
194 }
195 }
196
197 /**
198 * Generates a map where the key is the vertex and the value is a list
199 * of polys or loops that use that vertex as a corner. The lists are allocated
200 * from one memory pool.
201 *
202 * Wrapped by #BKE_mesh_vert_poly_map_create & BKE_mesh_vert_loop_map_create
203 */
mesh_vert_poly_or_loop_map_create(MeshElemMap ** r_map,int ** r_mem,const MPoly * mpoly,const MLoop * mloop,int totvert,int totpoly,int totloop,const bool do_loops)204 static void mesh_vert_poly_or_loop_map_create(MeshElemMap **r_map,
205 int **r_mem,
206 const MPoly *mpoly,
207 const MLoop *mloop,
208 int totvert,
209 int totpoly,
210 int totloop,
211 const bool do_loops)
212 {
213 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totvert, __func__);
214 int *indices, *index_iter;
215 int i, j;
216
217 indices = index_iter = MEM_mallocN(sizeof(int) * (size_t)totloop, __func__);
218
219 /* Count number of polys for each vertex */
220 for (i = 0; i < totpoly; i++) {
221 const MPoly *p = &mpoly[i];
222
223 for (j = 0; j < p->totloop; j++) {
224 map[mloop[p->loopstart + j].v].count++;
225 }
226 }
227
228 /* Assign indices mem */
229 for (i = 0; i < totvert; i++) {
230 map[i].indices = index_iter;
231 index_iter += map[i].count;
232
233 /* Reset 'count' for use as index in last loop */
234 map[i].count = 0;
235 }
236
237 /* Find the users */
238 for (i = 0; i < totpoly; i++) {
239 const MPoly *p = &mpoly[i];
240
241 for (j = 0; j < p->totloop; j++) {
242 unsigned int v = mloop[p->loopstart + j].v;
243
244 map[v].indices[map[v].count] = do_loops ? p->loopstart + j : i;
245 map[v].count++;
246 }
247 }
248
249 *r_map = map;
250 *r_mem = indices;
251 }
252
253 /**
254 * Generates a map where the key is the vertex and the value
255 * is a list of polys that use that vertex as a corner.
256 * The lists are allocated from one memory pool.
257 */
BKE_mesh_vert_poly_map_create(MeshElemMap ** r_map,int ** r_mem,const MPoly * mpoly,const MLoop * mloop,int totvert,int totpoly,int totloop)258 void BKE_mesh_vert_poly_map_create(MeshElemMap **r_map,
259 int **r_mem,
260 const MPoly *mpoly,
261 const MLoop *mloop,
262 int totvert,
263 int totpoly,
264 int totloop)
265 {
266 mesh_vert_poly_or_loop_map_create(r_map, r_mem, mpoly, mloop, totvert, totpoly, totloop, false);
267 }
268
269 /**
270 * Generates a map where the key is the vertex and the value
271 * is a list of loops that use that vertex as a corner.
272 * The lists are allocated from one memory pool.
273 */
BKE_mesh_vert_loop_map_create(MeshElemMap ** r_map,int ** r_mem,const MPoly * mpoly,const MLoop * mloop,int totvert,int totpoly,int totloop)274 void BKE_mesh_vert_loop_map_create(MeshElemMap **r_map,
275 int **r_mem,
276 const MPoly *mpoly,
277 const MLoop *mloop,
278 int totvert,
279 int totpoly,
280 int totloop)
281 {
282 mesh_vert_poly_or_loop_map_create(r_map, r_mem, mpoly, mloop, totvert, totpoly, totloop, true);
283 }
284
285 /**
286 * Generates a map where the key is the edge and the value
287 * is a list of looptris that use that edge.
288 * The lists are allocated from one memory pool.
289 */
BKE_mesh_vert_looptri_map_create(MeshElemMap ** r_map,int ** r_mem,const MVert * UNUSED (mvert),const int totvert,const MLoopTri * mlooptri,const int totlooptri,const MLoop * mloop,const int UNUSED (totloop))290 void BKE_mesh_vert_looptri_map_create(MeshElemMap **r_map,
291 int **r_mem,
292 const MVert *UNUSED(mvert),
293 const int totvert,
294 const MLoopTri *mlooptri,
295 const int totlooptri,
296 const MLoop *mloop,
297 const int UNUSED(totloop))
298 {
299 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totvert, __func__);
300 int *indices = MEM_mallocN(sizeof(int) * (size_t)totlooptri * 3, __func__);
301 int *index_step;
302 const MLoopTri *mlt;
303 int i;
304
305 /* count face users */
306 for (i = 0, mlt = mlooptri; i < totlooptri; mlt++, i++) {
307 for (int j = 3; j--;) {
308 map[mloop[mlt->tri[j]].v].count++;
309 }
310 }
311
312 /* create offsets */
313 index_step = indices;
314 for (i = 0; i < totvert; i++) {
315 map[i].indices = index_step;
316 index_step += map[i].count;
317
318 /* re-count, using this as an index below */
319 map[i].count = 0;
320 }
321
322 /* assign looptri-edge users */
323 for (i = 0, mlt = mlooptri; i < totlooptri; mlt++, i++) {
324 for (int j = 3; j--;) {
325 MeshElemMap *map_ele = &map[mloop[mlt->tri[j]].v];
326 map_ele->indices[map_ele->count++] = i;
327 }
328 }
329
330 *r_map = map;
331 *r_mem = indices;
332 }
333
334 /**
335 * Generates a map where the key is the vertex and the value
336 * is a list of edges that use that vertex as an endpoint.
337 * The lists are allocated from one memory pool.
338 */
BKE_mesh_vert_edge_map_create(MeshElemMap ** r_map,int ** r_mem,const MEdge * medge,int totvert,int totedge)339 void BKE_mesh_vert_edge_map_create(
340 MeshElemMap **r_map, int **r_mem, const MEdge *medge, int totvert, int totedge)
341 {
342 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totvert, "vert-edge map");
343 int *indices = MEM_mallocN(sizeof(int[2]) * (size_t)totedge, "vert-edge map mem");
344 int *i_pt = indices;
345
346 int i;
347
348 /* Count number of edges for each vertex */
349 for (i = 0; i < totedge; i++) {
350 map[medge[i].v1].count++;
351 map[medge[i].v2].count++;
352 }
353
354 /* Assign indices mem */
355 for (i = 0; i < totvert; i++) {
356 map[i].indices = i_pt;
357 i_pt += map[i].count;
358
359 /* Reset 'count' for use as index in last loop */
360 map[i].count = 0;
361 }
362
363 /* Find the users */
364 for (i = 0; i < totedge; i++) {
365 const unsigned int v[2] = {medge[i].v1, medge[i].v2};
366
367 map[v[0]].indices[map[v[0]].count] = i;
368 map[v[1]].indices[map[v[1]].count] = i;
369
370 map[v[0]].count++;
371 map[v[1]].count++;
372 }
373
374 *r_map = map;
375 *r_mem = indices;
376 }
377
378 /**
379 * A version of #BKE_mesh_vert_edge_map_create that references connected vertices directly
380 * (not their edges).
381 */
BKE_mesh_vert_edge_vert_map_create(MeshElemMap ** r_map,int ** r_mem,const MEdge * medge,int totvert,int totedge)382 void BKE_mesh_vert_edge_vert_map_create(
383 MeshElemMap **r_map, int **r_mem, const MEdge *medge, int totvert, int totedge)
384 {
385 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totvert, "vert-edge map");
386 int *indices = MEM_mallocN(sizeof(int[2]) * (size_t)totedge, "vert-edge map mem");
387 int *i_pt = indices;
388
389 int i;
390
391 /* Count number of edges for each vertex */
392 for (i = 0; i < totedge; i++) {
393 map[medge[i].v1].count++;
394 map[medge[i].v2].count++;
395 }
396
397 /* Assign indices mem */
398 for (i = 0; i < totvert; i++) {
399 map[i].indices = i_pt;
400 i_pt += map[i].count;
401
402 /* Reset 'count' for use as index in last loop */
403 map[i].count = 0;
404 }
405
406 /* Find the users */
407 for (i = 0; i < totedge; i++) {
408 const unsigned int v[2] = {medge[i].v1, medge[i].v2};
409
410 map[v[0]].indices[map[v[0]].count] = (int)v[1];
411 map[v[1]].indices[map[v[1]].count] = (int)v[0];
412
413 map[v[0]].count++;
414 map[v[1]].count++;
415 }
416
417 *r_map = map;
418 *r_mem = indices;
419 }
420
421 /**
422 * Generates a map where the key is the edge and the value is a list of loops that use that edge.
423 * Loops indices of a same poly are contiguous and in winding order.
424 * The lists are allocated from one memory pool.
425 */
BKE_mesh_edge_loop_map_create(MeshElemMap ** r_map,int ** r_mem,const MEdge * UNUSED (medge),const int totedge,const MPoly * mpoly,const int totpoly,const MLoop * mloop,const int totloop)426 void BKE_mesh_edge_loop_map_create(MeshElemMap **r_map,
427 int **r_mem,
428 const MEdge *UNUSED(medge),
429 const int totedge,
430 const MPoly *mpoly,
431 const int totpoly,
432 const MLoop *mloop,
433 const int totloop)
434 {
435 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totedge, "edge-poly map");
436 int *indices = MEM_mallocN(sizeof(int) * (size_t)totloop * 2, "edge-poly map mem");
437 int *index_step;
438 const MPoly *mp;
439 int i;
440
441 /* count face users */
442 for (i = 0, mp = mpoly; i < totpoly; mp++, i++) {
443 const MLoop *ml;
444 int j = mp->totloop;
445 for (ml = &mloop[mp->loopstart]; j--; ml++) {
446 map[ml->e].count += 2;
447 }
448 }
449
450 /* create offsets */
451 index_step = indices;
452 for (i = 0; i < totedge; i++) {
453 map[i].indices = index_step;
454 index_step += map[i].count;
455
456 /* re-count, using this as an index below */
457 map[i].count = 0;
458 }
459
460 /* assign loop-edge users */
461 for (i = 0, mp = mpoly; i < totpoly; mp++, i++) {
462 const MLoop *ml;
463 MeshElemMap *map_ele;
464 const int max_loop = mp->loopstart + mp->totloop;
465 int j = mp->loopstart;
466 for (ml = &mloop[j]; j < max_loop; j++, ml++) {
467 map_ele = &map[ml->e];
468 map_ele->indices[map_ele->count++] = j;
469 map_ele->indices[map_ele->count++] = j + 1;
470 }
471 /* last edge/loop of poly, must point back to first loop! */
472 map_ele->indices[map_ele->count - 1] = mp->loopstart;
473 }
474
475 *r_map = map;
476 *r_mem = indices;
477 }
478
479 /**
480 * Generates a map where the key is the edge and the value
481 * is a list of polygons that use that edge.
482 * The lists are allocated from one memory pool.
483 */
BKE_mesh_edge_poly_map_create(MeshElemMap ** r_map,int ** r_mem,const MEdge * UNUSED (medge),const int totedge,const MPoly * mpoly,const int totpoly,const MLoop * mloop,const int totloop)484 void BKE_mesh_edge_poly_map_create(MeshElemMap **r_map,
485 int **r_mem,
486 const MEdge *UNUSED(medge),
487 const int totedge,
488 const MPoly *mpoly,
489 const int totpoly,
490 const MLoop *mloop,
491 const int totloop)
492 {
493 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totedge, "edge-poly map");
494 int *indices = MEM_mallocN(sizeof(int) * (size_t)totloop, "edge-poly map mem");
495 int *index_step;
496 const MPoly *mp;
497 int i;
498
499 /* count face users */
500 for (i = 0, mp = mpoly; i < totpoly; mp++, i++) {
501 const MLoop *ml;
502 int j = mp->totloop;
503 for (ml = &mloop[mp->loopstart]; j--; ml++) {
504 map[ml->e].count++;
505 }
506 }
507
508 /* create offsets */
509 index_step = indices;
510 for (i = 0; i < totedge; i++) {
511 map[i].indices = index_step;
512 index_step += map[i].count;
513
514 /* re-count, using this as an index below */
515 map[i].count = 0;
516 }
517
518 /* assign poly-edge users */
519 for (i = 0, mp = mpoly; i < totpoly; mp++, i++) {
520 const MLoop *ml;
521 int j = mp->totloop;
522 for (ml = &mloop[mp->loopstart]; j--; ml++) {
523 MeshElemMap *map_ele = &map[ml->e];
524 map_ele->indices[map_ele->count++] = i;
525 }
526 }
527
528 *r_map = map;
529 *r_mem = indices;
530 }
531
532 /**
533 * This function creates a map so the source-data (vert/edge/loop/poly)
534 * can loop over the destination data (using the destination arrays origindex).
535 *
536 * This has the advantage that it can operate on any data-types.
537 *
538 * \param totsource: The total number of elements the that \a final_origindex points to.
539 * \param totfinal: The size of \a final_origindex
540 * \param final_origindex: The size of the final array.
541 *
542 * \note ``totsource`` could be ``totpoly``,
543 * ``totfinal`` could be ``tottessface`` and ``final_origindex`` its ORIGINDEX customdata.
544 * This would allow an MPoly to loop over its tessfaces.
545 */
BKE_mesh_origindex_map_create(MeshElemMap ** r_map,int ** r_mem,const int totsource,const int * final_origindex,const int totfinal)546 void BKE_mesh_origindex_map_create(MeshElemMap **r_map,
547 int **r_mem,
548 const int totsource,
549 const int *final_origindex,
550 const int totfinal)
551 {
552 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)totsource, "poly-tessface map");
553 int *indices = MEM_mallocN(sizeof(int) * (size_t)totfinal, "poly-tessface map mem");
554 int *index_step;
555 int i;
556
557 /* count face users */
558 for (i = 0; i < totfinal; i++) {
559 if (final_origindex[i] != ORIGINDEX_NONE) {
560 BLI_assert(final_origindex[i] < totsource);
561 map[final_origindex[i]].count++;
562 }
563 }
564
565 /* create offsets */
566 index_step = indices;
567 for (i = 0; i < totsource; i++) {
568 map[i].indices = index_step;
569 index_step += map[i].count;
570
571 /* re-count, using this as an index below */
572 map[i].count = 0;
573 }
574
575 /* assign poly-tessface users */
576 for (i = 0; i < totfinal; i++) {
577 if (final_origindex[i] != ORIGINDEX_NONE) {
578 MeshElemMap *map_ele = &map[final_origindex[i]];
579 map_ele->indices[map_ele->count++] = i;
580 }
581 }
582
583 *r_map = map;
584 *r_mem = indices;
585 }
586
587 /**
588 * A version of #BKE_mesh_origindex_map_create that takes a looptri array.
589 * Making a poly -> looptri map.
590 */
BKE_mesh_origindex_map_create_looptri(MeshElemMap ** r_map,int ** r_mem,const MPoly * mpoly,const int mpoly_num,const MLoopTri * looptri,const int looptri_num)591 void BKE_mesh_origindex_map_create_looptri(MeshElemMap **r_map,
592 int **r_mem,
593 const MPoly *mpoly,
594 const int mpoly_num,
595 const MLoopTri *looptri,
596 const int looptri_num)
597 {
598 MeshElemMap *map = MEM_callocN(sizeof(MeshElemMap) * (size_t)mpoly_num, "poly-tessface map");
599 int *indices = MEM_mallocN(sizeof(int) * (size_t)looptri_num, "poly-tessface map mem");
600 int *index_step;
601 int i;
602
603 /* create offsets */
604 index_step = indices;
605 for (i = 0; i < mpoly_num; i++) {
606 map[i].indices = index_step;
607 index_step += ME_POLY_TRI_TOT(&mpoly[i]);
608 }
609
610 /* assign poly-tessface users */
611 for (i = 0; i < looptri_num; i++) {
612 MeshElemMap *map_ele = &map[looptri[i].poly];
613 map_ele->indices[map_ele->count++] = i;
614 }
615
616 *r_map = map;
617 *r_mem = indices;
618 }
619
620 /** \} */
621
622 /* -------------------------------------------------------------------- */
623 /** \name Mesh loops/poly islands.
624 * Used currently for UVs and 'smooth groups'.
625 * \{ */
626
627 /**
628 * Callback deciding whether the given poly/loop/edge define an island boundary or not.
629 */
630 typedef bool (*MeshRemap_CheckIslandBoundary)(const struct MPoly *mpoly,
631 const struct MLoop *mloop,
632 const struct MEdge *medge,
633 const int nbr_egde_users,
634 const struct MPoly *mpoly_array,
635 const struct MeshElemMap *edge_poly_map,
636 void *user_data);
637
poly_edge_loop_islands_calc(const MEdge * medge,const int totedge,const MPoly * mpoly,const int totpoly,const MLoop * mloop,const int totloop,MeshElemMap * edge_poly_map,const bool use_bitflags,MeshRemap_CheckIslandBoundary edge_boundary_check,void * edge_boundary_check_data,int ** r_poly_groups,int * r_totgroup,BLI_bitmap ** r_edge_borders,int * r_totedgeborder)638 static void poly_edge_loop_islands_calc(const MEdge *medge,
639 const int totedge,
640 const MPoly *mpoly,
641 const int totpoly,
642 const MLoop *mloop,
643 const int totloop,
644 MeshElemMap *edge_poly_map,
645 const bool use_bitflags,
646 MeshRemap_CheckIslandBoundary edge_boundary_check,
647 void *edge_boundary_check_data,
648 int **r_poly_groups,
649 int *r_totgroup,
650 BLI_bitmap **r_edge_borders,
651 int *r_totedgeborder)
652 {
653 int *poly_groups;
654 int *poly_stack;
655
656 BLI_bitmap *edge_borders = NULL;
657 int num_edgeborders = 0;
658
659 int poly_prev = 0;
660 const int temp_poly_group_id = 3; /* Placeholder value. */
661
662 /* Group we could not find any available bit, will be reset to 0 at end. */
663 const int poly_group_id_overflowed = 5;
664
665 int tot_group = 0;
666 bool group_id_overflow = false;
667
668 /* map vars */
669 int *edge_poly_mem = NULL;
670
671 if (totpoly == 0) {
672 *r_totgroup = 0;
673 *r_poly_groups = NULL;
674 if (r_edge_borders) {
675 *r_edge_borders = NULL;
676 *r_totedgeborder = 0;
677 }
678 return;
679 }
680
681 if (r_edge_borders) {
682 edge_borders = BLI_BITMAP_NEW(totedge, __func__);
683 *r_totedgeborder = 0;
684 }
685
686 if (!edge_poly_map) {
687 BKE_mesh_edge_poly_map_create(
688 &edge_poly_map, &edge_poly_mem, medge, totedge, mpoly, totpoly, mloop, totloop);
689 }
690
691 poly_groups = MEM_callocN(sizeof(int) * (size_t)totpoly, __func__);
692 poly_stack = MEM_mallocN(sizeof(int) * (size_t)totpoly, __func__);
693
694 while (true) {
695 int poly;
696 int bit_poly_group_mask = 0;
697 int poly_group_id;
698 int ps_curr_idx = 0, ps_end_idx = 0; /* stack indices */
699
700 for (poly = poly_prev; poly < totpoly; poly++) {
701 if (poly_groups[poly] == 0) {
702 break;
703 }
704 }
705
706 if (poly == totpoly) {
707 /* all done */
708 break;
709 }
710
711 poly_group_id = use_bitflags ? temp_poly_group_id : ++tot_group;
712
713 /* start searching from here next time */
714 poly_prev = poly + 1;
715
716 poly_groups[poly] = poly_group_id;
717 poly_stack[ps_end_idx++] = poly;
718
719 while (ps_curr_idx != ps_end_idx) {
720 const MPoly *mp;
721 const MLoop *ml;
722 int j;
723
724 poly = poly_stack[ps_curr_idx++];
725 BLI_assert(poly_groups[poly] == poly_group_id);
726
727 mp = &mpoly[poly];
728 for (ml = &mloop[mp->loopstart], j = mp->totloop; j--; ml++) {
729 /* loop over poly users */
730 const int me_idx = (int)ml->e;
731 const MEdge *me = &medge[me_idx];
732 const MeshElemMap *map_ele = &edge_poly_map[me_idx];
733 const int *p = map_ele->indices;
734 int i = map_ele->count;
735 if (!edge_boundary_check(mp, ml, me, i, mpoly, map_ele, edge_boundary_check_data)) {
736 for (; i--; p++) {
737 /* if we meet other non initialized its a bug */
738 BLI_assert(ELEM(poly_groups[*p], 0, poly_group_id));
739
740 if (poly_groups[*p] == 0) {
741 poly_groups[*p] = poly_group_id;
742 poly_stack[ps_end_idx++] = *p;
743 }
744 }
745 }
746 else {
747 if (edge_borders && !BLI_BITMAP_TEST(edge_borders, me_idx)) {
748 BLI_BITMAP_ENABLE(edge_borders, me_idx);
749 num_edgeborders++;
750 }
751 if (use_bitflags) {
752 /* Find contiguous smooth groups already assigned,
753 * these are the values we can't reuse! */
754 for (; i--; p++) {
755 int bit = poly_groups[*p];
756 if (!ELEM(bit, 0, poly_group_id, poly_group_id_overflowed) &&
757 !(bit_poly_group_mask & bit)) {
758 bit_poly_group_mask |= bit;
759 }
760 }
761 }
762 }
763 }
764 }
765 /* And now, we have all our poly from current group in poly_stack
766 * (from 0 to (ps_end_idx - 1)),
767 * as well as all smoothgroups bits we can't use in bit_poly_group_mask.
768 */
769 if (use_bitflags) {
770 int i, *p, gid_bit = 0;
771 poly_group_id = 1;
772
773 /* Find first bit available! */
774 for (; (poly_group_id & bit_poly_group_mask) && (gid_bit < 32); gid_bit++) {
775 poly_group_id <<= 1; /* will 'overflow' on last possible iteration. */
776 }
777 if (UNLIKELY(gid_bit > 31)) {
778 /* All bits used in contiguous smooth groups, we can't do much!
779 * Note: this is *very* unlikely - theoretically, four groups are enough,
780 * I don't think we can reach this goal with such a simple algo,
781 * but I don't think either we'll never need all 32 groups!
782 */
783 printf(
784 "Warning, could not find an available id for current smooth group, faces will me "
785 "marked "
786 "as out of any smooth group...\n");
787
788 /* Can't use 0, will have to set them to this value later. */
789 poly_group_id = poly_group_id_overflowed;
790
791 group_id_overflow = true;
792 }
793 if (gid_bit > tot_group) {
794 tot_group = gid_bit;
795 }
796 /* And assign the final smooth group id to that poly group! */
797 for (i = ps_end_idx, p = poly_stack; i--; p++) {
798 poly_groups[*p] = poly_group_id;
799 }
800 }
801 }
802
803 if (use_bitflags) {
804 /* used bits are zero-based. */
805 tot_group++;
806 }
807
808 if (UNLIKELY(group_id_overflow)) {
809 int i = totpoly, *gid = poly_groups;
810 for (; i--; gid++) {
811 if (*gid == poly_group_id_overflowed) {
812 *gid = 0;
813 }
814 }
815 /* Using 0 as group id adds one more group! */
816 tot_group++;
817 }
818
819 if (edge_poly_mem) {
820 MEM_freeN(edge_poly_map);
821 MEM_freeN(edge_poly_mem);
822 }
823 MEM_freeN(poly_stack);
824
825 *r_totgroup = tot_group;
826 *r_poly_groups = poly_groups;
827 if (r_edge_borders) {
828 *r_edge_borders = edge_borders;
829 *r_totedgeborder = num_edgeborders;
830 }
831 }
832
poly_is_island_boundary_smooth_cb(const MPoly * mp,const MLoop * UNUSED (ml),const MEdge * me,const int nbr_egde_users,const MPoly * mpoly_array,const MeshElemMap * edge_poly_map,void * UNUSED (user_data))833 static bool poly_is_island_boundary_smooth_cb(const MPoly *mp,
834 const MLoop *UNUSED(ml),
835 const MEdge *me,
836 const int nbr_egde_users,
837 const MPoly *mpoly_array,
838 const MeshElemMap *edge_poly_map,
839 void *UNUSED(user_data))
840 {
841 /* Edge is sharp if one of its polys is flat, or edge itself is sharp,
842 * or edge is not used by exactly two polygons. */
843 if ((mp->flag & ME_SMOOTH) && !(me->flag & ME_SHARP) && (nbr_egde_users == 2)) {
844 /* In that case, edge appears to be smooth, but we need to check its other poly too. */
845 const MPoly *mp_other = (mp == &mpoly_array[edge_poly_map->indices[0]]) ?
846 &mpoly_array[edge_poly_map->indices[1]] :
847 &mpoly_array[edge_poly_map->indices[0]];
848 return (mp_other->flag & ME_SMOOTH) == 0;
849 }
850 return true;
851 }
852
853 /**
854 * Calculate smooth groups from sharp edges.
855 *
856 * \param r_totgroup: The total number of groups, 1 or more.
857 * \return Polygon aligned array of group index values (bitflags if use_bitflags is true),
858 * starting at 1 (0 being used as 'invalid' flag).
859 * Note it's callers's responsibility to MEM_freeN returned array.
860 */
BKE_mesh_calc_smoothgroups(const MEdge * medge,const int totedge,const MPoly * mpoly,const int totpoly,const MLoop * mloop,const int totloop,int * r_totgroup,const bool use_bitflags)861 int *BKE_mesh_calc_smoothgroups(const MEdge *medge,
862 const int totedge,
863 const MPoly *mpoly,
864 const int totpoly,
865 const MLoop *mloop,
866 const int totloop,
867 int *r_totgroup,
868 const bool use_bitflags)
869 {
870 int *poly_groups = NULL;
871
872 poly_edge_loop_islands_calc(medge,
873 totedge,
874 mpoly,
875 totpoly,
876 mloop,
877 totloop,
878 NULL,
879 use_bitflags,
880 poly_is_island_boundary_smooth_cb,
881 NULL,
882 &poly_groups,
883 r_totgroup,
884 NULL,
885 NULL);
886
887 return poly_groups;
888 }
889
890 #define MISLAND_DEFAULT_BUFSIZE 64
891
BKE_mesh_loop_islands_init(MeshIslandStore * island_store,const short item_type,const int items_num,const short island_type,const short innercut_type)892 void BKE_mesh_loop_islands_init(MeshIslandStore *island_store,
893 const short item_type,
894 const int items_num,
895 const short island_type,
896 const short innercut_type)
897 {
898 MemArena *mem = island_store->mem;
899
900 if (mem == NULL) {
901 mem = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
902 island_store->mem = mem;
903 }
904 /* else memarena should be cleared */
905
906 BLI_assert(
907 ELEM(item_type, MISLAND_TYPE_VERT, MISLAND_TYPE_EDGE, MISLAND_TYPE_POLY, MISLAND_TYPE_LOOP));
908 BLI_assert(ELEM(
909 island_type, MISLAND_TYPE_VERT, MISLAND_TYPE_EDGE, MISLAND_TYPE_POLY, MISLAND_TYPE_LOOP));
910
911 island_store->item_type = item_type;
912 island_store->items_to_islands_num = items_num;
913 island_store->items_to_islands = BLI_memarena_alloc(
914 mem, sizeof(*island_store->items_to_islands) * (size_t)items_num);
915
916 island_store->island_type = island_type;
917 island_store->islands_num_alloc = MISLAND_DEFAULT_BUFSIZE;
918 island_store->islands = BLI_memarena_alloc(
919 mem, sizeof(*island_store->islands) * island_store->islands_num_alloc);
920
921 island_store->innercut_type = innercut_type;
922 island_store->innercuts = BLI_memarena_alloc(
923 mem, sizeof(*island_store->innercuts) * island_store->islands_num_alloc);
924 }
925
BKE_mesh_loop_islands_clear(MeshIslandStore * island_store)926 void BKE_mesh_loop_islands_clear(MeshIslandStore *island_store)
927 {
928 island_store->item_type = MISLAND_TYPE_NONE;
929 island_store->items_to_islands_num = 0;
930 island_store->items_to_islands = NULL;
931
932 island_store->island_type = MISLAND_TYPE_NONE;
933 island_store->islands_num = 0;
934 island_store->islands = NULL;
935
936 island_store->innercut_type = MISLAND_TYPE_NONE;
937 island_store->innercuts = NULL;
938
939 if (island_store->mem) {
940 BLI_memarena_clear(island_store->mem);
941 }
942
943 island_store->islands_num_alloc = 0;
944 }
945
BKE_mesh_loop_islands_free(MeshIslandStore * island_store)946 void BKE_mesh_loop_islands_free(MeshIslandStore *island_store)
947 {
948 if (island_store->mem) {
949 BLI_memarena_free(island_store->mem);
950 island_store->mem = NULL;
951 }
952 }
953
BKE_mesh_loop_islands_add(MeshIslandStore * island_store,const int item_num,const int * items_indices,const int num_island_items,int * island_item_indices,const int num_innercut_items,int * innercut_item_indices)954 void BKE_mesh_loop_islands_add(MeshIslandStore *island_store,
955 const int item_num,
956 const int *items_indices,
957 const int num_island_items,
958 int *island_item_indices,
959 const int num_innercut_items,
960 int *innercut_item_indices)
961 {
962 MemArena *mem = island_store->mem;
963
964 MeshElemMap *isld, *innrcut;
965 const int curr_island_idx = island_store->islands_num++;
966 const size_t curr_num_islands = (size_t)island_store->islands_num;
967 int i = item_num;
968
969 while (i--) {
970 island_store->items_to_islands[items_indices[i]] = curr_island_idx;
971 }
972
973 if (UNLIKELY(curr_num_islands > island_store->islands_num_alloc)) {
974 MeshElemMap **islds, **innrcuts;
975
976 island_store->islands_num_alloc *= 2;
977 islds = BLI_memarena_alloc(mem, sizeof(*islds) * island_store->islands_num_alloc);
978 memcpy(islds, island_store->islands, sizeof(*islds) * (curr_num_islands - 1));
979 island_store->islands = islds;
980
981 innrcuts = BLI_memarena_alloc(mem, sizeof(*innrcuts) * island_store->islands_num_alloc);
982 memcpy(innrcuts, island_store->innercuts, sizeof(*innrcuts) * (curr_num_islands - 1));
983 island_store->innercuts = innrcuts;
984 }
985
986 island_store->islands[curr_island_idx] = isld = BLI_memarena_alloc(mem, sizeof(*isld));
987 isld->count = num_island_items;
988 isld->indices = BLI_memarena_alloc(mem, sizeof(*isld->indices) * (size_t)num_island_items);
989 memcpy(isld->indices, island_item_indices, sizeof(*isld->indices) * (size_t)num_island_items);
990
991 island_store->innercuts[curr_island_idx] = innrcut = BLI_memarena_alloc(mem, sizeof(*innrcut));
992 innrcut->count = num_innercut_items;
993 innrcut->indices = BLI_memarena_alloc(mem,
994 sizeof(*innrcut->indices) * (size_t)num_innercut_items);
995 memcpy(innrcut->indices,
996 innercut_item_indices,
997 sizeof(*innrcut->indices) * (size_t)num_innercut_items);
998 }
999
1000 /* TODO: I'm not sure edge seam flag is enough to define UV islands?
1001 * Maybe we should also consider UVmaps values
1002 * themselves (i.e. different UV-edges for a same mesh-edge => boundary edge too?).
1003 * Would make things much more complex though,
1004 * and each UVMap would then need its own mesh mapping, not sure we want that at all!
1005 */
1006 typedef struct MeshCheckIslandBoundaryUv {
1007 const MLoop *loops;
1008 const MLoopUV *luvs;
1009 const MeshElemMap *edge_loop_map;
1010 } MeshCheckIslandBoundaryUv;
1011
mesh_check_island_boundary_uv(const MPoly * UNUSED (mp),const MLoop * ml,const MEdge * me,const int UNUSED (nbr_egde_users),const MPoly * UNUSED (mpoly_array),const MeshElemMap * UNUSED (edge_poly_map),void * user_data)1012 static bool mesh_check_island_boundary_uv(const MPoly *UNUSED(mp),
1013 const MLoop *ml,
1014 const MEdge *me,
1015 const int UNUSED(nbr_egde_users),
1016 const MPoly *UNUSED(mpoly_array),
1017 const MeshElemMap *UNUSED(edge_poly_map),
1018 void *user_data)
1019 {
1020 if (user_data) {
1021 const MeshCheckIslandBoundaryUv *data = user_data;
1022 const MLoop *loops = data->loops;
1023 const MLoopUV *luvs = data->luvs;
1024 const MeshElemMap *edge_to_loops = &data->edge_loop_map[ml->e];
1025
1026 BLI_assert(edge_to_loops->count >= 2 && (edge_to_loops->count % 2) == 0);
1027
1028 const unsigned int v1 = loops[edge_to_loops->indices[0]].v;
1029 const unsigned int v2 = loops[edge_to_loops->indices[1]].v;
1030 const float *uvco_v1 = luvs[edge_to_loops->indices[0]].uv;
1031 const float *uvco_v2 = luvs[edge_to_loops->indices[1]].uv;
1032 for (int i = 2; i < edge_to_loops->count; i += 2) {
1033 if (loops[edge_to_loops->indices[i]].v == v1) {
1034 if (!equals_v2v2(uvco_v1, luvs[edge_to_loops->indices[i]].uv) ||
1035 !equals_v2v2(uvco_v2, luvs[edge_to_loops->indices[i + 1]].uv)) {
1036 return true;
1037 }
1038 }
1039 else {
1040 BLI_assert(loops[edge_to_loops->indices[i]].v == v2);
1041 UNUSED_VARS_NDEBUG(v2);
1042 if (!equals_v2v2(uvco_v2, luvs[edge_to_loops->indices[i]].uv) ||
1043 !equals_v2v2(uvco_v1, luvs[edge_to_loops->indices[i + 1]].uv)) {
1044 return true;
1045 }
1046 }
1047 }
1048 return false;
1049 }
1050
1051 /* Edge is UV boundary if tagged as seam. */
1052 return (me->flag & ME_SEAM) != 0;
1053 }
1054
mesh_calc_islands_loop_poly_uv(MVert * UNUSED (verts),const int UNUSED (totvert),MEdge * edges,const int totedge,MPoly * polys,const int totpoly,MLoop * loops,const int totloop,const MLoopUV * luvs,MeshIslandStore * r_island_store)1055 static bool mesh_calc_islands_loop_poly_uv(MVert *UNUSED(verts),
1056 const int UNUSED(totvert),
1057 MEdge *edges,
1058 const int totedge,
1059 MPoly *polys,
1060 const int totpoly,
1061 MLoop *loops,
1062 const int totloop,
1063 const MLoopUV *luvs,
1064 MeshIslandStore *r_island_store)
1065 {
1066 int *poly_groups = NULL;
1067 int num_poly_groups;
1068
1069 /* map vars */
1070 MeshElemMap *edge_poly_map;
1071 int *edge_poly_mem;
1072
1073 MeshElemMap *edge_loop_map;
1074 int *edge_loop_mem;
1075
1076 MeshCheckIslandBoundaryUv edge_boundary_check_data;
1077
1078 int *poly_indices;
1079 int *loop_indices;
1080 int num_pidx, num_lidx;
1081
1082 /* Those are used to detect 'inner cuts', i.e. edges that are borders,
1083 * and yet have two or more polys of a same group using them
1084 * (typical case: seam used to unwrap properly a cylinder). */
1085 BLI_bitmap *edge_borders = NULL;
1086 int num_edge_borders = 0;
1087 char *edge_border_count = NULL;
1088 int *edge_innercut_indices = NULL;
1089 int num_einnercuts = 0;
1090
1091 int grp_idx, p_idx, pl_idx, l_idx;
1092
1093 BKE_mesh_loop_islands_clear(r_island_store);
1094 BKE_mesh_loop_islands_init(
1095 r_island_store, MISLAND_TYPE_LOOP, totloop, MISLAND_TYPE_POLY, MISLAND_TYPE_EDGE);
1096
1097 BKE_mesh_edge_poly_map_create(
1098 &edge_poly_map, &edge_poly_mem, edges, totedge, polys, totpoly, loops, totloop);
1099
1100 if (luvs) {
1101 BKE_mesh_edge_loop_map_create(
1102 &edge_loop_map, &edge_loop_mem, edges, totedge, polys, totpoly, loops, totloop);
1103 edge_boundary_check_data.loops = loops;
1104 edge_boundary_check_data.luvs = luvs;
1105 edge_boundary_check_data.edge_loop_map = edge_loop_map;
1106 }
1107
1108 poly_edge_loop_islands_calc(edges,
1109 totedge,
1110 polys,
1111 totpoly,
1112 loops,
1113 totloop,
1114 edge_poly_map,
1115 false,
1116 mesh_check_island_boundary_uv,
1117 luvs ? &edge_boundary_check_data : NULL,
1118 &poly_groups,
1119 &num_poly_groups,
1120 &edge_borders,
1121 &num_edge_borders);
1122
1123 if (!num_poly_groups) {
1124 /* Should never happen... */
1125 MEM_freeN(edge_poly_map);
1126 MEM_freeN(edge_poly_mem);
1127
1128 if (edge_borders) {
1129 MEM_freeN(edge_borders);
1130 }
1131 return false;
1132 }
1133
1134 if (num_edge_borders) {
1135 edge_border_count = MEM_mallocN(sizeof(*edge_border_count) * (size_t)totedge, __func__);
1136 edge_innercut_indices = MEM_mallocN(sizeof(*edge_innercut_indices) * (size_t)num_edge_borders,
1137 __func__);
1138 }
1139
1140 poly_indices = MEM_mallocN(sizeof(*poly_indices) * (size_t)totpoly, __func__);
1141 loop_indices = MEM_mallocN(sizeof(*loop_indices) * (size_t)totloop, __func__);
1142
1143 /* Note: here we ignore '0' invalid group - this should *never* happen in this case anyway? */
1144 for (grp_idx = 1; grp_idx <= num_poly_groups; grp_idx++) {
1145 num_pidx = num_lidx = 0;
1146 if (num_edge_borders) {
1147 num_einnercuts = 0;
1148 memset(edge_border_count, 0, sizeof(*edge_border_count) * (size_t)totedge);
1149 }
1150
1151 for (p_idx = 0; p_idx < totpoly; p_idx++) {
1152 MPoly *mp;
1153
1154 if (poly_groups[p_idx] != grp_idx) {
1155 continue;
1156 }
1157
1158 mp = &polys[p_idx];
1159 poly_indices[num_pidx++] = p_idx;
1160 for (l_idx = mp->loopstart, pl_idx = 0; pl_idx < mp->totloop; l_idx++, pl_idx++) {
1161 MLoop *ml = &loops[l_idx];
1162 loop_indices[num_lidx++] = l_idx;
1163 if (num_edge_borders && BLI_BITMAP_TEST(edge_borders, ml->e) &&
1164 (edge_border_count[ml->e] < 2)) {
1165 edge_border_count[ml->e]++;
1166 if (edge_border_count[ml->e] == 2) {
1167 edge_innercut_indices[num_einnercuts++] = (int)ml->e;
1168 }
1169 }
1170 }
1171 }
1172
1173 BKE_mesh_loop_islands_add(r_island_store,
1174 num_lidx,
1175 loop_indices,
1176 num_pidx,
1177 poly_indices,
1178 num_einnercuts,
1179 edge_innercut_indices);
1180 }
1181
1182 MEM_freeN(edge_poly_map);
1183 MEM_freeN(edge_poly_mem);
1184
1185 if (luvs) {
1186 MEM_freeN(edge_loop_map);
1187 MEM_freeN(edge_loop_mem);
1188 }
1189
1190 MEM_freeN(poly_indices);
1191 MEM_freeN(loop_indices);
1192 MEM_freeN(poly_groups);
1193
1194 if (edge_borders) {
1195 MEM_freeN(edge_borders);
1196 }
1197
1198 if (num_edge_borders) {
1199 MEM_freeN(edge_border_count);
1200 MEM_freeN(edge_innercut_indices);
1201 }
1202 return true;
1203 }
1204
1205 /**
1206 * Calculate 'generic' UV islands, i.e. based only on actual geometry data (edge seams),
1207 * not some UV layers coordinates.
1208 */
BKE_mesh_calc_islands_loop_poly_edgeseam(MVert * verts,const int totvert,MEdge * edges,const int totedge,MPoly * polys,const int totpoly,MLoop * loops,const int totloop,MeshIslandStore * r_island_store)1209 bool BKE_mesh_calc_islands_loop_poly_edgeseam(MVert *verts,
1210 const int totvert,
1211 MEdge *edges,
1212 const int totedge,
1213 MPoly *polys,
1214 const int totpoly,
1215 MLoop *loops,
1216 const int totloop,
1217 MeshIslandStore *r_island_store)
1218 {
1219 return mesh_calc_islands_loop_poly_uv(
1220 verts, totvert, edges, totedge, polys, totpoly, loops, totloop, NULL, r_island_store);
1221 }
1222
1223 /**
1224 * Calculate UV islands.
1225 *
1226 * \note If no MLoopUV layer is passed, we only consider edges tagged as seams as UV boundaries.
1227 * This has the advantages of simplicity, and being valid/common to all UV maps.
1228 * However, it means actual UV islands without matching UV seams will not be handled correctly...
1229 * If a valid UV layer is passed as \a luvs parameter,
1230 * UV coordinates are also used to detect islands boundaries.
1231 *
1232 * \note All this could be optimized...
1233 * Not sure it would be worth the more complex code, though,
1234 * those loops are supposed to be really quick to do...
1235 */
BKE_mesh_calc_islands_loop_poly_uvmap(MVert * verts,const int totvert,MEdge * edges,const int totedge,MPoly * polys,const int totpoly,MLoop * loops,const int totloop,const MLoopUV * luvs,MeshIslandStore * r_island_store)1236 bool BKE_mesh_calc_islands_loop_poly_uvmap(MVert *verts,
1237 const int totvert,
1238 MEdge *edges,
1239 const int totedge,
1240 MPoly *polys,
1241 const int totpoly,
1242 MLoop *loops,
1243 const int totloop,
1244 const MLoopUV *luvs,
1245 MeshIslandStore *r_island_store)
1246 {
1247 BLI_assert(luvs != NULL);
1248 return mesh_calc_islands_loop_poly_uv(
1249 verts, totvert, edges, totedge, polys, totpoly, loops, totloop, luvs, r_island_store);
1250 }
1251
1252 /** \} */
1253