UV: Simplify packer, improving performance, efficiency and fixing bugs

Adds a generic `is_larger` and `get_aspect_scaled_extent` function to
simplify packing logic.

Migrate several packing functions so that they only improve a layout,
and early-exit packing if a better layout already exists.

Fix a `rotate_inside_square` logic error during xatlas packing.
This commit is contained in:
Chris Blackbourn
2023-05-11 17:08:34 +12:00
parent cc9678b5fe
commit ccb2dbddac

View File

@@ -89,6 +89,24 @@ static float dist_signed_squared_to_edge(float2 probe, float2 uva, float2 uvb)
return numerator_ssq / edge_length_squared; return numerator_ssq / edge_length_squared;
} }
/**
* \return the larger dimension of `extent`, factoring in the target aspect ratio.
*/
static float get_aspect_scaled_extent(const rctf &extent, const UVPackIsland_Params &params)
{
const float width = BLI_rctf_size_x(&extent);
const float height = BLI_rctf_size_y(&extent);
return std::max(width / params.target_aspect_y, height);
}
/**
* \return true iff `b` is a preferred layout over `a`, given the packing parameters supplied.
*/
static bool is_larger(const rctf &a, const rctf &b, const UVPackIsland_Params &params)
{
return get_aspect_scaled_extent(b, params) < get_aspect_scaled_extent(a, params);
}
PackIsland::PackIsland() PackIsland::PackIsland()
{ {
/* Initialize to the identity transform. */ /* Initialize to the identity transform. */
@@ -345,12 +363,11 @@ static void pack_islands_alpaca_turbo(const int64_t start_index,
const Span<UVAABBIsland *> islands, const Span<UVAABBIsland *> islands,
const float target_aspect_y, const float target_aspect_y,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
/* Exclude an initial AABB near the origin. */ /* Exclude an initial AABB near the origin. */
float next_u1 = *r_max_u; float next_u1 = r_extent->xmax;
float next_v1 = *r_max_v; float next_v1 = r_extent->ymax;
bool zigzag = next_u1 < next_v1 * target_aspect_y; /* Horizontal or Vertical strip? */ bool zigzag = next_u1 < next_v1 * target_aspect_y; /* Horizontal or Vertical strip? */
float u0 = zigzag ? next_u1 : 0.0f; float u0 = zigzag ? next_u1 : 0.0f;
@@ -395,9 +412,8 @@ static void pack_islands_alpaca_turbo(const int64_t start_index,
} }
} }
/* Write back total pack AABB. */ /* Write back extent. */
*r_max_u = next_u1; *r_extent = {0.0f, next_u1, 0.0f, next_v1};
*r_max_v = next_v1;
} }
/** /**
@@ -457,12 +473,11 @@ static void pack_islands_alpaca_rotate(const int64_t start_index,
const Span<UVAABBIsland *> islands, const Span<UVAABBIsland *> islands,
const float target_aspect_y, const float target_aspect_y,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
/* Exclude an initial AABB near the origin. */ /* Exclude an initial AABB near the origin. */
float next_u1 = *r_max_u; float next_u1 = r_extent->xmax;
float next_v1 = *r_max_v; float next_v1 = r_extent->ymax;
bool zigzag = next_u1 / target_aspect_y < 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. */ /* Track an AABB "hole" which may be filled at any time. */
@@ -553,8 +568,25 @@ static void pack_islands_alpaca_rotate(const int64_t start_index,
} }
/* Write back total pack AABB. */ /* Write back total pack AABB. */
*r_max_u = next_u1; *r_extent = {0.0f, next_u1, 0.0f, next_v1};
*r_max_v = next_v1; }
/**
* Use a fast algorithm to pack the supplied `aabbs`.
*/
static void pack_islands_fast(const int64_t start_index,
const Span<UVAABBIsland *> aabbs,
const bool rotate,
const float target_aspect_y,
MutableSpan<uv_phi> r_phis,
rctf *r_extent)
{
if (rotate) {
pack_islands_alpaca_rotate(start_index, aabbs, target_aspect_y, r_phis, r_extent);
}
else {
pack_islands_alpaca_turbo(start_index, aabbs, target_aspect_y, r_phis, r_extent);
}
} }
/** Frits Göbel, 1979. */ /** Frits Göbel, 1979. */
@@ -594,24 +626,12 @@ static void pack_gobel(const Span<UVAABBIsland *> aabbs,
} }
} }
/* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */ /* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */
static void pack_island_optimal_pack(const Span<UVAABBIsland *> aabbs, static void pack_islands_optimal_pack(const Span<UVAABBIsland *> aabbs,
const UVPackIsland_Params &params, const UVPackIsland_Params &params,
const bool all_can_rotate, const bool all_can_rotate,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
*r_max_u = 0.0f;
*r_max_v = 0.0f;
if (!all_can_rotate) {
/* Alpaca will produce an optimal layout when the inputs are uniform squares. */
pack_islands_alpaca_turbo(0, aabbs, params.target_aspect_y, r_phis, r_max_u, r_max_v);
return;
}
/* For many rectangle-only inputs, alpaca_rotate can produce an optimal layout. */
pack_islands_alpaca_rotate(0, aabbs, params.target_aspect_y, r_phis, r_max_u, r_max_v);
if (params.shape_method == ED_UVPACK_SHAPE_AABB) { if (params.shape_method == ED_UVPACK_SHAPE_AABB) {
return; return;
} }
@@ -647,10 +667,10 @@ static void pack_island_optimal_pack(const Span<UVAABBIsland *> aabbs,
int n = a * a + a + 3 + floorf((a - 1) * sqrtf(2.0f)); int n = a * a + a + 3 + floorf((a - 1) * sqrtf(2.0f));
if (island_count_patch == n) { if (island_count_patch == n) {
float max_uv_gobel = large_uv * (a + 1 + sqrtf(0.5f)); float max_uv_gobel = large_uv * (a + 1 + sqrtf(0.5f));
if (max_uv_gobel < std::max(*r_max_u, *r_max_v)) { rctf extent = {0.0f, max_uv_gobel, 0.0f, max_uv_gobel};
if (is_larger(*r_extent, extent, params)) {
*r_extent = extent;
pack_gobel(aabbs, large_uv, a, r_phis); pack_gobel(aabbs, large_uv, a, r_phis);
*r_max_u = max_uv_gobel;
*r_max_v = max_uv_gobel;
} }
return; return;
} }
@@ -659,10 +679,9 @@ static void pack_island_optimal_pack(const Span<UVAABBIsland *> aabbs,
/* Wrapper around #BLI_box_pack_2d. */ /* Wrapper around #BLI_box_pack_2d. */
static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs, static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
const float target_aspect_y, const UVPackIsland_Params &params,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
/* Allocate storage. */ /* Allocate storage. */
BoxPack *box_array = static_cast<BoxPack *>( BoxPack *box_array = static_cast<BoxPack *>(
@@ -671,7 +690,7 @@ static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
/* Prepare for box_pack_2d. */ /* Prepare for box_pack_2d. */
for (const int64_t i : aabbs.index_range()) { for (const int64_t i : aabbs.index_range()) {
BoxPack *box = box_array + i; BoxPack *box = box_array + i;
box->w = aabbs[i]->uv_diagonal.x / target_aspect_y; box->w = aabbs[i]->uv_diagonal.x / params.target_aspect_y;
box->h = aabbs[i]->uv_diagonal.y; box->h = aabbs[i]->uv_diagonal.y;
} }
@@ -680,19 +699,17 @@ static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
float box_max_u = 0.0f; float box_max_u = 0.0f;
float box_max_v = 0.0f; float box_max_v = 0.0f;
BLI_box_pack_2d(box_array, int(aabbs.size()), sort_boxes, &box_max_u, &box_max_v); BLI_box_pack_2d(box_array, int(aabbs.size()), sort_boxes, &box_max_u, &box_max_v);
box_max_u *= target_aspect_y; box_max_u *= params.target_aspect_y;
rctf extent = {0.0f, box_max_u, 0.0f, box_max_v};
if (std::max(box_max_u / target_aspect_y, box_max_v) < if (is_larger(*r_extent, extent, params)) {
std::max(*r_max_u / target_aspect_y, *r_max_v)) *r_extent = extent;
{
*r_max_u = box_max_u;
*r_max_v = box_max_v;
/* Write back box_pack UVs. */ /* Write back box_pack UVs. */
for (const int64_t i : aabbs.index_range()) { for (const int64_t i : aabbs.index_range()) {
BoxPack *box = box_array + i; BoxPack *box = box_array + i;
uv_phi &phi = *(uv_phi *)&r_phis[aabbs[i]->index]; uv_phi &phi = *(uv_phi *)&r_phis[aabbs[i]->index];
phi.rotation = 0.0f; /* #BLI_box_pack_2d never rotates. */ phi.rotation = 0.0f; /* #BLI_box_pack_2d never rotates. */
phi.translation.x = (box->x + box->w * 0.5f) * target_aspect_y; phi.translation.x = (box->x + box->w * 0.5f) * params.target_aspect_y;
phi.translation.y = (box->y + box->h * 0.5f); phi.translation.y = (box->y + box->h * 0.5f);
} }
} }
@@ -1020,8 +1037,7 @@ class UVMinimumEnclosingSquareFinder {
bounds.ymin -= margin_; bounds.ymin -= margin_;
bounds.xmax += margin_; bounds.xmax += margin_;
bounds.ymax += margin_; bounds.ymax += margin_;
const float current_quad = std::max(BLI_rctf_size_x(&bounds) / params_->target_aspect_y, const float current_quad = get_aspect_scaled_extent(bounds, *params_);
BLI_rctf_size_y(&bounds));
if (best_quad > current_quad) { if (best_quad > current_quad) {
best_quad = current_quad; best_quad = current_quad;
best_angle = angle; best_angle = angle;
@@ -1067,8 +1083,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
const float scale, const float scale,
const float margin, const float margin,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
if (island_indices.size() == 0) { if (island_indices.size() == 0) {
return false; /* Nothing to do. */ return false; /* Nothing to do. */
@@ -1088,7 +1103,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
} }
UVMinimumEnclosingSquareFinder square_finder(scale, margin, &params); UVMinimumEnclosingSquareFinder square_finder(scale, margin, &params);
square_finder.best_quad = std::max(*r_max_u / params.target_aspect_y, *r_max_v) * 0.999f; square_finder.best_quad = get_aspect_scaled_extent(*r_extent, params) * 0.999f;
float matrix[2][2]; float matrix[2][2];
@@ -1137,9 +1152,9 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
r_phis[i].translation.x -= square_finder.best_bounds.xmin; r_phis[i].translation.x -= square_finder.best_bounds.xmin;
r_phis[i].translation.y -= square_finder.best_bounds.ymin; r_phis[i].translation.y -= square_finder.best_bounds.ymin;
} }
*r_max_u = BLI_rctf_size_x(&square_finder.best_bounds); r_extent->xmax = BLI_rctf_size_x(&square_finder.best_bounds);
*r_max_v = BLI_rctf_size_y(&square_finder.best_bounds); r_extent->ymax = BLI_rctf_size_y(&square_finder.best_bounds);
return true; /* `r_phis` were modified. */ return true; /* `r_phis` and `r_extent` were modified. */
} }
/** /**
@@ -1157,18 +1172,17 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
* Performance would normally be `O(n^4)`, however the occupancy * Performance would normally be `O(n^4)`, however the occupancy
* bitmap_radix is fixed, which gives a reduced time complexity of `O(n^3)`. * bitmap_radix is fixed, which gives a reduced time complexity of `O(n^3)`.
*/ */
static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices, static int64_t pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
const Span<PackIsland *> islands, const Span<PackIsland *> islands,
const float scale, const float scale,
const float margin, const float margin,
const UVPackIsland_Params &params, const UVPackIsland_Params &params,
MutableSpan<uv_phi> r_phis, MutableSpan<uv_phi> r_phis,
float *r_max_u, rctf *r_extent)
float *r_max_v)
{ {
blender::Array<uv_phi> phis(r_phis.size());
Occupancy occupancy(guess_initial_scale(islands, scale, margin)); Occupancy occupancy(guess_initial_scale(islands, scale, margin));
float max_u = 0.0f; rctf extent = {0.0f, 0.0f, 0.0f, 0.0f};
float max_v = 0.0f;
/* A heuristic to improve final layout efficiency by making an /* A heuristic to improve final layout efficiency by making an
* intermediate call to #rotate_inside_square. */ * intermediate call to #rotate_inside_square. */
@@ -1192,7 +1206,7 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
const int64_t island_index = island_indices[traced_islands]->index; const int64_t island_index = island_indices[traced_islands]->index;
PackIsland *island = islands[island_index]; PackIsland *island = islands[island_index];
const float island_scale = island->can_scale_(params) ? scale : 1.0f; const float island_scale = island->can_scale_(params) ? scale : 1.0f;
occupancy.trace_island(island, r_phis[island_index], island_scale, margin, true); occupancy.trace_island(island, phis[island_index], island_scale, margin, true);
traced_islands++; traced_islands++;
} }
@@ -1201,7 +1215,7 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
uv_phi phi; uv_phi phi;
phi.translation = island->pivot_; phi.translation = island->pivot_;
phi.rotation = 0.0f; phi.rotation = 0.0f;
r_phis[island_indices[i]->index] = phi; phis[island_indices[i]->index] = phi;
i++; i++;
placed_can_rotate = false; placed_can_rotate = false;
continue; continue;
@@ -1262,30 +1276,31 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
} }
/* Place island. */ /* Place island. */
r_phis[island_indices[i]->index] = phi; phis[island_indices[i]->index] = phi;
i++; /* Next island. */ i++; /* Next island. */
if (i == square_milestone && placed_can_rotate) { if (i == square_milestone && placed_can_rotate) {
if (rotate_inside_square(island_indices.take_front(i), if (rotate_inside_square(
islands, island_indices.take_front(i), islands, params, scale, margin, phis, &extent))
params,
scale,
margin,
r_phis,
&max_u,
&max_v))
{ {
scan_line = 0; scan_line = 0;
traced_islands = 0; traced_islands = 0;
occupancy.clear(); occupancy.clear();
continue;
} }
} }
/* Update top-right corner. */ /* Update top-right corner. */
float2 top_right = island->get_diagonal_support(island_scale, phi.rotation, margin) + float2 top_right = island->get_diagonal_support(island_scale, phi.rotation, margin) +
phi.translation; phi.translation;
max_u = std::max(top_right.x, max_u); extent.xmax = std::max(top_right.x, extent.xmax);
max_v = std::max(top_right.y, max_v); extent.ymax = std::max(top_right.y, extent.ymax);
if (!is_larger(*r_extent, extent, params)) {
if (i >= square_milestone) {
return 0; /* Early exit, we already have a better layout. */
}
}
/* Heuristics to reduce size of brute-force search. */ /* Heuristics to reduce size of brute-force search. */
if (i < 128 || (i & 31) == 16) { if (i < 128 || (i & 31) == 16) {
@@ -1296,8 +1311,15 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
} }
} }
*r_max_u = max_u; if (!is_larger(*r_extent, extent, params)) {
*r_max_v = max_v; return 0;
}
*r_extent = extent;
for (const int64_t i : phis.index_range()) {
const int64_t island_index = island_indices[i]->index;
r_phis[island_index] = phis[island_index];
}
return phis.size();
} }
/** /**
@@ -1415,20 +1437,28 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
} }
const int64_t max_box_pack = std::min(alpaca_cutoff, islands.size()); const int64_t max_box_pack = std::min(alpaca_cutoff, islands.size());
float optimal_pack_u = 0.0f; rctf extent = {0.0f, 1e30f, 0.0f, 1e30f};
float optimal_pack_v = 0.0f;
if (all_can_translate) { if (all_can_translate) {
pack_island_optimal_pack(aabbs.as_span().take_front(max_box_pack), pack_islands_fast(0,
params, aabbs.as_span().take_front(max_box_pack),
all_can_rotate, all_can_rotate,
r_phis, params.target_aspect_y,
&optimal_pack_u, r_phis,
&optimal_pack_v); &extent);
}
if (all_can_translate) {
pack_islands_optimal_pack(
aabbs.as_span().take_front(max_box_pack), params, all_can_rotate, r_phis, &extent);
}
/* Call box_pack_2d (slow for large N.) */
if (all_can_translate) {
pack_island_box_pack_2d(aabbs.as_span().take_front(max_box_pack), params, r_phis, &extent);
} }
/* Call xatlas (slow for large N.) */ /* Call xatlas (slow for large N.) */
float max_u = 1e30f;
float max_v = 1e30f;
switch (params.shape_method) { switch (params.shape_method) {
case ED_UVPACK_SHAPE_CONVEX: case ED_UVPACK_SHAPE_CONVEX:
case ED_UVPACK_SHAPE_CONCAVE: case ED_UVPACK_SHAPE_CONCAVE:
@@ -1438,53 +1468,23 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
margin, margin,
params, params,
r_phis, r_phis,
&max_u, &extent);
&max_v);
break; break;
default: default:
break; break;
} }
/* Call box_pack_2d (slow for large N.) */
if (all_can_translate) {
pack_island_box_pack_2d(
aabbs.as_span().take_front(max_box_pack), params.target_aspect_y, r_phis, &max_u, &max_v);
if (std::max(optimal_pack_u / params.target_aspect_y, optimal_pack_v) <
std::max(max_u / params.target_aspect_y, max_v))
{
pack_island_optimal_pack(aabbs.as_span().take_front(max_box_pack),
params,
all_can_rotate,
r_phis,
&max_u,
&max_v);
}
}
/* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */ /* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */
if (all_can_rotate) { if (all_can_rotate) {
rotate_inside_square(aabbs.as_span().take_front(max_box_pack), rotate_inside_square(
islands, aabbs.as_span().take_front(max_box_pack), islands, params, scale, margin, r_phis, &extent);
params,
scale,
margin,
r_phis,
&max_u,
&max_v);
} }
/* Call Alpaca. */ /* Call fast packer for remaining islands. */
if (all_can_rotate) { pack_islands_fast(max_box_pack, aabbs, all_can_rotate, params.target_aspect_y, r_phis, &extent);
pack_islands_alpaca_rotate(
max_box_pack, aabbs, params.target_aspect_y, r_phis, &max_u, &max_v);
}
else {
pack_islands_alpaca_turbo(max_box_pack, aabbs, params.target_aspect_y, r_phis, &max_u, &max_v);
}
return std::max(max_u / params.target_aspect_y, max_v); return get_aspect_scaled_extent(extent, params);
} }
/** Find the optimal scale to pack islands into the unit square. /** Find the optimal scale to pack islands into the unit square.