471 lines
19 KiB
C
471 lines
19 KiB
C
#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)
|