Files
armorpaint/paint/plugins/io_gltf/cgltf.c
T
2026-04-16 09:55:34 +02:00

885 lines
26 KiB
C

#define CGLTF_IMPLEMENTATION
#include "cgltf.h"
#include "engine.h"
#include "iron_array.h"
#include "iron_obj.h"
#include <math.h>
#include <stdarg.h>
static bool has_next = false;
static int current_node = 0;
static float scale_pos = 1.0;
void io_gltf_parse_mesh(raw_mesh_t *raw, cgltf_mesh *mesh, float *to_world, float *scale) {
cgltf_primitive *prim = NULL;
uint32_t *inda = NULL;
for (int i = 0; i < mesh->primitives_count; ++i) {
// TODO: handle all primitives
prim = &mesh->primitives[i];
cgltf_accessor *a = prim->indices;
inda = malloc(sizeof(uint32_t) * a->count);
for (cgltf_size i = 0; i < a->count; ++i) {
inda[i] = cgltf_accessor_read_index(a, i);
}
}
if (inda == NULL) {
return;
}
int index_count = prim->indices->count;
int vertex_count = -1;
float *posa32 = NULL;
float *nora32 = NULL;
float *texa32 = NULL;
for (int i = 0; i < prim->attributes_count; ++i) {
cgltf_attribute *attrib = &prim->attributes[i];
if (attrib->type == cgltf_attribute_type_position) {
vertex_count = attrib->data->count;
posa32 = malloc(sizeof(float) * attrib->data->count * 3);
for (cgltf_size i = 0; i < attrib->data->count; ++i) {
cgltf_accessor_read_float(attrib->data, i, posa32 + i * 3, 3);
}
}
else if (attrib->type == cgltf_attribute_type_normal) {
nora32 = malloc(sizeof(float) * attrib->data->count * 3);
for (cgltf_size i = 0; i < attrib->data->count; ++i) {
cgltf_accessor_read_float(attrib->data, i, nora32 + i * 3, 3);
}
}
else if (attrib->type == cgltf_attribute_type_texcoord) {
texa32 = malloc(sizeof(float) * attrib->data->count * 2);
for (cgltf_size i = 0; i < attrib->data->count; ++i) {
cgltf_accessor_read_float(attrib->data, i, texa32 + i * 2, 2);
}
}
}
if (vertex_count == -1) {
return;
}
float *m = to_world;
for (int i = 0; i < vertex_count; ++i) {
// float x = posa32[i * 3 + 0];
// float y = posa32[i * 3 + 1];
// float z = posa32[i * 3 + 2];
float x = posa32[i * 3 + 0];
float y = -posa32[i * 3 + 2];
float z = posa32[i * 3 + 1];
posa32[i * 3 + 0] = m[0] * x + m[4] * y + m[8] * z + m[12];
posa32[i * 3 + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];
posa32[i * 3 + 2] = m[2] * x + m[6] * y + m[10] * z + m[14];
}
if (nora32 != NULL) {
for (int i = 0; i < vertex_count; ++i) {
// float x = nora32[i * 3 + 0] / scale[0];
// float y = nora32[i * 3 + 1] / scale[1];
// float z = nora32[i * 3 + 2] / scale[2];
float x = nora32[i * 3 + 0] / scale[0];
float y = -nora32[i * 3 + 2] / scale[2];
float z = nora32[i * 3 + 1] / scale[1];
float tx = m[0] * x + m[4] * y + m[8] * z;
float ty = m[1] * x + m[5] * y + m[9] * z;
float tz = m[2] * x + m[6] * y + m[10] * z;
float len = sqrtf(tx * tx + ty * ty + tz * tz);
if (len > 1e-6f) {
tx /= len;
ty /= len;
tz /= len;
}
nora32[i * 3 + 0] = tx;
nora32[i * 3 + 1] = ty;
nora32[i * 3 + 2] = tz;
}
}
// Pack positions to (-1, 1) range
float hx = 0.0;
float hy = 0.0;
float hz = 0.0;
for (int i = 0; i < vertex_count; ++i) {
float f = fabsf(posa32[i * 3]);
if (hx < f)
hx = f;
f = fabsf(posa32[i * 3 + 1]);
if (hy < f)
hy = f;
f = fabsf(posa32[i * 3 + 2]);
if (hz < f)
hz = f;
}
float _scale_pos = fmax(hx, fmax(hy, hz));
if (_scale_pos > scale_pos)
scale_pos = _scale_pos;
float inv = 1 / scale_pos;
// Pack into 16bit
short *posa = malloc(sizeof(short) * vertex_count * 4);
for (int i = 0; i < vertex_count; ++i) {
posa[i * 4] = posa32[i * 3] * 32767 * inv;
posa[i * 4 + 1] = posa32[i * 3 + 1] * 32767 * inv;
posa[i * 4 + 2] = posa32[i * 3 + 2] * 32767 * inv;
}
short *nora = malloc(sizeof(short) * vertex_count * 2);
if (nora32 != NULL) {
for (int i = 0; i < vertex_count; ++i) {
nora[i * 2] = nora32[i * 3] * 32767;
nora[i * 2 + 1] = nora32[i * 3 + 1] * 32767;
posa[i * 4 + 3] = nora32[i * 3 + 2] * 32767;
}
}
else {
// Calc normals
for (int i = 0; i < index_count / 3; ++i) {
int i1 = inda[i * 3];
int i2 = inda[i * 3 + 1];
int i3 = inda[i * 3 + 2];
float vax = posa32[i1 * 3];
float vay = posa32[i1 * 3 + 1];
float vaz = posa32[i1 * 3 + 2];
float vbx = posa32[i2 * 3];
float vby = posa32[i2 * 3 + 1];
float vbz = posa32[i2 * 3 + 2];
float vcx = posa32[i3 * 3];
float vcy = posa32[i3 * 3 + 1];
float vcz = posa32[i3 * 3 + 2];
float cbx = vcx - vbx;
float cby = vcy - vby;
float cbz = vcz - vbz;
float abx = vax - vbx;
float aby = vay - vby;
float abz = vaz - vbz;
float x = cbx, y = cby, z = cbz;
cbx = y * abz - z * aby;
cby = z * abx - x * abz;
cbz = x * aby - y * abx;
float n = sqrt(cbx * cbx + cby * cby + cbz * cbz);
if (n > 0.0) {
float inv_n = 1.0 / n;
cbx *= inv_n;
cby *= inv_n;
cbz *= inv_n;
}
nora[i1 * 2] = (int)(cbx * 32767);
nora[i1 * 2 + 1] = (int)(cby * 32767);
posa[i1 * 4 + 3] = (int)(cbz * 32767);
nora[i2 * 2] = (int)(cbx * 32767);
nora[i2 * 2 + 1] = (int)(cby * 32767);
posa[i2 * 4 + 3] = (int)(cbz * 32767);
nora[i3 * 2] = (int)(cbx * 32767);
nora[i3 * 2 + 1] = (int)(cby * 32767);
posa[i3 * 4 + 3] = (int)(cbz * 32767);
}
}
short *texa = NULL;
if (texa32 != NULL) {
texa = malloc(sizeof(short) * vertex_count * 2);
for (int i = 0; i < vertex_count; ++i) {
texa[i * 2] = texa32[i * 2] * 32767;
texa[i * 2 + 1] = texa32[i * 2 + 1] * 32767;
}
}
raw->posa = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->posa->buffer = posa;
raw->posa->length = raw->posa->capacity = vertex_count * 4;
raw->nora = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->nora->buffer = nora;
raw->nora->length = raw->nora->capacity = vertex_count * 2;
if (texa != NULL) {
raw->texa = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->texa->buffer = texa;
raw->texa->length = raw->texa->capacity = vertex_count * 2;
}
raw->inda = (u32_array_t *)malloc(sizeof(u32_array_t));
raw->inda->buffer = inda;
raw->inda->length = raw->inda->capacity = index_count;
raw->scale_pos = scale_pos;
raw->scale_tex = 1.0;
}
void *io_gltf_parse(char *buf, size_t size, const char *path) {
cgltf_options options = {0};
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, buf, size, &data);
if (result != cgltf_result_success) {
return NULL;
}
cgltf_load_buffers(&options, data, path);
raw_mesh_t *raw = (raw_mesh_t *)calloc(sizeof(raw_mesh_t), 1);
for (; current_node < data->nodes_count; ++current_node) {
cgltf_node *n = &data->nodes[current_node];
if (n->mesh != NULL) {
raw->name = malloc(strlen(n->name) + 1);
strcpy(raw->name, n->name);
float m[16];
cgltf_node_transform_world(n, m);
float scale[3] = {1.0f, 1.0f, 1.0f};
if (n->has_scale) {
scale[0] = n->scale[0];
scale[1] = n->scale[1];
scale[2] = n->scale[2];
}
io_gltf_parse_mesh(raw, n->mesh, m, scale);
break;
}
}
current_node++;
has_next = false;
for (size_t i = current_node; i < data->nodes_count; ++i) {
cgltf_node *n = &data->nodes[i];
if (n->mesh != NULL) {
has_next = true;
break;
}
}
cgltf_free(data);
if (!has_next) {
current_node = 0;
}
raw->has_next = has_next;
return raw;
}
void *io_gltf_parse_skinned(char *buf, size_t size, const char *path, int frame) {
cgltf_options options = {0};
cgltf_data *data = NULL;
if (cgltf_parse(&options, buf, size, &data) != cgltf_result_success)
return NULL;
cgltf_load_buffers(&options, data, path);
// Apply animation at the given frame index by directly writing TRS on each target node
if (data->animations_count > 0) {
cgltf_animation *anim = &data->animations[0];
for (cgltf_size c = 0; c < anim->channels_count; c++) {
cgltf_animation_channel *ch = &anim->channels[c];
if (ch->target_node == NULL)
continue;
cgltf_size fi = (cgltf_size)frame;
if (fi >= ch->sampler->output->count)
fi = ch->sampler->output->count - 1;
if (ch->target_path == cgltf_animation_path_type_translation) {
cgltf_accessor_read_float(ch->sampler->output, fi, ch->target_node->translation, 3);
ch->target_node->has_translation = 1;
}
else if (ch->target_path == cgltf_animation_path_type_rotation) {
cgltf_accessor_read_float(ch->sampler->output, fi, ch->target_node->rotation, 4);
ch->target_node->has_rotation = 1;
}
else if (ch->target_path == cgltf_animation_path_type_scale) {
cgltf_accessor_read_float(ch->sampler->output, fi, ch->target_node->scale, 3);
ch->target_node->has_scale = 1;
}
}
}
// Find first mesh node; prefer one that also has a skin
cgltf_node *mesh_node = NULL;
for (cgltf_size i = 0; i < data->nodes_count; i++) {
if (data->nodes[i].mesh != NULL) {
if (mesh_node == NULL)
mesh_node = &data->nodes[i];
if (data->nodes[i].skin != NULL) {
mesh_node = &data->nodes[i];
break;
}
}
}
if (mesh_node == NULL) {
cgltf_free(data);
return NULL;
}
cgltf_mesh *mesh = mesh_node->mesh;
cgltf_skin *skin = mesh_node->skin;
cgltf_size joint_count = skin ? skin->joints_count : 0;
// Build per-joint skinning matrices: skin_mat[j] = joint_world[j] * ibm[j]
float *skin_mats = NULL;
if (joint_count > 0) {
skin_mats = malloc(sizeof(float) * 16 * joint_count);
for (cgltf_size j = 0; j < joint_count; j++) {
float jw[16];
cgltf_node_transform_world(skin->joints[j], jw);
float ibm[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
if (skin->inverse_bind_matrices)
cgltf_accessor_read_float(skin->inverse_bind_matrices, j, ibm, 16);
// Column-major multiply: sm = jw * ibm
float *sm = &skin_mats[j * 16];
for (int col = 0; col < 4; col++)
for (int row = 0; row < 4; row++)
sm[col * 4 + row] = jw[0 * 4 + row] * ibm[col * 4 + 0] + jw[1 * 4 + row] * ibm[col * 4 + 1] + jw[2 * 4 + row] * ibm[col * 4 + 2] +
jw[3 * 4 + row] * ibm[col * 4 + 3];
}
}
// Read indices
cgltf_primitive *prim = NULL;
uint32_t *inda = NULL;
for (int i = 0; i < (int)mesh->primitives_count; i++) {
prim = &mesh->primitives[i];
cgltf_accessor *a = prim->indices;
inda = malloc(sizeof(uint32_t) * a->count);
for (cgltf_size k = 0; k < a->count; k++)
inda[k] = cgltf_accessor_read_index(a, k);
}
if (inda == NULL) {
free(skin_mats);
cgltf_free(data);
return NULL;
}
int index_count = (int)prim->indices->count;
int vertex_count = -1;
float *posa32 = NULL;
float *nora32 = NULL;
float *texa32 = NULL;
float *joints32 = NULL;
float *weights32 = NULL;
for (int i = 0; i < (int)prim->attributes_count; i++) {
cgltf_attribute *att = &prim->attributes[i];
cgltf_size vc = att->data->count;
if (att->type == cgltf_attribute_type_position) {
vertex_count = (int)vc;
posa32 = malloc(sizeof(float) * vc * 3);
for (cgltf_size k = 0; k < vc; k++)
cgltf_accessor_read_float(att->data, k, posa32 + k * 3, 3);
}
else if (att->type == cgltf_attribute_type_normal) {
nora32 = malloc(sizeof(float) * vc * 3);
for (cgltf_size k = 0; k < vc; k++)
cgltf_accessor_read_float(att->data, k, nora32 + k * 3, 3);
}
else if (att->type == cgltf_attribute_type_texcoord) {
texa32 = malloc(sizeof(float) * vc * 2);
for (cgltf_size k = 0; k < vc; k++)
cgltf_accessor_read_float(att->data, k, texa32 + k * 2, 2);
}
else if (att->type == cgltf_attribute_type_joints) {
joints32 = malloc(sizeof(float) * vc * 4);
for (cgltf_size k = 0; k < vc; k++)
cgltf_accessor_read_float(att->data, k, joints32 + k * 4, 4);
}
else if (att->type == cgltf_attribute_type_weights) {
weights32 = malloc(sizeof(float) * vc * 4);
for (cgltf_size k = 0; k < vc; k++)
cgltf_accessor_read_float(att->data, k, weights32 + k * 4, 4);
}
}
if (vertex_count == -1) {
free(skin_mats);
free(inda);
cgltf_free(data);
return NULL;
}
// Apply linear blend skinning in GLTF space
bool skinning_applied = false;
if (skin_mats != NULL && joints32 != NULL && weights32 != NULL) {
float *sp = malloc(sizeof(float) * vertex_count * 3);
float *sn = nora32 ? malloc(sizeof(float) * vertex_count * 3) : NULL;
for (int i = 0; i < vertex_count; i++) {
float px = posa32[i * 3], py = posa32[i * 3 + 1], pz = posa32[i * 3 + 2];
float nx = 0, ny = 0, nz = 0;
if (nora32) {
nx = nora32[i * 3];
ny = nora32[i * 3 + 1];
nz = nora32[i * 3 + 2];
}
float opx = 0, opy = 0, opz = 0;
float onx = 0, ony = 0, onz = 0;
for (int ji = 0; ji < 4; ji++) {
float w = weights32[i * 4 + ji];
if (w == 0.0f)
continue;
int j = (int)joints32[i * 4 + ji];
if (j < 0 || j >= (int)joint_count)
continue;
float *m = &skin_mats[j * 16];
opx += w * (m[0] * px + m[4] * py + m[8] * pz + m[12]);
opy += w * (m[1] * px + m[5] * py + m[9] * pz + m[13]);
opz += w * (m[2] * px + m[6] * py + m[10] * pz + m[14]);
if (sn) {
onx += w * (m[0] * nx + m[4] * ny + m[8] * nz);
ony += w * (m[1] * nx + m[5] * ny + m[9] * nz);
onz += w * (m[2] * nx + m[6] * ny + m[10] * nz);
}
}
sp[i * 3] = opx;
sp[i * 3 + 1] = opy;
sp[i * 3 + 2] = opz;
if (sn) {
float len = sqrtf(onx * onx + ony * ony + onz * onz);
if (len > 1e-6f) {
onx /= len;
ony /= len;
onz /= len;
}
sn[i * 3] = onx;
sn[i * 3 + 1] = ony;
sn[i * 3 + 2] = onz;
}
}
free(posa32);
posa32 = sp;
if (nora32) {
free(nora32);
nora32 = sn;
}
free(joints32);
free(weights32);
skinning_applied = true;
}
// Apply coordinate conversion (Y-up -> Z-up) and world transform.
// For skinned meshes the skinning already produced world-space positions, so
// use identity for the world transform and only do the axis swap.
float node_m[16];
float node_scale[3] = {1.0f, 1.0f, 1.0f};
if (skinning_applied) {
memset(node_m, 0, sizeof(node_m));
node_m[0] = node_m[5] = node_m[10] = node_m[15] = 1.0f;
}
else {
cgltf_node_transform_world(mesh_node, node_m);
if (mesh_node->has_scale) {
node_scale[0] = mesh_node->scale[0];
node_scale[1] = mesh_node->scale[1];
node_scale[2] = mesh_node->scale[2];
}
}
float *m = node_m;
for (int i = 0; i < vertex_count; i++) {
float x = posa32[i * 3];
float y = -posa32[i * 3 + 2];
float z = posa32[i * 3 + 1];
posa32[i * 3] = m[0] * x + m[4] * y + m[8] * z + m[12];
posa32[i * 3 + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];
posa32[i * 3 + 2] = m[2] * x + m[6] * y + m[10] * z + m[14];
}
if (nora32 != NULL) {
for (int i = 0; i < vertex_count; i++) {
float x = nora32[i * 3] / node_scale[0];
float y = -nora32[i * 3 + 2] / node_scale[2];
float z = nora32[i * 3 + 1] / node_scale[1];
float tx = m[0] * x + m[4] * y + m[8] * z;
float ty = m[1] * x + m[5] * y + m[9] * z;
float tz = m[2] * x + m[6] * y + m[10] * z;
float len = sqrtf(tx * tx + ty * ty + tz * tz);
if (len > 1e-6f) {
tx /= len;
ty /= len;
tz /= len;
}
nora32[i * 3] = tx;
nora32[i * 3 + 1] = ty;
nora32[i * 3 + 2] = tz;
}
}
// Pack positions to (-1, 1) range
float hx = 0.0f, hy = 0.0f, hz = 0.0f;
for (int i = 0; i < vertex_count; i++) {
float f = fabsf(posa32[i * 3]);
if (f > hx)
hx = f;
f = fabsf(posa32[i * 3 + 1]);
if (f > hy)
hy = f;
f = fabsf(posa32[i * 3 + 2]);
if (f > hz)
hz = f;
}
float _scale_pos = fmaxf(hx, fmaxf(hy, hz));
if (_scale_pos > scale_pos)
scale_pos = _scale_pos;
float inv = 1.0f / scale_pos;
// Pack to 16-bit
short *posa = malloc(sizeof(short) * vertex_count * 4);
for (int i = 0; i < vertex_count; i++) {
posa[i * 4] = (short)(posa32[i * 3] * 32767 * inv);
posa[i * 4 + 1] = (short)(posa32[i * 3 + 1] * 32767 * inv);
posa[i * 4 + 2] = (short)(posa32[i * 3 + 2] * 32767 * inv);
}
short *nora = malloc(sizeof(short) * vertex_count * 2);
if (nora32 != NULL) {
for (int i = 0; i < vertex_count; i++) {
nora[i * 2] = (short)(nora32[i * 3] * 32767);
nora[i * 2 + 1] = (short)(nora32[i * 3 + 1] * 32767);
posa[i * 4 + 3] = (short)(nora32[i * 3 + 2] * 32767);
}
}
else {
// Calc flat normals from triangles
for (int i = 0; i < index_count / 3; i++) {
int i1 = inda[i * 3], i2 = inda[i * 3 + 1], i3 = inda[i * 3 + 2];
float vax = posa32[i1 * 3], vay = posa32[i1 * 3 + 1], vaz = posa32[i1 * 3 + 2];
float vbx = posa32[i2 * 3], vby = posa32[i2 * 3 + 1], vbz = posa32[i2 * 3 + 2];
float vcx = posa32[i3 * 3], vcy = posa32[i3 * 3 + 1], vcz = posa32[i3 * 3 + 2];
float cbx = vcx - vbx, cby = vcy - vby, cbz = vcz - vbz;
float abx = vax - vbx, aby = vay - vby, abz = vaz - vbz;
float x = cbx, y = cby, z = cbz;
cbx = y * abz - z * aby;
cby = z * abx - x * abz;
cbz = x * aby - y * abx;
float n = sqrtf(cbx * cbx + cby * cby + cbz * cbz);
if (n > 0.0f) {
float inv_n = 1.0f / n;
cbx *= inv_n;
cby *= inv_n;
cbz *= inv_n;
}
nora[i1 * 2] = (short)(cbx * 32767);
nora[i1 * 2 + 1] = (short)(cby * 32767);
posa[i1 * 4 + 3] = (short)(cbz * 32767);
nora[i2 * 2] = (short)(cbx * 32767);
nora[i2 * 2 + 1] = (short)(cby * 32767);
posa[i2 * 4 + 3] = (short)(cbz * 32767);
nora[i3 * 2] = (short)(cbx * 32767);
nora[i3 * 2 + 1] = (short)(cby * 32767);
posa[i3 * 4 + 3] = (short)(cbz * 32767);
}
}
short *texa = NULL;
if (texa32 != NULL) {
texa = malloc(sizeof(short) * vertex_count * 2);
for (int i = 0; i < vertex_count; i++) {
texa[i * 2] = (short)(texa32[i * 2] * 32767);
texa[i * 2 + 1] = (short)(texa32[i * 2 + 1] * 32767);
}
}
raw_mesh_t *raw = (raw_mesh_t *)calloc(sizeof(raw_mesh_t), 1);
if (mesh_node->name != NULL) {
raw->name = malloc(strlen(mesh_node->name) + 1);
strcpy(raw->name, mesh_node->name);
}
raw->posa = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->posa->buffer = posa;
raw->posa->length = raw->posa->capacity = vertex_count * 4;
raw->nora = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->nora->buffer = nora;
raw->nora->length = raw->nora->capacity = vertex_count * 2;
if (texa != NULL) {
raw->texa = (i16_array_t *)malloc(sizeof(i16_array_t));
raw->texa->buffer = texa;
raw->texa->length = raw->texa->capacity = vertex_count * 2;
}
raw->inda = (u32_array_t *)malloc(sizeof(u32_array_t));
raw->inda->buffer = inda;
raw->inda->length = raw->inda->capacity = index_count;
raw->scale_pos = scale_pos;
raw->scale_tex = 1.0f;
free(posa32);
free(nora32);
free(texa32);
free(skin_mats);
cgltf_free(data);
return raw;
}
typedef struct {
char *buf;
size_t len;
size_t cap;
} json_buf_t;
static void json_buf_append(json_buf_t *j, const char *fmt, ...) {
char tmp[1024];
va_list ap;
va_start(ap, fmt);
int tl = vsnprintf(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
while (j->len + (size_t)tl + 1 > j->cap) {
j->cap *= 2;
j->buf = (char *)realloc(j->buf, j->cap);
}
memcpy(j->buf + j->len, tmp, (size_t)tl);
j->len += (size_t)tl;
}
void export_glb_run(char *path, any_array_t *paint_objects) {
int n = (int)paint_objects->length;
int *vcs = (int *)malloc(n * sizeof(int));
int *ics = (int *)malloc(n * sizeof(int));
uint32_t *pos_off = (uint32_t *)malloc(n * sizeof(uint32_t));
uint32_t *nor_off = (uint32_t *)malloc(n * sizeof(uint32_t));
uint32_t *tex_off = (uint32_t *)malloc(n * sizeof(uint32_t));
uint32_t *idx_off = (uint32_t *)malloc(n * sizeof(uint32_t));
float *pmins = (float *)malloc(n * 3 * sizeof(float));
float *pmaxs = (float *)malloc(n * 3 * sizeof(float));
// binary buffer: per object [positions][normals][texcoords][indices]
size_t bin_cap = 64 * 1024;
size_t bin_len = 0;
uint8_t *bin = (uint8_t *)malloc(bin_cap);
for (int oi = 0; oi < n; oi++) {
mesh_object_t *p = (mesh_object_t *)paint_objects->buffer[oi];
mesh_data_t *mesh = p->data;
float inv = 1.0f / 32767.0f;
float sc = mesh->scale_pos * inv;
i16_array_t *posa = mesh->vertex_arrays->buffer[0]->values;
i16_array_t *nora = mesh->vertex_arrays->buffer[1]->values;
i16_array_t *texa = mesh->vertex_arrays->buffer[2]->values;
int vc = (int)(posa->length / 4);
u32_array_t *inda = mesh->index_array;
int ic = (int)inda->length;
vcs[oi] = vc;
ics[oi] = ic;
// Positions (float32, VEC3), z-up to y-up
pos_off[oi] = (uint32_t)bin_len;
pmins[oi * 3 + 0] = pmins[oi * 3 + 1] = pmins[oi * 3 + 2] = 1e30f;
pmaxs[oi * 3 + 0] = pmaxs[oi * 3 + 1] = pmaxs[oi * 3 + 2] = -1e30f;
for (int i = 0; i < vc; i++) {
float x = posa->buffer[i * 4 + 0] * sc;
float y = posa->buffer[i * 4 + 2] * sc;
float z = -posa->buffer[i * 4 + 1] * sc;
if (x < pmins[oi * 3 + 0])
pmins[oi * 3 + 0] = x;
if (y < pmins[oi * 3 + 1])
pmins[oi * 3 + 1] = y;
if (z < pmins[oi * 3 + 2])
pmins[oi * 3 + 2] = z;
if (x > pmaxs[oi * 3 + 0])
pmaxs[oi * 3 + 0] = x;
if (y > pmaxs[oi * 3 + 1])
pmaxs[oi * 3 + 1] = y;
if (z > pmaxs[oi * 3 + 2])
pmaxs[oi * 3 + 2] = z;
if (bin_len + 12 > bin_cap) {
bin_cap *= 2;
bin = (uint8_t *)realloc(bin, bin_cap);
}
memcpy(bin + bin_len, &x, 4);
bin_len += 4;
memcpy(bin + bin_len, &y, 4);
bin_len += 4;
memcpy(bin + bin_len, &z, 4);
bin_len += 4;
}
// Normals (float32, VEC3), nz packed into posa[i*4+3]
nor_off[oi] = (uint32_t)bin_len;
for (int i = 0; i < vc; i++) {
float x = nora->buffer[i * 2 + 0] * inv;
float y = posa->buffer[i * 4 + 3] * inv;
float z = -nora->buffer[i * 2 + 1] * inv;
if (bin_len + 12 > bin_cap) {
bin_cap *= 2;
bin = (uint8_t *)realloc(bin, bin_cap);
}
memcpy(bin + bin_len, &x, 4);
bin_len += 4;
memcpy(bin + bin_len, &y, 4);
bin_len += 4;
memcpy(bin + bin_len, &z, 4);
bin_len += 4;
}
// Texcoords (float32, VEC2)
tex_off[oi] = (uint32_t)bin_len;
for (int i = 0; i < vc; i++) {
float u = texa->buffer[i * 2 + 0] * inv;
float v = texa->buffer[i * 2 + 1] * inv;
if (bin_len + 8 > bin_cap) {
bin_cap *= 2;
bin = (uint8_t *)realloc(bin, bin_cap);
}
memcpy(bin + bin_len, &u, 4);
bin_len += 4;
memcpy(bin + bin_len, &v, 4);
bin_len += 4;
}
// Indices (uint32)
idx_off[oi] = (uint32_t)bin_len;
for (int i = 0; i < ic; i++) {
uint32_t idx = inda->buffer[i];
if (bin_len + 4 > bin_cap) {
bin_cap *= 2;
bin = (uint8_t *)realloc(bin, bin_cap);
}
memcpy(bin + bin_len, &idx, 4);
bin_len += 4;
}
}
// Pad to 4-byte boundary with zeros
while (bin_len % 4 != 0) {
if (bin_len >= bin_cap) {
bin_cap *= 2;
bin = (uint8_t *)realloc(bin, bin_cap);
}
bin[bin_len++] = 0;
}
// Build JSON string
json_buf_t j = {(char *)malloc(4096), 0, 4096};
json_buf_append(&j, "{\"asset\":{\"generator\":\"ArmorPaint\",\"version\":\"2.0\"},\"scene\":0,");
// scenes
json_buf_append(&j, "\"scenes\":[{\"name\":\"Scene\",\"nodes\":[");
for (int i = 0; i < n; i++) {
if (i > 0)
json_buf_append(&j, ",");
json_buf_append(&j, "%d", i);
}
json_buf_append(&j, "]}],");
// nodes
json_buf_append(&j, "\"nodes\":[");
for (int i = 0; i < n; i++) {
if (i > 0)
json_buf_append(&j, ",");
mesh_object_t *p = (mesh_object_t *)paint_objects->buffer[i];
json_buf_append(&j, "{\"mesh\":%d,\"name\":\"%s\"}", i, p->base->name);
}
json_buf_append(&j, "],");
// meshes
json_buf_append(&j, "\"meshes\":[");
for (int i = 0; i < n; i++) {
if (i > 0)
json_buf_append(&j, ",");
mesh_object_t *p = (mesh_object_t *)paint_objects->buffer[i];
int base_ac = i * 4;
json_buf_append(&j, "{\"name\":\"%s\",\"primitives\":[{\"attributes\":{\"POSITION\":%d,\"NORMAL\":%d,\"TEXCOORD_0\":%d},\"indices\":%d}]}",
p->base->name, base_ac, base_ac + 1, base_ac + 2, base_ac + 3);
}
json_buf_append(&j, "],");
// accessors
json_buf_append(&j, "\"accessors\":[");
for (int i = 0; i < n; i++) {
if (i > 0)
json_buf_append(&j, ",");
int bv = i * 4;
int vc = vcs[i];
int ic = ics[i];
// position
json_buf_append(&j,
"{\"bufferView\":%d,\"componentType\":5126,\"count\":%d,\"type\":\"VEC3\","
"\"min\":[%.7g,%.7g,%.7g],\"max\":[%.7g,%.7g,%.7g]}",
bv, vc, pmins[i * 3 + 0], pmins[i * 3 + 1], pmins[i * 3 + 2], pmaxs[i * 3 + 0], pmaxs[i * 3 + 1], pmaxs[i * 3 + 2]);
// normal
json_buf_append(&j, ",{\"bufferView\":%d,\"componentType\":5126,\"count\":%d,\"type\":\"VEC3\"}", bv + 1, vc);
// texcoord
json_buf_append(&j, ",{\"bufferView\":%d,\"componentType\":5126,\"count\":%d,\"type\":\"VEC2\"}", bv + 2, vc);
// indices
json_buf_append(&j, ",{\"bufferView\":%d,\"componentType\":5125,\"count\":%d,\"type\":\"SCALAR\"}", bv + 3, ic);
}
json_buf_append(&j, "],");
// bufferViews
json_buf_append(&j, "\"bufferViews\":[");
for (int i = 0; i < n; i++) {
if (i > 0)
json_buf_append(&j, ",");
int vc = vcs[i];
int ic = ics[i];
json_buf_append(&j, "{\"buffer\":0,\"byteOffset\":%u,\"byteLength\":%u}", pos_off[i], (uint32_t)(vc * 12));
json_buf_append(&j, ",{\"buffer\":0,\"byteOffset\":%u,\"byteLength\":%u}", nor_off[i], (uint32_t)(vc * 12));
json_buf_append(&j, ",{\"buffer\":0,\"byteOffset\":%u,\"byteLength\":%u}", tex_off[i], (uint32_t)(vc * 8));
json_buf_append(&j, ",{\"buffer\":0,\"byteOffset\":%u,\"byteLength\":%u}", idx_off[i], (uint32_t)(ic * 4));
}
json_buf_append(&j, "],");
// buffers
json_buf_append(&j, "\"buffers\":[{\"byteLength\":%u}]}", (uint32_t)bin_len);
// Pad to 4-byte boundary with spaces
while (j.len % 4 != 0) {
if (j.len >= j.cap) {
j.cap *= 2;
j.buf = (char *)realloc(j.buf, j.cap);
}
j.buf[j.len++] = ' ';
}
// Write glb
uint32_t json_chunk_len = (uint32_t)j.len;
uint32_t bin_chunk_len = (uint32_t)bin_len;
uint32_t total_len = 12 + 8 + json_chunk_len + 8 + bin_chunk_len;
char out_path[4096];
int plen = (int)strlen(path);
if (plen >= 4 && strcmp(path + plen - 4, ".glb") == 0) {
snprintf(out_path, sizeof(out_path), "%s", path);
}
else {
snprintf(out_path, sizeof(out_path), "%s.glb", path);
}
FILE *f = fopen(out_path, "wb");
if (f != NULL) {
uint32_t magic = 0x46546C67; // "glTF"
uint32_t version = 2;
uint32_t json_type = 0x4E4F534A; // "JSON"
uint32_t bin_type = 0x004E4942; // "BIN\0"
fwrite(&magic, 4, 1, f);
fwrite(&version, 4, 1, f);
fwrite(&total_len, 4, 1, f);
fwrite(&json_chunk_len, 4, 1, f);
fwrite(&json_type, 4, 1, f);
fwrite(j.buf, 1, j.len, f);
fwrite(&bin_chunk_len, 4, 1, f);
fwrite(&bin_type, 4, 1, f);
fwrite(bin, 1, bin_len, f);
fclose(f);
}
free(vcs);
free(ics);
free(pos_off);
free(nor_off);
free(tex_off);
free(idx_off);
free(pmins);
free(pmaxs);
free(bin);
free(j.buf);
}