UV: improve layout efficiency of uv packing when rotation enabled

Add rotation support to the `xatlas` strategy.
This commit is contained in:
Chris Blackbourn
2023-04-01 13:37:09 +13:00
parent 984da82283
commit 43476e2d71
2 changed files with 133 additions and 49 deletions

View File

@@ -87,8 +87,15 @@ class PackIsland {
void add_polygon(const blender::Span<float2> uvs, MemArena *arena, Heap *heap);
void finalize_geometry(const UVPackIsland_Params &params, MemArena *arena, Heap *heap);
void build_transformation(const float scale, const float angle, float r_matrix[2][2]);
void build_inverse_transformation(const float scale, const float angle, float r_matrix[2][2]);
void build_transformation(const float scale, const float rotation, float r_matrix[2][2]) const;
void build_inverse_transformation(const float scale,
const float rotation,
float r_matrix[2][2]) const;
float2 get_diagonal_support(const float scale, const float rotation, const float margin) const;
float2 get_diagonal_support_d4(const float scale,
const float rotation,
const float margin) const;
/** Center of AABB and inside-or-touching the convex hull. */
float2 pivot_;

View File

@@ -25,6 +25,27 @@
namespace blender::geometry {
/* Store information about an island's placement such as translation, rotation and reflection. */
class uv_phi {
public:
uv_phi();
bool is_valid() const;
float2 translation;
float rotation;
/* bool reflect; */
};
uv_phi::uv_phi() : translation(-1.0f, -1.0f), rotation(0.0f)
{
/* Initialize invalid. */
}
bool uv_phi::is_valid() const
{
return translation.x != -1.0f;
}
void mul_v2_m2_add_v2v2(float r[2], const float mat[2][2], const float a[2], const float b[2])
{
/* Compute `r = mat * (a + b)` with high precision.
@@ -320,10 +341,10 @@ class Occupancy {
const bool write) const;
/* Write or Query an island on the bitmap. */
float trace_island(PackIsland *island,
float trace_island(const PackIsland *island,
const uv_phi phi,
const float scale,
const float margin,
const float2 &uv,
const bool write) const;
int bitmap_radix; /* Width and Height of `bitmap`. */
@@ -439,28 +460,60 @@ float Occupancy::trace_triangle(const float2 &uv0,
return -1.0f; /* Available. */
}
float Occupancy::trace_island(PackIsland *island,
float2 PackIsland::get_diagonal_support_d4(const float scale,
const float rotation,
const float margin) const
{
if (rotation == 0.0f) {
return half_diagonal_ * scale + margin; /* Fast path for common case. */
}
/* TODO: BLI_assert rotation is a "Dihedral Group D4" transform. */
float matrix[2][2];
build_transformation(scale, rotation, matrix);
float diagonal_rotated[2];
mul_v2_m2v2(diagonal_rotated, matrix, half_diagonal_);
return float2(fabsf(diagonal_rotated[0]) + margin, fabsf(diagonal_rotated[1]) + margin);
}
float2 PackIsland::get_diagonal_support(const float scale,
const float rotation,
const float margin) const
{
/* Only "D4" transforms are currently supported. */
return get_diagonal_support_d4(scale, rotation, margin);
}
float Occupancy::trace_island(const PackIsland *island,
const uv_phi phi,
const float scale,
const float margin,
const float2 &uv,
const bool write) const
{
float2 diagonal_support = island->get_diagonal_support(scale, phi.rotation, margin);
if (!write) {
if (uv.x < island->half_diagonal_.x * scale + margin ||
uv.y < island->half_diagonal_.y * scale + margin) {
if (phi.translation.x < diagonal_support.x || phi.translation.y < diagonal_support.y) {
return terminal; /* Occupied. */
}
}
const float2 delta = uv - island->pivot_ * scale;
float matrix[2][2];
island->build_transformation(scale, phi.rotation, matrix);
float2 pivot_transformed;
mul_v2_m2v2(pivot_transformed, matrix, island->pivot_);
float2 delta = phi.translation - pivot_transformed;
uint vert_count = uint(island->triangle_vertices_.size()); /* `uint` is faster than `int`. */
for (uint i = 0; i < vert_count; i += 3) {
uint j = (i + triangle_hint_) % vert_count;
float extent = trace_triangle(delta + island->triangle_vertices_[j] * scale,
delta + island->triangle_vertices_[j + 1] * scale,
delta + island->triangle_vertices_[j + 2] * scale,
margin,
write);
float2 uv0;
float2 uv1;
float2 uv2;
mul_v2_m2v2(uv0, matrix, island->triangle_vertices_[j]);
mul_v2_m2v2(uv1, matrix, island->triangle_vertices_[j + 1]);
mul_v2_m2v2(uv2, matrix, island->triangle_vertices_[j + 2]);
float extent = trace_triangle(uv0 + delta, uv1 + delta, uv2 + delta, margin, write);
if (!write && extent >= 0.0f) {
triangle_hint_ = j;
@@ -470,46 +523,51 @@ float Occupancy::trace_island(PackIsland *island,
return -1.0f; /* Available. */
}
static float2 find_best_fit_for_island(PackIsland *island,
int scan_line,
static uv_phi find_best_fit_for_island(const PackIsland *island,
const int scan_line,
Occupancy &occupancy,
const float scale,
const int angle_90_multiple,
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);
const int scan_line_x = int(scan_line * sqrt_target_aspect_y);
const int scan_line_y = int(scan_line / sqrt_target_aspect_y);
uv_phi phi;
phi.rotation = DEG2RADF(angle_90_multiple * 90);
float matrix[2][2];
island->build_transformation(scale, phi.rotation, matrix);
/* Caution, margin is zero for support_diagonal as we're tracking the top-right corner. */
float2 support_diagonal = island->get_diagonal_support_d4(scale, phi.rotation, 0.0f);
/* Scan using an "Alpaca"-style search, first horizontally using "less-than". */
int t = int(ceilf((2 * half_x_scaled + margin) * occupancy.bitmap_scale_reciprocal));
int t = int(ceilf((2 * support_diagonal.x + margin) * occupancy.bitmap_scale_reciprocal));
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);
phi.translation = float2(t * bitmap_scale, scan_line_y * bitmap_scale) - support_diagonal;
const float extent = occupancy.trace_island(island, phi, scale, margin, false);
if (extent < 0.0f) {
return probe; /* Success. */
return phi; /* Success. */
}
t = t + std::max(1, int(extent));
}
/* Then scan vertically using "less-than-or-equal" */
t = int(ceilf((2 * half_y_scaled + margin) * occupancy.bitmap_scale_reciprocal));
t = int(ceilf((2 * support_diagonal.y + margin) * occupancy.bitmap_scale_reciprocal));
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);
phi.translation = float2(scan_line_x * bitmap_scale, t * bitmap_scale) - support_diagonal;
const float extent = occupancy.trace_island(island, phi, scale, margin, false);
if (extent < 0.0f) {
return probe; /* Success. */
return phi; /* Success. */
}
t = t + std::max(1, int(extent));
}
return float2(-1, -1); /* Unable to find a place to fit. */
return uv_phi(); /* Unable to find a place to fit. */
}
static float guess_initial_scale(const Span<PackIsland *> islands,
@@ -544,7 +602,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,
const UVPackIsland_Params &params,
float *r_max_u,
float *r_max_v)
{
@@ -552,6 +610,7 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
float max_u = 0.0f;
float max_v = 0.0f;
blender::Array<uv_phi> phis(island_indices.size());
int scan_line = 0;
int i = 0;
@@ -563,12 +622,22 @@ 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, target_aspect_y);
uv_phi phi;
if (best.x <= -1.0f) {
int max_90_multiple = params.rotate && (i < 50) ? 4 : 1;
for (int angle_90_multiple = 0; angle_90_multiple < max_90_multiple; angle_90_multiple++) {
phi = find_best_fit_for_island(
island, scan_line, occupancy, scale, angle_90_multiple, margin, params.target_aspect_y);
if (phi.is_valid()) {
break;
}
}
if (!phi.is_valid()) {
/* Unable to find a fit on this scan_line. */
island = nullptr; /* Just mark it as null, we won't use it further. */
if (i < 10) {
scan_line++;
}
@@ -580,8 +649,8 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
* choice is 2. */
scan_line += 2;
}
if (scan_line <
occupancy.bitmap_radix * sqrtf(std::min(target_aspect_y, 1.0f / target_aspect_y))) {
if (scan_line < occupancy.bitmap_radix *
sqrtf(std::min(params.target_aspect_y, 1.0f / params.target_aspect_y))) {
continue; /* Try again on next scan_line. */
}
@@ -591,21 +660,27 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
/* Redraw already placed islands. (Greedy.) */
for (int j = 0; j < i; j++) {
PackIsland *other = islands[island_indices[j]->index];
float2 where = (other->pre_translate + other->pivot_) * scale;
occupancy.trace_island(islands[island_indices[j]->index], scale, margin, where, true);
occupancy.trace_island(islands[island_indices[j]->index], phis[j], scale, margin, true);
}
continue;
}
/* Place island. */
island->angle = 0.0f;
island->pre_translate = best / scale - island->pivot_;
occupancy.trace_island(island, scale, margin, best, true);
phis[i] = phi; /* Place island. */
occupancy.trace_island(island, phi, scale, margin, true);
i++; /* Next island. */
max_u = std::max(best.x + island->half_diagonal_.x * scale + margin, max_u);
max_v = std::max(best.y + island->half_diagonal_.y * scale + margin, max_v);
island->angle = phi.rotation;
float matrix_inverse[2][2];
island->build_inverse_transformation(scale, phi.rotation, matrix_inverse);
mul_v2_m2v2(island->pre_translate, matrix_inverse, phi.translation);
island->pre_translate -= island->pivot_;
float2 support = island->get_diagonal_support(scale, phi.rotation, margin);
float2 top_right = phi.translation + support;
max_u = std::max(top_right.x, max_u);
max_v = std::max(top_right.y, max_v);
if (i < 128 || (i & 31) == 16) {
scan_line = 0; /* Restart completely. */
@@ -874,7 +949,7 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
islands,
scale,
margin,
params.target_aspect_y,
params,
&max_u,
&max_v);
break;
@@ -1089,7 +1164,9 @@ void pack_islands(const Span<PackIsland *> &islands,
/** \} */
void PackIsland::build_transformation(const float scale, const float angle, float (*r_matrix)[2])
void PackIsland::build_transformation(const float scale,
const float angle,
float (*r_matrix)[2]) const
{
const float cos_angle = cosf(angle);
const float sin_angle = sinf(angle);
@@ -1101,7 +1178,7 @@ void PackIsland::build_transformation(const float scale, const float angle, floa
void PackIsland::build_inverse_transformation(const float scale,
const float angle,
float (*r_matrix)[2])
float (*r_matrix)[2]) const
{
/* TODO: Generate inverse transform directly. */
build_transformation(scale, angle, r_matrix);