Blender V5.0
sculpt_uv.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2002-2009 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9
10#include "MEM_guardedalloc.h"
11
12#include "BLI_ghash.h"
13#include "BLI_math_base_safe.h"
14#include "BLI_math_geom.h"
15#include "BLI_math_vector.h"
16#include "BLI_utildefines.h"
17
18#include "DNA_object_types.h"
19#include "DNA_scene_types.h"
20
21#include "BKE_brush.hh"
22#include "BKE_colortools.hh"
23#include "BKE_context.hh"
24#include "BKE_customdata.hh"
25#include "BKE_editmesh.hh"
26#include "BKE_image.hh"
27#include "BKE_mesh_mapping.hh"
28#include "BKE_paint.hh"
29
30#include "DEG_depsgraph.hh"
31
32#include "ED_image.hh"
33#include "ED_mesh.hh"
34#include "ED_screen.hh"
35#include "ED_uvedit.hh"
36
37#include "WM_api.hh"
38#include "WM_types.hh"
39
40#include "RNA_access.hh"
41#include "RNA_define.hh"
42
43#include "paint_intern.hh"
44#include "uvedit_intern.hh"
45
46#include "UI_view2d.hh"
47
48namespace {
49
50enum eBrushUVSculptTool {
51 UV_SCULPT_BRUSH_TYPE_GRAB = 0,
52 UV_SCULPT_BRUSH_TYPE_RELAX = 1,
53 UV_SCULPT_BRUSH_TYPE_PINCH = 2,
54};
55
56enum {
57 UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN = 0,
58 UV_SCULPT_BRUSH_TYPE_RELAX_HC = 1,
59 UV_SCULPT_BRUSH_TYPE_RELAX_COTAN = 2,
60};
61
62/* When set, the UV element is on the boundary of the graph.
63 * i.e. Instead of a 2-dimensional laplace operator, use a 1-dimensional version.
64 * Visually, UV elements on the graph boundary appear as borders of the UV Island. */
65#define MARK_BOUNDARY 1
66
67struct UvAdjacencyElement {
71 float *uv;
73 bool is_locked;
75 bool is_boundary;
76};
77
78struct UvEdge {
79 uint uv1;
80 uint uv2;
82 bool is_interior;
83};
84
85struct UVInitialStrokeElement {
87 int uv;
88
90 float strength;
91
93 float initial_uv[2];
94};
95
96struct UVInitialStroke {
98 UVInitialStrokeElement *initialSelection;
99
101 int totalInitialSelected;
102
104 float init_coord[2];
105};
106
108struct UvSculptData {
113 UvAdjacencyElement *uv;
114
116 int totalUniqueUvs;
117
119 UvEdge *uvedges;
120
122 int totalUvEdges;
123
125 UVInitialStroke *initial_stroke;
126
128 wmTimer *timer;
129
131 UvElementMap *elementMap;
132
134 UvSculpt *uvsculpt;
135
137 char tool;
138
140 char invert;
141
143 bool constrain_to_bounds;
144
146 float uv_base_offset[2];
147};
148
149} // namespace
150
151static void apply_sculpt_data_constraints(UvSculptData *sculptdata, float uv[2])
152{
153 if (!sculptdata->constrain_to_bounds) {
154 return;
155 }
156 float u = sculptdata->uv_base_offset[0];
157 float v = sculptdata->uv_base_offset[1];
158 uv[0] = clamp_f(uv[0], u, u + 1.0f);
159 uv[1] = clamp_f(uv[1], v, v + 1.0f);
160}
161
162static float calc_strength(const UvSculptData *sculptdata, float p, const float len)
163{
164 float strength = BKE_brush_curve_strength(
166 sculptdata->uvsculpt->curve_distance_falloff,
167 p,
168 len);
169
170 CLAMP(strength, 0.0f, 1.0f);
171
172 return strength;
173}
174
175/*********** Improved Laplacian Relaxation Operator ************************/
176/* original code by Raul Fernandez Hernandez "farsthary" *
177 * adapted to uv smoothing by Antony Riakiatakis *
178 ***************************************************************************/
179
181 float sum_co[2], p[2], b[2], sum_b[2];
183};
184
185static void HC_relaxation_iteration_uv(UvSculptData *sculptdata,
186 const int cd_loop_uv_offset,
187 const float mouse_coord[2],
188 const float alpha,
189 const float radius_sq,
190 const float aspectRatio)
191{
192 float diff[2];
193 int i;
194 const float radius = sqrtf(radius_sq);
195
196 Temp_UVData *tmp_uvdata = MEM_calloc_arrayN<Temp_UVData>(sculptdata->totalUniqueUvs,
197 "Temporal data");
198
199 /* counting neighbors */
200 for (i = 0; i < sculptdata->totalUvEdges; i++) {
201 UvEdge *tmpedge = sculptdata->uvedges + i;
202 tmp_uvdata[tmpedge->uv1].ncounter++;
203 tmp_uvdata[tmpedge->uv2].ncounter++;
204
205 add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv);
206 add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv);
207 }
208
209 for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
210 copy_v2_v2(diff, tmp_uvdata[i].sum_co);
211 mul_v2_fl(diff, 1.0f / tmp_uvdata[i].ncounter);
212 copy_v2_v2(tmp_uvdata[i].p, diff);
213
214 tmp_uvdata[i].b[0] = diff[0] - sculptdata->uv[i].uv[0];
215 tmp_uvdata[i].b[1] = diff[1] - sculptdata->uv[i].uv[1];
216 }
217
218 for (i = 0; i < sculptdata->totalUvEdges; i++) {
219 UvEdge *tmpedge = sculptdata->uvedges + i;
220 add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_b, tmp_uvdata[tmpedge->uv2].b);
221 add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_b, tmp_uvdata[tmpedge->uv1].b);
222 }
223
224 for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
225 if (sculptdata->uv[i].is_locked) {
226 continue;
227 }
228
229 sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord);
230 diff[1] /= aspectRatio;
231 float dist = dot_v2v2(diff, diff);
232 if (dist <= radius_sq) {
234 float strength;
235 strength = alpha * calc_strength(sculptdata, sqrtf(dist), radius);
236
237 sculptdata->uv[i].uv[0] = (1.0f - strength) * sculptdata->uv[i].uv[0] +
238 strength *
239 (tmp_uvdata[i].p[0] -
240 0.5f * (tmp_uvdata[i].b[0] +
241 tmp_uvdata[i].sum_b[0] / tmp_uvdata[i].ncounter));
242 sculptdata->uv[i].uv[1] = (1.0f - strength) * sculptdata->uv[i].uv[1] +
243 strength *
244 (tmp_uvdata[i].p[1] -
245 0.5f * (tmp_uvdata[i].b[1] +
246 tmp_uvdata[i].sum_b[1] / tmp_uvdata[i].ncounter));
247
248 apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv);
249
250 for (element = sculptdata->uv[i].element; element; element = element->next) {
251 if (element->separate && element != sculptdata->uv[i].element) {
252 break;
253 }
254 float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(element->l, cd_loop_uv_offset);
255 copy_v2_v2(*luv, sculptdata->uv[i].uv);
256 }
257 }
258 }
259
260 MEM_SAFE_FREE(tmp_uvdata);
261}
262
263/* Legacy version which only does laplacian relaxation.
264 * Probably a little faster as it caches UvEdges.
265 * Mostly preserved for comparison with `HC_relaxation_iteration_uv`.
266 * Once the HC method has been merged into `relaxation_iteration_uv`,
267 * all the `HC_*` and `laplacian_*` specific functions can probably be removed.
268 */
269
270static void laplacian_relaxation_iteration_uv(UvSculptData *sculptdata,
271 const int cd_loop_uv_offset,
272 const float mouse_coord[2],
273 const float alpha,
274 const float radius_sq,
275 const float aspectRatio)
276{
277 float diff[2];
278 int i;
279 const float radius = sqrtf(radius_sq);
280
281 Temp_UVData *tmp_uvdata = MEM_calloc_arrayN<Temp_UVData>(sculptdata->totalUniqueUvs,
282 "Temporal data");
283
284 /* counting neighbors */
285 for (i = 0; i < sculptdata->totalUvEdges; i++) {
286 UvEdge *tmpedge = sculptdata->uvedges + i;
287 bool code1 = sculptdata->uv[sculptdata->uvedges[i].uv1].is_boundary;
288 bool code2 = sculptdata->uv[sculptdata->uvedges[i].uv2].is_boundary;
289 if (code1 || (code1 == code2)) {
290 tmp_uvdata[tmpedge->uv2].ncounter++;
291 add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv);
292 }
293 if (code2 || (code1 == code2)) {
294 tmp_uvdata[tmpedge->uv1].ncounter++;
295 add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv);
296 }
297 }
298
299 /* Original Laplacian algorithm included removal of normal component of translation.
300 * here it is not needed since we translate along the UV plane always. */
301 for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
302 copy_v2_v2(tmp_uvdata[i].p, tmp_uvdata[i].sum_co);
303 mul_v2_fl(tmp_uvdata[i].p, 1.0f / tmp_uvdata[i].ncounter);
304 }
305
306 for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
307 if (sculptdata->uv[i].is_locked) {
308 continue;
309 }
310
311 sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord);
312 diff[1] /= aspectRatio;
313 float dist = dot_v2v2(diff, diff);
314 if (dist <= radius_sq) {
316 float strength;
317 strength = alpha * calc_strength(sculptdata, sqrtf(dist), radius);
318
319 sculptdata->uv[i].uv[0] = (1.0f - strength) * sculptdata->uv[i].uv[0] +
320 strength * tmp_uvdata[i].p[0];
321 sculptdata->uv[i].uv[1] = (1.0f - strength) * sculptdata->uv[i].uv[1] +
322 strength * tmp_uvdata[i].p[1];
323
324 apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv);
325
326 for (element = sculptdata->uv[i].element; element; element = element->next) {
327 if (element->separate && element != sculptdata->uv[i].element) {
328 break;
329 }
330
331 float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(element->l, cd_loop_uv_offset);
332 copy_v2_v2(*luv, sculptdata->uv[i].uv);
333 }
334 }
335 }
336
337 MEM_SAFE_FREE(tmp_uvdata);
338}
339
340static void add_weighted_edge(float (*delta_buf)[3],
341 const UvElement *storage,
342 const UvElement *ele_next,
343 const UvElement *ele_prev,
344 const float luv_next[2],
345 const float luv_prev[2],
346 const float weight)
347{
348 float delta[2];
349 sub_v2_v2v2(delta, luv_next, luv_prev);
350
351 bool code1 = (ele_prev->flag & MARK_BOUNDARY);
352 bool code2 = (ele_next->flag & MARK_BOUNDARY);
353 if (code1 || (code1 == code2)) {
354 int index_next = ele_next - storage;
355 delta_buf[index_next][0] -= delta[0] * weight;
356 delta_buf[index_next][1] -= delta[1] * weight;
357 delta_buf[index_next][2] += fabsf(weight);
358 }
359 if (code2 || (code1 == code2)) {
360 int index_prev = ele_prev - storage;
361 delta_buf[index_prev][0] += delta[0] * weight;
362 delta_buf[index_prev][1] += delta[1] * weight;
363 delta_buf[index_prev][2] += fabsf(weight);
364 }
365}
366
367static float tri_weight_v3(int method, const float *v1, const float *v2, const float *v3)
368{
369 switch (method) {
370 case UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN:
371 case UV_SCULPT_BRUSH_TYPE_RELAX_HC:
372 return 1.0f;
373 case UV_SCULPT_BRUSH_TYPE_RELAX_COTAN:
374 return cotangent_tri_weight_v3(v1, v2, v3);
375 default:
377 }
378 return 0.0f;
379}
380
381static void relaxation_iteration_uv(UvSculptData *sculptdata,
382 const int cd_loop_uv_offset,
383 const float mouse_coord[2],
384 const float alpha,
385 const float radius_sq,
386 const float aspect_ratio,
387 const int method)
388{
389 if (method == UV_SCULPT_BRUSH_TYPE_RELAX_HC) {
391 sculptdata, cd_loop_uv_offset, mouse_coord, alpha, radius_sq, aspect_ratio);
392 return;
393 }
394 if (method == UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN) {
396 sculptdata, cd_loop_uv_offset, mouse_coord, alpha, radius_sq, aspect_ratio);
397 return;
398 }
399
400 UvElement **head_table = BM_uv_element_map_ensure_head_table(sculptdata->elementMap);
401
402 const int total_uvs = sculptdata->elementMap->total_uvs;
403 float (*delta_buf)[3] = (float (*)[3])MEM_callocN(total_uvs * sizeof(float[3]), __func__);
404
405 const UvElement *storage = sculptdata->elementMap->storage;
406 for (int j = 0; j < total_uvs; j++) {
407 const UvElement *ele_curr = storage + j;
408 const UvElement *ele_next = BM_uv_element_get(sculptdata->elementMap, ele_curr->l->next);
409 const UvElement *ele_prev = BM_uv_element_get(sculptdata->elementMap, ele_curr->l->prev);
410
411 const float *v_curr_co = ele_curr->l->v->co;
412 const float *v_prev_co = ele_prev->l->v->co;
413 const float *v_next_co = ele_next->l->v->co;
414
415 const float (*luv_curr)[2] = BM_ELEM_CD_GET_FLOAT2_P(ele_curr->l, cd_loop_uv_offset);
416 const float (*luv_next)[2] = BM_ELEM_CD_GET_FLOAT2_P(ele_next->l, cd_loop_uv_offset);
417 const float (*luv_prev)[2] = BM_ELEM_CD_GET_FLOAT2_P(ele_prev->l, cd_loop_uv_offset);
418
419 const UvElement *head_curr = head_table[ele_curr - sculptdata->elementMap->storage];
420 const UvElement *head_next = head_table[ele_next - sculptdata->elementMap->storage];
421 const UvElement *head_prev = head_table[ele_prev - sculptdata->elementMap->storage];
422
423 /* If the mesh is triangulated with no boundaries, only one edge is required. */
424 const float weight_curr = tri_weight_v3(method, v_curr_co, v_prev_co, v_next_co);
425 add_weighted_edge(delta_buf, storage, head_next, head_prev, *luv_next, *luv_prev, weight_curr);
426
427 /* Triangulated with a boundary? We need the incoming edges to solve the boundary. */
428 const float weight_prev = tri_weight_v3(method, v_prev_co, v_curr_co, v_next_co);
429 add_weighted_edge(delta_buf, storage, head_next, head_curr, *luv_next, *luv_curr, weight_prev);
430
431 if (method == UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN) {
432 /* Laplacian method has zero weights on virtual edges. */
433 continue;
434 }
435
436 /* Meshes with quads (or other n-gons) need "virtual" edges too. */
437 const float weight_next = tri_weight_v3(method, v_next_co, v_curr_co, v_prev_co);
438 add_weighted_edge(delta_buf, storage, head_prev, head_curr, *luv_prev, *luv_curr, weight_next);
439 }
440
441 for (int i = 0; i < sculptdata->totalUniqueUvs; i++) {
442 UvAdjacencyElement *adj_el = &sculptdata->uv[i];
443 if (adj_el->is_locked) {
444 continue; /* Locked UVs can't move. */
445 }
446
447 /* Is UV within influence? */
448 float diff[2];
449 sub_v2_v2v2(diff, adj_el->uv, mouse_coord);
450 diff[1] /= aspect_ratio;
451 const float dist_sq = len_squared_v2(diff);
452 if (dist_sq > radius_sq) {
453 continue;
454 }
455 const float strength = alpha * calc_strength(sculptdata, sqrtf(dist_sq), sqrtf(radius_sq));
456
457 const float *delta_sum = delta_buf[adj_el->element - storage];
458
459 {
460 const float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(adj_el->element->l, cd_loop_uv_offset);
461 BLI_assert(adj_el->uv == (float *)luv); /* Only true for head. */
462 adj_el->uv[0] = (*luv)[0] + strength * safe_divide(delta_sum[0], delta_sum[2]);
463 adj_el->uv[1] = (*luv)[1] + strength * safe_divide(delta_sum[1], delta_sum[2]);
464 apply_sculpt_data_constraints(sculptdata, adj_el->uv);
465 }
466
467 /* Copy UV co-ordinates to all UvElements. */
468 UvElement *tail = adj_el->element;
469 while (tail) {
470 float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(tail->l, cd_loop_uv_offset);
471 copy_v2_v2(*luv, adj_el->uv);
472 tail = tail->next;
473 if (tail && tail->separate) {
474 break;
475 }
476 }
477 }
478
479 MEM_SAFE_FREE(delta_buf);
480}
481
483 wmOperator *op,
484 const wmEvent *event,
485 Object *obedit)
486{
487 ARegion *region = CTX_wm_region(C);
489 UvSculptData *sculptdata = (UvSculptData *)op->customdata;
490 eBrushUVSculptTool tool = eBrushUVSculptTool(sculptdata->tool);
491 int invert = sculptdata->invert ? -1 : 1;
492 float alpha = sculptdata->uvsculpt->strength;
493
494 float co[2];
495 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
496
498
499 int width, height;
500 ED_space_image_get_size(sima, &width, &height);
501
502 float zoomx, zoomy;
503 ED_space_image_get_zoom(sima, region, &zoomx, &zoomy);
504
505 const float radius = (sculptdata->uvsculpt->size / 2.0f) / (width * zoomx);
506 float aspectRatio = width / float(height);
507
508 /* We will compare squares to save some computation */
509 const float radius_sq = radius * radius;
510
511 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
512
513 switch (tool) {
514 case UV_SCULPT_BRUSH_TYPE_PINCH: {
515 int i;
516 alpha *= invert;
517 for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
518 if (sculptdata->uv[i].is_locked) {
519 continue;
520 }
521
522 float diff[2];
523 sub_v2_v2v2(diff, sculptdata->uv[i].uv, co);
524 diff[1] /= aspectRatio;
525 float dist = dot_v2v2(diff, diff);
526 if (dist <= radius_sq) {
528 float strength;
529 strength = alpha * calc_strength(sculptdata, sqrtf(dist), radius);
531
532 sculptdata->uv[i].uv[0] -= strength * diff[0] * 0.001f;
533 sculptdata->uv[i].uv[1] -= strength * diff[1] * 0.001f;
534
535 apply_sculpt_data_constraints(sculptdata, sculptdata->uv[i].uv);
536
537 for (element = sculptdata->uv[i].element; element; element = element->next) {
538 if (element->separate && element != sculptdata->uv[i].element) {
539 break;
540 }
541 float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(element->l, cd_loop_uv_offset);
542 copy_v2_v2(*luv, sculptdata->uv[i].uv);
543 }
544 }
545 }
546 break;
547 }
548 case UV_SCULPT_BRUSH_TYPE_RELAX: {
549 relaxation_iteration_uv(sculptdata,
550 cd_loop_uv_offset,
551 co,
552 alpha,
553 radius_sq,
554 aspectRatio,
555 RNA_enum_get(op->ptr, "relax_method"));
556 break;
557 }
558 case UV_SCULPT_BRUSH_TYPE_GRAB: {
559 int i;
560 float diff[2];
561 sub_v2_v2v2(diff, co, sculptdata->initial_stroke->init_coord);
562
563 for (i = 0; i < sculptdata->initial_stroke->totalInitialSelected; i++) {
565 int uvindex = sculptdata->initial_stroke->initialSelection[i].uv;
566 float strength = sculptdata->initial_stroke->initialSelection[i].strength;
567 sculptdata->uv[uvindex].uv[0] =
568 sculptdata->initial_stroke->initialSelection[i].initial_uv[0] + strength * diff[0];
569 sculptdata->uv[uvindex].uv[1] =
570 sculptdata->initial_stroke->initialSelection[i].initial_uv[1] + strength * diff[1];
571
572 apply_sculpt_data_constraints(sculptdata, sculptdata->uv[uvindex].uv);
573
574 for (element = sculptdata->uv[uvindex].element; element; element = element->next) {
575 if (element->separate && element != sculptdata->uv[uvindex].element) {
576 break;
577 }
578 float (*luv)[2] = BM_ELEM_CD_GET_FLOAT2_P(element->l, cd_loop_uv_offset);
579 copy_v2_v2(*luv, sculptdata->uv[uvindex].uv);
580 }
581 }
582 if (sima->flag & SI_LIVE_UNWRAP) {
584 }
585 break;
586 }
587 }
588}
589
591{
593 if (sima->flag & SI_LIVE_UNWRAP) {
595 }
596 UvSculptData *data = static_cast<UvSculptData *>(op->customdata);
597 if (data->timer) {
599 }
600 BM_uv_element_map_free(data->elementMap);
601 data->elementMap = nullptr;
602 MEM_SAFE_FREE(data->uv);
603 MEM_SAFE_FREE(data->uvedges);
604 if (data->initial_stroke) {
605 MEM_SAFE_FREE(data->initial_stroke->initialSelection);
606 MEM_SAFE_FREE(data->initial_stroke);
607 }
608
610 op->customdata = nullptr;
611}
612
614 BMLoop *l,
615 int island_index,
616 const bool do_islands)
617{
619 if (!element || (do_islands && element->island != island_index)) {
620 return -1;
621 }
622 return element - map->storage;
623}
624
625static uint uv_edge_hash(const void *key)
626{
627 const UvEdge *edge = static_cast<const UvEdge *>(key);
628 return (BLI_ghashutil_uinthash(edge->uv2) + BLI_ghashutil_uinthash(edge->uv1));
629}
630
631static bool uv_edge_compare(const void *a, const void *b)
632{
633 const UvEdge *edge1 = static_cast<const UvEdge *>(a);
634 const UvEdge *edge2 = static_cast<const UvEdge *>(b);
635
636 if ((edge1->uv1 == edge2->uv1) && (edge1->uv2 == edge2->uv2)) {
637 return false;
638 }
639 return true;
640}
641
642static void set_element_flag(UvElement *element, const int flag)
643{
644 while (element) {
645 element->flag |= flag;
646 element = element->next;
647 if (!element || element->separate) {
648 break;
649 }
650 }
651}
652
653static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wmEvent *event)
654{
655 Scene *scene = CTX_data_scene(C);
656 Object *obedit = CTX_data_edit_object(C);
657 ToolSettings *ts = scene->toolsettings;
658 UvSculptData *data = MEM_callocN<UvSculptData>(__func__);
660 BMesh *bm = em->bm;
661
662 op->customdata = data;
663
665
666 if (!data) {
667 return nullptr;
668 }
669
670 ARegion *region = CTX_wm_region(C);
671 float co[2];
672 BMFace *efa;
673 float (*luv)[2];
674 BMLoop *l;
675 BMIter iter, liter;
676
677 GHashIterator gh_iter;
678
679 bool do_island_optimization = !(ts->uv_sculpt_settings & UV_SCULPT_ALL_ISLANDS);
680 int island_index = 0;
681 if (STREQ(op->type->idname, "SCULPT_OT_uv_sculpt_relax")) {
682 data->tool = UV_SCULPT_BRUSH_TYPE_RELAX;
683 }
684 else if (STREQ(op->type->idname, "SCULPT_OT_uv_sculpt_grab")) {
685 data->tool = UV_SCULPT_BRUSH_TYPE_GRAB;
686 }
687 else {
688 data->tool = UV_SCULPT_BRUSH_TYPE_PINCH;
689 }
690 data->invert = RNA_boolean_get(op->ptr, "use_invert");
691
692 data->uvsculpt = &ts->uvsculpt;
693
694 /* Winding was added to island detection in 5197aa04c6bd
695 * However the sculpt tools can flip faces, potentially creating orphaned islands.
696 * See #100132 */
697 const bool use_winding = false;
698 const bool use_seams = true;
699 data->elementMap = BM_uv_element_map_create(
700 bm, scene, false, use_winding, use_seams, do_island_optimization);
701
702 if (!data->elementMap) {
704 return nullptr;
705 }
706
707 /* Mouse coordinates, useful for some functions like grab and sculpt all islands */
708 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
709
710 /* We need to find the active island here. */
711 if (do_island_optimization) {
713 uv_find_nearest_vert(scene, obedit, co, 0.0f, &hit);
714
715 UvElement *element = BM_uv_element_get(data->elementMap, hit.l);
716 if (element) {
717 island_index = element->island;
718 }
719 }
720
721 /* Count 'unique' UVs */
722 int unique_uvs = data->elementMap->total_unique_uvs;
723 if (do_island_optimization) {
724 unique_uvs = data->elementMap->island_total_unique_uvs[island_index];
725 }
726
727 /* Allocate the unique uv buffers */
728 data->uv = MEM_calloc_arrayN<UvAdjacencyElement>(unique_uvs, __func__);
729 /* Holds, for each UvElement in elementMap, an index of its unique UV. */
730 int *uniqueUv = MEM_malloc_arrayN<int>(data->elementMap->total_uvs, __func__);
731 GHash *edgeHash = BLI_ghash_new(uv_edge_hash, uv_edge_compare, "uv_brush_edge_hash");
732 /* we have at most totalUVs edges */
733 UvEdge *edges = MEM_calloc_arrayN<UvEdge>(data->elementMap->total_uvs, __func__);
734 if (!data->uv || !uniqueUv || !edgeHash || !edges) {
735 MEM_SAFE_FREE(edges);
736 MEM_SAFE_FREE(uniqueUv);
737 if (edgeHash) {
738 BLI_ghash_free(edgeHash, nullptr, nullptr);
739 }
741 return nullptr;
742 }
743
744 data->totalUniqueUvs = unique_uvs;
745 /* Index for the UvElements. */
746 int counter = -1;
747
748 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
749 /* initialize the unique UVs */
750 for (int i = 0; i < bm->totvert; i++) {
751 UvElement *element = data->elementMap->vertex[i];
752 for (; element; element = element->next) {
753 if (element->separate) {
754 if (do_island_optimization && (element->island != island_index)) {
755 /* skip this uv if not on the active island */
756 for (; element->next && !(element->next->separate); element = element->next) {
757 /* pass */
758 }
759 continue;
760 }
761
762 luv = BM_ELEM_CD_GET_FLOAT2_P(element->l, offsets.uv);
763
764 counter++;
765 data->uv[counter].element = element;
766 data->uv[counter].uv = *luv;
767 if (data->tool != UV_SCULPT_BRUSH_TYPE_GRAB) {
768 if (BM_ELEM_CD_GET_BOOL(element->l, offsets.pin)) {
769 data->uv[counter].is_locked = true;
770 }
771 }
772 }
773 /* Pointer arithmetic to the rescue, as always :). */
774 uniqueUv[element - data->elementMap->storage] = counter;
775 }
776 }
777 BLI_assert(counter + 1 == unique_uvs);
778
779 /* Now, on to generate our uv connectivity data */
780 counter = 0;
781 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
782 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
784 data->elementMap, l, island_index, do_island_optimization);
786 data->elementMap, l->next, island_index, do_island_optimization);
787
788 /* Skip edge if not found(unlikely) or not on valid island */
789 if (itmp1 == -1 || itmp2 == -1) {
790 continue;
791 }
792
793 int offset1 = uniqueUv[itmp1];
794 int offset2 = uniqueUv[itmp2];
795
796 /* Using an order policy, sort UVs according to address space.
797 * This avoids having two different UvEdges with the same UVs on different positions. */
798 if (offset1 < offset2) {
799 edges[counter].uv1 = offset1;
800 edges[counter].uv2 = offset2;
801 }
802 else {
803 edges[counter].uv1 = offset2;
804 edges[counter].uv2 = offset1;
805 }
806 UvEdge *prev_edge = static_cast<UvEdge *>(BLI_ghash_lookup(edgeHash, &edges[counter]));
807 if (prev_edge) {
808 prev_edge->is_interior = true;
809 edges[counter].is_interior = true;
810 }
811 else {
812 BLI_ghash_insert(edgeHash, &edges[counter], &edges[counter]);
813 }
814 counter++;
815 }
816 }
817
818 MEM_SAFE_FREE(uniqueUv);
819
820 /* Allocate connectivity data, we allocate edges once */
821 data->uvedges = MEM_calloc_arrayN<UvEdge>(BLI_ghash_len(edgeHash), __func__);
822 if (!data->uvedges) {
823 BLI_ghash_free(edgeHash, nullptr, nullptr);
824 MEM_SAFE_FREE(edges);
826 return nullptr;
827 }
828
829 /* fill the edges with data */
830 {
831 int i = 0;
832 GHASH_ITER (gh_iter, edgeHash) {
833 data->uvedges[i++] = *((UvEdge *)BLI_ghashIterator_getKey(&gh_iter));
834 }
835 data->totalUvEdges = BLI_ghash_len(edgeHash);
836 }
837
838 /* cleanup temporary stuff */
839 BLI_ghash_free(edgeHash, nullptr, nullptr);
840 MEM_SAFE_FREE(edges);
841
842 /* transfer boundary edge property to UVs */
843 for (int i = 0; i < data->totalUvEdges; i++) {
844 if (!data->uvedges[i].is_interior) {
845 data->uv[data->uvedges[i].uv1].is_boundary = true;
846 data->uv[data->uvedges[i].uv2].is_boundary = true;
848 data->uv[data->uvedges[i].uv1].is_locked = true;
849 data->uv[data->uvedges[i].uv2].is_locked = true;
850 }
851 set_element_flag(data->uv[data->uvedges[i].uv1].element, MARK_BOUNDARY);
852 set_element_flag(data->uv[data->uvedges[i].uv2].element, MARK_BOUNDARY);
853 }
854 }
855
857 data->constrain_to_bounds = (sima->flag & SI_CLIP_UV);
858 BKE_image_find_nearest_tile_with_offset(sima->image, co, data->uv_base_offset);
859
860 /* Allocate initial selection for grab tool */
861 if (data->tool == UV_SCULPT_BRUSH_TYPE_GRAB) {
862 float alpha = data->uvsculpt->strength;
863 float radius = data->uvsculpt->size / 2.0f;
864 int width, height;
865 ED_space_image_get_size(sima, &width, &height);
866 float zoomx, zoomy;
867 ED_space_image_get_zoom(sima, region, &zoomx, &zoomy);
868
869 float aspectRatio = width / float(height);
870 radius /= (width * zoomx);
871 const float radius_sq = radius * radius;
872
873 /* Allocate selection stack */
874 data->initial_stroke = static_cast<UVInitialStroke *>(
875 MEM_mallocN(sizeof(*data->initial_stroke), __func__));
876 if (!data->initial_stroke) {
878 }
879 data->initial_stroke->initialSelection = MEM_malloc_arrayN<UVInitialStrokeElement>(
880 data->totalUniqueUvs, __func__);
881 if (!data->initial_stroke->initialSelection) {
883 }
884
885 copy_v2_v2(data->initial_stroke->init_coord, co);
886
887 counter = 0;
888 for (int i = 0; i < data->totalUniqueUvs; i++) {
889 if (data->uv[i].is_locked) {
890 continue;
891 }
892
893 float diff[2];
894 sub_v2_v2v2(diff, data->uv[i].uv, co);
895 diff[1] /= aspectRatio;
896 float dist = dot_v2v2(diff, diff);
897 if (dist <= radius_sq) {
898 float strength;
899 strength = alpha * calc_strength(data, sqrtf(dist), radius);
900
901 data->initial_stroke->initialSelection[counter].uv = i;
902 data->initial_stroke->initialSelection[counter].strength = strength;
903 copy_v2_v2(data->initial_stroke->initialSelection[counter].initial_uv, data->uv[i].uv);
904 counter++;
905 }
906 }
907
908 data->initial_stroke->totalInitialSelected = counter;
909 if (sima->flag & SI_LIVE_UNWRAP) {
910 wmWindow *win_modal = CTX_wm_window(C);
911 ED_uvedit_live_unwrap_begin(scene, obedit, win_modal);
912 }
913 }
914
915 return static_cast<UvSculptData *>(op->customdata);
916}
917
919{
920 UvSculptData *data;
921 Object *obedit = CTX_data_edit_object(C);
922
923 if (!(data = uv_sculpt_stroke_init(C, op, event))) {
924 return OPERATOR_CANCELLED;
925 }
926
927 uv_sculpt_stroke_apply(C, op, event, obedit);
928
930
931 if (!data->timer) {
933 return OPERATOR_CANCELLED;
934 }
936
938}
939
941{
942 UvSculptData *data = (UvSculptData *)op->customdata;
943 Object *obedit = CTX_data_edit_object(C);
944
945 switch (event->type) {
946 case LEFTMOUSE:
947 case MIDDLEMOUSE:
948 case RIGHTMOUSE:
950 return OPERATOR_FINISHED;
951
952 case MOUSEMOVE:
954 uv_sculpt_stroke_apply(C, op, event, obedit);
955 break;
956 case TIMER:
957 if (event->customdata == data->timer) {
958 uv_sculpt_stroke_apply(C, op, event, obedit);
959 }
960 break;
961 default:
963 }
964
967 DEG_id_tag_update(static_cast<ID *>(obedit->data), 0);
969}
970
972{
973 PropertyRNA *prop;
974
975 prop = RNA_def_boolean(
976 ot->srna, "use_invert", false, "Invert", "Invert action for the duration of the stroke");
978}
979
981{
982 ot->name = "Grab UVs";
983 ot->description = "Grab UVs";
984 ot->idname = "SCULPT_OT_uv_sculpt_grab";
985
986 ot->invoke = uv_sculpt_stroke_invoke;
987 ot->modal = uv_sculpt_stroke_modal;
989
991
993}
994
996{
997 ot->name = "Relax UVs";
998 ot->description = "Relax UVs";
999 ot->idname = "SCULPT_OT_uv_sculpt_relax";
1000
1001 ot->invoke = uv_sculpt_stroke_invoke;
1002 ot->modal = uv_sculpt_stroke_modal;
1004
1005 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1006
1008
1009 static const EnumPropertyItem relax_method_items[] = {
1010 {UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN,
1011 "LAPLACIAN",
1012 0,
1013 "Laplacian",
1014 "Use Laplacian method for relaxation"},
1015 {UV_SCULPT_BRUSH_TYPE_RELAX_HC, "HC", 0, "HC", "Use HC method for relaxation"},
1016 {UV_SCULPT_BRUSH_TYPE_RELAX_COTAN,
1017 "COTAN",
1018 0,
1019 "Geometry",
1020 "Use Geometry (cotangent) relaxation, making UVs follow the underlying 3D geometry"},
1021 {0, nullptr, 0, nullptr, nullptr},
1022 };
1023
1024 RNA_def_enum(ot->srna,
1025 "relax_method",
1026 relax_method_items,
1027 UV_SCULPT_BRUSH_TYPE_RELAX_LAPLACIAN,
1028 "Relax Method",
1029 "Algorithm used for UV relaxation");
1030}
1031
1033{
1034 ot->name = "Pinch UVs";
1035 ot->description = "Pinch UVs";
1036 ot->idname = "SCULPT_OT_uv_sculpt_pinch";
1037
1038 ot->invoke = uv_sculpt_stroke_invoke;
1039 ot->modal = uv_sculpt_stroke_modal;
1041
1042 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1043
1045}
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
Definition brush.cc:1577
void BKE_curvemapping_init(CurveMapping *cumap)
SpaceImage * CTX_wm_space_image(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Object * CTX_data_edit_object(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
int CustomData_get_offset(const CustomData *data, eCustomDataType type)
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
int BKE_image_find_nearest_tile_with_offset(const Image *image, const float co[2], float r_uv_offset[2]) ATTR_NONNULL(2
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
BLI_INLINE void * BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:295
unsigned int BLI_ghashutil_uinthash(unsigned int key)
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:318
GHash * BLI_ghash_new(GHashHashFP hashfp, GHashCmpFP cmpfp, const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:686
unsigned int BLI_ghash_len(const GHash *gh) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:702
void * BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:731
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition BLI_ghash.cc:707
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.cc:860
MINLINE float clamp_f(float value, float min, float max)
MINLINE float safe_divide(float a, float b)
float cotangent_tri_weight_v3(const float v1[3], const float v2[3], const float v3[3])
Definition math_geom.cc:198
MINLINE float len_squared_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void add_v2_v2(float r[2], const float a[2])
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE float dot_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v2(float n[2])
unsigned int uint
#define CLAMP(a, b, c)
#define STREQ(a, b)
void DEG_id_tag_update(ID *id, unsigned int flags)
eBrushCurvePreset
@ CD_PROP_FLOAT2
Object is a sort of wrapper for general info.
@ UV_SCULPT_ALL_ISLANDS
@ UV_SCULPT_LOCK_BORDERS
struct UvSculpt UvSculpt
@ SI_CLIP_UV
@ SI_LIVE_UNWRAP
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
void ED_space_image_get_size(SpaceImage *sima, int *r_width, int *r_height)
void ED_space_image_get_zoom(SpaceImage *sima, const ARegion *region, float *r_zoomx, float *r_zoomy)
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)
UvElement ** BM_uv_element_map_ensure_head_table(UvElementMap *element_map)
bool ED_operator_uvedit_space_image(bContext *C)
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit, struct wmWindow *win_modal)
void ED_uvedit_live_unwrap_re_solve()
void ED_uvedit_live_unwrap_end(bool cancel)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
#define C
Definition RandGen.cpp:29
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1668
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define BM_ELEM_CD_GET_BOOL(ele, offset)
#define BM_ELEM_CD_GET_FLOAT2_P(ele, offset)
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
@ BM_FACES_OF_MESH
@ BM_LOOPS_OF_FACE
BMesh const char void * data
BMesh * bm
ATTR_WARN_UNUSED_RESULT const void * element
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMVert * v
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
nullptr float
IMETHOD Vector diff(const Vector &a, const Vector &b, double dt)
Definition frames.inl:1166
CCL_NAMESPACE_BEGIN ccl_device float invert(const float color, const float factor)
Definition invert.h:11
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
#define fabsf
#define sqrtf
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
void SCULPT_OT_uv_sculpt_relax(wmOperatorType *ot)
Definition sculpt_uv.cc:995
static wmOperatorStatus uv_sculpt_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
Definition sculpt_uv.cc:918
static void laplacian_relaxation_iteration_uv(UvSculptData *sculptdata, const int cd_loop_uv_offset, const float mouse_coord[2], const float alpha, const float radius_sq, const float aspectRatio)
Definition sculpt_uv.cc:270
static void register_common_props(wmOperatorType *ot)
Definition sculpt_uv.cc:971
static UvSculptData * uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wmEvent *event)
Definition sculpt_uv.cc:653
static void relaxation_iteration_uv(UvSculptData *sculptdata, const int cd_loop_uv_offset, const float mouse_coord[2], const float alpha, const float radius_sq, const float aspect_ratio, const int method)
Definition sculpt_uv.cc:381
static void add_weighted_edge(float(*delta_buf)[3], const UvElement *storage, const UvElement *ele_next, const UvElement *ele_prev, const float luv_next[2], const float luv_prev[2], const float weight)
Definition sculpt_uv.cc:340
static bool uv_edge_compare(const void *a, const void *b)
Definition sculpt_uv.cc:631
static float tri_weight_v3(int method, const float *v1, const float *v2, const float *v3)
Definition sculpt_uv.cc:367
static void apply_sculpt_data_constraints(UvSculptData *sculptdata, float uv[2])
Definition sculpt_uv.cc:151
static int uv_element_offset_from_face_get(UvElementMap *map, BMLoop *l, int island_index, const bool do_islands)
Definition sculpt_uv.cc:613
static void HC_relaxation_iteration_uv(UvSculptData *sculptdata, const int cd_loop_uv_offset, const float mouse_coord[2], const float alpha, const float radius_sq, const float aspectRatio)
Definition sculpt_uv.cc:185
static void uv_sculpt_stroke_apply(bContext *C, wmOperator *op, const wmEvent *event, Object *obedit)
Definition sculpt_uv.cc:482
static uint uv_edge_hash(const void *key)
Definition sculpt_uv.cc:625
static wmOperatorStatus uv_sculpt_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
Definition sculpt_uv.cc:940
void SCULPT_OT_uv_sculpt_pinch(wmOperatorType *ot)
#define MARK_BOUNDARY
Definition sculpt_uv.cc:65
static void uv_sculpt_stroke_exit(bContext *C, wmOperator *op)
Definition sculpt_uv.cc:590
static void set_element_flag(UvElement *element, const int flag)
Definition sculpt_uv.cc:642
void SCULPT_OT_uv_sculpt_grab(wmOperatorType *ot)
Definition sculpt_uv.cc:980
static float calc_strength(const UvSculptData *sculptdata, float p, const float len)
Definition sculpt_uv.cc:162
struct BMVert * v
struct BMLoop * prev
struct BMLoop * next
float co[3]
CustomData ldata
Definition DNA_ID.h:414
struct ToolSettings * toolsettings
struct Image * image
float p[2]
Definition sculpt_uv.cc:181
float b[2]
Definition sculpt_uv.cc:181
float sum_b[2]
Definition sculpt_uv.cc:181
float sum_co[2]
Definition sculpt_uv.cc:181
UvElement * storage
unsigned char flag
UvElement * next
struct CurveMapping * curve_distance_falloff
int8_t curve_distance_falloff_preset
wmEventType type
Definition WM_types.hh:757
int mval[2]
Definition WM_types.hh:763
void * customdata
Definition WM_types.hh:807
const char * idname
Definition WM_types.hh:1035
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
bool uv_find_nearest_vert(Scene *scene, Object *obedit, const float co[2], float penalty_dist, UvNearestHit *hit)
UvNearestHit uv_nearest_hit_init_max(const View2D *v2d)
uint len
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ RIGHTMOUSE
@ TIMER
@ MOUSEMOVE
@ LEFTMOUSE
@ MIDDLEMOUSE
@ INBETWEEN_MOUSEMOVE
wmOperatorType * ot
Definition wm_files.cc:4237
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
uint8_t flag
Definition wm_window.cc:145