#undef UFBXT_TEST_GROUP #define UFBXT_TEST_GROUP "parse" #if UFBXT_IMPL static void ufbxt_check_warning(ufbx_scene *scene, ufbx_warning_type type, size_t count, const char *substring) { bool found = false; for (size_t i = 0; i < scene->metadata.warnings.count; i++) { ufbx_warning *warning = &scene->metadata.warnings.data[i]; if (warning->type == type) { if (substring) { ufbxt_assert(strstr(warning->description.data, substring)); } if (count == SIZE_MAX) { ufbxt_assert(warning->count > 0); } else { ufbxt_assert(warning->count == count); } found = true; break; } } ufbxt_logf("Warning not found: %d", (int)type); ufbxt_assert(found); } #endif UFBXT_TEST(thread_safety) #if UFBXT_IMPL { if (!g_allow_non_thread_safe) { ufbxt_assert(ufbx_is_thread_safe()); } } #endif #if UFBXT_IMPL static ufbx_load_opts ufbxt_retain_dom_opts() { ufbx_load_opts opts = { 0 }; opts.retain_dom = true; return opts; } static ufbx_load_opts ufbxt_strict_opts() { ufbx_load_opts opts = { 0 }; opts.strict = true; return opts; } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(maya_cube_dom, maya_cube, ufbxt_retain_dom_opts, UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS) #if UFBXT_IMPL { ufbxt_assert(scene->dom_root); ufbx_dom_node *objects = ufbx_dom_find(scene->dom_root, "Objects"); ufbxt_assert(objects); ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); bool found = false; for (size_t i = 0; i < objects->children.count; i++) { if (node->element.dom_node == objects->children.data[i]) { found = true; break; } } ufbxt_assert(found); ufbx_mesh *mesh = ufbx_as_mesh(node->attrib); ufbxt_assert(mesh); ufbx_dom_node *dom_mesh = mesh->element.dom_node; ufbxt_assert(dom_mesh); ufbx_dom_node *dom_indices = ufbx_dom_find(dom_mesh, "PolygonVertexIndex"); ufbxt_assert(dom_indices); ufbxt_assert(dom_indices->values.count == 1); ufbx_dom_value *dom_indices_value = &dom_indices->values.data[0]; ufbxt_assert(dom_indices_value->type == UFBX_DOM_VALUE_ARRAY_I32); ufbxt_assert(dom_indices_value->value_int == 24); size_t count = (size_t)dom_indices_value->value_int; int32_t *data = (int32_t*)dom_indices_value->value_blob.data; size_t num_negative = 0; for (size_t i = 0; i < count; i++) { if (data[i] < 0) { num_negative++; } } ufbxt_assert(num_negative == 6); } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(maya_cube_strict, maya_cube, ufbxt_strict_opts, UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS) #if UFBXT_IMPL { } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max2009_blob_dom, max2009_blob, ufbxt_retain_dom_opts, UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS) #if UFBXT_IMPL { ufbxt_assert(scene->dom_root); } #endif UFBXT_FILE_TEST(maya_leading_comma) #if UFBXT_IMPL { ufbxt_assert(!strcmp(scene->metadata.creator.data, "FBX SDK/FBX Plugins version 2019.2")); } #endif UFBXT_FILE_TEST(maya_zero_end) #if UFBXT_IMPL { ufbxt_assert(!strcmp(scene->metadata.creator.data, "FBX SDK/FBX Plugins version 2019.2")); } #endif UFBXT_TEST(error_format_long) #if UFBXT_IMPL { char data[] = "Bad FBX"; ufbx_error error; ufbx_scene *scene = ufbx_load_memory(data, sizeof(data), NULL, &error); ufbxt_assert(!scene); char error_buf[512]; size_t length = ufbx_format_error(error_buf, sizeof(error_buf), &error); ufbxt_assert(strlen(error_buf) == length); #if defined(UFBX_DEV) ufbxt_assert(error.stack_size >= 2); #endif char func[64] = { 0 }, desc[64] = { 0 }; size_t line_begin = 0; size_t num_lines = 0; for (size_t i = 0; i < length; i++) { if (error_buf[i] == '\n') { if (num_lines == 0) { ufbxt_check_string(error.description); unsigned major = 0, minor = 0, patch = 0; int num_scanned = sscanf(error_buf + line_begin, "ufbx v%u.%u.%u error: %63[^\n]\n", &major, &minor, &patch, desc); ufbxt_assert(num_scanned == 4); ufbxt_assert(major == ufbx_version_major(ufbx_source_version)); ufbxt_assert(minor == ufbx_version_minor(ufbx_source_version)); ufbxt_assert(patch == ufbx_version_patch(ufbx_source_version)); ufbxt_assert(!strcmp(desc, error.description.data)); } else { size_t stack_ix = num_lines - 1; ufbxt_check_string(error.stack[stack_ix].function); ufbxt_check_string(error.stack[stack_ix].description); unsigned line = 0; int num_scanned = sscanf(error_buf + line_begin, "%u:%63[^:]: %63[^\n]\n", &line, func, desc); ufbxt_assert(num_scanned == 3); ufbxt_assert(stack_ix < error.stack_size); ufbxt_assert(line == error.stack[stack_ix].source_line); ufbxt_assert(!strcmp(func, error.stack[stack_ix].function.data)); ufbxt_assert(!strcmp(desc, error.stack[stack_ix].description.data)); } line_begin = i + 1; num_lines++; } } ufbxt_assert(num_lines == error.stack_size + 1); } #endif UFBXT_TEST(error_format_short) #if UFBXT_IMPL { char data[] = "Bad FBX"; ufbx_error error; ufbx_scene *scene = ufbx_load_memory(data, sizeof(data), NULL, &error); ufbxt_assert(!scene); char error_buf[512]; for (size_t buf_len = 0; buf_len <= ufbxt_arraycount(error_buf); buf_len++) { size_t ret_len = ufbx_format_error(error_buf, buf_len, &error); if (buf_len == 0) { ufbxt_assert(ret_len == 0); continue; } size_t str_len = strlen(error_buf); ufbxt_hintf("buf_len = %zu, ret_len = %zu, str_len = %zu", buf_len, ret_len, str_len); ufbxt_assert(ret_len == str_len); if (buf_len < 16) { ufbxt_assert(ret_len == buf_len - 1); } } } #endif UFBXT_FILE_TEST(maya_node_attribute_zoo) #if UFBXT_IMPL { ufbx_node *node; ufbxt_assert(scene->settings.axes.right == UFBX_COORDINATE_AXIS_POSITIVE_X); ufbxt_assert(scene->settings.axes.up == UFBX_COORDINATE_AXIS_POSITIVE_Y); ufbxt_assert(scene->settings.axes.front == UFBX_COORDINATE_AXIS_POSITIVE_Z); ufbxt_assert(scene->settings.time_mode == UFBX_TIME_MODE_24_FPS); ufbxt_assert_close_real(err, scene->settings.unit_meters, 0.01f); ufbxt_assert_close_real(err, scene->settings.frames_per_second, 24.0f); node = ufbx_find_node(scene, "Null"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_EMPTY); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_EMPTY); node = ufbx_find_node(scene, "Mesh"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_MESH); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_MESH); ufbxt_assert(&node->mesh->element == node->attrib); node = ufbx_find_node(scene, "Bone"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_BONE); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_BONE); ufbxt_assert(&node->bone->element == node->attrib); ufbxt_assert_close_real(err, node->bone->radius, 0.5f); ufbxt_assert_close_real(err, node->bone->relative_length, 1.0f); node = ufbx_find_node(scene, "Camera"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_CAMERA); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_CAMERA); ufbxt_assert(&node->camera->element == node->attrib); ufbxt_assert(node->camera->gate_fit == UFBX_GATE_FIT_FILL); ufbxt_assert_close_real(err, node->camera->field_of_view_deg.x / 10.0f, 54.43f / 10.0f); ufbxt_assert_close_real(err, node->camera->focal_length_mm, 35.0f); node = ufbx_find_node(scene, "Light"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_LIGHT); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_LIGHT); ufbxt_assert(&node->light->element == node->attrib); ufbxt_assert_close_real(err, node->light->intensity, 1.0f); ufbxt_assert_close_real(err, node->light->color.x, 1.0f); ufbxt_assert_close_real(err, node->light->color.y, 1.0f); ufbxt_assert_close_real(err, node->light->color.z, 1.0f); node = ufbx_find_node(scene, "StereoCamera"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_STEREO_CAMERA); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_STEREO_CAMERA); ufbx_stereo_camera *stereo = (ufbx_stereo_camera*)node->attrib; ufbxt_assert(stereo->left && stereo->left->element.type == UFBX_ELEMENT_CAMERA); ufbxt_assert(stereo->right && stereo->right->element.type == UFBX_ELEMENT_CAMERA); ufbx_prop left_focal_prop = ufbx_evaluate_prop(&scene->anim, &stereo->left->element, "FocalLength", 0.5); ufbx_prop right_focal_prop = ufbx_evaluate_prop(&scene->anim, &stereo->right->element, "FocalLength", 0.5); ufbxt_assert_close_real(err, left_focal_prop.value_real, 42.011f); ufbxt_assert_close_real(err, right_focal_prop.value_real, 42.011f); node = ufbx_find_node(scene, "NurbsCurve"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_CURVE); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_NURBS_CURVE); node = ufbx_find_node(scene, "NurbsSurface"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_NURBS_SURFACE); node = ufbx_find_node(scene, "NurbsTrim"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_TRIM_SURFACE); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_NURBS_TRIM_SURFACE); node = ufbx_find_node(scene, "LodGroup"); ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_LOD_GROUP); ufbxt_assert(node->attrib && node->attrib->type == UFBX_ELEMENT_LOD_GROUP); } #endif UFBXT_FILE_TEST(synthetic_duplicate_prop) #if UFBXT_IMPL { ufbx_prop *prop; ufbx_node *cube = ufbx_find_node(scene, "pCube1"); ufbxt_assert(cube); prop = ufbx_find_prop(&cube->props, "Lcl Translation"); ufbxt_assert(prop); ufbxt_assert_close_real(err, prop->value_vec3.x, -1.0f); prop = ufbx_find_prop(&cube->props, "Lcl Scaling"); ufbxt_assert(prop); ufbxt_assert_close_real(err, prop->value_vec3.x, 2.0f); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_missing_version, UFBXT_FILE_TEST_FLAG_ALLOW_STRICT_ERROR) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.version == 6100); ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_FBX_SDK); ufbxt_assert(scene->metadata.exporter_version == ufbx_pack_version(2007, 2, 28)); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_missing_exporter, UFBXT_FILE_TEST_FLAG_ALLOW_STRICT_ERROR) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.version == 6100); ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_UNKNOWN); ufbxt_assert(scene->metadata.exporter_version == 0); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_blender_old_exporter, UFBXT_FILE_TEST_FLAG_ALLOW_STRICT_ERROR) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.version == 6100); ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_UNKNOWN); ufbxt_assert(scene->metadata.exporter_version == 0); } #endif UFBXT_FILE_TEST(blender_272_cube) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.version == 7400); ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_BLENDER_BINARY); ufbxt_assert(scene->metadata.exporter_version == ufbx_pack_version(2, 72, 0)); ufbxt_assert(!strcmp(scene->metadata.original_application.vendor.data, "Blender Foundation")); ufbxt_assert(!strcmp(scene->metadata.original_application.name.data, "Blender (stable FBX IO)")); ufbxt_assert(!strcmp(scene->metadata.original_application.version.data, "2.72 (sub 0)")); ufbxt_assert(!strcmp(scene->metadata.latest_application.vendor.data, "Blender Foundation")); ufbxt_assert(!strcmp(scene->metadata.latest_application.name.data, "Blender (stable FBX IO)")); ufbxt_assert(!strcmp(scene->metadata.latest_application.version.data, "2.72 (sub 0)")); } #endif UFBXT_TEST(unicode_filename) #if UFBXT_IMPL { char buf[1024]; int len = snprintf(buf, sizeof(buf), "%ssynthetic_\x61\xce\xb2\xe3\x82\xab\xf0\x9f\x98\x82_7500_ascii.fbx", data_root); ufbxt_assert(len > 0 && len < sizeof(buf)); { ufbx_load_opts opts = { 0 }; opts.ignore_geometry = true; ufbx_scene *scene = ufbx_load_file(buf, &opts, NULL); ufbxt_assert(scene); ufbxt_assert(ufbx_find_node(scene, "pCube1")); ufbxt_check_scene(scene); ufbx_free_scene(scene); } // Remove terminating \0 for explicit length test buf[len] = 'x'; { ufbx_scene *scene = ufbx_load_file_len(buf, (size_t)len, NULL, NULL); ufbxt_assert(scene); ufbxt_assert(ufbx_find_node(scene, "pCube1")); ufbxt_check_scene(scene); ufbx_free_scene(scene); } } #endif UFBXT_FILE_TEST(maya_cube_big_endian) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.big_endian); } #endif UFBXT_FILE_TEST(synthetic_cube_nan) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_vertices >= 2); ufbxt_assert(isnan(mesh->vertices.data[0].x)); ufbxt_assert(isinf(mesh->vertices.data[0].y) && mesh->vertices.data[0].y < 0.0f); ufbxt_assert(isinf(mesh->vertices.data[0].z) && mesh->vertices.data[0].z > 0.0f); ufbxt_assert_close_real(err, mesh->vertices.data[1].x, 0.5f); ufbxt_assert_close_real(err, mesh->vertices.data[1].y, -0.5f); ufbxt_assert_close_real(err, mesh->vertices.data[1].z, 0.5f); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_string_collision, UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ|UFBXT_FILE_TEST_FLAG_SKIP_LOAD_OPTS_CHECKS) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); ufbx_prop *prop; prop = ufbx_find_prop(&node->props, "BIiMTNT"); ufbxt_assert(prop); ufbxt_assert(!strcmp(prop->value_str.data, "BJKKUfZ")); prop = ufbx_find_prop(&node->props, "BbAJerP"); ufbxt_assert(prop); ufbxt_assert(!strcmp(prop->value_str.data, "AAAJKop")); prop = ufbx_find_prop(&node->props, "BpgrcZR"); ufbxt_assert(prop); ufbxt_assert(!strcmp(prop->value_str.data, "APGAmLj")); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_id_collision, UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ|UFBXT_FILE_TEST_FLAG_SKIP_LOAD_OPTS_CHECKS) #if UFBXT_IMPL { ufbxt_assert(scene->nodes.count == 10002); ufbx_node *node = ufbx_find_node(scene, "First"); for (int32_t depth = 10000; depth >= 0; depth--) { ufbxt_assert(node); ufbxt_assert(node->node_depth == depth); node = node->parent; } ufbxt_assert(!node); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_node_depth_fail, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR) #if UFBXT_IMPL { } #endif UFBXT_TEST(open_file) #if UFBXT_IMPL { const char *name = "maya_cube_7500_ascii.fbx"; char buf[512]; snprintf(buf, sizeof(buf), "%s%s", data_root, name); for (size_t i = 0; i < 2; i++) { ufbx_stream stream = { 0 }; bool ok = ufbx_open_file(&stream, buf, i == 0 ? SIZE_MAX : strlen(buf)); ufbxt_assert(ok); ufbxt_assert(stream.skip_fn); ufbxt_assert(stream.read_fn); ufbxt_assert(stream.close_fn); ufbxt_assert(stream.skip_fn(stream.user, 2)); char result[3]; size_t num_read = stream.read_fn(stream.user, result, 3); ufbxt_assert(num_read == 3); ufbxt_assert(!memcmp(result, "FBX", 3)); stream.close_fn(stream.user); } } #endif UFBXT_TEST(file_not_found) #if UFBXT_IMPL { const char *name = "maya_cube_9999_emoji.fbx"; char buf[512]; snprintf(buf, sizeof(buf), "%s%s", data_root, name); ufbx_error error; ufbx_scene *scene = ufbx_load_file(buf, NULL, &error); ufbxt_assert(!scene); ufbxt_assert(error.type == UFBX_ERROR_FILE_NOT_FOUND); ufbxt_assert(error.info_length == strlen(error.info)); ufbxt_assert(!strcmp(error.info, buf)); } #endif UFBXT_TEST(retain_scene) #if UFBXT_IMPL { char path[512]; ufbxt_file_iterator iter = { "maya_cube" }; while (ufbxt_next_file(&iter, path, sizeof(path))) { ufbx_load_opts opts = { 0 }; ufbx_scene *scene = ufbx_load_file(path, &opts, NULL); ufbxt_assert(scene); ufbxt_check_scene(scene); ufbx_scene *eval_scene = ufbx_evaluate_scene(scene, NULL, 0.1, NULL, NULL); ufbx_free_scene(scene); // eval_scene should retain references ufbx_node *node = ufbx_find_node(eval_scene, "pCube1"); ufbxt_assert(node); ufbxt_assert(!strcmp(node->name.data, "pCube1")); // No-op retain and release ufbx_retain_scene(eval_scene); ufbx_free_scene(eval_scene); ufbx_free_scene(eval_scene); } } #endif UFBXT_FILE_TEST(synthetic_empty_elements) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); } #endif #if UFBXT_IMPL static void ufbxt_check_binary_prop(const ufbx_props *props) { ufbx_prop *prop = ufbx_find_prop(props, "Binary"); ufbxt_assert(prop); ufbxt_assert(prop->type == UFBX_PROP_BLOB); // TODO: Assert value } #endif UFBXT_FILE_TEST(synthetic_binary_props) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbxt_check_binary_prop(&node->props); ufbxt_check_binary_prop(&node->mesh->props); ufbxt_check_binary_prop(&scene->settings.props); } #endif UFBXT_FILE_TEST(maya_unicode) #if UFBXT_IMPL { // WHAT? Maya turns U+03B2 (Greek Small Letter Beta) into U+00DF (Latin Small Letter Sharp S) // The larger codepoints just get turned into one or two underscores which makes a bit more sense... ufbx_node *node = ufbx_find_node(scene, "\x61\xc3\x9f___"); ufbxt_assert(node); } #endif UFBXT_FILE_TEST(max_unicode) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "\x61\xce\xb2\xe3\x82\xab\xf0\x9f\x98\x82"); ufbxt_assert(node); } #endif UFBXT_FILE_TEST(blender_279_unicode) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "\x61\xce\xb2\xe3\x82\xab\xf0\x9f\x98\x82"); ufbxt_assert(node); } #endif #if UFBXT_IMPL static uint32_t ufbxt_decode_hex_char(char c) { if (c >= '0' && c <= '9') { return (uint32_t)c - '0'; } else if (c >= 'a' && c <= 'f') { return (uint32_t)c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { return (uint32_t)c - 'A' + 10; } else { ufbxt_assert(false && "Bad hex character"); return 0; } } static size_t ufbxt_decode_hex(uint8_t *dst, size_t dst_len, ufbx_string src) { ufbxt_assert(src.length % 2 == 0); size_t num = src.length / 2; ufbxt_assert(num <= dst_len); for (size_t i = 0; i < num; i++) { dst[i] = (uint8_t)( ufbxt_decode_hex_char(src.data[i * 2 + 0]) << 4u | ufbxt_decode_hex_char(src.data[i * 2 + 1]) << 0u ); } return num; } #endif UFBXT_FILE_TEST_FLAGS(synthetic_unicode, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE|UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ|UFBXT_FILE_TEST_FLAG_SKIP_LOAD_OPTS_CHECKS) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.warnings.count == 1); ufbxt_assert(scene->metadata.warnings.data[0].type == UFBX_WARNING_BAD_UNICODE); ufbxt_assert(scene->metadata.warnings.data[0].count >= 6000); uint8_t ref[128]; { ufbx_node *node = ufbx_find_node(scene, "Good"); ufbxt_assert(node); for (size_t prop_ix = 0; prop_ix < node->props.props.count; prop_ix++) { ufbx_prop *prop = &node->props.props.data[prop_ix]; size_t ref_len = ufbxt_decode_hex(ref, ufbxt_arraycount(ref), prop->name); ufbx_string src = prop->value_str; size_t src_ix = 0; for (size_t ref_ix = 0; ref_ix < ref_len; ref_ix++) { uint8_t rc = ref[ref_ix]; if (rc == 0) { ufbxt_assert(src_ix + 3 <= src.length); ufbxt_assert((uint8_t)src.data[src_ix + 0] == 0xef); ufbxt_assert((uint8_t)src.data[src_ix + 1] == 0xbf); ufbxt_assert((uint8_t)src.data[src_ix + 2] == 0xbd); src_ix += 3; } else { ufbxt_assert(src_ix < src.length); ufbxt_assert((uint8_t)src.data[src_ix] == rc); src_ix += 1; } } ufbxt_assert(src_ix == src.length); } } { ufbx_node *node = ufbx_find_node(scene, "Ok"); ufbxt_assert(node); for (size_t prop_ix = 0; prop_ix < node->props.props.count; prop_ix++) { ufbx_prop *prop = &node->props.props.data[prop_ix]; size_t ref_len = ufbxt_decode_hex(ref, ufbxt_arraycount(ref), prop->name); ufbx_string src = prop->value_str; ufbxt_assert(ref_len >= 1); ufbxt_assert((uint8_t)ref[0] == 0xff); size_t src_ix = 0; ufbxt_assert((uint8_t)src.data[src_ix + 0] == 0xef); ufbxt_assert((uint8_t)src.data[src_ix + 1] == 0xbf); ufbxt_assert((uint8_t)src.data[src_ix + 2] == 0xbd); src_ix += 3; for (size_t ref_ix = 1; ref_ix < ref_len; ref_ix++) { uint8_t rc = ref[ref_ix]; if (rc == 0) { ufbxt_assert(src_ix + 3 <= src.length); ufbxt_assert((uint8_t)src.data[src_ix + 0] == 0xef); ufbxt_assert((uint8_t)src.data[src_ix + 1] == 0xbf); ufbxt_assert((uint8_t)src.data[src_ix + 2] == 0xbd); src_ix += 3; } else { ufbxt_assert(src_ix < src.length); ufbxt_assert((uint8_t)src.data[src_ix] == rc); src_ix += 1; } } ufbxt_assert(src_ix == src.length); } } { ufbx_node *node = ufbx_find_node(scene, "Bad"); ufbxt_assert(node); for (size_t prop_ix = 0; prop_ix < node->props.props.count; prop_ix++) { ufbx_prop *prop = &node->props.props.data[prop_ix]; size_t ref_len = ufbxt_decode_hex(ref, ufbxt_arraycount(ref), prop->name); ufbx_string src = prop->value_str; const char *replacement = strstr(src.data, "\xef\xbf\xbd"); ufbxt_assert(replacement); } } } #endif UFBXT_FILE_TEST(max_quote) #if UFBXT_IMPL { if (scene->metadata.ascii && scene->metadata.version == 6100) { ufbxt_check_warning(scene, UFBX_WARNING_DUPLICATE_OBJECT_ID, 1, NULL); } { ufbx_node *node = ufbx_find_node(scene, "\"'&\""); ufbxt_assert(node); ufbxt_assert(node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->materials.count == 1); ufbx_material *material = mesh->materials.data[0].material; if (scene->metadata.ascii) { ufbxt_assert(!strcmp(material->name.data, "&&q&qu&quo"\"\"")); } else { ufbxt_assert(!strcmp(material->name.data, "&&q&qu&quo""\"")); } } { ufbx_node *node = ufbx_find_node(scene, "\""); ufbxt_assert(node); } { ufbx_node *node = ufbx_find_node(scene, """); if (scene->metadata.ascii) { ufbxt_assert(!node); } else { ufbxt_assert(node); } } } #endif UFBXT_FILE_TEST(max_colon_name) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Cube::Model"); ufbxt_assert(node); ufbxt_assert(node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->materials.count == 1); ufbx_material *material = mesh->materials.data[0].material; ufbxt_assert(!strcmp(material->name.data, "Material::Pink")); } #endif UFBXT_FILE_TEST_FLAGS(synthetic_truncated_quot_fail, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR) #if UFBXT_IMPL { } #endif UFBXT_FILE_TEST_FLAGS(synthetic_unicode_error_identity, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE) #if UFBXT_IMPL { ufbx_node *parent = ufbx_find_node(scene, "Parent"); ufbxt_assert(parent); ufbxt_assert(parent->children.count == 2); ufbxt_assert(!strcmp(parent->children.data[0]->name.data, parent->children.data[1]->name.data)); ufbxt_assert(parent->children.data[0]->name.data == parent->children.data[1]->name.data); for (size_t i = 0; i < parent->children.count; i++) { ufbx_node *child = parent->children.data[i]; bool left = child->world_transform.translation.x < 0.0f; ufbxt_assert(!strcmp(child->name.data, "Child_\xef\xbf\xbd")); ufbx_mesh *mesh = child->mesh; ufbxt_assert(mesh); ufbxt_assert(mesh->materials.count == 1); ufbx_material *material = mesh->materials.data[0].material; ufbxt_assert(!strcmp(material->name.data, "Material_\xef\xbf\xbd")); if (left) { ufbx_vec3 ref = { 1.0f, 0.0f, 0.0f }; ufbxt_assert_close_vec3(err, material->fbx.diffuse_color.value_vec3, ref); } else { ufbx_vec3 ref = { 0.0f, 1.0f, 0.0f }; ufbxt_assert_close_vec3(err, material->fbx.diffuse_color.value_vec3, ref); } } } #endif #if UFBXT_IMPL ufbx_load_opts ufbxt_unicode_error_question_opts() { ufbx_load_opts opts = { 0 }; opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK; return opts; } #endif UFBXT_FILE_TEST_OPTS_FLAGS(synthetic_broken_filename, ufbxt_unicode_error_question_opts, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE) #if UFBXT_IMPL { ufbxt_assert(scene->textures.count == 1); const char *abs_path_safe = "D:/Dev/clean/ufbx/data/textures/??????_diffuse.png"; const char *rel_path_safe = "textures\\??????_diffuse.png"; const char *abs_path_raw = "D:/Dev/clean/ufbx/data/textures/\xaa\xbb\xcc\xdd\xee\xff_diffuse.png"; const char *rel_path_raw = "textures\\\xaa\xbb\xcc\xdd\xee\xff_diffuse.png"; const char *name_path_raw = "\xaa\xbb\xcc\xdd\xee\xff_diffuse.png"; ufbx_texture *texture = scene->textures.data[0]; ufbxt_assert(texture->absolute_filename.length == strlen(abs_path_safe)); ufbxt_assert(texture->relative_filename.length == strlen(rel_path_safe)); ufbxt_assert(!strcmp(texture->absolute_filename.data, abs_path_safe)); ufbxt_assert(!strcmp(texture->relative_filename.data, rel_path_safe)); ufbxt_assert(texture->raw_absolute_filename.size == strlen(abs_path_raw)); ufbxt_assert(texture->raw_relative_filename.size == strlen(rel_path_raw)); ufbxt_assert(!memcmp(texture->raw_absolute_filename.data, abs_path_raw, texture->raw_absolute_filename.size)); ufbxt_assert(!memcmp(texture->raw_relative_filename.data, rel_path_raw, texture->raw_relative_filename.size)); { ufbxt_assert(strstr(texture->filename.data, "??????_diffuse.png")); const char *begin = memchr((const char*)texture->raw_filename.data, '\xaa', texture->raw_filename.size); ufbxt_assert(begin); size_t offset = begin - (const char*)texture->raw_filename.data; size_t left = texture->raw_filename.size - offset; ufbxt_assert(left == strlen(name_path_raw)); ufbxt_assert(!memcmp(begin, name_path_raw, left)); } ufbx_video *video = scene->videos.data[0]; ufbxt_assert(video->absolute_filename.length == strlen(abs_path_safe)); ufbxt_assert(video->relative_filename.length == strlen(rel_path_safe)); ufbxt_assert(!strcmp(video->absolute_filename.data, abs_path_safe)); ufbxt_assert(!strcmp(video->relative_filename.data, rel_path_safe)); ufbxt_assert(video->raw_absolute_filename.size == strlen(abs_path_raw)); ufbxt_assert(video->raw_relative_filename.size == strlen(rel_path_raw)); ufbxt_assert(!memcmp(video->raw_absolute_filename.data, abs_path_raw, video->raw_absolute_filename.size)); ufbxt_assert(!memcmp(video->raw_relative_filename.data, rel_path_raw, video->raw_relative_filename.size)); { ufbxt_assert(strstr(video->filename.data, "??????_diffuse.png")); const char *begin = memchr((const char*)video->raw_filename.data, '\xaa', video->raw_filename.size); ufbxt_assert(begin); size_t offset = begin - (const char*)video->raw_filename.data; size_t left = video->raw_filename.size - offset; ufbxt_assert(left == strlen(name_path_raw)); ufbxt_assert(!memcmp(begin, name_path_raw, left)); } { ufbx_string abs_path = ufbx_find_string(&video->props, "Path", ufbx_empty_string); ufbx_string rel_path = ufbx_find_string(&video->props, "RelPath", ufbx_empty_string); ufbxt_assert(abs_path.length == strlen(abs_path_safe)); ufbxt_assert(rel_path.length == strlen(rel_path_safe)); ufbxt_assert(!strcmp(abs_path.data, abs_path_safe)); ufbxt_assert(!strcmp(rel_path.data, rel_path_safe)); } { ufbx_blob abs_path = ufbx_find_blob(&video->props, "Path", ufbx_empty_blob); ufbx_blob rel_path = ufbx_find_blob(&video->props, "RelPath", ufbx_empty_blob); ufbxt_assert(abs_path.size == strlen(abs_path_raw)); ufbxt_assert(rel_path.size == strlen(rel_path_raw)); ufbxt_assert(!memcmp(abs_path.data, abs_path_raw, abs_path.size)); ufbxt_assert(!memcmp(rel_path.data, rel_path_raw, rel_path.size)); } } #endif #if UFBXT_IMPL static uint32_t ufbxt_fnv1a(const void *data, size_t size) { const char *src = (const char*)data; uint32_t hash = 0x811c9dc5u; for (size_t i = 0; i < size; i++) { hash = (hash ^ (uint32_t)(uint8_t)src[i]) * 0x01000193u; } return hash; } #endif UFBXT_FILE_TEST(revit_empty) #if UFBXT_IMPL { bool found_refs3 = false; bool found_refs = false; for (size_t i = 0; i < scene->unknowns.count; i++) { ufbx_unknown *unknown = scene->unknowns.data[i]; if (!strcmp(unknown->name.data, "ADSKAssetReferencesVersion3.0")) { ufbx_prop *prop = ufbx_find_prop(&unknown->props, "ADSKAssetReferencesBlobVersion3.0"); uint32_t size = scene->metadata.version == 7700 ? 11228u : 11305u; uint32_t hash = scene->metadata.version == 7700 ? 0x5eee86b6u : 0x7f39429du; ufbxt_assert(prop); ufbxt_assert(prop->type == UFBX_PROP_BLOB); ufbxt_assert(prop->value_real == (ufbx_real)size); ufbxt_assert(prop->value_int == size); ufbxt_assert(prop->value_blob.size == size); ufbx_blob blob = ufbx_find_blob(&unknown->props, "ADSKAssetReferencesBlobVersion3.0", ufbx_empty_blob); ufbxt_assert(blob.data == prop->value_blob.data); ufbxt_assert(blob.size == prop->value_blob.size); uint32_t hash_ref = ufbxt_fnv1a(blob.data, blob.size); ufbxt_assert(hash_ref == hash); ufbxt_assert(!found_refs3); found_refs3 = true; } else if (!strcmp(unknown->name.data, "ADSKAssetReferences")) { ufbx_prop *prop = ufbx_find_prop(&unknown->props, "ADSKAssetReferencesBlob"); uint32_t size = scene->metadata.version == 7700 ? 7910u : 7885u; uint32_t hash = scene->metadata.version == 7700 ? 0xa081f491u : 0x5aaeae9au; ufbxt_assert(prop); ufbxt_assert(prop->type == UFBX_PROP_BLOB); ufbxt_assert(prop->value_real == size); ufbxt_assert(prop->value_int == size); ufbxt_assert(prop->value_blob.size == size); ufbx_blob blob = ufbx_find_blob(&unknown->props, "ADSKAssetReferencesBlob", ufbx_empty_blob); ufbxt_assert(blob.data == prop->value_blob.data); ufbxt_assert(blob.size == prop->value_blob.size); uint32_t hash_ref = ufbxt_fnv1a(blob.data, blob.size); ufbxt_assert(hash_ref == hash); ufbxt_assert(!found_refs); found_refs = true; } } ufbxt_assert(found_refs); ufbxt_assert(found_refs3); } #endif UFBXT_FILE_TEST(maya_lock_mute) #if UFBXT_IMPL { uint32_t any_lock = UFBX_PROP_FLAG_LOCK_X | UFBX_PROP_FLAG_LOCK_Y | UFBX_PROP_FLAG_LOCK_Z | UFBX_PROP_FLAG_LOCK_W; uint32_t any_mute = UFBX_PROP_FLAG_MUTE_X | UFBX_PROP_FLAG_MUTE_Y | UFBX_PROP_FLAG_MUTE_Z | UFBX_PROP_FLAG_MUTE_W; uint32_t any_lock_mute = any_lock | any_mute; { ufbx_prop *prop; ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); prop = ufbx_find_prop(&node->props, "Lcl Translation"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_MUTE_X | UFBX_PROP_FLAG_LOCK_Z)); prop = ufbx_find_prop(&node->props, "Lcl Rotation"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_MUTE_Y | UFBX_PROP_FLAG_LOCK_Y)); prop = ufbx_find_prop(&node->props, "Lcl Scaling"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_MUTE_Z | UFBX_PROP_FLAG_LOCK_X)); } { ufbx_prop *prop; ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node); prop = ufbx_find_prop(&node->props, "Lcl Translation"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_MUTE_X | UFBX_PROP_FLAG_MUTE_Y | UFBX_PROP_FLAG_MUTE_Z)); prop = ufbx_find_prop(&node->props, "Lcl Rotation"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_LOCK_X | UFBX_PROP_FLAG_LOCK_Y | UFBX_PROP_FLAG_LOCK_Z)); prop = ufbx_find_prop(&node->props, "Lcl Scaling"); ufbxt_assert(prop && (prop->flags & any_lock_mute) == (UFBX_PROP_FLAG_MUTE_X | UFBX_PROP_FLAG_MUTE_Y | UFBX_PROP_FLAG_MUTE_Z | UFBX_PROP_FLAG_LOCK_X | UFBX_PROP_FLAG_LOCK_Y | UFBX_PROP_FLAG_LOCK_Z)); } } #endif #if UFBXT_IMPL static ufbx_load_opts ufbxt_filename_load_opts() { ufbx_load_opts opts = { 0 }; opts.filename.data = "fake/path/to/file.fbx"; opts.filename.length = SIZE_MAX; opts.path_separator = '/'; return opts; } #endif UFBXT_FILE_TEST_OPTS(synthetic_parent_directory, ufbxt_filename_load_opts) #if UFBXT_IMPL { ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "lambert1"); ufbxt_assert(material); ufbx_texture *temporary = material->fbx.diffuse_color.texture; ufbxt_assert(temporary && temporary->video); ufbx_video *temporary_video = temporary->video; ufbx_texture *inner = material->fbx.transparency_color.texture; ufbxt_assert(inner && inner->video); ufbx_video *inner_video = inner->video; ufbxt_assert(!strcmp(temporary->filename.data, "temporary.png")); ufbxt_assert(!strcmp(temporary_video->filename.data, "fake/path/temporary.png")); ufbxt_assert(!strcmp(inner->filename.data, "../directory/inner.png")); ufbxt_assert(!strcmp(inner_video->filename.data, "fake/path/directory/inner.png")); } #endif #if UFBXT_IMPL static ufbx_load_opts ufbxt_parent_dir_filename_load_opts() { ufbx_load_opts opts = { 0 }; opts.filename.data = "fake/../path/./file.fbx"; opts.filename.length = SIZE_MAX; opts.path_separator = '/'; return opts; } #endif UFBXT_FILE_TEST_OPTS_ALT(synthetic_parent_directory_parent, synthetic_parent_directory, ufbxt_parent_dir_filename_load_opts) #if UFBXT_IMPL { ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "lambert1"); ufbxt_assert(material); ufbx_texture *temporary = material->fbx.diffuse_color.texture; ufbxt_assert(temporary && temporary->video); ufbx_video *temporary_video = temporary->video; ufbx_texture *inner = material->fbx.transparency_color.texture; ufbxt_assert(inner && inner->video); ufbx_video *inner_video = inner->video; ufbxt_assert(!strcmp(temporary->filename.data, "fake/../../../temporary.png")); ufbxt_assert(!strcmp(temporary_video->filename.data, "fake/../temporary.png")); ufbxt_assert(!strcmp(inner->filename.data, "fake/../../../../directory/inner.png")); ufbxt_assert(!strcmp(inner_video->filename.data, "fake/../directory/inner.png")); } #endif UFBXT_TEST(unsafe_options) #if UFBXT_IMPL { char path[512]; ufbxt_file_iterator iter = { "maya_cube" }; while (ufbxt_next_file(&iter, path, sizeof(path))) { for (uint32_t mask = 0; mask < 0x8; mask++) { bool allow_unsafe = (mask & 0x1) != 0; uint32_t unsafe_mask = mask >> 1; ufbx_load_opts opts = { 0 }; opts.allow_unsafe = allow_unsafe; if ((unsafe_mask & 0x1) != 0) { opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE; } if ((unsafe_mask & 0x2) != 0) { opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE; } ufbx_error error; ufbx_scene *scene = ufbx_load_file(path, &opts, &error); if (allow_unsafe || unsafe_mask == 0) { ufbxt_assert(scene); ufbx_free_scene(scene); } else { ufbxt_assert(!scene); ufbxt_assert(error.type == UFBX_ERROR_UNSAFE_OPTIONS); } } } } #endif #if UFBXT_IMPL static ufbx_load_opts ufbxt_underscore_no_index_opts() { ufbx_load_opts opts = { 0 }; opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_NO_INDEX; opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE; return opts; } static ufbx_load_opts ufbxt_all_unsafe_opts() { ufbx_load_opts opts = { 0 }; opts.allow_unsafe = true; opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE; opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE; return opts; } static ufbx_load_opts ufbxt_fail_index_opts() { ufbx_load_opts opts = { 0 }; opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING; return opts; } static ufbx_load_opts ufbxt_fail_unicode_opts() { ufbx_load_opts opts = { 0 }; opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING; return opts; } #endif UFBXT_FILE_TEST_FLAGS(synthetic_unsafe_cube, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE) #if UFBXT_IMPL { ufbxt_check_warning(scene, UFBX_WARNING_INDEX_CLAMPED, 4, NULL); ufbxt_check_warning(scene, UFBX_WARNING_BAD_UNICODE, 1, NULL); ufbx_node *node = ufbx_find_node(scene, "pC" "\xef\xbf\xbd" "\xef\xbf\xbd" "e1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertex_uv.indices.data[0] == 0); ufbxt_assert(mesh->vertex_uv.indices.data[1] == 1); ufbxt_assert(mesh->vertex_uv.indices.data[2] == 3); ufbxt_assert(mesh->vertex_uv.indices.data[3] == 13); ufbxt_assert(mesh->vertex_uv.indices.data[4] == 13); ufbxt_assert(mesh->vertex_uv.indices.data[5] == 13); ufbxt_assert(mesh->vertex_uv.indices.data[6] == 13); ufbxt_assert(mesh->vertex_uv.indices.data[7] == 4); } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_unsafe_cube_underscore_no_index, synthetic_unsafe_cube, ufbxt_underscore_no_index_opts, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pC__e1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertex_uv.indices.data[0] == 0); ufbxt_assert(mesh->vertex_uv.indices.data[1] == 1); ufbxt_assert(mesh->vertex_uv.indices.data[2] == 3); ufbxt_assert(mesh->vertex_uv.indices.data[3] == UFBX_NO_INDEX); ufbxt_assert(mesh->vertex_uv.indices.data[4] == UFBX_NO_INDEX); ufbxt_assert(mesh->vertex_uv.indices.data[5] == UFBX_NO_INDEX); ufbxt_assert(mesh->vertex_uv.indices.data[6] == UFBX_NO_INDEX); ufbxt_assert(mesh->vertex_uv.indices.data[7] == 4); for (size_t i = 3; i <= 6; i++) { ufbx_vec2 v = ufbx_get_vertex_vec2(&mesh->vertex_uv, i); ufbxt_assert_close_vec2(err, v, ufbx_zero_vec2); } } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_unsafe_cube_unsafe, synthetic_unsafe_cube, ufbxt_all_unsafe_opts, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node_len(scene, "pC" "\xff" "\x00" "e1", 6); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertex_uv.indices.data[0] == 0); ufbxt_assert(mesh->vertex_uv.indices.data[1] == 1); ufbxt_assert(mesh->vertex_uv.indices.data[2] == 3); ufbxt_assert(mesh->vertex_uv.indices.data[3] == 0xffu); ufbxt_assert(mesh->vertex_uv.indices.data[4] == 0xffffu); ufbxt_assert(mesh->vertex_uv.indices.data[5] == 0xffffffu); ufbxt_assert(mesh->vertex_uv.indices.data[6] == 0xffffffffu); ufbxt_assert(mesh->vertex_uv.indices.data[7] == 4); } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_unsafe_cube_fail_index, synthetic_unsafe_cube, ufbxt_fail_index_opts, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE | UFBXT_FILE_TEST_FLAG_ALLOW_ERROR) #if UFBXT_IMPL { ufbxt_assert(!scene); ufbxt_assert(load_error->type == UFBX_ERROR_BAD_INDEX); } #endif UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_unsafe_cube_fail_unicode, synthetic_unsafe_cube, ufbxt_fail_unicode_opts, UFBXT_FILE_TEST_FLAG_ALLOW_INVALID_UNICODE | UFBXT_FILE_TEST_FLAG_ALLOW_ERROR) #if UFBXT_IMPL { ufbxt_assert(!scene); ufbxt_assert(load_error->type == UFBX_ERROR_INVALID_UTF8); } #endif UFBXT_FILE_TEST_ALT(find_prop_concat, maya_node_attribute_zoo) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "Null"); ufbxt_assert(node); { ufbx_string parts[] = { { "Geometric", 9 }, { "Rotation", 8 }, }; ufbx_prop *prop = ufbx_find_prop_concat(&node->props, parts, 2); ufbxt_assert(prop); ufbxt_assert(!strcmp(prop->name.data, "GeometricRotation")); } { ufbx_string parts[] = { { "Geometric", SIZE_MAX }, { "Translation", SIZE_MAX }, }; ufbx_prop *prop = ufbx_find_prop_concat(&node->props, parts, 2); ufbxt_assert(prop); ufbxt_assert(!strcmp(prop->name.data, "GeometricTranslation")); } } char chars[1024]; ufbx_string parts[512]; size_t num_parts = 0; for (size_t elem_ix = 0; elem_ix < scene->elements.count; elem_ix++) { ufbx_element *element = scene->elements.data[elem_ix]; ufbx_props *props = &element->props; while (props) { for (size_t prop_ix = 0; prop_ix < props->props.count; prop_ix++) { ufbx_prop *prop = &props->props.data[prop_ix]; ufbx_string name = prop->name; ufbxt_assert(name.length * 2 < ufbxt_arraycount(parts)); // One single part num_parts = 0; parts[num_parts] = name; num_parts++; ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); // One single part (NULL terminated) num_parts = 0; parts[num_parts].data = name.data; parts[num_parts].length = SIZE_MAX; num_parts++; ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); // Single characters num_parts = 0; for (size_t i = 0; i < name.length; i++) { parts[num_parts].data = &name.data[i]; parts[num_parts].length = 1; num_parts++; } ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); // Single characters (NULL terminated) num_parts = 0; for (size_t i = 0; i < name.length; i++) { char *part = chars + num_parts*2; part[0] = name.data[i]; part[1] = '\0'; parts[num_parts].data = part; parts[num_parts].length = SIZE_MAX; num_parts++; } ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); // Single characters with empty in between num_parts = 0; for (size_t i = 0; i < name.length; i++) { parts[num_parts].data = &name.data[i]; parts[num_parts].length = 1; num_parts++; parts[num_parts].data = NULL; parts[num_parts].length = 0; num_parts++; } ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); // Even parts for (size_t step = 1; step < name.length; step++) { num_parts = 0; for (size_t i = 0; i < name.length; i += step) { parts[num_parts].data = name.data + i; parts[num_parts].length = step; if (i + step > name.length) { parts[num_parts].length = SIZE_MAX; } num_parts++; } ufbxt_assert(ufbx_find_prop_concat(props, parts, num_parts) == prop); } } props = props->defaults; } } } #endif UFBXT_FILE_TEST(synthetic_duplicate_id) #if UFBXT_IMPL { if (scene->metadata.version >= 7000) { ufbxt_check_warning(scene, UFBX_WARNING_DUPLICATE_OBJECT_ID, 5, NULL); } else { ufbxt_check_warning(scene, UFBX_WARNING_DUPLICATE_OBJECT_ID, 3, NULL); } // Don't make any assumptions about the scene, ufbxt_check_scene() makes sure // that it doesn't break any of the API guarantees. } #endif UFBXT_FILE_TEST_OPTS(synthetic_recursive_transform, ufbxt_retain_dom_opts) #if UFBXT_IMPL { ufbx_dom_node *takes = ufbx_dom_find(scene->dom_root, "Takes"); ufbxt_assert(takes); ufbx_dom_node *take = ufbx_dom_find(takes, "Take"); ufbxt_assert(take); ufbx_dom_node *model = ufbx_dom_find(take, "Model"); ufbxt_assert(model); ufbx_dom_node *channel = ufbx_dom_find(model, "Channel"); ufbxt_assert(channel); ufbxt_assert(channel->values.count == 1); ufbxt_assert(channel->values.data[0].type == UFBX_DOM_VALUE_STRING); ufbxt_assert(!strcmp(channel->values.data[0].value_str.data, "Transform")); ufbx_dom_node *rec_channel = ufbx_dom_find(channel, "Channel"); ufbxt_assert(rec_channel); size_t count = 1; for (;;) { ufbxt_hintf("count=%zu", count); count += 1; ufbxt_assert(rec_channel->values.count == 1); ufbxt_assert(rec_channel->values.data[0].type == UFBX_DOM_VALUE_STRING); ufbxt_assert(!strcmp(rec_channel->values.data[0].value_str.data, "Transform")); if (rec_channel->children.count == 0) break; ufbxt_assert(rec_channel->children.count == 1); rec_channel = rec_channel->children.data[0]; } ufbxt_assert(count == 29); } #endif UFBXT_FILE_TEST(synthetic_recursive_connections) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); ufbx_prop *prop = ufbx_find_prop(&node->props, "Lcl Translation"); ufbxt_assert(prop); ufbxt_assert((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0); ufbx_vec3 ref = { 1.0f, 0.0f, 0.0f }; ufbx_prop value = ufbx_evaluate_prop(&scene->anim, &node->element, "Lcl Translation", 0.5); ufbxt_assert_close_vec3(err, value.value_vec3, ref); } { ufbx_node *node = ufbx_find_node(scene, "pCube3"); ufbxt_assert(node); ufbx_prop *prop = ufbx_find_prop(&node->props, "Lcl Translation"); ufbxt_assert(prop); ufbxt_assert((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0); ufbx_vec3 ref = { 0.0f, 0.0f, 1.0f }; ufbx_prop value = ufbx_evaluate_prop(&scene->anim, &node->element, "Lcl Translation", 0.5); ufbxt_assert_close_vec3(err, value.value_vec3, ref); } } #endif UFBXT_FILE_TEST(synthetic_rotation_order_layers) #if UFBXT_IMPL { ufbx_anim_layer *layer_base_layer = ufbx_as_anim_layer(ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "BaseLayer")); ufbx_anim_layer *layer_base = ufbx_as_anim_layer(ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Base")); ufbx_anim_layer *layer_rotation = ufbx_as_anim_layer(ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Rotation")); ufbx_anim_layer_desc layers[] = { { layer_base_layer, 1.0f }, { layer_base, 0.5f }, { layer_rotation, 0.25f }, }; ufbx_anim anim = { 0 }; anim.layers.data = layers; anim.layers.count = ufbxt_arraycount(layers); ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); // We don't really care about the result here as runtime rotation order is not really supported, // just want to make sure we don't hit any infinite recursion with rotation order layers (void)ufbx_evaluate_prop(&anim, &node->element, "Lcl Rotation", 0.2f); } #endif UFBXT_FILE_TEST(maya_notes) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node); ufbx_prop *notes = ufbx_find_prop(&node->props, "notes"); ufbxt_assert(notes); const char *lines[] = { "pCube1 notes:\n", }; const char *str = notes->value_str.data; for (size_t i = 0; i < ufbxt_arraycount(lines); i++) { ufbxt_hintf("i=%zu", i); const char *end = strchr(str, '\n'); if (!end) { end = str + strlen(str); } else { end += 1; } size_t len = (size_t)(end - str); ufbxt_assert(!strncmp(str, lines[i], len)); ufbxt_assert(len == strlen(lines[i])); str = end; } for (size_t i = 0; i < 4096; i++) { const char *end; unsigned long l = strtoul(str, &end, 10); ufbxt_assert(l == i); if (i + 1 < 16384) { ufbxt_assert(*end == ' '); str = end + 1; } } } { ufbx_material *material = ufbx_find_material(scene, "lambert1"); ufbxt_assert(material); ufbx_prop *notes = ufbx_find_prop(&material->props, "notes"); ufbxt_assert(notes); const char *lines[] = { "lambert1 notes:\n", "\n", "- material\n", "- \"lambertian\"\n", "- unicode: \x61\xc3\x9f???\n", "- tab\t\n", "- cr \n", }; const char *str = notes->value_str.data; for (size_t i = 0; i < ufbxt_arraycount(lines); i++) { ufbxt_hintf("i=%zu", i); const char *end = strchr(str, '\n'); if (!end) { end = str + strlen(str); } else { end += 1; } size_t len = (size_t)(end - str); ufbxt_assert(!strncmp(str, lines[i], len)); ufbxt_assert(len == strlen(lines[i])); str = end; } ufbxt_assert(*str == '\0'); } } #endif