Fix T86358: Use per face aspect correction for primitive UV projections

During UV unwrapping, Cube Projection, Sphere Projection, Cylinder
Projection and Project From View (in the 3D Viewport), when "Correct
Aspect" toggle is active, it now uses a query cache to perform a
per-face aspect ratio ("per_face_aspect") correction for the active
image of each face.

Reviewed By: campbellbarton

Ref D14852
This commit is contained in:
Chris Blackbourn 2022-05-10 11:09:29 +10:00 committed by Campbell Barton
parent a4c2060b91
commit 1c1e842879
Notes: blender-bot 2023-02-14 01:52:41 +01:00
Referenced by issue #86358, Cube UV projection correct aspect option does not work correctly with multiple assigned materials
2 changed files with 121 additions and 47 deletions

View File

@ -268,6 +268,10 @@ struct BMLoop **ED_uvedit_selected_verts(const struct Scene *scene,
int *r_verts_len);
void ED_uvedit_get_aspect(struct Object *obedit, float *r_aspx, float *r_aspy);
void ED_uvedit_get_aspect_from_material(Object *ob,
const int material_index,
float *r_aspx,
float *r_aspy);
void ED_uvedit_active_vert_loop_set(struct BMesh *bm, struct BMLoop *l);
struct BMLoop *ED_uvedit_active_vert_loop_get(struct BMesh *bm);

View File

