Files
coven/modules/ufbx/test/check_fbx.c

346 lines
9.7 KiB
C

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#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 <file.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"