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