Blender V4.5
transform_convert_mesh_uv.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "MEM_guardedalloc.h"
10
11#include "BLI_linklist_stack.h"
12#include "BLI_math_geom.h"
13#include "BLI_math_matrix.h"
14#include "BLI_math_vector.h"
15#include "BLI_math_vector.hh"
16
17#include "BKE_context.hh"
18#include "BKE_customdata.hh"
19#include "BKE_editmesh.hh"
20#include "BKE_mesh_mapping.hh"
21
22#include "ED_image.hh"
23#include "ED_mesh.hh"
24#include "ED_uvedit.hh"
25
26#include "WM_api.hh" /* For #WM_event_add_notifier to deal with stabilization nodes. */
27
28#include "transform.hh"
29#include "transform_convert.hh"
30
31namespace blender::ed::transform {
32
33/* -------------------------------------------------------------------- */
36
37static void UVsToTransData(const float aspect[2],
38 float *uv,
39 const float *center,
40 const float calc_dist,
41 const bool selected,
42 BMLoop *l,
43 TransData *r_td,
44 TransData2D *r_td2d)
45{
46 /* UV coords are scaled by aspects. this is needed for rotations and
47 * proportional editing to be consistent with the stretched UV coords
48 * that are displayed. this also means that for display and number-input,
49 * and when the UV coords are flushed, these are converted each time. */
50 r_td2d->loc[0] = uv[0] * aspect[0];
51 r_td2d->loc[1] = uv[1] * aspect[1];
52 r_td2d->loc[2] = 0.0f;
53 r_td2d->loc2d = uv;
54
55 r_td->flag = 0;
56 r_td->loc = r_td2d->loc;
57 copy_v2_v2(r_td->center, center ? center : r_td->loc);
58 r_td->center[2] = 0.0f;
59 copy_v3_v3(r_td->iloc, r_td->loc);
60
61 memset(r_td->axismtx, 0, sizeof(r_td->axismtx));
62 r_td->axismtx[2][2] = 1.0f;
63
64 r_td->ext = nullptr;
65 r_td->val = nullptr;
66
67 if (selected) {
68 r_td->flag |= TD_SELECTED;
69 r_td->dist = 0.0;
70 }
71 else {
72 r_td->dist = calc_dist;
73 }
74 unit_m3(r_td->mtx);
75 unit_m3(r_td->smtx);
76 r_td->extra = l;
77}
78
83 BMesh *bm,
84 float *dists,
85 const float aspect[2])
86{
87#define TMP_LOOP_SELECT_TAG BM_ELEM_TAG_ALT
88 /* Mostly copied from #transform_convert_mesh_connectivity_distance. */
90
91 /* Any BM_ELEM_TAG'd loop is added to 'queue_next', this makes sure that we don't add things
92 * twice. */
93 BLI_LINKSTACK_DECLARE(queue_next, BMLoop *);
94
95 BLI_LINKSTACK_INIT(queue);
96 BLI_LINKSTACK_INIT(queue_next);
97
98 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
99
100 BMIter fiter, liter;
101 BMVert *f;
102 BMLoop *l;
103
105
106 BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
107 /* Visible faces was tagged in #createTransUVs. */
109 continue;
110 }
111
112 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
113 float dist;
114 bool uv_vert_sel = uvedit_uv_select_test_ex(ts, l, offsets);
115
116 if (uv_vert_sel) {
117 BLI_LINKSTACK_PUSH(queue, l);
119 dist = 0.0f;
120 }
121 else {
123 dist = FLT_MAX;
124 }
125
126 /* Make sure all loops are in a clean tag state. */
128
129 int loop_idx = BM_elem_index_get(l);
130
131 dists[loop_idx] = dist;
132 }
133 }
134
135 /* Need to be very careful of feedback loops here, store previous dist's to avoid feedback. */
136 float *dists_prev = static_cast<float *>(MEM_dupallocN(dists));
137
138 do {
139 while ((l = BLI_LINKSTACK_POP(queue))) {
141
142 BMLoop *l_other, *l_connected;
143 BMIter l_connected_iter;
144
145 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
146 float l_uv[2];
147
148 copy_v2_v2(l_uv, luv);
149 mul_v2_v2(l_uv, aspect);
150
151 BM_ITER_ELEM (l_other, &liter, l->f, BM_LOOPS_OF_FACE) {
152 if (l_other == l) {
153 continue;
154 }
155 float other_uv[2], edge_vec[2];
156 float *luv_other = BM_ELEM_CD_GET_FLOAT_P(l_other, offsets.uv);
157
158 copy_v2_v2(other_uv, luv_other);
159 mul_v2_v2(other_uv, aspect);
160
161 sub_v2_v2v2(edge_vec, l_uv, other_uv);
162
163 const int i = BM_elem_index_get(l);
164 const int i_other = BM_elem_index_get(l_other);
165 float dist = len_v2(edge_vec) + dists_prev[i];
166
167 if (dist < dists[i_other]) {
168 dists[i_other] = dist;
169 }
170 else {
171 /* The face loop already has a shorter path to it. */
172 continue;
173 }
174
175 bool other_vert_sel, connected_vert_sel;
176
177 other_vert_sel = BM_elem_flag_test_bool(l_other, TMP_LOOP_SELECT_TAG);
178
179 BM_ITER_ELEM (l_connected, &l_connected_iter, l_other->v, BM_LOOPS_OF_VERT) {
180 if (l_connected == l_other) {
181 continue;
182 }
183 /* Visible faces was tagged in #createTransUVs. */
184 if (!BM_elem_flag_test(l_connected->f, BM_ELEM_TAG)) {
185 continue;
186 }
187
188 float *luv_connected = BM_ELEM_CD_GET_FLOAT_P(l_connected, offsets.uv);
189 connected_vert_sel = BM_elem_flag_test_bool(l_connected, TMP_LOOP_SELECT_TAG);
190
191 /* Check if this loop is connected in UV space.
192 * If the uv loops share the same selection state (if not, they are not connected as
193 * they have been ripped or other edit commands have separated them). */
194 bool connected = other_vert_sel == connected_vert_sel &&
195 equals_v2v2(luv_other, luv_connected);
196 if (!connected) {
197 continue;
198 }
199
200 /* The loop vert is occupying the same space, so it has the same distance. */
201 const int i_connected = BM_elem_index_get(l_connected);
202 dists[i_connected] = dist;
203
204 if (BM_elem_flag_test(l_connected, BM_ELEM_TAG) == 0) {
205 BM_elem_flag_enable(l_connected, BM_ELEM_TAG);
206 BLI_LINKSTACK_PUSH(queue_next, l_connected);
207 }
208 }
209 }
210 }
211
212 /* Clear elem flags for the next loop. */
213 for (LinkNode *lnk = queue_next; lnk; lnk = lnk->next) {
214 BMLoop *l_link = static_cast<BMLoop *>(lnk->link);
215 const int i = BM_elem_index_get(l_link);
216
218
219 /* Store all new dist values. */
220 dists_prev[i] = dists[i];
221 }
222
223 BLI_LINKSTACK_SWAP(queue, queue_next);
224
225 } while (BLI_LINKSTACK_SIZE(queue));
226
227#ifndef NDEBUG
228 /* Check that we didn't leave any loops tagged. */
229 BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
230 /* Visible faces was tagged in #createTransUVs. */
232 continue;
233 }
234
235 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
237 }
238 }
239#endif
240
241 BLI_LINKSTACK_FREE(queue);
242 BLI_LINKSTACK_FREE(queue_next);
243
244 MEM_freeN(dists_prev);
245#undef TMP_LOOP_SELECT_TAG
246}
247
249{
251 Scene *scene = t->scene;
252
253 const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0;
254 const bool is_prop_connected = (t->flag & T_PROP_CONNECTED) != 0;
255 const bool is_island_center = (t->around == V3D_AROUND_LOCAL_ORIGINS);
256
258
259 TransData *td = nullptr;
260 TransData2D *td2d = nullptr;
261 BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
262 BMFace *efa;
263 BMIter iter, liter;
264 UvElementMap *elementmap = nullptr;
265 struct IslandCenter {
266 float co[2];
267 int co_num;
268 } *island_center = nullptr;
269 int count = 0, countsel = 0;
270 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
271
272 if (!ED_space_image_show_uvedit(sima, tc->obedit)) {
273 continue;
274 }
275
276 /* Count. */
277 if (is_island_center) {
278 /* Create element map with island information. */
279 elementmap = BM_uv_element_map_create(em->bm, scene, true, false, true, true);
280 if (elementmap == nullptr) {
281 continue;
282 }
283
284 island_center = MEM_calloc_arrayN<IslandCenter>(elementmap->total_islands, __func__);
285 }
286
287 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
288 BMLoop *l;
289
290 if (!uvedit_face_visible_test(scene, efa)) {
292 continue;
293 }
294
296 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
297 /* Make sure that the loop element flag is cleared for when we use it in
298 * uv_set_connectivity_distance later. */
300 if (uvedit_uv_select_test(scene, l, offsets)) {
301 countsel++;
302
303 if (island_center) {
304 UvElement *element = BM_uv_element_get(elementmap, l);
305
306 if (element && !element->flag) {
307 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
308 add_v2_v2(island_center[element->island].co, luv);
309 island_center[element->island].co_num++;
310 element->flag = true;
311 }
312 }
313 }
314
315 if (is_prop_edit) {
316 count++;
317 }
318 }
319 }
320
321 float *prop_dists = nullptr;
322
323 /* Support other objects using proportional editing to adjust these, unless connected is
324 * enabled. */
325 if (((is_prop_edit && !is_prop_connected) ? count : countsel) == 0) {
326 goto finally;
327 }
328
329 if (is_island_center) {
330 for (int i = 0; i < elementmap->total_islands; i++) {
331 mul_v2_fl(island_center[i].co, 1.0f / island_center[i].co_num);
332 mul_v2_v2(island_center[i].co, t->aspect);
333 }
334 }
335
336 tc->data_len = (is_prop_edit) ? count : countsel;
337 tc->data = MEM_calloc_arrayN<TransData>(tc->data_len, "TransObData(UV Editing)");
338 /* For each 2d uv coord a 3d vector is allocated, so that they can be
339 * treated just as if they were 3d verts. */
340 tc->data_2d = MEM_calloc_arrayN<TransData2D>(tc->data_len, "TransObData2D(UV Editing)");
341
342 if (sima->flag & SI_CLIP_UV) {
343 t->flag |= T_CLIP_UV;
344 }
345
346 td = tc->data;
347 td2d = tc->data_2d;
348
349 if (is_prop_connected) {
350 prop_dists = MEM_calloc_arrayN<float>(em->bm->totloop, "TransObPropDists(UV Editing)");
351
352 uv_set_connectivity_distance(t->settings, em->bm, prop_dists, t->aspect);
353 }
354
355 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
356 BMLoop *l;
357
358 if (!BM_elem_flag_test(efa, BM_ELEM_TAG)) {
359 continue;
360 }
361
362 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
363 const bool selected = uvedit_uv_select_test(scene, l, offsets);
364 float(*luv)[2];
365 const float *center = nullptr;
366 float prop_distance = FLT_MAX;
367
368 if (!is_prop_edit && !selected) {
369 continue;
370 }
371
372 if (is_prop_connected) {
373 const int idx = BM_elem_index_get(l);
374 prop_distance = prop_dists[idx];
375 }
376
377 if (is_island_center) {
378 UvElement *element = BM_uv_element_get(elementmap, l);
379 if (element) {
380 center = island_center[element->island].co;
381 }
382 }
383
384 luv = (float(*)[2])BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
385 UVsToTransData(t->aspect, *luv, center, prop_distance, selected, l, td++, td2d++);
386 }
387 }
388
389 if (sima->flag & SI_LIVE_UNWRAP) {
390 wmWindow *win_modal = CTX_wm_window(C);
391 ED_uvedit_live_unwrap_begin(t->scene, tc->obedit, win_modal);
392 }
393
394 finally:
395 if (is_prop_connected) {
396 MEM_SAFE_FREE(prop_dists);
397 }
398 if (is_island_center) {
399 BM_uv_element_map_free(elementmap);
400
401 MEM_freeN(island_center);
402 }
403 }
404}
405
407
408/* -------------------------------------------------------------------- */
411
413{
414 SpaceImage *sima = static_cast<SpaceImage *>(t->area->spacedata.first);
415 const bool use_pixel_round = ((sima->pixel_round_mode != SI_PIXEL_ROUND_DISABLED) &&
416 (t->state != TRANS_CANCEL));
417
419 TransData2D *td;
420 int a;
421 float aspect_inv[2], size[2];
422
423 aspect_inv[0] = 1.0f / t->aspect[0];
424 aspect_inv[1] = 1.0f / t->aspect[1];
425
426 if (use_pixel_round) {
427 int size_i[2];
428 ED_space_image_get_size(sima, &size_i[0], &size_i[1]);
429 size[0] = size_i[0];
430 size[1] = size_i[1];
431 }
432
433 /* Flush to 2d vector from internally used 3d vector. */
434 for (a = 0, td = tc->data_2d; a < tc->data_len; a++, td++) {
435 td->loc2d[0] = td->loc[0] * aspect_inv[0];
436 td->loc2d[1] = td->loc[1] * aspect_inv[1];
437
438 if (use_pixel_round) {
439 td->loc2d[0] *= size[0];
440 td->loc2d[1] *= size[1];
441
442 switch (sima->pixel_round_mode) {
444 td->loc2d[0] = roundf(td->loc2d[0] - 0.5f) + 0.5f;
445 td->loc2d[1] = roundf(td->loc2d[1] - 0.5f) + 0.5f;
446 break;
448 td->loc2d[0] = roundf(td->loc2d[0]);
449 td->loc2d[1] = roundf(td->loc2d[1]);
450 break;
451 }
452
453 td->loc2d[0] /= size[0];
454 td->loc2d[1] /= size[1];
455 }
456 }
457 }
458}
459
461{
462 SpaceImage *sima = static_cast<SpaceImage *>(t->area->spacedata.first);
463
464 flushTransUVs(t);
465 if (sima->flag & SI_LIVE_UNWRAP) {
467 }
468
470 if (tc->data_len) {
471 DEG_id_tag_update(static_cast<ID *>(tc->obedit->data), ID_RECALC_GEOMETRY);
472 }
473 }
474}
475
477
478/* -------------------------------------------------------------------- */
481
482struct UVGroups {
484
485 private:
486 Vector<int> groups_offs_buffer_;
487 Vector<int> groups_offs_indices_;
488
489 public:
490 void init(const TransDataContainer *tc, BMesh *bm, const BMUVOffsets &offsets)
491 {
492 /* To identify #TransData by the corner, we first need to set all values in `index` to `-1`. */
493 BMIter fiter;
494 BMIter liter;
495 BMFace *f;
496 BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
497 BMLoop *l;
498 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
499 BM_elem_index_set(l, -1);
500 }
501 }
502
503 /* Now, count and set the index for the corners being transformed. */
504 this->sd_len = 0;
505 tc->foreach_index_selected([&](const int i) {
506 TransData *td = &tc->data[i];
507 this->sd_len++;
508
509 BMLoop *l = static_cast<BMLoop *>(td->extra);
511 });
512 bm->elem_index_dirty |= BM_LOOP;
513
514 /* Create the groups. */
515 groups_offs_buffer_.reserve(this->sd_len);
516 groups_offs_indices_.reserve((this->sd_len / 4) + 2);
517
518 TransData *td = tc->data;
519 for (int i = 0; i < tc->data_len; i++, td++) {
520 BMLoop *l_orig = static_cast<BMLoop *>(td->extra);
521 if (BM_elem_index_get(l_orig) == -1) {
522 /* Already added to a group. */
523 continue;
524 }
525
526 const float2 uv_orig = BM_ELEM_CD_GET_FLOAT_P(l_orig, offsets.uv);
527 groups_offs_indices_.append(groups_offs_buffer_.size());
528
529 BMIter liter;
530 BMLoop *l_iter;
531 BM_ITER_ELEM (l_iter, &liter, l_orig->v, BM_LOOPS_OF_VERT) {
532 if (BM_elem_index_get(l_iter) == -1) {
533 /* Already added to a group or not participating in the transformation. */
534 continue;
535 }
536
537 if (l_orig != l_iter &&
538 !compare_v2v2(uv_orig, BM_ELEM_CD_GET_FLOAT_P(l_iter, offsets.uv), FLT_EPSILON))
539 {
540 /* Non-connected. */
541 continue;
542 }
543
544 groups_offs_buffer_.append(BM_elem_index_get(l_iter));
545 BM_elem_index_set(l_iter, -1);
546 }
547 }
548 groups_offs_indices_.append(groups_offs_buffer_.size());
549 }
550
552 {
553 return OffsetIndices<int>(groups_offs_indices_);
554 }
555
556 Span<int> td_indices_get(const int group_index) const
557 {
558 return groups_offs_buffer_.as_span().slice(this->groups()[group_index]);
559 }
560
562 {
563 Array<TransDataVertSlideVert> r_sv(this->sd_len);
564 TransDataVertSlideVert *sv = r_sv.data();
565 for (const int group_index : this->groups().index_range()) {
566 for (int td_index : this->td_indices_get(group_index)) {
567 TransData *td = &tc->data[td_index];
568 sv->td = td;
569 sv++;
570 }
571 }
572
573 return r_sv;
574 }
575
577 {
578 Array<TransDataEdgeSlideVert> r_sv(this->sd_len);
579 TransDataEdgeSlideVert *sv = r_sv.data();
580 for (const int group_index : this->groups().index_range()) {
581 for (int td_index : this->td_indices_get(group_index)) {
582 TransData *td = &tc->data[td_index];
583 sv->td = td;
584 sv->dir_side[0] = float3(0);
585 sv->dir_side[1] = float3(0);
586 sv->loop_nr = -1;
587 sv++;
588 }
589 }
590
591 return r_sv;
592 }
593
595 const int group_index)
596 {
597 return sd_array.slice(this->groups()[group_index]);
598 }
599
601 const int group_index)
602 {
603 return sd_array.slice(this->groups()[group_index]);
604 }
605};
606
608{
609 UVGroups *uv_groups = static_cast<UVGroups *>(tc->custom.type.data);
610 if (uv_groups == nullptr) {
611 uv_groups = MEM_new<UVGroups>(__func__);
612 uv_groups->init(tc, bm, offsets);
613
614 /* Edge Slide and Vert Slide are often called in sequence, so, to avoid recalculating the
615 * groups, save them in the #TransDataContainer. */
616
617 tc->custom.type.data = uv_groups;
618 tc->custom.type.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) {
619 UVGroups *data = static_cast<UVGroups *>(custom_data->data);
620 MEM_delete(data);
621 custom_data->data = nullptr;
622 };
623 }
624
625 return uv_groups;
626}
627
629
630/* -------------------------------------------------------------------- */
633
635 const TransInfo *t, TransDataContainer *tc, Vector<float3> &r_loc_dst_buffer)
636{
637
639 BMesh *bm = em->bm;
640 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
641
642 UVGroups *uv_groups = mesh_uv_groups_get(tc, bm, offsets);
643
645
646 r_loc_dst_buffer.reserve(r_sv.size() * 4);
647
648 for (const int group_index : uv_groups->groups().index_range()) {
649 const int size_prev = r_loc_dst_buffer.size();
650
651 for (int td_index : uv_groups->td_indices_get(group_index)) {
652 TransData *td = &tc->data[td_index];
653 BMLoop *l = static_cast<BMLoop *>(td->extra);
654
655 for (BMLoop *l_dst : {l->prev, l->next}) {
656 const float2 uv_dest = BM_ELEM_CD_GET_FLOAT_P(l_dst, offsets.uv);
657 Span<float3> uvs_added = r_loc_dst_buffer.as_span().drop_front(size_prev);
658
659 bool skip = std::any_of(
660 uvs_added.begin(), uvs_added.end(), [&](const float3 &uv_dest_added) {
661 return compare_v2v2(uv_dest, uv_dest_added, FLT_EPSILON);
662 });
663
664 if (!skip) {
665 r_loc_dst_buffer.append(float3(uv_dest, 0.0f));
666 }
667 }
668 }
669
670 const int size_new = r_loc_dst_buffer.size() - size_prev;
671 for (TransDataVertSlideVert &sv : uv_groups->sd_group_get(r_sv, group_index)) {
672 /* The buffer address may change as the vector is resized. Avoid setting #Span now. */
673 // sv.targets = r_loc_dst_buffer.as_span().drop_front(size_prev);
674
675 /* Store the buffer slice temporarily in `target_curr`. */
676 sv.co_link_orig_3d = {static_cast<float3 *>(POINTER_FROM_INT(size_prev)), size_new};
677 sv.co_link_curr = 0;
678 }
679 }
680
681 if (t->aspect[0] != 1.0f || t->aspect[1] != 1.0f) {
682 for (float3 &dest : r_loc_dst_buffer) {
683 dest[0] *= t->aspect[0];
684 dest[1] *= t->aspect[1];
685 }
686 }
687
688 for (TransDataVertSlideVert &sv : r_sv) {
689 int start = POINTER_AS_INT(sv.co_link_orig_3d.data());
690 sv.co_link_orig_3d = r_loc_dst_buffer.as_span().slice(start, sv.co_link_orig_3d.size());
691 }
692
693 return r_sv;
694}
695
697
698/* -------------------------------------------------------------------- */
701
702/* Check if the UV group is a vertex between 2 faces. */
704 const BMUVOffsets &offsets,
705 Span<int> group)
706{
707 if (group.size() == 1) {
708 return false;
709 }
710 if (group.size() > 2) {
711 return false;
712 }
713
714 TransData *td_a = &tc->data[group[0]];
715 TransData *td_b = &tc->data[group[1]];
716 BMLoop *l_a = static_cast<BMLoop *>(td_a->extra);
717 BMLoop *l_b = static_cast<BMLoop *>(td_b->extra);
718 BMLoop *l_a_prev = l_a->prev;
719 BMLoop *l_a_next = l_a->next;
720 BMLoop *l_b_prev = l_b->next;
721 BMLoop *l_b_next = l_b->prev;
722 if (l_a_prev->v != l_b_prev->v) {
723 std::swap(l_b_prev, l_b_next);
724 if (l_a_prev->v != l_b_prev->v) {
725 return false;
726 }
727 }
728
729 if (l_a_next->v != l_b_next->v) {
730 return false;
731 }
732
733 const float2 uv_a_prev = BM_ELEM_CD_GET_FLOAT_P(l_a_prev, offsets.uv);
734 const float2 uv_b_prev = BM_ELEM_CD_GET_FLOAT_P(l_b_prev, offsets.uv);
735 if (!compare_v2v2(uv_a_prev, uv_b_prev, FLT_EPSILON)) {
736 return false;
737 }
738
739 const float2 uv_a_next = BM_ELEM_CD_GET_FLOAT_P(l_a_next, offsets.uv);
740 const float2 uv_b_next = BM_ELEM_CD_GET_FLOAT_P(l_b_next, offsets.uv);
741 if (!compare_v2v2(uv_a_next, uv_b_next, FLT_EPSILON)) {
742 return false;
743 }
744
745 return true;
746}
747
752static bool bm_loop_uv_calc_opposite_co(const BMLoop *l_tmp,
753 const float2 &uv_tmp,
754 const BMUVOffsets &offsets,
755 const float2 &ray_direction,
756 float2 &r_co)
757{
758 /* skip adjacent edges */
759 BMLoop *l_first = l_tmp->next;
760 BMLoop *l_last = l_tmp->prev;
761 BMLoop *l_iter;
762 float dist_sq_best = FLT_MAX;
763 bool found = false;
764
765 l_iter = l_first;
766 do {
767 const float2 uv_iter = BM_ELEM_CD_GET_FLOAT_P(l_iter, offsets.uv);
768 const float2 uv_iter_next = BM_ELEM_CD_GET_FLOAT_P(l_iter->next, offsets.uv);
769 float lambda;
770 if (isect_ray_seg_v2(uv_tmp, ray_direction, uv_iter, uv_iter_next, &lambda, nullptr) ||
771 isect_ray_seg_v2(uv_tmp, -ray_direction, uv_iter, uv_iter_next, &lambda, nullptr))
772 {
773 float2 isect_co = uv_tmp + ray_direction * lambda;
774 /* likelihood of multiple intersections per ngon is quite low,
775 * it would have to loop back on itself, but better support it
776 * so check for the closest opposite edge */
777 const float dist_sq_test = math::distance_squared(uv_tmp, isect_co);
778 if (dist_sq_test < dist_sq_best) {
779 r_co = isect_co;
780 dist_sq_best = dist_sq_test;
781 found = true;
782 }
783 }
784 } while ((l_iter = l_iter->next) != l_last);
785
786 return found;
787}
788
790 const float2 &uv,
791 const float2 &aspect,
792 const BMUVOffsets &offsets)
793{
794 BMFace *f = l->f;
795 BMLoop *l_next = l->next;
796 if (f->len == 4) {
797 /* we could use code below, but in this case
798 * sliding diagonally across the quad works well */
799 return BM_ELEM_CD_GET_FLOAT_P(l_next->next, offsets.uv);
800 }
801
802 BMLoop *l_prev = l->prev;
803 const float2 uv_prev = BM_ELEM_CD_GET_FLOAT_P(l_prev, offsets.uv);
804 const float2 uv_next = BM_ELEM_CD_GET_FLOAT_P(l_next, offsets.uv);
805
806 float2 ray_dir = (uv - uv_prev) + (uv_next - uv);
807 ray_dir = math::orthogonal(ray_dir * aspect);
808 ray_dir[0] /= aspect[0];
809 ray_dir[1] /= aspect[1];
810
811 float2 isect_co;
812 if (!bm_loop_uv_calc_opposite_co(l, uv, offsets, ray_dir, isect_co)) {
813 /* Rare case. */
814 mid_v3_v3v3(isect_co, l->prev->v->co, l_next->v->co);
815 }
816 return isect_co;
817}
818
821 int *r_group_len)
822{
825 BMesh *bm = em->bm;
826 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
827
828 const bool check_edge = ED_uvedit_select_mode_get(t->scene) == UV_SELECT_EDGE;
829
830 UVGroups *uv_groups = mesh_uv_groups_get(tc, bm, offsets);
831 Array<int2> groups_linked(uv_groups->groups().size(), int2(-1, -1));
832
833 {
834 /* Identify the group to which a loop belongs through the element's index value. */
835
836 /* First we just need to "clean up" the neighboring loops.
837 * This way we can identify where a group of sliding edges starts and where it ends. */
838 tc->foreach_index_selected([&](const int i) {
839 TransData *td = &tc->data[i];
840 BMLoop *l = static_cast<BMLoop *>(td->extra);
841 BM_elem_index_set(l->prev, -1);
842 BM_elem_index_set(l->next, -1);
843 });
844
845 /* Now set the group indexes. */
846 for (const int group_index : uv_groups->groups().index_range()) {
847 for (int td_index : uv_groups->td_indices_get(group_index)) {
848 TransData *td = &tc->data[td_index];
849 BMLoop *l = static_cast<BMLoop *>(td->extra);
850 BM_elem_index_set(l, group_index);
851 }
852 }
853 bm->elem_index_dirty |= BM_LOOP;
854 }
855
856 for (const int group_index : uv_groups->groups().index_range()) {
857 int2 &group_linked_pair = groups_linked[group_index];
858
859 for (int td_index : uv_groups->td_indices_get(group_index)) {
860 TransData *td = &tc->data[td_index];
861 BMLoop *l = static_cast<BMLoop *>(td->extra);
862
863 for (BMLoop *l_dst : {l->prev, l->next}) {
864 const int group_index_dst = BM_elem_index_get(l_dst);
865 if (group_index_dst == -1) {
866 continue;
867 }
868
869 if (ELEM(group_index_dst, group_linked_pair[0], group_linked_pair[1])) {
870 continue;
871 }
872
873 if (check_edge) {
874 BMLoop *l_edge = l_dst == l->prev ? l_dst : l;
875 if (!uvedit_edge_select_test_ex(t->settings, l_edge, offsets)) {
876 continue;
877 }
878 }
879
880 if (group_linked_pair[1] != -1) {
881 /* For Edge Slide, the vertex can only be connected to a maximum of 2 sliding edges. */
882 return r_sv;
883 }
884 const int slot = int(group_linked_pair[0] != -1);
885 group_linked_pair[slot] = group_index_dst;
886 }
887 }
888
889 if (group_linked_pair[0] == -1) {
890 /* For Edge Slide, the vertex must be connected to at least 1 sliding edge. */
891 return r_sv;
892 }
893 }
894
895 /* Alloc and initialize the #TransDataEdgeSlideVert. */
896 r_sv = uv_groups->sd_array_create_and_init_edge(tc);
897
898 /* Compute the sliding groups. */
899 int loop_nr = 0;
900 for (int i : r_sv.index_range()) {
901 if (r_sv[i].loop_nr != -1) {
902 /* This vertex has already been computed. */
903 continue;
904 }
905
906 BMLoop *l = static_cast<BMLoop *>(r_sv[i].td->extra);
907 int group_index = BM_elem_index_get(l);
908
909 /* Start from a vertex connected to just a single edge or any if it doesn't exist. */
910 int i_curr = group_index;
911 int i_prev = groups_linked[group_index][1];
912 while (!ELEM(i_prev, -1, group_index)) {
913 int tmp = groups_linked[i_prev][0] != i_curr ? groups_linked[i_prev][0] :
914 groups_linked[i_prev][1];
915 i_curr = i_prev;
916 i_prev = tmp;
917 }
918
927 struct SlideTempDataUV {
928 int i; /* The group index. */
929 struct {
930 BMFace *f;
931 float2 dst;
932 } fdata[2];
933 bool vert_is_inner; /* In the middle of two faces. */
942 int find_best_dir(const SlideTempDataUV *curr_side_other,
943 const BMLoop *l_src,
944 const BMLoop *l_dst,
945 const float2 &src,
946 const float2 &dst,
947 bool *r_do_isect_curr_dirs) const
948 {
949 *r_do_isect_curr_dirs = false;
950 const BMFace *f_curr = l_src->f;
951 if (curr_side_other->fdata[0].f &&
952 (curr_side_other->fdata[0].f == f_curr ||
953 compare_v2v2(dst, curr_side_other->fdata[0].dst, FLT_EPSILON)))
954 {
955 return 0;
956 }
957
958 if (curr_side_other->fdata[1].f &&
959 (curr_side_other->fdata[1].f == f_curr ||
960 compare_v2v2(dst, curr_side_other->fdata[1].dst, FLT_EPSILON)))
961 {
962 return 1;
963 }
964
965 if (curr_side_other->fdata[0].f || curr_side_other->fdata[1].f) {
966 /* Find the best direction checking the edges that share faces between them. */
967 int best_dir = -1;
968 const BMLoop *l_edge_dst = l_src->prev == l_dst ? l_src->prev : l_src;
969 const BMLoop *l_other = l_edge_dst->radial_next;
970 while (l_other != l_edge_dst) {
971 const BMLoop *l_other_dst = l_other->v == l_src->v ? l_other->next : l_other;
972 if (BM_elem_index_get(l_other_dst) != -1) {
973 /* This is a sliding edge corner. */
974 break;
975 }
976
977 if (l_other->f == curr_side_other->fdata[0].f) {
978 best_dir = 0;
979 break;
980 }
981 if (l_other->f == curr_side_other->fdata[1].f) {
982 best_dir = 1;
983 break;
984 }
985 l_other = (l_other->v == l_src->v ? l_other->prev : l_other->next)->radial_next;
986 }
987
988 if (best_dir != -1) {
989 *r_do_isect_curr_dirs = true;
990 return best_dir;
991 }
992 }
993
994 if (ELEM(nullptr, this->fdata[0].f, this->fdata[1].f)) {
995 return int(this->fdata[0].f != nullptr);
996 }
997
998 /* Find the closest direction. */
999 *r_do_isect_curr_dirs = true;
1000
1001 float2 dir_curr = dst - src;
1002 float2 dir0 = math::normalize(this->fdata[0].dst - src);
1003 float2 dir1 = math::normalize(this->fdata[1].dst - src);
1004 float dot0 = math::dot(dir_curr, dir0);
1005 float dot1 = math::dot(dir_curr, dir1);
1006 return int(dot0 < dot1);
1007 }
1008 } prev = {}, curr = {}, next = {}, tmp = {};
1009
1010 curr.i = i_curr;
1011 curr.vert_is_inner = mesh_uv_group_is_inner(tc, offsets, uv_groups->td_indices_get(curr.i));
1012
1013 /* Do not compute `prev` for now. Let the loop calculate `curr` twice. */
1014 prev.i = -1;
1015
1016 while (curr.i != -1) {
1017 int tmp_i = prev.i == -1 ? i_prev : prev.i;
1018 next.i = groups_linked[curr.i][0] != tmp_i ? groups_linked[curr.i][0] :
1019 groups_linked[curr.i][1];
1020 if (next.i != -1) {
1021 next.vert_is_inner = mesh_uv_group_is_inner(
1022 tc, offsets, uv_groups->td_indices_get(next.i));
1023
1024 tmp = curr;
1025 Span<int> td_indices_next = uv_groups->td_indices_get(next.i);
1026
1027 for (int td_index_curr : uv_groups->td_indices_get(curr.i)) {
1028 BMLoop *l_curr = static_cast<BMLoop *>(tc->data[td_index_curr].extra);
1029 const float2 src = BM_ELEM_CD_GET_FLOAT_P(l_curr, offsets.uv);
1030
1031 for (int td_index_next : td_indices_next) {
1032 BMLoop *l_next = static_cast<BMLoop *>(tc->data[td_index_next].extra);
1033 if (l_curr->f != l_next->f) {
1034 continue;
1035 }
1036
1037 BLI_assert(l_curr != l_next);
1038
1039 BMLoop *l1_dst, *l2_dst;
1040 if (l_curr->next == l_next) {
1041 l1_dst = l_curr->prev;
1042 l2_dst = l_next->next;
1043 }
1044 else {
1045 l1_dst = l_curr->next;
1046 l2_dst = l_next->prev;
1047 }
1048
1049 const float2 dst = BM_ELEM_CD_GET_FLOAT_P(l1_dst, offsets.uv);
1050
1051 /* Sometimes the sliding direction may fork (`isect_curr_dirs` is `true`).
1052 * In this case, the resulting direction is the intersection of the destinations. */
1053 bool isect_curr_dirs = false;
1054
1055 /* Identify the slot to slide according to the directions already computed in `curr`.
1056 */
1057 int best_dir = curr.find_best_dir(&tmp, l_curr, l1_dst, src, dst, &isect_curr_dirs);
1058
1059 if (curr.fdata[best_dir].f == nullptr) {
1060 curr.fdata[best_dir].f = l_curr->f;
1061 if (curr.vert_is_inner) {
1062 curr.fdata[best_dir].dst = isect_face_dst(l_curr, src, t->aspect, offsets);
1063 }
1064 else {
1065 curr.fdata[best_dir].dst = dst;
1066 }
1067 }
1068
1069 /* Compute `next`. */
1070 next.fdata[best_dir].f = l_curr->f;
1071 if (BM_elem_index_get(l2_dst) != -1 || next.vert_is_inner) {
1072 /* Case where the vertex slides over the face. */
1073 const float2 src_next = BM_ELEM_CD_GET_FLOAT_P(l_next, offsets.uv);
1074 next.fdata[best_dir].dst = isect_face_dst(l_next, src_next, t->aspect, offsets);
1075 }
1076 else {
1077 /* Case where the vertex slides over an edge. */
1078 const float2 dst_next = BM_ELEM_CD_GET_FLOAT_P(l2_dst, offsets.uv);
1079 next.fdata[best_dir].dst = dst_next;
1080 }
1081
1082 if (isect_curr_dirs) {
1083 /* The `best_dir` can only have one direction. */
1084 const float2 &dst0 = prev.fdata[best_dir].dst;
1085 const float2 &dst1 = curr.fdata[best_dir].dst;
1086 const float2 &dst2 = dst;
1087 const float2 &dst3 = next.fdata[best_dir].dst;
1088 if (isect_line_line_v2_point(dst0, dst1, dst2, dst3, curr.fdata[best_dir].dst) ==
1090 {
1091 curr.fdata[best_dir].dst = math::midpoint(dst1, dst2);
1092 }
1093 }
1094 /* There is only one pair of corners to slide per face, we don't need to keep checking
1095 * `if (f_curr != l_next->f)`. */
1096 break;
1097 }
1098 }
1099 }
1100
1101 TransDataEdgeSlideVert *sv_first = nullptr;
1102 for (TransDataEdgeSlideVert &sv : uv_groups->sd_group_get(r_sv, curr.i)) {
1103 if (sv_first) {
1104 TransData *td = sv.td;
1105 sv = *sv_first;
1106 sv.td = td;
1107 }
1108 else {
1109 sv_first = &sv;
1110 float2 iloc = sv.td->iloc;
1111 const float2 aspect = t->aspect;
1112 if (curr.fdata[0].f) {
1113 float2 dst = curr.fdata[0].dst * aspect;
1114 sv.dir_side[0] = float3(dst - iloc, 0.0f);
1115 }
1116 if (curr.fdata[1].f) {
1117 float2 dst = curr.fdata[1].dst * aspect;
1118 sv.dir_side[1] = float3(dst - iloc, 0.0f);
1119 }
1120 sv.edge_len = math::distance(sv.dir_side[0], sv.dir_side[1]);
1121 sv.loop_nr = loop_nr;
1122 }
1123 }
1124
1125 if (i_prev != -1 && prev.i == i_prev) {
1126 /* Cycle returned to the beginning.
1127 * The data with index `i_curr` was computed twice to make sure the directions are
1128 * correct the second time. */
1129 break;
1130 }
1131
1132 /* Move forward. */
1133 prev = curr;
1134 curr = next;
1135 next.fdata[0].f = next.fdata[1].f = nullptr;
1136 }
1137 loop_nr++;
1138 }
1139 *r_group_len = loop_nr;
1140 return r_sv;
1141}
1142
1144
1146 /*flags*/ (T_EDIT | T_POINTS | T_2D_EDIT),
1147 /*create_trans_data*/ createTransUVs,
1148 /*recalc_data*/ recalcData_uv,
1149 /*special_aftertrans_update*/ nullptr,
1150};
1151
1152} // namespace blender::ed::transform
SpaceImage * CTX_wm_space_image(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ISECT_LINE_LINE_COLINEAR
bool isect_ray_seg_v2(const float ray_origin[2], const float ray_direction[2], const float v0[2], const float v1[2], float *r_lambda, float *r_u)
int isect_line_line_v2_point(const float v0[2], const float v1[2], const float v2[2], const float v3[2], float r_vi[2])
void unit_m3(float m[3][3])
MINLINE float len_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_v2(float r[2], const float a[2])
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 bool equals_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE bool compare_v2v2(const float v1[2], const float v2[2], float limit) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
void mid_v3_v3v3(float r[3], const float a[3], const float b[3])
#define POINTER_FROM_INT(i)
#define POINTER_AS_INT(i)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ UV_SELECT_EDGE
@ SI_CLIP_UV
@ SI_LIVE_UNWRAP
@ SI_PIXEL_ROUND_CENTER
@ SI_PIXEL_ROUND_CORNER
@ SI_PIXEL_ROUND_DISABLED
@ V3D_AROUND_LOCAL_ORIGINS
void ED_space_image_get_size(SpaceImage *sima, int *r_width, int *r_height)
bool ED_space_image_show_uvedit(const SpaceImage *sima, Object *obedit)
UvElement * BM_uv_element_get(const UvElementMap *element_map, const BMLoop *l)
void BM_uv_element_map_free(UvElementMap *element_map)
UvElementMap * BM_uv_element_map_create(BMesh *bm, const Scene *scene, bool uv_selected, bool use_winding, bool use_seams, bool do_islands)
void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit, struct wmWindow *win_modal)
char ED_uvedit_select_mode_get(const Scene *scene)
void ED_uvedit_live_unwrap_re_solve()
bool uvedit_uv_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets)
bool uvedit_face_visible_test(const Scene *scene, const BMFace *efa)
bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMUVOffsets &offsets)
bool uvedit_edge_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMUVOffsets &offsets)
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
@ BM_ELEM_TAG
#define BM_ELEM_CD_GET_FLOAT_P(ele, offset)
@ BM_LOOP
#define BM_elem_index_get(ele)
#define BM_elem_flag_disable(ele, hflag)
#define BM_elem_index_set(ele, index)
#define BM_elem_flag_test(ele, hflag)
#define BM_elem_flag_test_bool(ele, hflag)
#define BM_elem_flag_enable(ele, hflag)
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
@ BM_FACES_OF_MESH
@ BM_LOOPS_OF_VERT
@ BM_LOOPS_OF_FACE
BMesh const char void * data
BMesh * bm
void BM_mesh_elem_index_ensure(BMesh *bm, const char htype)
ATTR_WARN_UNUSED_RESULT const void * element
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMLoop * l_b
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
int64_t size() const
Definition BLI_array.hh:245
const T * data() const
Definition BLI_array.hh:301
IndexRange index_range() const
Definition BLI_array.hh:349
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T * end() const
Definition BLI_span.hh:224
constexpr const T * begin() const
Definition BLI_span.hh:220
int64_t size() const
void append(const T &value)
void reserve(const int64_t min_capacity)
Span< T > as_span() const
#define MEM_SAFE_FREE(v)
int count
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong * next
static void recalcData_uv(TransInfo *t)
Array< TransDataVertSlideVert > transform_mesh_uv_vert_slide_data_create(const TransInfo *t, TransDataContainer *tc, Vector< float3 > &r_loc_dst_buffer)
static void uv_set_connectivity_distance(const ToolSettings *ts, BMesh *bm, float *dists, const float aspect[2])
static bool bm_loop_uv_calc_opposite_co(const BMLoop *l_tmp, const float2 &uv_tmp, const BMUVOffsets &offsets, const float2 &ray_direction, float2 &r_co)
Array< TransDataEdgeSlideVert > transform_mesh_uv_edge_slide_data_create(const TransInfo *t, TransDataContainer *tc, int *r_group_len)
static void UVsToTransData(const float aspect[2], float *uv, const float *center, const float calc_dist, const bool selected, BMLoop *l, TransData *r_td, TransData2D *r_td2d)
static float3 isect_face_dst(const BMLoop *l)
static UVGroups * mesh_uv_groups_get(TransDataContainer *tc, BMesh *bm, const BMUVOffsets &offsets)
TransConvertTypeInfo TransConvertType_MeshUV
static bool mesh_uv_group_is_inner(const TransDataContainer *tc, const BMUVOffsets &offsets, Span< int > group)
static void createTransUVs(bContext *C, TransInfo *t)
static void flushTransUVs(TransInfo *t)
T distance(const T &a, const T &b)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T midpoint(const T &a, const T &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< T, 3 > orthogonal(const VecBase< T, 3 > &v)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
struct BMVert * v
struct BMLoop * radial_next
struct BMLoop * prev
struct BMFace * f
struct BMLoop * next
float co[3]
int totloop
Definition DNA_ID.h:404
struct LinkNode * next
void * first
ListBase spacedata
void(* free_cb)(TransInfo *, TransDataContainer *tc, TransCustomData *custom_data)
Definition transform.hh:628
void foreach_index_selected(FunctionRef< void(int)> fn) const
Definition transform.hh:780
void init(const TransDataContainer *tc, BMesh *bm, const BMUVOffsets &offsets)
Array< TransDataEdgeSlideVert > sd_array_create_and_init_edge(TransDataContainer *tc)
Span< int > td_indices_get(const int group_index) const
MutableSpan< TransDataVertSlideVert > sd_group_get(MutableSpan< TransDataVertSlideVert > sd_array, const int group_index)
MutableSpan< TransDataEdgeSlideVert > sd_group_get(MutableSpan< TransDataEdgeSlideVert > sd_array, const int group_index)
Array< TransDataVertSlideVert > sd_array_create_and_init(TransDataContainer *tc)
i
Definition text_draw.cc:230
#define FOREACH_TRANS_DATA_CONTAINER(t, th)
Definition transform.hh:42
conversion and adaptation of different datablocks to a common struct.
#define TMP_LOOP_SELECT_TAG