#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #endif static void ufbxt_assert_fail_imp(const char *func, const char *file, size_t line, const char *msg) { fprintf(stderr, "%s:%zu: %s(%s) failed\n", file, line, func, msg); exit(2); } #define ufbxt_assert_fail(file, line, msg) ufbxt_assert_fail_imp("ufbxt_assert_fail", file, line, msg) #define ufbxt_assert(m_cond) do { if (!(m_cond)) ufbxt_assert_fail_imp("ufbxt_assert", __FILE__, __LINE__, #m_cond); } while (0) #define ufbx_assert(m_cond) do { if (!(m_cond)) ufbxt_assert_fail_imp("ufbx_assert", __FILE__, __LINE__, #m_cond); } while (0) bool g_verbose = false; #include "../ufbx.h" #include "check_scene.h" #include "check_material.h" #include "testing_utils.h" #include "cputime.h" #ifdef _WIN32 int wmain(int argc, wchar_t **wide_argv) #else int main(int argc, char **argv) #endif { #ifdef _WIN32 char **argv = (char**)malloc(sizeof(char*) * argc); ufbxt_assert(argv); for (int i = 0; i < argc; i++) { int res = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, NULL, 0, NULL, NULL); ufbxt_assert(res > 0); size_t dst_size = (size_t)res + 1; char *dst = (char*)malloc(dst_size); ufbxt_assert(dst); res = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, dst, (int)dst_size, NULL, NULL); ufbxt_assert(res > 0 && (size_t)res < dst_size); argv[i] = dst; } #endif cputime_begin_init(); const char *path = NULL; const char *obj_path = NULL; const char *mat_path = NULL; const char *dump_obj_path = NULL; int profile_runs = 0; int frame = INT_MIN; bool allow_bad_unicode = false; bool sink = false; bool ignore_missing_external = false; bool dedicated_allocs = false; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-v")) { g_verbose = true; } else if (!strcmp(argv[i], "--obj")) { if (++i < argc) obj_path = argv[i]; } else if (!strcmp(argv[i], "--mat")) { if (++i < argc) mat_path = argv[i]; } else if (!strcmp(argv[i], "--dump-obj")) { if (++i < argc) dump_obj_path = argv[i]; } else if (!strcmp(argv[i], "--profile-runs")) { if (++i < argc) profile_runs = atoi(argv[i]); } else if (!strcmp(argv[i], "--frame")) { if (++i < argc) frame = atoi(argv[i]); } else if (!strcmp(argv[i], "--allow-bad-unicode")) { allow_bad_unicode = true; } else if (!strcmp(argv[i], "--dedicated-allocs")) { dedicated_allocs = true; } else if (!strcmp(argv[i], "--sink")) { sink = true; } else if (!strcmp(argv[i], "--ignore-missing-external")) { ignore_missing_external = true; } else if (argv[i][0] == '-') { fprintf(stderr, "Unrecognized flag: %s\n", argv[i]); exit(1); } else { path = argv[i]; } } if (!path) { fprintf(stderr, "Usage: check_fbx \n"); return 1; } if (strstr(path, "ufbx-bad-unicode")) { allow_bad_unicode = true; } ufbx_load_opts opts = { 0 }; opts.evaluate_skinning = true; opts.evaluate_caches = true; opts.load_external_files = true; opts.generate_missing_normals = true; opts.ignore_missing_external_files = ignore_missing_external; opts.target_axes = ufbx_axes_right_handed_y_up; opts.target_unit_meters = 0.01; opts.obj_search_mtl_by_filename = true; if (dedicated_allocs) { opts.temp_allocator.huge_threshold = 1; opts.result_allocator.huge_threshold = 1; } if (!allow_bad_unicode) { opts.unicode_error_handling = UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING; } ufbx_error error; ufbx_scene *scene; uint64_t load_delta = 0; { uint64_t load_begin = cputime_cpu_tick(); scene = ufbx_load_file(path, &opts, &error); uint64_t load_end = cputime_cpu_tick(); load_delta = load_end - load_begin; } if (!scene) { char buf[1024]; ufbx_format_error(buf, sizeof(buf), &error); fprintf(stderr, "%s\n", buf); return 1; } cputime_end_init(); { size_t fbx_size = ufbxt_file_size(path); printf("Loaded in %.2fms: File %.1fkB, temp %.1fkB (%zu allocs), result %.1fkB (%zu allocs)\n", cputime_cpu_delta_to_sec(NULL, load_delta) * 1e3, (double)fbx_size * 1e-3, (double)scene->metadata.temp_memory_used * 1e-3, scene->metadata.temp_allocs, (double)scene->metadata.result_memory_used * 1e-3, scene->metadata.result_allocs ); } const char *exporters[] = { "Unknown", "FBX SDK", "Blender Binary", "Blender ASCII", "MotionBuilder", "Unity Exporter (from Building Crafter)", }; const char *formats[2][2] = { { "binary", "binary (big-endian)" }, { "ascii", "!?!?ascii (big-endian)!?!?" }, }; const char *application = scene->metadata.latest_application.name.data; if (!application[0]) application = "unknown"; printf("FBX %u %s via %s %u.%u.%u (%s)\n", scene->metadata.version, formats[scene->metadata.ascii][scene->metadata.big_endian], exporters[scene->metadata.exporter], ufbx_version_major(scene->metadata.exporter_version), ufbx_version_minor(scene->metadata.exporter_version), ufbx_version_patch(scene->metadata.exporter_version), application); { size_t fbx_size = 0; void *fbx_data = ufbxt_read_file(path, &fbx_size); if (fbx_data) { for (int i = 0; i < profile_runs; i++) { uint64_t load_begin = cputime_cpu_tick(); ufbx_scene *memory_scene = ufbx_load_memory(fbx_data, fbx_size, NULL, NULL); uint64_t load_end = cputime_cpu_tick(); printf("Loaded in %.2fms: File %.1fkB, temp %.1fkB (%zu allocs), result %.1fkB (%zu allocs)\n", cputime_cpu_delta_to_sec(NULL, load_end - load_begin) * 1e3, (double)fbx_size * 1e-3, (double)scene->metadata.temp_memory_used * 1e-3, scene->metadata.temp_allocs, (double)scene->metadata.result_memory_used * 1e-3, scene->metadata.result_allocs ); ufbxt_assert(memory_scene); ufbx_free_scene(memory_scene); } free(fbx_data); } } int result = 0; if (!strstr(path, "ufbx-unknown")) { bool ignore_unknowns = false; bool has_unknowns = false; for (size_t i = 0; i < scene->unknowns.count; i++) { ufbx_unknown *unknown = scene->unknowns.data[i]; if (strstr(unknown->super_type.data, "MotionBuilder")) continue; if (strstr(unknown->type.data, "Container")) continue; if (!strcmp(unknown->super_type.data, "Object") && unknown->type.length == 0 && unknown->sub_type.length == 0) continue; if (!strcmp(unknown->super_type.data, "PluginParameters")) continue; if (!strcmp(unknown->super_type.data, "TimelineXTrack")) continue; if (!strcmp(unknown->super_type.data, "GlobalShading")) continue; if (!strcmp(unknown->super_type.data, "ControlSetPlug")) continue; if (!strcmp(unknown->sub_type.data, "NodeAttribute")) continue; if (!strcmp(unknown->type.data, "GroupSelection")) continue; if (!strcmp(unknown->name.data, "ADSKAssetReferencesVersion3.0")) { ignore_unknowns = true; } has_unknowns = true; fprintf(stderr, "Unknown element: %s/%s/%s : %s\n", unknown->super_type.data, unknown->type.data, unknown->sub_type.data, unknown->name.data); } if (has_unknowns && !ignore_unknowns) { result = 3; } } bool known_unknown = false; if (strstr(scene->metadata.creator.data, "kenney")) known_unknown = true; if (strstr(scene->metadata.creator.data, "assetforge")) known_unknown = true; if (scene->metadata.version < 5800) known_unknown = true; ufbxt_assert(scene->metadata.exporter != UFBX_EXPORTER_UNKNOWN || known_unknown); ufbxt_check_scene(scene); if (obj_path) { size_t obj_size; void *obj_data = ufbxt_read_file_ex(obj_path, &obj_size); if (!obj_data) { fprintf(stderr, "Failed to read .obj file: %s\n", obj_path); return 1; } ufbxt_load_obj_opts obj_opts = { 0 }; ufbxt_obj_file *obj_file = ufbxt_load_obj(obj_data, obj_size, &obj_opts); obj_file->normalize_units = true; ufbx_scene *state; if (obj_file->animation_frame >= 0 || frame != INT_MIN) { ufbx_anim anim = scene->anim; if (obj_file->animation_name[0]) { ufbx_anim_stack *stack = ufbx_find_anim_stack(scene, obj_file->animation_name); ufbxt_assert(stack); anim = stack->anim; } double time = anim.time_begin + (double)obj_file->animation_frame / (double)scene->settings.frames_per_second; if (frame != INT_MIN) { time = (double)frame / (double)scene->settings.frames_per_second; } ufbx_evaluate_opts eval_opts = { 0 }; eval_opts.evaluate_skinning = true; eval_opts.evaluate_caches = true; eval_opts.load_external_files = true; state = ufbx_evaluate_scene(scene, &anim, time, &eval_opts, NULL); ufbxt_assert(state); } else { state = scene; ufbx_retain_scene(state); } if (dump_obj_path) { ufbxt_debug_dump_obj_scene(dump_obj_path, state); printf("Dumped .obj to %s\n", dump_obj_path); } ufbxt_diff_error err = { 0 }; ufbxt_diff_to_obj(state, obj_file, &err, 0); if (err.num > 0) { ufbx_real avg = err.sum / (ufbx_real)err.num; printf("Absolute diff: avg %.3g, max %.3g (%zu tests)\n", avg, err.max, err.num); } ufbx_free_scene(state); free(obj_file); free(obj_data); } else { if (dump_obj_path) { ufbxt_debug_dump_obj_scene(dump_obj_path, scene); printf("Dumped .obj to %s\n", dump_obj_path); } } if (mat_path) { size_t mat_size; void *mat_data = ufbxt_read_file_ex(mat_path, &mat_size); if (!mat_data) { fprintf(stderr, "Failed to read .mat file: %s\n", mat_path); return 1; } const char *mat_filename = mat_path + strlen(mat_path); while (mat_filename > mat_path && (mat_filename[-1] != '\\' && mat_filename[-1] != '/')) { mat_filename--; } bool ok = ufbxt_check_materials(scene, (const char*)mat_data, mat_filename); if (!ok && !result) { result = 4; } free(mat_data); } ufbx_free_scene(scene); if (sink) { printf("%u\n", ufbxt_sink); } return result; } #define CPUTIME_IMPLEMENTATION #ifndef UFBX_DEV #define UFBX_DEV #endif #include "cputime.h" #include "../ufbx.c"