paint: make obj export faster

This commit is contained in:
luboslenco
2026-09-28 15:10:09 +02:00
parent 2a3cc4c2fe
commit 9bf7c6521b
+192 -232
View File
@@ -1,14 +1,123 @@
#include "../global.h"
static void export_obj_write_string(u8_array_t *out, char *str) {
for (i32 i = 0; i < string_length(str); ++i) {
u8_array_push(out, char_code_at(str, i));
static void export_obj_reserve(u8_array_t *out, u32 size) {
if (out->length + size > out->capacity) {
u32 cap = out->capacity < 1024 ? 1024 : out->capacity * 2;
while (cap < out->length + size) {
cap *= 2;
}
out->buffer = realloc(out->buffer, cap);
out->capacity = cap;
}
}
static void export_obj_write_string(u8_array_t *out, char *str) {
u32 len = strlen(str);
export_obj_reserve(out, len);
memcpy(out->buffer + out->length, str, len);
out->length += len;
}
static void export_obj_write_char(u8_array_t *out, char c) {
export_obj_reserve(out, 1);
out->buffer[out->length++] = c;
}
static void export_obj_write_i32(u8_array_t *out, i32 i) {
char s[16];
i32 l = 0;
u32 u = i < 0 ? -(u32)i : (u32)i;
do {
s[l++] = '0' + u % 10;
u /= 10;
} while (u > 0);
export_obj_reserve(out, l + 1);
if (i < 0) {
out->buffer[out->length++] = '-';
}
while (l > 0) {
out->buffer[out->length++] = s[--l];
}
}
static void export_obj_write_f32(u8_array_t *out, f32 f) {
char s[64];
i32 l = snprintf(s, sizeof(s), "%f", f);
if (l <= 0 || l >= (i32)sizeof(s)) {
return;
}
while (l > 1 && s[l - 1] == '0') {
l--;
}
if (s[l - 1] == '.') {
l--;
}
export_obj_reserve(out, l);
memcpy(out->buffer + out->length, s, l);
out->length += l;
}
static void export_obj_write_vec(u8_array_t *out, char *prefix, f32 x, f32 y, f32 z, bool has_z) {
export_obj_write_string(out, prefix);
export_obj_write_f32(out, x);
export_obj_write_char(out, ' ');
export_obj_write_f32(out, y);
if (has_z) {
export_obj_write_char(out, ' ');
export_obj_write_f32(out, z);
}
export_obj_write_char(out, '\n');
}
static void export_obj_write_face(u8_array_t *out, i32 *p, i32 *t, i32 *n) {
export_obj_write_char(out, 'f');
for (i32 k = 0; k < 3; ++k) {
export_obj_write_char(out, ' ');
export_obj_write_i32(out, p[k]);
export_obj_write_char(out, '/');
export_obj_write_i32(out, t[k]);
export_obj_write_char(out, '/');
export_obj_write_i32(out, n[k]);
}
export_obj_write_char(out, '\n');
}
static u64 export_obj_key(i16 a, i16 b, i16 c) {
return (u64)(u16)a | ((u64)(u16)b << 16) | ((u64)(u16)c << 32);
}
static i32 export_obj_dedupe(u64 *keys, i32 len, i32 *map, i32 *first) {
u32 cap = 16;
while (cap < (u32)len * 2) {
cap *= 2;
}
i32 *table = calloc(cap, sizeof(i32)); // Unique index + 1, 0 is empty
i32 count = 0;
for (i32 i = 0; i < len; ++i) {
u64 k = keys[i];
u32 s = (u32)((k * 0x9E3779B97F4A7C15ull) >> 32) & (cap - 1);
while (true) {
i32 e = table[s];
if (e == 0) {
table[s] = count + 1;
first[count] = i;
map[i] = count++;
break;
}
if (keys[first[e - 1]] == k) {
map[i] = e - 1;
break;
}
s = (s + 1) & (cap - 1);
}
}
free(table);
return count;
}
void export_obj_run(char *path, mesh_object_t_array_t *paint_objects) {
u8_array_t *o = u8_array_create_from_raw((u8[]){}, 0);
u8_array_t *o = u8_array_create(0);
export_obj_write_string(o, "# armorpaint.org\n");
i32 poff = 0;
@@ -23,145 +132,66 @@ void export_obj_run(char *path, mesh_object_t_array_t *paint_objects) {
i16_array_t *nora = mesh->vertex_arrays->buffer[1]->values;
i16_array_t *texa = mesh->vertex_arrays->buffer[2]->values;
i32 len = math_floor(posa->length / 4.0);
i32 size = len > 0 ? len : 1;
// Merge shared vertices and remap indices
i16_array_t *posa2 = i16_array_create(len * 3);
i16_array_t *nora2 = i16_array_create(len * 3);
i16_array_t *texa2 = i16_array_create(len * 2);
i32_array_t *posmap = i32_array_create(len);
i32_array_t *normap = i32_array_create(len);
i32_array_t *texmap = i32_array_create(len);
u64 *keys = malloc(size * sizeof(u64));
i32 *posmap = malloc(size * sizeof(i32));
i32 *normap = malloc(size * sizeof(i32));
i32 *texmap = malloc(size * sizeof(i32));
i32 *posfst = malloc(size * sizeof(i32));
i32 *norfst = malloc(size * sizeof(i32));
i32 *texfst = malloc(size * sizeof(i32));
for (i32 j = 0; j < len; ++j)
keys[j] = export_obj_key(posa->buffer[j * 4], posa->buffer[j * 4 + 1], posa->buffer[j * 4 + 2]);
i32 pi = export_obj_dedupe(keys, len, posmap, posfst);
for (i32 j = 0; j < len; ++j)
keys[j] = export_obj_key(nora->buffer[j * 2], nora->buffer[j * 2 + 1], posa->buffer[j * 4 + 3]);
i32 ni = export_obj_dedupe(keys, len, normap, norfst);
for (i32 j = 0; j < len; ++j)
keys[j] = export_obj_key(texa->buffer[j * 2], texa->buffer[j * 2 + 1], 0);
i32 ti = export_obj_dedupe(keys, len, texmap, texfst);
i32 pi = 0;
i32 ni = 0;
i32 ti = 0;
for (i32 i = 0; i < len; ++i) {
bool found = false;
for (i32 j = 0; j < pi; ++j) {
if (posa2->buffer[j * 3] == posa->buffer[i * 4] && posa2->buffer[j * 3 + 1] == posa->buffer[i * 4 + 1] &&
posa2->buffer[j * 3 + 2] == posa->buffer[i * 4 + 2]) {
posmap->buffer[i] = j;
found = true;
break;
}
}
if (!found) {
posmap->buffer[i] = pi;
posa2->buffer[pi * 3] = posa->buffer[i * 4];
posa2->buffer[pi * 3 + 1] = posa->buffer[i * 4 + 1];
posa2->buffer[pi * 3 + 2] = posa->buffer[i * 4 + 2];
pi++;
}
found = false;
for (i32 j = 0; j < ni; ++j) {
if (nora2->buffer[j * 3] == nora->buffer[i * 2] && nora2->buffer[j * 3 + 1] == nora->buffer[i * 2 + 1] &&
nora2->buffer[j * 3 + 2] == posa->buffer[i * 4 + 3]) {
normap->buffer[i] = j;
found = true;
break;
}
}
if (!found) {
normap->buffer[i] = ni;
nora2->buffer[ni * 3] = nora->buffer[i * 2];
nora2->buffer[ni * 3 + 1] = nora->buffer[i * 2 + 1];
nora2->buffer[ni * 3 + 2] = posa->buffer[i * 4 + 3];
ni++;
}
found = false;
for (i32 j = 0; j < ti; ++j) {
if (texa2->buffer[j * 2] == texa->buffer[i * 2] && texa2->buffer[j * 2 + 1] == texa->buffer[i * 2 + 1]) {
texmap->buffer[i] = j;
found = true;
break;
}
}
if (!found) {
texmap->buffer[i] = ti;
texa2->buffer[ti * 2] = texa->buffer[i * 2];
texa2->buffer[ti * 2 + 1] = texa->buffer[i * 2 + 1];
ti++;
}
export_obj_write_string(o, "o ");
export_obj_write_string(o, p->base->name);
export_obj_write_char(o, '\n');
for (i32 j = 0; j < pi; ++j) {
i32 v = posfst[j];
export_obj_write_vec(o, "v ", posa->buffer[v * 4] * sc, posa->buffer[v * 4 + 2] * sc, -posa->buffer[v * 4 + 1] * sc, true);
}
export_obj_write_string(o, string_tmp("o %s\n", p->base->name));
for (i32 i = 0; i < pi; ++i) {
export_obj_write_string(o, "v ");
f32 f = posa2->buffer[i * 3] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = posa2->buffer[i * 3 + 2] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = -posa2->buffer[i * 3 + 1] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < ni; ++j) {
i32 v = norfst[j];
export_obj_write_vec(o, "vn ", nora->buffer[v * 2] * inv, posa->buffer[v * 4 + 3] * inv, -nora->buffer[v * 2 + 1] * inv, true);
}
for (i32 i = 0; i < ni; ++i) {
export_obj_write_string(o, "vn ");
f32 f = nora2->buffer[i * 3] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = nora2->buffer[i * 3 + 2] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = -nora2->buffer[i * 3 + 1] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
}
for (i32 i = 0; i < ti; ++i) {
export_obj_write_string(o, "vt ");
f32 f = texa2->buffer[i * 2] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = 1.0 - texa2->buffer[i * 2 + 1] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < ti; ++j) {
i32 v = texfst[j];
export_obj_write_vec(o, "vt ", texa->buffer[v * 2] * inv, 1.0 - texa->buffer[v * 2 + 1] * inv, 0.0, false);
}
u32_array_t *inda = mesh->index_array;
for (i32 i = 0; i < math_floor(inda->length / 3.0); ++i) {
i32 pi1 = posmap->buffer[inda->buffer[i * 3]] + 1 + poff;
i32 pi2 = posmap->buffer[inda->buffer[i * 3 + 1]] + 1 + poff;
i32 pi3 = posmap->buffer[inda->buffer[i * 3 + 2]] + 1 + poff;
i32 ni1 = normap->buffer[inda->buffer[i * 3]] + 1 + noff;
i32 ni2 = normap->buffer[inda->buffer[i * 3 + 1]] + 1 + noff;
i32 ni3 = normap->buffer[inda->buffer[i * 3 + 2]] + 1 + noff;
i32 ti1 = texmap->buffer[inda->buffer[i * 3]] + 1 + toff;
i32 ti2 = texmap->buffer[inda->buffer[i * 3 + 1]] + 1 + toff;
i32 ti3 = texmap->buffer[inda->buffer[i * 3 + 2]] + 1 + toff;
export_obj_write_string(o, "f ");
export_obj_write_string(o, i32_to_string(pi1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni1));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(pi2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni2));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(pi3));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti3));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni3));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < math_floor(inda->length / 3.0); ++j) {
i32 pf[3];
i32 tf[3];
i32 nf[3];
for (i32 k = 0; k < 3; ++k) {
i32 v = inda->buffer[j * 3 + k];
pf[k] = posmap[v] + 1 + poff;
tf[k] = texmap[v] + 1 + toff;
nf[k] = normap[v] + 1 + noff;
}
export_obj_write_face(o, pf, tf, nf);
}
poff += pi;
noff += ni;
toff += ti;
array_delete(posa2);
array_delete(nora2);
array_delete(texa2);
array_delete(posmap);
array_delete(normap);
array_delete(texmap);
free(keys);
free(posmap);
free(normap);
free(texmap);
free(posfst);
free(norfst);
free(texfst);
}
if (!ends_with(path, ".obj")) {
@@ -174,7 +204,7 @@ void export_obj_run(char *path, mesh_object_t_array_t *paint_objects) {
void export_obj_run_fast(char *path, mesh_object_t_array_t *paint_objects) {
// Skips merging shared vertices
u8_array_t *o = u8_array_create_from_raw((u8[]){}, 0);
u8_array_t *o = u8_array_create(0);
export_obj_write_string(o, "# armorpaint.org\n");
i32 poff = 0;
@@ -193,71 +223,31 @@ void export_obj_run_fast(char *path, mesh_object_t_array_t *paint_objects) {
i32 ni = pi;
i32 ti = pi;
export_obj_write_string(o, string_tmp("o %s\n", p->base->name));
for (i32 i = 0; i < pi; ++i) {
export_obj_write_string(o, "v ");
f32 f = posa->buffer[i * 4] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = posa->buffer[i * 4 + 2] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = -posa->buffer[i * 4 + 1] * sc;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
export_obj_write_string(o, "o ");
export_obj_write_string(o, p->base->name);
export_obj_write_char(o, '\n');
for (i32 j = 0; j < pi; ++j) {
export_obj_write_vec(o, "v ", posa->buffer[j * 4] * sc, posa->buffer[j * 4 + 2] * sc, -posa->buffer[j * 4 + 1] * sc, true);
}
for (i32 i = 0; i < ni; ++i) {
export_obj_write_string(o, "vn ");
f32 f = nora->buffer[i * 2] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = posa->buffer[i * 4 + 3] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = -nora->buffer[i * 2 + 1] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < ni; ++j) {
export_obj_write_vec(o, "vn ", nora->buffer[j * 2] * inv, posa->buffer[j * 4 + 3] * inv, -nora->buffer[j * 2 + 1] * inv, true);
}
for (i32 i = 0; i < ti; ++i) {
export_obj_write_string(o, "vt ");
f32 f = texa->buffer[i * 2] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, " ");
f = 1.0 - texa->buffer[i * 2 + 1] * inv;
export_obj_write_string(o, f32_to_string(f));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < ti; ++j) {
export_obj_write_vec(o, "vt ", texa->buffer[j * 2] * inv, 1.0 - texa->buffer[j * 2 + 1] * inv, 0.0, false);
}
u32_array_t *inda = mesh->index_array;
for (i32 i = 0; i < math_floor(inda->length / 3.0); ++i) {
i32 pi1 = inda->buffer[i * 3] + 1 + poff;
i32 pi2 = inda->buffer[i * 3 + 1] + 1 + poff;
i32 pi3 = inda->buffer[i * 3 + 2] + 1 + poff;
i32 ni1 = inda->buffer[i * 3] + 1 + noff;
i32 ni2 = inda->buffer[i * 3 + 1] + 1 + noff;
i32 ni3 = inda->buffer[i * 3 + 2] + 1 + noff;
i32 ti1 = inda->buffer[i * 3] + 1 + toff;
i32 ti2 = inda->buffer[i * 3 + 1] + 1 + toff;
i32 ti3 = inda->buffer[i * 3 + 2] + 1 + toff;
export_obj_write_string(o, "f ");
export_obj_write_string(o, i32_to_string(pi1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni1));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(pi2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni2));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(pi3));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ti3));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(ni3));
export_obj_write_string(o, "\n");
for (i32 j = 0; j < math_floor(inda->length / 3.0); ++j) {
i32 pf[3];
i32 tf[3];
i32 nf[3];
for (i32 k = 0; k < 3; ++k) {
i32 v = inda->buffer[j * 3 + k];
pf[k] = v + 1 + poff;
tf[k] = v + 1 + toff;
nf[k] = v + 1 + noff;
}
export_obj_write_face(o, pf, tf, nf);
}
poff += pi;
noff += ni;
@@ -332,7 +322,7 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) {
return;
}
u8_array_t *o = u8_array_create_from_raw((u8[]){}, 0);
u8_array_t *o = u8_array_create(0);
export_obj_write_string(o, "# armorpaint.org\n");
mesh_object_t *p = paint_objects->buffer[0];
@@ -398,19 +388,15 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) {
}
}
export_obj_write_string(o, string_tmp("o %s\n", p->base->name));
export_obj_write_string(o, "o ");
export_obj_write_string(o, p->base->name);
export_obj_write_char(o, '\n');
for (i32 i = 0; i < len; ++i) {
f32 x = cpos->buffer[i * 3] * sc;
f32 y = cpos->buffer[i * 3 + 1] * sc;
f32 z = cpos->buffer[i * 3 + 2] * sc;
export_obj_write_string(o, "v ");
export_obj_write_string(o, f32_to_string(x));
export_obj_write_string(o, " ");
export_obj_write_string(o, f32_to_string(z));
export_obj_write_string(o, " ");
export_obj_write_string(o, f32_to_string(-y));
export_obj_write_string(o, "\n");
export_obj_write_vec(o, "v ", x, z, -y, true);
}
for (i32 t = 0; t < tris; ++t) {
@@ -435,47 +421,21 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) {
ny /= nl;
nz /= nl;
}
export_obj_write_string(o, "vn ");
export_obj_write_string(o, f32_to_string(nx));
export_obj_write_string(o, " ");
export_obj_write_string(o, f32_to_string(nz));
export_obj_write_string(o, " ");
export_obj_write_string(o, f32_to_string(-ny));
export_obj_write_string(o, "\n");
export_obj_write_vec(o, "vn ", nx, nz, -ny, true);
}
for (i32 i = 0; i < len; ++i) {
i32 vid = inda->buffer[i];
f32 u = texa->buffer[vid * 2] * inv;
f32 v = 1.0 - texa->buffer[vid * 2 + 1] * inv;
export_obj_write_string(o, "vt ");
export_obj_write_string(o, f32_to_string(u));
export_obj_write_string(o, " ");
export_obj_write_string(o, f32_to_string(v));
export_obj_write_string(o, "\n");
export_obj_write_vec(o, "vt ", u, v, 0.0, false);
}
for (i32 t = 0; t < tris; ++t) {
i32 b = t * 3 + 1;
export_obj_write_string(o, "f ");
export_obj_write_string(o, i32_to_string(b));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(b));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(t + 1));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(b + 1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(b + 1));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(t + 1));
export_obj_write_string(o, " ");
export_obj_write_string(o, i32_to_string(b + 2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(b + 2));
export_obj_write_string(o, "/");
export_obj_write_string(o, i32_to_string(t + 1));
export_obj_write_string(o, "\n");
i32 b = t * 3 + 1;
i32 pf[3] = {b, b + 1, b + 2};
i32 nf[3] = {t + 1, t + 1, t + 1};
export_obj_write_face(o, pf, pf, nf);
}
if (!ends_with(path, ".obj")) {