UV: add geometry driven uv relax brush

Differential Revision: https://developer.blender.org/D15530
This commit is contained in:
Chris Blackbourn 2022-08-16 19:41:35 +12:00
parent 29c1d736c4
commit 74ea0bee9c
6 changed files with 192 additions and 17 deletions

View File

@ -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?
*/

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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

@ -1 +1 @@
Subproject commit da8bdd7244c7b6c2eadf4c949ff391d0cc430275
Subproject commit 2a541f164a222ef7bcd036d37687738acee8d946