#include "cell.h" #include #include #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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(js, prims_arr, pi, p); } JS_SetPropertyStr(js, m, "primitives", prims_arr); JS_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(js, snodes, ni, JS_NewInt32(js, (int)(scene->nodes[ni] - data->nodes))); JS_SetPropertyStr(js, s, "nodes", snodes); JS_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(js, channels, ci, c); } JS_SetPropertyStr(js, a, "channels", channels); JS_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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_SetPropertyNumber(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)