diff --git a/source/blender/geometry/intern/uv_pack.cc b/source/blender/geometry/intern/uv_pack.cc index 192706e7c87..f6e32659531 100644 --- a/source/blender/geometry/intern/uv_pack.cc +++ b/source/blender/geometry/intern/uv_pack.cc @@ -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 islands, const float target_aspect_y, MutableSpan 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 islands, const float target_aspect_y, MutableSpan 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 aabbs, + const bool rotate, + const float target_aspect_y, + MutableSpan 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 aabbs, } } /* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */ -static void pack_island_optimal_pack(const Span aabbs, - const UVPackIsland_Params ¶ms, - const bool all_can_rotate, - MutableSpan r_phis, - float *r_max_u, - float *r_max_v) +static void pack_islands_optimal_pack(const Span aabbs, + const UVPackIsland_Params ¶ms, + const bool all_can_rotate, + MutableSpan 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 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 aabbs, /* Wrapper around #BLI_box_pack_2d. */ static void pack_island_box_pack_2d(const Span aabbs, - const float target_aspect_y, + const UVPackIsland_Params ¶ms, MutableSpan r_phis, - float *r_max_u, - float *r_max_v) + rctf *r_extent) { /* Allocate storage. */ BoxPack *box_array = static_cast( @@ -671,7 +690,7 @@ static void pack_island_box_pack_2d(const Span 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 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 island_indices, const float scale, const float margin, MutableSpan 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 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 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 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 island_indices, - const Span islands, - const float scale, - const float margin, - const UVPackIsland_Params ¶ms, - MutableSpan r_phis, - float *r_max_u, - float *r_max_v) +static int64_t pack_island_xatlas(const Span island_indices, + const Span islands, + const float scale, + const float margin, + const UVPackIsland_Params ¶ms, + MutableSpan r_phis, + rctf *r_extent) { + blender::Array 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 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 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 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 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 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 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.