loaders
This commit is contained in:
470
fbx.c
470
fbx.c
@@ -0,0 +1,470 @@
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ufbx.h"
|
||||
|
||||
static JSValue make_float_array(JSContext *js, const double *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_float_array_f(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;
|
||||
}
|
||||
|
||||
JSValue js_fbx_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;
|
||||
|
||||
ufbx_load_opts opts = {0};
|
||||
opts.generate_missing_normals = true;
|
||||
opts.target_axes = ufbx_axes_right_handed_y_up;
|
||||
opts.target_unit_meters = 1.0;
|
||||
|
||||
ufbx_error error;
|
||||
ufbx_scene *scene = ufbx_load_memory(raw, len, &opts, &error);
|
||||
if (!scene)
|
||||
return JS_ThrowReferenceError(js, "failed to parse FBX: %s", error.description.data);
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
|
||||
// Count total vertices and indices across all meshes
|
||||
size_t total_vertices = 0;
|
||||
size_t total_indices = 0;
|
||||
int global_has_normals = 0;
|
||||
int global_has_uvs = 0;
|
||||
|
||||
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
||||
ufbx_mesh *mesh = scene->meshes.data[mi];
|
||||
total_vertices += mesh->num_indices;
|
||||
total_indices += mesh->num_triangles * 3;
|
||||
if (mesh->vertex_normal.exists) global_has_normals = 1;
|
||||
if (mesh->vertex_uv.exists) global_has_uvs = 1;
|
||||
}
|
||||
|
||||
int use_32bit = (total_vertices > 65535);
|
||||
|
||||
// Calculate buffer layout
|
||||
size_t pos_size = total_vertices * 3 * sizeof(float);
|
||||
size_t norm_size = global_has_normals ? total_vertices * 3 * sizeof(float) : 0;
|
||||
size_t uv_size = global_has_uvs ? total_vertices * 2 * sizeof(float) : 0;
|
||||
size_t idx_size = total_indices * (use_32bit ? sizeof(uint32_t) : sizeof(uint16_t));
|
||||
size_t total_buffer_size = pos_size + norm_size + uv_size + idx_size;
|
||||
|
||||
uint8_t *buffer_data = malloc(total_buffer_size);
|
||||
float *positions = (float *)buffer_data;
|
||||
float *normals = global_has_normals ? (float *)(buffer_data + pos_size) : NULL;
|
||||
float *uvs = global_has_uvs ? (float *)(buffer_data + pos_size + norm_size) : NULL;
|
||||
void *indices = buffer_data + pos_size + norm_size + uv_size;
|
||||
|
||||
// Fill vertex data and track per-mesh offsets
|
||||
size_t vertex_offset = 0;
|
||||
size_t index_offset = 0;
|
||||
|
||||
// Store mesh info for later
|
||||
typedef struct {
|
||||
size_t vertex_start;
|
||||
size_t vertex_count;
|
||||
size_t index_start;
|
||||
size_t index_count;
|
||||
} mesh_info_t;
|
||||
mesh_info_t *mesh_infos = malloc(scene->meshes.count * sizeof(mesh_info_t));
|
||||
|
||||
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
||||
ufbx_mesh *mesh = scene->meshes.data[mi];
|
||||
|
||||
mesh_infos[mi].vertex_start = vertex_offset;
|
||||
mesh_infos[mi].vertex_count = mesh->num_indices;
|
||||
mesh_infos[mi].index_start = index_offset;
|
||||
|
||||
// Copy vertex data (unindexed - one vertex per index)
|
||||
for (size_t i = 0; i < mesh->num_indices; i++) {
|
||||
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, i);
|
||||
positions[(vertex_offset + i) * 3 + 0] = (float)pos.x;
|
||||
positions[(vertex_offset + i) * 3 + 1] = (float)pos.y;
|
||||
positions[(vertex_offset + i) * 3 + 2] = (float)pos.z;
|
||||
|
||||
if (normals && mesh->vertex_normal.exists) {
|
||||
ufbx_vec3 norm = ufbx_get_vertex_vec3(&mesh->vertex_normal, i);
|
||||
normals[(vertex_offset + i) * 3 + 0] = (float)norm.x;
|
||||
normals[(vertex_offset + i) * 3 + 1] = (float)norm.y;
|
||||
normals[(vertex_offset + i) * 3 + 2] = (float)norm.z;
|
||||
} else if (normals) {
|
||||
normals[(vertex_offset + i) * 3 + 0] = 0;
|
||||
normals[(vertex_offset + i) * 3 + 1] = 1;
|
||||
normals[(vertex_offset + i) * 3 + 2] = 0;
|
||||
}
|
||||
|
||||
if (uvs && mesh->vertex_uv.exists) {
|
||||
ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, i);
|
||||
uvs[(vertex_offset + i) * 2 + 0] = (float)uv.x;
|
||||
uvs[(vertex_offset + i) * 2 + 1] = (float)uv.y;
|
||||
} else if (uvs) {
|
||||
uvs[(vertex_offset + i) * 2 + 0] = 0;
|
||||
uvs[(vertex_offset + i) * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Triangulate and create indices
|
||||
size_t mesh_index_count = 0;
|
||||
for (size_t fi = 0; fi < mesh->num_faces; fi++) {
|
||||
ufbx_face face = mesh->faces.data[fi];
|
||||
// Triangulate the face
|
||||
for (uint32_t ti = 0; ti < face.num_indices - 2; ti++) {
|
||||
size_t idx = index_offset + mesh_index_count;
|
||||
if (use_32bit) {
|
||||
((uint32_t *)indices)[idx + 0] = (uint32_t)(vertex_offset + face.index_begin);
|
||||
((uint32_t *)indices)[idx + 1] = (uint32_t)(vertex_offset + face.index_begin + ti + 1);
|
||||
((uint32_t *)indices)[idx + 2] = (uint32_t)(vertex_offset + face.index_begin + ti + 2);
|
||||
} else {
|
||||
((uint16_t *)indices)[idx + 0] = (uint16_t)(vertex_offset + face.index_begin);
|
||||
((uint16_t *)indices)[idx + 1] = (uint16_t)(vertex_offset + face.index_begin + ti + 1);
|
||||
((uint16_t *)indices)[idx + 2] = (uint16_t)(vertex_offset + face.index_begin + ti + 2);
|
||||
}
|
||||
mesh_index_count += 3;
|
||||
}
|
||||
}
|
||||
|
||||
mesh_infos[mi].index_count = mesh_index_count;
|
||||
vertex_offset += mesh->num_indices;
|
||||
index_offset += mesh_index_count;
|
||||
}
|
||||
|
||||
// Create buffer
|
||||
JSValue buffers_arr = JS_NewArray(js);
|
||||
JSValue buf = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, buf, "blob", js_new_blob_stoned_copy(js, buffer_data, total_buffer_size));
|
||||
JS_SetPropertyStr(js, buf, "byte_length", JS_NewInt64(js, total_buffer_size));
|
||||
JS_SetPropertyUint32(js, buffers_arr, 0, buf);
|
||||
JS_SetPropertyStr(js, obj, "buffers", buffers_arr);
|
||||
|
||||
// Create views
|
||||
JSValue views_arr = JS_NewArray(js);
|
||||
int view_idx = 0;
|
||||
|
||||
JSValue pos_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, pos_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_length", JS_NewInt64(js, pos_size));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, pos_view, "usage", JS_NewString(js, "vertex"));
|
||||
int pos_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, pos_view);
|
||||
|
||||
int norm_view_idx = -1;
|
||||
if (global_has_normals) {
|
||||
JSValue norm_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, norm_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_offset", JS_NewInt64(js, pos_size));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_length", JS_NewInt64(js, norm_size));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, norm_view, "usage", JS_NewString(js, "vertex"));
|
||||
norm_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, norm_view);
|
||||
}
|
||||
|
||||
int uv_view_idx = -1;
|
||||
if (global_has_uvs) {
|
||||
JSValue uv_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, uv_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_offset", JS_NewInt64(js, pos_size + norm_size));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_length", JS_NewInt64(js, uv_size));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, uv_view, "usage", JS_NewString(js, "vertex"));
|
||||
uv_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, uv_view);
|
||||
}
|
||||
|
||||
JSValue idx_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, idx_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_offset", JS_NewInt64(js, pos_size + norm_size + uv_size));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_length", JS_NewInt64(js, idx_size));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, idx_view, "usage", JS_NewString(js, "index"));
|
||||
int idx_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, idx_view);
|
||||
|
||||
JS_SetPropertyStr(js, obj, "views", views_arr);
|
||||
|
||||
// Create accessors per mesh
|
||||
JSValue accessors_arr = JS_NewArray(js);
|
||||
int acc_idx = 0;
|
||||
|
||||
JSValue meshes_arr = JS_NewArray(js);
|
||||
|
||||
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
||||
ufbx_mesh *mesh = scene->meshes.data[mi];
|
||||
mesh_info_t *info = &mesh_infos[mi];
|
||||
|
||||
// Position accessor
|
||||
int mesh_pos_acc = acc_idx;
|
||||
JSValue spa = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, spa, "view", JS_NewInt32(js, pos_view_idx));
|
||||
JS_SetPropertyStr(js, spa, "byte_offset", JS_NewInt64(js, info->vertex_start * 3 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, spa, "count", JS_NewInt64(js, info->vertex_count));
|
||||
JS_SetPropertyStr(js, spa, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, spa, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, spa, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, spa, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, spa, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, spa);
|
||||
|
||||
int mesh_norm_acc = -1;
|
||||
if (global_has_normals) {
|
||||
mesh_norm_acc = acc_idx;
|
||||
JSValue sna = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sna, "view", JS_NewInt32(js, norm_view_idx));
|
||||
JS_SetPropertyStr(js, sna, "byte_offset", JS_NewInt64(js, info->vertex_start * 3 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, sna, "count", JS_NewInt64(js, info->vertex_count));
|
||||
JS_SetPropertyStr(js, sna, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, sna, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, sna, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sna, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sna, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sna);
|
||||
}
|
||||
|
||||
int mesh_uv_acc = -1;
|
||||
if (global_has_uvs) {
|
||||
mesh_uv_acc = acc_idx;
|
||||
JSValue sua = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sua, "view", JS_NewInt32(js, uv_view_idx));
|
||||
JS_SetPropertyStr(js, sua, "byte_offset", JS_NewInt64(js, info->vertex_start * 2 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, sua, "count", JS_NewInt64(js, info->vertex_count));
|
||||
JS_SetPropertyStr(js, sua, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, sua, "type", JS_NewString(js, "vec2"));
|
||||
JS_SetPropertyStr(js, sua, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sua, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sua, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sua);
|
||||
}
|
||||
|
||||
int mesh_idx_acc = acc_idx;
|
||||
JSValue sia = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sia, "view", JS_NewInt32(js, idx_view_idx));
|
||||
JS_SetPropertyStr(js, sia, "byte_offset", JS_NewInt64(js, info->index_start * (use_32bit ? sizeof(uint32_t) : sizeof(uint16_t))));
|
||||
JS_SetPropertyStr(js, sia, "count", JS_NewInt64(js, info->index_count));
|
||||
JS_SetPropertyStr(js, sia, "component_type", JS_NewString(js, use_32bit ? "u32" : "u16"));
|
||||
JS_SetPropertyStr(js, sia, "type", JS_NewString(js, "scalar"));
|
||||
JS_SetPropertyStr(js, sia, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sia, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sia, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sia);
|
||||
|
||||
// Create mesh
|
||||
JSValue m = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, m, "name", mesh->element.name.length > 0 ? JS_NewString(js, mesh->element.name.data) : JS_NULL);
|
||||
|
||||
JSValue prims_arr = JS_NewArray(js);
|
||||
JSValue prim = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, prim, "topology", JS_NewString(js, "triangles"));
|
||||
|
||||
JSValue attrs = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, attrs, "POSITION", JS_NewInt32(js, mesh_pos_acc));
|
||||
if (mesh_norm_acc >= 0)
|
||||
JS_SetPropertyStr(js, attrs, "NORMAL", JS_NewInt32(js, mesh_norm_acc));
|
||||
if (mesh_uv_acc >= 0)
|
||||
JS_SetPropertyStr(js, attrs, "TEXCOORD_0", JS_NewInt32(js, mesh_uv_acc));
|
||||
JS_SetPropertyStr(js, prim, "attributes", attrs);
|
||||
|
||||
JS_SetPropertyStr(js, prim, "indices", JS_NewInt32(js, mesh_idx_acc));
|
||||
JS_SetPropertyStr(js, prim, "material", mesh->materials.count > 0 ? JS_NewInt32(js, mesh->materials.data[0]->typed_id) : JS_NULL);
|
||||
|
||||
JS_SetPropertyUint32(js, prims_arr, 0, prim);
|
||||
JS_SetPropertyStr(js, m, "primitives", prims_arr);
|
||||
|
||||
JS_SetPropertyUint32(js, meshes_arr, mi, m);
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(js, obj, "accessors", accessors_arr);
|
||||
JS_SetPropertyStr(js, obj, "meshes", meshes_arr);
|
||||
|
||||
// Materials
|
||||
JSValue materials_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < scene->materials.count; i++) {
|
||||
ufbx_material *mat = scene->materials.data[i];
|
||||
JSValue m = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, m, "name", mat->element.name.length > 0 ? JS_NewString(js, mat->element.name.data) : JS_NULL);
|
||||
|
||||
JSValue pbr = JS_NewObject(js);
|
||||
float bc[4] = {
|
||||
(float)mat->pbr.base_color.value_vec4.x,
|
||||
(float)mat->pbr.base_color.value_vec4.y,
|
||||
(float)mat->pbr.base_color.value_vec4.z,
|
||||
(float)mat->pbr.base_color.value_vec4.w
|
||||
};
|
||||
JS_SetPropertyStr(js, pbr, "base_color_factor", make_float_array_f(js, bc, 4));
|
||||
JS_SetPropertyStr(js, pbr, "base_color_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "metallic_factor", JS_NewFloat64(js, mat->pbr.metalness.value_real));
|
||||
JS_SetPropertyStr(js, pbr, "roughness_factor", JS_NewFloat64(js, mat->pbr.roughness.value_real));
|
||||
JS_SetPropertyStr(js, pbr, "metallic_roughness_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "normal_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "occlusion_texture", JS_NULL);
|
||||
float ef[3] = {
|
||||
(float)mat->pbr.emission_color.value_vec3.x,
|
||||
(float)mat->pbr.emission_color.value_vec3.y,
|
||||
(float)mat->pbr.emission_color.value_vec3.z
|
||||
};
|
||||
JS_SetPropertyStr(js, pbr, "emissive_factor", make_float_array_f(js, ef, 3));
|
||||
JS_SetPropertyStr(js, pbr, "emissive_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, m, "pbr", pbr);
|
||||
|
||||
JS_SetPropertyStr(js, m, "alpha_mode", JS_NewString(js, "OPAQUE"));
|
||||
JS_SetPropertyStr(js, m, "alpha_cutoff", JS_NewFloat64(js, 0.5));
|
||||
JS_SetPropertyStr(js, m, "double_sided", JS_FALSE);
|
||||
|
||||
JS_SetPropertyUint32(js, materials_arr, i, m);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "materials", materials_arr);
|
||||
|
||||
// Images/textures (simplified - just list texture files)
|
||||
JSValue images_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < scene->texture_files.count; i++) {
|
||||
ufbx_texture_file *tf = &scene->texture_files.data[i];
|
||||
JSValue im = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, im, "kind", JS_NewString(js, "uri"));
|
||||
JS_SetPropertyStr(js, im, "uri", tf->filename.length > 0 ? JS_NewString(js, tf->filename.data) : JS_NULL);
|
||||
JS_SetPropertyStr(js, im, "mime", JS_NULL);
|
||||
JS_SetPropertyUint32(js, images_arr, i, im);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "images", images_arr);
|
||||
|
||||
JSValue textures_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < scene->textures.count; i++) {
|
||||
ufbx_texture *tex = scene->textures.data[i];
|
||||
JSValue t = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, t, "image", tex->file_index != UFBX_NO_INDEX ? JS_NewInt32(js, tex->file_index) : JS_NULL);
|
||||
JS_SetPropertyStr(js, t, "sampler", JS_NULL);
|
||||
JS_SetPropertyUint32(js, textures_arr, i, t);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "textures", textures_arr);
|
||||
|
||||
JS_SetPropertyStr(js, obj, "samplers", JS_NewArray(js));
|
||||
|
||||
// Nodes
|
||||
JSValue nodes_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < scene->nodes.count; i++) {
|
||||
ufbx_node *node = scene->nodes.data[i];
|
||||
JSValue n = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, n, "name", node->element.name.length > 0 ? JS_NewString(js, node->element.name.data) : JS_NULL);
|
||||
|
||||
// Find mesh index if this node has a mesh
|
||||
int mesh_idx = -1;
|
||||
if (node->mesh) {
|
||||
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
||||
if (scene->meshes.data[mi] == node->mesh) {
|
||||
mesh_idx = (int)mi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
JS_SetPropertyStr(js, n, "mesh", mesh_idx >= 0 ? JS_NewInt32(js, mesh_idx) : JS_NULL);
|
||||
|
||||
JSValue children = JS_NewArray(js);
|
||||
for (size_t ci = 0; ci < node->children.count; ci++) {
|
||||
// Find child node index
|
||||
for (size_t ni = 0; ni < scene->nodes.count; ni++) {
|
||||
if (scene->nodes.data[ni] == node->children.data[ci]) {
|
||||
JS_SetPropertyUint32(js, children, ci, JS_NewInt32(js, ni));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
JS_SetPropertyStr(js, n, "children", children);
|
||||
|
||||
JS_SetPropertyStr(js, n, "matrix", JS_NULL);
|
||||
double t[3] = {node->local_transform.translation.x, node->local_transform.translation.y, node->local_transform.translation.z};
|
||||
double r[4] = {node->local_transform.rotation.x, node->local_transform.rotation.y, node->local_transform.rotation.z, node->local_transform.rotation.w};
|
||||
double s[3] = {node->local_transform.scale.x, node->local_transform.scale.y, node->local_transform.scale.z};
|
||||
JS_SetPropertyStr(js, n, "translation", make_float_array(js, t, 3));
|
||||
JS_SetPropertyStr(js, n, "rotation", make_float_array(js, r, 4));
|
||||
JS_SetPropertyStr(js, n, "scale", make_float_array(js, s, 3));
|
||||
JS_SetPropertyStr(js, n, "skin", JS_NULL);
|
||||
|
||||
JS_SetPropertyUint32(js, nodes_arr, i, n);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "nodes", nodes_arr);
|
||||
|
||||
// Scenes - FBX has one implicit scene with root node
|
||||
JSValue scenes_arr = JS_NewArray(js);
|
||||
JSValue scene_obj = JS_NewObject(js);
|
||||
JSValue scene_nodes = JS_NewArray(js);
|
||||
// Find root node index
|
||||
for (size_t i = 0; i < scene->nodes.count; i++) {
|
||||
if (scene->nodes.data[i] == scene->root_node) {
|
||||
JS_SetPropertyUint32(js, scene_nodes, 0, JS_NewInt32(js, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
JS_SetPropertyStr(js, scene_obj, "nodes", scene_nodes);
|
||||
JS_SetPropertyUint32(js, scenes_arr, 0, scene_obj);
|
||||
JS_SetPropertyStr(js, obj, "scenes", scenes_arr);
|
||||
JS_SetPropertyStr(js, obj, "scene", JS_NewInt32(js, 0));
|
||||
|
||||
// Animations
|
||||
JSValue anims_arr = JS_NewArray(js);
|
||||
for (size_t ai = 0; ai < scene->anim_stacks.count; ai++) {
|
||||
ufbx_anim_stack *stack = scene->anim_stacks.data[ai];
|
||||
JSValue a = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, a, "name", stack->element.name.length > 0 ? JS_NewString(js, stack->element.name.data) : JS_NULL);
|
||||
JS_SetPropertyStr(js, a, "samplers", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, a, "channels", JS_NewArray(js));
|
||||
JS_SetPropertyUint32(js, anims_arr, ai, a);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "animations", anims_arr);
|
||||
|
||||
// Skins
|
||||
JSValue skins_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < scene->skin_deformers.count; i++) {
|
||||
ufbx_skin_deformer *skin = scene->skin_deformers.data[i];
|
||||
JSValue s = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, s, "name", skin->element.name.length > 0 ? JS_NewString(js, skin->element.name.data) : JS_NULL);
|
||||
|
||||
JSValue joints = JS_NewArray(js);
|
||||
for (size_t ci = 0; ci < skin->clusters.count; ci++) {
|
||||
ufbx_skin_cluster *cluster = skin->clusters.data[ci];
|
||||
if (cluster->bone_node) {
|
||||
for (size_t ni = 0; ni < scene->nodes.count; ni++) {
|
||||
if (scene->nodes.data[ni] == cluster->bone_node) {
|
||||
JS_SetPropertyUint32(js, joints, ci, JS_NewInt32(js, ni));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JS_SetPropertyStr(js, s, "joints", joints);
|
||||
JS_SetPropertyStr(js, s, "inverse_bind_matrices", JS_NULL);
|
||||
JS_SetPropertyStr(js, s, "skeleton", JS_NULL);
|
||||
|
||||
JS_SetPropertyUint32(js, skins_arr, i, s);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "skins", skins_arr);
|
||||
|
||||
// Extensions
|
||||
JSValue exts = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, exts, "used", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, exts, "required", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, obj, "extensions", exts);
|
||||
|
||||
free(mesh_infos);
|
||||
free(buffer_data);
|
||||
ufbx_free_scene(scene);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_fbx_funcs[] = {
|
||||
MIST_FUNC_DEF(fbx, decode, 1),
|
||||
};
|
||||
|
||||
CELL_USE_FUNCS(js_fbx_funcs)
|
||||
|
||||
421
gltf.c
Normal file
421
gltf.c
Normal file
@@ -0,0 +1,421 @@
|
||||
#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_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)
|
||||
463
obj.c
463
obj.c
@@ -0,0 +1,463 @@
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TINYOBJ_LOADER_C_IMPLEMENTATION
|
||||
#include "tinyobj_loader_c.h"
|
||||
|
||||
typedef struct {
|
||||
const char *data;
|
||||
size_t len;
|
||||
} obj_reader_ctx_t;
|
||||
|
||||
static void obj_file_reader(void *ctx, const char *filename, int is_mtl, const char *obj_filename, char **buf, size_t *len)
|
||||
{
|
||||
(void)filename;
|
||||
(void)obj_filename;
|
||||
|
||||
if (buf) *buf = NULL;
|
||||
if (len) *len = 0;
|
||||
if (is_mtl) return;
|
||||
|
||||
if (!ctx || !buf || !len) return;
|
||||
|
||||
obj_reader_ctx_t *rctx = (obj_reader_ctx_t *)ctx;
|
||||
*buf = (char *)rctx->data;
|
||||
*len = rctx->len;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
JSValue js_obj_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;
|
||||
|
||||
tinyobj_attrib_t attrib;
|
||||
tinyobj_shape_t *shapes = NULL;
|
||||
tinyobj_material_t *materials = NULL;
|
||||
size_t num_shapes, num_materials;
|
||||
|
||||
obj_reader_ctx_t rctx;
|
||||
rctx.data = (const char *)raw;
|
||||
rctx.len = len;
|
||||
|
||||
int result = tinyobj_parse_obj(&attrib, &shapes, &num_shapes, &materials, &num_materials,
|
||||
"blob.obj", obj_file_reader, &rctx, TINYOBJ_FLAG_TRIANGULATE);
|
||||
|
||||
if (result != TINYOBJ_SUCCESS)
|
||||
return JS_ThrowReferenceError(js, "failed to parse OBJ file");
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
|
||||
// Build unified buffer with all vertex data for all shapes
|
||||
// First pass: count total vertices across all shapes
|
||||
size_t total_vertices = 0;
|
||||
int global_has_normals = 0;
|
||||
int global_has_uvs = 0;
|
||||
|
||||
for (size_t si = 0; si < num_shapes; si++) {
|
||||
tinyobj_shape_t *shape = &shapes[si];
|
||||
total_vertices += shape->length * 3;
|
||||
|
||||
// Check if any shape has normals/uvs
|
||||
for (size_t fi = 0; fi < shape->length; fi++) {
|
||||
size_t face_idx = shape->face_offset + fi;
|
||||
for (int vi = 0; vi < 3; vi++) {
|
||||
tinyobj_vertex_index_t idx = attrib.faces[face_idx * 3 + vi];
|
||||
if (idx.vn_idx >= 0) global_has_normals = 1;
|
||||
if (idx.vt_idx >= 0) global_has_uvs = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate buffer layout
|
||||
size_t pos_size = total_vertices * 3 * sizeof(float);
|
||||
size_t norm_size = global_has_normals ? total_vertices * 3 * sizeof(float) : 0;
|
||||
size_t uv_size = global_has_uvs ? total_vertices * 2 * sizeof(float) : 0;
|
||||
size_t idx_size = total_vertices * (total_vertices > 65535 ? sizeof(uint32_t) : sizeof(uint16_t));
|
||||
int use_32bit = (total_vertices > 65535);
|
||||
|
||||
size_t total_buffer_size = pos_size + norm_size + uv_size + idx_size;
|
||||
|
||||
// Allocate and fill buffer
|
||||
uint8_t *buffer_data = malloc(total_buffer_size);
|
||||
float *positions = (float *)buffer_data;
|
||||
float *normals = global_has_normals ? (float *)(buffer_data + pos_size) : NULL;
|
||||
float *uvs = global_has_uvs ? (float *)(buffer_data + pos_size + norm_size) : NULL;
|
||||
void *indices = buffer_data + pos_size + norm_size + uv_size;
|
||||
|
||||
size_t vertex_offset = 0;
|
||||
for (size_t si = 0; si < num_shapes; si++) {
|
||||
tinyobj_shape_t *shape = &shapes[si];
|
||||
size_t num_faces = shape->length;
|
||||
|
||||
for (size_t fi = 0; fi < num_faces; fi++) {
|
||||
size_t face_idx = shape->face_offset + fi;
|
||||
|
||||
for (int vi = 0; vi < 3; vi++) {
|
||||
tinyobj_vertex_index_t idx = attrib.faces[face_idx * 3 + vi];
|
||||
size_t v = vertex_offset;
|
||||
|
||||
// Position
|
||||
int v_idx = idx.v_idx;
|
||||
if (v_idx >= 0 && v_idx < (int)(attrib.num_vertices)) {
|
||||
positions[v * 3 + 0] = attrib.vertices[v_idx * 3 + 0];
|
||||
positions[v * 3 + 1] = attrib.vertices[v_idx * 3 + 1];
|
||||
positions[v * 3 + 2] = attrib.vertices[v_idx * 3 + 2];
|
||||
} else {
|
||||
positions[v * 3 + 0] = 0;
|
||||
positions[v * 3 + 1] = 0;
|
||||
positions[v * 3 + 2] = 0;
|
||||
}
|
||||
|
||||
// Normal
|
||||
if (normals) {
|
||||
int vn_idx = idx.vn_idx;
|
||||
if (vn_idx >= 0 && vn_idx < (int)(attrib.num_normals)) {
|
||||
normals[v * 3 + 0] = attrib.normals[vn_idx * 3 + 0];
|
||||
normals[v * 3 + 1] = attrib.normals[vn_idx * 3 + 1];
|
||||
normals[v * 3 + 2] = attrib.normals[vn_idx * 3 + 2];
|
||||
} else {
|
||||
normals[v * 3 + 0] = 0;
|
||||
normals[v * 3 + 1] = 1;
|
||||
normals[v * 3 + 2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// UV
|
||||
if (uvs) {
|
||||
int vt_idx = idx.vt_idx;
|
||||
if (vt_idx >= 0 && vt_idx < (int)(attrib.num_texcoords)) {
|
||||
uvs[v * 2 + 0] = attrib.texcoords[vt_idx * 2 + 0];
|
||||
uvs[v * 2 + 1] = attrib.texcoords[vt_idx * 2 + 1];
|
||||
} else {
|
||||
uvs[v * 2 + 0] = 0;
|
||||
uvs[v * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Index
|
||||
if (use_32bit)
|
||||
((uint32_t *)indices)[v] = (uint32_t)v;
|
||||
else
|
||||
((uint16_t *)indices)[v] = (uint16_t)v;
|
||||
|
||||
vertex_offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create buffer
|
||||
JSValue buffers_arr = JS_NewArray(js);
|
||||
JSValue buf = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, buf, "blob", js_new_blob_stoned_copy(js, buffer_data, total_buffer_size));
|
||||
JS_SetPropertyStr(js, buf, "byte_length", JS_NewInt64(js, total_buffer_size));
|
||||
JS_SetPropertyUint32(js, buffers_arr, 0, buf);
|
||||
JS_SetPropertyStr(js, obj, "buffers", buffers_arr);
|
||||
|
||||
// Create views
|
||||
JSValue views_arr = JS_NewArray(js);
|
||||
int view_idx = 0;
|
||||
|
||||
// Position view
|
||||
JSValue pos_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, pos_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_length", JS_NewInt64(js, pos_size));
|
||||
JS_SetPropertyStr(js, pos_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, pos_view, "usage", JS_NewString(js, "vertex"));
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, pos_view);
|
||||
int pos_view_idx = 0;
|
||||
|
||||
int norm_view_idx = -1;
|
||||
if (global_has_normals) {
|
||||
JSValue norm_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, norm_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_offset", JS_NewInt64(js, pos_size));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_length", JS_NewInt64(js, norm_size));
|
||||
JS_SetPropertyStr(js, norm_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, norm_view, "usage", JS_NewString(js, "vertex"));
|
||||
norm_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, norm_view);
|
||||
}
|
||||
|
||||
int uv_view_idx = -1;
|
||||
if (global_has_uvs) {
|
||||
JSValue uv_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, uv_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_offset", JS_NewInt64(js, pos_size + norm_size));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_length", JS_NewInt64(js, uv_size));
|
||||
JS_SetPropertyStr(js, uv_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, uv_view, "usage", JS_NewString(js, "vertex"));
|
||||
uv_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, uv_view);
|
||||
}
|
||||
|
||||
// Index view
|
||||
JSValue idx_view = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, idx_view, "buffer", JS_NewInt32(js, 0));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_offset", JS_NewInt64(js, pos_size + norm_size + uv_size));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_length", JS_NewInt64(js, idx_size));
|
||||
JS_SetPropertyStr(js, idx_view, "byte_stride", JS_NULL);
|
||||
JS_SetPropertyStr(js, idx_view, "usage", JS_NewString(js, "index"));
|
||||
int idx_view_idx = view_idx;
|
||||
JS_SetPropertyUint32(js, views_arr, view_idx++, idx_view);
|
||||
|
||||
JS_SetPropertyStr(js, obj, "views", views_arr);
|
||||
|
||||
// Create accessors
|
||||
JSValue accessors_arr = JS_NewArray(js);
|
||||
int acc_idx = 0;
|
||||
|
||||
// Position accessor
|
||||
JSValue pos_acc = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, pos_acc, "view", JS_NewInt32(js, pos_view_idx));
|
||||
JS_SetPropertyStr(js, pos_acc, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, pos_acc, "count", JS_NewInt64(js, total_vertices));
|
||||
JS_SetPropertyStr(js, pos_acc, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, pos_acc, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, pos_acc, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, pos_acc, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, pos_acc, "max", JS_NULL);
|
||||
int pos_acc_idx = acc_idx;
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, pos_acc);
|
||||
|
||||
int norm_acc_idx = -1;
|
||||
if (global_has_normals) {
|
||||
JSValue norm_acc = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, norm_acc, "view", JS_NewInt32(js, norm_view_idx));
|
||||
JS_SetPropertyStr(js, norm_acc, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, norm_acc, "count", JS_NewInt64(js, total_vertices));
|
||||
JS_SetPropertyStr(js, norm_acc, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, norm_acc, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, norm_acc, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, norm_acc, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, norm_acc, "max", JS_NULL);
|
||||
norm_acc_idx = acc_idx;
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, norm_acc);
|
||||
}
|
||||
|
||||
int uv_acc_idx = -1;
|
||||
if (global_has_uvs) {
|
||||
JSValue uv_acc = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, uv_acc, "view", JS_NewInt32(js, uv_view_idx));
|
||||
JS_SetPropertyStr(js, uv_acc, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, uv_acc, "count", JS_NewInt64(js, total_vertices));
|
||||
JS_SetPropertyStr(js, uv_acc, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, uv_acc, "type", JS_NewString(js, "vec2"));
|
||||
JS_SetPropertyStr(js, uv_acc, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, uv_acc, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, uv_acc, "max", JS_NULL);
|
||||
uv_acc_idx = acc_idx;
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, uv_acc);
|
||||
}
|
||||
|
||||
// Index accessor
|
||||
JSValue idx_acc = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, idx_acc, "view", JS_NewInt32(js, idx_view_idx));
|
||||
JS_SetPropertyStr(js, idx_acc, "byte_offset", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, idx_acc, "count", JS_NewInt64(js, total_vertices));
|
||||
JS_SetPropertyStr(js, idx_acc, "component_type", JS_NewString(js, use_32bit ? "u32" : "u16"));
|
||||
JS_SetPropertyStr(js, idx_acc, "type", JS_NewString(js, "scalar"));
|
||||
JS_SetPropertyStr(js, idx_acc, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, idx_acc, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, idx_acc, "max", JS_NULL);
|
||||
int idx_acc_idx = acc_idx;
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, idx_acc);
|
||||
|
||||
JS_SetPropertyStr(js, obj, "accessors", accessors_arr);
|
||||
|
||||
// Create meshes - each shape becomes a mesh with one primitive
|
||||
// But we need per-shape accessors since they reference different ranges
|
||||
// Actually, for OBJ we'll create per-shape accessors that point to subranges
|
||||
|
||||
// Rebuild accessors per shape
|
||||
JS_FreeValue(js, accessors_arr);
|
||||
accessors_arr = JS_NewArray(js);
|
||||
acc_idx = 0;
|
||||
|
||||
JSValue meshes_arr = JS_NewArray(js);
|
||||
vertex_offset = 0;
|
||||
|
||||
for (size_t si = 0; si < num_shapes; si++) {
|
||||
tinyobj_shape_t *shape = &shapes[si];
|
||||
size_t shape_vertices = shape->length * 3;
|
||||
|
||||
// Create accessors for this shape
|
||||
int shape_pos_acc = acc_idx;
|
||||
JSValue spa = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, spa, "view", JS_NewInt32(js, pos_view_idx));
|
||||
JS_SetPropertyStr(js, spa, "byte_offset", JS_NewInt64(js, vertex_offset * 3 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, spa, "count", JS_NewInt64(js, shape_vertices));
|
||||
JS_SetPropertyStr(js, spa, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, spa, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, spa, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, spa, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, spa, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, spa);
|
||||
|
||||
int shape_norm_acc = -1;
|
||||
if (global_has_normals) {
|
||||
shape_norm_acc = acc_idx;
|
||||
JSValue sna = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sna, "view", JS_NewInt32(js, norm_view_idx));
|
||||
JS_SetPropertyStr(js, sna, "byte_offset", JS_NewInt64(js, vertex_offset * 3 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, sna, "count", JS_NewInt64(js, shape_vertices));
|
||||
JS_SetPropertyStr(js, sna, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, sna, "type", JS_NewString(js, "vec3"));
|
||||
JS_SetPropertyStr(js, sna, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sna, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sna, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sna);
|
||||
}
|
||||
|
||||
int shape_uv_acc = -1;
|
||||
if (global_has_uvs) {
|
||||
shape_uv_acc = acc_idx;
|
||||
JSValue sua = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sua, "view", JS_NewInt32(js, uv_view_idx));
|
||||
JS_SetPropertyStr(js, sua, "byte_offset", JS_NewInt64(js, vertex_offset * 2 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, sua, "count", JS_NewInt64(js, shape_vertices));
|
||||
JS_SetPropertyStr(js, sua, "component_type", JS_NewString(js, "f32"));
|
||||
JS_SetPropertyStr(js, sua, "type", JS_NewString(js, "vec2"));
|
||||
JS_SetPropertyStr(js, sua, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sua, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sua, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sua);
|
||||
}
|
||||
|
||||
int shape_idx_acc = acc_idx;
|
||||
JSValue sia = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, sia, "view", JS_NewInt32(js, idx_view_idx));
|
||||
JS_SetPropertyStr(js, sia, "byte_offset", JS_NewInt64(js, vertex_offset * (use_32bit ? sizeof(uint32_t) : sizeof(uint16_t))));
|
||||
JS_SetPropertyStr(js, sia, "count", JS_NewInt64(js, shape_vertices));
|
||||
JS_SetPropertyStr(js, sia, "component_type", JS_NewString(js, use_32bit ? "u32" : "u16"));
|
||||
JS_SetPropertyStr(js, sia, "type", JS_NewString(js, "scalar"));
|
||||
JS_SetPropertyStr(js, sia, "normalized", JS_FALSE);
|
||||
JS_SetPropertyStr(js, sia, "min", JS_NULL);
|
||||
JS_SetPropertyStr(js, sia, "max", JS_NULL);
|
||||
JS_SetPropertyUint32(js, accessors_arr, acc_idx++, sia);
|
||||
|
||||
// Create mesh
|
||||
JSValue mesh = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, mesh, "name", shape->name ? JS_NewString(js, shape->name) : JS_NULL);
|
||||
|
||||
JSValue prims_arr = JS_NewArray(js);
|
||||
JSValue prim = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, prim, "topology", JS_NewString(js, "triangles"));
|
||||
|
||||
JSValue attrs = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, attrs, "POSITION", JS_NewInt32(js, shape_pos_acc));
|
||||
if (shape_norm_acc >= 0)
|
||||
JS_SetPropertyStr(js, attrs, "NORMAL", JS_NewInt32(js, shape_norm_acc));
|
||||
if (shape_uv_acc >= 0)
|
||||
JS_SetPropertyStr(js, attrs, "TEXCOORD_0", JS_NewInt32(js, shape_uv_acc));
|
||||
JS_SetPropertyStr(js, prim, "attributes", attrs);
|
||||
|
||||
JS_SetPropertyStr(js, prim, "indices", JS_NewInt32(js, shape_idx_acc));
|
||||
JS_SetPropertyStr(js, prim, "material", JS_NULL);
|
||||
|
||||
JS_SetPropertyUint32(js, prims_arr, 0, prim);
|
||||
JS_SetPropertyStr(js, mesh, "primitives", prims_arr);
|
||||
|
||||
JS_SetPropertyUint32(js, meshes_arr, si, mesh);
|
||||
vertex_offset += shape_vertices;
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(js, obj, "accessors", accessors_arr);
|
||||
JS_SetPropertyStr(js, obj, "meshes", meshes_arr);
|
||||
|
||||
// Materials from OBJ
|
||||
JSValue materials_arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < num_materials; i++) {
|
||||
tinyobj_material_t *mat = &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);
|
||||
float bc[4] = {mat->diffuse[0], mat->diffuse[1], mat->diffuse[2], 1.0f};
|
||||
JS_SetPropertyStr(js, pbr, "base_color_factor", make_float_array(js, bc, 4));
|
||||
JS_SetPropertyStr(js, pbr, "base_color_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "metallic_factor", JS_NewFloat64(js, 0.0));
|
||||
JS_SetPropertyStr(js, pbr, "roughness_factor", JS_NewFloat64(js, 1.0));
|
||||
JS_SetPropertyStr(js, pbr, "metallic_roughness_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "normal_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, pbr, "occlusion_texture", JS_NULL);
|
||||
float ef[3] = {mat->emission[0], mat->emission[1], mat->emission[2]};
|
||||
JS_SetPropertyStr(js, pbr, "emissive_factor", make_float_array(js, ef, 3));
|
||||
JS_SetPropertyStr(js, pbr, "emissive_texture", JS_NULL);
|
||||
JS_SetPropertyStr(js, m, "pbr", pbr);
|
||||
|
||||
JS_SetPropertyStr(js, m, "alpha_mode", JS_NewString(js, "OPAQUE"));
|
||||
JS_SetPropertyStr(js, m, "alpha_cutoff", JS_NewFloat64(js, 0.5));
|
||||
JS_SetPropertyStr(js, m, "double_sided", JS_FALSE);
|
||||
|
||||
JS_SetPropertyUint32(js, materials_arr, i, m);
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "materials", materials_arr);
|
||||
|
||||
// Empty arrays for unsupported features
|
||||
JS_SetPropertyStr(js, obj, "images", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, obj, "textures", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, obj, "samplers", JS_NewArray(js));
|
||||
|
||||
// Nodes - one node per mesh
|
||||
JSValue nodes_arr = JS_NewArray(js);
|
||||
JSValue scene_nodes = JS_NewArray(js);
|
||||
for (size_t i = 0; i < num_shapes; i++) {
|
||||
JSValue n = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, n, "name", shapes[i].name ? JS_NewString(js, shapes[i].name) : JS_NULL);
|
||||
JS_SetPropertyStr(js, n, "mesh", JS_NewInt32(js, i));
|
||||
JS_SetPropertyStr(js, n, "children", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, n, "matrix", JS_NULL);
|
||||
float t[3] = {0, 0, 0};
|
||||
float r[4] = {0, 0, 0, 1};
|
||||
float s[3] = {1, 1, 1};
|
||||
JS_SetPropertyStr(js, n, "translation", make_float_array(js, t, 3));
|
||||
JS_SetPropertyStr(js, n, "rotation", make_float_array(js, r, 4));
|
||||
JS_SetPropertyStr(js, n, "scale", make_float_array(js, s, 3));
|
||||
JS_SetPropertyStr(js, n, "skin", JS_NULL);
|
||||
JS_SetPropertyUint32(js, nodes_arr, i, n);
|
||||
JS_SetPropertyUint32(js, scene_nodes, i, JS_NewInt32(js, i));
|
||||
}
|
||||
JS_SetPropertyStr(js, obj, "nodes", nodes_arr);
|
||||
|
||||
// Scenes
|
||||
JSValue scenes_arr = JS_NewArray(js);
|
||||
JSValue scene = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, scene, "nodes", scene_nodes);
|
||||
JS_SetPropertyUint32(js, scenes_arr, 0, scene);
|
||||
JS_SetPropertyStr(js, obj, "scenes", scenes_arr);
|
||||
JS_SetPropertyStr(js, obj, "scene", JS_NewInt32(js, 0));
|
||||
|
||||
// Empty arrays for animations/skins
|
||||
JS_SetPropertyStr(js, obj, "animations", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, obj, "skins", JS_NewArray(js));
|
||||
|
||||
// Extensions
|
||||
JSValue exts = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, exts, "used", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, exts, "required", JS_NewArray(js));
|
||||
JS_SetPropertyStr(js, obj, "extensions", exts);
|
||||
|
||||
free(buffer_data);
|
||||
tinyobj_attrib_free(&attrib);
|
||||
tinyobj_shapes_free(shapes, num_shapes);
|
||||
tinyobj_materials_free(materials, num_materials);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_obj_funcs[] = {
|
||||
MIST_FUNC_DEF(obj, decode, 1),
|
||||
};
|
||||
|
||||
CELL_USE_FUNCS(js_obj_funcs)
|
||||
|
||||
181
tests/readout.ce
Normal file
181
tests/readout.ce
Normal file
@@ -0,0 +1,181 @@
|
||||
var gltf = use('gltf')
|
||||
var obj = use('obj')
|
||||
var fbx = use('fbx')
|
||||
var fs = use('cellfs')
|
||||
|
||||
if (args.length < 1) {
|
||||
log.console("Usage: cell run tests/readout.ce <model_file>")
|
||||
log.console("Supported formats: .gltf, .glb, .obj, .fbx")
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
var filepath = args[0]
|
||||
var dot = filepath.lastIndexOf('.')
|
||||
var ext = dot >= 0 ? filepath.slice(dot).toLowerCase() : ''
|
||||
|
||||
var data = fs.slurp(filepath)
|
||||
if (!data) {
|
||||
log.console(`Error: Could not read file: ${filepath}`)
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
var model = null
|
||||
if (ext == '.gltf' || ext == '.glb') {
|
||||
model = gltf.decode(data)
|
||||
} else if (ext == '.obj') {
|
||||
model = obj.decode(data)
|
||||
} else if (ext == '.fbx') {
|
||||
model = fbx.decode(data)
|
||||
} else {
|
||||
log.console(`Error: Unsupported file format: ${ext}`)
|
||||
log.console("Supported formats: .gltf, .glb, .obj, .fbx")
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
def print_array = function(arr, indent) {
|
||||
if (!arr || arr.length == 0) {
|
||||
log.console(`${indent}(empty)`)
|
||||
return
|
||||
}
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var item = arr[i]
|
||||
if (typeof item == 'object' && item != null) {
|
||||
log.console(`${indent}[${i}]:`)
|
||||
print_object(item, indent + " ")
|
||||
} else {
|
||||
log.console(`${indent}[${i}]: ${item}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def print_object = function(obj, indent) {
|
||||
if (!indent) indent = ""
|
||||
var keys = Object.keys(obj)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var val = obj[key]
|
||||
if (val == null) {
|
||||
log.console(`${indent}${key}: null`)
|
||||
} else if (typeof val == 'object') {
|
||||
if (val.constructor && val.constructor.name == 'Blob') {
|
||||
log.console(`${indent}${key}: <Blob ${val.length} bytes>`)
|
||||
} else if (Array.isArray(val)) {
|
||||
if (val.length == 0) {
|
||||
log.console(`${indent}${key}: []`)
|
||||
} else if (val.length <= 4 && typeof val[0] != 'object') {
|
||||
log.console(`${indent}${key}: [${val.join(', ')}]`)
|
||||
} else if (typeof val[0] != 'object') {
|
||||
log.console(`${indent}${key}: [${val.slice(0, 4).join(', ')}...] (${val.length} items)`)
|
||||
} else {
|
||||
log.console(`${indent}${key}: (${val.length} items)`)
|
||||
print_array(val, indent + " ")
|
||||
}
|
||||
} else {
|
||||
log.console(`${indent}${key}:`)
|
||||
print_object(val, indent + " ")
|
||||
}
|
||||
} else {
|
||||
log.console(`${indent}${key}: ${val}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.console(`\n=== Model Structure: ${filepath} ===\n`)
|
||||
|
||||
log.console(`Buffers: ${model.buffers.length}`)
|
||||
for (var i = 0; i < model.buffers.length; i++) {
|
||||
var buf = model.buffers[i]
|
||||
log.console(` [${i}] byte_length: ${buf.byte_length}`)
|
||||
}
|
||||
|
||||
log.console(`\nViews: ${model.views.length}`)
|
||||
for (var i = 0; i < model.views.length; i++) {
|
||||
var v = model.views[i]
|
||||
log.console(` [${i}] buffer: ${v.buffer}, offset: ${v.byte_offset}, length: ${v.byte_length}, stride: ${v.byte_stride}, usage: ${v.usage}`)
|
||||
}
|
||||
|
||||
log.console(`\nAccessors: ${model.accessors.length}`)
|
||||
for (var i = 0; i < model.accessors.length; i++) {
|
||||
var a = model.accessors[i]
|
||||
log.console(` [${i}] view: ${a.view}, offset: ${a.byte_offset}, count: ${a.count}, type: ${a.component_type}/${a.type}, normalized: ${a.normalized}`)
|
||||
}
|
||||
|
||||
log.console(`\nMeshes: ${model.meshes.length}`)
|
||||
for (var i = 0; i < model.meshes.length; i++) {
|
||||
var m = model.meshes[i]
|
||||
log.console(` [${i}] name: ${m.name}`)
|
||||
for (var j = 0; j < m.primitives.length; j++) {
|
||||
var p = m.primitives[j]
|
||||
log.console(` primitive[${j}]: topology: ${p.topology}, indices: ${p.indices}, material: ${p.material}`)
|
||||
var ak = Object.keys(p.attributes)
|
||||
var parts = []
|
||||
for (var k = 0; k < ak.length; k++) parts.push(ak[k] + ': ' + text(p.attributes[ak[k]]))
|
||||
log.console(' attributes: {' + parts.join(', ') + '}')
|
||||
}
|
||||
}
|
||||
|
||||
log.console(`\nMaterials: ${model.materials.length}`)
|
||||
for (var i = 0; i < model.materials.length; i++) {
|
||||
var m = model.materials[i]
|
||||
log.console(` [${i}] name: ${m.name}, alpha_mode: ${m.alpha_mode}, double_sided: ${m.double_sided}`)
|
||||
if (m.pbr) {
|
||||
log.console(` pbr: base_color: [${m.pbr.base_color_factor}], metallic: ${m.pbr.metallic_factor}, roughness: ${m.pbr.roughness_factor}`)
|
||||
}
|
||||
}
|
||||
|
||||
log.console(`\nImages: ${model.images.length}`)
|
||||
for (var i = 0; i < model.images.length; i++) {
|
||||
var im = model.images[i]
|
||||
log.console(` [${i}] kind: ${im.kind}, ${im.kind == 'uri' ? 'uri: ' + im.uri : 'view: ' + im.view}, mime: ${im.mime}`)
|
||||
}
|
||||
|
||||
log.console(`\nTextures: ${model.textures.length}`)
|
||||
for (var i = 0; i < model.textures.length; i++) {
|
||||
var t = model.textures[i]
|
||||
log.console(` [${i}] image: ${t.image}, sampler: ${t.sampler}`)
|
||||
}
|
||||
|
||||
log.console(`\nSamplers: ${model.samplers.length}`)
|
||||
for (var i = 0; i < model.samplers.length; i++) {
|
||||
var s = model.samplers[i]
|
||||
log.console(` [${i}] min: ${s.min_filter}, mag: ${s.mag_filter}, wrap_s: ${s.wrap_s}, wrap_t: ${s.wrap_t}`)
|
||||
}
|
||||
|
||||
log.console(`\nNodes: ${model.nodes.length}`)
|
||||
for (var i = 0; i < model.nodes.length; i++) {
|
||||
var n = model.nodes[i]
|
||||
log.console(` [${i}] name: ${n.name}, mesh: ${n.mesh}, children: [${n.children}], skin: ${n.skin}`)
|
||||
if (n.matrix) {
|
||||
log.console(` matrix: [${n.matrix.slice(0, 4)}...]`)
|
||||
} else {
|
||||
log.console(` T: [${n.translation}], R: [${n.rotation}], S: [${n.scale}]`)
|
||||
}
|
||||
}
|
||||
|
||||
log.console(`\nScenes: ${model.scenes.length}, default: ${model.scene}`)
|
||||
for (var i = 0; i < model.scenes.length; i++) {
|
||||
var s = model.scenes[i]
|
||||
log.console(` [${i}] nodes: [${s.nodes}]`)
|
||||
}
|
||||
|
||||
log.console(`\nAnimations: ${model.animations.length}`)
|
||||
for (var i = 0; i < model.animations.length; i++) {
|
||||
var a = model.animations[i]
|
||||
log.console(` [${i}] name: ${a.name}, samplers: ${a.samplers.length}, channels: ${a.channels.length}`)
|
||||
}
|
||||
|
||||
log.console(`\nSkins: ${model.skins.length}`)
|
||||
for (var i = 0; i < model.skins.length; i++) {
|
||||
var s = model.skins[i]
|
||||
log.console(` [${i}] name: ${s.name}, joints: ${s.joints.length}, ibm: ${s.inverse_bind_matrices}, skeleton: ${s.skeleton}`)
|
||||
}
|
||||
|
||||
log.console(`\nExtensions:`)
|
||||
log.console(` used: [${model.extensions.used}]`)
|
||||
log.console(` required: [${model.extensions.required}]`)
|
||||
|
||||
log.console(`\n=== End ===\n`)
|
||||
|
||||
log.console(json.encode(model))
|
||||
|
||||
$_.stop()
|
||||
Reference in New Issue
Block a user