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"
This commit is contained in:
Chris Blackbourn 2023-03-24 11:45:37 +13:00
parent 531f99ffbd
commit 957ac41237
5 changed files with 115 additions and 62 deletions

@ -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.
*/

@ -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<FaceIsland *> island_vector;
@ -1357,13 +1359,11 @@ 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);
}
}
if (params->rotate) {
face_island_uv_rotate_fit_aabb(island);
@ -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, &params);
scene, objects_changed, object_changed_len, nullptr, nullptr, false, &params);
/* #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, &params);
uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, false, &params);
/* Write back from BMesh to Mesh. */
BMeshToMeshParams bm_to_me_params{};

@ -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;
};

@ -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<UVAABBIsland *> 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<UVAABBIsland *> 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<UVAABBIsland *> aabbs,
const Span<PackIsland *> 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<UVAABBIsland *> 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<UVAABBIsland *> island_indices,
const Span<PackIsland *> 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<UVAABBIsland *> 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<UVAABBIsland *> 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<UVAABBIsland *> 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<UVAABBIsland *> 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<PackIsland *> 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<PackIsland *> 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<PackIsland *> 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.

@ -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. */
}
}
}