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:
@@ -89,6 +89,24 @@ static float dist_signed_squared_to_edge(float2 probe, float2 uva, float2 uvb)
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
return get_aspect_scaled_extent(b, params) < get_aspect_scaled_extent(a, params);
|
||||
}
|
||||
|
||||
PackIsland::PackIsland()
|
||||
{
|
||||
/* 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 float target_aspect_y,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
rctf *r_extent)
|
||||
{
|
||||
/* Exclude an initial AABB near the origin. */
|
||||
float next_u1 = *r_max_u;
|
||||
float next_v1 = *r_max_v;
|
||||
float next_u1 = r_extent->xmax;
|
||||
float next_v1 = r_extent->ymax;
|
||||
bool zigzag = next_u1 < next_v1 * target_aspect_y; /* Horizontal or Vertical strip? */
|
||||
|
||||
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. */
|
||||
*r_max_u = next_u1;
|
||||
*r_max_v = next_v1;
|
||||
/* Write back extent. */
|
||||
*r_extent = {0.0f, next_u1, 0.0f, next_v1};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -457,12 +473,11 @@ static void pack_islands_alpaca_rotate(const int64_t start_index,
|
||||
const Span<UVAABBIsland *> islands,
|
||||
const float target_aspect_y,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
rctf *r_extent)
|
||||
{
|
||||
/* Exclude an initial AABB near the origin. */
|
||||
float next_u1 = *r_max_u;
|
||||
float next_v1 = *r_max_v;
|
||||
float next_u1 = r_extent->xmax;
|
||||
float next_v1 = r_extent->ymax;
|
||||
bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */
|
||||
|
||||
/* 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. */
|
||||
*r_max_u = next_u1;
|
||||
*r_max_v = next_v1;
|
||||
*r_extent = {0.0f, next_u1, 0.0f, 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. */
|
||||
@@ -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. */
|
||||
static void pack_island_optimal_pack(const Span<UVAABBIsland *> aabbs,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
const bool all_can_rotate,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
static void pack_islands_optimal_pack(const Span<UVAABBIsland *> aabbs,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
const bool all_can_rotate,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
rctf *r_extent)
|
||||
{
|
||||
*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) {
|
||||
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));
|
||||
if (island_count_patch == n) {
|
||||
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);
|
||||
*r_max_u = max_uv_gobel;
|
||||
*r_max_v = max_uv_gobel;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -659,10 +679,9 @@ static void pack_island_optimal_pack(const Span<UVAABBIsland *> aabbs,
|
||||
|
||||
/* Wrapper around #BLI_box_pack_2d. */
|
||||
static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
|
||||
const float target_aspect_y,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
rctf *r_extent)
|
||||
{
|
||||
/* Allocate storage. */
|
||||
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. */
|
||||
for (const int64_t i : aabbs.index_range()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -680,19 +699,17 @@ static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
|
||||
float box_max_u = 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);
|
||||
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) <
|
||||
std::max(*r_max_u / target_aspect_y, *r_max_v))
|
||||
{
|
||||
*r_max_u = box_max_u;
|
||||
*r_max_v = box_max_v;
|
||||
if (is_larger(*r_extent, extent, params)) {
|
||||
*r_extent = extent;
|
||||
/* Write back box_pack UVs. */
|
||||
for (const int64_t i : aabbs.index_range()) {
|
||||
BoxPack *box = box_array + i;
|
||||
uv_phi &phi = *(uv_phi *)&r_phis[aabbs[i]->index];
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1020,8 +1037,7 @@ class UVMinimumEnclosingSquareFinder {
|
||||
bounds.ymin -= margin_;
|
||||
bounds.xmax += margin_;
|
||||
bounds.ymax += margin_;
|
||||
const float current_quad = std::max(BLI_rctf_size_x(&bounds) / params_->target_aspect_y,
|
||||
BLI_rctf_size_y(&bounds));
|
||||
const float current_quad = get_aspect_scaled_extent(bounds, *params_);
|
||||
if (best_quad > current_quad) {
|
||||
best_quad = current_quad;
|
||||
best_angle = angle;
|
||||
@@ -1067,8 +1083,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
|
||||
const float scale,
|
||||
const float margin,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
rctf *r_extent)
|
||||
{
|
||||
if (island_indices.size() == 0) {
|
||||
return false; /* Nothing to do. */
|
||||
@@ -1088,7 +1103,7 @@ static bool rotate_inside_square(const Span<UVAABBIsland *> island_indices,
|
||||
}
|
||||
|
||||
UVMinimumEnclosingSquareFinder square_finder(scale, margin, ¶ms);
|
||||
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];
|
||||
|
||||
@@ -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.y -= square_finder.best_bounds.ymin;
|
||||
}
|
||||
*r_max_u = BLI_rctf_size_x(&square_finder.best_bounds);
|
||||
*r_max_v = BLI_rctf_size_y(&square_finder.best_bounds);
|
||||
return true; /* `r_phis` were modified. */
|
||||
r_extent->xmax = BLI_rctf_size_x(&square_finder.best_bounds);
|
||||
r_extent->ymax = BLI_rctf_size_y(&square_finder.best_bounds);
|
||||
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
|
||||
* bitmap_radix is fixed, which gives a reduced time complexity of `O(n^3)`.
|
||||
*/
|
||||
static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
const Span<PackIsland *> islands,
|
||||
const float scale,
|
||||
const float margin,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
static int64_t pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
const Span<PackIsland *> islands,
|
||||
const float scale,
|
||||
const float margin,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
MutableSpan<uv_phi> r_phis,
|
||||
rctf *r_extent)
|
||||
{
|
||||
blender::Array<uv_phi> phis(r_phis.size());
|
||||
Occupancy occupancy(guess_initial_scale(islands, scale, margin));
|
||||
float max_u = 0.0f;
|
||||
float max_v = 0.0f;
|
||||
rctf extent = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
/* A heuristic to improve final layout efficiency by making an
|
||||
* 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;
|
||||
PackIsland *island = islands[island_index];
|
||||
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++;
|
||||
}
|
||||
|
||||
@@ -1201,7 +1215,7 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
uv_phi phi;
|
||||
phi.translation = island->pivot_;
|
||||
phi.rotation = 0.0f;
|
||||
r_phis[island_indices[i]->index] = phi;
|
||||
phis[island_indices[i]->index] = phi;
|
||||
i++;
|
||||
placed_can_rotate = false;
|
||||
continue;
|
||||
@@ -1262,30 +1276,31 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
}
|
||||
|
||||
/* Place island. */
|
||||
r_phis[island_indices[i]->index] = phi;
|
||||
phis[island_indices[i]->index] = phi;
|
||||
i++; /* Next island. */
|
||||
|
||||
if (i == square_milestone && placed_can_rotate) {
|
||||
if (rotate_inside_square(island_indices.take_front(i),
|
||||
islands,
|
||||
params,
|
||||
scale,
|
||||
margin,
|
||||
r_phis,
|
||||
&max_u,
|
||||
&max_v))
|
||||
if (rotate_inside_square(
|
||||
island_indices.take_front(i), islands, params, scale, margin, phis, &extent))
|
||||
{
|
||||
scan_line = 0;
|
||||
traced_islands = 0;
|
||||
occupancy.clear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update top-right corner. */
|
||||
float2 top_right = island->get_diagonal_support(island_scale, phi.rotation, margin) +
|
||||
phi.translation;
|
||||
max_u = std::max(top_right.x, max_u);
|
||||
max_v = std::max(top_right.y, max_v);
|
||||
extent.xmax = std::max(top_right.x, extent.xmax);
|
||||
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. */
|
||||
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;
|
||||
*r_max_v = max_v;
|
||||
if (!is_larger(*r_extent, extent, params)) {
|
||||
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());
|
||||
|
||||
float optimal_pack_u = 0.0f;
|
||||
float optimal_pack_v = 0.0f;
|
||||
rctf extent = {0.0f, 1e30f, 0.0f, 1e30f};
|
||||
|
||||
if (all_can_translate) {
|
||||
pack_island_optimal_pack(aabbs.as_span().take_front(max_box_pack),
|
||||
params,
|
||||
all_can_rotate,
|
||||
r_phis,
|
||||
&optimal_pack_u,
|
||||
&optimal_pack_v);
|
||||
pack_islands_fast(0,
|
||||
aabbs.as_span().take_front(max_box_pack),
|
||||
all_can_rotate,
|
||||
params.target_aspect_y,
|
||||
r_phis,
|
||||
&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.) */
|
||||
float max_u = 1e30f;
|
||||
float max_v = 1e30f;
|
||||
switch (params.shape_method) {
|
||||
case ED_UVPACK_SHAPE_CONVEX:
|
||||
case ED_UVPACK_SHAPE_CONCAVE:
|
||||
@@ -1438,53 +1468,23 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
|
||||
margin,
|
||||
params,
|
||||
r_phis,
|
||||
&max_u,
|
||||
&max_v);
|
||||
&extent);
|
||||
break;
|
||||
default:
|
||||
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. */
|
||||
|
||||
if (all_can_rotate) {
|
||||
rotate_inside_square(aabbs.as_span().take_front(max_box_pack),
|
||||
islands,
|
||||
params,
|
||||
scale,
|
||||
margin,
|
||||
r_phis,
|
||||
&max_u,
|
||||
&max_v);
|
||||
rotate_inside_square(
|
||||
aabbs.as_span().take_front(max_box_pack), islands, params, scale, margin, r_phis, &extent);
|
||||
}
|
||||
|
||||
/* Call Alpaca. */
|
||||
if (all_can_rotate) {
|
||||
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);
|
||||
}
|
||||
/* Call fast packer for remaining islands. */
|
||||
pack_islands_fast(max_box_pack, aabbs, all_can_rotate, params.target_aspect_y, r_phis, &extent);
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user