From 5c5650f0f8d1c74c3a64732e774ba84e35efd65b Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Wed, 8 Mar 2023 05:43:15 +0100 Subject: [PATCH] Cleanup: UV: Rearrange functions in cpp files to improve type safety No functional changes. --- source/blender/editors/include/ED_uvedit.h | 21 - .../blender/editors/uvedit/uvedit_islands.cc | 475 +----------------- .../editors/uvedit/uvedit_unwrap_ops.cc | 450 +++++++++++++++++ source/blender/geometry/GEO_uv_pack.hh | 4 - 4 files changed, 453 insertions(+), 497 deletions(-) diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index ccc53a43177..c154fa5af01 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -26,7 +26,6 @@ struct Object; struct Scene; struct SpaceImage; struct ToolSettings; -struct UVPackIsland_Params; struct View2D; struct ViewLayer; struct bContext; @@ -356,26 +355,6 @@ bool uv_coords_isect_udim(const struct Image *image, const int udim_grid[2], const float coords[2]); -/** - * Pack UV islands from multiple objects. - * - * \param scene: Scene containing the objects to be packed. - * \param objects: Array of Objects to pack. - * \param objects_len: Length of `objects` array. - * \param bmesh_override: BMesh array aligned with `objects`. - * Optional, when non-null this overrides object's BMesh. - * This is needed to perform UV packing on objects that aren't in edit-mode. - * \param udim_params: Parameters to specify UDIM target and UDIM source image. - * \param params: Parameters and options to pass to the packing engine. - * - */ -void ED_uvedit_pack_islands_multi(const struct Scene *scene, - Object **objects, - uint objects_len, - struct BMesh **bmesh_override, - const struct UVMapUDIM_Params *closest_udim, - const struct UVPackIsland_Params *params); - #ifdef __cplusplus } #endif diff --git a/source/blender/editors/uvedit/uvedit_islands.cc b/source/blender/editors/uvedit/uvedit_islands.cc index aae65284d8c..132210cddb9 100644 --- a/source/blender/editors/uvedit/uvedit_islands.cc +++ b/source/blender/editors/uvedit/uvedit_islands.cc @@ -11,223 +11,14 @@ * 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 "BLI_math.h" + +#include "DNA_image_types.h" #include "ED_uvedit.h" /* Own include. */ -#include "GEO_uv_pack.hh" - -#include "WM_api.h" -#include "WM_types.h" - -#include "bmesh.h" - -static void mul_v2_m2_add_v2v2(float r[2], - const float mat[2][2], - const float a[2], - const float b[2]) -{ - /* Compute `r = mat * (a + b)` with high precision. */ - const double x = double(a[0]) + double(b[0]); - const double y = double(a[1]) + double(b[1]); - - r[0] = float(mat[0][0] * x + mat[1][0] * y); - r[1] = float(mat[0][1] * x + mat[1][1] * y); -} - -static void island_uv_transform(FaceIsland *island, - const float matrix[2][2], /* Scale and rotation. */ - const float pre_translate[2] /* (pre) Translation. */ -) -{ - /* Use a pre-transform to compute `A * (x+b)` - * - * \note Ordinarily, we'd use a post_transform like `A * x + b` - * In general, post-transforms are easier to work with when using homogenous co-ordinates. - * - * When UV mapping into the unit square, post-transforms can lose precision on small islands. - * Instead we're using a pre-transform to maintain precision. - * - * To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b` - */ - - const int cd_loop_uv_offset = island->offsets.uv; - const int faces_len = island->faces_len; - for (int i = 0; i < faces_len; i++) { - BMFace *f = island->faces[i]; - BMLoop *l; - BMIter iter; - BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) { - float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset); - mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate); - } - } -} - -/* -------------------------------------------------------------------- */ -/** \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( - 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 float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset); - copy_v2_v2(coords[coords_len++], luv); - - /* 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 float *luv_radial = BM_ELEM_CD_GET_FLOAT_P(l_radial, cd_loop_uv_offset); - if (equals_v2v2(luv, luv_radial)) { - /* 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->offsets.uv; - - /* 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]; - float pre_translate[2] = {0, 0}; - angle_to_mat2(matrix, angle); - matrix[1][0] *= 1.0f / aspect_y; - /* matrix[1][1] *= aspect_y / aspect_y; */ - matrix[0][1] *= aspect_y; - island_uv_transform(island, matrix, pre_translate); - } -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name UDIM packing helper functions * \{ */ @@ -261,58 +52,6 @@ bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const floa 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); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -436,211 +175,3 @@ int bm_mesh_calc_uv_islands(const Scene *scene, } /** \} */ - -static bool island_has_pins(const Scene *scene, - FaceIsland *island, - const UVPackIsland_Params *params) -{ - const bool pin_unselected = params->pin_unselected; - const bool only_selected_faces = params->only_selected_faces; - BMLoop *l; - BMIter iter; - const int pin_offset = island->offsets.pin; - for (int i = 0; i < island->faces_len; i++) { - BMFace *efa = island->faces[i]; - if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - return true; - } - BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) { - if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) { - return true; - } - if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) { - return true; - } - } - } - return false; -} - -/* -------------------------------------------------------------------- */ -/** \name Public UV Island Packing - * - * \note This behavior loosely follows #geometry::uv_parametrizer_pack. - * \{ */ - -void ED_uvedit_pack_islands_multi(const Scene *scene, - Object **objects, - const uint objects_len, - BMesh **bmesh_override, - const UVMapUDIM_Params *closest_udim, - const UVPackIsland_Params *params) -{ - blender::Vector 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 BMUVOffsets offsets = BM_uv_map_get_offsets(bm); - if (offsets.uv == -1) { - continue; - } - - const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f; - - bool only_selected_faces = params->only_selected_faces; - bool only_selected_uvs = params->only_selected_uvs; - if (params->ignore_pinned && params->pin_unselected) { - only_selected_faces = false; - only_selected_uvs = false; - } - ListBase island_list = {nullptr}; - bm_mesh_calc_uv_islands(scene, - bm, - &island_list, - only_selected_faces, - only_selected_uvs, - params->use_seams, - aspect_y, - offsets); - - /* 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(scene, island, params)) { - 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]; - if (closest_udim) { - /* Only calculate selection bounding box if using closest_udim. */ - for (int i = 0; i < island->faces_len; i++) { - BMFace *f = island->faces[i]; - BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv); - } - } - - if (params->rotate) { - face_island_uv_rotate_fit_aabb(island); - } - - bm_face_array_calc_bounds( - island->faces, island->faces_len, island->offsets.uv, &island->bounds_rect); - } - - /* Center of bounding box containing all selected UVs. */ - float selection_center[2]; - if (closest_udim) { - 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}; - blender::Vector pack_island_vector; - for (int i = 0; i < island_vector.size(); i++) { - FaceIsland *face_island = island_vector[i]; - blender::geometry::PackIsland *pack_island = new blender::geometry::PackIsland(); - pack_island->bounds_rect = face_island->bounds_rect; - pack_island_vector.append(pack_island); - } - BoxPack *box_array = pack_islands(pack_island_vector, *params, scale); - - float base_offset[2] = {0.0f, 0.0f}; - copy_v2_v2(base_offset, params->udim_base_offset); - - if (closest_udim) { - const Image *image = closest_udim->image; - const int *udim_grid = closest_udim->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]; - } - } - - float matrix[2][2]; - float matrix_inverse[2][2]; - float pre_translate[2]; - for (int i = 0; i < island_vector.size(); i++) { - FaceIsland *island = island_vector[box_array[i].index]; - matrix[0][0] = scale[0]; - matrix[0][1] = 0.0f; - matrix[1][0] = 0.0f; - matrix[1][1] = scale[1]; - invert_m2_m2(matrix_inverse, matrix); - - /* Add base_offset, post transform. */ - mul_v2_m2v2(pre_translate, matrix_inverse, base_offset); - - /* Translate to box_array from bounds_rect. */ - pre_translate[0] += box_array[i].x - island->bounds_rect.xmin; - pre_translate[1] += box_array[i].y - island->bounds_rect.ymin; - island_uv_transform(island, matrix, pre_translate); - } - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *obedit = objects[ob_index]; - DEG_id_tag_update(static_cast(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); - } - - for (int i = 0; i < pack_island_vector.size(); i++) { - blender::geometry::PackIsland *pack_island = pack_island_vector[i]; - pack_island_vector[i] = nullptr; - delete pack_island; - } - - MEM_freeN(box_array); -} - -/** \} */ diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index fe40ec36873..220ec545542 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -19,6 +19,7 @@ #include "DNA_scene_types.h" #include "BLI_array.hh" +#include "BLI_convexhull_2d.h" #include "BLI_linklist.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -1016,6 +1017,455 @@ void UV_OT_minimize_stretch(wmOperatorType *ot) /** \} */ +/** Compute `r = mat * (a + b)` with high precision. */ +static void mul_v2_m2_add_v2v2(float r[2], + const float mat[2][2], + const float a[2], + const float b[2]) +{ + const double x = double(a[0]) + double(b[0]); + const double y = double(a[1]) + double(b[1]); + + r[0] = float(mat[0][0] * x + mat[1][0] * y); + r[1] = float(mat[0][1] * x + mat[1][1] * y); +} + +static void island_uv_transform(FaceIsland *island, + const float matrix[2][2], /* Scale and rotation. */ + const float pre_translate[2] /* (pre) Translation. */ +) +{ + /* Use a pre-transform to compute `A * (x+b)` + * + * \note Ordinarily, we'd use a post_transform like `A * x + b` + * In general, post-transforms are easier to work with when using homogenous co-ordinates. + * + * When UV mapping into the unit square, post-transforms can lose precision on small islands. + * Instead we're using a pre-transform to maintain precision. + * + * To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b` + */ + + const int cd_loop_uv_offset = island->offsets.uv; + const int faces_len = island->faces_len; + for (int i = 0; i < faces_len; i++) { + BMFace *f = island->faces[i]; + BMLoop *l; + BMIter iter; + BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) { + float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset); + mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate); + } + } +} + +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( + 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 float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset); + copy_v2_v2(coords[coords_len++], luv); + + /* 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 float *luv_radial = BM_ELEM_CD_GET_FLOAT_P(l_radial, cd_loop_uv_offset); + if (equals_v2v2(luv, luv_radial)) { + /* 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->offsets.uv; + + /* 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]; + float pre_translate[2] = {0, 0}; + angle_to_mat2(matrix, angle); + matrix[1][0] *= 1.0f / aspect_y; + /* matrix[1][1] *= aspect_y / aspect_y; */ + matrix[0][1] *= aspect_y; + island_uv_transform(island, matrix, pre_translate); + } +} + +/** + * 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); +} + +static bool island_has_pins(const Scene *scene, + FaceIsland *island, + const UVPackIsland_Params *params) +{ + const bool pin_unselected = params->pin_unselected; + const bool only_selected_faces = params->only_selected_faces; + BMLoop *l; + BMIter iter; + const int pin_offset = island->offsets.pin; + for (int i = 0; i < island->faces_len; i++) { + BMFace *efa = island->faces[i]; + if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + return true; + } + BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) { + if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) { + return true; + } + if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) { + return true; + } + } + } + return false; +} + +/** + * Pack UV islands from multiple objects. + * + * \param scene: Scene containing the objects to be packed. + * \param objects: Array of Objects to pack. + * \param objects_len: Length of `objects` array. + * \param bmesh_override: BMesh array aligned with `objects`. + * Optional, when non-null this overrides object's BMesh. + * This is needed to perform UV packing on objects that aren't in edit-mode. + * \param udim_params: Parameters to specify UDIM target and UDIM source image. + * \param params: Parameters and options to pass to the packing engine. + * + */ +void ED_uvedit_pack_islands_multi(const Scene *scene, + Object **objects, + const int objects_len, + BMesh **bmesh_override, + const UVMapUDIM_Params *closest_udim, + const UVPackIsland_Params *params) +{ + blender::Vector 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 BMUVOffsets offsets = BM_uv_map_get_offsets(bm); + if (offsets.uv == -1) { + continue; + } + + const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f; + + bool only_selected_faces = params->only_selected_faces; + bool only_selected_uvs = params->only_selected_uvs; + if (params->ignore_pinned && params->pin_unselected) { + only_selected_faces = false; + only_selected_uvs = false; + } + ListBase island_list = {nullptr}; + bm_mesh_calc_uv_islands(scene, + bm, + &island_list, + only_selected_faces, + only_selected_uvs, + params->use_seams, + aspect_y, + offsets); + + /* 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(scene, island, params)) { + 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]; + if (closest_udim) { + /* Only calculate selection bounding box if using closest_udim. */ + for (int i = 0; i < island->faces_len; i++) { + BMFace *f = island->faces[i]; + BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv); + } + } + + if (params->rotate) { + face_island_uv_rotate_fit_aabb(island); + } + + bm_face_array_calc_bounds( + island->faces, island->faces_len, island->offsets.uv, &island->bounds_rect); + } + + /* Center of bounding box containing all selected UVs. */ + float selection_center[2]; + if (closest_udim) { + 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}; + blender::Vector pack_island_vector; + for (int i = 0; i < island_vector.size(); i++) { + FaceIsland *face_island = island_vector[i]; + blender::geometry::PackIsland *pack_island = new blender::geometry::PackIsland(); + pack_island->bounds_rect = face_island->bounds_rect; + pack_island_vector.append(pack_island); + } + BoxPack *box_array = pack_islands(pack_island_vector, *params, scale); + + float base_offset[2] = {0.0f, 0.0f}; + copy_v2_v2(base_offset, params->udim_base_offset); + + if (closest_udim) { + const Image *image = closest_udim->image; + const int *udim_grid = closest_udim->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]; + } + } + + float matrix[2][2]; + float matrix_inverse[2][2]; + float pre_translate[2]; + for (int i = 0; i < island_vector.size(); i++) { + FaceIsland *island = island_vector[box_array[i].index]; + matrix[0][0] = scale[0]; + matrix[0][1] = 0.0f; + matrix[1][0] = 0.0f; + matrix[1][1] = scale[1]; + invert_m2_m2(matrix_inverse, matrix); + + /* Add base_offset, post transform. */ + mul_v2_m2v2(pre_translate, matrix_inverse, base_offset); + + /* Translate to box_array from bounds_rect. */ + pre_translate[0] += box_array[i].x - island->bounds_rect.xmin; + pre_translate[1] += box_array[i].y - island->bounds_rect.ymin; + island_uv_transform(island, matrix, pre_translate); + } + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + DEG_id_tag_update(static_cast(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); + } + + for (int i = 0; i < pack_island_vector.size(); i++) { + blender::geometry::PackIsland *pack_island = pack_island_vector[i]; + pack_island_vector[i] = nullptr; + delete pack_island; + } + + MEM_freeN(box_array); +} + /* -------------------------------------------------------------------- */ /** \name Pack UV Islands Operator * \{ */ diff --git a/source/blender/geometry/GEO_uv_pack.hh b/source/blender/geometry/GEO_uv_pack.hh index a6adcb4cbaf..622efe1e608 100644 --- a/source/blender/geometry/GEO_uv_pack.hh +++ b/source/blender/geometry/GEO_uv_pack.hh @@ -11,9 +11,6 @@ * \ingroup geo */ -/** Workaround to forward-declare C type in C++ header. */ -extern "C" { - enum eUVPackIsland_MarginMethod { ED_UVPACK_MARGIN_SCALED = 0, /* Use scale of existing UVs to multiply margin. */ ED_UVPACK_MARGIN_ADD, /* Just add the margin, ignoring any UV scale. */ @@ -43,7 +40,6 @@ struct UVPackIsland_Params { /** Additional translation for bottom left corner. */ float udim_base_offset[2]; }; -} namespace blender::geometry {