Files
cell-model/gltf.c
2025-12-13 16:13:32 -06:00

423 lines
18 KiB
C

#include "cell.h"
#include <string.h>
#include <stdlib.h>
#define CGLTF_IMPLEMENTATION
#include "cgltf.h"
static const char *component_type_str(cgltf_component_type t)
{
switch (t) {
case cgltf_component_type_r_8: return "i8";
case cgltf_component_type_r_8u: return "u8";
case cgltf_component_type_r_16: return "i16";
case cgltf_component_type_r_16u: return "u16";
case cgltf_component_type_r_32u: return "u32";
case cgltf_component_type_r_32f: return "f32";
default: return "unknown";
}
}
static const char *type_str(cgltf_type t)
{
switch (t) {
case cgltf_type_scalar: return "scalar";
case cgltf_type_vec2: return "vec2";
case cgltf_type_vec3: return "vec3";
case cgltf_type_vec4: return "vec4";
case cgltf_type_mat2: return "mat2";
case cgltf_type_mat3: return "mat3";
case cgltf_type_mat4: return "mat4";
default: return "unknown";
}
}
static const char *topology_str(cgltf_primitive_type t)
{
switch (t) {
case cgltf_primitive_type_points: return "points";
case cgltf_primitive_type_lines: return "lines";
case cgltf_primitive_type_line_loop: return "line_loop";
case cgltf_primitive_type_line_strip: return "line_strip";
case cgltf_primitive_type_triangles: return "triangles";
case cgltf_primitive_type_triangle_strip: return "triangle_strip";
case cgltf_primitive_type_triangle_fan: return "triangle_fan";
default: return "unknown";
}
}
static const char *attribute_name(cgltf_attribute_type t, int index)
{
static char buf[32];
switch (t) {
case cgltf_attribute_type_position: return "POSITION";
case cgltf_attribute_type_normal: return "NORMAL";
case cgltf_attribute_type_tangent: return "TANGENT";
case cgltf_attribute_type_texcoord:
snprintf(buf, sizeof(buf), "TEXCOORD_%d", index);
return buf;
case cgltf_attribute_type_color:
snprintf(buf, sizeof(buf), "COLOR_%d", index);
return buf;
case cgltf_attribute_type_joints:
snprintf(buf, sizeof(buf), "JOINTS_%d", index);
return buf;
case cgltf_attribute_type_weights:
snprintf(buf, sizeof(buf), "WEIGHTS_%d", index);
return buf;
default: return "UNKNOWN";
}
}
static JSValue make_float_array(JSContext *js, const float *arr, int count)
{
JSValue a = JS_NewArray(js);
for (int i = 0; i < count; i++)
JS_SetPropertyUint32(js, a, i, JS_NewFloat64(js, arr[i]));
return a;
}
static JSValue make_texture_info(JSContext *js, cgltf_texture_view *tv, cgltf_data *data)
{
if (!tv->texture) return JS_NULL;
JSValue o = JS_NewObject(js);
JS_SetPropertyStr(js, o, "texture", JS_NewInt32(js, (int)(tv->texture - data->textures)));
JS_SetPropertyStr(js, o, "texcoord", JS_NewInt32(js, tv->texcoord));
if (tv->has_transform) {
JSValue tr = JS_NewObject(js);
JS_SetPropertyStr(js, tr, "offset", make_float_array(js, tv->transform.offset, 2));
JS_SetPropertyStr(js, tr, "scale", make_float_array(js, tv->transform.scale, 2));
JS_SetPropertyStr(js, tr, "rotation", JS_NewFloat64(js, tv->transform.rotation));
JS_SetPropertyStr(js, o, "transform", tr);
}
return o;
}
JSValue js_gltf_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (raw == NULL) return JS_EXCEPTION;
cgltf_options options = {0};
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, raw, len, &data);
if (result != cgltf_result_success)
return JS_ThrowReferenceError(js, "failed to parse glTF: %d", result);
result = cgltf_load_buffers(&options, data, NULL);
if (result != cgltf_result_success) {
cgltf_free(data);
return JS_ThrowReferenceError(js, "failed to load glTF buffers: %d", result);
}
JSValue obj = JS_NewObject(js);
// Buffers
JSValue buffers_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->buffers_count; i++) {
cgltf_buffer *buf = &data->buffers[i];
JSValue b = JS_NewObject(js);
if (buf->data && buf->size > 0)
JS_SetPropertyStr(js, b, "blob", js_new_blob_stoned_copy(js, buf->data, buf->size));
else
JS_SetPropertyStr(js, b, "blob", JS_NULL);
JS_SetPropertyStr(js, b, "byte_length", JS_NewInt64(js, buf->size));
JS_SetPropertyUint32(js, buffers_arr, i, b);
}
JS_SetPropertyStr(js, obj, "buffers", buffers_arr);
// Buffer views
JSValue views_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->buffer_views_count; i++) {
cgltf_buffer_view *bv = &data->buffer_views[i];
JSValue v = JS_NewObject(js);
JS_SetPropertyStr(js, v, "buffer", JS_NewInt32(js, (int)(bv->buffer - data->buffers)));
JS_SetPropertyStr(js, v, "byte_offset", JS_NewInt64(js, bv->offset));
JS_SetPropertyStr(js, v, "byte_length", JS_NewInt64(js, bv->size));
JS_SetPropertyStr(js, v, "byte_stride", bv->stride ? JS_NewInt32(js, bv->stride) : JS_NULL);
const char *usage = "unknown";
if (bv->type == cgltf_buffer_view_type_vertices) usage = "vertex";
else if (bv->type == cgltf_buffer_view_type_indices) usage = "index";
JS_SetPropertyStr(js, v, "usage", JS_NewString(js, usage));
JS_SetPropertyUint32(js, views_arr, i, v);
}
JS_SetPropertyStr(js, obj, "views", views_arr);
// Accessors
JSValue accessors_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->accessors_count; i++) {
cgltf_accessor *acc = &data->accessors[i];
JSValue a = JS_NewObject(js);
if (acc->buffer_view)
JS_SetPropertyStr(js, a, "view", JS_NewInt32(js, (int)(acc->buffer_view - data->buffer_views)));
else
JS_SetPropertyStr(js, a, "view", JS_NULL);
JS_SetPropertyStr(js, a, "byte_offset", JS_NewInt64(js, acc->offset));
JS_SetPropertyStr(js, a, "count", JS_NewInt64(js, acc->count));
JS_SetPropertyStr(js, a, "component_type", JS_NewString(js, component_type_str(acc->component_type)));
JS_SetPropertyStr(js, a, "type", JS_NewString(js, type_str(acc->type)));
JS_SetPropertyStr(js, a, "normalized", JS_NewBool(js, acc->normalized));
if (acc->has_min) {
int n = cgltf_num_components(acc->type);
JS_SetPropertyStr(js, a, "min", make_float_array(js, acc->min, n));
} else {
JS_SetPropertyStr(js, a, "min", JS_NULL);
}
if (acc->has_max) {
int n = cgltf_num_components(acc->type);
JS_SetPropertyStr(js, a, "max", make_float_array(js, acc->max, n));
} else {
JS_SetPropertyStr(js, a, "max", JS_NULL);
}
JS_SetPropertyUint32(js, accessors_arr, i, a);
}
JS_SetPropertyStr(js, obj, "accessors", accessors_arr);
// Meshes
JSValue meshes_arr = JS_NewArray(js);
for (cgltf_size mi = 0; mi < data->meshes_count; mi++) {
cgltf_mesh *mesh = &data->meshes[mi];
JSValue m = JS_NewObject(js);
JS_SetPropertyStr(js, m, "name", mesh->name ? JS_NewString(js, mesh->name) : JS_NULL);
JSValue prims_arr = JS_NewArray(js);
for (cgltf_size pi = 0; pi < mesh->primitives_count; pi++) {
cgltf_primitive *prim = &mesh->primitives[pi];
JSValue p = JS_NewObject(js);
JS_SetPropertyStr(js, p, "topology", JS_NewString(js, topology_str(prim->type)));
JSValue attrs = JS_NewObject(js);
for (cgltf_size ai = 0; ai < prim->attributes_count; ai++) {
cgltf_attribute *attr = &prim->attributes[ai];
const char *name = attribute_name(attr->type, attr->index);
JS_SetPropertyStr(js, attrs, name, JS_NewInt32(js, (int)(attr->data - data->accessors)));
}
JS_SetPropertyStr(js, p, "attributes", attrs);
if (prim->indices)
JS_SetPropertyStr(js, p, "indices", JS_NewInt32(js, (int)(prim->indices - data->accessors)));
else
JS_SetPropertyStr(js, p, "indices", JS_NULL);
if (prim->material)
JS_SetPropertyStr(js, p, "material", JS_NewInt32(js, (int)(prim->material - data->materials)));
else
JS_SetPropertyStr(js, p, "material", JS_NULL);
JS_SetPropertyUint32(js, prims_arr, pi, p);
}
JS_SetPropertyStr(js, m, "primitives", prims_arr);
JS_SetPropertyUint32(js, meshes_arr, mi, m);
}
JS_SetPropertyStr(js, obj, "meshes", meshes_arr);
// Images
JSValue images_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->images_count; i++) {
cgltf_image *img = &data->images[i];
JSValue im = JS_NewObject(js);
if (img->buffer_view) {
JS_SetPropertyStr(js, im, "kind", JS_NewString(js, "buffer_view"));
JS_SetPropertyStr(js, im, "view", JS_NewInt32(js, (int)(img->buffer_view - data->buffer_views)));
} else if (img->uri) {
JS_SetPropertyStr(js, im, "kind", JS_NewString(js, "uri"));
JS_SetPropertyStr(js, im, "uri", JS_NewString(js, img->uri));
}
JS_SetPropertyStr(js, im, "mime", img->mime_type ? JS_NewString(js, img->mime_type) : JS_NULL);
JS_SetPropertyUint32(js, images_arr, i, im);
}
JS_SetPropertyStr(js, obj, "images", images_arr);
// Textures
JSValue textures_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->textures_count; i++) {
cgltf_texture *tex = &data->textures[i];
JSValue t = JS_NewObject(js);
JS_SetPropertyStr(js, t, "image", tex->image ? JS_NewInt32(js, (int)(tex->image - data->images)) : JS_NULL);
JS_SetPropertyStr(js, t, "sampler", tex->sampler ? JS_NewInt32(js, (int)(tex->sampler - data->samplers)) : JS_NULL);
JS_SetPropertyUint32(js, textures_arr, i, t);
}
JS_SetPropertyStr(js, obj, "textures", textures_arr);
// Samplers
JSValue samplers_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->samplers_count; i++) {
cgltf_sampler *samp = &data->samplers[i];
JSValue s = JS_NewObject(js);
JS_SetPropertyStr(js, s, "min_filter", JS_NewInt32(js, samp->min_filter));
JS_SetPropertyStr(js, s, "mag_filter", JS_NewInt32(js, samp->mag_filter));
JS_SetPropertyStr(js, s, "wrap_s", JS_NewInt32(js, samp->wrap_s));
JS_SetPropertyStr(js, s, "wrap_t", JS_NewInt32(js, samp->wrap_t));
JS_SetPropertyUint32(js, samplers_arr, i, s);
}
JS_SetPropertyStr(js, obj, "samplers", samplers_arr);
// Materials
JSValue materials_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->materials_count; i++) {
cgltf_material *mat = &data->materials[i];
JSValue m = JS_NewObject(js);
JS_SetPropertyStr(js, m, "name", mat->name ? JS_NewString(js, mat->name) : JS_NULL);
JSValue pbr = JS_NewObject(js);
if (mat->has_pbr_metallic_roughness) {
cgltf_pbr_metallic_roughness *pmr = &mat->pbr_metallic_roughness;
JS_SetPropertyStr(js, pbr, "base_color_factor", make_float_array(js, pmr->base_color_factor, 4));
JS_SetPropertyStr(js, pbr, "base_color_texture", make_texture_info(js, &pmr->base_color_texture, data));
JS_SetPropertyStr(js, pbr, "metallic_factor", JS_NewFloat64(js, pmr->metallic_factor));
JS_SetPropertyStr(js, pbr, "roughness_factor", JS_NewFloat64(js, pmr->roughness_factor));
JS_SetPropertyStr(js, pbr, "metallic_roughness_texture", make_texture_info(js, &pmr->metallic_roughness_texture, data));
}
JS_SetPropertyStr(js, pbr, "normal_texture", make_texture_info(js, &mat->normal_texture, data));
JS_SetPropertyStr(js, pbr, "occlusion_texture", make_texture_info(js, &mat->occlusion_texture, data));
JS_SetPropertyStr(js, pbr, "emissive_factor", make_float_array(js, mat->emissive_factor, 3));
JS_SetPropertyStr(js, pbr, "emissive_texture", make_texture_info(js, &mat->emissive_texture, data));
JS_SetPropertyStr(js, m, "pbr", pbr);
const char *alpha_mode = "OPAQUE";
if (mat->alpha_mode == cgltf_alpha_mode_mask) alpha_mode = "MASK";
else if (mat->alpha_mode == cgltf_alpha_mode_blend) alpha_mode = "BLEND";
JS_SetPropertyStr(js, m, "alpha_mode", JS_NewString(js, alpha_mode));
JS_SetPropertyStr(js, m, "alpha_cutoff", JS_NewFloat64(js, mat->alpha_cutoff));
JS_SetPropertyStr(js, m, "double_sided", JS_NewBool(js, mat->double_sided));
JS_SetPropertyStr(js, m, "unlit", JS_NewBool(js, mat->unlit));
JS_SetPropertyUint32(js, materials_arr, i, m);
}
JS_SetPropertyStr(js, obj, "materials", materials_arr);
// Nodes
JSValue nodes_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->nodes_count; i++) {
cgltf_node *node = &data->nodes[i];
JSValue n = JS_NewObject(js);
JS_SetPropertyStr(js, n, "name", node->name ? JS_NewString(js, node->name) : JS_NULL);
JS_SetPropertyStr(js, n, "mesh", node->mesh ? JS_NewInt32(js, (int)(node->mesh - data->meshes)) : JS_NULL);
JSValue children = JS_NewArray(js);
for (cgltf_size ci = 0; ci < node->children_count; ci++)
JS_SetPropertyUint32(js, children, ci, JS_NewInt32(js, (int)(node->children[ci] - data->nodes)));
JS_SetPropertyStr(js, n, "children", children);
if (node->has_matrix) {
JS_SetPropertyStr(js, n, "matrix", make_float_array(js, node->matrix, 16));
JS_SetPropertyStr(js, n, "translation", JS_NULL);
JS_SetPropertyStr(js, n, "rotation", JS_NULL);
JS_SetPropertyStr(js, n, "scale", JS_NULL);
} else {
JS_SetPropertyStr(js, n, "matrix", JS_NULL);
JS_SetPropertyStr(js, n, "translation", make_float_array(js, node->translation, 3));
JS_SetPropertyStr(js, n, "rotation", make_float_array(js, node->rotation, 4));
JS_SetPropertyStr(js, n, "scale", make_float_array(js, node->scale, 3));
}
JS_SetPropertyStr(js, n, "skin", node->skin ? JS_NewInt32(js, (int)(node->skin - data->skins)) : JS_NULL);
JS_SetPropertyUint32(js, nodes_arr, i, n);
}
JS_SetPropertyStr(js, obj, "nodes", nodes_arr);
// Scenes
JSValue scenes_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->scenes_count; i++) {
cgltf_scene *scene = &data->scenes[i];
JSValue s = JS_NewObject(js);
JSValue snodes = JS_NewArray(js);
for (cgltf_size ni = 0; ni < scene->nodes_count; ni++)
JS_SetPropertyUint32(js, snodes, ni, JS_NewInt32(js, (int)(scene->nodes[ni] - data->nodes)));
JS_SetPropertyStr(js, s, "nodes", snodes);
JS_SetPropertyUint32(js, scenes_arr, i, s);
}
JS_SetPropertyStr(js, obj, "scenes", scenes_arr);
JS_SetPropertyStr(js, obj, "scene", data->scene ? JS_NewInt32(js, (int)(data->scene - data->scenes)) : JS_NULL);
// Animations
JSValue anims_arr = JS_NewArray(js);
for (cgltf_size ai = 0; ai < data->animations_count; ai++) {
cgltf_animation *anim = &data->animations[ai];
JSValue a = JS_NewObject(js);
JS_SetPropertyStr(js, a, "name", anim->name ? JS_NewString(js, anim->name) : JS_NULL);
JSValue samplers = JS_NewArray(js);
for (cgltf_size si = 0; si < anim->samplers_count; si++) {
cgltf_animation_sampler *samp = &anim->samplers[si];
JSValue s = JS_NewObject(js);
JS_SetPropertyStr(js, s, "input", samp->input ? JS_NewInt32(js, (int)(samp->input - data->accessors)) : JS_NULL);
JS_SetPropertyStr(js, s, "output", samp->output ? JS_NewInt32(js, (int)(samp->output - data->accessors)) : JS_NULL);
const char *interp = "LINEAR";
if (samp->interpolation == cgltf_interpolation_type_step) interp = "STEP";
else if (samp->interpolation == cgltf_interpolation_type_cubic_spline) interp = "CUBICSPLINE";
JS_SetPropertyStr(js, s, "interpolation", JS_NewString(js, interp));
JS_SetPropertyUint32(js, samplers, si, s);
}
JS_SetPropertyStr(js, a, "samplers", samplers);
JSValue channels = JS_NewArray(js);
for (cgltf_size ci = 0; ci < anim->channels_count; ci++) {
cgltf_animation_channel *chan = &anim->channels[ci];
JSValue c = JS_NewObject(js);
// Find sampler index within this animation
int samp_idx = -1;
for (cgltf_size si = 0; si < anim->samplers_count; si++) {
if (&anim->samplers[si] == chan->sampler) { samp_idx = (int)si; break; }
}
JS_SetPropertyStr(js, c, "sampler", JS_NewInt32(js, samp_idx));
JSValue target = JS_NewObject(js);
JS_SetPropertyStr(js, target, "node", chan->target_node ? JS_NewInt32(js, (int)(chan->target_node - data->nodes)) : JS_NULL);
const char *path = "unknown";
switch (chan->target_path) {
case cgltf_animation_path_type_translation: path = "translation"; break;
case cgltf_animation_path_type_rotation: path = "rotation"; break;
case cgltf_animation_path_type_scale: path = "scale"; break;
case cgltf_animation_path_type_weights: path = "weights"; break;
default: break;
}
JS_SetPropertyStr(js, target, "path", JS_NewString(js, path));
JS_SetPropertyStr(js, c, "target", target);
JS_SetPropertyUint32(js, channels, ci, c);
}
JS_SetPropertyStr(js, a, "channels", channels);
JS_SetPropertyUint32(js, anims_arr, ai, a);
}
JS_SetPropertyStr(js, obj, "animations", anims_arr);
// Skins
JSValue skins_arr = JS_NewArray(js);
for (cgltf_size i = 0; i < data->skins_count; i++) {
cgltf_skin *skin = &data->skins[i];
JSValue s = JS_NewObject(js);
JS_SetPropertyStr(js, s, "name", skin->name ? JS_NewString(js, skin->name) : JS_NULL);
JSValue joints = JS_NewArray(js);
for (cgltf_size ji = 0; ji < skin->joints_count; ji++)
JS_SetPropertyUint32(js, joints, ji, JS_NewInt32(js, (int)(skin->joints[ji] - data->nodes)));
JS_SetPropertyStr(js, s, "joints", joints);
JS_SetPropertyStr(js, s, "inverse_bind_matrices", skin->inverse_bind_matrices ? JS_NewInt32(js, (int)(skin->inverse_bind_matrices - data->accessors)) : JS_NULL);
JS_SetPropertyStr(js, s, "skeleton", skin->skeleton ? JS_NewInt32(js, (int)(skin->skeleton - data->nodes)) : JS_NULL);
JS_SetPropertyUint32(js, skins_arr, i, s);
}
JS_SetPropertyStr(js, obj, "skins", skins_arr);
// Extensions
JSValue exts = JS_NewObject(js);
JSValue used = JS_NewArray(js);
for (cgltf_size i = 0; i < data->extensions_used_count; i++)
JS_SetPropertyUint32(js, used, i, JS_NewString(js, data->extensions_used[i]));
JS_SetPropertyStr(js, exts, "used", used);
JSValue required = JS_NewArray(js);
for (cgltf_size i = 0; i < data->extensions_required_count; i++)
JS_SetPropertyUint32(js, required, i, JS_NewString(js, data->extensions_required[i]));
JS_SetPropertyStr(js, exts, "required", required);
JS_SetPropertyStr(js, obj, "extensions", exts);
cgltf_free(data);
return obj;
}
static const JSCFunctionListEntry js_gltf_funcs[] = {
MIST_FUNC_DEF(gltf, decode, 1),
};
CELL_USE_FUNCS(js_gltf_funcs)