Fix #103469: Update UV Sphere Projection with Seam support. #104847
|
@ -1520,6 +1520,9 @@ static void uv_transform_properties(wmOperatorType *ot, int radius)
|
|||
"Align",
|
||||
"How to determine rotation around the pole");
|
||||
RNA_def_enum(ot->srna, "pole", pole_items, PINCH, "Pole", "How to handle faces at the poles");
|
||||
RNA_def_boolean(
|
||||
ot->srna, "seam", 0, "Preserve Seams", "Separate projections by islands isolated by seams");
|
||||
|
||||
if (radius) {
|
||||
RNA_def_float(ot->srna,
|
||||
"radius",
|
||||
|
@ -2796,25 +2799,114 @@ static void uv_map_mirror(BMFace *efa,
|
|||
}
|
||||
}
|
||||
|
||||
static void uv_sphere_project(BMFace *efa,
|
||||
const float center[3],
|
||||
const float rotmat[3][3],
|
||||
const bool fan,
|
||||
const int cd_loop_uv_offset)
|
||||
/** Store a face and it's current branch on the generalized atan2 function.
|
||||
*
|
||||
* In complex analysis, we can generalize the arctangent function
|
||||
* into a multi-valued function that is "almost everywhere continuous"
|
||||
* in the complex plane.
|
||||
*
|
||||
* The downside is that we need to keep track of which "branch" of the
|
||||
* multi-valued function we are currently on.
|
||||
Chris_Blackbourn marked this conversation as resolved
|
||||
*
|
||||
* \note Even though atan2(a+bi, c+di) is now (multiply) defined for all
|
||||
* complex inputs, we will only evaluate it with `b==0` and `d==0`.
|
||||
*/
|
||||
|
||||
Chris_Blackbourn marked this conversation as resolved
Campbell Barton
commented
The way this is used as an argument and re-assigned I find a bit confusing, could call The way this is used as an argument and re-assigned I find a bit confusing, could call `const float branch_init`, then use `branch` for values inline.
|
||||
struct uv_face_branch {
|
||||
BMFace *efa;
|
||||
float branch;
|
||||
};
|
||||
|
||||
/** Compute the sphere projection for a BMFace using #map_to_sphere and store on BMLoops.
|
||||
*
|
||||
Chris_Blackbourn marked this conversation as resolved
Campbell Barton
commented
Is there any reason not to use a single vector (storing a Face pointer and branch value for each item)? Avoids having to keep the arrays in sync and I'd expect would read better. Is there any reason not to use a single vector (storing a Face pointer and branch value for each item)? Avoids having to keep the arrays in sync and I'd expect would read better.
|
||||
* Heuristics are used in #uv_map_mirror to improve winding.
|
||||
*
|
||||
* if `fan` is true, faces with UVs at the pole have corrections appled to fan the UVs.
|
||||
*
|
||||
* if `use_seams` is true, the unwrapping will flood fill across the mesh, using
|
||||
* seams to mark boundaries, and #BM_ELEM_TAG to prevent revisiting faces.
|
||||
*/
|
||||
static float uv_sphere_project(const Scene *scene,
|
||||
BMesh *bm,
|
||||
BMFace *efa_init,
|
||||
const float center[3],
|
||||
const float rotmat[3][3],
|
||||
const bool fan,
|
||||
const BMUVOffsets offsets,
|
||||
const bool only_selected_uvs,
|
||||
const bool use_seams,
|
||||
const float branch_init)
|
||||
{
|
||||
blender::Array<bool, BM_DEFAULT_NGON_STACK_SIZE> regular(efa->len);
|
||||
int i;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
|
||||
float pv[3];
|
||||
sub_v3_v3v3(pv, l->v->co, center);
|
||||
mul_m3_v3(rotmat, pv);
|
||||
regular[i] = map_to_sphere(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
|
||||
float max_u = 0.0f;
|
||||
if (BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
|
||||
return max_u;
|
||||
}
|
||||
|
||||
uv_map_mirror(efa, regular.data(), fan, cd_loop_uv_offset);
|
||||
/* Similar to BM_mesh_calc_face_groups with added connectivity information. */
|
||||
blender::Vector<uv_face_branch> stack;
|
||||
stack.append({efa_init, branch_init});
|
||||
|
||||
while (stack.size()) {
|
||||
uv_face_branch face_branch = stack.pop_last();
|
||||
BMFace *efa = face_branch.efa;
|
||||
|
||||
if (use_seams) {
|
||||
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
|
||||
continue; /* Faces might be in the stack more than once. */
|
||||
}
|
||||
|
||||
BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
|
||||
}
|
||||
|
||||
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
continue; /* Unselected face, ignore. */
|
||||
}
|
||||
|
||||
if (only_selected_uvs) {
|
||||
if (!uvedit_face_select_test(scene, efa, offsets)) {
|
||||
uvedit_face_select_disable(scene, bm, efa, offsets);
|
||||
continue; /* Unselected UV, ignore. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember which UVs are at the pole. */
|
||||
blender::Array<bool, BM_DEFAULT_NGON_STACK_SIZE> regular(efa->len);
|
||||
|
||||
int i;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
|
||||
float pv[3];
|
||||
sub_v3_v3v3(pv, l->v->co, center);
|
||||
mul_m3_v3(rotmat, pv);
|
||||
regular[i] = map_to_sphere(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
|
||||
if (!use_seams) {
|
||||
continue; /* Nothing more to do. */
|
||||
}
|
||||
|
||||
/* Move UV to correct branch. */
|
||||
luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
|
||||
max_u = max_ff(max_u, luv[0]);
|
||||
|
||||
BMEdge *edge = l->e;
|
||||
if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
|
||||
continue; /* Stop flood fill at seams. */
|
||||
}
|
||||
|
||||
/* Extend flood fill by pushing to stack. */
|
||||
BMFace *efa2;
|
||||
BMIter iter2;
|
||||
BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
|
||||
if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
|
||||
stack.append({efa2, luv[0]});
|
||||
}
|
||||
}
|
||||
}
|
||||
uv_map_mirror(efa, regular.data(), fan, offsets.uv);
|
||||
}
|
||||
|
||||
return max_u;
|
||||
}
|
||||
|
||||
static int sphere_project_exec(bContext *C, wmOperator *op)
|
||||
|
@ -2854,20 +2946,25 @@ static int sphere_project_exec(bContext *C, wmOperator *op)
|
|||
uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
|
||||
|
||||
const bool fan = RNA_enum_get(op->ptr, "pole");
|
||||
const bool use_seams = RNA_boolean_get(op->ptr, "seam");
|
||||
|
||||
if (use_seams) {
|
||||
BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false);
|
||||
}
|
||||
|
||||
float island_offset = 0.0f;
|
||||
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
||||
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (only_selected_uvs) {
|
||||
if (!uvedit_face_select_test(scene, efa, offsets)) {
|
||||
uvedit_face_select_disable(scene, em->bm, efa, offsets);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
uv_sphere_project(efa, center, rotmat, fan, offsets.uv);
|
||||
const float max_u = uv_sphere_project(scene,
|
||||
em->bm,
|
||||
efa,
|
||||
center,
|
||||
rotmat,
|
||||
fan,
|
||||
offsets,
|
||||
only_selected_uvs,
|
||||
use_seams,
|
||||
island_offset + 0.5f);
|
||||
island_offset = ceilf(max_ff(max_u, island_offset));
|
||||
}
|
||||
|
||||
const bool per_face_aspect = true;
|
||||
|
@ -2905,25 +3002,92 @@ void UV_OT_sphere_project(wmOperatorType *ot)
|
|||
/** \name Cylinder UV Project Operator
|
||||
* \{ */
|
||||
|
||||
static void uv_cylinder_project(BMFace *efa,
|
||||
const float center[3],
|
||||
const float rotmat[3][3],
|
||||
const bool fan,
|
||||
const int cd_loop_uv_offset)
|
||||
/* See #uv_sphere_project for description of parameters. */
|
||||
static float uv_cylinder_project(const Scene *scene,
|
||||
BMesh *bm,
|
||||
BMFace *efa_init,
|
||||
const float center[3],
|
||||
const float rotmat[3][3],
|
||||
const bool fan,
|
||||
const BMUVOffsets offsets,
|
||||
const bool only_selected_uvs,
|
||||
const bool use_seams,
|
||||
const float branch_init)
|
||||
{
|
||||
blender::Array<bool, BM_DEFAULT_NGON_STACK_SIZE> regular(efa->len);
|
||||
int i;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
|
||||
float pv[3];
|
||||
sub_v3_v3v3(pv, l->v->co, center);
|
||||
mul_m3_v3(rotmat, pv);
|
||||
regular[i] = map_to_tube(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
|
||||
float max_u = 0.0f;
|
||||
if (BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
|
||||
return max_u;
|
||||
}
|
||||
|
||||
uv_map_mirror(efa, regular.data(), fan, cd_loop_uv_offset);
|
||||
/* Similar to BM_mesh_calc_face_groups with added connectivity information. */
|
||||
|
||||
blender::Vector<uv_face_branch> stack;
|
||||
|
||||
stack.append({efa_init, branch_init});
|
||||
|
||||
while (stack.size()) {
|
||||
uv_face_branch face_branch = stack.pop_last();
|
||||
BMFace *efa = face_branch.efa;
|
||||
|
||||
if (use_seams) {
|
||||
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
|
||||
continue; /* Faces might be in the stack more than once. */
|
||||
}
|
||||
|
||||
BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
|
||||
}
|
||||
|
||||
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
continue; /* Unselected face, ignore. */
|
||||
}
|
||||
|
||||
if (only_selected_uvs) {
|
||||
if (!uvedit_face_select_test(scene, efa, offsets)) {
|
||||
uvedit_face_select_disable(scene, bm, efa, offsets);
|
||||
continue; /* Unselected UV, ignore. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember which UVs are at the pole. */
|
||||
blender::Array<bool, BM_DEFAULT_NGON_STACK_SIZE> regular(efa->len);
|
||||
|
||||
int i;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
|
||||
float pv[3];
|
||||
sub_v3_v3v3(pv, l->v->co, center);
|
||||
mul_m3_v3(rotmat, pv);
|
||||
regular[i] = map_to_tube(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
|
||||
|
||||
if (!use_seams) {
|
||||
continue; /* Nothing more to do. */
|
||||
}
|
||||
|
||||
/* Move UV to correct branch. */
|
||||
luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
|
||||
max_u = max_ff(max_u, luv[0]);
|
||||
|
||||
BMEdge *edge = l->e;
|
||||
if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
|
||||
continue; /* Stop flood fill at seams. */
|
||||
}
|
||||
|
||||
/* Extend flood fill by pushing to stack. */
|
||||
BMFace *efa2;
|
||||
BMIter iter2;
|
||||
BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
|
||||
if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
|
||||
stack.append({efa2, luv[0]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uv_map_mirror(efa, regular.data(), fan, offsets.uv);
|
||||
}
|
||||
|
||||
return max_u;
|
||||
}
|
||||
|
||||
static int cylinder_project_exec(bContext *C, wmOperator *op)
|
||||
|
@ -2963,6 +3127,13 @@ static int cylinder_project_exec(bContext *C, wmOperator *op)
|
|||
uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
|
||||
|
||||
const bool fan = RNA_enum_get(op->ptr, "pole");
|
||||
const bool use_seams = RNA_boolean_get(op->ptr, "seam");
|
||||
|
||||
if (use_seams) {
|
||||
BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false);
|
||||
}
|
||||
|
||||
float island_offset = 0.0f;
|
||||
|
||||
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
||||
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
|
@ -2974,7 +3145,17 @@ static int cylinder_project_exec(bContext *C, wmOperator *op)
|
|||
continue;
|
||||
}
|
||||
|
||||
uv_cylinder_project(efa, center, rotmat, fan, offsets.uv);
|
||||
const float max_u = uv_cylinder_project(scene,
|
||||
em->bm,
|
||||
efa,
|
||||
center,
|
||||
rotmat,
|
||||
fan,
|
||||
offsets,
|
||||
only_selected_uvs,
|
||||
use_seams,
|
||||
island_offset + 0.5f);
|
||||
island_offset = ceilf(max_ff(max_u, island_offset));
|
||||
}
|
||||
|
||||
const bool per_face_aspect = true;
|
||||
|
|
Loading…
Reference in New Issue
This implements it's own face-group detection, unless there is some reason not to -
BM_mesh_calc_face_groups
avoids inlining this logic.BM_mesh_calc_face_groups
does not record the connectivity of the faces which are visited.For example, a single rectangular ribbon made of many faces can wrap around a cylinder multiple times, but only produce one island. We need to know how the faces are locally connected to extend out the UVs in a consistent manner.