From 957ac41237fb19e03956dda0a293455b604a95ac Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Fri, 24 Mar 2023 11:45:37 +1300 Subject: [PATCH] Fix #78396: Pack UVs to original bounding box Adds the ability to pack UVs back into the original bounding box. Choose UV Editor > Menu > UV > Pack Islands Then change "Pack To" to "Original bounding box" --- source/blender/editors/include/ED_uvedit.h | 6 -- .../editors/uvedit/uvedit_unwrap_ops.cc | 87 ++++++++++++------- source/blender/geometry/GEO_uv_pack.hh | 2 + source/blender/geometry/intern/uv_pack.cc | 79 ++++++++++++----- .../geometry/intern/uv_parametrizer.cc | 3 +- 5 files changed, 115 insertions(+), 62 deletions(-) diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index 294d184ed34..56780640ad7 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -361,12 +361,6 @@ int bm_mesh_calc_uv_islands(const Scene *scene, const float aspect_y, BMUVOffsets offsets); -struct UVMapUDIM_Params { - const struct Image *image; - /** Copied from #SpaceImage.tile_grid_shape */ - int grid_shape[2]; -}; - /** * Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image. */ diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index f4303a2426a..4f2b8c5c6a9 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -1287,15 +1287,17 @@ static bool island_has_pins(const Scene *scene, * \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 udim_source_closest: UDIM source SpaceImage. + * \param original_selection: Pack to original selection. * \param params: Parameters and options to pass to the packing engine. */ static void uvedit_pack_islands_multi(const Scene *scene, Object **objects, const int objects_len, BMesh **bmesh_override, - const UVMapUDIM_Params *closest_udim, - const blender::geometry::UVPackIsland_Params *params) + const SpaceImage *udim_source_closest, + const bool original_selection, + blender::geometry::UVPackIsland_Params *params) { blender::Vector island_vector; @@ -1357,12 +1359,10 @@ static void uvedit_pack_islands_multi(const Scene *scene, 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); - } + + 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) { @@ -1375,9 +1375,15 @@ static void uvedit_pack_islands_multi(const Scene *scene, /* 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; + mid_v2_v2v2(selection_center, selection_min_co, selection_max_co); + + if (original_selection) { + /* Protect against degenerate source AABB. */ + if ((selection_max_co[0] - selection_min_co[0]) * (selection_max_co[1] - selection_min_co[1]) > + 1e-40f) { + params->target_aspect_y = (selection_max_co[0] - selection_min_co[0]) / + (selection_max_co[1] - selection_min_co[1]); + } } MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); @@ -1415,14 +1421,15 @@ static void uvedit_pack_islands_multi(const Scene *scene, } BLI_heap_free(heap, nullptr); BLI_memarena_free(arena); + 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; + if (udim_source_closest) { + const Image *image = udim_source_closest->image; + const int *udim_grid = udim_source_closest->tile_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) { @@ -1470,6 +1477,20 @@ static void uvedit_pack_islands_multi(const Scene *scene, /* Perform the transformation. */ island_uv_transform(island, matrix, pre_translate); + if (original_selection) { + const float rescale_x = (selection_max_co[0] - selection_min_co[0]) / + params->target_aspect_y; + const float rescale_y = (selection_max_co[1] - selection_min_co[1]); + const float rescale = std::min(rescale_x, rescale_y); + matrix[0][0] = rescale; + matrix[0][1] = 0.0f; + matrix[1][0] = 0.0f; + matrix[1][1] = rescale; + pre_translate[0] = selection_min_co[0] / rescale; + pre_translate[1] = selection_min_co[1] / rescale; + island_uv_transform(island, matrix, pre_translate); + } + /* Cleanup memory. */ pack_island_vector[i] = nullptr; delete pack_island; @@ -1494,7 +1515,8 @@ static void uvedit_pack_islands_multi(const Scene *scene, /* Packing targets. */ enum { PACK_UDIM_SRC_CLOSEST = 0, - PACK_UDIM_SRC_ACTIVE = 1, + PACK_UDIM_SRC_ACTIVE, + PACK_ORIGINAL_AABB, }; static int pack_islands_exec(bContext *C, wmOperator *op) @@ -1539,21 +1561,17 @@ static int pack_islands_exec(bContext *C, wmOperator *op) pack_island_params.shape_method = eUVPackIsland_ShapeMethod( RNA_enum_get(op->ptr, "shape_method")); - UVMapUDIM_Params closest_udim_buf; - UVMapUDIM_Params *closest_udim = nullptr; if (udim_source == PACK_UDIM_SRC_ACTIVE) { pack_island_params.setUDIMOffsetFromSpaceImage(sima); } - else if (sima) { - BLI_assert(udim_source == PACK_UDIM_SRC_CLOSEST); - closest_udim = &closest_udim_buf; - closest_udim->image = sima->image; - closest_udim->grid_shape[0] = sima->tile_grid_shape[0]; - closest_udim->grid_shape[1] = sima->tile_grid_shape[1]; - } - uvedit_pack_islands_multi( - scene, objects, objects_len, nullptr, closest_udim, &pack_island_params); + uvedit_pack_islands_multi(scene, + objects, + objects_len, + nullptr, + (udim_source == PACK_UDIM_SRC_CLOSEST) ? sima : nullptr, + (udim_source == PACK_ORIGINAL_AABB), + &pack_island_params); MEM_freeN(objects); return OPERATOR_FINISHED; @@ -1591,6 +1609,11 @@ void UV_OT_pack_islands(wmOperatorType *ot) 0, "Active UDIM", "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"}, + {PACK_ORIGINAL_AABB, + "ORIGINAL_AABB", + 0, + "Original bounding box", + "Pack to starting bounding box of islands"}, {0, nullptr, 0, nullptr, nullptr}, }; /* identifiers */ @@ -2336,7 +2359,8 @@ void ED_uvedit_live_unwrap(const Scene *scene, Object **objects, int objects_len pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED; pack_island_params.margin = scene->toolsettings->uvcalc_margin; - uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params); + uvedit_pack_islands_multi( + scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params); } } @@ -2478,7 +2502,8 @@ static int unwrap_exec(bContext *C, wmOperator *op) RNA_enum_get(op->ptr, "margin_method")); pack_island_params.margin = RNA_float_get(op->ptr, "margin"); - uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params); + uvedit_pack_islands_multi( + scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params); MEM_freeN(objects); @@ -2859,7 +2884,7 @@ static int smart_project_exec(bContext *C, wmOperator *op) params.margin = RNA_float_get(op->ptr, "island_margin"); uvedit_pack_islands_multi( - scene, objects_changed, object_changed_len, nullptr, nullptr, ¶ms); + scene, objects_changed, object_changed_len, nullptr, nullptr, false, ¶ms); /* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */ const bool per_face_aspect = false; @@ -3847,7 +3872,7 @@ void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob) params.margin_method = ED_UVPACK_MARGIN_SCALED; params.margin = 0.001f; - uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, ¶ms); + uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, false, ¶ms); /* Write back from BMesh to Mesh. */ BMeshToMeshParams bm_to_me_params{}; diff --git a/source/blender/geometry/GEO_uv_pack.hh b/source/blender/geometry/GEO_uv_pack.hh index 785e0c76058..e03a1a33dd2 100644 --- a/source/blender/geometry/GEO_uv_pack.hh +++ b/source/blender/geometry/GEO_uv_pack.hh @@ -66,6 +66,8 @@ class UVPackIsland_Params { eUVPackIsland_MarginMethod margin_method; /** Additional translation for bottom left corner. */ float udim_base_offset[2]; + /** Target aspect ratio. */ + float target_aspect_y; /** Which shape to use when packing. */ eUVPackIsland_ShapeMethod shape_method; }; diff --git a/source/blender/geometry/intern/uv_pack.cc b/source/blender/geometry/intern/uv_pack.cc index e73d7d51da4..9909cf57504 100644 --- a/source/blender/geometry/intern/uv_pack.cc +++ b/source/blender/geometry/intern/uv_pack.cc @@ -181,6 +181,7 @@ UVPackIsland_Params::UVPackIsland_Params() margin_method = ED_UVPACK_MARGIN_SCALED; udim_base_offset[0] = 0.0f; udim_base_offset[1] = 0.0f; + target_aspect_y = 1.0f; shape_method = ED_UVPACK_SHAPE_AABB; } @@ -205,13 +206,14 @@ class UVAABBIsland { * the input must be pre-sorted, which costs an additional `O(nlogn)` time complexity. */ static void pack_islands_alpaca_turbo(const Span islands, + const float target_aspect_y, float *r_max_u, float *r_max_v) { /* Exclude an initial AABB near the origin. */ float next_u1 = *r_max_u; float next_v1 = *r_max_v; - bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */ + bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */ float u0 = zigzag ? next_u1 : 0.0f; float v0 = zigzag ? 0.0f : next_v1; @@ -230,7 +232,7 @@ static void pack_islands_alpaca_turbo(const Span islands, } if (restart) { /* We're at the end of a strip. Restart from U axis or V axis. */ - zigzag = next_u1 < next_v1; + zigzag = next_u1 / target_aspect_y < next_v1; u0 = zigzag ? next_u1 : 0.0f; v0 = zigzag ? 0.0f : next_v1; } @@ -262,6 +264,7 @@ static void pack_island_box_pack_2d(const Span aabbs, const Span islands, const float scale, const float margin, + const float target_aspect_y, float *r_max_u, float *r_max_v) { @@ -273,21 +276,24 @@ static void pack_island_box_pack_2d(const Span aabbs, for (const int64_t i : aabbs.index_range()) { PackIsland *island = islands[aabbs[i]->index]; BoxPack *box = box_array + i; - box->w = island->half_diagonal_.x * 2 * scale + 2 * margin; + box->w = (island->half_diagonal_.x * 2 * scale + 2 * margin) / target_aspect_y; box->h = island->half_diagonal_.y * 2 * scale + 2 * margin; } const bool sort_boxes = false; /* Use existing ordering from `aabbs`. */ /* \note Writes to `*r_max_u` and `*r_max_v`. */ - BLI_box_pack_2d(box_array, int(islands.size()), sort_boxes, r_max_u, r_max_v); + BLI_box_pack_2d(box_array, int(aabbs.size()), sort_boxes, r_max_u, r_max_v); + + *r_max_u *= target_aspect_y; /* Write back box_pack UVs. */ for (const int64_t i : aabbs.index_range()) { PackIsland *island = islands[aabbs[i]->index]; BoxPack *box = box_array + i; island->angle = 0.0f; /* #BLI_box_pack_2d never rotates. */ - island->pre_translate.x = (box->x + box->w * 0.5f) / scale - island->pivot_.x; + island->pre_translate.x = (box->x + box->w * 0.5f) * target_aspect_y / scale - + island->pivot_.x; island->pre_translate.y = (box->y + box->h * 0.5f) / scale - island->pivot_.y; } @@ -464,17 +470,26 @@ float Occupancy::trace_island(PackIsland *island, return -1.0f; /* Available. */ } -static float2 find_best_fit_for_island( - PackIsland *island, int scan_line, Occupancy &occupancy, const float scale, const float margin) +static float2 find_best_fit_for_island(PackIsland *island, + int scan_line, + Occupancy &occupancy, + const float scale, + const float margin, + const float target_aspect_y) { const float bitmap_scale = 1.0f / occupancy.bitmap_scale_reciprocal; const float half_x_scaled = island->half_diagonal_.x * scale; const float half_y_scaled = island->half_diagonal_.y * scale; + const float sqrt_target_aspect_y = sqrtf(target_aspect_y); + int scan_line_x = int(scan_line * sqrt_target_aspect_y); + int scan_line_y = int(scan_line / sqrt_target_aspect_y); + /* Scan using an "Alpaca"-style search, first horizontally using "less-than". */ int t = int(ceilf((2 * half_x_scaled + margin) * occupancy.bitmap_scale_reciprocal)); - while (t < scan_line) { - const float2 probe(t * bitmap_scale - half_x_scaled, scan_line * bitmap_scale - half_y_scaled); + while (t < scan_line_x) { + const float2 probe(t * bitmap_scale - half_x_scaled, + scan_line_y * bitmap_scale - half_y_scaled); const float extent = occupancy.trace_island(island, scale, margin, probe, false); if (extent < 0.0f) { return probe; /* Success. */ @@ -484,8 +499,9 @@ static float2 find_best_fit_for_island( /* Then scan vertically using "less-than-or-equal" */ t = int(ceilf((2 * half_y_scaled + margin) * occupancy.bitmap_scale_reciprocal)); - while (t <= scan_line) { - const float2 probe(scan_line * bitmap_scale - half_x_scaled, t * bitmap_scale - half_y_scaled); + while (t <= scan_line_y) { + const float2 probe(scan_line_x * bitmap_scale - half_x_scaled, + t * bitmap_scale - half_y_scaled); const float extent = occupancy.trace_island(island, scale, margin, probe, false); if (extent < 0.0f) { return probe; /* Success. */ @@ -528,6 +544,7 @@ static void pack_island_xatlas(const Span island_indices, const Span islands, const float scale, const float margin, + const float target_aspect_y, float *r_max_u, float *r_max_v) { @@ -546,7 +563,8 @@ static void pack_island_xatlas(const Span island_indices, while (i < island_indices.size()) { PackIsland *island = islands[island_indices[i]->index]; - const float2 best = find_best_fit_for_island(island, scan_line, occupancy, scale, margin); + const float2 best = find_best_fit_for_island( + island, scan_line, occupancy, scale, margin, target_aspect_y); if (best.x <= -1.0f) { /* Unable to find a fit on this scan_line. */ @@ -562,7 +580,8 @@ static void pack_island_xatlas(const Span island_indices, * choice is 2. */ scan_line += 2; } - if (scan_line < occupancy.bitmap_radix) { + if (scan_line < + occupancy.bitmap_radix * sqrtf(std::min(target_aspect_y, 1.0f / target_aspect_y))) { continue; /* Try again on next scan_line. */ } @@ -654,13 +673,14 @@ static void update_hole_rotate(float2 &hole, * Tracking the "Hole" has a slight performance cost, while improving packing efficiency. */ static void pack_islands_alpaca_rotate(const Span islands, + const float target_aspect_y, float *r_max_u, float *r_max_v) { /* Exclude an initial AABB near the origin. */ float next_u1 = *r_max_u; float next_v1 = *r_max_v; - bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */ + bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */ /* Track an AABB "hole" which may be filled at any time. */ float2 hole(0.0f); @@ -716,7 +736,7 @@ static void pack_islands_alpaca_rotate(const Span islands, if (restart) { update_hole_rotate(hole, hole_diagonal, hole_rotate, u0, v0, next_u1, next_v1); /* We're at the end of a strip. Restart from U axis or V axis. */ - zigzag = next_u1 < next_v1; + zigzag = next_u1 / target_aspect_y < next_v1; u0 = zigzag ? next_u1 : 0.0f; v0 = zigzag ? 0.0f : next_v1; } @@ -842,7 +862,6 @@ static float pack_islands_scale_margin(const Span islands, alpaca_cutoff = alpaca_cutoff_fast; } } - const int64_t max_box_pack = std::min(alpaca_cutoff, islands.size()); /* Call box_pack_2d (slow for large N.) */ @@ -851,23 +870,35 @@ static float pack_islands_scale_margin(const Span islands, switch (params.shape_method) { case ED_UVPACK_SHAPE_CONVEX: case ED_UVPACK_SHAPE_CONCAVE: - pack_island_xatlas( - aabbs.as_span().take_front(max_box_pack), islands, scale, margin, &max_u, &max_v); + pack_island_xatlas(aabbs.as_span().take_front(max_box_pack), + islands, + scale, + margin, + params.target_aspect_y, + &max_u, + &max_v); break; default: - pack_island_box_pack_2d( - aabbs.as_span().take_front(max_box_pack), islands, scale, margin, &max_u, &max_v); + pack_island_box_pack_2d(aabbs.as_span().take_front(max_box_pack), + islands, + scale, + margin, + params.target_aspect_y, + &max_u, + &max_v); break; } - /* At this stage, `max_u` and `max_v` contain the box_pack UVs. */ + /* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */ /* Call Alpaca. */ if (params.rotate && !contains_non_square_islands) { - pack_islands_alpaca_rotate(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v); + pack_islands_alpaca_rotate( + aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v); } else { - pack_islands_alpaca_turbo(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v); + pack_islands_alpaca_turbo( + aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v); } /* Write back Alpaca UVs. */ @@ -888,7 +919,7 @@ static float pack_islands_scale_margin(const Span islands, delete aabb; } - return std::max(max_u, max_v); + return std::max(max_u / params.target_aspect_y, max_v); } /** Find the optimal scale to pack islands into the unit square. diff --git a/source/blender/geometry/intern/uv_parametrizer.cc b/source/blender/geometry/intern/uv_parametrizer.cc index efc6639af5d..a87355470e1 100644 --- a/source/blender/geometry/intern/uv_parametrizer.cc +++ b/source/blender/geometry/intern/uv_parametrizer.cc @@ -444,10 +444,11 @@ static void uv_parametrizer_scale_x(ParamHandle *phandle, const float scale_x) return; /* Identity transform. */ } + /* Scale every chart. */ for (int i = 0; i < phandle->ncharts; i++) { PChart *chart = phandle->charts[i]; for (PVert *v = chart->verts; v; v = v->nextlink) { - v->uv[0] *= scale_x; + v->uv[0] *= scale_x; /* Only scale x axis. */ } } }