@ -267,26 +267,35 @@ static bool uvedit_have_selection_multi(const Scene *scene,
return have_select;
}
void ED_uvedit_get_aspect_from_material(Object *ob,
const int material_index,
float *r_aspx,
float *r_aspy)
{
if (UNLIKELY(material_index < 0 || material_index >= ob->totcol)) {
*r_aspx = 1.0f;
*r_aspy = 1.0f;
return;
}
Image *ima;
ED_object_get_active_image(ob, material_index + 1, &ima, NULL, NULL, NULL);
ED_image_get_uv_aspect(ima, NULL, r_aspx, r_aspy);
}
void ED_uvedit_get_aspect(Object *ob, float *r_aspx, float *r_aspy)
{
BMEditMesh *em = BKE_editmesh_from_object(ob);
BLI_assert(em != NULL);
bool sloppy = true;
bool selected = false;
BMFace *efa;
Image *ima;
efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
if (efa) {
ED_object_get_active_image(ob, efa->mat_nr + 1, &ima, NULL, NULL, NULL);
ED_image_get_uv_aspect(ima, NULL, r_aspx, r_aspy);
}
else {
BMFace *efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
if (!efa) {
*r_aspx = 1.0f;
*r_aspy = 1.0f;
return;
}
ED_uvedit_get_aspect_from_material(ob, efa->mat_nr, r_aspx, r_aspy);
}
static void construct_param_handle_face_add(ParamHandle *handle,
@ -1527,49 +1536,88 @@ static void uv_transform_properties(wmOperatorType *ot, int radius)
}
}
static void shrink_loop_uv_by_aspect_ratio(BMFace *efa,
const int cd_loop_uv_offset,
const float aspect_y)
{
BLI_assert(aspect_y != 1.0f); /* Nothing to do, should be handled by caller. */
BLI_assert(aspect_y > 0.0f); /* Negative aspect ratios are not supported. */
BMLoop *l;
BMIter iter;
BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) {
MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
if (aspect_y > 1.0f) {
/* Reduce round-off error, i.e. `u = (u - 0.5) / aspect_y + 0.5`. */
luv->uv[0] = luv->uv[0] / aspect_y + (0.5f - 0.5f / aspect_y);
}
else {
/* Reduce round-off error, i.e. `v = (v - 0.5) * aspect_y + 0.5`. */
luv->uv[1] = luv->uv[1] * aspect_y + (0.5f - 0.5f * aspect_y);
}
}
}
static void correct_uv_aspect(Object *ob, BMEditMesh *em)
{
BMLoop *l;
BMIter iter, liter;
MLoopUV *luv;
BMFace *efa;
float scale, aspx, aspy;
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
float aspx, aspy;
ED_uvedit_get_aspect(ob, &aspx, &aspy);
const float aspect_y = aspx / aspy;
if (aspect_y == 1.0f) {
/* Scaling by 1.0 has no effect. */
return;
}
BMFace *efa;
BMIter iter;
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
}
}
}
if (aspx == aspy) {
static void correct_uv_aspect_per_face(Object *ob, BMEditMesh *em)
{
const int materials_num = ob->totcol;
if (materials_num == 0) {
/* Without any materials, there is no aspect_y information and nothing to do. */
return;
}
if (aspx > aspy) {
scale = aspy / aspx;
float *material_aspect_y = BLI_array_alloca(material_aspect_y, materials_num);
/* Lazily initialize aspect ratio for materials. */
copy_vn_fl(material_aspect_y, materials_num, -1.0f);
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
continue;
}
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
luv->uv[0] = ((luv->uv[0] - 0.5f) * scale) + 0.5f;
}
BMFace *efa;
BMIter iter;
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
continue;
}
}
else {
scale = aspx / aspy;
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
luv->uv[1] = ((luv->uv[1] - 0.5f) * scale) + 0.5f;
}
const int material_index = efa->mat_nr;
if (UNLIKELY(material_index < 0 || material_index >= materials_num)) {
/* The index might be for a material slot which is not currently setup. */
continue;
}
float aspect_y = material_aspect_y[material_index];
if (aspect_y == -1.0f) {
/* Lazily initialize aspect ratio for materials. */
float aspx, aspy;
ED_uvedit_get_aspect_from_material(ob, material_index, &aspx, &aspy);
aspect_y = aspx / aspy;
material_aspect_y[material_index] = aspect_y;
}
if (aspect_y == 1.0f) {
/* Scaling by 1.0 has no effect. */
continue;
}
shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
}
}
@ -1613,7 +1661,17 @@ static void uv_map_clip_correct_properties(wmOperatorType *ot)
uv_map_clip_correct_properties_ex(ot, true);
}
static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOperator *op)
/**
* \param per_face_aspect: Calculate the aspect ratio per-face,
* otherwise use a single aspect for all UV's based on the material of the active face.
* TODO: using per-face aspect may split UV islands so more advanced UV projection methods
* such as "Unwrap" & "Smart UV Projections" will need to handle aspect correction themselves.
* For now keep using a single aspect for all faces in this case.
*/
static void uv_map_clip_correct_multi(Object **objects,
uint objects_len,
wmOperator *op,
bool per_face_aspect)
{
BMFace *efa;
BMLoop *l;
@ -1633,9 +1691,14 @@ static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOper
BMEditMesh *em = BKE_editmesh_from_object(ob);
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
/* correct for image aspect ratio */
/* Correct for image aspect ratio. */
if (correct_aspect) {
correct_uv_aspect(ob, em);
if (per_face_aspect) {
correct_uv_aspect_per_face(ob, em);
}
else {
correct_uv_aspect(ob, em);
}
}
if (scale_to_bounds) {
@ -1678,6 +1741,11 @@ static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOper
dy = 1.0f / dy;
}
if (dx == 1.0f && dy == 1.0f) {
/* Scaling by 1.0 has no effect. */
return;
}
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
@ -1702,7 +1770,7 @@ static void uv_map_clip_correct_multi(Object **objects, uint objects_len, wmOper
static void uv_map_clip_correct(Object *ob, wmOperator *op)
{
uv_map_clip_correct_multi(&ob, 1, op);
uv_map_clip_correct_multi(&ob, 1, op, true);
}
/** \} */
@ -2283,7 +2351,9 @@ static int smart_project_exec(bContext *C, wmOperator *op)
.use_seams = true,
});
uv_map_clip_correct_multi(objects_changed, object_changed_len, op);
/* #ED_uvedit_pack_islands_multi only supports `per_face_aspect = false`. */
const bool per_face_aspect = false;
uv_map_clip_correct_multi(objects_changed, object_changed_len, op, per_face_aspect);
}
MEM_freeN(objects_changed);
@ -2485,7 +2555,7 @@ static int uv_from_view_exec(bContext *C, wmOperator *op)
}
if (changed_multi) {
uv_map_clip_correct_multi(objects, objects_len, op);
uv_map_clip_correct_multi(objects, objects_len, op, true);
}
MEM_freeN(objects);