Make uv unwrap faster

This commit is contained in:
luboslenco
2025-12-15 19:52:58 +01:00
parent 315835e533
commit 05dba448d9
9 changed files with 705 additions and 10455 deletions
+694
View File
@@ -0,0 +1,694 @@
#include "uv_unwrap.h"
#include <float.h>
#include <iron_system.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
float x, y, z;
} vec3_t;
typedef struct {
float x, y;
} vec2_t;
static inline vec3_t v3_add(vec3_t a, vec3_t b) {
return (vec3_t){a.x + b.x, a.y + b.y, a.z + b.z};
}
static inline vec3_t v3_sub(vec3_t a, vec3_t b) {
return (vec3_t){a.x - b.x, a.y - b.y, a.z - b.z};
}
static inline vec3_t v3_mul(vec3_t a, float s) {
return (vec3_t){a.x * s, a.y * s, a.z * s};
}
static inline float v3_dot(vec3_t a, vec3_t b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static inline float v3_len(vec3_t a) {
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
}
static inline vec3_t v3_cross(vec3_t a, vec3_t b) {
return (vec3_t){a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
static inline vec3_t v3_norm(vec3_t a) {
float l = v3_len(a);
if (l < 1e-6f)
return (vec3_t){0, 0, 1};
return v3_mul(a, 1.0f / l);
}
#define sb__raw(a) ((int *)(a) - 2)
#define sb__len(a) (sb__raw(a)[0])
#define sb__cap_val(a) (sb__raw(a)[1])
#define sb_count(a) ((a) ? sb__len(a) : 0)
#define sb_cap(a) ((a) ? sb__cap_val(a) : 0)
#define sb_free(a) ((a) ? free(sb__raw(a)), 0 : 0)
#define sb_push(a, v) (sb__grow((void **)&(a), sizeof(*(a))) ? ((a)[sb__len(a)++] = (v), 0) : 0)
static int sb__grow(void **ptr, size_t item_size) {
int *arr = *ptr ? sb__raw(*ptr) : NULL;
if (arr) {
int len = arr[0];
int cap = arr[1];
if (len < cap)
return 1;
int new_cap = cap * 2;
int *new_arr = (int *)realloc(arr, sizeof(int) * 2 + item_size * new_cap);
if (!new_arr)
return 0;
new_arr[1] = new_cap;
*ptr = new_arr + 2;
}
else {
int new_cap = 16;
int *new_arr = (int *)malloc(sizeof(int) * 2 + item_size * new_cap);
if (!new_arr)
return 0;
new_arr[0] = 0;
new_arr[1] = new_cap;
*ptr = new_arr + 2;
}
return 1;
}
typedef struct {
int *faces; // SB
vec2_t *uvs; // SB
vec2_t min, max;
int p_x, p_y;
} Island;
typedef struct {
float *pa;
int index;
} sort_vert_t;
typedef struct {
int u1, u2, face;
} edge_ref_t;
typedef struct {
int spatial_idx;
int local_idx;
} relax_vert_t;
typedef struct {
int u, v;
} relax_edge_t;
typedef struct pack_node {
struct pack_node *child[2];
int x, y, w, h;
int occupied;
} pack_node_t;
static int compare_verts(const void *a, const void *b) {
const sort_vert_t *va = (const sort_vert_t *)a;
const sort_vert_t *vb = (const sort_vert_t *)b;
float eps = 0.0001f;
if (fabsf(va->pa[0] - vb->pa[0]) > eps)
return (va->pa[0] > vb->pa[0]) ? 1 : -1;
if (fabsf(va->pa[1] - vb->pa[1]) > eps)
return (va->pa[1] > vb->pa[1]) ? 1 : -1;
if (fabsf(va->pa[2] - vb->pa[2]) > eps)
return (va->pa[2] > vb->pa[2]) ? 1 : -1;
return 0;
}
static int compare_edges(const void *a, const void *b) {
const edge_ref_t *ea = (const edge_ref_t *)a;
const edge_ref_t *eb = (const edge_ref_t *)b;
if (ea->u1 != eb->u1)
return ea->u1 - eb->u1;
return ea->u2 - eb->u2;
}
static int compare_relax_verts(const void *a, const void *b) {
const relax_vert_t *va = (const relax_vert_t *)a;
const relax_vert_t *vb = (const relax_vert_t *)b;
return va->spatial_idx - vb->spatial_idx;
}
static int compare_relax_edges(const void *a, const void *b) {
const relax_edge_t *ea = (const relax_edge_t *)a;
const relax_edge_t *eb = (const relax_edge_t *)b;
if (ea->u != eb->u)
return ea->u - eb->u;
return ea->v - eb->v;
}
static int compare_islands_area(const void *a, const void *b) {
const Island *ia = (const Island *)a;
const Island *ib = (const Island *)b;
float area_a = (ia->max.x - ia->min.x) * (ia->max.y - ia->min.y);
float area_b = (ib->max.x - ib->min.x) * (ib->max.y - ib->min.y);
return (area_a < area_b) ? 1 : -1;
}
static pack_node_t *pack_insert(pack_node_t *node, int w, int h) {
if (!node)
return NULL;
if (w > node->w || h > node->h)
return NULL;
if (!node->child[0]) {
if (node->occupied)
return NULL;
// Perfect fit or fits within node
// If perfectly same size, use it
if (w == node->w && h == node->h) {
node->occupied = 1;
return node;
}
// Split the node to create a perfect fit for the requested size
node->child[0] = (pack_node_t *)calloc(1, sizeof(pack_node_t));
node->child[1] = (pack_node_t *)calloc(1, sizeof(pack_node_t));
// Decide split axis based on maximizing the remaining rectangle area
int dw = node->w - w;
int dh = node->h - h;
if (dw > dh) {
// Split vertically: Child 0 is Left (fit w), Child 1 is Right (remainder)
node->child[0]->x = node->x;
node->child[0]->y = node->y;
node->child[0]->w = w;
node->child[0]->h = node->h; // Keep full height
node->child[1]->x = node->x + w;
node->child[1]->y = node->y;
node->child[1]->w = dw;
node->child[1]->h = node->h;
}
else {
// Split horizontally: Child 0 is Top (fit h), Child 1 is Bottom (remainder)
node->child[0]->x = node->x;
node->child[0]->y = node->y;
node->child[0]->w = node->w; // Keep full width
node->child[0]->h = h;
node->child[1]->x = node->x;
node->child[1]->y = node->y + h;
node->child[1]->w = node->w;
node->child[1]->h = dh;
}
// Insert into the newly created child that matches the dimension we just set up
return pack_insert(node->child[0], w, h);
}
// Recursive insert
pack_node_t *res = pack_insert(node->child[0], w, h);
if (!res)
res = pack_insert(node->child[1], w, h);
return res;
}
static void free_pack_node(pack_node_t *node) {
if (!node)
return;
free_pack_node(node->child[0]);
free_pack_node(node->child[1]);
free(node);
}
static void relax_island(Island *isl, uint32_t *indices, float *pa, int iterations) {
if (sb_count(isl->faces) < 2)
return;
int num_uvs = sb_count(isl->uvs);
relax_vert_t *rverts = (relax_vert_t *)malloc(sizeof(relax_vert_t) * num_uvs);
for (int i = 0; i < num_uvs; ++i) {
int f = i / 3;
int v = i % 3;
rverts[i].spatial_idx = indices[isl->faces[f] * 3 + v];
rverts[i].local_idx = i;
}
qsort(rverts, num_uvs, sizeof(relax_vert_t), compare_relax_verts);
int *uv_weld_map = (int *)malloc(sizeof(int) * num_uvs);
int current_group_id = -1;
int last_spatial = -1;
for (int i = 0; i < num_uvs; ++i) {
if (rverts[i].spatial_idx != last_spatial) {
current_group_id = rverts[i].local_idx;
last_spatial = rverts[i].spatial_idx;
}
uv_weld_map[rverts[i].local_idx] = current_group_id;
}
free(rverts);
relax_edge_t *redges = NULL;
typedef struct {
int count;
int neighbors[8];
float dists[8];
} VInfo;
VInfo *v_info = (VInfo *)calloc(num_uvs, sizeof(VInfo));
for (int f = 0; f < sb_count(isl->faces); ++f) {
for (int k = 0; k < 3; ++k) {
int u_local = f * 3 + k;
int v_local = f * 3 + (k + 1) % 3;
int u_weld = uv_weld_map[u_local];
int v_weld = uv_weld_map[v_local];
if (u_weld > v_weld) {
int t = u_weld;
u_weld = v_weld;
v_weld = t;
}
relax_edge_t re = {u_weld, v_weld};
sb_push(redges, re);
int idx_u = indices[isl->faces[f] * 3 + k];
int idx_v = indices[isl->faces[f] * 3 + (k + 1) % 3];
vec3_t p1 = {pa[idx_u * 3], pa[idx_u * 3 + 1], pa[idx_u * 3 + 2]};
vec3_t p2 = {pa[idx_v * 3], pa[idx_v * 3 + 1], pa[idx_v * 3 + 2]};
float d = v3_len(v3_sub(p1, p2));
if (v_info[u_local].count < 8) {
v_info[u_local].neighbors[v_info[u_local].count] = v_local;
v_info[u_local].dists[v_info[u_local].count] = d;
v_info[u_local].count++;
}
if (v_info[v_local].count < 8) {
v_info[v_local].neighbors[v_info[v_local].count] = u_local;
v_info[v_local].dists[v_info[v_local].count] = d;
v_info[v_local].count++;
}
}
}
qsort(redges, sb_count(redges), sizeof(relax_edge_t), compare_relax_edges);
char *weld_is_border = (char *)calloc(num_uvs, 1);
int num_edges = sb_count(redges);
int i = 0;
while (i < num_edges) {
int count = 1;
while (i + count < num_edges && redges[i + count].u == redges[i].u && redges[i + count].v == redges[i].v)
count++;
if (count == 1) {
weld_is_border[redges[i].u] = 1;
weld_is_border[redges[i].v] = 1;
}
i += count;
}
sb_free(redges);
char *is_border = (char *)calloc(num_uvs, 1);
for (int k = 0; k < num_uvs; ++k) {
if (weld_is_border[uv_weld_map[k]])
is_border[k] = 1;
}
free(weld_is_border);
vec2_t *temp_uvs = (vec2_t *)malloc(sizeof(vec2_t) * num_uvs);
float *sum_x = (float *)calloc(num_uvs, sizeof(float));
float *sum_y = (float *)calloc(num_uvs, sizeof(float));
int *counts = (int *)calloc(num_uvs, sizeof(int));
for (int iter = 0; iter < iterations; ++iter) {
memcpy(temp_uvs, isl->uvs, sizeof(vec2_t) * num_uvs);
for (int k = 0; k < num_uvs; ++k) {
if (is_border[k])
continue;
vec2_t sum_pos = {0, 0};
float weight_sum = 0;
for (int n = 0; n < v_info[k].count; ++n) {
int neighbor_idx = v_info[k].neighbors[n];
float target_dist = v_info[k].dists[n];
if (target_dist < 0.0001f)
target_dist = 0.0001f;
vec2_t n_pos = temp_uvs[neighbor_idx];
float w = 1.0f / target_dist;
sum_pos.x += n_pos.x * w;
sum_pos.y += n_pos.y * w;
weight_sum += w;
}
if (weight_sum > 0) {
isl->uvs[k].x = sum_pos.x / weight_sum;
isl->uvs[k].y = sum_pos.y / weight_sum;
}
}
memset(sum_x, 0, sizeof(float) * num_uvs);
memset(sum_y, 0, sizeof(float) * num_uvs);
memset(counts, 0, sizeof(int) * num_uvs);
for (int k = 0; k < num_uvs; ++k) {
int wid = uv_weld_map[k];
sum_x[wid] += isl->uvs[k].x;
sum_y[wid] += isl->uvs[k].y;
counts[wid]++;
}
for (int k = 0; k < num_uvs; ++k) {
int wid = uv_weld_map[k];
if (counts[wid] > 1) {
isl->uvs[k].x = sum_x[wid] / counts[wid];
isl->uvs[k].y = sum_y[wid] / counts[wid];
}
}
}
free(sum_x);
free(sum_y);
free(counts);
free(temp_uvs);
free(uv_weld_map);
free(v_info);
free(is_border);
}
void proc_uv_unwrap(raw_mesh_t *mesh) {
double t = iron_time();
// Prepare Data
int vertex_count = mesh->posa->length / 4;
float *pa = (float *)malloc(sizeof(float) * vertex_count * 3);
float *na = (float *)malloc(sizeof(float) * vertex_count * 3);
float inv = 1.0f / 32767.0f;
for (int i = 0; i < vertex_count; i++) {
pa[i * 3] = mesh->posa->buffer[i * 4] * inv;
pa[i * 3 + 1] = mesh->posa->buffer[i * 4 + 1] * inv;
pa[i * 3 + 2] = mesh->posa->buffer[i * 4 + 2] * inv;
na[i * 3] = mesh->nora->buffer[i * 2] * inv;
na[i * 3 + 1] = mesh->nora->buffer[i * 2 + 1] * inv;
na[i * 3 + 2] = mesh->posa->buffer[i * 4 + 3] * inv;
}
int index_count = mesh->inda->length;
uint32_t *indices = mesh->inda->buffer;
int face_count = index_count / 3;
// Weld
int *weld_map = (int *)malloc(sizeof(int) * vertex_count);
sort_vert_t *sverts = (sort_vert_t *)malloc(sizeof(sort_vert_t) * vertex_count);
for (int i = 0; i < vertex_count; i++) {
sverts[i].pa = &pa[i * 3];
sverts[i].index = i;
}
qsort(sverts, vertex_count, sizeof(sort_vert_t), compare_verts);
int unique_counter = 0;
float eps = 0.0001f;
for (int i = 0; i < vertex_count; i++) {
if (i > 0) {
if (fabsf(sverts[i].pa[0] - sverts[i - 1].pa[0]) > eps || fabsf(sverts[i].pa[1] - sverts[i - 1].pa[1]) > eps ||
fabsf(sverts[i].pa[2] - sverts[i - 1].pa[2]) > eps) {
unique_counter++;
}
}
weld_map[sverts[i].index] = unique_counter;
}
free(sverts);
// Adjacency
vec3_t *face_normals = (vec3_t *)malloc(sizeof(vec3_t) * face_count);
for (int i = 0; i < face_count; i++) {
uint32_t i0 = indices[i * 3];
uint32_t i1 = indices[i * 3 + 1];
uint32_t i2 = indices[i * 3 + 2];
vec3_t v0 = {pa[i0 * 3], pa[i0 * 3 + 1], pa[i0 * 3 + 2]};
vec3_t v1 = {pa[i1 * 3], pa[i1 * 3 + 1], pa[i1 * 3 + 2]};
vec3_t v2 = {pa[i2 * 3], pa[i2 * 3 + 1], pa[i2 * 3 + 2]};
face_normals[i] = v3_norm(v3_cross(v3_sub(v1, v0), v3_sub(v2, v0)));
}
edge_ref_t *edges = (edge_ref_t *)malloc(sizeof(edge_ref_t) * face_count * 3);
for (int i = 0; i < face_count; i++) {
for (int j = 0; j < 3; j++) {
int idx1 = weld_map[indices[i * 3 + j]];
int idx2 = weld_map[indices[i * 3 + (j + 1) % 3]];
if (idx1 > idx2) {
int t = idx1;
idx1 = idx2;
idx2 = t;
}
edges[i * 3 + j] = (edge_ref_t){idx1, idx2, i};
}
}
qsort(edges, face_count * 3, sizeof(edge_ref_t), compare_edges);
int **adj = (int **)calloc(face_count, sizeof(int *));
for (int i = 0; i < face_count * 3 - 1; i++) {
if (edges[i].u1 == edges[i + 1].u1 && edges[i].u2 == edges[i + 1].u2) {
int f1 = edges[i].face;
int f2 = edges[i + 1].face;
sb_push(adj[f1], f2);
sb_push(adj[f2], f1);
}
}
free(edges);
// Segmentation
float angle_limit_deg = 66.0f;
float angle_threshold = cosf(angle_limit_deg * (3.14159f / 180.0f));
int *face_visited = (int *)calloc(face_count, sizeof(int));
Island *islands = NULL;
for (int i = 0; i < face_count; i++) {
if (face_visited[i])
continue;
Island island = {0};
int *queue = NULL;
sb_push(queue, i);
face_visited[i] = 1;
sb_push(island.faces, i);
int head = 0;
vec3_t island_avg = face_normals[i];
while (head < sb_count(queue)) {
int curr_f = queue[head++];
int *neighbors = adj[curr_f];
for (int k = 0; k < sb_count(neighbors); k++) {
int next_f = neighbors[k];
if (face_visited[next_f])
continue;
float dot_local = v3_dot(face_normals[curr_f], face_normals[next_f]);
float dot_global = v3_dot(v3_norm(island_avg), face_normals[next_f]);
if (dot_local > angle_threshold && dot_global > 0.5f) {
face_visited[next_f] = 1;
sb_push(island.faces, next_f);
sb_push(queue, next_f);
island_avg = v3_add(island_avg, face_normals[next_f]);
}
}
}
// Project
island_avg = v3_norm(island_avg);
vec3_t up = {0, 1, 0};
if (fabsf(v3_dot(up, island_avg)) > 0.9f)
up = (vec3_t){0, 0, 1};
vec3_t right = v3_norm(v3_cross(up, island_avg));
up = v3_norm(v3_cross(island_avg, right));
for (int k = 0; k < sb_count(island.faces); k++) {
int f = island.faces[k];
for (int v = 0; v < 3; v++) {
int idx = indices[f * 3 + v];
vec3_t pos = {pa[idx * 3], pa[idx * 3 + 1], pa[idx * 3 + 2]};
vec2_t uv;
uv.x = v3_dot(pos, right);
uv.y = v3_dot(pos, up);
sb_push(island.uvs, uv);
}
}
// Relax
relax_island(&island, indices, pa, 5);
// Calc Bounds
island.min = (vec2_t){FLT_MAX, FLT_MAX};
island.max = (vec2_t){-FLT_MAX, -FLT_MAX};
for (int k = 0; k < sb_count(island.uvs); k++) {
if (island.uvs[k].x < island.min.x)
island.min.x = island.uvs[k].x;
if (island.uvs[k].y < island.min.y)
island.min.y = island.uvs[k].y;
if (island.uvs[k].x > island.max.x)
island.max.x = island.uvs[k].x;
if (island.uvs[k].y > island.max.y)
island.max.y = island.uvs[k].y;
}
// Normalize to local 0,0
for (int k = 0; k < sb_count(island.uvs); k++) {
island.uvs[k].x -= island.min.x;
island.uvs[k].y -= island.min.y;
}
sb_push(islands, island);
sb_free(queue);
}
free(face_visited);
for (int i = 0; i < face_count; i++)
sb_free(adj[i]);
free(adj);
free(weld_map);
// Sort by Area
qsort(islands, sb_count(islands), sizeof(Island), compare_islands_area);
int map_size = 2048;
int padding = 4;
// Calc total area
float total_area = 0;
for (int i = 0; i < sb_count(islands); i++) {
total_area += (islands[i].max.x - islands[i].min.x) * (islands[i].max.y - islands[i].min.y);
}
// Iterative Fit
float ideal_scale = 1.0f;
if (total_area > 0.0001f) {
ideal_scale = sqrtf((map_size * map_size * 1.0f) / total_area); // Start high
}
if (ideal_scale > 1000.0f)
ideal_scale = 1000.0f;
float final_scale = 0;
// Retry loop
for (float try_scale = ideal_scale; try_scale > 0.001f; try_scale *= 0.95f) {
pack_node_t *root = (pack_node_t *)calloc(1, sizeof(pack_node_t));
root->w = map_size;
root->h = map_size;
int success = 1;
for (int i = 0; i < sb_count(islands); i++) {
int w = (int)((islands[i].max.x - islands[i].min.x) * try_scale) + padding * 2;
int h = (int)((islands[i].max.y - islands[i].min.y) * try_scale) + padding * 2;
if (w < 1)
w = 1;
if (h < 1)
h = 1;
pack_node_t *node = pack_insert(root, w, h);
if (node) {
// Store temporary packed coords in island
islands[i].p_x = node->x;
islands[i].p_y = node->y;
}
else {
success = 0;
break;
}
}
free_pack_node(root);
if (success) {
final_scale = try_scale;
break;
}
}
// Final Apply
for (int i = 0; i < sb_count(islands); i++) {
float off_x = (islands[i].p_x + padding) / (float)map_size;
float off_y = (islands[i].p_y + padding) / (float)map_size;
// Recalculate dimensions based on the winning scale
int w_px = (int)((islands[i].max.x - islands[i].min.x) * final_scale) + padding * 2;
int h_px = (int)((islands[i].max.y - islands[i].min.y) * final_scale) + padding * 2;
float uv_w = (w_px - padding * 2) / (float)map_size;
float uv_h = (h_px - padding * 2) / (float)map_size;
float local_w = (islands[i].max.x - islands[i].min.x);
float local_h = (islands[i].max.y - islands[i].min.y);
for (int k = 0; k < sb_count(islands[i].uvs); k++) {
float u_norm = (local_w > 1e-6f) ? islands[i].uvs[k].x / local_w : 0.0f;
float v_norm = (local_h > 1e-6f) ? islands[i].uvs[k].y / local_h : 0.0f;
islands[i].uvs[k].x = off_x + u_norm * uv_w;
islands[i].uvs[k].y = off_y + v_norm * uv_h;
}
}
// Output
int16_t *pa_out = NULL;
int16_t *na_out = NULL;
int16_t *ta_out = NULL;
uint32_t *ia_out = NULL;
int out_v_count = 0;
for (int i = 0; i < sb_count(islands); i++) {
Island *isl = &islands[i];
for (int f = 0; f < sb_count(isl->faces); f++) {
int face_idx = isl->faces[f];
for (int v = 0; v < 3; v++) {
uint32_t old_idx = indices[face_idx * 3 + v];
sb_push(pa_out, (int16_t)(pa[old_idx * 3] / inv));
sb_push(pa_out, (int16_t)(pa[old_idx * 3 + 1] / inv));
sb_push(pa_out, (int16_t)(pa[old_idx * 3 + 2] / inv));
sb_push(pa_out, (int16_t)(na[old_idx * 3 + 2] / inv));
sb_push(na_out, (int16_t)(na[old_idx * 3] / inv));
sb_push(na_out, (int16_t)(na[old_idx * 3 + 1] / inv));
vec2_t uv = isl->uvs[f * 3 + v];
sb_push(ta_out, (int16_t)(uv.x / inv));
sb_push(ta_out, (int16_t)(uv.y / inv));
sb_push(ia_out, out_v_count);
out_v_count++;
}
}
sb_free(isl->faces);
sb_free(isl->uvs);
}
sb_free(islands);
if (mesh->posa->buffer)
free(mesh->posa->buffer);
if (mesh->nora->buffer)
free(mesh->nora->buffer);
if (mesh->inda->buffer)
free(mesh->inda->buffer);
if (mesh->texa) {
if (mesh->texa->buffer)
free(mesh->texa->buffer);
free(mesh->texa);
}
mesh->posa->buffer = pa_out;
mesh->posa->length = out_v_count * 4;
mesh->posa->capacity = sb_cap(pa_out);
mesh->nora->buffer = na_out;
mesh->nora->length = out_v_count * 2;
mesh->texa = (i16_array_t *)malloc(sizeof(i16_array_t));
mesh->texa->buffer = ta_out;
mesh->texa->length = out_v_count * 2;
mesh->texa->capacity = sb_cap(ta_out);
mesh->inda->buffer = ia_out;
mesh->inda->length = out_v_count;
mesh->inda->capacity = sb_cap(ia_out);
mesh->vertex_count = out_v_count;
mesh->index_count = out_v_count;
free(pa);
free(na);
free(face_normals);
iron_log("Unwrapped in %fs\n", iron_time() - t);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <iron_obj.h>
void proc_uv_unwrap(raw_mesh_t *mesh);