From 74ea0bee9c0af14ddd2105aafe4b9597885fa3c1 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Tue, 16 Aug 2022 19:41:35 +1200 Subject: UV: add geometry driven uv relax brush Differential Revision: https://developer.blender.org/D15530 --- source/blender/editors/include/ED_mesh.h | 9 +- source/blender/editors/mesh/editmesh_utils.c | 9 +- source/blender/editors/sculpt_paint/sculpt_uv.c | 183 ++++++++++++++++++++++-- source/blender/makesdna/DNA_scene_types.h | 1 + source/blender/makesrna/intern/rna_scene.c | 5 + source/tools | 2 +- 6 files changed, 192 insertions(+), 17 deletions(-) (limited to 'source') diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index a1c1c816d4c..a0eaf4232c0 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -133,6 +133,7 @@ void EDBM_update(struct Mesh *me, const struct EDBMUpdate_Params *params); void EDBM_update_extern(struct Mesh *me, bool do_tessellation, bool is_destructive); /** + * * A specialized vert map used by stitch operator. */ struct UvElementMap *BM_uv_element_map_create(struct BMesh *bm, @@ -141,11 +142,13 @@ struct UvElementMap *BM_uv_element_map_create(struct BMesh *bm, bool use_winding, bool do_islands); void BM_uv_element_map_free(struct UvElementMap *element_map); -struct UvElement *BM_uv_element_get(struct UvElementMap *map, - struct BMFace *efa, - struct BMLoop *l); +struct UvElement *BM_uv_element_get(const struct UvElementMap *map, + const struct BMFace *efa, + const struct BMLoop *l); struct UvElement *BM_uv_element_get_head(struct UvElementMap *map, struct UvElement *child); +struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map); + /** * Can we edit UV's for this mesh? */ diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index e931dd02a9e..941965357c1 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -593,10 +593,10 @@ UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, uint v) return vmap->vert[v]; } -static void bm_uv_ensure_head_table(UvElementMap *element_map) +struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map) { if (element_map->head_table) { - return; + return element_map->head_table; } /* For each UvElement, locate the "separate" UvElement that precedes it in the linked list. */ @@ -616,6 +616,7 @@ static void bm_uv_ensure_head_table(UvElementMap *element_map) } } } + return element_map->head_table; } #define INVALID_ISLAND ((unsigned int)-1) @@ -645,7 +646,7 @@ static int bm_uv_edge_select_build_islands(UvElementMap *element_map, bool uv_selected, int cd_loop_uv_offset) { - bm_uv_ensure_head_table(element_map); + BM_uv_element_map_ensure_head_table(element_map); int total_uvs = element_map->total_uvs; @@ -1070,7 +1071,7 @@ void BM_uv_element_map_free(UvElementMap *element_map) } } -UvElement *BM_uv_element_get(UvElementMap *element_map, BMFace *efa, BMLoop *l) +UvElement *BM_uv_element_get(const UvElementMap *element_map, const BMFace *efa, const BMLoop *l) { UvElement *element = element_map->vertex[BM_elem_index_get(l->v)]; while (element) { diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.c b/source/blender/editors/sculpt_paint/sculpt_uv.c index ccfcc82f006..936fde8ac59 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.c +++ b/source/blender/editors/sculpt_paint/sculpt_uv.c @@ -9,7 +9,7 @@ #include "MEM_guardedalloc.h" #include "BLI_ghash.h" -#include "BLI_math.h" +#include "BLI_math_base_safe.h" #include "BLI_utildefines.h" #include "DNA_brush_types.h" @@ -43,6 +43,11 @@ #include "UI_view2d.h" +/* When set, the UV element is on the boundary of the graph. + * i.e. Instead of a 2-dimensional laplace operator, use a 1-dimensional version. + * Visually, UV elements on the graph boundary appear as borders of the UV Island. */ +#define MARK_BOUNDARY 1 + typedef struct UvAdjacencyElement { /* pointer to original uvelement */ UvElement *element; @@ -230,6 +235,13 @@ static void HC_relaxation_iteration_uv(BMEditMesh *em, MEM_SAFE_FREE(tmp_uvdata); } +/* Legacy version which only does laplacian relaxation. + * Probably a little faster as it caches UvEdges. + * Mostly preserved for comparison with `HC_relaxation_iteration_uv`. + * Once the HC method has been merged into `relaxation_iteration_uv`, + * all the `HC_*` and `laplacian_*` specific functions can probably be removed. + */ + static void laplacian_relaxation_iteration_uv(BMEditMesh *em, UvSculptData *sculptdata, const float mouse_coord[2], @@ -306,6 +318,151 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, MEM_SAFE_FREE(tmp_uvdata); } +static void add_weighted_edge(float (*delta_buf)[3], + const UvElement *storage, + const UvElement *ele_next, + const UvElement *ele_prev, + const MLoopUV *luv_next, + const MLoopUV *luv_prev, + const float weight) +{ + float delta[2]; + sub_v2_v2v2(delta, luv_next->uv, luv_prev->uv); + + bool code1 = (ele_prev->flag & MARK_BOUNDARY); + bool code2 = (ele_next->flag & MARK_BOUNDARY); + if (code1 || (code1 == code2)) { + int index_next = ele_next - storage; + delta_buf[index_next][0] -= delta[0] * weight; + delta_buf[index_next][1] -= delta[1] * weight; + delta_buf[index_next][2] += fabsf(weight); + } + if (code2 || (code1 == code2)) { + int index_prev = ele_prev - storage; + delta_buf[index_prev][0] += delta[0] * weight; + delta_buf[index_prev][1] += delta[1] * weight; + delta_buf[index_prev][2] += fabsf(weight); + } +} + +static float tri_weight_v3(int method, const float *v1, const float *v2, const float *v3) +{ + switch (method) { + case UV_SCULPT_TOOL_RELAX_LAPLACIAN: + case UV_SCULPT_TOOL_RELAX_HC: + return 1.0f; + case UV_SCULPT_TOOL_RELAX_COTAN: + return cotangent_tri_weight_v3(v1, v2, v3); + default: + BLI_assert_unreachable(); + } + return 0.0f; +} + +static void relaxation_iteration_uv(BMEditMesh *em, + UvSculptData *sculptdata, + const float mouse_coord[2], + const float alpha, + const float radius_squared, + const float aspect_ratio, + const int method) +{ + if (method == UV_SCULPT_TOOL_RELAX_HC) { + HC_relaxation_iteration_uv(em, sculptdata, mouse_coord, alpha, radius_squared, aspect_ratio); + return; + } + if (method == UV_SCULPT_TOOL_RELAX_LAPLACIAN) { + laplacian_relaxation_iteration_uv( + em, sculptdata, mouse_coord, alpha, radius_squared, aspect_ratio); + return; + } + + struct UvElement **head_table = BM_uv_element_map_ensure_head_table(sculptdata->elementMap); + + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BLI_assert(cd_loop_uv_offset >= 0); + + const int total_uvs = sculptdata->elementMap->total_uvs; + float(*delta_buf)[3] = (float(*)[3])MEM_callocN(total_uvs * sizeof(float[3]), __func__); + + const UvElement *storage = sculptdata->elementMap->storage; + for (int j = 0; j < total_uvs; j++) { + const UvElement *ele_curr = storage + j; + const BMFace *efa = ele_curr->l->f; + const UvElement *ele_next = BM_uv_element_get(sculptdata->elementMap, efa, ele_curr->l->next); + const UvElement *ele_prev = BM_uv_element_get(sculptdata->elementMap, efa, ele_curr->l->prev); + + const float *v_curr_co = ele_curr->l->v->co; + const float *v_prev_co = ele_prev->l->v->co; + const float *v_next_co = ele_next->l->v->co; + + const MLoopUV *luv_curr = BM_ELEM_CD_GET_VOID_P(ele_curr->l, cd_loop_uv_offset); + const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(ele_next->l, cd_loop_uv_offset); + const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(ele_prev->l, cd_loop_uv_offset); + + const UvElement *head_curr = head_table[ele_curr - sculptdata->elementMap->storage]; + const UvElement *head_next = head_table[ele_next - sculptdata->elementMap->storage]; + const UvElement *head_prev = head_table[ele_prev - sculptdata->elementMap->storage]; + + /* If the mesh is triangulated with no boundaries, only one edge is required. */ + const float weight_curr = tri_weight_v3(method, v_curr_co, v_prev_co, v_next_co); + add_weighted_edge(delta_buf, storage, head_next, head_prev, luv_next, luv_prev, weight_curr); + + /* Triangulated with a boundary? We need the incoming edges to solve the boundary. */ + const float weight_prev = tri_weight_v3(method, v_prev_co, v_curr_co, v_next_co); + add_weighted_edge(delta_buf, storage, head_next, head_curr, luv_next, luv_curr, weight_prev); + + if (method == UV_SCULPT_TOOL_RELAX_LAPLACIAN) { + /* Laplacian method has zero weights on virtual edges. */ + continue; + } + + /* Meshes with quads (or other n-gons) need "virtual" edges too. */ + const float weight_next = tri_weight_v3(method, v_next_co, v_curr_co, v_prev_co); + add_weighted_edge(delta_buf, storage, head_prev, head_curr, luv_prev, luv_curr, weight_next); + } + + Brush *brush = BKE_paint_brush(sculptdata->uvsculpt); + for (int i = 0; i < sculptdata->totalUniqueUvs; i++) { + UvAdjacencyElement *adj_el = &sculptdata->uv[i]; + if (adj_el->is_locked) { + continue; /* Locked UVs can't move. */ + } + + /* Is UV within brush's influence? */ + float diff[2]; + sub_v2_v2v2(diff, adj_el->uv, mouse_coord); + diff[1] /= aspect_ratio; + const float dist_squared = len_squared_v2(diff); + if (dist_squared > radius_squared) { + continue; + } + const float strength = alpha * BKE_brush_curve_strength_clamped( + brush, sqrtf(dist_squared), sqrtf(radius_squared)); + + const float *delta_sum = delta_buf[adj_el->element - storage]; + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(adj_el->element->l, cd_loop_uv_offset); + BLI_assert(adj_el->uv == luv->uv); /* Only true for head. */ + adj_el->uv[0] = luv->uv[0] + strength * safe_divide(delta_sum[0], delta_sum[2]); + adj_el->uv[1] = luv->uv[1] + strength * safe_divide(delta_sum[1], delta_sum[2]); + + apply_sculpt_data_constraints(sculptdata, adj_el->uv); + + /* Copy UV co-ordinates to all UvElements. */ + UvElement *tail = adj_el->element; + while (tail) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(tail->l, cd_loop_uv_offset); + copy_v2_v2(luv->uv, adj_el->uv); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + + MEM_SAFE_FREE(delta_buf); +} + static void uv_sculpt_stroke_apply(bContext *C, wmOperator *op, const wmEvent *event, @@ -383,16 +540,11 @@ static void uv_sculpt_stroke_apply(bContext *C, } /* - * Smooth Tool + * Relax Tool */ else if (tool == UV_SCULPT_TOOL_RELAX) { - uint method = toolsettings->uv_relax_method; - if (method == UV_SCULPT_TOOL_RELAX_HC) { - HC_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio); - } - else { - laplacian_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio); - } + relaxation_iteration_uv( + em, sculptdata, co, alpha, radius, aspectRatio, toolsettings->uv_relax_method); } /* @@ -476,6 +628,17 @@ static bool uv_edge_compare(const void *a, const void *b) return true; } +static void set_element_flag(UvElement *element, const int flag) +{ + while (element) { + element->flag |= flag; + element = element->next; + if (!element || element->separate) { + break; + } + } +} + static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wmEvent *event) { Scene *scene = CTX_data_scene(C); @@ -666,6 +829,8 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm data->uv[data->uvedges[i].uv1].is_locked = true; data->uv[data->uvedges[i].uv2].is_locked = true; } + set_element_flag(data->uv[data->uvedges[i].uv1].element, MARK_BOUNDARY); + set_element_flag(data->uv[data->uvedges[i].uv2].element, MARK_BOUNDARY); } } diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 2c3c16393e0..7e50a81df3e 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -820,6 +820,7 @@ typedef struct RenderProfile { /** #ToolSettings.uv_relax_method */ #define UV_SCULPT_TOOL_RELAX_LAPLACIAN 1 #define UV_SCULPT_TOOL_RELAX_HC 2 +#define UV_SCULPT_TOOL_RELAX_COTAN 3 /* Stereo Flags */ #define STEREO_RIGHT_NAME "right" diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 2e78cc97099..5abcbb14904 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -88,6 +88,11 @@ static const EnumPropertyItem uv_sculpt_relaxation_items[] = { "Laplacian", "Use Laplacian method for relaxation"}, {UV_SCULPT_TOOL_RELAX_HC, "HC", 0, "HC", "Use HC method for relaxation"}, + {UV_SCULPT_TOOL_RELAX_COTAN, + "COTAN", + 0, + "Geometry", + "Use Geometry (cotangent) relaxation, making UV's follow the underlying 3D geometry"}, {0, NULL, 0, NULL, NULL}, }; #endif diff --git a/source/tools b/source/tools index da8bdd7244c..2a541f164a2 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit da8bdd7244c7b6c2eadf4c949ff391d0cc430275 +Subproject commit 2a541f164a222ef7bcd036d37687738acee8d946 -- cgit v1.2.3