#ifndef UFBXT_CHECK_SCENE_H_INCLUDED #define UFBXT_CHECK_SCENE_H_INCLUDED #ifndef ufbxt_assert #include #define ufbxt_assert(cond) assert(cond) #endif #ifndef ufbxt_arraycount #define ufbxt_arraycount(arr) (sizeof(arr) / sizeof(*(arr))) #endif static uint32_t ufbxt_sink = 0; #include #include #include "../ufbx.h" static int ufbxt_is_utf8(const char *str, size_t length) { // Table of valid UTF-8 byte sequences: {min,max} (inclusive) // Adapted from https://www.w3.org/International/questions/qa-forms-utf-8 static const unsigned char utf8_forms[8][4][2] = { { {0xc2,0xdf}, {0x80,0xbf}, }, // Non-overlong 2-byte { {0xe0,0xe0}, {0xa0,0xbf}, {0x80,0xbf}, }, // Excluding overlongs { {0xe1,0xec}, {0x80,0xbf}, {0x80,0xbf}, }, // Straight 3-byte { {0xee,0xef}, {0x80,0xbf}, {0x80,0xbf}, }, // Straight 3-byte { {0xed,0xed}, {0x80,0x9f}, {0x80,0xbf}, }, // Excluding surrogates { {0xf0,0xf0}, {0x90,0xbf}, {0x80,0xbf}, {0x80,0xbf}, }, // Planes 1-3 { {0xf1,0xf3}, {0x80,0xbf}, {0x80,0xbf}, {0x80,0xbf}, }, // Planes 4-15 { {0xf4,0xf4}, {0x80,0x8f}, {0x80,0xbf}, {0x80,0xbf}, }, // Plane 16 }; size_t pos = 0, form, len; while (pos < length) { unsigned char c = (unsigned char)str[pos]; // Fast path for ASCII (U+0000 excluded in ufbx) if (c >= 0x01 && c <= 0x7f) { pos += 1; continue; } // All forms can be distinguished from the leading byte for (form = 0; form < 8; form++) { const unsigned char *range = utf8_forms[form][0]; // Check that the first byte is within range if (c >= range[0] && c <= range[1]) break; } // Invalid starting byte if (form == 8) return 0; // Validate the following 1-3 bytes for (len = 1; len < 4; len++) { const unsigned char *range = utf8_forms[form][len]; // Forms end with max=0 (zero initialized) if (range[1] == 0) break; // Out of bounds if (pos + len >= length) return 0; // Check that the next byte is within range c = (unsigned char)str[pos + len]; if (c < utf8_forms[form][len][0] || c > utf8_forms[form][len][1]) return 0; } // Advance to the next codepoint pos += len; } return 1; } static bool ufbxt_float_equal(double a, double b) { if (isnan(a)) { return isnan(b); } else { return a == b; } } static void ufbxt_check_string(ufbx_string str) { // Data may never be NULL, empty strings should have data = "" ufbxt_assert(str.data != NULL); ufbxt_assert(strlen(str.data) == str.length); // `ufbx_string` is always UTF-8 ufbxt_assert(ufbxt_is_utf8(str.data, str.length)); } static void ufbxt_check_memory(const void *data, size_t size) { const uint8_t *d = (const uint8_t*)data; for (size_t i = 0; i < size; i++) { ufbxt_sink += (uint32_t)d[i]; } } static void ufbxt_check_blob(ufbx_blob blob) { ufbxt_check_memory(blob.data, blob.size); } #define ufbxt_check_list(list) ufbxt_check_memory((list).data, (list).count * sizeof(*(list).data)) static void ufbxt_check_element_ptr_any(ufbx_scene *scene, ufbx_element *element) { if (!element) return; ufbxt_assert(scene->elements.data[element->element_id] == element); ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element); } static void ufbxt_check_element_ptr(ufbx_scene *scene, void *v_element, ufbx_element_type type) { if (!v_element) return; ufbx_element *element = (ufbx_element*)v_element; ufbxt_assert(element->type == type); ufbxt_assert(scene->elements.data[element->element_id] == element); ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element); } static void ufbxt_check_vertex_element(ufbx_scene *scene, ufbx_mesh *mesh, void *void_elem, size_t elem_size) { ufbx_vertex_attrib *elem = (ufbx_vertex_attrib*)void_elem; if (!elem->exists) { ufbxt_assert(elem->values.data == NULL); ufbxt_assert(elem->values.count == 0); ufbxt_assert(elem->indices.data == NULL); ufbxt_assert(elem->indices.count == 0); return; } ufbxt_assert(elem->values.count >= 0); ufbxt_assert(elem->values.count <= INT32_MAX); ufbxt_assert(elem->indices.count == mesh->num_indices); if (mesh->num_indices > 0) { ufbxt_assert(elem->indices.data != NULL); } // Check that the indices are in range for (size_t i = 0; i < mesh->num_indices; i++) { uint32_t ix = elem->indices.data[i]; ufbxt_assert(ix < elem->values.count || (ix == UFBX_NO_INDEX && scene->metadata.may_contain_no_index)); } // Check that the data at invalid index is valid and zero if (elem->indices.count > 0) { char zero[32] = { 0 }; ufbxt_assert(elem_size <= 32); ufbxt_assert(!memcmp((char*)elem->values.data - elem_size, zero, elem_size)); } } static void ufbxt_check_mesh_list_count(ufbx_scene *scene, ufbx_mesh *mesh, size_t count, size_t required_count, bool optional) { if (count > 0 || !optional) { ufbxt_assert(count == required_count); } } #define ufbxt_check_mesh_list(scene, mesh, list, required_count, optional) do { \ ufbxt_check_list(list); \ ufbxt_check_mesh_list_count((scene), (mesh), (list).count, (required_count), (optional)); \ } while (0) static void ufbxt_check_props(ufbx_scene *scene, const ufbx_props *props, bool top) { ufbx_prop *prev = NULL; for (size_t i = 0; i < props->props.count; i++) { ufbx_prop *prop = &props->props.data[i]; ufbxt_assert(prop->type < UFBX_PROP_TYPE_COUNT); ufbxt_check_string(prop->name); ufbxt_check_string(prop->value_str); ufbxt_check_blob(prop->value_blob); // Properties should be sorted by name if (prev) { ufbxt_assert(prop->_internal_key >= prev->_internal_key); ufbxt_assert(strcmp(prop->name.data, prev->name.data) >= 0); } ufbx_prop *ref = ufbx_find_prop(props, prop->name.data); if (top) { ufbxt_assert(ref == prop); } else { ufbxt_assert(ref != NULL); } // `REAL/VEC2/VEC3/VEC4` are mutually exclusive uint32_t vec_flag = (uint32_t)prop->flags & (UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_VEC2|UFBX_PROP_FLAG_VALUE_VEC3|UFBX_PROP_FLAG_VALUE_VEC4); ufbxt_assert((vec_flag & (vec_flag - 1)) == 0); prev = prop; } if (props->defaults) { ufbxt_check_props(scene, props->defaults, false); } } static void ufbxt_check_element(ufbx_scene *scene, ufbx_element *element) { if (scene->dom_root && scene->metadata.file_format == UFBX_FILE_FORMAT_FBX) { bool requires_dom = true; uint32_t version = scene->metadata.version; if (element->type == UFBX_ELEMENT_NODE && ((ufbx_node*)element)->is_root) { requires_dom = false; } else if (version < 6000 && element->type == UFBX_ELEMENT_ANIM_STACK) { requires_dom = false; } else if (version < 6000 && element->type == UFBX_ELEMENT_ANIM_LAYER) { requires_dom = false; } else if (version < 6000 && element->type == UFBX_ELEMENT_SKIN_DEFORMER) { requires_dom = false; } if (requires_dom) { ufbxt_assert(element->dom_node); } } ufbxt_check_props(scene, &element->props, true); ufbxt_check_string(element->name); ufbxt_assert(scene->elements.data[element->element_id] == element); ufbxt_assert(scene->elements.data[element->element_id] == element); ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element); for (size_t i = 0; i < element->connections_src.count; i++) { ufbx_connection *c = &element->connections_src.data[i]; ufbxt_check_string(c->src_prop); ufbxt_check_string(c->dst_prop); ufbxt_assert(c->src == element); ufbxt_check_element_ptr_any(scene, c->dst); if (i > 0) { int cmp = strcmp(c[-1].src_prop.data, c[0].src_prop.data); ufbxt_assert(cmp <= 0); if (cmp == 0) { ufbxt_assert(strcmp(c[-1].dst_prop.data, c[0].dst_prop.data) <= 0); } } } for (size_t i = 0; i < element->connections_dst.count; i++) { ufbx_connection *c = &element->connections_dst.data[i]; ufbxt_check_string(c->src_prop); ufbxt_check_string(c->dst_prop); ufbxt_assert(c->dst == element); ufbxt_check_element_ptr_any(scene, c->src); if (i > 0) { int cmp = strcmp(c[-1].dst_prop.data, c[0].dst_prop.data); ufbxt_assert(cmp <= 0); if (cmp == 0) { ufbxt_assert(strcmp(c[-1].src_prop.data, c[0].src_prop.data) <= 0); } } } ufbxt_assert(element->type >= 0); ufbxt_assert(element->type < UFBX_ELEMENT_TYPE_COUNT); if (element->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && element->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { for (size_t i = 0; i < element->instances.count; i++) { ufbx_node *node = element->instances.data[i]; ufbxt_check_element_ptr(scene, node, UFBX_ELEMENT_NODE); bool found = false; for (size_t j = 0; j < node->all_attribs.count; j++) { if (node->all_attribs.data[j] == element) { found = true; break; } } ufbxt_assert(found); } } else { ufbxt_assert(element->instances.count == 0); } } static void ufbxt_check_unknown(ufbx_scene *scene, ufbx_unknown *unknown) { ufbxt_check_string(unknown->type); ufbxt_check_string(unknown->sub_type); ufbxt_check_string(unknown->super_type); } static void ufbxt_check_node(ufbx_scene *scene, ufbx_node *node) { ufbxt_check_element_ptr(scene, node->parent, UFBX_ELEMENT_NODE); if (node->parent) { bool found = false; for (size_t i = 0; i < node->parent->children.count; i++) { if (node->parent->children.data[i] == node) { found = true; break; } } ufbxt_assert(found); } for (size_t i = 0; i < node->children.count; i++) { ufbxt_assert(node->children.data[i]->parent == node); } for (size_t i = 0; i < node->all_attribs.count; i++) { ufbx_element *attrib = node->all_attribs.data[i]; ufbxt_check_element_ptr_any(scene, attrib); bool found = false; for (size_t j = 0; j < attrib->instances.count; j++) { if (attrib->instances.data[j] == node) { found = true; break; } } ufbxt_assert(found); } if (node->all_attribs.count > 0) { ufbxt_assert(node->attrib == node->all_attribs.data[0]); if (node->all_attribs.count == 1) { ufbxt_assert(node->attrib_type == node->attrib->type); } } switch (node->attrib_type) { case UFBX_ELEMENT_MESH: ufbxt_assert(node->mesh); break; case UFBX_ELEMENT_LIGHT: ufbxt_assert(node->light); break; case UFBX_ELEMENT_CAMERA: ufbxt_assert(node->camera); break; case UFBX_ELEMENT_BONE: ufbxt_assert(node->bone); break; default: /* No shorthand */ break; } for (size_t i = 0; i < node->materials.count; i++) { ufbxt_assert(node->materials.data[i]); ufbxt_check_element_ptr(scene, node->materials.data[i], UFBX_ELEMENT_MATERIAL); } ufbxt_assert((uint32_t)node->attrib_type < (uint32_t)UFBX_ELEMENT_TYPE_COUNT); ufbxt_assert((uint32_t)node->inherit_type < (uint32_t)UFBX_INHERIT_TYPE_COUNT); if (node->is_root) { ufbxt_assert(!node->has_geometry_transform); } ufbxt_check_element_ptr(scene, node->geometry_transform_helper, UFBX_ELEMENT_NODE); if (node->geometry_transform_helper) { ufbxt_assert(node->geometry_transform_helper->is_geometry_transform_helper); if (scene->metadata.has_warning[UFBX_WARNING_DUPLICATE_OBJECT_ID]) { // In broken cases we may have multiple geometry transform helpers ufbxt_assert(node->children.count > 0); ufbxt_assert(node->children.data[0]->is_geometry_transform_helper); } else { // Geometry transform helper must always be the first child if the scene ufbxt_assert(node->geometry_transform_helper == node->children.data[0]); } } if (node->is_geometry_transform_helper) { ufbxt_assert(node->parent); if (scene->metadata.has_warning[UFBX_WARNING_DUPLICATE_OBJECT_ID]) { // In broken cases we may have multiple geometry transform helpers ufbxt_assert(node->parent->geometry_transform_helper); } else { ufbxt_assert(node->parent->geometry_transform_helper == node); } } } static void ufbxt_check_mesh(ufbx_scene *scene, ufbx_mesh *mesh) { // ufbx_mesh *found = ufbx_find_mesh(scene, mesh->node.name.data); // ufbxt_assert(found && !strcmp(found->node.name.data, mesh->node.name.data)); ufbxt_assert(mesh->vertices.data == mesh->vertex_position.values.data); ufbxt_assert(mesh->vertices.count == mesh->num_vertices); ufbxt_assert(mesh->vertex_indices.data == mesh->vertex_position.indices.data); ufbxt_assert(mesh->vertex_indices.count == mesh->num_indices); ufbxt_assert(mesh->vertex_first_index.count == mesh->num_vertices); for (size_t ii = 0; ii < mesh->num_indices; ii++) { uint32_t vi = mesh->vertex_indices.data[ii]; if (vi != UFBX_NO_INDEX) { ufbxt_assert(vi < mesh->num_vertices); ufbxt_assert(mesh->vertex_first_index.data[vi] <= ii); } } for (size_t vi = 0; vi < mesh->num_vertices; vi++) { int32_t ii = (int32_t)mesh->vertex_first_index.data[vi]; if (ii >= 0) { ufbxt_assert(mesh->vertex_indices.data[ii] == vi); } else { ufbxt_assert((uint32_t)ii == UFBX_NO_INDEX); } } ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_position, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_normal, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_tangent, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_bitangent, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_uv, sizeof(ufbx_vec2)); ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_color, sizeof(ufbx_vec4)); ufbxt_check_vertex_element(scene, mesh, &mesh->skinned_position, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &mesh->skinned_normal, sizeof(ufbx_vec3)); ufbxt_assert(mesh->num_vertices == mesh->vertex_position.values.count); ufbxt_assert(mesh->num_triangles <= mesh->num_indices); ufbxt_assert(mesh->vertex_position.value_reals == 3); ufbxt_assert(mesh->vertex_normal.value_reals == 3); ufbxt_assert(mesh->vertex_tangent.value_reals == 3); ufbxt_assert(mesh->vertex_bitangent.value_reals == 3); ufbxt_assert(mesh->vertex_uv.value_reals == 2); ufbxt_assert(mesh->vertex_color.value_reals == 4); ufbxt_assert(mesh->vertex_crease.value_reals == 1); if (!scene->metadata.may_contain_missing_vertex_position) { ufbxt_assert(mesh->vertex_position.exists); } if (mesh->vertex_position.exists) { ufbxt_assert(mesh->vertex_position.unique_per_vertex); } ufbxt_check_mesh_list(scene, mesh, mesh->edges, mesh->num_edges, false); ufbxt_check_mesh_list(scene, mesh, mesh->edge_crease, mesh->num_edges, true); ufbxt_check_mesh_list(scene, mesh, mesh->edge_smoothing, mesh->num_edges, true); ufbxt_check_mesh_list(scene, mesh, mesh->edge_visibility, mesh->num_edges, true); ufbxt_check_mesh_list(scene, mesh, mesh->face_material, mesh->num_faces, true); ufbxt_check_mesh_list(scene, mesh, mesh->face_smoothing, mesh->num_faces, true); ufbxt_check_mesh_list(scene, mesh, mesh->face_group, mesh->num_faces, true); ufbxt_check_mesh_list(scene, mesh, mesh->face_hole, mesh->num_faces, true); size_t num_triangles = 0; size_t max_face_triangles = 0; size_t num_bad_faces[3] = { 0 }; uint32_t prev_end = 0; ufbxt_assert(mesh->faces.count == mesh->num_faces); for (size_t i = 0; i < mesh->num_faces; i++) { ufbx_face face = mesh->faces.data[i]; ufbxt_assert(face.index_begin == prev_end); prev_end = face.index_begin + face.num_indices; ufbxt_assert(prev_end <= mesh->num_indices); if (face.num_indices >= 3) { size_t tris = face.num_indices - 2; num_triangles += tris; if (tris > max_face_triangles) { max_face_triangles = tris; } } else { num_bad_faces[face.num_indices]++; } } ufbxt_assert(mesh->num_triangles == num_triangles); ufbxt_assert(mesh->max_face_triangles == max_face_triangles); ufbxt_assert(mesh->num_empty_faces == num_bad_faces[0]); ufbxt_assert(mesh->num_point_faces == num_bad_faces[1]); ufbxt_assert(mesh->num_line_faces == num_bad_faces[2]); if (!mesh->from_tessellated_nurbs && !mesh->subdivision_evaluated) { ufbxt_assert(scene->metadata.max_face_triangles >= max_face_triangles); } ufbxt_assert(mesh->edges.count == mesh->num_edges); for (size_t i = 0; i < mesh->num_edges; i++) { ufbx_edge edge = mesh->edges.data[i]; ufbxt_assert(edge.a < mesh->num_indices); ufbxt_assert(edge.b < mesh->num_indices); } for (size_t i = 0; i < mesh->uv_sets.count; i++) { ufbx_uv_set *set = &mesh->uv_sets.data[i]; ufbxt_assert(set->vertex_uv.value_reals == 2); ufbxt_assert(set->vertex_tangent.value_reals == 3); ufbxt_assert(set->vertex_bitangent.value_reals == 3); if (i == 0) { ufbxt_assert(mesh->vertex_uv.values.data == set->vertex_uv.values.data); ufbxt_assert(mesh->vertex_uv.values.count == set->vertex_uv.values.count); ufbxt_assert(mesh->vertex_uv.indices.data == set->vertex_uv.indices.data); ufbxt_assert(mesh->vertex_uv.indices.count == set->vertex_uv.indices.count); } ufbxt_check_string(set->name); ufbxt_check_vertex_element(scene, mesh, &set->vertex_uv, sizeof(ufbx_vec2)); ufbxt_check_vertex_element(scene, mesh, &set->vertex_tangent, sizeof(ufbx_vec3)); ufbxt_check_vertex_element(scene, mesh, &set->vertex_bitangent, sizeof(ufbx_vec3)); } for (size_t i = 0; i < mesh->color_sets.count; i++) { ufbx_color_set *set = &mesh->color_sets.data[i]; ufbxt_assert(set->vertex_color.value_reals == 4); if (i == 0) { ufbxt_assert(mesh->vertex_color.values.data == set->vertex_color.values.data); ufbxt_assert(mesh->vertex_color.values.count == set->vertex_color.values.count); ufbxt_assert(mesh->vertex_color.indices.data == set->vertex_color.indices.data); ufbxt_assert(mesh->vertex_color.indices.count == set->vertex_color.indices.count); } ufbxt_check_string(set->name); ufbxt_check_vertex_element(scene, mesh, &set->vertex_color, sizeof(ufbx_vec4)); } for (size_t i = 0; i < mesh->num_edges; i++) { ufbx_edge edge = mesh->edges.data[i]; ufbxt_assert(edge.a < mesh->num_indices); ufbxt_assert(edge.b < mesh->num_indices); } if (mesh->face_material.count) { ufbxt_assert(mesh->face_material.count == mesh->num_faces); for (size_t i = 0; i < mesh->num_faces; i++) { int32_t material = mesh->face_material.data[i]; ufbxt_assert(material >= 0 && (size_t)material < mesh->materials.count); } } else { ufbxt_assert(mesh->face_material.count == 0); } for (size_t i = 0; i < mesh->materials.count; i++) { ufbx_mesh_material *mat = &mesh->materials.data[i]; ufbxt_check_element_ptr(scene, mat->material, UFBX_ELEMENT_MATERIAL); if (!scene->metadata.may_contain_null_materials) { ufbxt_assert(mat->material); } ufbxt_assert(mat->face_indices.count == mat->num_faces); size_t mat_bad_faces[3] = { 0 }; size_t mat_tris = 0; for (size_t j = 0; j < mat->num_faces; j++) { uint32_t ix = mat->face_indices.data[j]; ufbx_face face = mesh->faces.data[ix]; if (face.num_indices >= 3) { mat_tris += face.num_indices - 2; } else { mat_bad_faces[face.num_indices]++; } ufbxt_assert(mesh->face_material.data[ix] == (int32_t)i); } ufbxt_assert(mat->num_triangles == mat_tris); ufbxt_assert(mat->num_empty_faces == mat_bad_faces[0]); ufbxt_assert(mat->num_point_faces == mat_bad_faces[1]); ufbxt_assert(mat->num_line_faces == mat_bad_faces[2]); } if (mesh->face_group.count) { ufbxt_assert(mesh->face_group.count == mesh->num_faces); for (size_t i = 0; i < mesh->num_faces; i++) { uint32_t group = mesh->face_group.data[i]; ufbxt_assert((size_t)group < mesh->face_groups.count); } } else { ufbxt_assert(mesh->face_groups.count == 0); } if (mesh->face_groups.count > 0) { size_t total_group_faces = 0; size_t total_group_tris = 0; for (size_t i = 0; i < mesh->face_groups.count; i++) { ufbx_face_group *group = &mesh->face_groups.data[i]; if (i > 0) { ufbxt_assert(group->id >= mesh->face_groups.data[i - 1].id); } ufbxt_check_string(group->name); ufbxt_assert(group->face_indices.count == group->num_faces); size_t group_tris = 0; for (size_t j = 0; j < group->num_faces; j++) { uint32_t ix = group->face_indices.data[j]; ufbx_face face = mesh->faces.data[ix]; if (face.num_indices >= 3) { group_tris += face.num_indices - 2; } ufbxt_assert(mesh->face_group.data[ix] == (uint32_t)i); } ufbxt_assert(group->num_triangles == group_tris); total_group_faces += group->num_faces; total_group_tris += group->num_triangles; } ufbxt_assert(total_group_faces == mesh->num_faces); ufbxt_assert(total_group_tris == mesh->num_triangles); } for (size_t i = 0; i < mesh->skin_deformers.count; i++) { ufbxt_assert(mesh->skin_deformers.data[i]->vertices.count >= mesh->num_vertices); ufbxt_check_element_ptr(scene, mesh->skin_deformers.data[i], UFBX_ELEMENT_SKIN_DEFORMER); } for (size_t i = 0; i < mesh->blend_deformers.count; i++) { ufbxt_check_element_ptr(scene, mesh->blend_deformers.data[i], UFBX_ELEMENT_BLEND_DEFORMER); } for (size_t i = 0; i < mesh->cache_deformers.count; i++) { ufbxt_check_element_ptr(scene, mesh->cache_deformers.data[i], UFBX_ELEMENT_CACHE_DEFORMER); } for (size_t i = 0; i < mesh->all_deformers.count; i++) { ufbxt_check_element_ptr_any(scene, mesh->all_deformers.data[i]); } if (!scene->metadata.may_contain_null_materials) { for (size_t i = 0; i < mesh->instances.count; i++) { ufbxt_assert(mesh->instances.data[i]->materials.count >= mesh->materials.count); } } // No loose UV or color if (mesh->vertex_uv.exists) { ufbxt_assert(mesh->uv_sets.count > 0); } if (mesh->vertex_color.exists) { ufbxt_assert(mesh->color_sets.count > 0); } } static void ufbxt_check_light(ufbx_scene *scene, ufbx_light *light) { ufbxt_assert((uint32_t)light->type < (uint32_t)UFBX_LIGHT_TYPE_COUNT); ufbxt_assert((uint32_t)light->decay < (uint32_t)UFBX_LIGHT_DECAY_COUNT); ufbxt_assert((uint32_t)light->area_shape < (uint32_t)UFBX_LIGHT_AREA_SHAPE_COUNT); } static void ufbxt_check_camera(ufbx_scene *scene, ufbx_camera *camera) { ufbxt_assert((uint32_t)camera->projection_mode < (uint32_t)UFBX_PROJECTION_MODE_COUNT); ufbxt_assert((uint32_t)camera->aspect_mode < (uint32_t)UFBX_ASPECT_MODE_COUNT); ufbxt_assert((uint32_t)camera->aperture_mode < (uint32_t)UFBX_APERTURE_MODE_COUNT); ufbxt_assert((uint32_t)camera->gate_fit < (uint32_t)UFBX_GATE_FIT_COUNT); ufbxt_assert((uint32_t)camera->aperture_format < (uint32_t)UFBX_APERTURE_FORMAT_COUNT); if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) { ufbxt_assert(ufbxt_float_equal(camera->projection_plane.x, camera->field_of_view_tan.x)); ufbxt_assert(ufbxt_float_equal(camera->projection_plane.y, camera->field_of_view_tan.y)); } else if (camera->projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC) { ufbxt_assert(ufbxt_float_equal(camera->projection_plane.x, camera->orthographic_size.x)); ufbxt_assert(ufbxt_float_equal(camera->projection_plane.y, camera->orthographic_size.y)); } else { ufbxt_assert(false && "Unhandled projection_mode"); } } static void ufbxt_check_bone(ufbx_scene *scene, ufbx_bone *bone) { } static void ufbxt_check_empty(ufbx_scene *scene, ufbx_empty *empty) { } static void ufbxt_check_line_curve(ufbx_scene *scene, ufbx_line_curve *line) { for (size_t i = 0; i < line->point_indices.count; i++) { int32_t ix = line->point_indices.data[i]; ufbxt_assert(ix >= 0 && (uint32_t)ix < line->control_points.count); } for (size_t i = 0; i < line->segments.count; i++) { ufbx_line_segment seg = line->segments.data[i]; ufbxt_assert(seg.num_indices <= line->point_indices.count); ufbxt_assert(seg.index_begin <= line->point_indices.count - seg.num_indices); } ufbxt_check_list(line->control_points); } static void ufbxt_check_nurbs_basis(ufbx_scene *scene, ufbx_nurbs_basis *basis) { ufbxt_assert((uint32_t)basis->topology < (uint32_t)UFBX_NURBS_TOPOLOGY_COUNT); ufbxt_check_list(basis->knot_vector); ufbxt_check_list(basis->spans); if (basis->valid) { ufbxt_assert(basis->order > 1); ufbxt_assert(basis->knot_vector.count >= 2*(basis->order-1) + 1); } } static void ufbxt_check_nurbs_curve(ufbx_scene *scene, ufbx_nurbs_curve *curve) { ufbxt_check_nurbs_basis(scene, &curve->basis); ufbxt_check_list(curve->control_points); } static void ufbxt_check_nurbs_surface(ufbx_scene *scene, ufbx_nurbs_surface *surface) { ufbxt_check_nurbs_basis(scene, &surface->basis_u); ufbxt_check_nurbs_basis(scene, &surface->basis_v); ufbxt_check_list(surface->control_points); } static void ufbxt_check_nurbs_trim_surface(ufbx_scene *scene, ufbx_nurbs_trim_surface *surface) { } static void ufbxt_check_nurbs_trim_boundary(ufbx_scene *scene, ufbx_nurbs_trim_boundary *boundary) { } static void ufbxt_check_procedural_geometry(ufbx_scene *scene, ufbx_procedural_geometry *geometry) { } static void ufbxt_check_stereo_camera(ufbx_scene *scene, ufbx_stereo_camera *camera) { ufbxt_check_element_ptr(scene, camera->left, UFBX_ELEMENT_CAMERA); ufbxt_check_element_ptr(scene, camera->right, UFBX_ELEMENT_CAMERA); } static void ufbxt_check_camera_switcher(ufbx_scene *scene, ufbx_camera_switcher *switcher) { } static void ufbxt_check_marker(ufbx_scene *scene, ufbx_marker *marker) { ufbxt_assert((uint32_t)marker->type < (uint32_t)UFBX_MARKER_TYPE_COUNT); } static void ufbxt_check_lod_level(ufbx_scene *scene, ufbx_lod_group *lod_group, ufbx_lod_level *level) { ufbxt_assert((uint32_t)level->display < (uint32_t)UFBX_LOD_DISPLAY_COUNT); } static void ufbxt_check_lod_group(ufbx_scene *scene, ufbx_lod_group *lod_group) { for (size_t i = 0; i < lod_group->lod_levels.count; i++) { ufbxt_check_lod_level(scene, lod_group, &lod_group->lod_levels.data[i]); } } static void ufbxt_check_skin_deformer(ufbx_scene *scene, ufbx_skin_deformer *deformer) { for (size_t i = 0; i < deformer->clusters.count; i++) { ufbxt_check_element_ptr(scene, deformer->clusters.data[i], UFBX_ELEMENT_SKIN_CLUSTER); if (!scene->metadata.may_contain_broken_elements) { ufbxt_assert(deformer->clusters.data[i]->bone_node); } } for (size_t i = 0; i < deformer->vertices.count; i++) { ufbx_skin_vertex vertex = deformer->vertices.data[i]; ufbxt_assert(vertex.weight_begin <= deformer->weights.count); ufbxt_assert(deformer->weights.count - vertex.weight_begin >= vertex.num_weights); for (size_t i = 1; i < vertex.num_weights; i++) { size_t ix = vertex.weight_begin + i; ufbxt_assert(deformer->weights.data[ix - 1].weight >= deformer->weights.data[ix].weight); } } for (size_t i = 0; i < deformer->weights.count; i++) { ufbxt_assert(deformer->weights.data[i].cluster_index < deformer->clusters.count); } } static void ufbxt_check_skin_cluster(ufbx_scene *scene, ufbx_skin_cluster *cluster) { ufbxt_check_element_ptr(scene, cluster->bone_node, UFBX_ELEMENT_NODE); ufbxt_check_list(cluster->vertices); ufbxt_check_list(cluster->weights); } static void ufbxt_check_blend_deformer(ufbx_scene *scene, ufbx_blend_deformer *deformer) { for (size_t i = 0; i < deformer->channels.count; i++) { ufbxt_check_element_ptr(scene, deformer->channels.data[i], UFBX_ELEMENT_BLEND_CHANNEL); } } static void ufbxt_check_blend_channel(ufbx_scene *scene, ufbx_blend_channel *channel) { for (size_t i = 0; i < channel->keyframes.count; i++) { if (i > 0) { ufbxt_assert(channel->keyframes.data[i - 1].target_weight <= channel->keyframes.data[i].target_weight); } ufbxt_check_element_ptr(scene, channel->keyframes.data[i].shape, UFBX_ELEMENT_BLEND_SHAPE); } } static void ufbxt_check_blend_shape(ufbx_scene *scene, ufbx_blend_shape *shape) { ufbxt_check_list(shape->offset_vertices); ufbxt_check_list(shape->position_offsets); ufbxt_check_list(shape->normal_offsets); ufbxt_assert(shape->offset_vertices.count == shape->position_offsets.count); if (shape->normal_offsets.count > 0) { ufbxt_assert(shape->normal_offsets.count == shape->offset_vertices.count); } } static void ufbxt_check_cache_deformer(ufbx_scene *scene, ufbx_cache_deformer *deformer) { ufbxt_check_string(deformer->channel); ufbxt_check_element_ptr(scene, deformer->file, UFBX_ELEMENT_CACHE_FILE); } static void ufbxt_check_cache_file(ufbx_scene *scene, ufbx_cache_file *file) { ufbxt_check_string(file->filename); ufbxt_check_string(file->absolute_filename); ufbxt_check_string(file->relative_filename); } static void ufbxt_check_material_map(ufbx_scene *scene, ufbx_material *material, ufbx_material_map *map) { ufbxt_check_element_ptr(scene, map->texture, UFBX_ELEMENT_TEXTURE); ufbxt_assert(map->value_components <= 4); } static void ufbxt_check_material(ufbx_scene *scene, ufbx_material *material) { for (size_t i = 0; i < material->textures.count; i++) { ufbxt_check_string(material->textures.data[i].material_prop); ufbxt_check_string(material->textures.data[i].shader_prop); ufbxt_check_element_ptr(scene, material->textures.data[i].texture, UFBX_ELEMENT_TEXTURE); } for (size_t i = 0; i < UFBX_MATERIAL_FBX_MAP_COUNT; i++) { ufbxt_check_material_map(scene, material, &material->fbx.maps[i]); } for (size_t i = 0; i < UFBX_MATERIAL_PBR_MAP_COUNT; i++) { ufbxt_check_material_map(scene, material, &material->pbr.maps[i]); } ufbxt_check_string(material->shader_prop_prefix); ufbxt_check_element_ptr(scene, material->shader, UFBX_ELEMENT_SHADER); } static void ufbxt_check_shader_texture_input(ufbx_scene *scene, ufbx_texture *texture, ufbx_shader_texture *shader, ufbx_shader_texture_input *input) { ufbxt_check_string(input->name); ufbxt_assert(input->prop); ufbxt_check_string(input->value_str); if (input->prop) { ufbx_prop *prop = input->prop; ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data)); } if (input->texture_prop) { ufbx_prop *prop = input->texture_prop; ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data)); } if (input->texture_enabled_prop) { ufbx_prop *prop = input->texture_enabled_prop; ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data)); } ufbxt_check_element_ptr(scene, input->texture, UFBX_ELEMENT_TEXTURE); } static void ufbxt_check_shader_texture(ufbx_scene *scene, ufbx_texture *texture, ufbx_shader_texture *shader) { ufbxt_check_string(shader->shader_name); ufbxt_check_string(shader->shader_source); ufbxt_check_element_ptr(scene, shader->main_texture, UFBX_ELEMENT_TEXTURE); for (size_t i = 0; i < shader->inputs.count; i++) { ufbxt_check_shader_texture_input(scene, texture, shader, &shader->inputs.data[i]); } } static void ufbxt_check_texture(ufbx_scene *scene, ufbx_texture *texture) { ufbxt_check_string(texture->filename); ufbxt_check_string(texture->relative_filename); ufbxt_check_string(texture->absolute_filename); ufbxt_check_blob(texture->raw_filename); ufbxt_check_blob(texture->raw_relative_filename); ufbxt_check_blob(texture->raw_absolute_filename); ufbxt_check_element_ptr(scene, texture->video, UFBX_ELEMENT_VIDEO); for (size_t i = 0; i < texture->layers.count; i++) { ufbxt_assert(texture->layers.data[i].texture); ufbxt_check_element_ptr(scene, texture->layers.data[i].texture, UFBX_ELEMENT_TEXTURE); } if (texture->shader) { ufbxt_check_shader_texture(scene, texture, texture->shader); } for (size_t i = 0; i < texture->file_textures.count; i++) { ufbx_texture *file = texture->file_textures.data[i]; ufbxt_check_element_ptr(scene, file, UFBX_ELEMENT_TEXTURE); ufbxt_assert(file->type == UFBX_TEXTURE_FILE); } if (texture->type == UFBX_TEXTURE_FILE) { ufbxt_assert(texture->file_textures.count >= 1); ufbxt_assert(texture->file_textures.data[0] == texture); } else if (texture->type == UFBX_TEXTURE_SHADER) { ufbxt_assert(texture->shader); } if (texture->raw_absolute_filename.size > 0 || texture->raw_relative_filename.size > 0) { ufbxt_assert(texture->has_file); } ufbxt_assert(texture->file_index < scene->texture_files.count || texture->file_index == UFBX_NO_INDEX); if (texture->has_file) { ufbxt_assert(texture->file_index < scene->texture_files.count); ufbx_texture_file *file = &scene->texture_files.data[texture->file_index]; ufbxt_assert(!strcmp(file->absolute_filename.data, texture->absolute_filename.data) || !strcmp(file->relative_filename.data, texture->relative_filename.data)); } else { ufbxt_assert(texture->file_index == UFBX_NO_INDEX); } } static void ufbxt_check_video(ufbx_scene *scene, ufbx_video *video) { ufbxt_check_string(video->filename); ufbxt_check_string(video->relative_filename); ufbxt_check_string(video->absolute_filename); ufbxt_check_blob(video->raw_filename); ufbxt_check_blob(video->raw_relative_filename); ufbxt_check_blob(video->raw_absolute_filename); } static void ufbxt_check_shader(ufbx_scene *scene, ufbx_shader *shader) { ufbxt_assert((uint32_t)shader->type < (uint32_t)UFBX_SHADER_TYPE_COUNT); for (size_t i = 0; i < shader->bindings.count; i++) { ufbxt_check_element_ptr(scene, shader->bindings.data[i], UFBX_ELEMENT_SHADER_BINDING); } } static void ufbxt_check_shader_binding(ufbx_scene *scene, ufbx_shader_binding *binding) { for (size_t i = 0; i < binding->prop_bindings.count; i++) { ufbx_shader_prop_binding prop = binding->prop_bindings.data[i]; ufbxt_check_string(prop.material_prop); ufbxt_check_string(prop.shader_prop); } } static void ufbxt_check_anim(ufbx_scene *scene, ufbx_anim *anim) { for (size_t i = 0; i < anim->layers.count; i++) { ufbxt_check_element_ptr(scene, anim->layers.data[i].layer, UFBX_ELEMENT_ANIM_LAYER); } ufbxt_assert(anim->prop_overrides.count == 0); } static void ufbxt_check_anim_stack(ufbx_scene *scene, ufbx_anim_stack *anim_stack) { for (size_t i = 0; i < anim_stack->layers.count; i++) { ufbxt_check_element_ptr(scene, anim_stack->layers.data[i], UFBX_ELEMENT_ANIM_LAYER); } } static void ufbxt_check_anim_layer(ufbx_scene *scene, ufbx_anim_layer *anim_layer) { ufbxt_check_string(anim_layer->name); for (size_t i = 0; i < anim_layer->anim_values.count; i++) { ufbxt_check_element_ptr(scene, anim_layer->anim_values.data[i], UFBX_ELEMENT_ANIM_VALUE); } size_t props_left = 0; ufbx_element *prev_element = NULL; for (size_t i = 0; i < anim_layer->anim_props.count; i++) { ufbx_anim_prop *anim_prop = &anim_layer->anim_props.data[i]; ufbxt_check_string(anim_prop->prop_name); ufbxt_check_element_ptr_any(scene, anim_prop->element); ufbxt_check_element_ptr(scene, anim_prop->anim_value, UFBX_ELEMENT_ANIM_VALUE); ufbx_anim_prop *ref = ufbx_find_anim_prop(anim_layer, anim_prop->element, anim_prop->prop_name.data); ufbxt_assert(ref); ufbxt_assert(!strcmp(ref->prop_name.data, anim_prop->prop_name.data)); if (props_left > 0) { ufbxt_assert(anim_prop->element == prev_element); props_left--; } else { ufbx_anim_prop_list list = ufbx_find_anim_props(anim_layer, anim_prop->element); ufbxt_assert(list.count > 0); prev_element = anim_prop->element; props_left = list.count - 1; } } ufbxt_assert(props_left == 0); } static void ufbxt_check_anim_value(ufbx_scene *scene, ufbx_anim_value *anim_value) { ufbxt_check_element_ptr(scene, anim_value->curves[0], UFBX_ELEMENT_ANIM_CURVE); ufbxt_check_element_ptr(scene, anim_value->curves[1], UFBX_ELEMENT_ANIM_CURVE); ufbxt_check_element_ptr(scene, anim_value->curves[2], UFBX_ELEMENT_ANIM_CURVE); } static void ufbxt_check_anim_curve(ufbx_scene *scene, ufbx_anim_curve *anim_curve) { for (size_t i = 0; i < anim_curve->keyframes.count; i++) { ufbx_keyframe key = anim_curve->keyframes.data[i]; ufbxt_assert((uint32_t)key.interpolation < (uint32_t)UFBX_INTERPOLATION_COUNT); } } static void ufbxt_check_display_layer(ufbx_scene *scene, ufbx_display_layer *layer) { for (size_t i = 0; i < layer->nodes.count; i++) { ufbxt_assert(layer->nodes.data[i]); ufbxt_check_element_ptr(scene, layer->nodes.data[i], UFBX_ELEMENT_NODE); } } static void ufbxt_check_selection_set(ufbx_scene *scene, ufbx_selection_set *set) { for (size_t i = 0; i < set->nodes.count; i++) { ufbxt_assert(set->nodes.data[i]); ufbxt_check_element_ptr(scene, set->nodes.data[i], UFBX_ELEMENT_SELECTION_NODE); } } static void ufbxt_check_selection_node(ufbx_scene *scene, ufbx_selection_node *node) { ufbxt_check_element_ptr(scene, node->target_node, UFBX_ELEMENT_NODE); ufbxt_check_element_ptr(scene, node->target_mesh, UFBX_ELEMENT_MESH); } static void ufbxt_check_character(ufbx_scene *scene, ufbx_character *character) { } static void ufbxt_check_constraint(ufbx_scene *scene, ufbx_constraint *constraint) { ufbxt_check_element_ptr(scene, constraint->node, UFBX_ELEMENT_NODE); ufbxt_check_element_ptr(scene, constraint->aim_up_node, UFBX_ELEMENT_NODE); ufbxt_check_element_ptr(scene, constraint->ik_effector, UFBX_ELEMENT_NODE); ufbxt_check_element_ptr(scene, constraint->ik_end_node, UFBX_ELEMENT_NODE); for (size_t i = 0; i < constraint->targets.count; i++) { ufbxt_assert(constraint->targets.data[i].node); ufbxt_check_element_ptr(scene, constraint->targets.data[i].node, UFBX_ELEMENT_NODE); } } static void ufbxt_check_pose(ufbx_scene *scene, ufbx_pose *pose) { for (size_t i = 0; i < pose->bone_poses.count; i++) { ufbx_bone_pose bone_pose = pose->bone_poses.data[i]; ufbxt_assert(bone_pose.bone_node); ufbxt_check_element_ptr(scene, bone_pose.bone_node, UFBX_ELEMENT_NODE); } } static void ufbxt_check_metadata_object(ufbx_scene *scene, ufbx_metadata_object *metadata_object) { } static void ufbxt_check_texture_file(ufbx_scene *scene, ufbx_texture_file *texture_file) { ufbxt_check_string(texture_file->filename); ufbxt_check_string(texture_file->absolute_filename); ufbxt_check_string(texture_file->relative_filename); ufbxt_check_blob(texture_file->raw_filename); ufbxt_check_blob(texture_file->raw_absolute_filename); ufbxt_check_blob(texture_file->raw_relative_filename); ufbxt_check_blob(texture_file->content); } static void ufbxt_check_application(ufbx_scene *scene, ufbx_application *application) { ufbxt_check_string(application->name); ufbxt_check_string(application->vendor); ufbxt_check_string(application->version); } static void ufbxt_check_metadata(ufbx_scene *scene, ufbx_metadata *metadata) { ufbxt_check_string(metadata->creator); ufbxt_check_string(metadata->filename); ufbxt_check_string(metadata->relative_root); ufbxt_check_blob(metadata->raw_filename); ufbxt_check_blob(metadata->raw_relative_root); ufbxt_check_application(scene, &metadata->latest_application); ufbxt_check_application(scene, &metadata->original_application); if (metadata->file_format == UFBX_FILE_FORMAT_FBX) { } else if (metadata->file_format == UFBX_FILE_FORMAT_OBJ) { ufbxt_assert(metadata->ascii); } else if (metadata->file_format == UFBX_FILE_FORMAT_MTL) { ufbxt_assert(metadata->ascii); } else { ufbxt_assert(0 && "Invalid file format"); } for (size_t i = 0; i < metadata->warnings.count; i++) { ufbx_warning *warning = &metadata->warnings.data[i]; ufbxt_check_string(warning->description); ufbxt_assert(metadata->has_warning[warning->type]); } } static void ufbxt_check_dom_value(ufbx_scene *scene, ufbx_dom_value *value) { ufbxt_check_string(value->value_str); ufbxt_check_blob(value->value_blob); size_t array_stride = 0; switch (value->type) { case UFBX_DOM_VALUE_NUMBER: break; case UFBX_DOM_VALUE_STRING: break; case UFBX_DOM_VALUE_ARRAY_I8: array_stride = 1; break; case UFBX_DOM_VALUE_ARRAY_I32: array_stride = 4; break; case UFBX_DOM_VALUE_ARRAY_I64: array_stride = 8; break; case UFBX_DOM_VALUE_ARRAY_F32: array_stride = 4; break; case UFBX_DOM_VALUE_ARRAY_F64: array_stride = 8; break; case UFBX_DOM_VALUE_ARRAY_RAW_STRING: array_stride = sizeof(ufbx_blob); break; case UFBX_DOM_VALUE_ARRAY_IGNORED: array_stride = 0; break; default: ufbxt_assert(0 && "Unhandled type"); break; } if (array_stride) { ufbxt_assert(value->value_blob.size == (size_t)value->value_int * array_stride); } if (value->type == UFBX_DOM_VALUE_ARRAY_RAW_STRING) { const ufbx_blob *blobs = (const ufbx_blob*)value->value_blob.data; for (size_t i = 0; i < (size_t)value->value_int; i++) { ufbxt_check_blob(blobs[i]); } } } static void ufbxt_check_dom_node(ufbx_scene *scene, ufbx_dom_node *node) { ufbxt_assert(node); ufbxt_check_string(node->name); for (size_t i = 0; i < node->children.count; i++) { ufbxt_check_dom_node(scene, node->children.data[i]); } for (size_t i = 0; i < node->values.count; i++) { ufbxt_check_dom_value(scene, &node->values.data[i]); } } static void ufbxt_check_scene(ufbx_scene *scene) { // TODO: Partial safety validation? if (scene->metadata.is_unsafe) return; ufbxt_check_metadata(scene, &scene->metadata); for (size_t i = 0; i < scene->elements.count; i++) { ufbxt_assert(scene->elements.data[i]->element_id == (uint32_t)i); ufbxt_check_element(scene, scene->elements.data[i]); } for (size_t i = 0; i < scene->unknowns.count; i++) { ufbxt_check_unknown(scene, scene->unknowns.data[i]); } for (size_t i = 0; i < scene->nodes.count; i++) { ufbxt_check_node(scene, scene->nodes.data[i]); } for (size_t i = 0; i < scene->meshes.count; i++) { ufbxt_check_mesh(scene, scene->meshes.data[i]); } for (size_t i = 0; i < scene->lights.count; i++) { ufbxt_check_light(scene, scene->lights.data[i]); } for (size_t i = 0; i < scene->cameras.count; i++) { ufbxt_check_camera(scene, scene->cameras.data[i]); } for (size_t i = 0; i < scene->bones.count; i++) { ufbxt_check_bone(scene, scene->bones.data[i]); } for (size_t i = 0; i < scene->empties.count; i++) { ufbxt_check_empty(scene, scene->empties.data[i]); } for (size_t i = 0; i < scene->line_curves.count; i++) { ufbxt_check_line_curve(scene, scene->line_curves.data[i]); } for (size_t i = 0; i < scene->nurbs_curves.count; i++) { ufbxt_check_nurbs_curve(scene, scene->nurbs_curves.data[i]); } for (size_t i = 0; i < scene->nurbs_surfaces.count; i++) { ufbxt_check_nurbs_surface(scene, scene->nurbs_surfaces.data[i]); } for (size_t i = 0; i < scene->nurbs_trim_surfaces.count; i++) { ufbxt_check_nurbs_trim_surface(scene, scene->nurbs_trim_surfaces.data[i]); } for (size_t i = 0; i < scene->nurbs_trim_boundaries.count; i++) { ufbxt_check_nurbs_trim_boundary(scene, scene->nurbs_trim_boundaries.data[i]); } for (size_t i = 0; i < scene->procedural_geometries.count; i++) { ufbxt_check_procedural_geometry(scene, scene->procedural_geometries.data[i]); } for (size_t i = 0; i < scene->stereo_cameras.count; i++) { ufbxt_check_stereo_camera(scene, scene->stereo_cameras.data[i]); } for (size_t i = 0; i < scene->camera_switchers.count; i++) { ufbxt_check_camera_switcher(scene, scene->camera_switchers.data[i]); } for (size_t i = 0; i < scene->markers.count; i++) { ufbxt_check_marker(scene, scene->markers.data[i]); } for (size_t i = 0; i < scene->lod_groups.count; i++) { ufbxt_check_lod_group(scene, scene->lod_groups.data[i]); } for (size_t i = 0; i < scene->skin_deformers.count; i++) { ufbxt_check_skin_deformer(scene, scene->skin_deformers.data[i]); } for (size_t i = 0; i < scene->skin_clusters.count; i++) { ufbxt_check_skin_cluster(scene, scene->skin_clusters.data[i]); } for (size_t i = 0; i < scene->blend_deformers.count; i++) { ufbxt_check_blend_deformer(scene, scene->blend_deformers.data[i]); } for (size_t i = 0; i < scene->blend_channels.count; i++) { ufbxt_check_blend_channel(scene, scene->blend_channels.data[i]); } for (size_t i = 0; i < scene->blend_shapes.count; i++) { ufbxt_check_blend_shape(scene, scene->blend_shapes.data[i]); } for (size_t i = 0; i < scene->cache_deformers.count; i++) { ufbxt_check_cache_deformer(scene, scene->cache_deformers.data[i]); } for (size_t i = 0; i < scene->cache_files.count; i++) { ufbxt_check_cache_file(scene, scene->cache_files.data[i]); } for (size_t i = 0; i < scene->materials.count; i++) { ufbxt_check_material(scene, scene->materials.data[i]); } for (size_t i = 0; i < scene->textures.count; i++) { ufbxt_check_texture(scene, scene->textures.data[i]); } for (size_t i = 0; i < scene->videos.count; i++) { ufbxt_check_video(scene, scene->videos.data[i]); } for (size_t i = 0; i < scene->shaders.count; i++) { ufbxt_check_shader(scene, scene->shaders.data[i]); } for (size_t i = 0; i < scene->shader_bindings.count; i++) { ufbxt_check_shader_binding(scene, scene->shader_bindings.data[i]); } for (size_t i = 0; i < scene->anim_stacks.count; i++) { ufbxt_check_anim_stack(scene, scene->anim_stacks.data[i]); } for (size_t i = 0; i < scene->anim_layers.count; i++) { ufbxt_check_anim_layer(scene, scene->anim_layers.data[i]); } for (size_t i = 0; i < scene->anim_values.count; i++) { ufbxt_check_anim_value(scene, scene->anim_values.data[i]); } for (size_t i = 0; i < scene->anim_curves.count; i++) { ufbxt_check_anim_curve(scene, scene->anim_curves.data[i]); } for (size_t i = 0; i < scene->display_layers.count; i++) { ufbxt_check_display_layer(scene, scene->display_layers.data[i]); } for (size_t i = 0; i < scene->selection_sets.count; i++) { ufbxt_check_selection_set(scene, scene->selection_sets.data[i]); } for (size_t i = 0; i < scene->selection_nodes.count; i++) { ufbxt_check_selection_node(scene, scene->selection_nodes.data[i]); } for (size_t i = 0; i < scene->characters.count; i++) { ufbxt_check_character(scene, scene->characters.data[i]); } for (size_t i = 0; i < scene->constraints.count; i++) { ufbxt_check_constraint(scene, scene->constraints.data[i]); } for (size_t i = 0; i < scene->poses.count; i++) { ufbxt_check_pose(scene, scene->poses.data[i]); } for (size_t i = 0; i < scene->metadata_objects.count; i++) { ufbxt_check_metadata_object(scene, scene->metadata_objects.data[i]); } for (size_t i = 0; i < scene->texture_files.count; i++) { ufbxt_assert(scene->texture_files.data[i].index == i); ufbxt_check_texture_file(scene, &scene->texture_files.data[i]); } if (scene->dom_root) { ufbxt_check_dom_node(scene, scene->dom_root); } for (size_t type_ix = 0; type_ix < UFBX_ELEMENT_TYPE_COUNT; type_ix++) { size_t num_elements = scene->elements_by_type[type_ix].count; for (size_t i = 0; i < num_elements; i++) { ufbx_element *element = scene->elements_by_type[type_ix].data[i]; ufbxt_assert(element->type == (ufbx_element_type)type_ix); ufbxt_assert(element->typed_id == (uint32_t)i); } } ufbxt_assert(scene->root_node); ufbxt_check_element_ptr(scene, scene->root_node, UFBX_ELEMENT_NODE); ufbxt_assert(scene->root_node->is_root); } #endif