diff options
Diffstat (limited to 'source/blender/editors/uvedit/uvedit_islands.cc')
-rw-r--r-- | source/blender/editors/uvedit/uvedit_islands.cc | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/source/blender/editors/uvedit/uvedit_islands.cc b/source/blender/editors/uvedit/uvedit_islands.cc new file mode 100644 index 00000000000..92745667505 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_islands.cc @@ -0,0 +1,803 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup eduv + * + * Utilities for manipulating UV islands. + * + * \note This is similar to `GEO_uv_parametrizer.h`, + * however the data structures there don't support arbitrary topology + * such as an edge with 3 or more faces using it. + * This API uses #BMesh data structures and doesn't have limitations for manifold meshes. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" + +#include "BLI_boxpack_2d.h" +#include "BLI_convexhull_2d.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "BKE_customdata.h" +#include "BKE_editmesh.h" +#include "BKE_image.h" + +#include "DEG_depsgraph.h" + +#include "ED_uvedit.h" /* Own include. */ + +#include "WM_api.h" +#include "WM_types.h" + +#include "bmesh.h" + +/* -------------------------------------------------------------------- */ +/** \name UV Face Utilities + * \{ */ + +static void bm_face_uv_translate_and_scale_around_pivot(BMFace *f, + const float offset[2], + const float scale[2], + const float pivot[2], + const int cd_loop_uv_offset) +{ + BMLoop *l_iter; + BMLoop *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + MLoopUV *luv = static_cast<MLoopUV *>(BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset)); + for (int i = 0; i < 2; i++) { + luv->uv[i] = offset[i] + (((luv->uv[i] - pivot[i]) * scale[i]) + pivot[i]); + } + } while ((l_iter = l_iter->next) != l_first); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Face Array Utilities + * \{ */ + +static void bm_face_array_calc_bounds(BMFace **faces, + const int faces_len, + const int cd_loop_uv_offset, + rctf *r_bounds_rect) +{ + BLI_assert(cd_loop_uv_offset >= 0); + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, cd_loop_uv_offset); + } + r_bounds_rect->xmin = bounds_min[0]; + r_bounds_rect->ymin = bounds_min[1]; + r_bounds_rect->xmax = bounds_max[0]; + r_bounds_rect->ymax = bounds_max[1]; +} + +/** + * Return an array of un-ordered UV coordinates, + * without duplicating coordinates for loops that share a vertex. + */ +static float (*bm_face_array_calc_unique_uv_coords( + BMFace **faces, int faces_len, const int cd_loop_uv_offset, int *r_coords_len))[2] +{ + BLI_assert(cd_loop_uv_offset >= 0); + int coords_len_alloc = 0; + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_enable(l_iter, BM_ELEM_TAG); + } while ((l_iter = l_iter->next) != l_first); + coords_len_alloc += f->len; + } + + float(*coords)[2] = static_cast<float(*)[2]>( + MEM_mallocN(sizeof(*coords) * coords_len_alloc, __func__)); + int coords_len = 0; + + for (int i = 0; i < faces_len; i++) { + BMFace *f = faces[i]; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { + /* Already walked over, continue. */ + continue; + } + + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + const MLoopUV *luv = static_cast<const MLoopUV *>( + BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset)); + copy_v2_v2(coords[coords_len++], luv->uv); + + /* Un tag all connected so we don't add them twice. + * Note that we will tag other loops not part of `faces` but this is harmless, + * since we're only turning off a tag. */ + BMVert *v_pivot = l_iter->v; + BMEdge *e_first = v_pivot->e; + const BMEdge *e = e_first; + do { + if (e->l != nullptr) { + const BMLoop *l_radial = e->l; + do { + if (l_radial->v == l_iter->v) { + if (BM_elem_flag_test(l_radial, BM_ELEM_TAG)) { + const MLoopUV *luv_radial = static_cast<const MLoopUV *>( + BM_ELEM_CD_GET_VOID_P(l_radial, cd_loop_uv_offset)); + if (equals_v2v2(luv->uv, luv_radial->uv)) { + /* Don't add this UV when met in another face in `faces`. */ + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + } + } + } + } while ((l_radial = l_radial->radial_next) != e->l); + } + } while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != e_first); + } while ((l_iter = l_iter->next) != l_first); + } + *r_coords_len = coords_len; + return coords; +} + +static void face_island_uv_rotate_fit_aabb(FaceIsland *island) +{ + BMFace **faces = island->faces; + const int faces_len = island->faces_len; + const float aspect_y = island->aspect_y; + const int cd_loop_uv_offset = island->cd_loop_uv_offset; + + /* Calculate unique coordinates since calculating a convex hull can be an expensive operation. */ + int coords_len; + float(*coords)[2] = bm_face_array_calc_unique_uv_coords( + faces, faces_len, cd_loop_uv_offset, &coords_len); + + /* Correct aspect ratio. */ + if (aspect_y != 1.0f) { + for (int i = 0; i < coords_len; i++) { + coords[i][1] /= aspect_y; + } + } + + float angle = BLI_convexhull_aabb_fit_points_2d(coords, coords_len); + + /* Rotate coords by `angle` before computing bounding box. */ + if (angle != 0.0f) { + float matrix[2][2]; + angle_to_mat2(matrix, angle); + matrix[0][1] *= aspect_y; + matrix[1][1] *= aspect_y; + for (int i = 0; i < coords_len; i++) { + mul_m2_v2(matrix, coords[i]); + } + } + + /* Compute new AABB. */ + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < coords_len; i++) { + minmax_v2v2_v2(bounds_min, bounds_max, coords[i]); + } + + float size[2]; + sub_v2_v2v2(size, bounds_max, bounds_min); + if (size[1] < size[0]) { + angle += DEG2RADF(90.0f); + } + + MEM_freeN(coords); + + /* Apply rotation back to BMesh. */ + if (angle != 0.0f) { + float matrix[2][2]; + angle_to_mat2(matrix, angle); + matrix[1][0] *= 1.0f / aspect_y; + /* matrix[1][1] *= aspect_y / aspect_y; */ + matrix[0][1] *= aspect_y; + for (int i = 0; i < faces_len; i++) { + BM_face_uv_transform(faces[i], matrix, cd_loop_uv_offset); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UDIM packing helper functions + * \{ */ + +bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) { + return true; + } + /* Check if selection lies on a valid UDIM image tile. */ + if (is_tiled_image) { + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + const int target_x = (tile_index % 10); + const int target_y = (tile_index / 10); + if (coords_floor[0] == target_x && coords_floor[1] == target_y) { + return true; + } + } + } + /* Probably not required since UDIM grid checks for 1001. */ + else if (image && !is_tiled_image) { + if (is_zero_v2(coords_floor)) { + return true; + } + } + + return false; +} + +/** + * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. + */ +static float uv_nearest_image_tile_distance(const Image *image, + const float coords[2], + float nearest_tile_co[2]) +{ + BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co); + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** + * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number. + */ +static float uv_nearest_grid_tile_distance(const int udim_grid[2], + const float coords[2], + float nearest_tile_co[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + + if (coords[0] > udim_grid[0]) { + nearest_tile_co[0] = udim_grid[0] - 1; + } + else if (coords[0] < 0) { + nearest_tile_co[0] = 0; + } + else { + nearest_tile_co[0] = coords_floor[0]; + } + + if (coords[1] > udim_grid[1]) { + nearest_tile_co[1] = udim_grid[1] - 1; + } + else if (coords[1] < 0) { + nearest_tile_co[1] = 0; + } + else { + nearest_tile_co[1] = coords_floor[1]; + } + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Calculate UV Islands + * \{ */ + +struct SharedUVLoopData { + int cd_loop_uv_offset; + bool use_seams; +}; + +static bool bm_loop_uv_shared_edge_check(const BMLoop *l_a, const BMLoop *l_b, void *user_data) +{ + const struct SharedUVLoopData *data = static_cast<const struct SharedUVLoopData *>(user_data); + + if (data->use_seams) { + if (BM_elem_flag_test(l_a->e, BM_ELEM_SEAM)) { + return false; + } + } + + return BM_loop_uv_share_edge_check((BMLoop *)l_a, (BMLoop *)l_b, data->cd_loop_uv_offset); +} + +/** + * Calculate islands and add them to \a island_list returning the number of items added. + */ +int bm_mesh_calc_uv_islands(const Scene *scene, + BMesh *bm, + ListBase *island_list, + const bool only_selected_faces, + const bool only_selected_uvs, + const bool use_seams, + const float aspect_y, + const int cd_loop_uv_offset) +{ + BLI_assert(cd_loop_uv_offset >= 0); + int island_added = 0; + BM_mesh_elem_table_ensure(bm, BM_FACE); + + int *groups_array = static_cast<int *>( + MEM_mallocN(sizeof(*groups_array) * size_t(bm->totface), __func__)); + + int(*group_index)[2]; + + /* Calculate the tag to use. */ + uchar hflag_face_test = 0; + if (only_selected_faces) { + if (only_selected_uvs) { + BMFace *f; + BMIter iter; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + bool value = false; + if (BM_elem_flag_test(f, BM_ELEM_SELECT) && + uvedit_face_select_test(scene, f, cd_loop_uv_offset)) { + value = true; + } + BM_elem_flag_set(f, BM_ELEM_TAG, value); + } + hflag_face_test = BM_ELEM_TAG; + } + else { + hflag_face_test = BM_ELEM_SELECT; + } + } + + struct SharedUVLoopData user_data = {0}; + user_data.cd_loop_uv_offset = cd_loop_uv_offset; + user_data.use_seams = use_seams; + + const int group_len = BM_mesh_calc_face_groups(bm, + groups_array, + &group_index, + nullptr, + bm_loop_uv_shared_edge_check, + &user_data, + hflag_face_test, + BM_EDGE); + + for (int i = 0; i < group_len; i++) { + const int faces_start = group_index[i][0]; + const int faces_len = group_index[i][1]; + BMFace **faces = static_cast<BMFace **>(MEM_mallocN(sizeof(*faces) * faces_len, __func__)); + + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + + for (int j = 0; j < faces_len; j++) { + faces[j] = BM_face_at_index(bm, groups_array[faces_start + j]); + } + + struct FaceIsland *island = static_cast<struct FaceIsland *>( + MEM_callocN(sizeof(*island), __func__)); + island->faces = faces; + island->faces_len = faces_len; + island->cd_loop_uv_offset = cd_loop_uv_offset; + island->aspect_y = aspect_y; + BLI_addtail(island_list, island); + island_added += 1; + } + + MEM_freeN(groups_array); + MEM_freeN(group_index); + return island_added; +} + +/** \} */ + +static float pack_islands_scale_margin(const blender::Vector<FaceIsland *> &island_vector, + BoxPack *box_array, + const float scale, + const float margin) +{ + for (const int index : island_vector.index_range()) { + FaceIsland *island = island_vector[index]; + BoxPack *box = &box_array[index]; + box->index = index; + box->w = BLI_rctf_size_x(&island->bounds_rect) * scale + 2 * margin; + box->h = BLI_rctf_size_y(&island->bounds_rect) * scale + 2 * margin; + } + float max_u, max_v; + BLI_box_pack_2d(box_array, island_vector.size(), &max_u, &max_v); + return max_ff(max_u, max_v); +} + +static float pack_islands_margin_fraction(const blender::Vector<FaceIsland *> &island_vector, + BoxPack *box_array, + const float margin_fraction) +{ + /* + * Root finding using a combined search / modified-secant method. + * First, use a robust search procedure to bracket the root within a factor of 10. + * Then, use a modified-secant method to converge. + * + * This is a specialized solver using domain knowledge to accelerate convergence. + */ + + float scale_low = 0.0f; + float value_low = 0.0f; + float scale_high = 0.0f; + float value_high = 0.0f; + float scale_last = 0.0f; + + /* Scaling smaller than `min_scale_roundoff` is unlikely to fit and + * will destroy information in existing UVs. */ + float min_scale_roundoff = 1e-5f; + + /* Certain inputs might have poor convergence properties. + * Use `max_iteration` to prevent an infinite loop. */ + int max_iteration = 25; + for (int iteration = 0; iteration < max_iteration; iteration++) { + float scale = 1.0f; + + if (iteration == 0) { + BLI_assert(iteration == 0); + BLI_assert(scale == 1.0f); + BLI_assert(scale_low == 0.0f); + BLI_assert(scale_high == 0.0f); + } + else if (scale_low == 0.0f) { + BLI_assert(scale_high > 0.0f); + /* Search mode, shrink layout until we can find a scale that fits. */ + scale = scale_high * 0.1f; + } + else if (scale_high == 0.0f) { + BLI_assert(scale_low > 0.0f); + /* Search mode, grow layout until we can find a scale that doesn't fit. */ + scale = scale_low * 10.0f; + } + else { + /* Bracket mode, use modified secant method to find root. */ + BLI_assert(scale_low > 0.0f); + BLI_assert(scale_high > 0.0f); + BLI_assert(value_low <= 0.0f); + BLI_assert(value_high >= 0.0f); + if (scale_high < scale_low * 1.0001f) { + /* Convergence. */ + break; + } + + /* Secant method for area. */ + scale = (sqrtf(scale_low) * value_high - sqrtf(scale_high) * value_low) / + (value_high - value_low); + scale = scale * scale; + + if (iteration & 1) { + /* Modified binary-search to improve robustness. */ + scale = sqrtf(scale * sqrtf(scale_low * scale_high)); + } + } + + scale = max_ff(scale, min_scale_roundoff); + + /* Evaluate our `f`. */ + scale_last = scale; + float max_uv = pack_islands_scale_margin( + island_vector, box_array, scale_last, margin_fraction); + float value = sqrtf(max_uv) - 1.0f; + + if (value <= 0.0f) { + scale_low = scale; + value_low = value; + } + else { + scale_high = scale; + value_high = value; + if (scale == min_scale_roundoff) { + /* Unable to pack without damaging UVs. */ + scale_low = scale; + break; + } + } + } + + const bool flush = true; + if (flush) { + /* Write back best pack as a side-effect. First get best pack. */ + if (scale_last != scale_low) { + scale_last = scale_low; + float max_uv = pack_islands_scale_margin( + island_vector, box_array, scale_last, margin_fraction); + UNUSED_VARS(max_uv); + /* TODO (?): `if (max_uv < 1.0f) { scale_last /= max_uv; }` */ + } + + /* Then expand FaceIslands by the correct amount. */ + for (const int index : island_vector.index_range()) { + BoxPack *box = &box_array[index]; + box->x /= scale_last; + box->y /= scale_last; + FaceIsland *island = island_vector[index]; + BLI_rctf_pad( + &island->bounds_rect, margin_fraction / scale_last, margin_fraction / scale_last); + } + } + return scale_last; +} + +static float calc_margin_from_aabb_length_sum(const blender::Vector<FaceIsland *> &island_vector, + const struct UVPackIsland_Params ¶ms) +{ + /* Logic matches behavior from #GEO_uv_parametrizer_pack. + * Attempt to give predictable results + * not dependent on current UV scale by using + * `aabb_length_sum` (was "`area`") to multiply + * the margin by the length (was "area"). + */ + double aabb_length_sum = 0.0f; + for (FaceIsland *island : island_vector) { + float w = BLI_rctf_size_x(&island->bounds_rect); + float h = BLI_rctf_size_y(&island->bounds_rect); + aabb_length_sum += sqrtf(w * h); + } + return params.margin * aabb_length_sum * 0.1f; +} + +static BoxPack *pack_islands_params(const blender::Vector<FaceIsland *> &island_vector, + const struct UVPackIsland_Params ¶ms, + float r_scale[2]) +{ + BoxPack *box_array = static_cast<BoxPack *>( + MEM_mallocN(sizeof(*box_array) * island_vector.size(), __func__)); + + if (params.margin == 0.0f) { + /* Special case for zero margin. Margin_method is ignored as all formulas give same result. */ + const float max_uv = pack_islands_scale_margin(island_vector, box_array, 1.0f, 0.0f); + r_scale[0] = 1.0f / max_uv; + r_scale[1] = r_scale[0]; + return box_array; + } + + if (params.margin_method == ED_UVPACK_MARGIN_FRACTION) { + /* Uses a line search on scale. ~10x slower than other method. */ + const float scale = pack_islands_margin_fraction(island_vector, box_array, params.margin); + r_scale[0] = scale; + r_scale[1] = scale; + /* pack_islands_margin_fraction will pad FaceIslands, return early. */ + return box_array; + } + + float margin = params.margin; + switch (params.margin_method) { + case ED_UVPACK_MARGIN_ADD: /* Default for Blender 2.8 and earlier. */ + break; /* Nothing to do. */ + case ED_UVPACK_MARGIN_SCALED: /* Default for Blender 3.3 and later. */ + margin = calc_margin_from_aabb_length_sum(island_vector, params); + break; + case ED_UVPACK_MARGIN_FRACTION: /* Added as an option in Blender 3.4. */ + BLI_assert_unreachable(); /* Handled above. */ + break; + default: + BLI_assert_unreachable(); + } + + const float max_uv = pack_islands_scale_margin(island_vector, box_array, 1.0f, margin); + r_scale[0] = 1.0f / max_uv; + r_scale[1] = r_scale[0]; + + for (int index = 0; index < island_vector.size(); index++) { + FaceIsland *island = island_vector[index]; + BLI_rctf_pad(&island->bounds_rect, margin, margin); + } + return box_array; +} + +static bool island_has_pins(FaceIsland *island) +{ + BMLoop *l; + BMIter iter; + const int cd_loop_uv_offset = island->cd_loop_uv_offset; + for (int i = 0; i < island->faces_len; i++) { + BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) { + MLoopUV *luv = static_cast<MLoopUV *>(BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset)); + if (luv->flag & MLOOPUV_PINNED) { + return true; + } + } + } + return false; +} + +/* -------------------------------------------------------------------- */ +/** \name Public UV Island Packing + * + * \note This behavior loosely follows #GEO_uv_parametrizer_pack. + * \{ */ + +void ED_uvedit_pack_islands_multi(const Scene *scene, + Object **objects, + const uint objects_len, + BMesh **bmesh_override, + const struct UVMapUDIM_Params *udim_params, + const struct UVPackIsland_Params *params) +{ + blender::Vector<FaceIsland *> island_vector; + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMesh *bm = nullptr; + if (bmesh_override) { + /* Note: obedit is still required for aspect ratio and ID_RECALC_GEOMETRY. */ + bm = bmesh_override[ob_index]; + } + else { + BMEditMesh *em = BKE_editmesh_from_object(obedit); + bm = em->bm; + } + BLI_assert(bm); + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + if (cd_loop_uv_offset == -1) { + continue; + } + + float aspect_y = 1.0f; + if (params->correct_aspect) { + float aspx, aspy; + ED_uvedit_get_aspect(obedit, &aspx, &aspy); + if (aspx != aspy) { + aspect_y = aspx / aspy; + } + } + + ListBase island_list = {nullptr}; + bm_mesh_calc_uv_islands(scene, + bm, + &island_list, + params->only_selected_faces, + params->only_selected_uvs, + params->use_seams, + aspect_y, + cd_loop_uv_offset); + + /* Remove from linked list and append to blender::Vector. */ + LISTBASE_FOREACH_MUTABLE (struct FaceIsland *, island, &island_list) { + BLI_remlink(&island_list, island); + if (params->ignore_pinned && island_has_pins(island)) { + MEM_freeN(island->faces); + MEM_freeN(island); + continue; + } + island_vector.append(island); + } + } + + if (island_vector.size() == 0) { + return; + } + + /* Coordinates of bounding box containing all selected UVs. */ + float selection_min_co[2], selection_max_co[2]; + INIT_MINMAX2(selection_min_co, selection_max_co); + + for (int index = 0; index < island_vector.size(); index++) { + FaceIsland *island = island_vector[index]; + /* Skip calculation if using specified UDIM option. */ + if (udim_params && (udim_params->use_target_udim == false)) { + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < island->faces_len; i++) { + BMFace *f = island->faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset); + } + + selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]); + selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]); + selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]); + selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]); + } + + if (params->rotate) { + face_island_uv_rotate_fit_aabb(island); + } + + bm_face_array_calc_bounds( + island->faces, island->faces_len, island->cd_loop_uv_offset, &island->bounds_rect); + } + + /* Center of bounding box containing all selected UVs. */ + float selection_center[2]; + if (udim_params && (udim_params->use_target_udim == false)) { + selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f; + selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f; + } + + float scale[2] = {1.0f, 1.0f}; + BoxPack *box_array = pack_islands_params(island_vector, *params, scale); + + /* Tile offset. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* CASE: ignore UDIM. */ + if (udim_params == nullptr) { + /* pass */ + } + /* CASE: Active/specified(smart uv project) UDIM. */ + else if (udim_params->use_target_udim) { + + /* Calculate offset based on specified_tile_index. */ + base_offset[0] = (udim_params->target_udim - 1001) % 10; + base_offset[1] = (udim_params->target_udim - 1001) / 10; + } + + /* CASE: Closest UDIM. */ + else { + const Image *image = udim_params->image; + const int *udim_grid = udim_params->grid_shape; + /* Check if selection lies on a valid UDIM grid tile. */ + bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center); + if (is_valid_udim) { + base_offset[0] = floorf(selection_center[0]); + base_offset[1] = floorf(selection_center[1]); + } + /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */ + else { + float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX}; + float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX; + if (image) { + nearest_image_tile_dist = uv_nearest_image_tile_distance( + image, selection_center, nearest_image_tile_co); + } + + float nearest_grid_tile_co[2] = {0.0f, 0.0f}; + nearest_grid_tile_dist = uv_nearest_grid_tile_distance( + udim_grid, selection_center, nearest_grid_tile_co); + + base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[0] : + nearest_grid_tile_co[0]; + base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[1] : + nearest_grid_tile_co[1]; + } + } + + for (int i = 0; i < island_vector.size(); i++) { + FaceIsland *island = island_vector[box_array[i].index]; + const float pivot[2] = { + island->bounds_rect.xmin, + island->bounds_rect.ymin, + }; + const float offset[2] = { + ((box_array[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0], + ((box_array[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1], + }; + for (int j = 0; j < island->faces_len; j++) { + BMFace *efa = island->faces[j]; + bm_face_uv_translate_and_scale_around_pivot( + efa, offset, scale, pivot, island->cd_loop_uv_offset); + } + } + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data); + } + + for (FaceIsland *island : island_vector) { + MEM_freeN(island->faces); + MEM_freeN(island); + } + + MEM_freeN(box_array); +} + +/** \} */ |