plugins: improve uv_unwrap
This commit is contained in:
+413
-272
@@ -11,6 +11,7 @@
|
||||
// Cosine of 66 degrees - angle threshold for chart grouping
|
||||
#define UV_ANGLE_THRESHOLD 0.4067f
|
||||
#define UV_PACK_MARGIN 0.001f
|
||||
#define UV_PACK_EPS 1e-6f
|
||||
|
||||
// Position hash map entry for canonical vertex deduplication
|
||||
typedef struct {
|
||||
@@ -26,6 +27,17 @@ typedef struct {
|
||||
bool occupied;
|
||||
} uv_edge_entry_t;
|
||||
|
||||
// 2D point for convex hull / min-area rectangle
|
||||
typedef struct {
|
||||
float u, v;
|
||||
} uv_pt_t;
|
||||
|
||||
// Chart sort key for packing order
|
||||
typedef struct {
|
||||
float key;
|
||||
int id;
|
||||
} uv_sort_t;
|
||||
|
||||
static uint32_t uv_hash_pos(int16_t x, int16_t y, int16_t z) {
|
||||
uint32_t h = (uint32_t)(x + 32768);
|
||||
h = h * 2654435761u ^ (uint32_t)(y + 32768);
|
||||
@@ -37,6 +49,262 @@ static uint32_t uv_hash_edge(int v0, int v1) {
|
||||
return (uint32_t)v0 * 2654435761u ^ (uint32_t)v1 * 2246822519u;
|
||||
}
|
||||
|
||||
static int uv_pt_cmp(const void *a, const void *b) {
|
||||
const uv_pt_t *p = (const uv_pt_t *)a;
|
||||
const uv_pt_t *q = (const uv_pt_t *)b;
|
||||
if (p->u != q->u) {
|
||||
return p->u < q->u ? -1 : 1;
|
||||
}
|
||||
if (p->v != q->v) {
|
||||
return p->v < q->v ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uv_sort_cmp(const void *a, const void *b) {
|
||||
float ka = ((const uv_sort_t *)a)->key;
|
||||
float kb = ((const uv_sort_t *)b)->key;
|
||||
if (ka != kb) {
|
||||
return ka > kb ? -1 : 1; // Descending
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static float uv_cross(uv_pt_t o, uv_pt_t a, uv_pt_t b) {
|
||||
return (a.u - o.u) * (b.v - o.v) - (a.v - o.v) * (b.u - o.u);
|
||||
}
|
||||
|
||||
// Reduce hull candidates for large point sets: keep only per-column v-extremes.
|
||||
// All discarded points are interior in v within their column, so the hull of the
|
||||
// kept points closely matches the true hull. Returns new count.
|
||||
#define UV_HULL_COLS 256
|
||||
static int uv_hull_prefilter(uv_pt_t *pts, int n) {
|
||||
if (n <= UV_HULL_COLS * 2) {
|
||||
return n;
|
||||
}
|
||||
float min_u = FLT_MAX;
|
||||
float max_u = -FLT_MAX;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (pts[i].u < min_u)
|
||||
min_u = pts[i].u;
|
||||
if (pts[i].u > max_u)
|
||||
max_u = pts[i].u;
|
||||
}
|
||||
if (max_u - min_u < 1e-12f) {
|
||||
return n;
|
||||
}
|
||||
float col_scale = (UV_HULL_COLS - 1) / (max_u - min_u);
|
||||
uv_pt_t col_min[UV_HULL_COLS];
|
||||
uv_pt_t col_max[UV_HULL_COLS];
|
||||
bool col_used[UV_HULL_COLS];
|
||||
memset(col_used, 0, sizeof(col_used));
|
||||
for (int i = 0; i < n; i++) {
|
||||
int col = (int)((pts[i].u - min_u) * col_scale);
|
||||
if (!col_used[col]) {
|
||||
col_used[col] = true;
|
||||
col_min[col] = pts[i];
|
||||
col_max[col] = pts[i];
|
||||
}
|
||||
else {
|
||||
if (pts[i].v < col_min[col].v || (pts[i].v == col_min[col].v && pts[i].u < col_min[col].u))
|
||||
col_min[col] = pts[i];
|
||||
if (pts[i].v > col_max[col].v || (pts[i].v == col_max[col].v && pts[i].u > col_max[col].u))
|
||||
col_max[col] = pts[i];
|
||||
}
|
||||
}
|
||||
int k = 0;
|
||||
for (int col = 0; col < UV_HULL_COLS; col++) {
|
||||
if (col_used[col]) {
|
||||
pts[k++] = col_min[col];
|
||||
pts[k++] = col_max[col];
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
// Monotone chain convex hull; sorts pts in place, out must hold 2 * n + 1 points
|
||||
static int uv_convex_hull(uv_pt_t *pts, int n, uv_pt_t *out) {
|
||||
n = uv_hull_prefilter(pts, n);
|
||||
qsort(pts, n, sizeof(uv_pt_t), uv_pt_cmp);
|
||||
int k = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
while (k >= 2 && uv_cross(out[k - 2], out[k - 1], pts[i]) <= 0.0f) {
|
||||
k--;
|
||||
}
|
||||
out[k++] = pts[i];
|
||||
}
|
||||
int lower = k + 1;
|
||||
for (int i = n - 2; i >= 0; i--) {
|
||||
while (k >= lower && uv_cross(out[k - 2], out[k - 1], pts[i]) <= 0.0f) {
|
||||
k--;
|
||||
}
|
||||
out[k++] = pts[i];
|
||||
}
|
||||
return k - 1; // Last point repeats the first
|
||||
}
|
||||
|
||||
// Find rotation (cos, sin) that aligns the hull's minimal bounding rectangle with the axes.
|
||||
// The optimal rectangle has an edge collinear with a hull edge, so sweep hull edges.
|
||||
// Minimizes rectangle area, or the larger side when by_max_dim is set (best for a lone chart,
|
||||
// where the fit scale is limited by the larger dimension).
|
||||
static void uv_min_rect_dir(const uv_pt_t *hull, int hn, bool by_max_dim, float *out_cx, float *out_cy) {
|
||||
float best_area = FLT_MAX;
|
||||
int step = hn > 360 ? hn / 360 : 1;
|
||||
for (int i = -1; i < hn; i += step) {
|
||||
float dx, dy;
|
||||
if (i == -1) {
|
||||
// Identity orientation as baseline candidate
|
||||
dx = 1.0f;
|
||||
dy = 0.0f;
|
||||
}
|
||||
else {
|
||||
uv_pt_t a = hull[i];
|
||||
uv_pt_t b = hull[(i + 1) % hn];
|
||||
dx = b.u - a.u;
|
||||
dy = b.v - a.v;
|
||||
float len = sqrtf(dx * dx + dy * dy);
|
||||
if (len < 1e-12f) {
|
||||
continue;
|
||||
}
|
||||
dx /= len;
|
||||
dy /= len;
|
||||
}
|
||||
float min_d = FLT_MAX;
|
||||
float max_d = -FLT_MAX;
|
||||
float min_p = FLT_MAX;
|
||||
float max_p = -FLT_MAX;
|
||||
for (int j = 0; j < hn; j++) {
|
||||
float d = hull[j].u * dx + hull[j].v * dy;
|
||||
float p = hull[j].v * dx - hull[j].u * dy;
|
||||
if (d < min_d)
|
||||
min_d = d;
|
||||
if (d > max_d)
|
||||
max_d = d;
|
||||
if (p < min_p)
|
||||
min_p = p;
|
||||
if (p > max_p)
|
||||
max_p = p;
|
||||
}
|
||||
float side_d = max_d - min_d;
|
||||
float side_p = max_p - min_p;
|
||||
float area = by_max_dim ? (side_d > side_p ? side_d : side_p) : side_d * side_p;
|
||||
if (area < best_area) {
|
||||
best_area = area;
|
||||
*out_cx = dx;
|
||||
*out_cy = dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the lowest (then leftmost) skyline position where a cw x ch rectangle fits in [0,1]
|
||||
static bool uv_sky_find(const float *sky_x, const float *sky_y, int sky_len, float cw, float ch, float *out_x, float *out_y) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < sky_len; j++) {
|
||||
float x0 = sky_x[j];
|
||||
float x_end = x0 + cw;
|
||||
if (x_end > 1.0f + UV_PACK_EPS) {
|
||||
break; // Segments are sorted by x, later ones only extend further right
|
||||
}
|
||||
// Find max y across skyline segments this rectangle spans
|
||||
float max_y = 0.0f;
|
||||
for (int k = j; k < sky_len; k++) {
|
||||
if (sky_x[k] >= x_end - UV_PACK_EPS) {
|
||||
break;
|
||||
}
|
||||
if (sky_y[k] > max_y) {
|
||||
max_y = sky_y[k];
|
||||
}
|
||||
}
|
||||
if (max_y + ch <= 1.0f + UV_PACK_EPS && (!found || max_y < *out_y)) {
|
||||
*out_x = x0;
|
||||
*out_y = max_y;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// Replace the skyline over [x0, x1) with height y; returns new segment count
|
||||
static int uv_sky_insert(float *sky_x, float *sky_y, int sky_len, float *tmp_x, float *tmp_y, float x0, float x1, float y) {
|
||||
int tmp_len = 0;
|
||||
bool inserted = false;
|
||||
for (int k = 0; k < sky_len; k++) {
|
||||
float seg_x0 = sky_x[k];
|
||||
float seg_x1 = (k + 1 < sky_len) ? sky_x[k + 1] : 1.0f;
|
||||
float seg_y = sky_y[k];
|
||||
if (seg_x1 <= x0 + UV_PACK_EPS || seg_x0 >= x1 - UV_PACK_EPS) {
|
||||
// Segment fully outside the new rectangle
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
else {
|
||||
if (seg_x0 < x0 - UV_PACK_EPS) {
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
if (!inserted) {
|
||||
tmp_x[tmp_len] = x0;
|
||||
tmp_y[tmp_len] = y;
|
||||
tmp_len++;
|
||||
inserted = true;
|
||||
}
|
||||
if (seg_x1 > x1 + UV_PACK_EPS) {
|
||||
tmp_x[tmp_len] = x1;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Merge adjacent segments at the same height
|
||||
sky_len = 0;
|
||||
for (int k = 0; k < tmp_len; k++) {
|
||||
if (sky_len > 0 && fabsf(tmp_y[k] - sky_y[sky_len - 1]) < UV_PACK_EPS) {
|
||||
continue;
|
||||
}
|
||||
sky_x[sky_len] = tmp_x[k];
|
||||
sky_y[sky_len] = tmp_y[k];
|
||||
sky_len++;
|
||||
}
|
||||
return sky_len;
|
||||
}
|
||||
|
||||
// Pack all charts at the given scale; tries both orientations per chart and keeps
|
||||
// the lower placement. Returns false if any chart does not fit in [0,1].
|
||||
static bool uv_pack_run(int chart_count, const int *order, const float *chart_w, const float *chart_h, float scale, float margin, float *sky_x, float *sky_y,
|
||||
float *tmp_x, float *tmp_y, float *off_u, float *off_v, bool *rotated) {
|
||||
int sky_len = 1;
|
||||
sky_x[0] = 0.0f;
|
||||
sky_y[0] = 0.0f;
|
||||
|
||||
for (int i = 0; i < chart_count; i++) {
|
||||
int c = order[i];
|
||||
float cw = chart_w[c] * scale + margin;
|
||||
float ch = chart_h[c] * scale + margin;
|
||||
|
||||
float x0 = 0.0f, y0 = 0.0f, x1 = 0.0f, y1 = 0.0f;
|
||||
bool fit0 = uv_sky_find(sky_x, sky_y, sky_len, cw, ch, &x0, &y0);
|
||||
bool fit1 = cw != ch && uv_sky_find(sky_x, sky_y, sky_len, ch, cw, &x1, &y1);
|
||||
if (!fit0 && !fit1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rot = fit1 && (!fit0 || y1 < y0 || (y1 == y0 && x1 < x0));
|
||||
float px = rot ? x1 : x0;
|
||||
float py = rot ? y1 : y0;
|
||||
float pw = rot ? ch : cw;
|
||||
float ph = rot ? cw : ch;
|
||||
|
||||
off_u[c] = px + margin * 0.5f;
|
||||
off_v[c] = py + margin * 0.5f;
|
||||
rotated[c] = rot;
|
||||
|
||||
sky_len = uv_sky_insert(sky_x, sky_y, sky_len, tmp_x, tmp_y, px, px + pw, py + ph);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
double t = iron_time();
|
||||
|
||||
@@ -270,20 +538,8 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
free(chart_ny);
|
||||
free(chart_nz);
|
||||
|
||||
// Project vertices onto chart planes and compute per-chart bounding boxes
|
||||
float *uv_out = (float *)malloc(sizeof(float) * index_count * 2);
|
||||
float *c_min_u = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *c_min_v = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *c_max_u = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *c_max_v = (float *)malloc(sizeof(float) * chart_count);
|
||||
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
c_min_u[c] = FLT_MAX;
|
||||
c_min_v[c] = FLT_MAX;
|
||||
c_max_u[c] = -FLT_MAX;
|
||||
c_max_v[c] = -FLT_MAX;
|
||||
}
|
||||
|
||||
// Project vertices onto chart planes
|
||||
float *uv_out = (float *)malloc(sizeof(float) * index_count * 2);
|
||||
for (int f = 0; f < face_count; f++) {
|
||||
int c = chart_id[f];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
@@ -291,19 +547,9 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
float px = pa[vi * 3];
|
||||
float py = pa[vi * 3 + 1];
|
||||
float pz = pa[vi * 3 + 2];
|
||||
float u = px * chart_ux[c] + py * chart_uy[c] + pz * chart_uz[c];
|
||||
float v = px * chart_vx[c] + py * chart_vy[c] + pz * chart_vz[c];
|
||||
int idx = (f * 3 + k) * 2;
|
||||
uv_out[idx] = u;
|
||||
uv_out[idx + 1] = v;
|
||||
if (u < c_min_u[c])
|
||||
c_min_u[c] = u;
|
||||
if (v < c_min_v[c])
|
||||
c_min_v[c] = v;
|
||||
if (u > c_max_u[c])
|
||||
c_max_u[c] = u;
|
||||
if (v > c_max_v[c])
|
||||
c_max_v[c] = v;
|
||||
uv_out[idx] = px * chart_ux[c] + py * chart_uy[c] + pz * chart_uz[c];
|
||||
uv_out[idx + 1] = px * chart_vx[c] + py * chart_vy[c] + pz * chart_vz[c];
|
||||
}
|
||||
}
|
||||
free(chart_ux);
|
||||
@@ -313,76 +559,165 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
free(chart_vy);
|
||||
free(chart_vz);
|
||||
|
||||
// Normalize UVs per chart to origin
|
||||
// Equalize texel density: scale each chart so its UV area matches its 3D surface
|
||||
// area, compensating the shrink from planar projection of curved charts
|
||||
float *chart_area3d = (float *)calloc(chart_count, sizeof(float));
|
||||
float *chart_areauv = (float *)calloc(chart_count, sizeof(float));
|
||||
for (int f = 0; f < face_count; f++) {
|
||||
int i0 = indices[f * 3];
|
||||
int i1 = indices[f * 3 + 1];
|
||||
int i2 = indices[f * 3 + 2];
|
||||
float e1x = pa[i1 * 3] - pa[i0 * 3];
|
||||
float e1y = pa[i1 * 3 + 1] - pa[i0 * 3 + 1];
|
||||
float e1z = pa[i1 * 3 + 2] - pa[i0 * 3 + 2];
|
||||
float e2x = pa[i2 * 3] - pa[i0 * 3];
|
||||
float e2y = pa[i2 * 3 + 1] - pa[i0 * 3 + 1];
|
||||
float e2z = pa[i2 * 3 + 2] - pa[i0 * 3 + 2];
|
||||
float cx = e1y * e2z - e1z * e2y;
|
||||
float cy = e1z * e2x - e1x * e2z;
|
||||
float cz = e1x * e2y - e1y * e2x;
|
||||
chart_area3d[chart_id[f]] += 0.5f * sqrtf(cx * cx + cy * cy + cz * cz);
|
||||
|
||||
int b = f * 3 * 2;
|
||||
float u0 = uv_out[b];
|
||||
float v0 = uv_out[b + 1];
|
||||
float du1 = uv_out[b + 2] - u0;
|
||||
float dv1 = uv_out[b + 3] - v0;
|
||||
float du2 = uv_out[b + 4] - u0;
|
||||
float dv2 = uv_out[b + 5] - v0;
|
||||
chart_areauv[chart_id[f]] += 0.5f * fabsf(du1 * dv2 - du2 * dv1);
|
||||
}
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
float s = chart_areauv[c] > 1e-12f ? sqrtf(chart_area3d[c] / chart_areauv[c]) : 1.0f;
|
||||
// Projection only shrinks, so s >= 1 up to noise; cap pathological slivers
|
||||
if (s < 0.5f)
|
||||
s = 0.5f;
|
||||
if (s > 4.0f)
|
||||
s = 4.0f;
|
||||
chart_area3d[c] = s; // Reuse as per-chart scale
|
||||
}
|
||||
for (int f = 0; f < face_count; f++) {
|
||||
float s = chart_area3d[chart_id[f]];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
int idx = (f * 3 + k) * 2;
|
||||
uv_out[idx] *= s;
|
||||
uv_out[idx + 1] *= s;
|
||||
}
|
||||
}
|
||||
free(chart_area3d);
|
||||
free(chart_areauv);
|
||||
|
||||
// Group face corners by chart
|
||||
int *c_start = (int *)calloc(chart_count + 1, sizeof(int));
|
||||
for (int f = 0; f < face_count; f++) {
|
||||
c_start[chart_id[f] + 1] += 3;
|
||||
}
|
||||
int max_corners = 0;
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
if (c_start[c + 1] > max_corners) {
|
||||
max_corners = c_start[c + 1];
|
||||
}
|
||||
c_start[c + 1] += c_start[c];
|
||||
}
|
||||
int *c_corner = (int *)malloc(sizeof(int) * index_count);
|
||||
int *c_fill = (int *)malloc(sizeof(int) * chart_count);
|
||||
memcpy(c_fill, c_start, sizeof(int) * chart_count);
|
||||
for (int f = 0; f < face_count; f++) {
|
||||
int c = chart_id[f];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
int idx = (f * 3 + k) * 2;
|
||||
uv_out[idx] -= c_min_u[c];
|
||||
uv_out[idx + 1] -= c_min_v[c];
|
||||
c_corner[c_fill[c]++] = f * 3 + k;
|
||||
}
|
||||
}
|
||||
free(c_fill);
|
||||
|
||||
// Compute chart sizes and total area for scaling
|
||||
float *chart_w = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *chart_h = (float *)malloc(sizeof(float) * chart_count);
|
||||
float total_area = 0.0f;
|
||||
// Rotate each chart to its minimal-area bounding rectangle, normalize to origin
|
||||
// and compute chart sizes and total area for scaling
|
||||
uv_pt_t *pts = (uv_pt_t *)malloc(sizeof(uv_pt_t) * max_corners);
|
||||
uv_pt_t *hull = (uv_pt_t *)malloc(sizeof(uv_pt_t) * (max_corners * 2 + 1));
|
||||
float *chart_w = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *chart_h = (float *)malloc(sizeof(float) * chart_count);
|
||||
float total_area = 0.0f;
|
||||
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
chart_w[c] = c_max_u[c] - c_min_u[c];
|
||||
chart_h[c] = c_max_v[c] - c_min_v[c];
|
||||
int m = c_start[c + 1] - c_start[c];
|
||||
float cx = 1.0f;
|
||||
float cy = 0.0f;
|
||||
if (m >= 3) {
|
||||
for (int i = 0; i < m; i++) {
|
||||
int idx = c_corner[c_start[c] + i] * 2;
|
||||
pts[i].u = uv_out[idx];
|
||||
pts[i].v = uv_out[idx + 1];
|
||||
}
|
||||
int hn = uv_convex_hull(pts, m, hull);
|
||||
if (hn >= 3) {
|
||||
uv_min_rect_dir(hull, hn, chart_count == 1, &cx, &cy);
|
||||
}
|
||||
}
|
||||
|
||||
float min_u = FLT_MAX;
|
||||
float min_v = FLT_MAX;
|
||||
float max_u = -FLT_MAX;
|
||||
float max_v = -FLT_MAX;
|
||||
for (int i = 0; i < m; i++) {
|
||||
int idx = c_corner[c_start[c] + i] * 2;
|
||||
float u = uv_out[idx];
|
||||
float v = uv_out[idx + 1];
|
||||
float ru = u * cx + v * cy;
|
||||
float rv = v * cx - u * cy;
|
||||
uv_out[idx] = ru;
|
||||
uv_out[idx + 1] = rv;
|
||||
if (ru < min_u)
|
||||
min_u = ru;
|
||||
if (rv < min_v)
|
||||
min_v = rv;
|
||||
if (ru > max_u)
|
||||
max_u = ru;
|
||||
if (rv > max_v)
|
||||
max_v = rv;
|
||||
}
|
||||
for (int i = 0; i < m; i++) {
|
||||
int idx = c_corner[c_start[c] + i] * 2;
|
||||
uv_out[idx] -= min_u;
|
||||
uv_out[idx + 1] -= min_v;
|
||||
}
|
||||
|
||||
chart_w[c] = max_u - min_u;
|
||||
chart_h[c] = max_v - min_v;
|
||||
if (chart_w[c] < 1e-10f)
|
||||
chart_w[c] = 1e-6f;
|
||||
if (chart_h[c] < 1e-10f)
|
||||
chart_h[c] = 1e-6f;
|
||||
total_area += chart_w[c] * chart_h[c];
|
||||
}
|
||||
free(c_min_u);
|
||||
free(c_min_v);
|
||||
free(c_max_u);
|
||||
free(c_max_v);
|
||||
free(pts);
|
||||
free(hull);
|
||||
free(c_corner);
|
||||
free(c_start);
|
||||
|
||||
// Sort charts by area (descending) for packing
|
||||
// Sort charts by largest dimension (descending) for packing
|
||||
uv_sort_t *chart_sort = (uv_sort_t *)malloc(sizeof(uv_sort_t) * chart_count);
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
chart_sort[c].key = chart_w[c] > chart_h[c] ? chart_w[c] : chart_h[c];
|
||||
chart_sort[c].id = c;
|
||||
}
|
||||
qsort(chart_sort, chart_count, sizeof(uv_sort_t), uv_sort_cmp);
|
||||
int *chart_order = (int *)malloc(sizeof(int) * chart_count);
|
||||
for (int i = 0; i < chart_count; i++) {
|
||||
chart_order[i] = i;
|
||||
}
|
||||
for (int i = 1; i < chart_count; i++) {
|
||||
int key = chart_order[i];
|
||||
float ka = chart_w[key] * chart_h[key];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && chart_w[chart_order[j]] * chart_h[chart_order[j]] < ka) {
|
||||
chart_order[j + 1] = chart_order[j];
|
||||
j--;
|
||||
}
|
||||
chart_order[j + 1] = key;
|
||||
}
|
||||
|
||||
// Determine per-chart rotation: if width > height, rotate 90 degrees for tighter packing
|
||||
bool *chart_rotated = (bool *)calloc(chart_count, sizeof(bool));
|
||||
float *pack_w = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *pack_h = (float *)malloc(sizeof(float) * chart_count);
|
||||
for (int c = 0; c < chart_count; c++) {
|
||||
if (chart_w[c] > chart_h[c]) {
|
||||
chart_rotated[c] = true;
|
||||
pack_w[c] = chart_h[c];
|
||||
pack_h[c] = chart_w[c];
|
||||
}
|
||||
else {
|
||||
pack_w[c] = chart_w[c];
|
||||
pack_h[c] = chart_h[c];
|
||||
}
|
||||
chart_order[i] = chart_sort[i].id;
|
||||
}
|
||||
free(chart_sort);
|
||||
|
||||
// Skyline packing with binary search for optimal scale
|
||||
float *chart_off_u = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *chart_off_v = (float *)malloc(sizeof(float) * chart_count);
|
||||
float *chart_off_u = (float *)calloc(chart_count, sizeof(float));
|
||||
float *chart_off_v = (float *)calloc(chart_count, sizeof(float));
|
||||
bool *chart_rotated = (bool *)calloc(chart_count, sizeof(bool));
|
||||
|
||||
// Skyline: array of (x, y) pairs representing the top edge of placed islands
|
||||
int sky_cap = chart_count + 1;
|
||||
int sky_cap = chart_count * 2 + 4;
|
||||
float *sky_x = (float *)malloc(sizeof(float) * sky_cap);
|
||||
float *sky_y = (float *)malloc(sizeof(float) * sky_cap);
|
||||
int sky_len;
|
||||
float *tmp_x = (float *)malloc(sizeof(float) * sky_cap);
|
||||
float *tmp_y = (float *)malloc(sizeof(float) * sky_cap);
|
||||
|
||||
float margin = UV_PACK_MARGIN;
|
||||
float scale_lo = 0.0f;
|
||||
@@ -390,118 +725,9 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
float scale = 0.0f;
|
||||
|
||||
// Binary search: find largest scale where all islands fit in [0,1]
|
||||
for (int iter = 0; iter < 40; iter++) {
|
||||
for (int iter = 0; iter < 24; iter++) {
|
||||
float try_scale = (scale_lo + scale_hi) * 0.5f;
|
||||
|
||||
// Reset skyline
|
||||
sky_len = 1;
|
||||
sky_x[0] = 0.0f;
|
||||
sky_y[0] = 0.0f;
|
||||
|
||||
bool fits = true;
|
||||
for (int i = 0; i < chart_count; i++) {
|
||||
int c = chart_order[i];
|
||||
float cw = pack_w[c] * try_scale + margin;
|
||||
float ch = pack_h[c] * try_scale + margin;
|
||||
|
||||
// Find best skyline position (lowest y where island fits)
|
||||
float best_x = 0.0f;
|
||||
float best_y = FLT_MAX;
|
||||
int best_j = -1;
|
||||
|
||||
for (int j = 0; j < sky_len; j++) {
|
||||
float x0 = sky_x[j];
|
||||
float x_end = sky_x[j] + cw;
|
||||
if (x_end > 1.0f + 1e-6f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find max y across skyline segments this island spans
|
||||
float max_y = 0.0f;
|
||||
for (int k = j; k < sky_len; k++) {
|
||||
float seg_end = (k + 1 < sky_len) ? sky_x[k + 1] : 1.0f;
|
||||
if (sky_x[k] >= x_end - 1e-6f) {
|
||||
break;
|
||||
}
|
||||
if (sky_y[k] > max_y) {
|
||||
max_y = sky_y[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (max_y + ch <= 1.0f + 1e-6f && max_y < best_y) {
|
||||
best_y = max_y;
|
||||
best_x = x0;
|
||||
best_j = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_j == -1) {
|
||||
fits = false;
|
||||
break;
|
||||
}
|
||||
|
||||
chart_off_u[c] = best_x + margin * 0.5f;
|
||||
chart_off_v[c] = best_y + margin * 0.5f;
|
||||
|
||||
// Update skyline: insert new segment for this island
|
||||
float new_x0 = best_x;
|
||||
float new_x1 = best_x + cw;
|
||||
float new_y = best_y + ch;
|
||||
|
||||
// Collect segments that are NOT fully covered by the new island
|
||||
float tmp_x[1024];
|
||||
float tmp_y[1024];
|
||||
int tmp_len = 0;
|
||||
|
||||
for (int k = 0; k < sky_len; k++) {
|
||||
float seg_x0 = sky_x[k];
|
||||
float seg_x1 = (k + 1 < sky_len) ? sky_x[k + 1] : 1.0f;
|
||||
float seg_y = sky_y[k];
|
||||
|
||||
if (seg_x1 <= new_x0 + 1e-6f || seg_x0 >= new_x1 - 1e-6f) {
|
||||
// Segment fully outside new island
|
||||
if (tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Segment overlaps with new island
|
||||
if (seg_x0 < new_x0 - 1e-6f && tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
// Insert the new island segment at its left edge
|
||||
if (tmp_len == 0 || tmp_x[tmp_len - 1] < new_x0 - 1e-6f || tmp_y[tmp_len - 1] != new_y) {
|
||||
if (tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = new_x0;
|
||||
tmp_y[tmp_len] = new_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
if (seg_x1 > new_x1 + 1e-6f && tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = new_x1;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and copy back
|
||||
sky_len = 0;
|
||||
for (int k = 0; k < tmp_len && sky_len < sky_cap; k++) {
|
||||
if (sky_len > 0 && fabsf(tmp_y[k] - sky_y[sky_len - 1]) < 1e-6f) {
|
||||
continue; // Merge segments at same height
|
||||
}
|
||||
sky_x[sky_len] = tmp_x[k];
|
||||
sky_y[sky_len] = tmp_y[k];
|
||||
sky_len++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fits) {
|
||||
if (uv_pack_run(chart_count, chart_order, chart_w, chart_h, try_scale, margin, sky_x, sky_y, tmp_x, tmp_y, chart_off_u, chart_off_v, chart_rotated)) {
|
||||
scale = try_scale;
|
||||
scale_lo = try_scale;
|
||||
}
|
||||
@@ -511,96 +737,13 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
}
|
||||
|
||||
// Final pass with best scale to get definitive offsets
|
||||
sky_len = 1;
|
||||
sky_x[0] = 0.0f;
|
||||
sky_y[0] = 0.0f;
|
||||
|
||||
for (int i = 0; i < chart_count; i++) {
|
||||
int c = chart_order[i];
|
||||
float cw = pack_w[c] * scale + margin;
|
||||
float ch = pack_h[c] * scale + margin;
|
||||
|
||||
float best_x = 0.0f;
|
||||
float best_y = FLT_MAX;
|
||||
int best_j = -1;
|
||||
|
||||
for (int j = 0; j < sky_len; j++) {
|
||||
float x_end = sky_x[j] + cw;
|
||||
if (x_end > 1.0f + 1e-6f) {
|
||||
continue;
|
||||
}
|
||||
float max_y = 0.0f;
|
||||
for (int k = j; k < sky_len; k++) {
|
||||
if (sky_x[k] >= x_end - 1e-6f) {
|
||||
break;
|
||||
}
|
||||
if (sky_y[k] > max_y) {
|
||||
max_y = sky_y[k];
|
||||
}
|
||||
}
|
||||
if (max_y + ch <= 1.0f + 1e-6f && max_y < best_y) {
|
||||
best_y = max_y;
|
||||
best_x = sky_x[j];
|
||||
best_j = j;
|
||||
}
|
||||
}
|
||||
|
||||
chart_off_u[c] = best_x + margin * 0.5f;
|
||||
chart_off_v[c] = best_y + margin * 0.5f;
|
||||
|
||||
float new_x0 = best_x;
|
||||
float new_x1 = best_x + cw;
|
||||
float new_y = best_y + ch;
|
||||
|
||||
float tmp_x[1024];
|
||||
float tmp_y[1024];
|
||||
int tmp_len = 0;
|
||||
|
||||
for (int k = 0; k < sky_len; k++) {
|
||||
float seg_x0 = sky_x[k];
|
||||
float seg_x1 = (k + 1 < sky_len) ? sky_x[k + 1] : 1.0f;
|
||||
float seg_y = sky_y[k];
|
||||
|
||||
if (seg_x1 <= new_x0 + 1e-6f || seg_x0 >= new_x1 - 1e-6f) {
|
||||
if (tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (seg_x0 < new_x0 - 1e-6f && tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = seg_x0;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
if (tmp_len == 0 || tmp_x[tmp_len - 1] < new_x0 - 1e-6f || tmp_y[tmp_len - 1] != new_y) {
|
||||
if (tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = new_x0;
|
||||
tmp_y[tmp_len] = new_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
if (seg_x1 > new_x1 + 1e-6f && tmp_len < 1024) {
|
||||
tmp_x[tmp_len] = new_x1;
|
||||
tmp_y[tmp_len] = seg_y;
|
||||
tmp_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sky_len = 0;
|
||||
for (int k = 0; k < tmp_len && sky_len < sky_cap; k++) {
|
||||
if (sky_len > 0 && fabsf(tmp_y[k] - sky_y[sky_len - 1]) < 1e-6f) {
|
||||
continue;
|
||||
}
|
||||
sky_x[sky_len] = tmp_x[k];
|
||||
sky_y[sky_len] = tmp_y[k];
|
||||
sky_len++;
|
||||
}
|
||||
if (scale > 0.0f) {
|
||||
uv_pack_run(chart_count, chart_order, chart_w, chart_h, scale, margin, sky_x, sky_y, tmp_x, tmp_y, chart_off_u, chart_off_v, chart_rotated);
|
||||
}
|
||||
free(sky_x);
|
||||
free(sky_y);
|
||||
free(tmp_x);
|
||||
free(tmp_y);
|
||||
free(chart_order);
|
||||
|
||||
// Apply packing offsets, scale, and rotation to all UVs
|
||||
@@ -624,8 +767,6 @@ void proc_uv_unwrap(raw_mesh_t *mesh) {
|
||||
free(chart_off_u);
|
||||
free(chart_off_v);
|
||||
free(chart_rotated);
|
||||
free(pack_w);
|
||||
free(pack_h);
|
||||
free(chart_w);
|
||||
free(chart_h);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user