Getting to the way it's supposed to be!

This commit is contained in:
2024-10-12 00:43:51 +02:00
parent 84729f9d27
commit 8f2dad9cec
2663 changed files with 540071 additions and 14 deletions

View File

@@ -0,0 +1,27 @@
#define UFBXT_TEST_GROUP ""
#include "test_deflate.h"
#include "test_math.h"
#include "test_parse.h"
#include "test_mesh.h"
#include "test_transform.h"
#include "test_animation.h"
#include "test_skin.h"
#include "test_material.h"
#include "test_camera.h"
#include "test_triangulate.h"
#include "test_topology.h"
#include "test_legacy.h"
#include "test_collections.h"
#include "test_constraints.h"
#include "test_curves.h"
#include "test_cache.h"
#include "test_nurbs.h"
#include "test_obj.h"
#include "test_convert.h"
#include "test_api.h"
#include "test_scenes.h"
#include "test_fuzz.h"
#undef UFBXT_TEST_GROUP

View File

@@ -0,0 +1,345 @@
#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"

View File

@@ -0,0 +1,557 @@
#ifndef UFBXT_CHECK_MATERIAL_H_INCLUDED
#define UFBXT_CHECK_MATERIAL_H_INCLUDED
#include "../ufbx.h"
#include <stdlib.h>
static const char *ufbxt_fbx_map_name(ufbx_material_fbx_map map)
{
switch (map) {
case UFBX_MATERIAL_FBX_DIFFUSE_FACTOR: return "diffuse_factor";
case UFBX_MATERIAL_FBX_DIFFUSE_COLOR: return "diffuse_color";
case UFBX_MATERIAL_FBX_SPECULAR_FACTOR: return "specular_factor";
case UFBX_MATERIAL_FBX_SPECULAR_COLOR: return "specular_color";
case UFBX_MATERIAL_FBX_SPECULAR_EXPONENT: return "specular_exponent";
case UFBX_MATERIAL_FBX_REFLECTION_FACTOR: return "reflection_factor";
case UFBX_MATERIAL_FBX_REFLECTION_COLOR: return "reflection_color";
case UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR: return "transparency_factor";
case UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR: return "transparency_color";
case UFBX_MATERIAL_FBX_EMISSION_FACTOR: return "emission_factor";
case UFBX_MATERIAL_FBX_EMISSION_COLOR: return "emission_color";
case UFBX_MATERIAL_FBX_AMBIENT_FACTOR: return "ambient_factor";
case UFBX_MATERIAL_FBX_AMBIENT_COLOR: return "ambient_color";
case UFBX_MATERIAL_FBX_NORMAL_MAP: return "normal_map";
case UFBX_MATERIAL_FBX_BUMP: return "bump";
case UFBX_MATERIAL_FBX_BUMP_FACTOR: return "bump_factor";
case UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR: return "displacement_factor";
case UFBX_MATERIAL_FBX_DISPLACEMENT: return "displacement";
case UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR: return "vector_displacement_factor";
case UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT: return "vector_displacement";
}
ufbxt_assert(0 && "Unhandled PBR map name");
return NULL;
}
static const char *ufbxt_pbr_map_name(ufbx_material_pbr_map map)
{
switch (map) {
case UFBX_MATERIAL_PBR_BASE_FACTOR: return "base_factor";
case UFBX_MATERIAL_PBR_BASE_COLOR: return "base_color";
case UFBX_MATERIAL_PBR_ROUGHNESS: return "roughness";
case UFBX_MATERIAL_PBR_METALNESS: return "metalness";
case UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS: return "diffuse_roughness";
case UFBX_MATERIAL_PBR_SPECULAR_FACTOR: return "specular_factor";
case UFBX_MATERIAL_PBR_SPECULAR_COLOR: return "specular_color";
case UFBX_MATERIAL_PBR_SPECULAR_IOR: return "specular_ior";
case UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY: return "specular_anisotropy";
case UFBX_MATERIAL_PBR_SPECULAR_ROTATION: return "specular_rotation";
case UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR: return "transmission_factor";
case UFBX_MATERIAL_PBR_TRANSMISSION_COLOR: return "transmission_color";
case UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH: return "transmission_depth";
case UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER: return "transmission_scatter";
case UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY: return "transmission_scatter_anisotropy";
case UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION: return "transmission_dispersion";
case UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS: return "transmission_roughness";
case UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS: return "transmission_extra_roughness";
case UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY: return "transmission_priority";
case UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV: return "transmission_enable_in_aov";
case UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR: return "subsurface_factor";
case UFBX_MATERIAL_PBR_SUBSURFACE_COLOR: return "subsurface_color";
case UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS: return "subsurface_radius";
case UFBX_MATERIAL_PBR_SUBSURFACE_SCALE: return "subsurface_scale";
case UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY: return "subsurface_anisotropy";
case UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR: return "subsurface_tint_color";
case UFBX_MATERIAL_PBR_SUBSURFACE_TYPE: return "subsurface_type";
case UFBX_MATERIAL_PBR_SHEEN_FACTOR: return "sheen_factor";
case UFBX_MATERIAL_PBR_SHEEN_COLOR: return "sheen_color";
case UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS: return "sheen_roughness";
case UFBX_MATERIAL_PBR_COAT_FACTOR: return "coat_factor";
case UFBX_MATERIAL_PBR_COAT_COLOR: return "coat_color";
case UFBX_MATERIAL_PBR_COAT_ROUGHNESS: return "coat_roughness";
case UFBX_MATERIAL_PBR_COAT_IOR: return "coat_ior";
case UFBX_MATERIAL_PBR_COAT_ANISOTROPY: return "coat_anisotropy";
case UFBX_MATERIAL_PBR_COAT_ROTATION: return "coat_rotation";
case UFBX_MATERIAL_PBR_COAT_NORMAL: return "coat_normal";
case UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR: return "coat_affect_base_color";
case UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS: return "coat_affect_base_roughness";
case UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS: return "thin_film_thickness";
case UFBX_MATERIAL_PBR_THIN_FILM_IOR: return "thin_film_ior";
case UFBX_MATERIAL_PBR_EMISSION_FACTOR: return "emission_factor";
case UFBX_MATERIAL_PBR_EMISSION_COLOR: return "emission_color";
case UFBX_MATERIAL_PBR_OPACITY: return "opacity";
case UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE: return "indirect_diffuse";
case UFBX_MATERIAL_PBR_INDIRECT_SPECULAR: return "indirect_specular";
case UFBX_MATERIAL_PBR_NORMAL_MAP: return "normal_map";
case UFBX_MATERIAL_PBR_TANGENT_MAP: return "tangent_map";
case UFBX_MATERIAL_PBR_DISPLACEMENT_MAP: return "displacement_map";
case UFBX_MATERIAL_PBR_MATTE_FACTOR: return "matte_factor";
case UFBX_MATERIAL_PBR_MATTE_COLOR: return "matte_color";
case UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION: return "ambient_occlusion";
case UFBX_MATERIAL_PBR_GLOSSINESS: return "glossiness";
case UFBX_MATERIAL_PBR_COAT_GLOSSINESS: return "coat_glossiness";
case UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS: return "transmission_glossiness";
}
ufbxt_assert(0 && "Unhandled PBR map name");
return 0;
}
static const char *ufbxt_material_feature_name(ufbx_material_pbr_map map)
{
switch (map) {
case UFBX_MATERIAL_FEATURE_PBR: return "pbr";
case UFBX_MATERIAL_FEATURE_METALNESS: return "metalness";
case UFBX_MATERIAL_FEATURE_DIFFUSE: return "diffuse";
case UFBX_MATERIAL_FEATURE_SPECULAR: return "specular";
case UFBX_MATERIAL_FEATURE_EMISSION: return "emission";
case UFBX_MATERIAL_FEATURE_TRANSMISSION: return "transmission";
case UFBX_MATERIAL_FEATURE_COAT: return "coat";
case UFBX_MATERIAL_FEATURE_SHEEN: return "sheen";
case UFBX_MATERIAL_FEATURE_OPACITY: return "opacity";
case UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION: return "ambient_occlusion";
case UFBX_MATERIAL_FEATURE_MATTE: return "matte";
case UFBX_MATERIAL_FEATURE_UNLIT: return "unlit";
case UFBX_MATERIAL_FEATURE_IOR: return "ior";
case UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS: return "diffuse_roughness";
case UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS: return "transmission_roughness";
case UFBX_MATERIAL_FEATURE_THIN_WALLED: return "thin_walled";
case UFBX_MATERIAL_FEATURE_CAUSTICS: return "caustics";
case UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND: return "exit_to_background";
case UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS: return "internal_reflections";
case UFBX_MATERIAL_FEATURE_DOUBLE_SIDED: return "double_sided";
case UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS: return "roughness_as_glossiness";
case UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS: return "coat_roughness_as_glossiness";
case UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS: return "transmission_roughness_as_glossiness";
}
ufbxt_assert(0 && "Unhandled material feature name");
return 0;
}
static const char *ufbxt_shader_type_name(ufbx_shader_type map)
{
switch (map) {
case UFBX_SHADER_UNKNOWN: return "unknown";
case UFBX_SHADER_FBX_LAMBERT: return "fbx_lambert";
case UFBX_SHADER_FBX_PHONG: return "fbx_phong";
case UFBX_SHADER_OSL_STANDARD_SURFACE: return "osl_standard_surface";
case UFBX_SHADER_ARNOLD_STANDARD_SURFACE: return "arnold_standard_surface";
case UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL: return "3ds_max_physical_material";
case UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH: return "3ds_max_pbr_metal_rough";
case UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS: return "3ds_max_pbr_spec_gloss";
case UFBX_SHADER_GLTF_MATERIAL: return "gltf_material";
case UFBX_SHADER_SHADERFX_GRAPH: return "shaderfx_graph";
case UFBX_SHADER_BLENDER_PHONG: return "blender_phong";
case UFBX_SHADER_WAVEFRONT_MTL: return "wavefront_mtl";
}
ufbxt_assert(0 && "Unhandled material feature name");
return 0;
}
static size_t ufbxt_tokenize_line(char *buf, size_t buf_len, const char **tokens, size_t max_tokens, const char **p_line, const char *sep_chars)
{
bool prev_sep = true;
size_t num_tokens = 0;
size_t buf_pos = 0;
const char *p = *p_line;
for (;;) {
char c = *p;
if (c == '\0' || c == '\n') break;
bool sep = false;
for (const char *s = sep_chars; *s; s++) {
if (*s == c) {
sep = true;
break;
}
}
if (!sep) {
if (prev_sep) {
ufbxt_assert(buf_pos < buf_len);
buf[buf_pos++] = '\0';
ufbxt_assert(num_tokens < max_tokens);
tokens[num_tokens++] = buf + buf_pos;
}
if (c == '"') {
p++;
while (*p != '"') {
c = *p;
if (c == '\\') {
p++;
switch (*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case '\\': c = '\\'; break;
case '"': c = '"'; break;
default:
fprintf(stderr, "Bad escape '\\%c'\n", *p);
ufbxt_assert(0 && "Bad escape");
return 0;
}
}
ufbxt_assert(buf_pos < buf_len);
buf[buf_pos++] = c;
p++;
}
} else {
ufbxt_assert(buf_pos < buf_len);
buf[buf_pos++] = c;
}
}
prev_sep = sep;
p++;
}
ufbxt_assert(buf_pos < buf_len);
buf[buf_pos++] = '\0';
for (size_t i = num_tokens; i < max_tokens; i++) {
tokens[i] = "";
}
if (*p == '\n') p += 1;
*p_line = p;
return num_tokens;
}
static bool ufbxt_check_materials(ufbx_scene *scene, const char *spec, const char *filename)
{
bool ok = true;
char line_buf[512];
const char *tokens[8];
char dot_buf[512];
const char *dots[8];
bool seen_materials[256];
ufbx_material *material = NULL;
size_t num_materials = 0;
size_t num_props = 0;
double err_sum = 0.0;
double err_max = 0.0;
size_t err_num = 0;
bool material_error = false;
bool require_all = false;
long version = 0;
int line = 0;
while (*spec != '\0') {
size_t num_tokens = ufbxt_tokenize_line(line_buf, sizeof(line_buf), tokens, ufbxt_arraycount(tokens), &spec, " \t\r");
line++;
if (num_tokens == 0) continue;
const char *first_token = tokens[0];
size_t num_dots = ufbxt_tokenize_line(dot_buf, sizeof(dot_buf), dots, ufbxt_arraycount(dots), &first_token, ".");
if (!strcmp(dots[0], "version")) {
char *end = NULL;
version = strtol(tokens[1], &end, 10);
if (!end || *end != '\0') {
fprintf(stderr, "%s:%d: Bad value in '%s': '%s'\n", filename, line, tokens[0], tokens[1]);
ok = false;
continue;
}
continue;
} else if (!strcmp(dots[0], "require")) {
if (!strcmp(tokens[1], "all")) {
if (scene->materials.count > ufbxt_arraycount(seen_materials)) {
fprintf(stderr, "%s:%d: Too many materials in file for 'reqiure all': %zu (max %zu)\n", filename, line,
scene->materials.count, (size_t)ufbxt_arraycount(seen_materials));
ok = false;
continue;
}
require_all = true;
memset(seen_materials, 0, scene->materials.count * sizeof(bool));
} else {
fprintf(stderr, "%s:%d: Bad require directive: '%s'\n", filename, line, tokens[1]);
ok = false;
continue;
}
continue;
} else if (!strcmp(dots[0], "material")) {
if (*tokens[1] == '\0') {
fprintf(stderr, "%s:%d: Expected material name for 'material'\n", filename, line);
ok = false;
}
material = ufbx_find_material(scene, tokens[1]);
if (!material) {
fprintf(stderr, "%s:%d: Material not found: '%s'\n", filename, line, tokens[1]);
ok = false;
}
material_error = !material;
if (material) {
seen_materials[material->typed_id] = true;
}
num_materials++;
continue;
}
if (!material) {
if (!material_error) {
fprintf(stderr, "%s:%d: Statement '%s' needs to have a material defined\n", filename, line, tokens[0]);
}
ok = false;
continue;
}
num_props++;
if (!strcmp(dots[0], "fbx") || !strcmp(dots[0], "pbr")) {
ufbx_material_map *map = NULL;
if (!strcmp(dots[0], "fbx")) {
ufbx_material_fbx_map name = (ufbx_material_fbx_map)UFBX_MATERIAL_FBX_MAP_COUNT;
for (size_t i = 0; i < UFBX_MATERIAL_FBX_MAP_COUNT; i++) {
const char *str = ufbxt_fbx_map_name((ufbx_material_fbx_map)i);
if (!strcmp(dots[1], str)) {
name = (ufbx_material_fbx_map)i;
break;
}
}
if (name == (ufbx_material_fbx_map)UFBX_MATERIAL_FBX_MAP_COUNT) {
fprintf(stderr, "%s:%d: Unknown FBX material map '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
ok = false;
} else {
map = &material->fbx.maps[name];
}
} else if (!strcmp(dots[0], "pbr")) {
ufbx_material_pbr_map name = (ufbx_material_pbr_map)UFBX_MATERIAL_PBR_MAP_COUNT;
for (size_t i = 0; i < UFBX_MATERIAL_PBR_MAP_COUNT; i++) {
const char *str = ufbxt_pbr_map_name((ufbx_material_pbr_map)i);
if (!strcmp(dots[1], str)) {
name = (ufbx_material_pbr_map)i;
break;
}
}
if (name == (ufbx_material_pbr_map)UFBX_MATERIAL_PBR_MAP_COUNT) {
fprintf(stderr, "%s:%d: Unknown PBR material map '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
ok = false;
} else {
map = &material->pbr.maps[name];
}
} else {
ufbxt_assert(0 && "Unhandled branch");
}
// Errors reported above, but set ok to false just to be sure..
if (!map) {
ok = false;
continue;
}
if (!strcmp(dots[2], "texture")) {
size_t tok_start = 1;
bool content = false;
for (;;) {
const char *tok = tokens[tok_start];
if (!strcmp(tok, "content")) {
content = true;
} else {
break;
}
tok_start++;
}
const char *tex_name = tokens[tok_start];
if (map->texture) {
if (strcmp(map->texture->relative_filename.data, tex_name) != 0) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s is different: got '%s', expected '%s'\n", filename, line,
material->name.data, dots[0], dots[1], map->texture->relative_filename.data, tex_name);
ok = false;
}
if (content && map->texture->content.size == 0) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s expected content for the texture %s\n", filename, line,
material->name.data, dots[0], dots[1], map->texture->relative_filename.data);
ok = false;
}
} else {
fprintf(stderr, "%s:%d: Material '%s' %s.%s missing texture, expected '%s'\n", filename, line,
material->name.data, dots[0], dots[1], tex_name);
ok = false;
}
} else {
size_t tok_start = 1;
bool implicit = false;
bool widen = false;
for (;;) {
const char *tok = tokens[tok_start];
if (!strcmp(tok, "implicit")) {
implicit = true;
} else if (!strcmp(tok, "widen")) {
widen = true;
} else {
break;
}
tok_start++;
}
double ref[4];
size_t num_ref = 0;
for (; num_ref < 4; num_ref++) {
const char *tok = tokens[tok_start + num_ref];
if (!*tok) break;
char *end = NULL;
ref[num_ref] = strtod(tok, &end);
if (!end || *end != '\0') {
fprintf(stderr, "%s:%d: Bad number in '%s': '%s'\n", filename, line, tokens[0], tok);
ok = false;
num_ref = 0;
break;
}
}
if (num_ref == 0) continue;
if (!implicit && !map->has_value) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s not defined in material\n", filename, line,
material->name.data, dots[0], dots[1]);
ok = false;
continue;
} else if (implicit && map->has_value) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s defined in material, expected implicit\n", filename, line,
material->name.data, dots[0], dots[1]);
ok = false;
continue;
}
if (!implicit && !widen && (size_t)map->value_components != num_ref) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s has wrong number of components: got %zu, expected %zu\n", filename, line,
material->name.data, dots[0], dots[1], (size_t)map->value_components, num_ref);
ok = false;
continue;
}
if (widen && map->value_components >= num_ref) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s exected to be widened got %zu components, expected %zu\n", filename, line,
material->name.data, dots[0], dots[1], (size_t)map->value_components, num_ref);
ok = false;
continue;
}
char mat_str[128];
char ref_str[128];
size_t mat_pos = 0;
size_t ref_pos = 0;
bool equal = true;
for (size_t i = 0; i < num_ref; i++) {
double mat_value = map->value_vec4.v[i];
double ref_value = ref[i];
double err = fabs(mat_value - ref_value);
if (err > 0.002) {
equal = false;
}
err_sum += err;
if (err > err_max) err_max = err;
err_num += 1;
if (i > 0) {
mat_pos += (size_t)snprintf(mat_str + mat_pos, sizeof(mat_str) - mat_pos, ", ");
ref_pos += (size_t)snprintf(ref_str + ref_pos, sizeof(ref_str) - ref_pos, ", ");
}
mat_pos += (size_t)snprintf(mat_str + mat_pos, sizeof(mat_str) - mat_pos, "%.3f", mat_value);
ref_pos += (size_t)snprintf(ref_str + ref_pos, sizeof(ref_str) - ref_pos, "%.3f", ref_value);
}
if (!equal) {
fprintf(stderr, "%s:%d: Material '%s' %s.%s has wrong value: got (%s), expected (%s)\n", filename, line,
material->name.data, dots[0], dots[1], mat_str, ref_str);
ok = false;
}
}
} else if (!strcmp(dots[0], "features")) {
ufbx_material_feature name = (ufbx_material_feature)UFBX_MATERIAL_FEATURE_COUNT;
for (size_t i = 0; i < UFBX_MATERIAL_FEATURE_COUNT; i++) {
const char *str = ufbxt_material_feature_name((ufbx_material_feature)i);
if (!strcmp(dots[1], str)) {
name = (ufbx_material_feature)i;
break;
}
}
if (name == (ufbx_material_feature)UFBX_MATERIAL_FEATURE_COUNT) {
fprintf(stderr, "%s:%d: Unknown material feature '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
ok = false;
}
ufbx_material_feature_info *feature = &material->features.features[name];
char *end = NULL;
long enabled = strtol(tokens[1], &end, 10);
if (!end || *end != '\0') {
fprintf(stderr, "%s:%d: Bad value in '%s': '%s'\n", filename, line, tokens[0], tokens[1]);
ok = false;
continue;
}
if (enabled != (long)feature->enabled) {
fprintf(stderr, "%s:%d: Material '%s' features.%s mismatch: got %ld, expected %ld\n", filename, line,
material->name.data, dots[1], (long)feature->enabled, enabled);
ok = false;
continue;
}
} else if (!strcmp(dots[0], "shader_type")) {
ufbx_shader_type name = (ufbx_shader_type)UFBX_SHADER_TYPE_COUNT;
for (size_t i = 0; i < UFBX_SHADER_TYPE_COUNT; i++) {
const char *str = ufbxt_shader_type_name((ufbx_shader_type)i);
if (!strcmp(tokens[1], str)) {
name = (ufbx_shader_type)i;
break;
}
}
if (name == (ufbx_shader_type)UFBX_SHADER_TYPE_COUNT) {
fprintf(stderr, "%s:%d: Unknown shader type '%s' in '%s'\n", filename, line, tokens[1], tokens[0]);
ok = false;
}
if (material->shader_type != name) {
const char *mat_name = ufbxt_shader_type_name(material->shader_type);
fprintf(stderr, "%s:%d: Material '%s' shader type mismatch: got '%s', expected '%s'\n", filename, line,
material->name.data, mat_name, tokens[1]);
}
} else {
fprintf(stderr, "%s:%d: Invalid token '%s'\n", filename, line, tokens[0]);
ok = false;
}
}
if (require_all) {
for (size_t i = 0; i < scene->materials.count; i++) {
if (!seen_materials[i]) {
fprintf(stderr, "%s: 'require all': Material in FBX not described: '%s'\n", filename,
scene->materials.data[i]->name.data);
ok = false;
}
}
}
if (ok) {
double avg = err_num > 0 ? err_sum / (double)err_num : 0.0;
printf("Checked %zu materials, %zu properties (error avg %.3g, max %.3g, %zu tests)\n", num_materials, num_props, avg, err_max, err_num);
}
return ok;
}
#endif

File diff suppressed because it is too large Load Diff

292
modules/ufbx/test/cputime.h Normal file
View File

@@ -0,0 +1,292 @@
#ifndef UFBXT_CPUTIME_H_INCLUDED
#define UFBXT_CPUTIME_H_INCLUDED
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint64_t os_tick;
uint64_t cpu_tick;
} cputime_sync_point;
typedef struct {
cputime_sync_point begin, end;
uint64_t os_freq;
uint64_t cpu_freq;
double rcp_os_freq;
double rcp_cpu_freq;
} cputime_sync_span;
extern const cputime_sync_span *cputime_default_sync;
void cputime_begin_init();
void cputime_end_init();
void cputime_init();
void cputime_begin_sync(cputime_sync_span *span);
void cputime_end_sync(cputime_sync_span *span);
uint64_t cputime_cpu_tick();
uint64_t cputime_os_tick();
double cputime_cpu_delta_to_sec(const cputime_sync_span *span, uint64_t cpu_delta);
double cputime_os_delta_to_sec(const cputime_sync_span *span, uint64_t os_delta);
double cputime_cpu_tick_to_sec(const cputime_sync_span *span, uint64_t cpu_tick);
double cputime_os_tick_to_sec(const cputime_sync_span *span, uint64_t os_tick);
#ifdef __cplusplus
}
#endif
#endif
#if defined(CPUTIME_IMPLEMENTATION)
#ifndef CPUTIME_IMEPLEMENTED
#define CPUTIME_IMEPLEMENTED
#ifdef __cplusplus
extern "C" {
#endif
#if defined(CPUTIME_STANDARD_C) || defined(UFBX_STANDARD_C)
#include <time.h>
void cputime_sync_now(cputime_sync_point *sync, int accuracy)
{
uint64_t tick = (uint64_t)clock();
sync->cpu_tick = tick;
sync->os_tick = tick;
}
uint64_t cputime_cpu_tick()
{
return (uint64_t)clock();
}
uint64_t cputime_os_tick()
{
return (uint64_t)clock();
}
static uint64_t cputime_os_freq()
{
return (uint64_t)CLOCKS_PER_SEC;
}
static void cputime_os_wait()
{
}
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#if defined(_MSC_VER)
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
void cputime_sync_now(cputime_sync_point *sync, int accuracy)
{
uint64_t best_delta = UINT64_MAX;
uint64_t os_tick = 0, cpu_tick = 0;
int runs = accuracy ? accuracy : 100;
for (int i = 0; i < runs; i++) {
LARGE_INTEGER begin, end;
QueryPerformanceCounter(&begin);
uint64_t cycle = __rdtsc();
QueryPerformanceCounter(&end);
uint64_t delta = end.QuadPart - begin.QuadPart;
if (delta < best_delta) {
os_tick = (begin.QuadPart + end.QuadPart) / 2;
cpu_tick = cycle;
}
if (delta == 0) break;
}
sync->cpu_tick = cpu_tick;
sync->os_tick = os_tick;
}
uint64_t cputime_cpu_tick()
{
return __rdtsc();
}
uint64_t cputime_os_tick()
{
LARGE_INTEGER res;
QueryPerformanceCounter(&res);
return res.QuadPart;
}
static uint64_t cputime_os_freq()
{
LARGE_INTEGER res;
QueryPerformanceFrequency(&res);
return res.QuadPart;
}
static void cputime_os_wait()
{
Sleep(1);
}
#else
#include <time.h>
#if (defined(__i386__) || defined(__x86_64__)) && (defined(__GNUC__) || defined(__clang__))
#include <x86intrin.h>
#define cputime_imp_timestamp() (uint64_t)(__rdtsc())
#else
static uint64_t cputime_imp_timestamp()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return (uint64_t)time.tv_sec*UINT64_C(1000000000) + (uint64_t)time.tv_nsec;
}
#endif
void cputime_sync_now(cputime_sync_point *sync, int accuracy)
{
uint64_t best_delta = UINT64_MAX;
uint64_t os_tick, cpu_tick;
struct timespec begin, end;
int runs = accuracy ? accuracy : 100;
for (int i = 0; i < runs; i++) {
clock_gettime(CLOCK_REALTIME, &begin);
uint64_t cycle = cputime_imp_timestamp();
clock_gettime(CLOCK_REALTIME, &end);
uint64_t begin_ns = (uint64_t)begin.tv_sec*UINT64_C(1000000000) + (uint64_t)begin.tv_nsec;
uint64_t end_ns = (uint64_t)end.tv_sec*UINT64_C(1000000000) + (uint64_t)end.tv_nsec;
uint64_t delta = end_ns - begin_ns;
if (delta < best_delta) {
os_tick = (begin_ns + end_ns) / 2;
cpu_tick = cycle;
}
if (delta == 0) break;
}
sync->cpu_tick = cpu_tick;
sync->os_tick = os_tick;
}
uint64_t cputime_cpu_tick()
{
return cputime_imp_timestamp();
}
uint64_t cputime_os_tick()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec*UINT64_C(1000000000) + (uint64_t)ts.tv_nsec;
}
static uint64_t cputime_os_freq()
{
return UINT64_C(1000000000);
}
static void cputime_os_wait()
{
struct timespec duration;
duration.tv_sec = 0;
duration.tv_nsec = 1000000000l;
nanosleep(&duration, NULL);
}
#endif
static cputime_sync_span g_cputime_sync;
const cputime_sync_span *cputime_default_sync = &g_cputime_sync;
void cputime_begin_init()
{
cputime_begin_sync(&g_cputime_sync);
}
void cputime_end_init()
{
cputime_end_sync(&g_cputime_sync);
}
void cputime_init()
{
cputime_begin_init();
cputime_end_init();
}
void cputime_begin_sync(cputime_sync_span *span)
{
cputime_sync_now(&span->begin, 0);
}
void cputime_end_sync(cputime_sync_span *span)
{
uint64_t os_freq = cputime_os_freq();
uint64_t min_span = os_freq / 1000;
uint64_t os_tick = cputime_os_tick();
while (os_tick - span->begin.os_tick <= min_span) {
cputime_os_wait();
os_tick = cputime_os_tick();
}
cputime_sync_now(&span->end, 0);
uint64_t len_os = span->end.os_tick - span->begin.os_tick;
uint64_t len_cpu = span->end.cpu_tick - span->begin.cpu_tick;
double cpu_freq = (double)len_cpu / (double)len_os * (double)os_freq;
span->os_freq = os_freq;
span->cpu_freq = (uint64_t)cpu_freq;
span->rcp_os_freq = 1.0 / (double)os_freq;
span->rcp_cpu_freq = 1.0 / cpu_freq;
}
double cputime_cpu_delta_to_sec(const cputime_sync_span *span, uint64_t cpu_delta)
{
if (!span) span = &g_cputime_sync;
return (double)cpu_delta * span->rcp_cpu_freq;
}
double cputime_os_delta_to_sec(const cputime_sync_span *span, uint64_t os_delta)
{
if (!span) span = &g_cputime_sync;
return (double)os_delta * span->rcp_os_freq;
}
double cputime_cpu_tick_to_sec(const cputime_sync_span *span, uint64_t cpu_tick)
{
if (!span) span = &g_cputime_sync;
return (double)(cpu_tick - span->begin.cpu_tick) * span->rcp_cpu_freq;
}
double cputime_os_tick_to_sec(const cputime_sync_span *span, uint64_t os_tick)
{
if (!span) span = &g_cputime_sync;
return (double)(os_tick - span->begin.os_tick) * span->rcp_os_freq;
}
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@@ -0,0 +1,430 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <cctype>
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)
#include "fbxdom.h"
#include <vector>
#include <string>
#include <algorithm>
#include "../../ufbx.h"
#include "../check_scene.h"
bool g_verbose = false;
std::vector<char> read_file(const char *path)
{
FILE *f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
std::vector<char> data;
data.resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(data.data(), 1, data.size(), f);
fclose(f);
return data;
}
using mutate_simple_fn = bool(fbxdom::node_ptr node, uint32_t index);
bool mutate_remove_child(fbxdom::node_ptr node, uint32_t index)
{
if (index >= node->children.size()) return false;
node->children.erase(node->children.begin() + index);
return true;
}
bool mutate_duplicate_child(fbxdom::node_ptr node, uint32_t index)
{
if (index >= node->children.size()) return false;
fbxdom::node_ptr child = node->children[index];
node->children.insert(node->children.begin() + index, std::move(child));
return true;
}
bool mutate_reverse_children(fbxdom::node_ptr node, uint32_t index)
{
if (index > 0) return false;
std::reverse(node->children.begin(), node->children.end());
return true;
}
bool mutate_remove_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
node->values.erase(node->values.begin() + index);
return true;
}
bool mutate_duplicate_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value value = node->values[index];
node->values.insert(node->values.begin() + index, std::move(value));
return true;
}
bool mutate_clear_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value &value = node->values[index];
value.data_int = 0;
value.data_float = 0.0f;
value.data_array.length = 0;
value.data_array.compressed_length = 0;
value.data_array.encoding = 0;
return true;
}
bool mutate_shrink_value(fbxdom::node_ptr node, uint32_t index)
{
if (node->values.size() > 10) return false;
if (index >= node->values.size()) return false;
fbxdom::value &value = node->values[index];
if (value.type == 'L') {
value.type = 'I';
} else if (value.type == 'D') {
value.type = 'F';
} else if (value.type == 'I') {
value.type = 'Y';
} else if (value.type == 'Y') {
value.type = 'C';
} else if (value.type == 'S') {
value.type = 'L';
} else {
value.type = 'I';
}
return true;
}
bool mutate_reverse_values(fbxdom::node_ptr node, uint32_t index)
{
if (index > 0) return false;
std::reverse(node->values.begin(), node->values.end());
return true;
}
struct mutator
{
const char *name;
mutate_simple_fn *simple_fn;
};
static const mutator mutators[] = {
{ "remove child", &mutate_remove_child },
{ "duplicate child", &mutate_duplicate_child },
{ "reverse children", &mutate_reverse_children },
{ "remove value", &mutate_remove_value },
{ "duplicate value", &mutate_duplicate_value },
{ "clear value", &mutate_clear_value },
{ "shrink value", &mutate_shrink_value },
{ "reverse values", &mutate_reverse_values },
};
static const size_t num_mutators = sizeof(mutators) / sizeof(*mutators);
struct mutable_result
{
fbxdom::node_ptr self;
fbxdom::node_ptr target;
};
fbxdom::node_ptr find_path(fbxdom::node_ptr root, const std::vector<uint32_t> &path)
{
fbxdom::node_ptr node = root;
for (uint32_t ix : path) {
node = node->children[ix];
}
return node;
}
void format_path(char *buf, size_t buf_size, fbxdom::node_ptr root, const std::vector<uint32_t> &path)
{
int res = snprintf(buf, buf_size, "Root");
if (res < 0 || res >= buf_size) return;
size_t pos = (size_t)res;
fbxdom::node_ptr node = root;
for (uint32_t ix : path) {
node = node->children[ix];
int res = snprintf(buf + pos, buf_size - pos, "/%u/%.*s", ix, (int)node->name.size(), node->name.data());
pos += res;
if (res < 0 || pos >= buf_size) break;
}
}
fbxdom::node_ptr mutate_path(fbxdom::node_ptr &node, const std::vector<uint32_t> &path, uint32_t depth=0)
{
fbxdom::node_ptr copy = node->copy();
if (depth < path.size()) {
fbxdom::node_ptr &child_ref = copy->children[path[depth]];
fbxdom::node_ptr child = mutate_path(child_ref, path, depth + 1);
node = copy;
return child;
} else {
node = copy;
return copy;
}
}
bool increment_path(fbxdom::node_ptr root, std::vector<uint32_t> &path)
{
{
fbxdom::node_ptr node = find_path(root, path);
if (!node->children.empty()) {
path.push_back(0);
return true;
}
}
while (!path.empty()) {
uint32_t index = path.back();
path.pop_back();
fbxdom::node_ptr current = find_path(root, path);
if (index + 1 < current->children.size()) {
path.push_back(index + 1);
return true;
}
}
return false;
}
struct mutation_iterator
{
std::vector<uint32_t> path = { };
uint32_t mutator_index = 0;
uint32_t mutator_internal_index = 0;
};
fbxdom::node_ptr next_mutation(fbxdom::node_ptr root, mutation_iterator &iter)
{
for (;;) {
fbxdom::node_ptr new_root = root;
fbxdom::node_ptr new_node = mutate_path(new_root, iter.path);
if (mutators[iter.mutator_index].simple_fn(new_node, iter.mutator_internal_index)) {
iter.mutator_internal_index++;
return new_root;
}
iter.mutator_internal_index = 0;
if (iter.mutator_index + 1 < num_mutators) {
iter.mutator_index++;
continue;
}
iter.mutator_index = 0;
if (increment_path(root, iter.path)) {
continue;
}
return { };
}
}
static char dump_buffer[16*1024*1024];
size_t g_coverage_counter = 0;
#if defined(DOMFUZZ_COVERAGE)
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop)
{
std::fill(start, stop, 1u);
}
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard)
{
if (!*guard) return;
g_coverage_counter++;
*guard = 0;
}
#endif
int selected_step = -1;
int start_step = -1;
size_t coverage_output_count = 0;
bool process_fbx_file(const char *input_file, const char *coverage_path, bool dry_run)
{
std::vector<char> data = read_file(input_file);
{
ufbx_scene *scene = ufbx_load_memory(data.data(), data.size(), NULL, NULL);
if (!scene) return 0;
if (scene->metadata.ascii || scene->metadata.big_endian) {
ufbx_free_scene(scene);
return false;
}
ufbx_free_scene(scene);
}
fbxdom::node_ptr root = fbxdom::parse(data.data(), data.size());
ufbx_error error = { };
{
size_t dump_size = fbxdom::dump(dump_buffer, sizeof(dump_buffer), root);
ufbx_scene *scene = ufbx_load_memory(data.data(), data.size(), NULL, &error);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
ufbx_free_scene(scene);
}
char path_str[512];
mutation_iterator iter;
fbxdom::node_ptr mut_root;
int current_step = 0;
while ((mut_root = next_mutation(root, iter)) != nullptr) {
if (selected_step >= 0) {
if (current_step != selected_step) {
current_step++;
continue;
}
}
if (start_step >= 0) {
if (current_step < start_step) {
current_step++;
continue;
}
}
format_path(path_str, sizeof(path_str), root, iter.path);
if (g_verbose) {
printf("%d: %s/ %s %u: ", current_step, path_str, mutators[iter.mutator_index].name, iter.mutator_internal_index);
}
size_t dump_size = fbxdom::dump(dump_buffer, sizeof(dump_buffer), mut_root);
if (dry_run) {
if (g_verbose) {
printf("SKIP (dry run)\n");
}
continue;
}
size_t pre_cov = g_coverage_counter;
ufbx_scene *scene = ufbx_load_memory(dump_buffer, dump_size, NULL, &error);
size_t post_cov = g_coverage_counter;
if (scene) {
ufbxt_check_scene(scene);
if (g_verbose) {
printf("OK!\n");
}
} else {
if (g_verbose) {
printf("%s\n", error.description.data);
}
}
if (coverage_path && pre_cov != post_cov) {
size_t index = ++coverage_output_count;
char dst_path[1024];
if (!g_verbose) {
printf("%d: %s/ %s %u: ", current_step, path_str, mutators[iter.mutator_index].name, iter.mutator_internal_index);
}
snprintf(dst_path, sizeof(dst_path), "%s%06zu.fbx", coverage_path, coverage_output_count);
printf("%zu new edges! Writing to: %s\n", post_cov - pre_cov, dst_path);
FILE *f = fopen(dst_path, "wb");
ufbxt_assert(f);
ufbxt_assert(fwrite(dump_buffer, 1, dump_size, f) == dump_size);
ufbxt_assert(fclose(f) == 0);
}
ufbx_free_scene(scene);
current_step++;
}
return true;
}
void process_coverage(const char *input_file, const char *output)
{
FILE *f = fopen(input_file, "r");
ufbxt_assert(f);
std::vector<std::string> files;
printf("== DRY RUN ==\n");
char line_buf[1024];
while (fgets(line_buf, sizeof(line_buf), f)) {
std::string line { line_buf };
while (!line.empty() && std::isspace(line.back())) {
line.pop_back();
}
if (line.empty()) continue;
printf("-- %s --\n", line.c_str());
if (!process_fbx_file(line.c_str(), NULL, true)) {
fprintf(stderr, "Failed to load test case\n");
exit(3);
}
files.push_back(std::move(line));
}
fclose(f);
if (g_coverage_counter == 0) {
fprintf(stderr, "No coverage detected.. make sure to compile with DOMFUZZ_COVERAGE");
exit(3);
}
printf("Found %zu initial edges\n", g_coverage_counter);
printf("== REAL RUN ==\n");
for (const std::string &file : files) {
printf("-- %s --\n", file.c_str());
if (!process_fbx_file(file.c_str(), output, false)) {
fprintf(stderr, "Failed to load test case\n");
exit(3);
}
}
}
int main(int argc, char **argv)
{
const char *input_file = NULL;
const char *coverage_output = NULL;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v")) {
g_verbose = 1;
} else if (!strcmp(argv[i], "--step")) {
if (++i < argc) selected_step = atoi(argv[i]);
} else if (!strcmp(argv[i], "--start")) {
if (++i < argc) start_step = atoi(argv[i]);
} else if (!strcmp(argv[i], "--coverage")) {
if (++i < argc) coverage_output = argv[i];
} else {
input_file = argv[i];
}
}
if (coverage_output) {
process_coverage(input_file, coverage_output);
} else if (input_file) {
process_fbx_file(input_file, NULL, false);
} else {
fprintf(stderr, "Usage: domfuzz <file.fbx>\n");
}
return 0;
}

View File

@@ -0,0 +1,331 @@
#include "fbxdom.h"
#include <assert.h>
#include <string.h>
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
#define FBXDOM_NATIVE_WRITE 1
#else
#define FBXDOM_NATIVE_WRITE 0
#endif
namespace fbxdom {
struct binary_reader
{
const char *ptr;
const char *start;
const char *end;
binary_reader(const void *data, size_t size)
: ptr((const char*)data), start((const char*)data)
, end((const char*)data + size) { }
size_t offset() const { return ptr - start; }
void skip(size_t size) {
assert((size_t)(end - ptr) >= size);
ptr += size;
}
template <typename T>
T peek() {
assert((size_t)(end - ptr) >= sizeof(T));
uint64_t value = 0;
for (size_t i = 0; i < sizeof(T); i++) {
value |= (uint64_t)(uint8_t)ptr[i] << i * 8;
}
T result;
memcpy(&result, &value, sizeof(T));
return result;
}
template <typename T>
T read() {
T result = peek<T>();
ptr += sizeof(T);
return result;
}
std::vector<char> read_vector(size_t size)
{
std::vector<char> result;
assert((size_t)(end - ptr) >= size);
result.insert(result.begin(), ptr, ptr + size);
ptr += size;
return result;
}
};
struct binary_writer
{
char *ptr;
char *start;
char *end;
binary_writer(const void *data, size_t size)
: ptr((char*)data), start((char*)data)
, end((char*)data + size) { }
size_t offset() { return ptr - start; }
void skip(size_t size)
{
assert((size_t)(end - ptr) >= size);
for (size_t i = 0; i < size; i++) {
*ptr++ = 0;
}
}
template <typename T, typename U>
void write(U value, size_t offset) {
#if FBXDOM_NATIVE_WRITE
*(T*)(start + offset) = (T)value;
#else
T tvalue = (T)value;
uint64_t uint = 0;
memcpy(&uint, &tvalue, sizeof(T));
for (size_t i = 0; i < sizeof(T); i++) {
start[offset + i] = (uint >> (i*8)) & 0xff;
}
#endif
}
template <typename T, typename U>
void write(U value) {
#if FBXDOM_NATIVE_WRITE
*(T*)ptr = (T)value;
ptr += sizeof(T);
#else
T tvalue = (T)value;
uint64_t uint = 0;
memcpy(&uint, &tvalue, sizeof(T));
for (size_t i = 0; i < sizeof(T); i++) {
ptr[i] = (uint >> (i*8)) & 0xff;
}
ptr += sizeof(T);
#endif
}
void write(const char *data, size_t size)
{
memcpy(ptr, data, size);
ptr += size;
}
void write(const std::vector<char> &data)
{
write(data.data(), data.size());
}
};
struct binary_parser
{
binary_reader reader;
uint32_t version = 0;
binary_parser(const void *data, size_t size)
: reader(data, size) { }
void parse_array(value &v, size_t elem_size)
{
v.data_array.length = reader.read<uint32_t>();
v.data_array.encoding = reader.read<uint32_t>();
v.data_array.compressed_length = reader.read<uint32_t>();
v.data = reader.read_vector(v.data_array.compressed_length);
}
value parse_value()
{
value v;
char type = reader.read<char>();
v.type = type;
switch (type) {
case 'B': case 'C': v.data_float = (double)(v.data_int = reader.read<int8_t>()); break;
case 'Y': v.data_float = (double)(v.data_int = reader.read<int16_t>()); break;
case 'I': v.data_float = (double)(v.data_int = reader.read<int32_t>()); break;
case 'L': v.data_float = (double)(v.data_int = reader.read<int64_t>()); break;
case 'F': v.data_int = (int64_t)(v.data_float = reader.read<float>()); break;
case 'D': v.data_int = (int64_t)(v.data_float = reader.read<double>()); break;
case 'b': case 'c': parse_array(v, 1); break;
case 'i': case 'f': parse_array(v, 4); break;
case 'l': case 'd': parse_array(v, 8); break;
case 'S': case 'R': v.data = reader.read_vector(reader.read<uint32_t>()); break;
}
return v;
}
node_ptr parse_node()
{
size_t num_properties;
size_t end_offset;
if (version >= 7500) {
end_offset = (size_t)reader.read<uint64_t>();
num_properties = (size_t)reader.read<uint64_t>();
reader.skip(8);
} else {
end_offset = (size_t)reader.read<uint32_t>();
num_properties = (size_t)reader.read<uint32_t>();
reader.skip(4);
}
size_t name_len = reader.read<uint8_t>();
if (end_offset == 0 && name_len == 0) return { };
node_ptr n = std::make_shared<node>();
n->name = reader.read_vector(name_len);
n->values.reserve(num_properties);
for (size_t i = 0; i < num_properties; i++) {
n->values.push_back(parse_value());
}
while (reader.offset() < end_offset || end_offset == 0) {
node_ptr child = parse_node();
if (!child) break;
n->children.push_back(std::move(child));
}
return n;
}
node_ptr parse_root()
{
std::vector<char> magic = reader.read_vector(21);
if (memcmp(magic.data(), "Kaydara FBX Binary \x00", 21) != 0) return { };
reader.skip(2);
version = reader.read<uint32_t>();
node_ptr root = std::make_shared<node>();
{
value v;
v.data_int = version;
root->values.push_back(v);
}
for (;;) {
node_ptr child = parse_node();
if (!child) break;
root->children.push_back(std::move(child));
}
return root;
}
};
struct binary_dumper
{
binary_writer writer;
uint32_t version = 0;
binary_dumper(void *data, size_t size)
: writer(data, size) { }
void dump_array(const value &v)
{
writer.write<uint32_t>(v.data_array.length);
writer.write<uint32_t>(v.data_array.encoding);
writer.write<uint32_t>(v.data_array.compressed_length);
writer.write(v.data);
}
void dump_value(const value &v)
{
writer.write<char>(v.type);
switch (v.type) {
case 'B': case 'C': writer.write<int8_t>(v.data_int); break;
case 'Y': writer.write<int16_t>(v.data_int); break;
case 'I': writer.write<int32_t>(v.data_int); break;
case 'L': writer.write<int64_t>(v.data_int); break;
case 'F': writer.write<float>(v.data_float); break;
case 'D': writer.write<double>(v.data_float); break;
case 'b': case 'c': dump_array(v); break;
case 'i': case 'f': dump_array(v); break;
case 'l': case 'd': dump_array(v); break;
case 'S': case 'R':
writer.write<uint32_t>(v.data.size());
writer.write(v.data);
break;
}
}
void dump_node(node_ptr node)
{
size_t node_start = writer.offset();
if (version >= 7500) {
writer.skip(8);
writer.write<uint64_t>(node->values.size());
writer.skip(8);
} else {
writer.skip(4);
writer.write<uint32_t>(node->values.size());
writer.skip(4);
}
writer.write<uint8_t>(node->name.size());
writer.write(node->name);
size_t value_start = writer.offset();
for (const value &v : node->values) {
dump_value(v);
}
size_t value_end = writer.offset();
if (!node->children.empty()) {
for (const node_ptr child : node->children) {
dump_node(child);
}
writer.skip(version >= 7500 ? 25 : 13);
}
size_t node_end = writer.offset();
if (version >= 7500) {
writer.write<uint64_t>(node_end, node_start + 0);
writer.write<uint64_t>(value_end - value_start, node_start + 16);
} else {
writer.write<uint32_t>(node_end, node_start + 0);
writer.write<uint32_t>(value_end - value_start, node_start + 8);
}
}
void dump_root(node_ptr root)
{
if (root->values.size() > 0) {
version = (uint32_t)root->values[0].data_int;
} else {
version = 7500;
}
writer.write("Kaydara FBX Binary \x00\x1a\x00", 23);
writer.write<uint32_t>(version);
for (const node_ptr child : root->children) {
dump_node(child);
}
writer.skip(version >= 7500 ? 25 : 13);
}
};
node_ptr node::copy() const
{
return std::make_shared<node>(*this);
}
node_ptr parse(const void *data, size_t size)
{
return binary_parser(data, size).parse_root();
}
size_t dump(void *dst, size_t size, node_ptr root)
{
binary_dumper d { dst, size };
d.dump_root(root);
return d.writer.offset();
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <stdint.h>
#include <string.h>
namespace fbxdom {
struct node;
using node_ptr = std::shared_ptr<node>;
struct array_info
{
uint32_t length = 0;
uint32_t encoding = 0;
uint32_t compressed_length = 0;
};
struct value
{
char type = 0;
array_info data_array;
int64_t data_int = 0;
double data_float = 0.0;
std::vector<char> data;
};
struct node
{
std::vector<char> name;
std::vector<value> values;
std::vector<node_ptr> children;
node_ptr copy() const;
};
node_ptr parse(const void *data, size_t size);
size_t dump(void *dst, size_t size, node_ptr root);
}

View File

@@ -0,0 +1,252 @@
#define _CRT_SECURE_NO_WARNINGS
#include "../ufbx.h"
#include "hash_scene.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
typedef struct {
size_t pos;
size_t size;
union {
size_t size_and_align[2];
bool reverse;
};
char data[];
} ufbxt_linear_alloactor;
static void *linear_alloc_fn(void *user, size_t size)
{
ufbxt_linear_alloactor *ator = (ufbxt_linear_alloactor*)user;
size = (size + 7u) & ~(size_t)7;
size_t pos = ator->pos;
if (ator->reverse) {
assert(pos >= size);
ator->pos = pos - size;
return ator->data + (pos - size);
} else {
assert(ator->size - pos >= size);
ator->pos = pos + size;
return ator->data + pos;
}
}
static void linear_free_allocator_fn(void *user)
{
ufbxt_linear_alloactor *ator = (ufbxt_linear_alloactor*)user;
free(ator);
}
static void linear_allocator_init(ufbx_allocator *allocator, size_t size, bool reverse)
{
ufbxt_linear_alloactor *ator = (ufbxt_linear_alloactor*)malloc(sizeof(ufbxt_linear_alloactor) + size);
assert(ator);
ator->size = size;
ator->reverse = reverse;
if (reverse) {
ator->pos = ator->size;
} else {
ator->pos = 0;
}
allocator->alloc_fn = &linear_alloc_fn;
allocator->free_allocator_fn = &linear_free_allocator_fn;
allocator->user = ator;
}
typedef struct {
bool dedicated_allocs;
bool no_read_buffer;
const char *allocator;
size_t linear_allocator_size;
} ufbxt_hasher_opts;
static void setup_allocator(ufbx_allocator_opts *ator_opts, ufbxt_hasher_opts *opts)
{
if (opts->dedicated_allocs) {
ator_opts->huge_threshold = 1;
}
size_t linear_size = opts->linear_allocator_size ? opts->linear_allocator_size : 128u*1024u*1024u;
if (opts->allocator) {
if (!strcmp(opts->allocator, "linear")) {
linear_allocator_init(&ator_opts->allocator, linear_size, false);
} else if (!strcmp(opts->allocator, "linear-reverse")) {
linear_allocator_init(&ator_opts->allocator, linear_size, true);
} else {
fprintf(stderr, "Unknown allocator: %s\n", opts->allocator);
exit(1);
}
}
}
static ufbx_scene *load_scene(const char *filename, int frame, ufbxt_hasher_opts *hasher_opts)
{
ufbx_load_opts opts = { 0 };
opts.load_external_files = true;
opts.evaluate_caches = true;
opts.evaluate_skinning = true;
opts.target_axes = ufbx_axes_right_handed_y_up;
opts.target_unit_meters = 1.0f;
if (hasher_opts->no_read_buffer) {
opts.read_buffer_size = 1;
}
setup_allocator(&opts.temp_allocator, hasher_opts);
setup_allocator(&opts.result_allocator, hasher_opts);
ufbx_error error;
ufbx_scene *scene = ufbx_load_file(filename, &opts, &error);
if (!scene) {
fprintf(stderr, "Failed to load scene: %s\n", error.description.data);
exit(2);
}
if (frame > 0) {
ufbx_evaluate_opts eval_opts = { 0 };
eval_opts.evaluate_caches = true;
eval_opts.evaluate_skinning = true;
eval_opts.load_external_files = true;
double time = scene->anim.time_begin + frame / scene->settings.frames_per_second;
ufbx_scene *state = ufbx_evaluate_scene(scene, NULL, time, NULL, &error);
if (!state) {
fprintf(stderr, "Failed to evaluate scene: %s\n", error.description.data);
exit(2);
}
ufbx_free_scene(scene);
scene = state;
}
return scene;
}
int main(int argc, char **argv)
{
const char *filename = NULL;
const char *dump_filename = NULL;
int max_dump_errors = -1;
bool do_check = false;
bool dump_all = false;
bool verbose = false;
ufbxt_hasher_opts hasher_opts = { 0 };
int frame = -1;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--frame")) {
if (++i < argc) frame = atoi(argv[i]);
} else if (!strcmp(argv[i], "--verbose")) {
verbose = true;
} else if (!strcmp(argv[i], "--check")) {
do_check = true;
} else if (!strcmp(argv[i], "--dump")) {
if (++i < argc) dump_filename = argv[i];
} else if (!strcmp(argv[i], "--dump-all")) {
dump_all = true;
} else if (!strcmp(argv[i], "--dedicated-allocs")) {
hasher_opts.dedicated_allocs = true;
} else if (!strcmp(argv[i], "--no-read-buffer")) {
hasher_opts.no_read_buffer = true;
} else if (!strcmp(argv[i], "--allocator")) {
if (++i < argc) hasher_opts.allocator = argv[i];
} else if (!strcmp(argv[i], "--max-dump-errors")) {
if (++i < argc) max_dump_errors = atoi(argv[i]);
} else {
if (filename) {
if (argv[i][0] == '-') {
fprintf(stderr, "Unknown flag: %s\n", argv[i]);
exit(1);
} else {
fprintf(stderr, "Error: Multiple input files\n");
exit(1);
}
}
filename = argv[i];
}
}
if (!filename) {
fprintf(stderr, "Usage: hash_scene <file.fbx>\n");
fprintf(stderr, " hash_scene --check <hashes.txt>\n");
return 1;
}
int num_fail = 0;
int num_total = 0;
FILE *dump_file = NULL;
if (do_check) {
FILE *f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "Failed to open hash file\n");
return 1;
}
uint64_t fbx_hash = 0;
char fbx_file[1024];
while (fscanf(f, "%" SCNx64 " %d %s", &fbx_hash, &frame, fbx_file) == 3) {
ufbx_scene *scene = load_scene(fbx_file, frame, &hasher_opts);
uint64_t hash = ufbxt_hash_scene(scene, NULL);
if (hash != fbx_hash || dump_all) {
if (num_fail < max_dump_errors || dump_all) {
if (!dump_file) {
dump_file = fopen(dump_filename, "wb");
assert(dump_file);
}
fprintf(dump_file, "\n-- %d %s\n\n", frame, fbx_file);
ufbxt_hash_scene(scene, dump_file);
}
if (!dump_all) {
printf("%s: FAIL %" PRIx64 " (local) vs %" PRIx64 " (reference)\n",
fbx_file, hash, fbx_hash);
num_fail++;
}
} else {
if (verbose) {
printf("%s: OK\n", fbx_file);
}
}
num_total++;
}
fclose(f);
if (!dump_all) {
if (verbose || num_fail > 0) {
printf("\n");
}
printf("%d/%d hashes match\n", num_total - num_fail, num_total);
}
} else {
ufbx_scene *scene = load_scene(filename, frame, &hasher_opts);
if (dump_filename) {
dump_file = fopen(dump_filename, "wb");
assert(dump_file);
}
uint64_t hash = ufbxt_hash_scene(scene, dump_file);
printf("%016" PRIx64 "\n", hash);
}
if (dump_file) {
fclose(dump_file);
}
return num_fail > 0 ? 3 : 0;
}
#define UFBX_NO_MATH_H
#define UFBX_MATH_PREFIX fdlibm_
#include "../ufbx.c"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <cctype>
bool g_verbose = false;
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)
#include <vector>
#include <memory>
#include <string>
#include "../ufbx.h"
#include "check_scene.h"
#define arraycount(arr) ((sizeof(arr))/(sizeof(*(arr))))
const char *interesting_tokens[] = {
"",
" ",
"/",
"0",
"1.0",
".0",
".",
"1e64",
"-1",
"-1000",
"1000",
"#",
"-",
"-x",
"\n",
"\\",
"\\\n",
};
// No need to duplicate `interesting_tokens[]`
const char *interesting_leading_tokens[] = {
"v",
"vn",
"vt",
"f",
"l",
"p",
"g",
"o",
"usemtl",
"newmtl",
"mtllib",
"Kd",
"map_Kd",
};
const char *interesting_lines[] = {
"g Objfuzz",
"usemtl Objfuzz",
"newmtl Objfuzz",
"mttlib objfuzz.mtl",
"v 1 2 3",
"vt 1 2",
"vn 1 2 3",
"f 1 2 3",
"f 1/1 2/2 3/3",
"f 1//1 2//2 3//3",
"f 1/1/1 2/2/2 3/3/3",
"map_Kd base.png",
};
bool mutate_insert_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (index >= arraycount(interesting_tokens)) return false;
tokens.insert(tokens.begin() + token, interesting_tokens[index]);
return true;
}
bool mutate_replace_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (index >= arraycount(interesting_tokens)) return false;
tokens[token] = interesting_tokens[index];
return true;
}
bool mutate_duplicate_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (index > 0) return false;
std::string tok = tokens[token];
tokens.insert(tokens.begin() + token, tok);
return true;
}
bool mutate_remove_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (index > 0) return false;
tokens.erase(tokens.begin() + token);
return true;
}
bool mutate_replace_first_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0) return false;
if (index >= arraycount(interesting_leading_tokens)) return false;
tokens[0] = interesting_leading_tokens[index];
return true;
}
bool mutate_insert_first_token(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0) return false;
if (index >= arraycount(interesting_leading_tokens)) return false;
tokens.insert(tokens.begin(), interesting_leading_tokens[index]);
return true;
}
bool mutate_remove_line(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0 || index > 0) return false;
tokens.clear();
return true;
}
bool mutate_insert_line(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0) return false;
if (index >= arraycount(interesting_lines)) return false;
tokens.push_back(interesting_lines[index]);
return true;
}
bool mutate_replace_line(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0) return false;
if (index >= arraycount(interesting_lines)) return false;
tokens.clear();
tokens.push_back(interesting_lines[index]);
return true;
}
bool mutate_duplicate_line(std::vector<std::string> &tokens, uint32_t token, uint32_t index)
{
if (token > 0 || index > 0) return false;
auto copy = tokens;
tokens.insert(tokens.end(), copy.begin(), copy.end());
return true;
}
enum class token_category {
initial,
slash,
comment,
whitespace,
newline,
other,
end,
};
token_category categorize_char(char c)
{
switch (c) {
case '/':
return token_category::slash;
case '#':
return token_category::comment;
case ' ':
case '\t':
return token_category::whitespace;
case '\r':
case '\n':
return token_category::newline;
default:
return token_category::other;
}
}
std::vector<std::string> tokenize_line(const std::string &line)
{
std::vector<std::string> result;
size_t len = line.length();
size_t prev_begin = 0;
token_category prev_cat = token_category::initial;
for (size_t i = 0; i <= len; i++) {
token_category cat = i < len ? categorize_char(line[i]) : token_category::end;
if (cat != prev_cat) {
if (prev_begin < i) {
result.push_back(line.substr(prev_begin, i - prev_begin));
}
prev_begin = i;
prev_cat = cat;
}
}
return result;
}
using mutate_fn = bool(std::vector<std::string> &tokens, uint32_t token, uint32_t index);
struct mutator
{
const char *name;
mutate_fn *fn;
};
static const mutator mutators[] = {
{ "insert token", &mutate_insert_token },
{ "replace token", &mutate_replace_token },
{ "duplicate token", &mutate_duplicate_token },
{ "remove token", &mutate_remove_token },
{ "replace first token", &mutate_replace_first_token },
{ "insert first token", &mutate_insert_first_token },
{ "remove line", &mutate_remove_line },
{ "insert line", &mutate_insert_line },
{ "replace line", &mutate_replace_line },
{ "duplicate line", &mutate_duplicate_line },
};
std::vector<char> read_file(const char *path)
{
FILE *f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
std::vector<char> data;
data.resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(data.data(), 1, data.size(), f);
fclose(f);
return data;
}
std::vector<std::string> split_lines(const char *data, size_t length)
{
std::vector<std::string> lines;
size_t line_begin = 0;
for (size_t i = 0; i <= length; i++) {
char c = i < length ? data[i] : '\0';
if (c == '\n' || c == '\0') {
size_t inclusive = c == '\n' ? 1 : 0;
lines.emplace_back(data + line_begin, i - line_begin + inclusive);
line_begin = i + 1;
}
}
return lines;
}
std::string concatenate(const std::string *parts, size_t count)
{
std::string result;
for (size_t i = 0; i < count; i++) {
result += parts[i];
}
return result;
}
void process_file(const char *input_file, ufbx_file_format file_format)
{
std::vector<char> data = read_file(input_file);
std::vector<std::string> lines = split_lines(data.data(), data.size());
int current_step = 0;
// Add dummy line to end for mutators
lines.emplace_back();
for (uint32_t line = 0; line < lines.size(); line++) {
std::vector<std::string> tokens = tokenize_line(lines[line]);
// Add dummy token to end for mutators
tokens.emplace_back();
for (uint32_t mut = 0; mut < arraycount(mutators); mut++) {
bool done = false;
for (uint32_t token = 0; !done && token < tokens.size(); token++) {
for (uint32_t index = 0; index < tokens.size(); index++) {
std::vector<std::string> copy = tokens;
bool ok = mutators[mut].fn(copy, token, index);
if (!ok) {
done = index == 0;
break;
}
std::string parts[] = {
concatenate(lines.data(), line),
concatenate(copy.data(), copy.size()),
concatenate(lines.data() + line + 1, lines.size() - (line + 1)),
};
std::string file = concatenate(parts, arraycount(parts));
if (g_verbose) {
printf("%d: line %u token %u: %s %u: ", current_step, line, token, mutators[mut].name, index);
}
ufbx_load_opts opts = { };
opts.file_format = file_format;
ufbx_error error;
ufbx_scene *scene = ufbx_load_memory(file.data(), file.length(), &opts, &error);
if (scene) {
if (g_verbose) {
printf("OK!\n");
}
ufbxt_check_scene(scene);
ufbx_free_scene(scene);
} else {
if (g_verbose) {
printf("%s\n", error.description.data);
}
}
current_step++;
}
}
}
}
}
int main(int argc, char **argv)
{
const char *input_file = NULL;
ufbx_file_format file_format = UFBX_FILE_FORMAT_OBJ;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v")) {
g_verbose = true;
} else if (!strcmp(argv[i], "--mtl")) {
file_format = UFBX_FILE_FORMAT_MTL;
} else {
input_file = argv[i];
}
}
if (input_file) {
process_file(input_file, file_format);
} else {
fprintf(stderr, "Usage: objfuzz <file.obj>\n");
}
return 0;
}

3399
modules/ufbx/test/runner.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,570 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "animation"
#if UFBXT_IMPL
typedef struct {
int frame;
double value;
} ufbxt_key_ref;
#endif
UFBXT_FILE_TEST(maya_interpolation_modes)
#if UFBXT_IMPL
{
// Curve evaluated values at 24fps
static const ufbx_real values[] = {
-8.653366, // Start from zero time
-8.653366,-8.602998,-8.464664,-8.257528,-8.00075,-7.713489,-7.414906,-7.124163,-6.86042,
-6.642837,-6.490576,-6.388305,-6.306414,-6.242637,-6.19471,-6.160368,-6.137348,-6.123385,
-6.116215,-6.113573,-6.113196,-5.969524,-5.825851,-5.682179,-5.538507,-5.394835,-5.251163,
-5.107491,-4.963819,-4.820146,-4.676474,-4.532802,-4.38913,-4.245458,-4.101785,-3.958113,-4.1529,
-4.347686,-4.542472,-4.737258,-4.932045,-5.126832,-5.321618,-5.516404,-5.71119,-5.905977,-5.767788,
-5.315578,-4.954943,-4.83559,-4.856855,-4.960766,-5.118543,-4.976541,-4.885909,-4.865979,-4.93845,
-5.099224,-5.270246,-5.359269,-5.349404,-5.261964,-5.118543,-5.264501,-5.33535,-5.285445,-5.058857,
-4.69383,-4.357775,-4.124978,-3.981697,-3.904232,-3.875225,-3.875225,-3.875225,-3.875225,-3.875225,
-3.875225,-3.875225,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,
-2.942738,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,5.603338,5.603338,
5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,
5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338
};
ufbxt_assert(scene->anim_layers.count == 1);
ufbx_anim_layer *layer = scene->anim_layers.data[0];
for (size_t i = 0; i < layer->anim_values.count; i++) {
ufbx_anim_value *value = layer->anim_values.data[i];
if (strcmp(value->name.data, "Lcl Translation")) continue;
ufbx_anim_curve *curve = value->curves[0];
size_t num_keys = 12;
ufbxt_assert(curve->keyframes.count == num_keys);
ufbx_keyframe *keys = curve->keyframes.data;
static const ufbxt_key_ref key_ref[] = {
{ 1, -8.653366 },
{ 11, -6.490576 },
{ 21, -6.113196 },
{ 36, -3.958113 },
{ 46, -5.905977 },
{ 53, -5.118543 },
{ 63, -5.118543 },
{ 73, -3.875225 },
{ 80, -2.942738 },
{ 89, -1.927362 },
{ 96, -1.243537 },
{ 120, 5.603338 },
};
ufbxt_assert(ufbxt_arraycount(key_ref) == num_keys);
for (size_t i = 0; i < num_keys; i++) {
ufbxt_assert_close_real(err, keys[i].time, (double)key_ref[i].frame / 24.0);
ufbxt_assert_close_real(err, keys[i].value, key_ref[i].value);
if (i > 0) ufbxt_assert(keys[i].left.dx > 0.0f);
if (i + 1 < num_keys) ufbxt_assert(keys[i].right.dx > 0.0f);
}
ufbxt_assert(keys[0].interpolation == UFBX_INTERPOLATION_CUBIC);
ufbxt_assert(keys[0].right.dy == 0.0f);
ufbxt_assert(keys[1].interpolation == UFBX_INTERPOLATION_CUBIC);
ufbxt_assert_close_real(err, keys[1].left.dy/keys[1].left.dx, keys[1].right.dy/keys[1].left.dx);
ufbxt_assert(keys[2].interpolation == UFBX_INTERPOLATION_LINEAR);
ufbxt_assert_close_real(err, keys[3].left.dy/keys[3].left.dx, keys[2].right.dy/keys[2].right.dx);
ufbxt_assert(keys[3].interpolation == UFBX_INTERPOLATION_LINEAR);
ufbxt_assert_close_real(err, keys[4].left.dy/keys[4].left.dx, keys[3].right.dy/keys[3].right.dx);
ufbxt_assert(keys[4].interpolation == UFBX_INTERPOLATION_CUBIC);
ufbxt_assert(keys[4].right.dy == 0.0f);
ufbxt_assert(keys[5].interpolation == UFBX_INTERPOLATION_CUBIC);
ufbxt_assert(keys[5].left.dy < 0.0f);
ufbxt_assert(keys[5].right.dy > 0.0f);
ufbxt_assert(keys[6].interpolation == UFBX_INTERPOLATION_CUBIC);
ufbxt_assert(keys[6].left.dy > 0.0f);
ufbxt_assert(keys[6].right.dy < 0.0f);
ufbxt_assert(keys[7].interpolation == UFBX_INTERPOLATION_CONSTANT_PREV);
ufbxt_assert(keys[8].interpolation == UFBX_INTERPOLATION_CONSTANT_PREV);
ufbxt_assert(keys[9].interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT);
ufbxt_assert(keys[10].interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT);
for (size_t i = 0; i < ufbxt_arraycount(values); i++) {
// Round up to the next frame to make stepped tangents consistent
double time = (double)i * (1.0/24.0) + 0.000001;
ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
ufbxt_assert_close_real(err, value, values[i]);
}
size_t num_samples = 64 * 1024;
for (size_t i = 0; i < num_samples; i++) {
double time = (double)i * (5.0 / (double)num_samples);
ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
ufbxt_assert(value >= -16.0f && value <= 16.0f);
}
}
}
#endif
UFBXT_FILE_TEST(maya_auto_clamp)
#if UFBXT_IMPL
{
// Curve evaluated values at 24fps
static const ufbx_real values[] = {
0.000, 0.000, 0.273, 0.515, 0.718, 0.868, 0.945, 0.920, 0.779, 0.611,
0.591, 0.747, 1.206, 2.059, 3.191, 4.489, 5.837, 7.121, 8.228, 9.042,
9.449, 9.694, 10.128, 10.610, 10.873, 10.927, 10.854, 10.704, 10.502,
10.264, 10.000,
};
ufbxt_assert(scene->anim_layers.count == 1);
ufbx_anim_layer *layer = scene->anim_layers.data[0];
for (size_t i = 0; i < layer->anim_values.count; i++) {
ufbx_anim_value *value = layer->anim_values.data[i];
if (strcmp(value->name.data, "Lcl Translation")) continue;
ufbx_anim_curve *curve = value->curves[0];
ufbxt_assert(curve->keyframes.count == 4);
for (size_t i = 0; i < ufbxt_arraycount(values); i++) {
double time = (double)i * (1.0/24.0);
ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
ufbxt_assert_close_real(err, value, values[i]);
}
}
}
#endif
UFBXT_FILE_TEST(maya_resampled)
#if UFBXT_IMPL
{
static const ufbx_real values6[] = {
0,0,0,0,0,0,0,0,0,
-0.004, -0.022, -0.056, -0.104, -0.166, -0.241, -0.328, -0.427, -0.536, -0.654, -0.783,
-0.919, -1.063, -1.214, -1.371, -1.533, -1.700, -1.871, -2.044, -2.220, -2.398, -2.577,
-2.755, -2.933, -3.109, -3.283, -3.454, -3.621, -3.784, -3.941, -4.093, -4.237, -4.374,
-4.503, -4.623, -4.733, -4.832, -4.920, -4.996, -5.059, -5.108, -5.143, -5.168, -5.186,
-5.200, -5.209, -5.215, -5.218, -5.220, -5.220, -5.216, -5.192, -5.151, -5.091, -5.013,
-4.919, -4.810, -4.686,
};
static const ufbx_real values7[] = {
0,0,0,0,0,0,0,0,
0.000, -0.004, -0.025, -0.061, -0.112, -0.176, -0.252, -0.337, -0.431, -0.533, -0.648,
-0.776, -0.915, -1.064, -1.219, -1.378, -1.539, -1.700, -1.865, -2.037, -2.216, -2.397, -2.580,
-2.761, -2.939, -3.111, -3.278, -3.447, -3.615, -3.782, -3.943, -4.098, -4.244, -4.379,
-4.500, -4.614, -4.722, -4.821, -4.911, -4.990, -5.056, -5.107, -5.143, -5.168, -5.186, -5.200,
-5.209, -5.215, -5.218, -5.220, -5.220, -5.215, -5.190, -5.145, -5.082, -5.002, -4.908,
-4.800, -4.680, -4.550, -4.403, -4.239,
};
const ufbx_real *values = scene->metadata.version >= 7000 ? values7 : values6;
size_t num_values = scene->metadata.version >= 7000 ? ufbxt_arraycount(values7) : ufbxt_arraycount(values6);
ufbxt_assert(scene->anim_layers.count == 1);
ufbx_anim_layer *layer = scene->anim_layers.data[0];
for (size_t i = 0; i < layer->anim_values.count; i++) {
ufbx_anim_value *value = layer->anim_values.data[i];
if (strcmp(value->name.data, "Lcl Translation")) continue;
ufbx_anim_curve *curve = value->curves[0];
for (size_t i = 0; i < num_values; i++) {
double time = (double)i * (1.0/200.0);
ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
ufbxt_assert_close_real(err, value, values[i]);
}
}
}
#endif
#if UFBXT_IMPL
typedef struct {
int frame;
ufbx_real intensity;
ufbx_vec3 color;
} ufbxt_anim_light_ref;
typedef struct {
int frame;
ufbx_vec3 translation;
ufbx_vec3 rotation_euler;
ufbx_vec3 scale;
} ufbxt_anim_transform_ref;
#endif
UFBXT_FILE_TEST(maya_anim_light)
#if UFBXT_IMPL
{
static const ufbxt_anim_light_ref refs[] = {
{ 0, 3.072, { 0.148, 0.095, 0.440 } },
{ 12, 1.638, { 0.102, 0.136, 0.335 } },
{ 24, 1.948, { 0.020, 0.208, 0.149 } },
{ 32, 3.676, { 0.010, 0.220, 0.113 } },
{ 40, 4.801, { 0.118, 0.195, 0.115 } },
{ 48, 3.690, { 0.288, 0.155, 0.117 } },
{ 56, 1.565, { 0.421, 0.124, 0.119 } },
{ 60, 1.145, { 0.442, 0.119, 0.119 } },
};
for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
const ufbxt_anim_light_ref *ref = &refs[i];
double time = ref->frame * (1.0/24.0);
ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
ufbxt_assert(state);
ufbxt_check_scene(state);
ufbx_node *light_node = ufbx_find_node(state, "pointLight1");
ufbxt_assert(light_node);
ufbx_light *light = light_node->light;
ufbxt_assert(light);
ufbxt_assert_close_real(err, light->intensity, ref->intensity);
ufbxt_assert_close_vec3(err, light->color, ref->color);
ufbx_free_scene(state);
}
{
ufbx_node *node = ufbx_find_node(scene, "pointLight1");
ufbxt_assert(node && node->light);
uint32_t element_id = node->light->element.element_id;
ufbx_prop_override overrides[] = {
{ element_id, "Intensity", { (ufbx_real)10.0 } },
{ element_id, "Color", { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 } },
{ element_id, "|NewProp", { 10, 20, 30 }, "Test" },
{ element_id, "IntProp", { 0, 0, 0 }, "", 15 },
};
ufbx_anim anim = scene->anim;
anim.prop_overrides = ufbx_prepare_prop_overrides(overrides, ufbxt_arraycount(overrides));
ufbx_scene *state = ufbx_evaluate_scene(scene, &anim, 1.0f, NULL, NULL);
ufbxt_assert(state);
ufbxt_check_scene(state);
ufbx_node *light_node = ufbx_find_node(state, "pointLight1");
ufbxt_assert(light_node);
ufbx_light *light = light_node->light;
ufbxt_assert(light);
ufbx_vec3 ref_color = { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 };
ufbxt_assert_close_real(err, light->intensity, 0.1f);
ufbxt_assert_close_vec3(err, light->color, ref_color);
{
ufbx_vec3 ref_new = { 10, 20, 30 };
ufbx_prop *new_prop = ufbx_find_prop(&light->props, "|NewProp");
ufbxt_assert(new_prop);
ufbxt_assert((new_prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert(!strcmp(new_prop->value_str.data, "Test"));
ufbxt_assert(new_prop->value_int == 10);
ufbxt_assert_close_vec3(err, new_prop->value_vec3, ref_new);
ufbx_prop *int_prop = ufbx_find_prop(&light->props, "IntProp");
ufbxt_assert(int_prop);
ufbxt_assert((int_prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert_close_real(err, int_prop->value_real, 15.0f);
ufbxt_assert(int_prop->value_int == 15);
}
{
ufbx_element *original_light = &node->light->element;
ufbx_prop color = ufbx_evaluate_prop(&anim, original_light, "Color", 1.0);
ufbxt_assert((color.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert_close_vec3(err, color.value_vec3, ref_color);
ufbx_prop intensity = ufbx_evaluate_prop(&anim, original_light, "Intensity", 1.0);
ufbxt_assert((intensity.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert_close_real(err, intensity.value_real, 10.0f);
ufbx_vec3 ref_new = { 10, 20, 30 };
ufbx_prop new_prop = ufbx_evaluate_prop(&anim, original_light, "|NewProp", 1.0);
ufbxt_assert((new_prop.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert(!strcmp(new_prop.value_str.data, "Test"));
ufbxt_assert(new_prop.value_int == 10);
ufbxt_assert_close_vec3(err, new_prop.value_vec3, ref_new);
ufbx_prop int_prop = ufbx_evaluate_prop(&anim, original_light, "IntProp", 1.0);
ufbxt_assert((int_prop.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
ufbxt_assert_close_real(err, int_prop.value_real, 15.0f);
ufbxt_assert(int_prop.value_int == 15);
}
ufbx_free_scene(state);
}
{
ufbx_anim_layer *layer = scene->anim_layers.data[0];
ufbx_node *node = ufbx_find_node(scene, "pointLight1");
ufbxt_assert(node && node->light);
ufbx_light *light = node->light;
{
ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &node->element);
ufbxt_assert(props.count == 3);
ufbxt_assert(!strcmp(props.data[0].prop_name.data, "Lcl Rotation"));
ufbxt_assert(!strcmp(props.data[1].prop_name.data, "Lcl Scaling"));
ufbxt_assert(!strcmp(props.data[2].prop_name.data, "Lcl Translation"));
ufbx_anim_prop *prop;
prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Rotation");
ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Rotation"));
prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Scaling");
ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Scaling"));
prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Translation");
ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Translation"));
}
{
ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &light->element);
ufbxt_assert(props.count == 2);
ufbxt_assert(!strcmp(props.data[0].prop_name.data, "Color"));
ufbxt_assert(!strcmp(props.data[1].prop_name.data, "Intensity"));
ufbx_anim_prop *prop;
prop = ufbx_find_anim_prop(layer, &light->element, "Color");
ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Color"));
prop = ufbx_find_anim_prop(layer, &light->element, "Intensity");
ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Intensity"));
prop = ufbx_find_anim_prop(layer, &light->element, "Nonexistent");
ufbxt_assert(prop == NULL);
}
{
ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &layer->element);
ufbxt_assert(props.count == 0);
ufbx_anim_prop *prop = ufbx_find_anim_prop(layer, &layer->element, "Weight");
ufbxt_assert(prop == NULL);
}
}
}
#endif
UFBXT_FILE_TEST(maya_transform_animation)
#if UFBXT_IMPL
{
static const ufbxt_anim_transform_ref refs[] = {
{ 1, { 0.000f, 0.000f, 0.000f }, { 0.000f, 0.000f, 0.000f }, { 1.000f, 1.000f, 1.000f } },
{ 5, { 0.226f, 0.452f, 0.677f }, { 2.258f, 4.515f, 6.773f }, { 1.023f, 1.045f, 1.068f } },
{ 14, { 1.000f, 2.000f, 3.000f }, { 10.000f, 20.000f, 30.000f }, { 1.100f, 1.200f, 1.300f } },
{ 20, { -0.296f, -0.592f, -0.888f }, { -2.960f, -5.920f, -8.880f }, { 0.970f, 0.941f, 0.911f } },
{ 24, { -1.000f, -2.000f, -3.000f }, { -10.000f, -20.000f, -30.000f }, { 0.900f, 0.800f, 0.700f } },
};
ufbx_node *node = ufbx_find_node(scene, "pCube1");
for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
const ufbxt_anim_transform_ref *ref = &refs[i];
double time = ref->frame * (1.0/24.0);
ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
ufbxt_assert(state);
ufbxt_check_scene(state);
ufbx_transform t1 = state->nodes.data[node->element.typed_id]->local_transform;
ufbx_transform t2 = ufbx_evaluate_transform(&scene->anim, node, time);
ufbx_vec3 t1_euler = ufbx_quat_to_euler(t1.rotation, UFBX_ROTATION_ORDER_XYZ);
ufbx_vec3 t2_euler = ufbx_quat_to_euler(t2.rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxt_assert_close_vec3(err, ref->translation, t1.translation);
ufbxt_assert_close_vec3(err, ref->translation, t2.translation);
ufbxt_assert_close_vec3(err, ref->rotation_euler, t1_euler);
ufbxt_assert_close_vec3(err, ref->rotation_euler, t2_euler);
ufbxt_assert_close_vec3(err, ref->scale, t1.scale);
ufbxt_assert_close_vec3(err, ref->scale, t2.scale);
ufbx_free_scene(state);
}
{
uint32_t element_id = node->element.element_id;
ufbxt_anim_transform_ref ref = refs[2];
ref.translation.x -= 0.1f;
ref.translation.y -= 0.2f;
ref.translation.z -= 0.3f;
ref.scale.x = 2.0f;
ref.scale.y = 3.0f;
ref.scale.z = 4.0f;
ufbx_prop_override overrides[] = {
{ element_id, "Color", { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 } },
{ element_id, "|NewProp", { 10, 20, 30 }, "Test", },
{ element_id, "Lcl Scaling", { 2.0f, 3.0f, 4.0f } },
{ element_id, "RotationOffset", { -0.1f, -0.2f, -0.3f } },
};
double time = 14.0/24.0;
ufbx_anim anim = scene->anim;
anim.prop_overrides = ufbx_prepare_prop_overrides(overrides, ufbxt_arraycount(overrides));
ufbx_scene *state = ufbx_evaluate_scene(scene, &anim, time, NULL, NULL);
ufbxt_assert(state);
ufbxt_check_scene(state);
ufbx_transform t1 = state->nodes.data[node->element.typed_id]->local_transform;
ufbx_transform t2 = ufbx_evaluate_transform(&anim, node, time);
ufbx_vec3 t1_euler = ufbx_quat_to_euler(t1.rotation, UFBX_ROTATION_ORDER_XYZ);
ufbx_vec3 t2_euler = ufbx_quat_to_euler(t2.rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxt_assert_close_vec3(err, ref.translation, t1.translation);
ufbxt_assert_close_vec3(err, ref.translation, t2.translation);
ufbxt_assert_close_vec3(err, ref.rotation_euler, t1_euler);
ufbxt_assert_close_vec3(err, ref.rotation_euler, t2_euler);
ufbxt_assert_close_vec3(err, ref.scale, t1.scale);
ufbxt_assert_close_vec3(err, ref.scale, t2.scale);
ufbx_free_scene(state);
}
}
#endif
UFBXT_FILE_TEST(maya_anim_layers)
#if UFBXT_IMPL
{
ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
ufbxt_assert(x && y);
ufbxt_assert(y->compose_rotation == false);
ufbxt_assert(y->compose_scale == false);
}
#endif
UFBXT_FILE_TEST(maya_anim_layers_acc)
#if UFBXT_IMPL
{
ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
ufbxt_assert(x && y);
ufbxt_assert(y->compose_rotation == true);
ufbxt_assert(y->compose_scale == true);
}
#endif
UFBXT_FILE_TEST(maya_anim_layers_over)
#if UFBXT_IMPL
{
ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
ufbxt_assert(x && y);
ufbxt_assert(y->compose_rotation == false);
ufbxt_assert(y->compose_scale == false);
}
#endif
UFBXT_FILE_TEST(maya_anim_layers_over_acc)
#if UFBXT_IMPL
{
ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
ufbxt_assert(x && y);
ufbxt_assert(y->compose_rotation == true);
ufbxt_assert(y->compose_scale == true);
}
#endif
#if UFBXT_IMPL
typedef struct {
double time;
bool visible;
} ufbxt_visibility_ref;
#endif
UFBXT_FILE_TEST(maya_cube_blinky)
#if UFBXT_IMPL
{
ufbxt_visibility_ref refs[] = {
{ 1.0, false },
{ 9.5, false },
{ 10.5, true },
{ 11.5, false },
{ 15.0, false },
{ 19.5, false },
{ 20.5, false },
{ 25.0, false },
{ 29.5, false },
{ 30.5, true },
{ 40.0, true },
{ 50.0, true },
};
for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
double time = refs[i].time / 24.0;
ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
ufbxt_assert(state);
ufbxt_check_scene(state);
ufbx_node *node = ufbx_find_node(state, "pCube1");
ufbxt_assert(node);
ufbxt_assert(node->visible == refs[i].visible);
ufbx_free_scene(state);
}
}
#endif
#if UFBXT_IMPL
typedef struct {
double time;
ufbx_real value;
} ufbxt_anim_ref;
#endif
UFBXT_FILE_TEST(maya_anim_interpolation)
#if UFBXT_IMPL
{
ufbxt_anim_ref anim_ref[] = {
// Cubic
{ 0.0 / 30.0, 0.0 },
{ 1.0 / 30.0, -0.855245 },
{ 2.0 / 30.0, -1.13344 },
{ 3.0 / 30.0, -1.17802 },
{ 4.0 / 30.0, -1.10882 },
{ 5.0 / 30.0, -0.991537 },
{ 6.0 / 30.0, -0.875223 },
{ 7.0 / 30.0, -0.808958 },
{ 8.0 / 30.0, -0.858419 },
{ 9.0 / 30.0, -1.14293 },
// Linear
{ 10.0 / 30.0, -2.0 },
// Constant previous
{ 20.0 / 30.0, -4.0 },
{ 25.0 / 30.0 - 0.001, -4.0 },
// Constant next
{ 25.0 / 30.0, -6.0 },
{ 25.0 / 30.0 + 0.001, -8.0 },
// Constant previous
{ 30.0 / 30.0, -8.0 },
{ 35.0 / 30.0 - 0.001, -8.0 },
// Linear
{ 35.0 / 30.0, -10.0 },
// Constant next
{ 40.0 / 30.0, -12.0 },
{ 40.0 / 30.0 + 0.001, -14.0 },
// Constant previous
{ 45.0 / 30.0, -14.0 },
{ 50.0 / 30.0 - 0.001, -14.0 },
// Constant next
{ 50.0 / 30.0, -16.0 },
{ 50.0 / 30.0 + 0.001, -14.0 },
// inal
{ 55.0 / 30.0, -14.0, },
};
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node);
for (size_t i = 0; i < ufbxt_arraycount(anim_ref); i++) {
ufbxt_anim_ref ref = anim_ref[i];
ufbxt_hintf("%zu: %f (frame %.2f)", i, ref.time, ref.time * 30.0f);
ufbx_prop p = ufbx_evaluate_prop(&scene->anim, &node->element, "Lcl Translation", ref.time);
ufbxt_assert_close_real(err, p.value_vec3.x, ref.value);
}
}
#endif

View File

@@ -0,0 +1,134 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "api"
#if UFBXT_IMPL
static void ufbxt_close_memory(void *user, void *data, size_t data_size)
{
free(data);
}
static bool ufbxt_open_file_memory_default(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info)
{
++*(size_t*)user;
size_t size;
void *data = ufbxt_read_file(path, &size);
if (!data) return false;
bool ok = ufbx_open_memory(stream, data, size, NULL, NULL);
free(data);
return ok;
}
static bool ufbxt_open_file_memory_temp(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info)
{
++*(size_t*)user;
size_t size;
void *data = ufbxt_read_file(path, &size);
if (!data) return false;
ufbx_open_memory_opts opts = { 0 };
opts.allocator.allocator = info->temp_allocator;
bool ok = ufbx_open_memory(stream, data, size, &opts, NULL);
free(data);
return ok;
}
static bool ufbxt_open_file_memory_ref(void *user, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_open_file_info *info)
{
++*(size_t*)user;
size_t size;
void *data = ufbxt_read_file(path, &size);
if (!data) return false;
ufbx_open_memory_opts opts = { 0 };
opts.no_copy = true;
opts.close_cb.fn = &ufbxt_close_memory;
return ufbx_open_memory(stream, data, size, &opts, NULL);
}
#endif
#if UFBXT_IMPL
static void ufbxt_do_open_memory_test(const char *filename, size_t expected_calls_fbx, size_t expected_calls_obj, ufbx_open_file_fn *open_file_fn)
{
char path[512];
ufbxt_file_iterator iter = { filename };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
for (size_t i = 0; i < 2; i++) {
ufbx_load_opts opts = { 0 };
size_t num_calls = 0;
opts.open_file_cb.fn = open_file_fn;
opts.open_file_cb.user = &num_calls;
opts.load_external_files = true;
if (i == 1) {
opts.read_buffer_size = 1;
}
ufbx_error error;
ufbx_scene *scene = ufbx_load_file(path, &opts, &error);
if (!scene) ufbxt_log_error(&error);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
if (scene->metadata.file_format == UFBX_FILE_FORMAT_FBX) {
ufbxt_assert(num_calls == expected_calls_fbx);
} else if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ) {
ufbxt_assert(num_calls == expected_calls_obj);
} else {
ufbxt_assert(false);
}
ufbx_free_scene(scene);
}
}
}
#endif
UFBXT_TEST(open_memory_default)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("maya_cache_sine", 5, 0, ufbxt_open_file_memory_default);
}
#endif
UFBXT_TEST(open_memory_temp)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("maya_cache_sine", 5, 0, ufbxt_open_file_memory_temp);
}
#endif
UFBXT_TEST(open_memory_ref)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("maya_cache_sine", 5, 0, ufbxt_open_file_memory_ref);
}
#endif
UFBXT_TEST(obj_open_memory_default)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("blender_279_ball", 1, 2, ufbxt_open_file_memory_default);
}
#endif
UFBXT_TEST(obj_open_memory_temp)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("blender_279_ball", 1, 2, ufbxt_open_file_memory_temp);
}
#endif
UFBXT_TEST(obj_open_memory_ref)
#if UFBXT_IMPL
{
ufbxt_do_open_memory_test("blender_279_ball", 1, 2, ufbxt_open_file_memory_ref);
}
#endif

View File

@@ -0,0 +1,178 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "cache"
#if UFBXT_IMPL
static void ufbxt_test_sine_cache(ufbxt_diff_error *err, const char *path, double begin, double end, double err_threshold)
{
char buf[512];
snprintf(buf, sizeof(buf), "%s%s", data_root, path);
ufbx_geometry_cache *cache = ufbx_load_geometry_cache(buf, NULL, NULL);
ufbxt_assert(cache);
ufbxt_assert(cache->channels.count == 2);
bool found_cube1 = false;
for (size_t i = 0; i < cache->channels.count; i++) {
ufbx_cache_channel *channel = &cache->channels.data[i];
ufbxt_assert(channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_POSITION);
if (!strcmp(channel->name.data, "pCubeShape1")) {
found_cube1 = true;
ufbx_vec3 pos[64];
for (double time = begin; time <= end + 0.0001; time += 0.1/24.0) {
size_t num_verts = ufbx_sample_geometry_cache_vec3(channel, time, pos, ufbxt_arraycount(pos), NULL);
ufbxt_assert(num_verts == 36);
size_t dry_verts = ufbx_sample_geometry_cache_vec3(channel, time, pos, SIZE_MAX, NULL);
ufbxt_assert(num_verts == dry_verts);
double t = (time - 1.0/24.0) / (29.0/24.0) * 4.0;
double pi2 = 3.141592653589793*2.0;
double err_scale = 0.001 / err_threshold;
for (size_t i = 0; i < num_verts; i++) {
ufbx_vec3 v = pos[i];
double sx = sin((v.y + t * 0.5f)*pi2) * 0.25;
double vx = v.x;
vx += vx > 0.0 ? -0.5 : 0.5;
ufbxt_assert_close_real(err, vx*err_scale, sx*err_scale);
}
}
}
}
ufbxt_assert(found_cube1);
ufbx_free_geometry_cache(cache);
}
#endif
UFBXT_FILE_TEST(maya_cache_sine)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->cache_deformers.count == 2);
for (size_t i = 0; i < 2; i++) {
ufbx_cache_deformer *deformer = mesh->cache_deformers.data[i];
ufbxt_assert(deformer->file);
ufbxt_assert(deformer->file->format == UFBX_CACHE_FILE_FORMAT_MC);
}
ufbxt_check_frame(scene, err, false, "maya_cache_sine_12", NULL, 12.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_cache_sine_18", NULL, 18.0/24.0);
}
#endif
UFBXT_TEST(maya_cache_sine_caches)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
ufbxt_test_sine_cache(&err, "caches/sine_mcmf_undersample/cache.xml", 1.0/24.0, 29.0/24.0, 0.04);
ufbxt_test_sine_cache(&err, "caches/sine_mcsd_oversample/cache.xml", 1.0/24.0, 29.0/24.0, 0.003);
ufbxt_test_sine_cache(&err, "caches/sine_mxmd_oversample/cache.xml", 11.0/24.0, 19.0/24.0, 0.003);
ufbxt_test_sine_cache(&err, "caches/sine_mxsf_regular/cache.xml", 1.0/24.0, 29.0/24.0, 0.008);
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_FILE_TEST(max_cache_box)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Box001");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->cache_deformers.count == 1);
ufbx_cache_deformer *deformer = mesh->cache_deformers.data[0];
ufbxt_assert(deformer->file);
ufbxt_assert(deformer->file->format == UFBX_CACHE_FILE_FORMAT_PC2);
ufbxt_assert(deformer->external_cache);
ufbxt_assert(deformer->external_channel);
ufbxt_assert(deformer->external_channel->frames.count == 11);
for (size_t i = 0; i < deformer->external_channel->frames.count; i++) {
ufbx_cache_frame *frame = &deformer->external_channel->frames.data[i];
ufbxt_assert(frame->file_format == UFBX_CACHE_FILE_FORMAT_PC2);
}
ufbxt_check_frame(scene, err, false, "max_cache_box_44", NULL, 44.0/30.0);
ufbxt_check_frame(scene, err, false, "max_cache_box_48", NULL, 48.0/30.0);
}
#endif
UFBXT_TEST(cache_xml_parse)
#if UFBXT_IMPL
{
char buf[512];
snprintf(buf, sizeof(buf), "%s%s", data_root, "caches/sine_xml_parse/cache.xml");
ufbx_geometry_cache *cache = ufbx_load_geometry_cache(buf, NULL, NULL);
ufbxt_assert(cache);
ufbxt_assert(cache->extra_info.count == 2);
ufbxt_check_string(cache->extra_info.data[0]);
ufbxt_check_string(cache->extra_info.data[1]);
const char *ex0 = "cdata! \"'&<><--&lt;&#x61;<tag></tag>-->!CDATA[]>\x61\xce\xb2\xe3\x82\xab\xf0\x9f\x98\x82]]";
ufbxt_assert(!strcmp(cache->extra_info.data[0].data, ex0));
const char *ex1 = "\"'&<>\x61\x61\x61\xce\xb2\xce\xb2\xce\xb2\xe3\x82\xab\xe3\x82\xab\xe3\x82\xab\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xf0\x9f\x98\x82";
ufbxt_assert(!strcmp(cache->extra_info.data[1].data, ex1));
ufbxt_assert(cache->channels.count == 2);
ufbxt_check_string(cache->channels.data[0].name);
ufbxt_check_string(cache->channels.data[1].name);
ufbxt_check_string(cache->channels.data[0].interpretation_name);
ufbxt_check_string(cache->channels.data[1].interpretation_name);
ufbxt_assert(cache->channels.data[0].interpretation == UFBX_CACHE_INTERPRETATION_UNKNOWN);
ufbxt_assert(cache->channels.data[1].interpretation == UFBX_CACHE_INTERPRETATION_UNKNOWN);
ufbxt_assert(!strcmp(cache->channels.data[0].interpretation_name.data, "<!--\"positions\"-->"));
ufbxt_assert(!strcmp(cache->channels.data[1].interpretation_name.data, "<![CDATA[<positions>]]>"));
ufbx_free_geometry_cache(cache);
}
#endif
UFBXT_TEST(cache_xml_depth)
#if UFBXT_IMPL
{
char buf[512];
snprintf(buf, sizeof(buf), "%s%s", data_root, "cache_xml_depth.xml");
ufbx_error error;
ufbx_geometry_cache *cache = ufbx_load_geometry_cache(buf, NULL, &error);
ufbxt_assert(!cache);
ufbxt_assert(error.type == UFBX_ERROR_UNKNOWN);
}
#endif
UFBXT_FILE_TEST_OPTS(marvelous_quad, ufbxt_scale_to_cm_opts)
#if UFBXT_IMPL
{
{
ufbx_node *node = ufbx_find_node(scene, "marvelous_quad");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 1);
ufbx_material *material = mesh->materials.data[0].material;
// What? Marvelous writes relative filenames as absolute.
// TODO: Quirk mode to fix this?
const char *prefix = "D:\\Dev\\clean\\ufbx\\data\\";
ufbxt_check_material_texture_ex(scene, material->fbx.ambient_color.texture, prefix, "marvelous_quad_diffuse_100-1.png", true);
ufbxt_check_material_texture_ex(scene, material->fbx.diffuse_color.texture, prefix, "marvelous_quad_diffuse_100-1.png", true);
ufbxt_check_material_texture_ex(scene, material->fbx.normal_map.texture, prefix, "marvelous_quad_normal_100-1.png", true);
ufbxt_check_material_texture_ex(scene, material->pbr.base_color.texture, prefix, "marvelous_quad_diffuse_100-1.png", true);
ufbxt_check_material_texture_ex(scene, material->pbr.normal_map.texture, prefix, "marvelous_quad_normal_100-1.png", true);
}
ufbxt_check_frame(scene, err, false, "marvelous_quad_12", NULL, 12.0/24.0);
ufbxt_check_frame(scene, err, false, "marvelous_quad_22", NULL, 22.0/24.0);
}
#endif

View File

@@ -0,0 +1,51 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "camera"
#if UFBXT_IMPL
static void ufbxt_check_ortho_camera(ufbxt_diff_error *err, ufbx_scene *scene, const char *name, ufbx_gate_fit gate_fit, ufbx_real extent, ufbx_real width, ufbx_real height)
{
ufbxt_hintf("Cameara %s", name);
ufbx_node *node = ufbx_find_node(scene, name);
ufbxt_assert(node && node->camera);
ufbx_camera *camera = node->camera;
ufbxt_assert(camera->projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC);
ufbxt_assert(camera->gate_fit == gate_fit);
ufbxt_assert_close_real(err, camera->orthographic_extent, extent);
ufbxt_assert_close_real(err, camera->orthographic_size.x, width);
ufbxt_assert_close_real(err, camera->orthographic_size.y, height);
}
#endif
UFBXT_FILE_TEST(maya_ortho_camera_400x200)
#if UFBXT_IMPL
{
ufbxt_check_ortho_camera(err, scene, "Fill", UFBX_GATE_FIT_FILL, 30.0f, 30.0f, 15.0f);
ufbxt_check_ortho_camera(err, scene, "Horizontal", UFBX_GATE_FIT_HORIZONTAL, 30.0f, 30.0f, 15.0f);
ufbxt_check_ortho_camera(err, scene, "Vertical", UFBX_GATE_FIT_VERTICAL, 30.0f, 60.0f, 30.0f);
ufbxt_check_ortho_camera(err, scene, "Overscan", UFBX_GATE_FIT_OVERSCAN, 30.0f, 60.0f, 30.0f);
}
#endif
UFBXT_FILE_TEST(maya_ortho_camera_200x300)
#if UFBXT_IMPL
{
ufbxt_check_ortho_camera(err, scene, "Fill", UFBX_GATE_FIT_FILL, 30.0f, 20.0f, 30.0f);
ufbxt_check_ortho_camera(err, scene, "Horizontal", UFBX_GATE_FIT_HORIZONTAL, 30.0f, 30.0f, 45.0f);
ufbxt_check_ortho_camera(err, scene, "Vertical", UFBX_GATE_FIT_VERTICAL, 30.0f, 20.0f, 30.0f);
ufbxt_check_ortho_camera(err, scene, "Overscan", UFBX_GATE_FIT_OVERSCAN, 30.0f, 30.0f, 45.0f);
}
#endif
UFBXT_FILE_TEST(maya_ortho_camera_size)
#if UFBXT_IMPL
{
ufbxt_check_ortho_camera(err, scene, "Ortho_10", UFBX_GATE_FIT_FILL, 10.0f, 10.0f, 10.0f);
ufbxt_check_ortho_camera(err, scene, "Ortho_30", UFBX_GATE_FIT_FILL, 30.0f, 30.0f, 30.0f);
ufbxt_check_ortho_camera(err, scene, "Ortho_35", UFBX_GATE_FIT_FILL, 35.0f, 35.0f, 35.0f);
ufbxt_check_ortho_camera(err, scene, "Ortho_100", UFBX_GATE_FIT_FILL, 100.0f, 100.0f, 100.0f);
}
#endif

View File

@@ -0,0 +1,192 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "collections"
UFBXT_FILE_TEST(max_selection_sets)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Box001");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "ObjectCube");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 0);
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_TopFace_Face");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 2);
for (size_t i = 0; i < sel->faces.count; i++) {
ufbx_face face = mesh->faces.data[sel->faces.data[i]];
for (size_t j = 0; j < face.num_indices; j++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + j);
pos = ufbx_transform_position(&node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 2.0f);
}
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_BottomFace_Face");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 2);
for (size_t i = 0; i < sel->faces.count; i++) {
ufbx_face face = mesh->faces.data[sel->faces.data[i]];
for (size_t j = 0; j < face.num_indices; j++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + j);
pos = ufbx_transform_position(&node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 0.0f);
}
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_SideFaces_Face");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 8);
for (size_t i = 0; i < sel->faces.count; i++) {
ufbx_face face = mesh->faces.data[sel->faces.data[i]];
ufbx_real avg = 0.0f;
for (size_t j = 0; j < face.num_indices; j++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + j);
pos = ufbx_transform_position(&node->geometry_to_world, pos);
avg += pos.y;
}
avg /= (ufbx_real)face.num_indices * 10.0f;
ufbxt_assert(avg > 0.5f && avg < 1.5f);
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_TopEdges_Edge");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 4);
ufbxt_assert(sel->faces.count == 0);
for (size_t i = 0; i < sel->edges.count; i++) {
ufbx_edge edge = mesh->edges.data[sel->edges.data[i]];
for (size_t j = 0; j < 2; j++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.indices[j]);
pos = ufbx_transform_position(&node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 2.0f);
}
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_BottomEdges_Edge");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 0);
ufbxt_assert(sel->edges.count == 4);
ufbxt_assert(sel->faces.count == 0);
ufbx_mesh *mesh = sel->target_mesh;
for (size_t i = 0; i < sel->edges.count; i++) {
ufbx_edge edge = mesh->edges.data[sel->edges.data[i]];
for (size_t j = 0; j < 2; j++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.indices[j]);
pos = ufbx_transform_position(&node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 0.0f);
}
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_TopVerts_Vertex");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 4);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 0);
for (size_t i = 0; i < sel->vertices.count; i++) {
ufbx_vec3 pos = mesh->vertices.data[sel->vertices.data[i]];
pos = ufbx_transform_position(&sel->target_node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 2.0f);
}
}
{
ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, "Box001_BottomVerts_Vertex");
ufbxt_assert(set && set->nodes.count == 1);
ufbx_selection_node *sel = set->nodes.data[0];
ufbxt_assert(sel->target_node == node && sel->target_mesh == mesh);
ufbxt_assert(!sel->include_node);
ufbxt_assert(sel->vertices.count == 4);
ufbxt_assert(sel->edges.count == 0);
ufbxt_assert(sel->faces.count == 0);
for (size_t i = 0; i < sel->vertices.count; i++) {
ufbx_vec3 pos = mesh->vertices.data[sel->vertices.data[i]];
pos = ufbx_transform_position(&sel->target_node->geometry_to_world, pos);
ufbxt_assert_close_real(err, pos.y / 10.0f, 0.0f);
}
}
}
#endif
UFBXT_FILE_TEST(maya_display_layers)
#if UFBXT_IMPL
{
ufbx_vec3 colors[] = { { 1,0,0 }, { 0,1,0 }, { 0,0,1 } };
ufbxt_assert(scene->display_layers.count == 3);
ufbxt_assert(scene->nodes.count == 5);
{
ufbx_display_layer *layer = (ufbx_display_layer*)ufbx_find_element(scene, UFBX_ELEMENT_DISPLAY_LAYER, "LayerA");
ufbxt_assert(layer && layer->nodes.count == 1 && !strcmp(layer->nodes.data[0]->name.data, "NodeA"));
ufbxt_assert(layer->visible && !layer->frozen);
ufbxt_assert_close_vec3(err, layer->ui_color, colors[0]);
}
{
ufbx_display_layer *layer = (ufbx_display_layer*)ufbx_find_element(scene, UFBX_ELEMENT_DISPLAY_LAYER, "LayerB");
ufbxt_assert(layer && layer->nodes.count == 1 && !strcmp(layer->nodes.data[0]->name.data, "NodeB"));
ufbxt_assert(layer->visible && layer->frozen);
ufbxt_assert_close_vec3(err, layer->ui_color, colors[1]);
}
{
ufbx_display_layer *layer = (ufbx_display_layer*)ufbx_find_element(scene, UFBX_ELEMENT_DISPLAY_LAYER, "LayerC");
ufbxt_assert(layer && layer->nodes.count == 1 && !strcmp(layer->nodes.data[0]->name.data, "NodeC"));
ufbxt_assert(!layer->visible && !layer->frozen);
ufbxt_assert_close_vec3(err, layer->ui_color, colors[2]);
}
}
#endif

View File

@@ -0,0 +1,151 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "constraints"
UFBXT_FILE_TEST(maya_constraint_zoo)
#if UFBXT_IMPL
{
bool found_parent = false;
bool found_aim = false;
bool found_rotation = false;
bool found_scale = false;
bool found_position = false;
bool found_ik = false;
for (size_t i = 0; i < scene->constraints.count; i++) {
ufbx_constraint *constraint = scene->constraints.data[i];
const char *name = constraint->node->name.data;
if (!strcmp(name, "joint1") && constraint->type == UFBX_CONSTRAINT_PARENT) {
found_parent = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(constraint->constrain_translation[0] && constraint->constrain_translation[1] && constraint->constrain_translation[2]);
ufbxt_assert(constraint->constrain_rotation[0] && constraint->constrain_rotation[1] && constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "Parent"));
} else if (!strcmp(name, "joint2") && constraint->type == UFBX_CONSTRAINT_AIM) {
found_aim = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(!constraint->constrain_translation[0] && !constraint->constrain_translation[1] && !constraint->constrain_translation[2]);
ufbxt_assert(constraint->constrain_rotation[0] && constraint->constrain_rotation[1] && constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert(constraint->aim_up_node);
ufbxt_assert(constraint->aim_up_type == UFBX_CONSTRAINT_AIM_UP_TO_NODE);
ufbxt_assert(!strcmp(constraint->aim_up_node->name.data, "Up"));
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "Aim"));
} else if (!strcmp(name, "joint3") && constraint->type == UFBX_CONSTRAINT_ROTATION) {
found_rotation = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(!constraint->constrain_translation[0] && !constraint->constrain_translation[1] && !constraint->constrain_translation[2]);
ufbxt_assert(constraint->constrain_rotation[0] && constraint->constrain_rotation[1] && constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "joint1"));
} else if (!strcmp(name, "joint4") && constraint->type == UFBX_CONSTRAINT_SCALE) {
found_scale = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(!constraint->constrain_translation[0] && !constraint->constrain_translation[1] && !constraint->constrain_translation[2]);
ufbxt_assert(!constraint->constrain_rotation[0] && !constraint->constrain_rotation[1] && !constraint->constrain_rotation[2]);
ufbxt_assert(constraint->constrain_scale[0] && constraint->constrain_scale[1] && constraint->constrain_scale[2]);
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "Scale"));
} else if (!strcmp(name, "joint5") && constraint->type == UFBX_CONSTRAINT_POSITION) {
found_position = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(constraint->constrain_translation[0] && constraint->constrain_translation[1] && constraint->constrain_translation[2]);
ufbxt_assert(!constraint->constrain_rotation[0] && !constraint->constrain_rotation[1] && !constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "Position"));
} else if (!strcmp(name, "joint7") && constraint->type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) {
found_ik = true;
ufbxt_assert(constraint->targets.count == 1);
ufbxt_assert(!constraint->constrain_translation[0] && !constraint->constrain_translation[1] && !constraint->constrain_translation[2]);
ufbxt_assert(constraint->constrain_rotation[0] && constraint->constrain_rotation[1] && constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert_close_real(err, constraint->targets.data[0].weight, 1.0f);
ufbxt_assert(!strcmp(constraint->targets.data[0].node->name.data, "Pole"));
ufbxt_assert(constraint->ik_effector && !strcmp(constraint->ik_effector->name.data, "IKHandle"));
ufbxt_assert(constraint->ik_end_node && !strcmp(constraint->ik_end_node->name.data, "joint10"));
}
}
ufbxt_assert(found_parent);
ufbxt_assert(found_aim);
ufbxt_assert(found_rotation);
ufbxt_assert(found_scale);
ufbxt_assert(found_position);
ufbxt_assert(found_ik);
}
#endif
UFBXT_FILE_TEST_FLAGS(maya_character, UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ)
#if UFBXT_IMPL
{
// TODO: Test things
}
#endif
UFBXT_FILE_TEST(maya_constraint_multi)
#if UFBXT_IMPL
{
ufbxt_assert(scene->constraints.count == 1);
ufbx_constraint *constraint = scene->constraints.data[0];
ufbxt_assert(constraint->type == UFBX_CONSTRAINT_POSITION);
ufbxt_assert(constraint->constrain_translation[0] && constraint->constrain_translation[1] && constraint->constrain_translation[2]);
ufbxt_assert(!constraint->constrain_rotation[0] && !constraint->constrain_rotation[1] && !constraint->constrain_rotation[2]);
ufbxt_assert(!constraint->constrain_scale[0] && !constraint->constrain_scale[1] && !constraint->constrain_scale[2]);
ufbxt_assert(constraint->targets.count == 2);
for (size_t i = 0; i < constraint->targets.count; i++) {
const ufbx_constraint_target *target = &constraint->targets.data[i];
if (!strcmp(target->node->name.data, "TargetA")) {
ufbxt_assert_close_real(err, target->weight, 1.0f);
} else if (!strcmp(target->node->name.data, "TargetB")) {
ufbxt_assert_close_real(err, target->weight, 2.0f);
} else {
ufbxt_assert(false && "Unexpected target");
}
}
}
#endif
UFBXT_FILE_TEST_FLAGS(maya_human_ik, UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ)
#if UFBXT_IMPL
{
uint32_t ik_count = 0;
uint32_t fk_count = 0;
uint32_t unknown_count = 0;
for (size_t i = 0; i < scene->markers.count; i++) {
ufbx_marker *marker = scene->markers.data[i];
if (marker->type == UFBX_MARKER_IK_EFFECTOR) {
ik_count++;
} else if (marker->type == UFBX_MARKER_FK_EFFECTOR) {
fk_count++;
} else {
unknown_count++;
}
}
ufbxt_assert(ik_count == 28);
ufbxt_assert(fk_count == 62);
ufbxt_assert(unknown_count == 0);
{
ufbx_node *node = ufbx_find_node(scene, "Character1_Ctrl_HipsEffector");
ufbxt_assert(node);
ufbxt_assert(node->attrib_type == UFBX_ELEMENT_MARKER);
ufbx_marker *marker = (ufbx_marker*)node->attrib;
ufbxt_assert(marker->type == UFBX_MARKER_IK_EFFECTOR);
}
{
ufbx_node *node = ufbx_find_node(scene, "Character1_Ctrl_RightFoot");
ufbxt_assert(node);
ufbxt_assert(node->attrib_type == UFBX_ELEMENT_MARKER);
ufbx_marker *marker = (ufbx_marker*)node->attrib;
ufbxt_assert(marker->type == UFBX_MARKER_FK_EFFECTOR);
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "curves"
UFBXT_FILE_TEST(max_curve_line)
#if UFBXT_IMPL
{
ufbx_node *polyline_node = ufbx_find_node(scene, "Polyline");
ufbx_node *bezier_node = ufbx_find_node(scene, "Bezier");
ufbxt_assert(polyline_node);
ufbxt_assert(bezier_node);
ufbxt_assert(polyline_node->attrib_type == UFBX_ELEMENT_LINE_CURVE);
ufbxt_assert(bezier_node->attrib_type == UFBX_ELEMENT_LINE_CURVE);
ufbxt_assert(polyline_node->attrib);
ufbxt_assert(bezier_node->attrib);
static const ufbx_vec3 ref_points[] = {
{ -1.0f, 0.0f, -1.0f },
{ 1.0f, 1.0f, -1.0f },
{ 1.0f, 0.0f, 1.0f },
{ 0.0f, -1.0f, 2.0f },
{ -2.0f, 0.0f, 2.0f },
{ -1.0f, 0.0f, 1.0f },
{ -1.0f, 0.0f, -1.0f },
};
ufbx_line_curve *polyline = (ufbx_line_curve*)polyline_node->attrib;
ufbxt_assert(polyline->point_indices.count == 7);
ufbxt_assert(polyline->segments.count == 1);
ufbxt_assert(polyline->segments.data[0].index_begin == 0);
ufbxt_assert(polyline->segments.data[0].num_indices == 7);
for (size_t i = 0; i < polyline->point_indices.count; i++) {
ufbx_vec3 p = polyline->control_points.data[polyline->point_indices.data[i]];
p = ufbx_transform_position(&polyline_node->geometry_to_world, p);
p.x *= 0.1f;
p.y *= 0.1f;
p.z *= 0.1f;
ufbxt_assert_close_vec3(err, p, ref_points[i]);
}
ufbx_vec3 polyline_color = { 0.223529411764706f ,0.0313725490196078f, 0.533333333333333f };
ufbxt_assert_close_vec3(err, polyline->color, polyline_color);
ufbx_line_curve *bezier = (ufbx_line_curve*)bezier_node->attrib;
ufbxt_assert(bezier->point_indices.count == 15);
ufbxt_assert(bezier->segments.count == 1);
ufbxt_assert(bezier->segments.data[0].index_begin == 0);
ufbxt_assert(bezier->segments.data[0].num_indices == 15);
for (size_t i = 0; i < bezier->point_indices.count; i++) {
ufbx_vec3 p = bezier->control_points.data[bezier->point_indices.data[i]];
p = ufbx_transform_position(&bezier_node->geometry_to_world, p);
p.x *= 0.1f;
p.y *= 0.1f;
p.z *= 0.1f;
ufbxt_assert(p.y >= -0.0001f);
ufbxt_assert_close_real(err, p.z, 0.0f);
}
ufbx_vec3 bezier_color = { 0.603921568627451f ,0.725490196078431f, 0.898039215686275f };
ufbxt_assert_close_vec3(err, bezier->color, bezier_color);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,291 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "fuzz"
UFBXT_TEST(fuzz_files)
#if UFBXT_IMPL
{
size_t ok = 0;
size_t i = g_fuzz_step < SIZE_MAX ? g_fuzz_step : 0;
for (; i < 10000; i++) {
if (g_fuzz_file != SIZE_MAX && i != g_fuzz_file) continue;
char name[512];
char buf[512];
snprintf(name, sizeof(name), "fuzz_%04zu", i);
snprintf(buf, sizeof(buf), "%sfuzz/fuzz_%04zu.fbx", data_root, i);
size_t size;
void *data = ufbxt_read_file(buf, &size);
if (!data) break;
ufbx_error error;
ufbx_load_opts load_opts = { 0 };
load_opts.temp_allocator.memory_limit = 0x4000000; // 64MB
load_opts.result_allocator.memory_limit = 0x4000000; // 64MB
load_opts.file_format = UFBX_FILE_FORMAT_FBX;
ufbx_scene *scene = ufbx_load_memory(data, size, &load_opts, &error);
if (scene) {
ufbxt_check_scene(scene);
ok++;
}
bool allow_error = scene == NULL;
ufbx_free_scene(scene);
ufbxt_progress_ctx stream_progress_ctx = { 0 };
bool temp_freed = false, result_freed = false;
ufbx_load_opts stream_opts = load_opts;
ufbxt_init_allocator(&stream_opts.temp_allocator, &temp_freed);
ufbxt_init_allocator(&stream_opts.result_allocator, &result_freed);
stream_opts.read_buffer_size = 1;
stream_opts.temp_allocator.huge_threshold = 1;
stream_opts.result_allocator.huge_threshold = 1;
stream_opts.progress_cb.fn = &ufbxt_measure_progress;
stream_opts.progress_cb.user = &stream_progress_ctx;
stream_opts.progress_interval_hint = 1;
ufbx_scene *streamed_scene = ufbx_load_file(buf, &stream_opts, &error);
if (streamed_scene) {
ufbxt_check_scene(streamed_scene);
ufbxt_assert(scene);
} else {
ufbxt_assert(!scene);
}
ufbx_free_scene(streamed_scene);
ufbxt_assert(temp_freed);
ufbxt_assert(result_freed);
int prev_fuzz_quality = g_fuzz_quality;
g_fuzz_quality = g_heavy_fuzz_quality;
ufbxt_do_fuzz(name, data, size, buf, allow_error, UFBX_FILE_FORMAT_FBX, NULL);
g_fuzz_quality = prev_fuzz_quality;
free(data);
}
ufbxt_logf(".. Loaded fuzz files: %zu (%zu non-errors)", i, ok);
}
#endif
UFBXT_TEST(fuzz_cache_xml)
#if UFBXT_IMPL
{
size_t ok = 0;
size_t i = g_fuzz_step < SIZE_MAX ? g_fuzz_step : 0;
for (; i < 10000; i++) {
if (g_fuzz_file != SIZE_MAX && i != g_fuzz_file) continue;
char buf[512];
snprintf(buf, sizeof(buf), "%scache_fuzz/xml/fuzz_%04zu.xml", data_root, i);
ufbx_geometry_cache_opts cache_opts = { 0 };
cache_opts.temp_allocator.memory_limit = 0x4000000; // 64MB
cache_opts.result_allocator.memory_limit = 0x4000000; // 64MB
if (g_dedicated_allocs) {
cache_opts.temp_allocator.huge_threshold = 1;
cache_opts.result_allocator.huge_threshold = 1;
}
size_t size;
void *data = ufbxt_read_file(buf, &size);
if (!data) break;
// TODO: Read memory?
ufbx_error error;
ufbx_geometry_cache *cache = ufbx_load_geometry_cache(buf, &cache_opts, &error);
if (cache) {
ufbxt_check_string(cache->root_filename);
ok++;
}
ufbx_free_geometry_cache(cache);
free(data);
}
ufbxt_logf(".. Loaded fuzz files: %zu (%zu non-errors)", i, ok);
}
#endif
UFBXT_TEST(fuzz_cache_mcx)
#if UFBXT_IMPL
{
size_t ok = 0;
size_t i = g_fuzz_step < SIZE_MAX ? g_fuzz_step : 0;
for (; i < 10000; i++) {
if (g_fuzz_file != SIZE_MAX && i != g_fuzz_file) continue;
char buf[512];
snprintf(buf, sizeof(buf), "%scache_fuzz/mcx/fuzz_%04zu.mcx", data_root, i);
ufbx_geometry_cache_opts cache_opts = { 0 };
cache_opts.temp_allocator.memory_limit = 0x4000000; // 64MB
cache_opts.result_allocator.memory_limit = 0x4000000; // 64MB
if (g_dedicated_allocs) {
cache_opts.temp_allocator.huge_threshold = 1;
cache_opts.result_allocator.huge_threshold = 1;
}
size_t size;
void *data = ufbxt_read_file(buf, &size);
if (!data) break;
// TODO: Read memory?
ufbx_error error;
ufbx_geometry_cache *cache = ufbx_load_geometry_cache(buf, &cache_opts, &error);
if (cache) {
ufbxt_check_string(cache->root_filename);
ok++;
}
ufbx_free_geometry_cache(cache);
free(data);
}
ufbxt_logf(".. Loaded fuzz files: %zu (%zu non-errors)", i, ok);
}
#endif
UFBXT_TEST(fuzz_obj_files)
#if UFBXT_IMPL
{
size_t ok = 0;
size_t i = g_fuzz_step < SIZE_MAX ? g_fuzz_step : 0;
for (; i < 10000; i++) {
if (g_fuzz_file != SIZE_MAX && i != g_fuzz_file) continue;
char name[512];
char buf[512];
snprintf(name, sizeof(name), "obj_fuzz_%04zu", i);
snprintf(buf, sizeof(buf), "%sobj_fuzz/fuzz_%04zu.obj", data_root, i);
size_t size;
void *data = ufbxt_read_file(buf, &size);
if (!data) break;
ufbx_error error;
ufbx_load_opts load_opts = { 0 };
load_opts.temp_allocator.memory_limit = 0x4000000; // 64MB
load_opts.result_allocator.memory_limit = 0x4000000; // 64MB
load_opts.file_format = UFBX_FILE_FORMAT_OBJ;
ufbx_scene *scene = ufbx_load_memory(data, size, &load_opts, &error);
if (scene) {
ufbxt_check_scene(scene);
ok++;
}
bool allow_error = scene == NULL;
ufbx_free_scene(scene);
ufbxt_progress_ctx stream_progress_ctx = { 0 };
bool temp_freed = false, result_freed = false;
ufbx_load_opts stream_opts = load_opts;
ufbxt_init_allocator(&stream_opts.temp_allocator, &temp_freed);
ufbxt_init_allocator(&stream_opts.result_allocator, &result_freed);
stream_opts.read_buffer_size = 1;
stream_opts.temp_allocator.huge_threshold = 1;
stream_opts.result_allocator.huge_threshold = 1;
stream_opts.progress_cb.fn = &ufbxt_measure_progress;
stream_opts.progress_cb.user = &stream_progress_ctx;
stream_opts.progress_interval_hint = 1;
ufbx_scene *streamed_scene = ufbx_load_file(buf, &stream_opts, &error);
if (streamed_scene) {
ufbxt_check_scene(streamed_scene);
ufbxt_assert(scene);
} else {
ufbxt_assert(!scene);
}
ufbx_free_scene(streamed_scene);
ufbxt_assert(temp_freed);
ufbxt_assert(result_freed);
ufbxt_do_fuzz(name, data, size, buf, allow_error, UFBX_FILE_FORMAT_OBJ, NULL);
free(data);
}
ufbxt_logf(".. Loaded fuzz files: %zu (%zu non-errors)", i, ok);
}
#endif
UFBXT_TEST(fuzz_mtl_files)
#if UFBXT_IMPL
{
size_t ok = 0;
size_t i = g_fuzz_step < SIZE_MAX ? g_fuzz_step : 0;
for (; i < 10000; i++) {
if (g_fuzz_file != SIZE_MAX && i != g_fuzz_file) continue;
char name[512];
char buf[512];
snprintf(name, sizeof(name), "mtl_fuzz_%04zu", i);
snprintf(buf, sizeof(buf), "%smtl_fuzz/fuzz_%04zu.mtl", data_root, i);
size_t size;
void *data = ufbxt_read_file(buf, &size);
if (!data) break;
ufbx_error error;
ufbx_load_opts load_opts = { 0 };
load_opts.temp_allocator.memory_limit = 0x4000000; // 64MB
load_opts.result_allocator.memory_limit = 0x4000000; // 64MB
load_opts.file_format = UFBX_FILE_FORMAT_MTL;
ufbx_scene *scene = ufbx_load_memory(data, size, &load_opts, &error);
if (scene) {
ufbxt_check_scene(scene);
ok++;
}
bool allow_error = scene == NULL;
ufbx_free_scene(scene);
ufbxt_progress_ctx stream_progress_ctx = { 0 };
bool temp_freed = false, result_freed = false;
ufbx_load_opts stream_opts = load_opts;
ufbxt_init_allocator(&stream_opts.temp_allocator, &temp_freed);
ufbxt_init_allocator(&stream_opts.result_allocator, &result_freed);
stream_opts.read_buffer_size = 1;
stream_opts.temp_allocator.huge_threshold = 1;
stream_opts.result_allocator.huge_threshold = 1;
stream_opts.progress_cb.fn = &ufbxt_measure_progress;
stream_opts.progress_cb.user = &stream_progress_ctx;
stream_opts.progress_interval_hint = 1;
ufbx_scene *streamed_scene = ufbx_load_file(buf, &stream_opts, &error);
if (streamed_scene) {
ufbxt_check_scene(streamed_scene);
ufbxt_assert(scene);
} else {
ufbxt_assert(!scene);
}
ufbx_free_scene(streamed_scene);
ufbxt_assert(temp_freed);
ufbxt_assert(result_freed);
ufbxt_do_fuzz(name, data, size, buf, allow_error, UFBX_FILE_FORMAT_MTL, NULL);
free(data);
}
ufbxt_logf(".. Loaded fuzz files: %zu (%zu non-errors)", i, ok);
}
#endif

View File

@@ -0,0 +1,253 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "legacy"
#if UFBXT_IMPL
static void ufbxt_diff_material_value(ufbxt_diff_error *err, const ufbx_material_map *color, const ufbx_material_map *factor, ufbx_vec3 value)
{
ufbxt_assert_close_real(err, color->value_vec3.x * factor->value_vec3.x, value.x);
ufbxt_assert_close_real(err, color->value_vec3.y * factor->value_vec3.x, value.y);
ufbxt_assert_close_real(err, color->value_vec3.z * factor->value_vec3.x, value.z);
}
#endif
UFBXT_FILE_TEST(max7_cube)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(max7_cube_normals)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(max2009_blob)
#if UFBXT_IMPL
{
ufbxt_assert(scene->lights.count == 3);
ufbxt_assert(scene->cameras.count == 1);
{
ufbx_node *node = ufbx_find_node(scene, "Box01");
ufbxt_assert(node);
ufbxt_assert(node->mesh);
ufbxt_assert(node->children.count == 16);
ufbx_mesh *mesh = node->mesh;
size_t num_top = 0;
size_t num_left = 0;
size_t num_right = 0;
size_t num_front = 0;
for (size_t fi = 0; fi < mesh->num_faces; fi++) {
ufbx_face face = mesh->faces.data[fi];
ufbx_vec3 center = ufbx_zero_vec3;
for (size_t i = 0; i < face.num_indices; i++) {
ufbx_vec3 v = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i);
center.x += v.x;
center.y += v.y;
center.z += v.z;
}
center.x /= (ufbx_real)face.num_indices;
center.y /= (ufbx_real)face.num_indices;
center.z /= (ufbx_real)face.num_indices;
if (center.z >= 14.0f) {
ufbx_mesh_material *mat = &mesh->materials.data[mesh->face_material.data[fi]];
ufbxt_assert(!strcmp(mat->material->name.data, "Top"));
num_top++;
}
if (center.y <= -10.0f) {
ufbx_mesh_material *mat = &mesh->materials.data[mesh->face_material.data[fi]];
ufbxt_assert(!strcmp(mat->material->name.data, "Right"));
num_right++;
}
if (center.y >= 10.0f) {
ufbx_mesh_material *mat = &mesh->materials.data[mesh->face_material.data[fi]];
ufbxt_assert(!strcmp(mat->material->name.data, "Left"));
num_left++;
}
if (center.x >= 9.0f) {
ufbx_mesh_material *mat = &mesh->materials.data[mesh->face_material.data[fi]];
ufbxt_assert(!strcmp(mat->material->name.data, "Front"));
num_front++;
}
}
ufbxt_assert(num_top >= 80);
ufbxt_assert(num_left >= 80);
ufbxt_assert(num_right >= 80);
ufbxt_assert(num_front >= 80);
}
{
ufbx_node *node = ufbx_find_node(scene, "Omni01");
ufbxt_assert(node && node->light);
ufbx_light *light = node->light;
ufbxt_assert(light->type == UFBX_LIGHT_POINT);
if (scene->metadata.version < 6000) {
ufbxt_assert(light->decay == UFBX_LIGHT_DECAY_QUADRATIC);
}
ufbx_vec3 color = { 0.172549024224281f, 0.364705890417099f, 1.0f };
ufbxt_assert_close_vec3(err, light->color, color);
ufbxt_assert_close_real(err, light->intensity, 1.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "Fspot01");
ufbxt_assert(node && node->light);
ufbx_light *light = node->light;
ufbxt_assert(light->type == UFBX_LIGHT_SPOT);
if (scene->metadata.version < 6000) {
ufbxt_assert(light->decay == UFBX_LIGHT_DECAY_QUADRATIC);
}
ufbx_vec3 color = { 0.972549080848694f ,0.0705882385373116f, 0.0705882385373116f };
ufbxt_assert_close_vec3(err, light->color, color);
ufbxt_assert_close_real(err, light->intensity, 1.0f);
ufbxt_assert_close_real(err, light->outer_angle, 45.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "FDirect02");
ufbxt_assert(node && node->light);
ufbx_light *light = node->light;
ufbxt_assert(light->type == UFBX_LIGHT_DIRECTIONAL);
if (scene->metadata.version < 6000) {
ufbxt_assert(light->decay == UFBX_LIGHT_DECAY_NONE);
}
ufbx_vec3 color = { 0.533333361148834f ,0.858823597431183f, 0.647058844566345f };
ufbxt_assert_close_vec3(err, light->color, color);
ufbxt_assert_close_real(err, light->intensity, 1.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "Camera01");
ufbxt_assert(node && node->camera);
ufbx_camera *camera = node->camera;
ufbxt_assert(camera->aspect_mode == UFBX_ASPECT_MODE_WINDOW_SIZE);
ufbxt_assert(camera->aperture_mode == UFBX_APERTURE_MODE_HORIZONTAL);
ufbx_vec2 aperture = { 1.41732287406921f ,1.06299209594727f };
ufbxt_assert_close_real(err, camera->focal_length_mm, 43.4558439883016f);
ufbxt_assert_close_vec2(err, camera->film_size_inch, aperture);
ufbxt_assert_close_vec2(err, camera->aperture_size_inch, aperture);
}
{
ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Left");
ufbxt_assert(material);
ufbx_vec3 ambient = { 0.588235318660736f, 0.588235318660736f, 0.588235318660736f };
ufbx_vec3 diffuse = { 0.588235318660736f, 0.588235318660736f, 0.588235318660736f };
ufbx_vec3 specular = { 0.179999984502793f, 0.179999984502793f, 0.179999984502793f };
ufbx_vec3 emission = { 0.823529481887817f, 0.0f, 0.0f };
ufbx_real shininess = 1.99999991737042f;
ufbxt_diff_material_value(err, &material->fbx.ambient_color, &material->fbx.ambient_factor, ambient);
ufbxt_diff_material_value(err, &material->fbx.diffuse_color, &material->fbx.diffuse_factor, diffuse);
ufbxt_diff_material_value(err, &material->fbx.specular_color, &material->fbx.specular_factor, specular);
ufbxt_diff_material_value(err, &material->fbx.emission_color, &material->fbx.emission_factor, emission);
ufbxt_assert_close_real(err, material->fbx.specular_exponent.value_vec3.x, shininess);
}
{
ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Front");
ufbxt_assert(material);
ufbx_vec3 ambient = { 0.588235318660736f, 0.921568691730499f, 0.925490260124207f };
ufbx_vec3 diffuse = { 0.588235318660736f, 0.921568691730499f, 0.925490260124207f };
ufbx_vec3 specular = { 0.0f, 0.0f, 0.0f };
ufbx_vec3 emission = { 0.0f, 0.0f, 0.0f };
ufbx_real shininess = 1.99999991737042f;
ufbxt_diff_material_value(err, &material->fbx.ambient_color, &material->fbx.ambient_factor, ambient);
ufbxt_diff_material_value(err, &material->fbx.diffuse_color, &material->fbx.diffuse_factor, diffuse);
ufbxt_diff_material_value(err, &material->fbx.specular_color, &material->fbx.specular_factor, specular);
ufbxt_diff_material_value(err, &material->fbx.emission_color, &material->fbx.emission_factor, emission);
ufbxt_assert_close_real(err, material->fbx.specular_exponent.value_vec3.x, shininess);
}
{
ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Right");
ufbxt_assert(material);
ufbx_vec3 ambient = { 0.588235318660736f, 0.588235318660736f, 0.588235318660736f };
ufbx_vec3 diffuse = { 0.588235318660736f, 0.588235318660736f, 0.588235318660736f };
ufbx_vec3 specular = { 0.0f, 0.0f, 0.0f };
ufbx_vec3 emission = { 0.0f, 0.803921639919281f, 0.0f };
ufbx_real shininess = 7.99999900844507f;
ufbxt_diff_material_value(err, &material->fbx.ambient_color, &material->fbx.ambient_factor, ambient);
ufbxt_diff_material_value(err, &material->fbx.diffuse_color, &material->fbx.diffuse_factor, diffuse);
ufbxt_diff_material_value(err, &material->fbx.specular_color, &material->fbx.specular_factor, specular);
ufbxt_diff_material_value(err, &material->fbx.emission_color, &material->fbx.emission_factor, emission);
ufbxt_assert_close_real(err, material->fbx.specular_exponent.value_vec3.x, shininess);
}
{
ufbx_material *material = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Top");
ufbxt_assert(material);
ufbx_vec3 ambient = { 0.564705908298492f, 0.603921592235565f, 0.890196144580841f };
ufbx_vec3 diffuse = { 0.564705908298492f, 0.603921592235565f, 0.890196144580841f };
ufbx_vec3 specular = { 0.0f, 0.0f, 0.0f };
ufbx_vec3 emission = { 0.0f, 0.0f, 0.0f };
ufbx_real shininess = 1.99999991737042f;
ufbxt_diff_material_value(err, &material->fbx.ambient_color, &material->fbx.ambient_factor, ambient);
ufbxt_diff_material_value(err, &material->fbx.diffuse_color, &material->fbx.diffuse_factor, diffuse);
ufbxt_diff_material_value(err, &material->fbx.specular_color, &material->fbx.specular_factor, specular);
ufbxt_diff_material_value(err, &material->fbx.emission_color, &material->fbx.emission_factor, emission);
ufbxt_assert_close_real(err, material->fbx.specular_exponent.value_vec3.x, shininess);
}
ufbxt_check_frame(scene, err, false, "max2009_blob_8", NULL, 8.0/30.0);
ufbxt_check_frame(scene, err, false, "max2009_blob_18", NULL, 18.0/30.0);
}
#endif
UFBXT_FILE_TEST(max7_skin)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "max7_skin_5", NULL, 5.0/30.0);
ufbxt_check_frame(scene, err, false, "max7_skin_15", NULL, 15.0/30.0);
}
#endif
UFBXT_FILE_TEST(max7_blend_cube)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Box01");
ufbxt_assert(node);
ufbxt_assert(node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->blend_deformers.count == 1);
ufbx_blend_deformer *blend = mesh->blend_deformers.data[0];
ufbxt_assert(blend->channels.count == 2);
ufbxt_check_frame(scene, err, false, "max7_blend_cube_8", NULL, 8.0/30.0);
ufbxt_check_frame(scene, err, false, "max7_blend_cube_24", NULL, 24.0/30.0);
}
#endif
UFBXT_FILE_TEST(max6_teapot)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Teapot01");
ufbxt_assert(node);
ufbxt_assert(node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_uv.exists);
}
#endif
UFBXT_FILE_TEST(synthetic_legacy_nonzero_material)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Box01");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->faces.count == 1);
ufbxt_assert(mesh->num_indices == 3);
ufbxt_assert(mesh->materials.count == 2);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "Right"));
ufbxt_assert(mesh->materials.data[0].num_faces == 0);
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "Left"));
ufbxt_assert(mesh->materials.data[1].num_faces == 1);
ufbxt_assert(mesh->face_material.count == 1);
ufbxt_assert(mesh->face_material.data[0] == 1);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,319 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "math"
#if UFBXT_IMPL
static ufbx_real ufbxt_quat_error(ufbx_quat a, ufbx_quat b)
{
double pos = fabs(a.x-b.x) + fabs(a.y-b.y) + fabs(a.z-b.z) + fabs(a.w-b.w);
double neg = fabs(a.x+b.x) + fabs(a.y+b.y) + fabs(a.z+b.z) + fabs(a.w+b.w);
return pos < neg ? pos : neg;
}
static uint32_t ufbxt_xorshift32(uint32_t *state)
{
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
uint32_t x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return *state = x;
}
static ufbx_real ufbxt_xorshift32_real(uint32_t *state)
{
uint32_t u = ufbxt_xorshift32(state);
return (ufbx_real)u * (ufbx_real)2.3283064365386962890625e-10;
}
#endif
UFBXT_TEST(quat_to_euler_structured)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
for (int iorder = 0; iorder < 6; iorder++) {
ufbx_rotation_order order = (ufbx_rotation_order)iorder;
for (int x = -360; x <= 360; x += 45)
for (int y = -360; y <= 360; y += 45)
for (int z = -360; z <= 360; z += 45) {
ufbx_vec3 v = { (ufbx_real)x, (ufbx_real)y, (ufbx_real)z };
ufbx_quat q = ufbx_euler_to_quat(v, order);
ufbx_vec3 v2 = ufbx_quat_to_euler(q, order);
ufbx_quat q2 = ufbx_euler_to_quat(v2, order);
ufbxt_assert_close_real(&err, ufbxt_quat_error(q, q2), 0.0f);
ufbxt_assert_close_real(&err, ufbxt_quat_error(q, q2), 0.0f);
}
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(quat_to_euler_random)
#if UFBXT_IMPL
{
size_t steps = ufbxt_begin_fuzz() ? 10000000 : 100000;
ufbxt_diff_error err = { 0 };
for (int iorder = 0; iorder < 6; iorder++) {
ufbx_rotation_order order = (ufbx_rotation_order)iorder;
uint32_t state = 1;
for (size_t i = 0; i < steps; i++) {
if (g_fuzz && ufbxt_fuzz_should_skip((int)i >> 8)) continue;
ufbx_quat q;
q.x = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.y = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.z = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.w = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
ufbx_real qm = (ufbx_real)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
q.x /= qm;
q.y /= qm;
q.z /= qm;
q.w /= qm;
ufbx_vec3 v = ufbx_quat_to_euler(q, order);
ufbx_quat q2 = ufbx_euler_to_quat(v, order);
ufbxt_assert_close_real(&err, ufbxt_quat_error(q, q2), 0.0f);
}
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(matrix_to_transform_structured)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
for (int sx = -2; sx <= 2; sx++)
for (int sy = -2; sy <= 2; sy++)
for (int sz = -2; sz <= 2; sz++)
for (int rx = -2; rx <= 2; rx++)
for (int ry = -2; ry <= 2; ry++)
for (int rz = -2; rz <= 2; rz++)
for (int rw = -2; rw <= 2; rw++)
{
// TODO: Support single axis squish?
if (sx == 0 || sy == 0 || sz == 0) continue;
if (rx == 0 && ry == 0 && rz == 0 && rw == 0) continue;
ufbx_transform t;
ufbx_quat q = { (ufbx_real)rx / 3.0f, (ufbx_real)ry / 3.0f, (ufbx_real)rz / 3.0f, (ufbx_real)rw / 3.0f };
ufbx_real qm = (ufbx_real)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
t.rotation.x = q.x / qm;
t.rotation.y = q.y / qm;
t.rotation.z = q.z / qm;
t.rotation.w = q.w / qm;
t.translation.x = 1.0f;
t.translation.y = 2.0f;
t.translation.z = 3.0f;
t.scale.x = (ufbx_real)sx / 2.0f;
t.scale.y = (ufbx_real)sy / 2.0f;
t.scale.z = (ufbx_real)sz / 2.0f;
ufbx_matrix m = ufbx_transform_to_matrix(&t);
ufbx_transform t2 = ufbx_matrix_to_transform(&m);
if (sx < 0 || sy < 0 || sz < 0) {
// Flipped signs cannot be uniquely recovered, check that the transforms are identical
ufbx_matrix m2 = ufbx_transform_to_matrix(&t2);
ufbxt_assert_close_matrix(&err, m, m2);
} else {
ufbxt_assert_close_vec3(&err, t.translation, t2.translation);
ufbxt_assert_close_vec3(&err, t.scale, t2.scale);
ufbxt_assert_close_real(&err, ufbxt_quat_error(t.rotation, t2.rotation), 0.0f);
}
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(matrix_to_transform_random)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
uint32_t state = 1;
size_t steps = ufbxt_begin_fuzz() ? 1000000 : 100000;
for (size_t i = 0; i < steps; i++) {
if (g_fuzz && ufbxt_fuzz_should_skip((int)i >> 4)) continue;
ufbx_transform t;
ufbx_quat q;
q.x = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.y = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.z = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.w = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
ufbx_real qm = (ufbx_real)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
t.rotation.x = q.x / qm;
t.rotation.y = q.y / qm;
t.rotation.z = q.z / qm;
t.rotation.w = q.w / qm;
t.translation.x = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.translation.y = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.translation.z = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.scale.x = ufbxt_xorshift32_real(&state) * 10.0f + 0.01f;
t.scale.y = ufbxt_xorshift32_real(&state) * 10.0f + 0.01f;
t.scale.z = ufbxt_xorshift32_real(&state) * 10.0f + 0.01f;
uint32_t flip = ufbxt_xorshift32(&state);
// Prevent most of the inputs being flips
if (flip & 8) flip = 0;
if (flip & 1) t.scale.x *= -1.0f;
if (flip & 2) t.scale.y *= -1.0f;
if (flip & 4) t.scale.z *= -1.0f;
ufbx_matrix m = ufbx_transform_to_matrix(&t);
ufbx_transform t2 = ufbx_matrix_to_transform(&m);
if (flip) {
// Flipped signs cannot be uniquely recovered, check that the transforms are identical
ufbx_matrix m2 = ufbx_transform_to_matrix(&t2);
ufbxt_assert_close_matrix(&err, m, m2);
} else {
ufbxt_assert_close_vec3(&err, t.translation, t2.translation);
ufbxt_assert_close_vec3(&err, t.scale, t2.scale);
ufbxt_assert_close_real(&err, ufbxt_quat_error(t.rotation, t2.rotation), 0.0f);
}
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(matrix_inverse_simple)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
{
ufbx_matrix m = { 0 };
m.m00 = 2.0f;
m.m11 = 0.5f;
m.m22 = 0.25f;
m.m03 = 1.0f;
m.m13 = 2.0f;
m.m23 = 3.0f;
ufbx_matrix im = ufbx_matrix_invert(&m);
ufbxt_assert_close_real(&err, im.m00, 0.5f);
ufbxt_assert_close_real(&err, im.m10, 0.0f);
ufbxt_assert_close_real(&err, im.m20, 0.0f);
ufbxt_assert_close_real(&err, im.m01, 0.0f);
ufbxt_assert_close_real(&err, im.m11, 2.0f);
ufbxt_assert_close_real(&err, im.m21, 0.0f);
ufbxt_assert_close_real(&err, im.m02, 0.0f);
ufbxt_assert_close_real(&err, im.m12, 0.0f);
ufbxt_assert_close_real(&err, im.m22, 4.0f);
ufbxt_assert_close_real(&err, im.m03, -0.5f);
ufbxt_assert_close_real(&err, im.m13, -4.0f);
ufbxt_assert_close_real(&err, im.m23, -12.0f);
}
{
ufbx_matrix m = { 0 };
m.m00 = 1.0f;
m.m12 = -1.0f;
m.m21 = 1.0f;
m.m13 = 1.0f;
m.m23 = 2.0f;
ufbx_matrix im = ufbx_matrix_invert(&m);
ufbxt_assert_close_real(&err, im.m00, 1.0f);
ufbxt_assert_close_real(&err, im.m10, 0.0f);
ufbxt_assert_close_real(&err, im.m20, 0.0f);
ufbxt_assert_close_real(&err, im.m01, 0.0f);
ufbxt_assert_close_real(&err, im.m11, 0.0f);
ufbxt_assert_close_real(&err, im.m21, -1.0f);
ufbxt_assert_close_real(&err, im.m02, 0.0f);
ufbxt_assert_close_real(&err, im.m12, 1.0f);
ufbxt_assert_close_real(&err, im.m22, 0.0f);
ufbxt_assert_close_real(&err, im.m03, 0.0f);
ufbxt_assert_close_real(&err, im.m13, -2.0f);
ufbxt_assert_close_real(&err, im.m23, 1.0f);
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(matrix_inverse_random)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
size_t steps = ufbxt_begin_fuzz() ? 1000000 : 10000;
uint32_t state = 1;
for (size_t i = 0; i < steps; i++) {
if (g_fuzz && ufbxt_fuzz_should_skip((int)i >> 4)) continue;
ufbx_transform t;
ufbx_quat q;
q.x = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.y = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.z = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
q.w = ufbxt_xorshift32_real(&state) * 2.0f - 1.0f;
ufbx_real qm = (ufbx_real)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
t.rotation.x = q.x / qm;
t.rotation.y = q.y / qm;
t.rotation.z = q.z / qm;
t.rotation.w = q.w / qm;
t.translation.x = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.translation.y = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.translation.z = ufbxt_xorshift32_real(&state) * 20.0f - 10.0f;
t.scale.x = ufbxt_xorshift32_real(&state) * 10.0f + 0.1f;
t.scale.y = ufbxt_xorshift32_real(&state) * 10.0f + 0.1f;
t.scale.z = ufbxt_xorshift32_real(&state) * 10.0f + 0.1f;
uint32_t flip = ufbxt_xorshift32(&state);
// Prevent most of the inputs being flips
if (flip & 8) flip = 0;
if (flip & 1) t.scale.x *= -1.0f;
if (flip & 2) t.scale.y *= -1.0f;
if (flip & 4) t.scale.z *= -1.0f;
ufbx_matrix m = ufbx_transform_to_matrix(&t);
ufbx_matrix im = ufbx_matrix_invert(&m);
ufbx_matrix identity = ufbx_matrix_mul(&m, &im);
ufbxt_assert_close_real(&err, identity.m00, 1.0f);
ufbxt_assert_close_real(&err, identity.m10, 0.0f);
ufbxt_assert_close_real(&err, identity.m20, 0.0f);
ufbxt_assert_close_real(&err, identity.m01, 0.0f);
ufbxt_assert_close_real(&err, identity.m11, 1.0f);
ufbxt_assert_close_real(&err, identity.m21, 0.0f);
ufbxt_assert_close_real(&err, identity.m02, 0.0f);
ufbxt_assert_close_real(&err, identity.m12, 0.0f);
ufbxt_assert_close_real(&err, identity.m22, 1.0f);
ufbxt_assert_close_real(&err, identity.m03, 0.0f);
ufbxt_assert_close_real(&err, identity.m13, 0.0f);
ufbxt_assert_close_real(&err, identity.m23, 0.0f);
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,637 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "nurbs"
#if UFBXT_IMPL
typedef struct {
ufbx_real u;
ufbx_vec3 position;
ufbx_vec3 derivative;
} ufbxt_curve_sample;
typedef struct {
ufbx_real u, v;
ufbx_vec3 position;
ufbx_vec3 derivative_u;
ufbx_vec3 derivative_v;
} ufbxt_surface_sample;
#endif
UFBXT_FILE_TEST(maya_nurbs_curve_form)
#if UFBXT_IMPL
{
ufbx_node *node_open = ufbx_find_node(scene, "circleOpen");
ufbx_node *node_closed = ufbx_find_node(scene, "circleClosed");
ufbx_node *node_periodic = ufbx_find_node(scene, "circlePeriodic");
ufbxt_assert(node_open && node_open->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbxt_assert(node_closed && node_closed->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbxt_assert(node_periodic && node_periodic->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbx_nurbs_curve *open = (ufbx_nurbs_curve*)node_open->attrib;
ufbx_nurbs_curve *closed = (ufbx_nurbs_curve*)node_closed->attrib;
ufbx_nurbs_curve *periodic = (ufbx_nurbs_curve*)node_periodic->attrib;
ufbxt_assert(open->basis.valid);
ufbxt_assert(closed->basis.valid);
ufbxt_assert(periodic->basis.valid);
ufbxt_assert(open->basis.topology == UFBX_NURBS_TOPOLOGY_OPEN);
ufbxt_assert(closed->basis.topology == UFBX_NURBS_TOPOLOGY_CLOSED);
ufbxt_assert(periodic->basis.topology == UFBX_NURBS_TOPOLOGY_PERIODIC);
ufbxt_assert(open->basis.order == 4);
ufbxt_assert(closed->basis.order == 4);
ufbxt_assert(periodic->basis.order == 4);
ufbxt_assert(!open->basis.is_2d);
ufbxt_assert(!closed->basis.is_2d);
ufbxt_assert(!periodic->basis.is_2d);
{
ufbxt_assert_close_real(err, open->basis.t_min, 0.0f);
ufbxt_assert_close_real(err, open->basis.t_max, 1.0f);
ufbxt_assert(open->basis.knot_vector.count == 8);
ufbxt_assert(open->basis.spans.count == 2);
ufbxt_assert(open->control_points.count == 4);
{
ufbx_real weights[16];
size_t knot = ufbx_evaluate_nurbs_basis(&open->basis, 0.0f, weights, 16, NULL, 0);
ufbxt_assert(knot == 0);
ufbxt_assert_close_real(err, weights[0], 1.0f);
ufbxt_assert_close_real(err, weights[1], 0.0f);
ufbxt_assert_close_real(err, weights[2], 0.0f);
ufbxt_assert_close_real(err, weights[3], 0.0f);
}
{
ufbx_real weights[16];
size_t knot = ufbx_evaluate_nurbs_basis(&open->basis, 0.5f, weights, 16, NULL, 0);
ufbxt_assert(knot == 0);
ufbxt_assert_close_real(err, weights[0], 0.125f);
ufbxt_assert_close_real(err, weights[1], 0.375f);
ufbxt_assert_close_real(err, weights[2], 0.375f);
ufbxt_assert_close_real(err, weights[3], 0.125f);
}
{
ufbx_real weights[16];
size_t knot = ufbx_evaluate_nurbs_basis(&open->basis, 1.0f, weights, 16, NULL, 0);
ufbxt_assert(knot == 0);
ufbxt_assert_close_real(err, weights[0], 0.0f);
ufbxt_assert_close_real(err, weights[1], 0.0f);
ufbxt_assert_close_real(err, weights[2], 0.0f);
ufbxt_assert_close_real(err, weights[3], 1.0f);
}
{
ufbxt_curve_sample samples[] = {
{ 0.00f, { 0.000000f, 0.0f, -1.000000f }, { -1.500000f, 0.0f, 0.000000f } },
{ 0.10f, { -0.149500f, 0.0f, -0.985500f }, { -1.485000f, 0.0f, 0.285000f } },
{ 0.20f, { -0.296000f, 0.0f, -0.944000f }, { -1.440000f, 0.0f, 0.540000f } },
{ 0.30f, { -0.436500f, 0.0f, -0.878500f }, { -1.365000f, 0.0f, 0.765000f } },
{ 0.40f, { -0.568000f, 0.0f, -0.792000f }, { -1.260000f, 0.0f, 0.960000f } },
{ 0.50f, { -0.687500f, 0.0f, -0.687500f }, { -1.125000f, 0.0f, 1.125000f } },
{ 0.60f, { -0.792000f, 0.0f, -0.568000f }, { -0.960000f, 0.0f, 1.260000f } },
{ 0.70f, { -0.878500f, 0.0f, -0.436500f }, { -0.765000f, 0.0f, 1.365000f } },
{ 0.80f, { -0.944000f, 0.0f, -0.296000f }, { -0.540000f, 0.0f, 1.440000f } },
{ 0.90f, { -0.985500f, 0.0f, -0.149500f }, { -0.285000f, 0.0f, 1.485000f } },
{ 1.00f, { -1.000000f, 0.0f, -0.000000f }, { 0.000000f, 0.0f, 1.500000f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(open, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
}
{
ufbxt_assert_close_real(err, closed->basis.t_min, 1.0f);
ufbxt_assert_close_real(err, closed->basis.t_max, 5.0f);
ufbxt_assert(closed->basis.knot_vector.count == 11);
ufbxt_assert(closed->basis.spans.count == 5);
ufbxt_assert(closed->control_points.count == 6);
{
ufbx_real weights[16];
size_t knot = ufbx_evaluate_nurbs_basis(&closed->basis, 3.14f, weights, 16, NULL, 0);
ufbxt_assert(knot == 2);
ufbxt_assert_close_real(err, weights[0], 0.106009f);
ufbxt_assert_close_real(err, weights[1], 0.648438f);
ufbxt_assert_close_real(err, weights[2], 0.244866f);
ufbxt_assert_close_real(err, weights[3], 0.000686f);
}
{
ufbxt_curve_sample samples[] = {
{ 1.00f, { -1.000000f, 0.0f, 0.000000f }, { 0.000000f, 0.0f, 1.500000f } },
{ 1.30f, { -0.878500f, 0.0f, 0.436500f }, { 0.765000f, 0.0f, 1.365000f } },
{ 1.60f, { -0.568000f, 0.0f, 0.792000f }, { 1.260000f, 0.0f, 0.960000f } },
{ 1.90f, { -0.149500f, 0.0f, 0.985500f }, { 1.485000f, 0.0f, 0.285000f } },
{ 2.20f, { 0.296000f, 0.0f, 0.944000f }, { 1.440000f, 0.0f, -0.540000f } },
{ 2.50f, { 0.687500f, 0.0f, 0.687500f }, { 1.125000f, 0.0f, -1.125000f } },
{ 2.80f, { 0.944000f, 0.0f, 0.296000f }, { 0.540000f, 0.0f, -1.440000f } },
{ 3.10f, { 0.985500f, 0.0f, -0.149500f }, { -0.285000f, 0.0f, -1.485000f } },
{ 3.40f, { 0.792000f, 0.0f, -0.568000f }, { -0.960000f, 0.0f, -1.260000f } },
{ 3.70f, { 0.436500f, 0.0f, -0.878500f }, { -1.365000f, 0.0f, -0.765000f } },
{ 4.00f, { 0.000000f, 0.0f, -1.000000f }, { -1.500000f, 0.0f, -0.000000f } },
{ 4.30f, { -0.436500f, 0.0f, -0.878500f }, { -1.365000f, 0.0f, 0.765000f } },
{ 4.60f, { -0.792000f, 0.0f, -0.568000f }, { -0.960000f, 0.0f, 1.260000f } },
{ 4.90f, { -0.985500f, 0.0f, -0.149500f }, { -0.285000f, 0.0f, 1.485000f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(closed, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
}
{
ufbxt_assert_close_real(err, periodic->basis.t_min, 0.0f);
ufbxt_assert_close_real(err, periodic->basis.t_max, 4.0f);
ufbxt_assert(periodic->basis.knot_vector.count == 11);
ufbxt_assert(periodic->basis.spans.count == 5);
ufbxt_assert(periodic->control_points.count == 4);
{
ufbxt_curve_sample samples[] = {
{ 0.00f, { 0.000000f, 0.0f, -1.000000f }, { -1.500000f, 0.0f, 0.000000f } },
{ 0.20f, { -0.296000f, 0.0f, -0.944000f }, { -1.440000f, 0.0f, 0.540000f } },
{ 0.40f, { -0.568000f, 0.0f, -0.792000f }, { -1.260000f, 0.0f, 0.960000f } },
{ 0.60f, { -0.792000f, 0.0f, -0.568000f }, { -0.960000f, 0.0f, 1.260000f } },
{ 0.80f, { -0.944000f, 0.0f, -0.296000f }, { -0.540000f, 0.0f, 1.440000f } },
{ 1.00f, { -1.000000f, 0.0f, 0.000000f }, { 0.000000f, 0.0f, 1.500000f } },
{ 1.20f, { -0.944000f, 0.0f, 0.296000f }, { 0.540000f, 0.0f, 1.440000f } },
{ 1.40f, { -0.792000f, 0.0f, 0.568000f }, { 0.960000f, 0.0f, 1.260000f } },
{ 1.60f, { -0.568000f, 0.0f, 0.792000f }, { 1.260000f, 0.0f, 0.960000f } },
{ 1.80f, { -0.296000f, 0.0f, 0.944000f }, { 1.440000f, 0.0f, 0.540000f } },
{ 2.00f, { -0.000000f, 0.0f, 1.000000f }, { 1.500000f, 0.0f, 0.000000f } },
{ 2.20f, { 0.296000f, 0.0f, 0.944000f }, { 1.440000f, 0.0f, -0.540000f } },
{ 2.40f, { 0.568000f, 0.0f, 0.792000f }, { 1.260000f, 0.0f, -0.960000f } },
{ 2.60f, { 0.792000f, 0.0f, 0.568000f }, { 0.960000f, 0.0f, -1.260000f } },
{ 2.80f, { 0.944000f, 0.0f, 0.296000f }, { 0.540000f, 0.0f, -1.440000f } },
{ 3.00f, { 1.000000f, 0.0f, -0.000000f }, { -0.000000f, 0.0f, -1.500000f } },
{ 3.20f, { 0.944000f, 0.0f, -0.296000f }, { -0.540000f, 0.0f, -1.440000f } },
{ 3.40f, { 0.792000f, 0.0f, -0.568000f }, { -0.960000f, 0.0f, -1.260000f } },
{ 3.60f, { 0.568000f, 0.0f, -0.792000f }, { -1.260000f, 0.0f, -0.960000f } },
{ 3.80f, { 0.296000f, 0.0f, -0.944000f }, { -1.440000f, 0.0f, -0.540000f } },
{ 4.00f, { -0.000000f, 0.0f, -1.000000f }, { -1.500000f, 0.0f, 0.000000f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(periodic, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
}
}
#endif
UFBXT_FILE_TEST(max_nurbs_curve_rational)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Curve001");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbx_nurbs_curve *curve = (ufbx_nurbs_curve*)node->attrib;
ufbxt_curve_sample samples[] = {
{ 0.000000f, { 0.000000f, -40.000000f, 0.000000f }, { -24.322954f, 0.000000f, 6.080737f } },
{ 0.974593f, { -17.005555f, -37.570554f, 4.572092f }, { -12.337594f, 4.358807f, 3.678146f } },
{ 1.949186f, { -26.036811f, -32.223433f, 7.596081f }, { -6.784099f, 6.393973f, 2.654863f } },
{ 2.923779f, { -30.970646f, -25.399582f, 9.909292f }, { -3.585288f, 7.504839f, 2.146619f } },
{ 3.898372f, { -33.353768f, -17.752430f, 11.854892f }, { -1.420225f, 8.123470f, 1.872876f } },
{ 4.872965f, { -33.899033f, -9.674799f, 13.598350f }, { 0.243486f, 8.402346f, 1.719326f } },
{ 5.847558f, { -32.960866f, -1.468619f, 15.227145f }, { 1.651919f, 8.390724f, 1.631066f } },
{ 6.822152f, { -30.721680f, 6.587631f, 16.788850f }, { 2.927439f, 8.093263f, 1.577779f } },
{ 7.796745f, { -27.278008f, 14.208835f, 18.307632f }, { 4.130179f, 7.494472f, 1.540372f } },
{ 8.771338f, { -22.686868f, 21.090577f, 19.792049f }, { 5.283867f, 6.572031f, 1.505301f } },
{ 9.745931f, { -16.994772f, 26.907367f, 21.239065f }, { 6.387689f, 5.306512f, 1.462049f } },
{ 10.720524f, { -10.270443f, 31.351748f, 22.650823f }, { 7.377118f, 3.800029f, 1.451715f } },
{ 11.695117f, { -2.706724f, 34.283196f, 24.096090f }, { 8.085573f, 2.198608f, 1.524685f } },
{ 12.669710f, { 5.351791f, 35.610305f, 25.636226f }, { 8.370286f, 0.517035f, 1.639198f } },
{ 13.644303f, { 13.435696f, 35.294565f, 27.290675f }, { 8.125935f, -1.151753f, 1.752252f } },
{ 14.618896f, { 21.010971f, 33.416195f, 29.038561f }, { 7.331955f, -2.663870f, 1.825309f } },
{ 15.593489f, { 27.575750f, 30.201053f, 30.826822f }, { 6.075349f, -3.873362f, 1.832138f } },
{ 16.568082f, { 32.759100f, 25.997218f, 32.585338f }, { 4.530977f, -4.682518f, 1.764466f } },
{ 17.542675f, { 36.382081f, 21.211799f, 34.244771f }, { 2.907510f, -5.069834f, 1.631536f } },
{ 18.517268f, { 38.464958f, 16.297312f, 35.744787f }, { 1.405886f, -4.869993f, 1.433972f } },
{ 19.491861f, { 39.216555f, 11.905556f, 37.036473f }, { 0.183372f, -4.095993f, 1.223988f } },
{ 20.466455f, { 38.892809f, 8.347449f, 38.154114f }, { -0.825634f, -3.206871f, 1.084857f } },
{ 21.441048f, { 37.616454f, 5.638129f, 39.185141f }, { -1.808639f, -2.363861f, 1.050754f } },
{ 22.415641f, { 35.306462f, 3.716150f, 40.244592f }, { -2.930163f, -1.617486f, 1.138404f } },
{ 23.390234f, { 31.980875f, 2.380957f, 41.416165f }, { -3.862865f, -1.167548f, 1.271465f } },
{ 24.364827f, { 27.819473f, 1.365943f, 42.736393f }, { -4.662313f, -0.950026f, 1.446458f } },
{ 25.339420f, { 22.913277f, 0.459596f, 44.257763f }, { -5.398160f, -0.950224f, 1.690832f } },
{ 26.314013f, { 17.312988f, -0.588856f, 46.074971f }, { -6.083534f, -1.274792f, 2.070482f } },
{ 27.288606f, { 11.096546f, -2.257805f, 48.396616f }, { -6.632300f, -2.349350f, 2.781984f } },
{ 28.263199f, { 4.579470f, -5.973973f, 51.849971f }, { -6.516068f, -6.140789f, 4.679450f } },
{ 29.237792f, { -0.000000f, -20.000002f, 60.000000f }, { -0.000000f, -32.801115f, 16.400558f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(curve, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
#endif
UFBXT_FILE_TEST(maya_nurbs_curve_multiplicity)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "curve1");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbx_nurbs_curve *curve = (ufbx_nurbs_curve*)node->attrib;
ufbxt_assert(curve->basis.order == 6);
ufbxt_assert_close_real(err, curve->basis.t_min, 0.0f);
ufbxt_assert_close_real(err, curve->basis.t_max, 43.257f);
for (size_t i = 1; i < curve->basis.spans.count; i++) {
ufbxt_assert(curve->basis.spans.data[i - 1] < curve->basis.spans.data[i]);
}
ufbxt_curve_sample samples[] = {
{ 0.000000f, { 0.908068f, 0.000000f, -0.405819f }, { 0.253319f, 0.000000f, -0.445464f } },
{ 1.081421f, { 1.119511f, 0.000000f, -0.868651f }, { 0.205140f, 0.000000f, -0.285504f } },
{ 2.162843f, { 1.387340f, 0.000000f, -0.963056f }, { 0.256681f, 0.000000f, 0.053404f } },
{ 3.244264f, { 1.633207f, 0.000000f, -0.849916f }, { 0.200801f, 0.000000f, 0.139006f } },
{ 4.325685f, { 1.835297f, 0.000000f, -0.683140f }, { 0.178149f, 0.000000f, 0.161468f } },
{ 5.407107f, { 2.026478f, 0.000000f, -0.516048f }, { 0.177779f, 0.000000f, 0.141353f } },
{ 6.488528f, { 2.222653f, 0.000000f, -0.388757f }, { 0.185504f, 0.000000f, 0.089817f } },
{ 7.569949f, { 2.427523f, 0.000000f, -0.328685f }, { 0.192948f, 0.000000f, 0.019186f } },
{ 8.651371f, { 2.638875f, 0.000000f, -0.349293f }, { 0.197550f, 0.000000f, -0.057045f } },
{ 9.732792f, { 2.854870f, 0.000000f, -0.448812f }, { 0.202560f, 0.000000f, -0.124212f } },
{ 10.814214f, { 3.080323f, 0.000000f, -0.608983f }, { 0.217038f, 0.000000f, -0.166482f } },
{ 11.895635f, { 3.332994f, 0.000000f, -0.793795f }, { 0.255859f, 0.000000f, -0.166851f } },
{ 12.977056f, { 3.649869f, 0.000000f, -0.948214f }, { 0.339710f, 0.000000f, -0.107146f } },
{ 14.058478f, { 3.985671f, 0.000000f, 0.194786f }, { -0.143623f, 0.000000f, 5.538350f } },
{ 15.139899f, { 3.504179f, 0.000000f, 3.786682f }, { -0.683192f, 0.000000f, 1.734052f } },
{ 16.221320f, { 2.613011f, 0.000000f, 4.871228f }, { -0.929318f, 0.000000f, 0.531104f } },
{ 17.302742f, { 1.550585f, 0.000000f, 5.237214f }, { -1.016831f, 0.000000f, 0.198109f } },
{ 18.384163f, { 0.445465f, 0.000000f, 5.328320f }, { -1.014626f, 0.000000f, -0.029385f } },
{ 19.465584f, { -0.620174f, 0.000000f, 5.174213f }, { -0.946609f, 0.000000f, -0.255004f } },
{ 20.547006f, { -1.584181f, 0.000000f, 4.779433f }, { -0.829428f, 0.000000f, -0.473124f } },
{ 21.628427f, { -2.402332f, 0.000000f, 4.157381f }, { -0.679606f, 0.000000f, -0.673164f } },
{ 22.709848f, { -3.048214f, 0.000000f, 3.335470f }, { -0.513558f, 0.000000f, -0.839798f } },
{ 23.791270f, { -3.513108f, 0.000000f, 2.360250f }, { -0.347591f, 0.000000f, -0.952957f } },
{ 24.872691f, { -3.805868f, 0.000000f, 1.302539f }, { -0.197901f, 0.000000f, -0.987827f } },
{ 25.954113f, { -3.952805f, 0.000000f, 0.262553f }, { -0.080577f, 0.000000f, -0.914853f } },
{ 27.035534f, { -3.997572f, 0.000000f, -0.624962f }, { -0.011599f, 0.000000f, -0.699733f } },
{ 28.116955f, { -2.943720f, 0.000000f, -0.502852f }, { 0.978119f, 0.000000f, 1.270640f } },
{ 29.198377f, { -2.049079f, 0.000000f, -0.429651f }, { 0.608714f, 0.000000f, -0.657963f } },
{ 30.279798f, { -1.426889f, 0.000000f, -0.907600f }, { 0.591440f, 0.000000f, -0.061570f } },
{ 31.361219f, { -1.030212f, 0.000000f, -0.664031f }, { 0.116667f, 0.000000f, 0.406368f } },
{ 32.442641f, { -0.988133f, 0.000000f, -0.182528f }, { 0.031241f, 0.000000f, 0.447589f } },
{ 33.524062f, { -0.910710f, 0.000000f, 0.269968f }, { 0.117183f, 0.000000f, 0.384560f } },
{ 34.605483f, { -0.736010f, 0.000000f, 0.651700f }, { 0.200556f, 0.000000f, 0.327364f } },
{ 35.686905f, { -0.495401f, 0.000000f, 0.997855f }, { 0.233755f, 0.000000f, 0.324195f } },
{ 36.768326f, { -0.254763f, 0.000000f, 1.378865f }, { 0.200505f, 0.000000f, 0.392064f } },
{ 37.849747f, { -0.080667f, 0.000000f, 1.866711f }, { 0.115792f, 0.000000f, 0.516827f } },
{ 38.931169f, { -0.006578f, 0.000000f, 2.501231f }, { 0.025863f, 0.000000f, 0.653179f } },
{ 40.012590f, { 1.291208f, 0.000000f, 3.143478f }, { 1.958604f, 0.000000f, 0.677384f } },
{ 41.094012f, { 0.552930f, 0.000000f, 3.783813f }, { -1.959784f, 0.000000f, 0.211471f } },
{ 42.175433f, { -1.250738f, 0.000000f, 3.642614f }, { -1.058494f, 0.000000f, -0.348306f } },
{ 43.256854f, { -1.669232f, 0.000000f, 3.251459f }, { 0.154248f, 0.000000f, -0.305748f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(curve, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
#endif
UFBXT_FILE_TEST(maya_nurbs_curve_linear)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "curve1");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
ufbx_nurbs_curve *curve = (ufbx_nurbs_curve*)node->attrib;
ufbxt_assert(curve->basis.order == 2);
ufbxt_curve_sample samples[] = {
{ 0.000000f, { 0.000000f, 0.000000f, -1.000000f }, { 1.000000f, 0.000000f, 0.000000f } },
{ 1.400000f, { 1.000000f, 0.000000f, -1.400000f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 2.800000f, { 1.800000f, 0.000000f, -2.000000f }, { 1.000000f, 0.000000f, 0.000000f } },
{ 4.200000f, { 1.800000f, 0.000000f, 1.000000f }, { -1.000000f, 0.000000f, 0.000000f } },
{ 5.600000f, { 1.000000f, 0.000000f, 1.600000f }, { 0.000000f, 0.000000f, 1.000000f } },
{ 7.000000f, { -1.000000f, 0.000000f, 2.000000f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 8.400000f, { -1.400000f, 0.000000f, 1.000000f }, { -1.000000f, 0.000000f, 0.000000f } },
{ 9.800000f, { -2.000000f, 0.000000f, -1.400000f }, { 0.000000f, 0.000000f, -3.000000f } },
{ 11.200000f, { -1.000000f, 0.000000f, -1.800000f }, { 0.000000f, 0.000000f, 1.000000f } },
{ 12.600000f, { -0.400000f, 0.000000f, -1.000000f }, { 1.000000f, 0.000000f, 0.000000f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_curve_sample *sample = &samples[i];
ufbx_curve_point p = ufbx_evaluate_nurbs_curve(curve, sample->u);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative, sample->derivative);
}
}
#endif
UFBXT_FILE_TEST(maya_nurbs_surface_plane)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "nurbsPlane1");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE);
ufbx_nurbs_surface *surface = (ufbx_nurbs_surface*)node->attrib;
ufbxt_assert(surface->span_subdivision_u == 4);
ufbxt_assert(surface->span_subdivision_v == 4);
ufbxt_assert(surface->material);
ufbxt_assert(!strcmp(surface->material->name.data, "lambert1"));
ufbxt_assert(surface->num_control_points_u == 5);
ufbxt_assert(surface->num_control_points_v == 6);
ufbxt_assert(!surface->flip_normals);
ufbxt_surface_sample samples[] = {
{ 0.000000f, 0.000000f, { -0.500000f, -0.550589f, 0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 0.000000f, 0.200000f, { -0.500000f, 0.113108f, 0.300000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 1.958420f, -1.000000f } },
{ 0.000000f, 0.400000f, { -0.500000f, 0.342215f, 0.100000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 0.565126f, -1.000000f } },
{ 0.000000f, 0.600000f, { -0.500000f, 0.391664f, -0.100000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 0.035320f, -1.000000f } },
{ 0.000000f, 0.800000f, { -0.500000f, 0.392449f, -0.300000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, -0.000000f, -1.000000f } },
{ 0.000000f, 1.000000f, { -0.500000f, 0.392449f, -0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 0.200000f, 0.000000f, { -0.300000f, -0.550589f, 0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 0.200000f, 0.200000f, { -0.300000f, 0.084626f, 0.300000f }, { 1.000000f, -0.249221f, -0.000000f }, { -0.000000f, 1.734630f, -1.000000f } },
{ 0.200000f, 0.400000f, { -0.300007f, 0.276510f, 0.100005f }, { 0.999902f, -0.574922f, 0.000072f }, { -0.000294f, 0.456622f, -0.999783f } },
{ 0.200000f, 0.600000f, { -0.300418f, 0.316464f, -0.099691f }, { 0.993731f, -0.657995f, 0.004628f }, { -0.004701f, 0.028539f, -0.996529f } },
{ 0.200000f, 0.800000f, { -0.302031f, 0.317098f, -0.298501f }, { 0.969539f, -0.659314f, 0.022489f }, { -0.009697f, -0.000000f, -0.992841f } },
{ 0.200000f, 1.000000f, { -0.303265f, 0.317098f, -0.497590f }, { 0.951027f, -0.659314f, 0.036156f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 0.400000f, 0.000000f, { -0.100000f, -0.550589f, 0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 0.400000f, 0.200000f, { -0.100000f, 0.027661f, 0.300000f }, { 1.000000f, -0.284823f, -0.000000f }, { -0.000000f, 1.287050f, -1.000000f } },
{ 0.400000f, 0.400000f, { -0.100052f, 0.145099f, 0.100039f }, { 0.999608f, -0.657053f, 0.000289f }, { -0.002351f, 0.239613f, -0.998265f } },
{ 0.400000f, 0.600000f, { -0.103343f, 0.166065f, -0.097532f }, { 0.974926f, -0.751994f, 0.018512f }, { -0.037611f, 0.014976f, -0.972232f } },
{ 0.400000f, 0.800000f, { -0.116246f, 0.166398f, -0.288006f }, { 0.878155f, -0.753501f, 0.089956f }, { -0.077574f, 0.000000f, -0.942729f } },
{ 0.400000f, 1.000000f, { -0.126119f, 0.166398f, -0.480717f }, { 0.804107f, -0.753501f, 0.144624f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 0.600000f, 0.000000f, { 0.100000f, -0.550589f, 0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 0.600000f, 0.200000f, { 0.100000f, -0.016249f, 0.300000f }, { 1.000000f, -0.142412f, -0.000000f }, { 0.000000f, 0.942041f, -1.000000f } },
{ 0.600000f, 0.400000f, { 0.099827f, 0.043804f, 0.100128f }, { 0.999216f, -0.328527f, 0.000578f }, { -0.007787f, 0.072336f, -0.994251f } },
{ 0.600000f, 0.600000f, { 0.088926f, 0.050133f, -0.091824f }, { 0.949851f, -0.375997f, 0.037024f }, { -0.124588f, 0.004521f, -0.908019f } },
{ 0.600000f, 0.800000f, { 0.046185f, 0.050233f, -0.260269f }, { 0.756309f, -0.376751f, 0.179913f }, { -0.256963f, 0.000000f, -0.810289f } },
{ 0.600000f, 1.000000f, { 0.013481f, 0.050233f, -0.436124f }, { 0.608214f, -0.376751f, 0.289249f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 0.800000f, 0.000000f, { 0.300000f, -0.550589f, 0.500000f }, { 1.000000f, -0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 0.800000f, 0.200000f, { 0.300000f, -0.032864f, 0.300000f }, { 1.000000f, -0.035603f, 0.000000f }, { 0.000000f, 0.811497f, -1.000000f } },
{ 0.800000f, 0.400000f, { 0.299670f, 0.005475f, 0.100243f }, { 0.999314f, -0.082132f, 0.000506f }, { -0.014839f, 0.009042f, -0.989045f } },
{ 0.800000f, 0.600000f, { 0.278896f, 0.006267f, -0.084419f }, { 0.956120f, -0.093999f, 0.032396f }, { -0.237422f, 0.000565f, -0.824715f } },
{ 0.800000f, 0.800000f, { 0.197447f, 0.006279f, -0.224287f }, { 0.786770f, -0.094188f, 0.157424f }, { -0.489684f, 0.000000f, -0.638475f } },
{ 0.800000f, 1.000000f, { 0.135123f, 0.006279f, -0.378275f }, { 0.657187f, -0.094188f, 0.253093f }, { 0.000000f, 0.000000f, -1.000000f } },
{ 1.000000f, 0.000000f, { 0.500000f, -0.550589f, 0.500000f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 4.955298f, -1.000000f } },
{ 1.000000f, 0.200000f, { 0.500000f, -0.035238f, 0.300000f }, { 1.000000f, -0.000000f, 0.000000f }, { 0.000000f, 0.792848f, -1.000000f } },
{ 1.000000f, 0.400000f, { 0.499592f, -0.000000f, 0.100301f }, { 1.000000f, -0.000000f, 0.000000f }, { -0.018365f, 0.000000f, -0.986441f } },
{ 1.000000f, 0.600000f, { 0.473881f, 0.000000f, -0.080717f }, { 1.000000f, -0.000000f, 0.000000f }, { -0.293840f, 0.000000f, -0.783063f } },
{ 1.000000f, 0.800000f, { 0.373078f, 0.000000f, -0.206295f }, { 1.000000f, 0.000000f, 0.000000f }, { -0.606044f, 0.000000f, -0.552568f } },
{ 1.000000f, 1.000000f, { 0.295945f, 0.000000f, -0.349350f }, { 1.000000f, 0.000000f, 0.000000f }, { 0.000000f, 0.000000f, -1.000000f } },
};
for (size_t i = 0; i < ufbxt_arraycount(samples); i++) {
ufbxt_hintf("i: %zu", i);
const ufbxt_surface_sample *sample = &samples[i];
ufbx_surface_point p = ufbx_evaluate_nurbs_surface(surface, sample->u, sample->v);
ufbxt_assert_close_vec3(err, p.position, sample->position);
ufbxt_assert_close_vec3(err, p.derivative_u, sample->derivative_u);
ufbxt_assert_close_vec3(err, p.derivative_v, sample->derivative_v);
}
}
#endif
UFBXT_FILE_TEST(maya_nurbs_surface_sphere)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "nurbsSphere1");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE);
ufbx_nurbs_surface *surface = (ufbx_nurbs_surface*)node->attrib;
ufbxt_assert(surface->material);
ufbxt_assert(!strcmp(surface->material->name.data, "lambert1"));
ufbxt_assert_close_real(err, surface->basis_u.t_min, 0.0f);
ufbxt_assert_close_real(err, surface->basis_u.t_max, 8.0f);
ufbxt_assert_close_real(err, surface->basis_v.t_min, 0.0f);
ufbxt_assert_close_real(err, surface->basis_v.t_max, 10.0f);
for (ufbx_real u = 0.1f; u <= 7.9f; u += 0.05f) {
for (ufbx_real v = 0.0f; v <= 10.0f; v += 0.05f) {
ufbx_surface_point point = ufbx_evaluate_nurbs_surface(surface, u, v);
ufbx_vec3 normal = ufbxt_normalize(ufbxt_cross3(point.derivative_u, point.derivative_v));
ufbxt_assert_close_vec3(err, ufbxt_mul3(point.position, 0.1f), ufbxt_mul3(normal, 0.1f));
}
}
}
#endif
UFBXT_FILE_TEST(maya_nurbs_low_sphere)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST_ALT(nurbs_alloc_fail, maya_nurbs_surface_plane)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "nurbsPlane1");
ufbxt_assert(node && node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE);
ufbx_nurbs_surface *surface = (ufbx_nurbs_surface*)node->attrib;
for (size_t max_temp = 1; max_temp < 10000; max_temp++) {
ufbx_tessellate_surface_opts opts = { 0 };
opts.temp_allocator.huge_threshold = 1;
opts.temp_allocator.allocation_limit = max_temp;
ufbxt_hintf("Temp limit: %zu", max_temp);
ufbx_error error;
ufbx_mesh *tess_mesh = ufbx_tessellate_nurbs_surface(surface, &opts, &error);
if (tess_mesh) {
ufbxt_logf(".. Tested up to %zu temporary allocations", max_temp);
ufbx_free_mesh(tess_mesh);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
for (size_t max_result = 1; max_result < 10000; max_result++) {
ufbx_tessellate_surface_opts opts = { 0 };
opts.result_allocator.huge_threshold = 1;
opts.result_allocator.allocation_limit = max_result;
ufbxt_hintf("Result limit: %zu", max_result);
ufbx_error error;
ufbx_mesh *tess_mesh = ufbx_tessellate_nurbs_surface(surface, &opts, &error);
if (tess_mesh) {
ufbxt_logf(".. Tested up to %zu result allocations", max_result);
ufbx_free_mesh(tess_mesh);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
}
#endif
UFBXT_FILE_TEST(synthetic_nurbs_invalid)
#if UFBXT_IMPL
{
{
ufbx_node *node = ufbx_find_node(scene, "curve1");
ufbxt_assert(node);
ufbx_nurbs_curve *curve = ufbx_as_nurbs_curve(node->attrib);
ufbxt_assert(!curve->basis.valid);
ufbx_error error;
ufbx_line_curve *line = ufbx_tessellate_nurbs_curve(curve, NULL, &error);
ufbxt_assert(!line);
ufbxt_assert(error.type == UFBX_ERROR_BAD_NURBS);
}
{
ufbx_node *node = ufbx_find_node(scene, "nurbsPlane1");
ufbxt_assert(node);
ufbx_nurbs_surface *surface = ufbx_as_nurbs_surface(node->attrib);
ufbxt_assert(!surface->basis_u.valid);
ufbxt_assert(!surface->basis_v.valid);
ufbx_error error;
ufbx_mesh *mesh = ufbx_tessellate_nurbs_surface(surface, NULL, &error);
ufbxt_assert(!mesh);
ufbxt_assert(error.type == UFBX_ERROR_BAD_NURBS);
}
}
#endif
UFBXT_FILE_TEST(synthetic_nurbs_truncated)
#if UFBXT_IMPL
{
{
ufbx_node *node = ufbx_find_node(scene, "curve1");
ufbxt_assert(node);
ufbx_nurbs_curve *curve = ufbx_as_nurbs_curve(node->attrib);
ufbxt_assert(curve->basis.valid);
ufbx_line_curve *line = ufbx_tessellate_nurbs_curve(curve, NULL, NULL);
ufbxt_assert(line);
ufbx_free_line_curve(line);
}
{
ufbx_node *node = ufbx_find_node(scene, "nurbsPlane1");
ufbxt_assert(node);
ufbx_nurbs_surface *surface = ufbx_as_nurbs_surface(node->attrib);
ufbxt_assert(surface->basis_u.valid);
ufbxt_assert(surface->basis_v.valid);
ufbx_error error;
ufbx_mesh *mesh = ufbx_tessellate_nurbs_surface(surface, NULL, &error);
ufbxt_assert(mesh);
ufbx_free_mesh(mesh);
}
}
#endif
UFBXT_FILE_TEST(max_nurbs_to_line)
#if UFBXT_IMPL
{
ufbx_node *line_node = ufbx_find_node(scene, "Line");
ufbx_node *nurbs_node = ufbx_find_node(scene, "Nurbs");
ufbxt_assert(line_node);
ufbxt_assert(nurbs_node);
ufbx_line_curve *line = ufbx_as_line_curve(line_node->attrib);
ufbx_nurbs_curve *nurbs = ufbx_as_nurbs_curve(nurbs_node->attrib);
ufbxt_assert(line);
ufbxt_assert(nurbs);
ufbx_tessellate_curve_opts opts = { 0 };
opts.span_subdivision = 5;
ufbx_line_curve *tess_line = ufbx_tessellate_nurbs_curve(nurbs, &opts, NULL);
ufbxt_assert(tess_line);
size_t num_indices = line->point_indices.count;
ufbxt_assert(line->segments.count == 1);
ufbxt_assert(tess_line->segments.count == 1);
ufbxt_assert(tess_line->point_indices.count == num_indices);
ufbxt_assert(line->segments.data[0].index_begin == 0);
ufbxt_assert(tess_line->segments.data[0].index_begin == 0);
ufbxt_assert(line->segments.data[0].num_indices == num_indices);
ufbxt_assert(tess_line->segments.data[0].num_indices == num_indices);
for (size_t i = 0; i < num_indices; i++) {
ufbx_vec3 point = line->control_points.data[line->point_indices.data[i]];
ufbx_vec3 tess_point = tess_line->control_points.data[tess_line->point_indices.data[i]];
ufbxt_assert_close_vec3(err, point, tess_point);
}
ufbx_retain_line_curve(tess_line);
ufbx_free_line_curve(tess_line);
ufbx_free_line_curve(tess_line);
}
#endif
UFBXT_FILE_TEST_ALT(tessellate_line_alloc_fail, max_nurbs_to_line)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Nurbs");
ufbx_nurbs_curve *nurbs = ufbx_as_nurbs_curve(node->attrib);
ufbxt_assert(nurbs);
for (size_t max_temp = 1; max_temp < 10000; max_temp++) {
ufbx_tessellate_curve_opts opts = { 0 };
opts.temp_allocator.huge_threshold = 1;
opts.temp_allocator.allocation_limit = max_temp;
ufbxt_hintf("Temp limit: %zu", max_temp);
ufbx_error error;
ufbx_line_curve *line = ufbx_tessellate_nurbs_curve(nurbs, &opts, &error);
if (line) {
ufbxt_logf(".. Tested up to %zu temporary allocations", max_temp);
ufbx_free_line_curve(line);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
for (size_t max_result = 1; max_result < 10000; max_result++) {
ufbx_tessellate_curve_opts opts = { 0 };
opts.result_allocator.huge_threshold = 1;
opts.result_allocator.allocation_limit = max_result;
ufbxt_hintf("Result limit: %zu", max_result);
ufbx_error error;
ufbx_line_curve *line = ufbx_tessellate_nurbs_curve(nurbs, &opts, &error);
if (line) {
ufbxt_logf(".. Tested up to %zu result allocations", max_result);
ufbx_free_line_curve(line);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
}
#endif

1531
modules/ufbx/test/test_obj.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "scenes"
UFBXT_FILE_TEST_FLAGS(maya_slime, UFBXT_FILE_TEST_FLAG_HEAVY_TO_FUZZ)
#if UFBXT_IMPL
{
ufbx_node *node_high = ufbx_find_node(scene, "Slime_002:Slime_Body_high");
ufbxt_assert(node_high);
ufbxt_assert(!node_high->visible);
}
#endif
UFBXT_FILE_TEST(blender_293_barbarian)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST_ALT(evaluate_alloc_fail, blender_293_barbarian)
#if UFBXT_IMPL
{
for (size_t max_temp = 1; max_temp < 10000; max_temp++) {
ufbx_evaluate_opts opts = { 0 };
opts.temp_allocator.huge_threshold = 1;
opts.temp_allocator.allocation_limit = max_temp;
opts.evaluate_skinning = true;
ufbxt_hintf("Temp limit: %zu", max_temp);
ufbx_error error;
ufbx_scene *eval_scene = ufbx_evaluate_scene(scene, NULL, 0.2, &opts, &error);
if (eval_scene) {
ufbxt_logf(".. Tested up to %zu temporary allocations", max_temp);
ufbx_free_scene(eval_scene);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
for (size_t max_result = 1; max_result < 10000; max_result++) {
ufbx_evaluate_opts opts = { 0 };
opts.result_allocator.huge_threshold = 1;
opts.result_allocator.allocation_limit = max_result;
opts.evaluate_skinning = true;
ufbxt_hintf("Result limit: %zu", max_result);
ufbx_error error;
ufbx_scene *eval_scene = ufbx_evaluate_scene(scene, NULL, 0.2, &opts, &error);
if (eval_scene) {
ufbxt_logf(".. Tested up to %zu result allocations", max_result);
ufbx_free_scene(eval_scene);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
}
#endif
UFBXT_FILE_TEST(maya_kenney_character)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "maya_kenney_character_4", NULL, 4.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_kenney_character_9", NULL, 9.0/24.0);
{
ufbx_node *node = ufbx_find_node(scene, "characterMedium");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
}
for (int frame = 3; frame <= 15; frame += 10) {
double time = (double)frame / scene->settings.frames_per_second;
ufbx_scene *state = ufbx_evaluate_scene(scene, NULL, time, NULL, NULL);
ufbxt_assert(state);
ufbx_node *node = ufbx_find_node(scene, "characterMedium");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
for (size_t level = 0; level <= 2; level++) {
ufbx_mesh *sub_mesh = mesh;
if (level > 0) {
ufbx_subdivide_opts opts = { 0 };
opts.evaluate_source_vertices = true;
opts.evaluate_skin_weights = true;
sub_mesh = ufbx_subdivide_mesh(mesh, level, &opts, NULL);
ufbxt_assert(sub_mesh);
ufbxt_check_source_vertices(sub_mesh, mesh, err);
} else {
ufbx_retain_mesh(sub_mesh);
}
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
for (size_t vi = 0; vi < sub_mesh->num_vertices; vi++) {
ufbx_vec3 skin_pos = sub_mesh->skinned_position.values.data[vi];
if (level == 0) {
ufbx_matrix mat = ufbx_get_skin_vertex_matrix(skin, vi, NULL);
ufbx_vec3 local_pos = sub_mesh->vertices.data[vi];
ufbx_vec3 world_pos = ufbx_transform_position(&mat, local_pos);
ufbxt_assert_close_vec3(err, world_pos, skin_pos);
ufbx_skin_vertex skin_vertex = skin->vertices.data[vi];
ufbx_matrix sum = { 0 };
for (size_t wi = 0; wi < skin_vertex.num_weights; wi++) {
ufbx_skin_weight weight = skin->weights.data[skin_vertex.weight_begin + wi];
ufbx_skin_cluster *cluster = skin->clusters.data[weight.cluster_index];
for (size_t i = 0; i < 12; i++) {
sum.v[i] += cluster->geometry_to_world.v[i] * weight.weight;
}
}
ufbx_vec3 manual_pos = ufbx_transform_position(&sum, local_pos);
ufbxt_assert_close_vec3(err, manual_pos, skin_pos);
} else {
ufbx_subdivision_result *sub_res = sub_mesh->subdivision_result;
ufbxt_assert(sub_res);
ufbx_subdivision_weight_range range = sub_res->skin_cluster_ranges.data[vi];
ufbx_matrix sum = { 0 };
for (size_t wi = 0; wi < range.num_weights; wi++) {
ufbx_subdivision_weight weight = sub_res->skin_cluster_weights.data[range.weight_begin + wi];
ufbx_skin_cluster *cluster = skin->clusters.data[weight.index];
for (size_t i = 0; i < 12; i++) {
sum.v[i] += cluster->geometry_to_world.v[i] * weight.weight;
}
}
ufbx_vec3 local_pos = sub_mesh->vertices.data[vi];
ufbx_vec3 manual_pos = ufbx_transform_position(&sum, local_pos);
ufbxt_assert_close_vec3_threshold(err, manual_pos, skin_pos, 10.0f);
manual_pos = manual_pos;
}
}
ufbx_free_mesh(sub_mesh);
}
ufbx_free_scene(state);
}
}
#endif

View File

@@ -0,0 +1,519 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "skin"
#if UFBXT_IMPL
void ufbxt_check_stack_times(ufbx_scene *scene, ufbxt_diff_error *err, const char *stack_name, double begin, double end)
{
ufbx_anim_stack *stack = (ufbx_anim_stack*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_STACK, stack_name);
ufbxt_assert(stack);
ufbxt_assert(!strcmp(stack->name.data, stack_name));
ufbxt_assert_close_real(err, (ufbx_real)stack->time_begin, (ufbx_real)begin);
ufbxt_assert_close_real(err, (ufbx_real)stack->time_end, (ufbx_real)end);
}
void ufbxt_check_frame(ufbx_scene *scene, ufbxt_diff_error *err, bool check_normals, const char *file_name, const char *anim_name, double time)
{
char buf[512];
snprintf(buf, sizeof(buf), "%s%s.obj", data_root, file_name);
ufbxt_hintf("Frame from '%s' %s time %.2fs",
anim_name ? anim_name : "(implicit animation)",
buf, time);
size_t obj_size = 0;
void *obj_data = ufbxt_read_file(buf, &obj_size);
ufbxt_obj_file *obj_file = obj_data ? ufbxt_load_obj(obj_data, obj_size, NULL) : NULL;
ufbxt_assert(obj_file);
free(obj_data);
ufbx_evaluate_opts opts = { 0 };
opts.evaluate_skinning = true;
opts.evaluate_caches = true;
opts.load_external_files = true;
ufbx_anim anim = scene->anim;
if (anim_name) {
for (size_t i = 0; i < scene->anim_stacks.count; i++) {
ufbx_anim_stack *stack = scene->anim_stacks.data[i];
if (strstr(stack->name.data, anim_name)) {
ufbxt_assert(stack->layers.count > 0);
anim = stack->anim;
break;
}
}
}
ufbx_scene *eval = ufbx_evaluate_scene(scene, &anim, time, &opts, NULL);
ufbxt_assert(eval);
ufbxt_check_scene(eval);
uint32_t diff_flags = 0;
if (check_normals) diff_flags |= UFBXT_OBJ_DIFF_FLAG_CHECK_DEFORMED_NORMALS;
ufbxt_diff_to_obj(eval, obj_file, err, diff_flags);
ufbx_free_scene(eval);
free(obj_file);
}
#endif
UFBXT_FILE_TEST(blender_279_sausage)
#if UFBXT_IMPL
{
if (scene->metadata.ascii) {
// ???: In the 6100 ASCII file Spin Take starts from -1 frames
ufbxt_check_stack_times(scene, err, "Base", 0.0, 1.0/24.0);
ufbxt_check_stack_times(scene, err, "Spin", -1.0/24.0, 18.0/24.0);
ufbxt_check_stack_times(scene, err, "Wiggle", 0.0, 19.0/24.0);
} else {
ufbxt_check_stack_times(scene, err, "Skeleton|Base", 0.0, 1.0/24.0);
ufbxt_check_stack_times(scene, err, "Skeleton|Spin", 0.0, 19.0/24.0);
ufbxt_check_stack_times(scene, err, "Skeleton|Wiggle", 0.0, 19.0/24.0);
}
const char *cluster_names[][2] = {
{ "Bottom", "Cluster Skin Bottom" },
{ "Middle", "Cluster Skin Middle" },
{ "Top", "Cluster Skin Top" },
};
const ufbx_matrix cluster_bind_ref[][2] = {
{
{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
{ 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
},
{
{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.9f, 0.0f },
{ 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, -1.9f, 0.0f, 0.0f },
},
{
{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -3.8f, 0.0f },
{ 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, -3.8f, 0.0f, 0.0f },
},
};
for (size_t i = 0; i < 3; i++) {
ufbx_skin_cluster *cluster = (ufbx_skin_cluster*)ufbx_find_element(scene, UFBX_ELEMENT_SKIN_CLUSTER, cluster_names[i][scene->metadata.ascii]);
ufbxt_assert(cluster);
ufbxt_assert_close_vec3(err, cluster->geometry_to_bone.cols[0], cluster_bind_ref[i][scene->metadata.ascii].cols[0]);
ufbxt_assert_close_vec3(err, cluster->geometry_to_bone.cols[1], cluster_bind_ref[i][scene->metadata.ascii].cols[1]);
ufbxt_assert_close_vec3(err, cluster->geometry_to_bone.cols[2], cluster_bind_ref[i][scene->metadata.ascii].cols[2]);
ufbxt_assert_close_vec3(err, cluster->geometry_to_bone.cols[3], cluster_bind_ref[i][scene->metadata.ascii].cols[3]);
}
ufbxt_check_frame(scene, err, true, "blender_279_sausage_base_0", "Base", 0.0);
ufbxt_check_frame(scene, err, false, "blender_279_sausage_spin_15", "Spin", 15.0/24.0);
ufbxt_check_frame(scene, err, false, "blender_279_sausage_wiggle_20", "Wiggle", 20.0/24.0);
}
#endif
UFBXT_FILE_TEST(maya_game_sausage)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST_SUFFIX(maya_game_sausage, wiggle)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_10", NULL, 10.0/24.0);
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_18", NULL, 18.0/24.0);
}
#endif
UFBXT_FILE_TEST_SUFFIX(maya_game_sausage, spin)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "maya_game_sausage_spin_7", NULL, 27.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_spin_15", NULL, 35.0/24.0);
}
#endif
UFBXT_FILE_TEST_SUFFIX(maya_game_sausage, deform)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "maya_game_sausage_deform_8", NULL, 48.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_deform_15", NULL, 55.0/24.0);
}
#endif
UFBXT_FILE_TEST_SUFFIX(maya_game_sausage, combined)
#if UFBXT_IMPL
{
ufbxt_check_stack_times(scene, err, "wiggle", 1.0/24.0, 20.0/24.0);
ufbxt_check_stack_times(scene, err, "spin", 20.0/24.0, 40.0/24.0);
ufbxt_check_stack_times(scene, err, "deform", 40.0/24.0, 60.0/24.0);
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_10", "wiggle", 10.0/24.0);
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_18", "wiggle", 18.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_spin_7", "spin", 27.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_spin_15", "spin", 35.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_deform_8", "deform", 48.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_game_sausage_deform_15", "deform", 55.0/24.0);
}
#endif
UFBXT_FILE_TEST(synthetic_sausage_wiggle_no_link)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_10", NULL, 10.0/24.0);
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_18", NULL, 18.0/24.0);
}
#endif
UFBXT_FILE_TEST(synthetic_sausage_wiggle_no_bind)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_10", NULL, 10.0/24.0);
ufbxt_check_frame(scene, err, true, "maya_game_sausage_wiggle_18", NULL, 18.0/24.0);
}
#endif
UFBXT_FILE_TEST(maya_blend_shape_cube)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->blend_deformers.count == 1);
ufbx_blend_deformer *deformer = mesh->blend_deformers.data[0];
ufbxt_assert(deformer);
ufbx_blend_channel *top[2] = { NULL, NULL };
for (size_t i = 0; i < deformer->channels.count; i++) {
ufbx_blend_channel *chan = deformer->channels.data[i];
if (strstr(chan->name.data, "TopH")) top[0] = chan;
if (strstr(chan->name.data, "TopV")) top[1] = chan;
}
ufbxt_assert(top[0] && top[1]);
ufbxt_assert_close_real(err, top[0]->weight, 1.0);
ufbxt_assert_close_real(err, top[1]->weight, 1.0);
double keyframes[][3] = {
{ 1.0/24.0, 1.0, 1.0 },
{ 16.0/24.0, 0.279, 0.670 },
{ 53.0/24.0, 0.901, 0.168 },
{ 120.0/24.0, 1.0, 1.0 },
};
ufbx_vec3 ref_offsets[2][8] = {
{ {0,0,0},{0,0,0},{0.317,0,0},{-0.317,0,0},{0.317,0,0},{-0.317,0,0},{0,0,0},{0,0,0}, },
{ {0,0,0},{0,0,0},{0,0,-0.284},{0,0,-0.284},{0,0,0.284},{0,0,0.284},{0,0,0},{0,0,0}, },
};
for (size_t chan_ix = 0; chan_ix < 2; chan_ix++) {
ufbx_blend_channel *chan = top[chan_ix];
ufbxt_assert(chan->keyframes.count == 1);
ufbxt_assert_close_real(err, chan->keyframes.data[0].target_weight, 1.0);
ufbx_blend_shape *shape = chan->keyframes.data[0].shape;
ufbx_vec3 offsets[8] = { 0 };
ufbx_add_blend_shape_vertex_offsets(shape, offsets, 8, 1.0f);
for (size_t i = 0; i < 8; i++) {
ufbx_vec3 ref = ref_offsets[chan_ix][i];
ufbx_vec3 off_a = offsets[i];
ufbx_vec3 off_b = ufbx_get_blend_shape_vertex_offset(shape, i);
ufbxt_assert_close_vec3(err, ref, off_a);
ufbxt_assert_close_vec3(err, ref, off_b);
}
for (size_t key_ix = 0; key_ix < ufbxt_arraycount(keyframes); key_ix++) {
double *frame = keyframes[key_ix];
double time = frame[0];
ufbx_real ref = (ufbx_real)frame[1 + chan_ix];
ufbx_real weight = ufbx_evaluate_blend_weight(&scene->anim, chan, time);
ufbxt_assert_close_real(err, weight, ref);
ufbx_prop prop = ufbx_evaluate_prop(&scene->anim, &chan->element, "DeformPercent", time);
ufbxt_assert_close_real(err, prop.value_real / 100.0f, ref);
}
}
{
ufbx_vec3 offsets[8] = { 0 };
ufbx_add_blend_vertex_offsets(deformer, offsets, 8, 1.0f);
for (size_t i = 0; i < 8; i++) {
ufbx_vec3 ref = ufbxt_add3(ref_offsets[0][i], ref_offsets[1][i]);
ufbxt_assert_close_vec3(err, ref, offsets[i]);
}
}
for (int eval_skin = 0; eval_skin <= 1; eval_skin++) {
for (size_t key_ix = 0; key_ix < ufbxt_arraycount(keyframes); key_ix++) {
double *frame = keyframes[key_ix];
double time = frame[0];
ufbx_evaluate_opts opts = { 0 };
opts.evaluate_skinning = eval_skin != 0;
ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, &opts, NULL);
ufbxt_assert(state);
ufbx_real weights[2];
for (size_t chan_ix = 0; chan_ix < 2; chan_ix++) {
ufbx_real ref = (ufbx_real)frame[1 + chan_ix];
ufbx_blend_channel *chan = state->blend_channels.data[top[chan_ix]->typed_id];
ufbxt_assert(chan);
ufbx_prop prop = ufbx_evaluate_prop(&scene->anim, &chan->element, "DeformPercent", time);
ufbxt_assert_close_real(err, prop.value_real / 100.0, ref);
ufbxt_assert_close_real(err, chan->weight, ref);
weights[chan_ix] = chan->weight;
}
if (eval_skin) {
ufbx_blend_deformer *eval_deformer = state->blend_deformers.data[deformer->typed_id];
ufbx_mesh *eval_mesh = state->meshes.data[mesh->typed_id];
for (size_t i = 0; i < 8; i++) {
ufbx_vec3 original_pos = eval_mesh->vertex_position.values.data[i];
ufbx_vec3 skinned_pos = eval_mesh->skinned_position.values.data[i];
ufbx_vec3 ref = original_pos;
ufbx_vec3 blend_pos = ufbx_get_blend_vertex_offset(eval_deformer, i);
ref = ufbxt_add3(ref, ufbxt_mul3(ref_offsets[0][i], weights[0]));
ref = ufbxt_add3(ref, ufbxt_mul3(ref_offsets[1][i], weights[1]));
blend_pos = ufbxt_add3(original_pos, blend_pos);
ufbxt_assert_close_vec3(err, ref, skinned_pos);
ufbxt_assert_close_vec3(err, ref, blend_pos);
}
}
ufbxt_check_scene(state);
ufbx_free_scene(state);
}
}
}
#endif
UFBXT_FILE_TEST(maya_blend_inbetween)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_1", NULL, 1.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_30", NULL, 30.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_60", NULL, 60.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_65", NULL, 65.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_71", NULL, 71.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_80", NULL, 80.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_89", NULL, 89.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_blend_inbetween_120", NULL, 120.0/24.0);
}
#endif
UFBXT_FILE_TEST(synthetic_blend_shape_order)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pPlatonic1");
ufbxt_assert(node);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh);
static const ufbx_vec3 ref[] = {
{ -0.041956f, -0.004164f, -1.064113f },
{ 0.736301f, 0.734684f, -0.451944f },
{ -0.263699f, 1.059604f, -0.451944f },
{ -0.951595f, -0.168521f, -0.372847f },
{ -0.333561f, -1.019172f, -0.372847f },
{ 1.004034f, -0.525731f, -0.447214f },
{ 1.019802f, -0.010427f, 0.466597f },
{ 0.289087f, 1.059604f, 0.442483f },
{ -0.816271f, 0.564766f, 0.459767f },
{ -0.870921f, -0.699814f, 0.400384f },
{ 0.514865f, -0.854815f, 0.383101f },
{ 0.238471f, -0.004164f, 0.935887f },
};
ufbx_blend_shape *shape = (ufbx_blend_shape*)ufbx_find_element(scene, UFBX_ELEMENT_BLEND_SHAPE, "Target");
ufbxt_assert(shape);
ufbx_vec3 pos[12];
memcpy(pos, mesh->vertices.data, sizeof(pos));
ufbx_add_blend_shape_vertex_offsets(shape, pos, 12, 0.5f);
for (size_t i = 0; i < mesh->num_vertices; i++) {
ufbx_vec3 vert = mesh->vertices.data[i];
ufbx_vec3 off = ufbx_get_blend_shape_vertex_offset(shape, i);
ufbx_vec3 res = { vert.x+off.x*0.5f, vert.y+off.y*0.5f, vert.z+off.z*0.5f };
ufbxt_assert_close_vec3(err, res, ref[i]);
ufbxt_assert_close_vec3(err, pos[i], ref[i]);
}
}
#endif
UFBXT_FILE_TEST(blender_293_half_skinned)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh);
ufbxt_assert(mesh->num_vertices == 4);
ufbxt_assert(mesh->skin_deformers.count == 1);
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
ufbxt_assert(skin->vertices.count == 4);
ufbxt_assert(skin->vertices.data[0].num_weights == 1);
ufbxt_assert(skin->vertices.data[0].weight_begin == 0);
ufbxt_assert(skin->vertices.data[1].num_weights == 1);
ufbxt_assert(skin->vertices.data[1].weight_begin == 1);
ufbxt_assert(skin->vertices.data[2].num_weights == 0);
ufbxt_assert(skin->vertices.data[3].num_weights == 0);
}
#endif
UFBXT_FILE_TEST(maya_dual_quaternion)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
ufbxt_assert(skin->skinning_method == UFBX_SKINNING_METHOD_DUAL_QUATERNION);
}
#endif
UFBXT_FILE_TEST(maya_dual_quaternion_scale)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
ufbxt_assert(skin->skinning_method == UFBX_SKINNING_METHOD_DUAL_QUATERNION);
}
#endif
UFBXT_FILE_TEST(maya_dq_weights)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "maya_dq_weights_10", NULL, 10.0/24.0);
ufbxt_check_frame(scene, err, false, "maya_dq_weights_18", NULL, 18.0/24.0);
}
#endif
UFBXT_FILE_TEST(maya_bone_radius)
#if UFBXT_IMPL
{
for (size_t i = 1; i < scene->nodes.count; i++) {
ufbx_node *node = scene->nodes.data[i];
ufbxt_assert(strstr(node->name.data, "joint"));
ufbxt_assert(node->bone);
ufbxt_assert_close_real(err, node->bone->radius, (ufbx_real)i * 0.2f);
ufbxt_assert_close_real(err, node->bone->relative_length, 1.0f);
}
}
#endif
UFBXT_FILE_TEST(blender_279_bone_radius)
#if UFBXT_IMPL
{
ufbx_node *armature = ufbx_find_node(scene, "Armature");
ufbx_node *node = armature;
for (size_t i = 0; node->children.count > 0; i++) {
node = node->children.data[0];
ufbxt_assert(strstr(node->name.data, "Bone"));
ufbxt_assert(node->bone);
// Blender end bones have some weird scaling factor
// TODO: Add quirks mode for this?
if (strstr(node->name.data, "end")) continue;
if (scene->metadata.ascii) {
ufbxt_assert_close_real(err, node->bone->radius, 1.0f);
} else {
ufbxt_assert_close_real(err, node->bone->radius, (ufbx_real)(i + 1) * 0.1f);
}
ufbxt_assert_close_real(err, node->bone->relative_length, 1.0f);
}
}
#endif
UFBXT_FILE_TEST_FLAGS(synthetic_broken_cluster, UFBXT_FILE_TEST_FLAG_ALLOW_STRICT_ERROR)
#if UFBXT_IMPL
{
ufbx_node *cube = ufbx_find_node(scene, "pCube1");
ufbxt_assert(cube && cube->mesh);
ufbx_mesh *mesh = cube->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
ufbxt_assert(skin->clusters.count == 2);
ufbxt_assert(!strcmp(skin->clusters.data[0]->bone_node->name.data, "joint3"));
ufbxt_assert(!strcmp(skin->clusters.data[1]->bone_node->name.data, "joint1"));
}
#endif
UFBXT_TEST(synthetic_broken_cluster_connect)
#if UFBXT_IMPL
{
char path[512];
ufbxt_file_iterator iter = { "synthetic_broken_cluster" };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
ufbx_load_opts opts = { 0 };
opts.connect_broken_elements = true;
ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
ufbxt_assert(scene);
ufbx_node *cube = ufbx_find_node(scene, "pCube1");
ufbxt_assert(cube && cube->mesh);
ufbx_mesh *mesh = cube->mesh;
ufbxt_assert(mesh->skin_deformers.count == 1);
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
ufbxt_assert(skin->clusters.count == 3);
ufbxt_assert(!strcmp(skin->clusters.data[0]->bone_node->name.data, "joint3"));
ufbxt_assert(skin->clusters.data[1]->bone_node == NULL);
ufbxt_assert(!strcmp(skin->clusters.data[2]->bone_node->name.data, "joint1"));
// HACK: Patch the bone of `clusters.data[1]` to be valid for `ufbxt_check_scene()`...
skin->clusters.data[1]->bone_node = skin->clusters.data[0]->bone_node;
ufbxt_check_scene(scene);
ufbx_free_scene(scene);
}
}
#endif
UFBXT_TEST(synthetic_broken_cluster_safe)
#if UFBXT_IMPL
{
char path[512];
// Same test as above but check that the scene is valid if we don't
// pass `connect_broken_elements`.
ufbxt_file_iterator iter = { "synthetic_broken_cluster" };
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_free_scene(scene);
}
}
#endif
UFBXT_FILE_TEST(max_transformed_skin)
#if UFBXT_IMPL
{
ufbxt_check_frame(scene, err, false, "max_transformed_skin_5", NULL, 5.0/30.0);
ufbxt_check_frame(scene, err, false, "max_transformed_skin_15", NULL, 15.0/30.0);
}
#endif
UFBXT_FILE_TEST(synthetic_bind_to_root)
#if UFBXT_IMPL
{
ufbxt_check_warning(scene, UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, SIZE_MAX, NULL);
// Some unknown exporter is exporting skin deformers being parented to root
// This test exists to check that it is handled gracefully if quirks are enabled
}
#endif

View File

@@ -0,0 +1,302 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "topology"
#if UFBXT_IMPL
void ufbxt_check_generated_normals(ufbx_mesh *mesh, ufbxt_diff_error *err, size_t expected_normals)
{
ufbx_topo_edge *topo = calloc(mesh->num_indices, sizeof(ufbx_topo_edge));
ufbxt_assert(topo);
ufbx_compute_topology(mesh, topo, mesh->num_indices);
uint32_t *normal_indices = calloc(mesh->num_indices, sizeof(uint32_t));
size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, mesh->num_indices, normal_indices, mesh->num_indices, false);
if (expected_normals > 0) {
ufbxt_assert(num_normals == expected_normals);
}
ufbx_vec3 *normals = calloc(num_normals, sizeof(ufbx_vec3));
ufbx_compute_normals(mesh, &mesh->vertex_position, normal_indices, mesh->num_indices, normals, num_normals);
ufbx_vertex_vec3 new_normals = { 0 };
new_normals.exists = true;
new_normals.values.data = normals;
new_normals.values.count = num_normals;
new_normals.indices.data = normal_indices;
new_normals.indices.count = mesh->num_indices;
for (size_t i = 0; i < mesh->num_indices; i++) {
ufbx_vec3 fn = ufbx_get_vertex_vec3(&mesh->vertex_normal, i);
ufbx_vec3 rn = ufbx_get_vertex_vec3(&new_normals, i);
ufbxt_assert_close_vec3(err, fn, rn);
}
free(normals);
free(normal_indices);
free(topo);
}
#endif
UFBXT_FILE_TEST(maya_edge_smoothing)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_check_generated_normals(mesh, err, 16);
}
#endif
UFBXT_FILE_TEST(maya_no_smoothing)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_check_generated_normals(mesh, err, 16);
}
#endif
UFBXT_FILE_TEST(maya_planar_ngon)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pDisc1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_check_generated_normals(mesh, err, 0);
}
#endif
UFBXT_FILE_TEST(maya_subsurf_cube)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(maya_subsurf_plane)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(maya_subsurf_cube_crease)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_293_suzanne_subsurf)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST_ALT(subsurf_alloc_fail, maya_subsurf_cube)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
for (size_t max_temp = 1; max_temp < 10000; max_temp++) {
ufbx_subdivide_opts opts = { 0 };
opts.temp_allocator.huge_threshold = 1;
opts.temp_allocator.allocation_limit = max_temp;
ufbxt_hintf("Temp limit: %zu", max_temp);
ufbx_error error;
ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, &opts, &error);
if (sub_mesh) {
ufbxt_logf(".. Tested up to %zu temporary allocations", max_temp);
ufbx_free_mesh(sub_mesh);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
for (size_t max_result = 1; max_result < 10000; max_result++) {
ufbx_subdivide_opts opts = { 0 };
opts.result_allocator.huge_threshold = 1;
opts.result_allocator.allocation_limit = max_result;
ufbxt_hintf("Result limit: %zu", max_result);
ufbx_error error;
ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, &opts, &error);
if (sub_mesh) {
ufbxt_logf(".. Tested up to %zu result allocations", max_result);
ufbx_free_mesh(sub_mesh);
break;
}
ufbxt_assert(error.type == UFBX_ERROR_ALLOCATION_LIMIT);
}
}
#endif
UFBXT_FILE_TEST(blender_293_suzanne_subsurf_uv)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_293x_nonmanifold_subsurf)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_293_ngon_subsurf)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_293x_subsurf_boundary)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_293x_subsurf_max_crease)
#if UFBXT_IMPL
{
ufbx_mesh *mesh = (ufbx_mesh*)ufbx_find_element(scene, UFBX_ELEMENT_MESH, "Plane");
ufbx_mesh *subdivided = ufbx_subdivide_mesh(mesh, 1, NULL, NULL);
for (size_t i = 0; i < mesh->num_edges; i++) {
ufbxt_assert_close_real(err, mesh->edge_crease.data[i], 1.0f);
}
size_t num_edge = 0;
size_t num_center = 0;
for (size_t i = 0; i < subdivided->num_edges; i++) {
ufbx_edge edge = subdivided->edges.data[i];
ufbx_vec3 a = ufbx_get_vertex_vec3(&subdivided->vertex_position, edge.a);
ufbx_vec3 b = ufbx_get_vertex_vec3(&subdivided->vertex_position, edge.b);
ufbx_real a_len = a.x*a.x + a.y*a.y + a.z*a.z;
ufbx_real b_len = b.x*b.x + b.y*b.y + b.z*b.z;
if (a_len < 0.01f || b_len < 0.01f) {
ufbxt_assert_close_real(err, subdivided->edge_crease.data[i], 0.0f);
num_center++;
} else {
ufbxt_assert_close_real(err, subdivided->edge_crease.data[i], 1.0f);
num_edge++;
}
}
ufbxt_assert(num_edge == 8);
ufbxt_assert(num_center == 4);
ufbx_free_mesh(subdivided);
}
#endif
UFBXT_FILE_TEST(maya_subsurf_max_crease)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *subdivided = ufbx_subdivide_mesh(node->mesh, 1, NULL, NULL);
ufbxt_assert(subdivided);
size_t num_top = 0;
size_t num_bottom = 0;
for (size_t i = 0; i < subdivided->num_edges; i++) {
ufbx_edge edge = subdivided->edges.data[i];
ufbx_vec3 a = ufbx_get_vertex_vec3(&subdivided->vertex_position, edge.a);
ufbx_vec3 b = ufbx_get_vertex_vec3(&subdivided->vertex_position, edge.b);
ufbx_real a_len = a.x*a.x + a.z*a.z;
ufbx_real b_len = b.x*b.x + b.z*b.z;
if (a.y < -0.49f && b.y < -0.49f && a_len > 0.01f && b_len > 0.01f) {
ufbxt_assert_close_real(err, subdivided->edge_crease.data[i], 0.8f);
num_bottom++;
} else if (a.y > +0.49f && b.y > +0.49f && a_len > 0.01f && b_len > 0.01f) {
ufbxt_assert_close_real(err, subdivided->edge_crease.data[i], 1.0f);
num_top++;
} else {
ufbxt_assert_close_real(err, subdivided->edge_crease.data[i], 0.0f);
}
a = a;
}
ufbxt_assert(num_top == 8);
ufbxt_assert(num_bottom == 8);
ufbx_free_mesh(subdivided);
}
#endif
UFBXT_FILE_TEST(maya_subsurf_3x_cube)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(maya_subsurf_3x_cube_crease)
#if UFBXT_IMPL
{
}
#endif
#if UFBXT_IMPL
typedef struct {
ufbx_vec3 position;
ufbx_vec3 normal;
} ufbxt_vertex_pn;
#endif
UFBXT_FILE_TEST(blender_293_half_smooth_cube)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Cube");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_indices == 6*4);
ufbxt_assert(mesh->num_triangles == 6*2);
ufbxt_vertex_pn vertices[36];
uint32_t indices[36];
size_t num_indices = 0;
uint32_t tri[64];
for (size_t fi = 0; fi < mesh->num_faces; fi++) {
size_t num_tris = ufbx_triangulate_face(tri, 64, mesh, mesh->faces.data[fi]);
for (size_t ti = 0; ti < num_tris * 3; ti++) {
vertices[num_indices].position = ufbx_get_vertex_vec3(&mesh->vertex_position, tri[ti]);
vertices[num_indices].normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, tri[ti]);
num_indices++;
}
}
ufbx_vertex_stream stream = { vertices, sizeof(ufbxt_vertex_pn) };
size_t num_vertices = ufbx_generate_indices(&stream, 1, indices, num_indices, NULL, NULL);
ufbxt_assert(num_vertices == 12);
}
#endif
UFBXT_FILE_TEST(maya_vertex_crease_single)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(maya_vertex_crease)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST(blender_312x_vertex_crease)
#if UFBXT_IMPL
{
}
#endif

View File

@@ -0,0 +1,385 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "transform"
UFBXT_FILE_TEST(maya_pivots)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node);
ufbx_vec3 origin_ref = { 0.7211236250, 1.8317762500, -0.6038020000 };
ufbxt_assert_close_vec3(err, node->local_transform.translation, origin_ref);
}
#endif
#if UFBXT_IMPL
static void ufbxt_check_rotation_order(ufbx_scene *scene, const char *name, ufbx_rotation_order order)
{
ufbx_node *node = ufbx_find_node(scene, name);
ufbxt_assert(node);
ufbx_prop *prop = ufbx_find_prop(&node->props, "RotationOrder");
ufbxt_assert(prop);
ufbxt_assert((ufbx_rotation_order)prop->value_int == order);
}
#endif
UFBXT_FILE_TEST(maya_rotation_order)
#if UFBXT_IMPL
{
ufbxt_check_rotation_order(scene, "XYZ", UFBX_ROTATION_ORDER_XYZ);
ufbxt_check_rotation_order(scene, "XZY", UFBX_ROTATION_ORDER_XZY);
ufbxt_check_rotation_order(scene, "YZX", UFBX_ROTATION_ORDER_YZX);
ufbxt_check_rotation_order(scene, "YXZ", UFBX_ROTATION_ORDER_YXZ);
ufbxt_check_rotation_order(scene, "ZXY", UFBX_ROTATION_ORDER_ZXY);
ufbxt_check_rotation_order(scene, "ZYX", UFBX_ROTATION_ORDER_ZYX);
}
#endif
UFBXT_FILE_TEST(maya_post_rotate_order)
#if UFBXT_IMPL
{
ufbxt_check_rotation_order(scene, "pCube1", UFBX_ROTATION_ORDER_XYZ);
ufbxt_check_rotation_order(scene, "pCube2", UFBX_ROTATION_ORDER_ZYX);
}
#endif
UFBXT_FILE_TEST(synthetic_pre_post_rotate)
#if UFBXT_IMPL
{
ufbxt_check_rotation_order(scene, "pCube1", UFBX_ROTATION_ORDER_XYZ);
ufbxt_check_rotation_order(scene, "pCube2", UFBX_ROTATION_ORDER_ZYX);
}
#endif
UFBXT_FILE_TEST(maya_parented_cubes)
#if UFBXT_IMPL
{
}
#endif
UFBXT_FILE_TEST_FLAGS(synthetic_geometric_squish, UFBXT_FILE_TEST_FLAG_OPT_HANDLING_IGNORE_NORMALS_IN_DIFF)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pSphere1");
ufbxt_assert(node);
ufbxt_assert_close_real(err, node->geometry_transform.scale.y, 0.01f);
}
#endif
UFBXT_FILE_TEST_FLAGS(synthetic_geometric_transform, UFBXT_FILE_TEST_FLAG_OPT_HANDLING_IGNORE_NORMALS_IN_DIFF)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Parent");
ufbxt_assert(node);
ufbx_vec3 euler = ufbx_quat_to_euler(node->geometry_transform.rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxt_assert_close_real(err, node->geometry_transform.translation.x, -0.5f);
ufbxt_assert_close_real(err, node->geometry_transform.translation.y, -1.0f);
ufbxt_assert_close_real(err, node->geometry_transform.translation.z, -1.5f);
ufbxt_assert_close_real(err, euler.x, 20.0f);
ufbxt_assert_close_real(err, euler.y, 40.0f);
ufbxt_assert_close_real(err, euler.z, 60.0f);
ufbxt_assert_close_real(err, node->geometry_transform.scale.x, 2.0f);
ufbxt_assert_close_real(err, node->geometry_transform.scale.y, 3.0f);
ufbxt_assert_close_real(err, node->geometry_transform.scale.z, 4.0f);
}
#endif
UFBXT_FILE_TEST(maya_cube_hidden)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCube1");
ufbxt_assert(node);
ufbxt_assert(!node->visible);
}
#endif
UFBXT_TEST(root_transform)
#if UFBXT_IMPL
{
ufbxt_diff_error err = { 0 };
char path[512];
ufbxt_file_iterator iter = { "maya_cube" };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
ufbx_load_opts opts = { 0 };
ufbx_vec3 euler = { { 90.0f, 0.0f, 0.0f } };
opts.use_root_transform = true;
opts.root_transform.translation.x = -1.0f;
opts.root_transform.translation.y = -2.0f;
opts.root_transform.translation.z = -3.0f;
opts.root_transform.rotation = ufbx_euler_to_quat(euler, UFBX_ROTATION_ORDER_XYZ);
opts.root_transform.scale.x = 2.0f;
opts.root_transform.scale.y = 3.0f;
opts.root_transform.scale.z = 4.0f;
ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
ufbxt_assert_close_vec3(&err, scene->root_node->local_transform.translation, opts.root_transform.translation);
ufbxt_assert_close_quat(&err, scene->root_node->local_transform.rotation, opts.root_transform.rotation);
ufbxt_assert_close_vec3(&err, scene->root_node->local_transform.scale, opts.root_transform.scale);
ufbx_transform eval = ufbx_evaluate_transform(&scene->anim, scene->root_node, 0.1);
ufbxt_assert_close_vec3(&err, eval.translation, opts.root_transform.translation);
ufbxt_assert_close_quat(&err, eval.rotation, opts.root_transform.rotation);
ufbxt_assert_close_vec3(&err, eval.scale, opts.root_transform.scale);
ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, 0.1, NULL, NULL);
ufbxt_assert(state);
ufbxt_assert_close_vec3(&err, state->root_node->local_transform.translation, opts.root_transform.translation);
ufbxt_assert_close_quat(&err, state->root_node->local_transform.rotation, opts.root_transform.rotation);
ufbxt_assert_close_vec3(&err, state->root_node->local_transform.scale, opts.root_transform.scale);
ufbx_free_scene(state);
ufbx_free_scene(scene);
}
ufbxt_assert(iter.num_found >= 8);
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif
UFBXT_TEST(blender_axes)
#if UFBXT_IMPL
{
char path[512], name[512];
static const char *axis_names[] = {
"px", "nx", "py", "ny", "pz", "nz",
};
for (uint32_t fwd_ix = 0; fwd_ix < 6; fwd_ix++) {
for (uint32_t up_ix = 0; up_ix < 6; up_ix++) {
// Don't allow collinear axes
if ((fwd_ix >> 1) == (up_ix >> 1)) continue;
ufbx_coordinate_axis axis_fwd = (ufbx_coordinate_axis)fwd_ix;
ufbx_coordinate_axis axis_up = (ufbx_coordinate_axis)up_ix;
snprintf(name, sizeof(name), "blender_axes/axes_%s%s", axis_names[fwd_ix], axis_names[up_ix]);
ufbxt_file_iterator iter = { name };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
ufbxt_diff_error err = { 0 };
// Load normally and check axes
{
ufbx_scene *scene = ufbx_load_file(path, NULL, NULL);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
ufbx_coordinate_axis axis_front = axis_fwd ^ 1;
ufbxt_assert(scene->settings.axes.front == axis_front);
ufbxt_assert(scene->settings.axes.up == axis_up);
ufbx_free_scene(scene);
}
// Axis conversion
for (int mode = 0; mode < 4; mode++) {
ufbxt_hintf("mode = %d", mode);
ufbx_load_opts opts = { 0 };
bool transform_root = (mode % 2) != 0;
bool use_adjust = (mode / 2) != 0;
opts.target_axes = ufbx_axes_right_handed_z_up;
opts.target_unit_meters = 1.0f;
if (transform_root) {
opts.use_root_transform = true;
opts.root_transform = ufbx_identity_transform;
opts.root_transform.translation.z = 1.0f;
}
if (use_adjust) {
opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
} else {
opts.space_conversion = UFBX_SPACE_CONVERSION_TRANSFORM_ROOT;
}
ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
ufbx_node *plane = ufbx_find_node(scene, "Plane");
ufbxt_assert(plane && plane->mesh);
ufbx_mesh *mesh = plane->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
ufbxt_assert(face.num_indices == 3);
if (use_adjust && !transform_root) {
ufbx_node *root = scene->root_node;
ufbx_vec3 identity_scale = { 1.0f, 1.0f, 1.0f };
ufbxt_assert_close_quat(&err, root->local_transform.rotation, ufbx_identity_quat);
ufbxt_assert_close_vec3(&err, root->local_transform.scale, identity_scale);
}
for (uint32_t i = 0; i < face.num_indices; i++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i);
ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, face.index_begin + i);
pos = ufbx_transform_position(&plane->geometry_to_world, pos);
ufbx_vec3 ref;
if (uv.x < 0.5f && uv.y < 0.5f) {
ref.x = 1.0f;
ref.y = 1.0f;
ref.z = 3.0f;
} else if (uv.x > 0.5f && uv.y < 0.5f) {
ref.x = 2.0f;
ref.y = 2.0f;
ref.z = 3.0f;
} else if (uv.x < 0.5f && uv.y > 0.5f) {
ref.x = 1.0f;
ref.y = 2.0f;
ref.z = 4.0f;
} else {
ufbxt_assert(0 && "Shouldn't exist");
}
if (transform_root) {
ref.z += 1.0f;
}
ufbxt_assert_close_vec3(&err, pos, ref);
}
ufbx_free_scene(scene);
}
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
}
}
}
#endif
#if UFBXT_IMPL
static ufbx_load_opts ufbxt_scale_to_cm_opts()
{
ufbx_load_opts opts = { 0 };
opts.target_unit_meters = 0.01f;
return opts;
}
#endif
UFBXT_FILE_TEST_OPTS(maya_scale_no_inherit, ufbxt_scale_to_cm_opts)
#if UFBXT_IMPL
{
{
ufbx_node *node = ufbx_find_node(scene, "joint1");
ufbxt_assert(node);
ufbxt_assert(node->inherit_type == UFBX_INHERIT_NORMAL);
ufbxt_assert_close_real(err, node->local_transform.scale.x, 0.02f);
ufbxt_assert_close_real(err, node->local_transform.scale.y, 0.03f);
ufbxt_assert_close_real(err, node->local_transform.scale.z, 0.04f);
ufbxt_assert_close_real(err, node->world_transform.scale.x, 2.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.y, 3.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.z, 4.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "joint2");
ufbxt_assert(node);
ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
ufbxt_assert_close_real(err, node->local_transform.scale.x, 100.0f);
ufbxt_assert_close_real(err, node->local_transform.scale.y, 100.0f);
ufbxt_assert_close_real(err, node->local_transform.scale.z, 100.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.x, 100.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.y, 100.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.z, 100.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "joint3");
ufbxt_assert(node);
ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
ufbxt_assert_close_real(err, node->local_transform.scale.x, 1.0f);
ufbxt_assert_close_real(err, node->local_transform.scale.y, 1.0f);
ufbxt_assert_close_real(err, node->local_transform.scale.z, 1.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.x, 1.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.y, 1.0f);
ufbxt_assert_close_real(err, node->world_transform.scale.z, 1.0f);
}
{
ufbx_node *node = ufbx_find_node(scene, "joint4");
ufbxt_assert(node);
ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
ufbxt_assert_close_real(err, node->local_transform.scale.x, 1.5f);
ufbxt_assert_close_real(err, node->local_transform.scale.y, 2.5f);
ufbxt_assert_close_real(err, node->local_transform.scale.z, 3.5f);
ufbxt_assert_close_real(err, node->world_transform.scale.x, 1.5f);
ufbxt_assert_close_real(err, node->world_transform.scale.y, 2.5f);
ufbxt_assert_close_real(err, node->world_transform.scale.z, 3.5f);
}
{
ufbx_node *node = ufbx_find_node(scene, "joint3");
{
ufbx_transform transform = ufbx_evaluate_transform(&scene->anim, node, 1.0);
ufbxt_assert_close_real(err, transform.scale.x, 0.3f);
ufbxt_assert_close_real(err, transform.scale.y, 0.6f);
ufbxt_assert_close_real(err, transform.scale.z, 0.9f);
}
{
ufbx_transform transform = ufbx_evaluate_transform(&scene->anim, node, 0.5);
ufbxt_assert_close_real(err, transform.scale.x, 0.67281f);
ufbxt_assert_close_real(err, transform.scale.y, 0.81304f);
ufbxt_assert_close_real(err, transform.scale.z, 0.95326f);
}
}
}
#endif
UFBXT_FILE_TEST(synthetic_node_dag)
#if UFBXT_IMPL
{
ufbx_node *root = scene->root_node;
ufbx_node *a = ufbx_find_node(scene, "A");
ufbx_node *b = ufbx_find_node(scene, "B");
ufbx_node *c = ufbx_find_node(scene, "C");
ufbx_node *d = ufbx_find_node(scene, "D");
ufbxt_assert(root && a && b && c && d);
ufbxt_assert(root->children.count == 1);
ufbxt_assert(root->children.data[0] == a);
ufbxt_assert(a->parent == root);
ufbxt_assert(a->children.count == 1);
ufbxt_assert(a->children.data[0] == b);
ufbxt_assert(b->parent == a);
ufbxt_assert(b->children.count == 1);
ufbxt_assert(b->children.data[0] == c);
ufbxt_assert(c->parent == b);
ufbxt_assert(c->children.count == 1);
ufbxt_assert(c->children.data[0] == d);
ufbxt_assert(d->parent == c);
ufbxt_assert(d->children.count == 0);
}
#endif
UFBXT_FILE_TEST_FLAGS(synthetic_node_cycle_fail, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR)
#if UFBXT_IMPL
{
ufbxt_assert(!scene);
}
#endif

View File

@@ -0,0 +1,263 @@
#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "triangulate"
#if UFBXT_IMPL
size_t do_triangulate_test(ufbx_scene *scene)
{
size_t num_fail = 0;
for (size_t mesh_ix = 0; mesh_ix < scene->meshes.count; mesh_ix++) {
ufbx_mesh *mesh = scene->meshes.data[mesh_ix];
ufbxt_assert(mesh->instances.count == 1);
bool should_be_top_left = mesh->instances.data[0]->name.data[0] == 'A';
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
ufbxt_assert(face.index_begin == 0);
ufbxt_assert(face.num_indices == 4);
uint32_t tris[6];
bool ok = ufbx_triangulate_face(tris, 6, mesh, face);
ufbxt_assert(ok);
size_t top_left_ix = 0;
ufbx_real best_dot = HUGE_VALF;
for (size_t ix = 0; ix < 4; ix++) {
ufbx_vec3 v = ufbx_get_vertex_vec3(&mesh->vertex_position, ix);
ufbx_real dot = v.x + v.z;
if (dot < best_dot) {
top_left_ix = ix;
best_dot = dot;
}
}
uint32_t top_left_count = 0;
for (size_t i = 0; i < 6; i++) {
if (tris[i] == top_left_ix) top_left_count++;
}
if (should_be_top_left != (top_left_count == 2)) {
ufbxt_logf("Fail: %s", mesh->instances.data[0]->name.data);
num_fail++;
}
}
ufbxt_logf("Triangulations OK: %zu/%zu", scene->meshes.count - num_fail, scene->meshes.count);
return num_fail;
}
#endif
UFBXT_FILE_TEST(maya_triangulate)
#if UFBXT_IMPL
{
size_t num_fail = do_triangulate_test(scene);
ufbxt_assert(num_fail <= 4);
}
#endif
UFBXT_FILE_TEST(maya_triangulate_down)
#if UFBXT_IMPL
{
size_t num_fail = do_triangulate_test(scene);
ufbxt_assert(num_fail <= 1);
}
#endif
UFBXT_FILE_TEST(maya_tri_cone)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "pCone1");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
for (size_t i = 0; i < mesh->num_faces; i++) {
ufbx_face face = mesh->faces.data[i];
ufbxt_assert(face.num_indices >= 3 && face.num_indices <= 32);
uint32_t tris[32];
size_t num_tris = face.num_indices - 2;
for (size_t i = 0; i < 32; i++) {
ufbx_panic panic;
panic.did_panic = false;
size_t num_tris_returned = ufbx_catch_triangulate_face(&panic, tris, i, mesh, face);
if (i >= num_tris * 3) {
ufbxt_assert(!panic.did_panic);
ufbxt_assert(num_tris_returned == num_tris);
} else {
ufbxt_assert(panic.did_panic);
ufbxt_assert(num_tris_returned == 0);
}
}
ufbxt_assert(ufbx_triangulate_face(tris, ufbxt_arraycount(tris), mesh, face));
}
}
#endif
#if UFBXT_IMPL
static void ufbxt_ngon_write_obj(const char *path, ufbx_mesh *mesh, const uint32_t *indices, size_t num_triangles)
{
FILE *f = fopen(path, "w");
for (size_t i = 0; i < mesh->num_vertices; i++) {
ufbx_vec3 v = mesh->vertices.data[i];
fprintf(f, "v %f %f %f\n", v.x, v.y, v.z);
}
fprintf(f, "\n");
for (size_t i = 0; i < num_triangles; i++) {
const uint32_t *tri = indices + i * 3;
fprintf(f, "f %u %u %u\n",
mesh->vertex_indices.data[tri[0]] + 1,
mesh->vertex_indices.data[tri[1]] + 1,
mesh->vertex_indices.data[tri[2]] + 1);
}
fclose(f);
}
static ufbx_vec2 ufbxt_ngon_3d_to_2d(const ufbx_vec3 basis[2], ufbx_vec3 pos)
{
ufbx_vec2 uv;
uv.x = ufbxt_dot3(basis[0], pos);
uv.y = ufbxt_dot3(basis[1], pos);
return uv;
}
static void ufbxt_check_ngon_triangulation(ufbxt_diff_error *err, ufbx_mesh *mesh, ufbx_face face, uint32_t *indices, size_t num_triangles, const ufbx_vec3 basis[2])
{
// Check that the area matches for now
// TODO: More rigorous tests
ufbx_real poly_area = 0.0f;
for (size_t i = 0; i < face.num_indices; i++) {
ufbx_vec2 a = ufbxt_ngon_3d_to_2d(basis, ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i + 0));
ufbx_vec2 b = ufbxt_ngon_3d_to_2d(basis, ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + (i + 1) % face.num_indices));
poly_area += 0.5f * (a.x*b.y - a.y*b.x);
}
ufbx_real tri_area = 0.0f;
for (size_t i = 0; i < num_triangles; i++) {
ufbx_vec2 a = ufbxt_ngon_3d_to_2d(basis, ufbx_get_vertex_vec3(&mesh->vertex_position, indices[i*3 + 0]));
ufbx_vec2 b = ufbxt_ngon_3d_to_2d(basis, ufbx_get_vertex_vec3(&mesh->vertex_position, indices[i*3 + 1]));
ufbx_vec2 c = ufbxt_ngon_3d_to_2d(basis, ufbx_get_vertex_vec3(&mesh->vertex_position, indices[i*3 + 2]));
ufbx_real area = 0.5f * (a.x*(b.y-c.y) + b.x*(c.y-a.y) + c.x*(a.y-b.y));
ufbxt_assert(area >= -0.01f);
tri_area += area;
}
ufbxt_assert_close_real(err, poly_area, tri_area);
}
#endif
UFBXT_FILE_TEST(blender_300_ngon_intersection)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
uint32_t indices[3*3];
size_t num_tris = ufbx_triangulate_face(indices, ufbxt_arraycount(indices), mesh, face);
ufbxt_assert(num_tris == 3);
const ufbx_vec3 basis[2] = {
{ { 1.0f, 0.0f, 0.0f } },
{ { 0.0f, 1.0f, 0.0f } },
};
ufbxt_check_ngon_triangulation(err, mesh, face, indices, num_tris, basis);
}
#endif
UFBXT_FILE_TEST(blender_300_ngon_e)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
uint32_t indices[10*3];
size_t num_tris = ufbx_triangulate_face(indices, ufbxt_arraycount(indices), mesh, face);
ufbxt_assert(num_tris == 10);
const ufbx_vec3 basis[2] = {
{ { 1.0f, 0.0f, 0.0f } },
{ { 0.0f, 1.0f, 0.0f } },
};
ufbxt_check_ngon_triangulation(err, mesh, face, indices, num_tris, basis);
}
#endif
UFBXT_FILE_TEST(blender_300_ngon_abstract)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
uint32_t indices[144*3];
size_t num_tris = ufbx_triangulate_face(indices, ufbxt_arraycount(indices), mesh, face);
ufbxt_assert(num_tris == 144);
const ufbx_vec3 basis[2] = {
{ { 1.0f, 0.0f, 0.0f } },
{ { 0.0f, 1.0f, 0.0f } },
};
ufbxt_check_ngon_triangulation(err, mesh, face, indices, num_tris, basis);
}
#endif
UFBXT_FILE_TEST(blender_300_ngon_big)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbx_face face = mesh->faces.data[0];
uint32_t expected_tris = 8028;
uint32_t *indices = malloc(expected_tris * 3 * sizeof(uint32_t));
ufbxt_assert(indices);
size_t num_tris = ufbx_triangulate_face(indices, expected_tris * 3, mesh, face);
ufbxt_assert(num_tris == expected_tris);
const ufbx_vec3 basis[2] = {
{ { 1.0f, 0.0f, 0.0f } },
{ { 0.0f, 1.0f, 0.0f } },
};
ufbxt_check_ngon_triangulation(err, mesh, face, indices, num_tris, basis);
free(indices);
}
#endif
UFBXT_FILE_TEST(blender_300_ngon_irregular)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Plane");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
uint32_t indices[256];
for (size_t i = 0; i < mesh->num_faces; i++) {
ufbx_face face = mesh->faces.data[i];
size_t num_tris = ufbx_triangulate_face(indices, ufbxt_arraycount(indices), mesh, face);
ufbxt_assert(num_tris == face.num_indices - 2);
}
}
#endif

View File

@@ -0,0 +1,14 @@
#ifndef UFBXT_TESTING_BASICS_INCLUDED
#define UFBXT_TESTING_BASICS_INCLUDED
#define ufbxt_arraycount(arr) (sizeof(arr) / sizeof(*(arr)))
#if defined(_MSC_VER)
#define ufbxt_noinline __declspec(noinline)
#elif defined(__clang__) || defined(__GNUC__)
#define ufbxt_noinline __attribute__((noinline))
#else
#define ufbxt_noinline
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
#include "../ufbx.h"
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
#include <cstdlib>
#include <stdio.h>
std::atomic_int32_t atomic_count { 0 };
int32_t race_count = 0;
bool allocator_freed = false;
void free_allocator(void*)
{
allocator_freed = true;
}
void worker(ufbx_scene *scene)
{
std::this_thread::sleep_for(std::chrono::milliseconds{100});
for (uint32_t i = 0; i < 0x1000000; i++) {
ufbx_retain_scene(scene);
ufbx_free_scene(scene);
atomic_count.fetch_add(1, std::memory_order_relaxed);
race_count++;
}
}
int main(int argc, char **argv)
{
if (argc < 3) {
fprintf(stderr, "Usage: check_threads <file.fbx> <num-threads>\n");
return 1;
}
if (!ufbx_is_thread_safe()) {
fprintf(stderr, "WARNING: Not thread safe, expect failure!\n");
}
size_t num_threads = (size_t)atoi(argv[2]);
ufbx_load_opts opts = { };
opts.result_allocator.allocator.free_allocator_fn = &free_allocator;
ufbx_error error;
ufbx_scene *scene = ufbx_load_file(argv[1], &opts, &error);
if (!scene) {
fprintf(stderr, "Failed to load scene: %s\n", error.description.data);
return 1;
}
if (allocator_freed) {
fprintf(stderr, "Allocator prematurely freed\n");
return 1;
}
for (uint32_t run = 0; run < 8; run++) {
std::vector<std::thread> threads;
for (size_t i = 0; i < num_threads; i++) {
threads.push_back(std::thread(worker, scene));
}
for (std::thread &thread : threads) {
thread.join();
}
int32_t delta = std::abs(atomic_count.load(std::memory_order_relaxed) - race_count);
printf("Race delta: %d\n", delta);
if (delta >= 1000) break;
}
ufbx_free_scene(scene);
if (!allocator_freed) {
fprintf(stderr, "Scene not freed\n");
return 1;
}
return 0;
}

View File

@@ -0,0 +1,304 @@
#include "../ufbx.c"
#include "../ufbx.h"
#include <stdio.h>
#include <string.h>
void test_assert(bool cond)
{
if (!cond) {
exit(1);
}
}
typedef struct {
uint32_t a, b;
} uint_pair;
static size_t g_linear_size = 2;
void sort_uints(uint32_t *test_data, uint32_t *test_tmp, size_t test_size)
{
ufbxi_macro_stable_sort(uint32_t, g_linear_size, test_data, test_tmp, test_size, (*a < *b));
}
void sort_pairs_by_a(uint_pair *data, uint_pair *tmp, size_t size)
{
ufbxi_macro_stable_sort(uint_pair, g_linear_size, data, tmp, size, (a->a < b->a));
}
void sort_pairs_by_b(uint_pair *data, uint_pair *tmp, size_t size)
{
ufbxi_macro_stable_sort(uint_pair, g_linear_size, data, tmp, size, (a->b < b->b));
}
size_t find_uint(uint32_t *test_data, size_t test_size, uint32_t value)
{
size_t index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(uint32_t, g_linear_size, &index, test_data, 0, test_size,
(*a < value),
(*a == value));
return index;
}
size_t find_uint_end(uint32_t *test_data, size_t test_size, size_t test_begin, uint32_t value)
{
size_t index = SIZE_MAX;
ufbxi_macro_upper_bound_eq(uint32_t, g_linear_size, &index, test_data, test_begin, test_size, (*a == value));
return index;
}
size_t find_pair_by_a(uint_pair *data, size_t size, uint32_t value)
{
size_t pair_ix = SIZE_MAX;
ufbxi_macro_lower_bound_eq(uint_pair, g_linear_size, &pair_ix, data, 0, size,
(a->a < value),
(a->a == value));
return pair_ix;
}
void sort_strings(const char **data, const void *tmp, size_t size)
{
ufbxi_macro_stable_sort(const char*, g_linear_size, data, tmp, size, (strcmp(*a, *b) < 0));
}
size_t find_first_string(const char **data, size_t size, const char *str)
{
size_t str_index;
ufbxi_macro_lower_bound_eq(const char*, g_linear_size, &str_index, data, 0, size,
(strcmp(*a, str) < 0),
(strcmp(*a, str) == 0));
return str_index;
}
static uint32_t xorshift32(uint32_t *state)
{
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
uint32_t x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return *state = x;
}
static ufbx_real xorshift32_real(uint32_t *state)
{
uint32_t u = xorshift32(state);
return (ufbx_real)u * (ufbx_real)2.3283064365386962890625e-10;
}
void generate_linear(uint32_t *dst, size_t size, uint32_t start, uint32_t delta)
{
for (size_t i = 0; i < size; i++) {
dst[i] = start;
start += delta;
}
}
void generate_random(uint32_t *dst, size_t size, uint32_t seed, uint32_t mod)
{
uint32_t state = seed | 1;
for (size_t i = 0; i < size; i++) {
dst[i] = xorshift32(&state) % mod;
}
}
#define MAX_SORT_SIZE 2048
ufbx_real quat_error(ufbx_quat a, ufbx_quat b)
{
double pos = fabs(a.x-b.x) + fabs(a.y-b.y) + fabs(a.z-b.z) + fabs(a.w-b.w);
double neg = fabs(a.x+b.x) + fabs(a.y+b.y) + fabs(a.z+b.z) + fabs(a.w+b.w);
return pos < neg ? pos : neg;
}
void test_quats()
{
uint32_t state = 1;
for (int iorder = 0; iorder < 6; iorder++) {
ufbx_rotation_order order = (ufbx_rotation_order)iorder;
printf("quat_to_euler %d/6\n", iorder+1);
for (int axis = 0; axis < 3; axis++) {
for (size_t i = 1; i <= 360; i++) {
ufbx_vec3 v = { 0.0f, 0.0f, 0.0f };
v.v[axis] = (ufbx_real)i;
ufbx_quat q = ufbx_euler_to_quat(v, order);
ufbx_vec3 v2 = ufbx_quat_to_euler(q, order);
ufbx_quat q2 = ufbx_euler_to_quat(v2, order);
test_assert(quat_error(q, q2) < 0.001f);
test_assert(quat_error(q, q2) < 0.001f);
}
}
for (int x = -360; x <= 360; x += 10)
for (int y = -360; y <= 360; y += 10)
for (int z = -360; z <= 360; z += 10) {
ufbx_vec3 v = { (ufbx_real)x, (ufbx_real)y, (ufbx_real)z };
ufbx_quat q = ufbx_euler_to_quat(v, order);
ufbx_vec3 v2 = ufbx_quat_to_euler(q, order);
ufbx_quat q2 = ufbx_euler_to_quat(v2, order);
test_assert(quat_error(q, q2) < 0.1f);
test_assert(quat_error(q, q2) < 0.1f);
}
for (size_t i = 0; i < 1000000; i++) {
ufbx_quat q;
q.x = xorshift32_real(&state) * 2.0f - 1.0f;
q.y = xorshift32_real(&state) * 2.0f - 1.0f;
q.z = xorshift32_real(&state) * 2.0f - 1.0f;
q.w = xorshift32_real(&state) * 2.0f - 1.0f;
ufbx_real qm = (ufbx_real)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
q.x /= qm;
q.y /= qm;
q.z /= qm;
q.w /= qm;
ufbx_vec3 v = ufbx_quat_to_euler(q, order);
ufbx_quat q2 = ufbx_euler_to_quat(v, order);
test_assert(
fabs(q.x-q2.x) + fabs(q.y-q2.y) + fabs(q.z-q2.z) + fabs(q.w-q2.w) < 0.001f ||
fabs(q.x+q2.x) + fabs(q.y+q2.y) + fabs(q.z+q2.z) + fabs(q.w+q2.w) < 0.001f );
test_assert(true);
}
}
}
void test_sort(uint32_t *data, size_t size)
{
assert(size <= MAX_SORT_SIZE);
static size_t call_count = 0;
static uint32_t uint_tmp_buffer[MAX_SORT_SIZE];
static uint_pair pair_buffer[MAX_SORT_SIZE];
static uint_pair pair_tmp_buffer[MAX_SORT_SIZE];
call_count++;
for (size_t i = 0; i < size; i++) {
pair_buffer[i].a = data[i];
pair_buffer[i].b = (uint32_t)i;
}
sort_uints(data, uint_tmp_buffer, size);
sort_pairs_by_a(pair_buffer, pair_tmp_buffer, size);
for (size_t i = 1; i < size; i++) {
test_assert(data[i - 1] <= data[i]);
test_assert(pair_buffer[i - 1].a <= pair_buffer[i].a);
if (pair_buffer[i - 1].a == pair_buffer[i].a) {
test_assert(pair_buffer[i - 1].b < pair_buffer[i].b);
}
}
for (size_t i = 0; i < size; i++) {
uint32_t value = data[i];
size_t index = find_uint(data, size, value);
test_assert(index <= i);
test_assert(data[index] == value);
test_assert(index == find_pair_by_a(pair_buffer, size, value));
if (index > 0) {
test_assert(data[index - 1] < value);
}
size_t end = find_uint_end(data, size, index, value);
test_assert(end > i);
test_assert(data[end - 1] == value);
if (end < size) {
test_assert(data[end] > value);
}
}
test_assert(find_uint(data, size, UINT32_MAX) == SIZE_MAX);
sort_pairs_by_b(pair_buffer, pair_tmp_buffer, size);
for (size_t i = 0; i < size; i++) {
test_assert(pair_buffer[i].b == (uint32_t)i);
}
}
void test_sort_strings(uint32_t *data, size_t size)
{
assert(size <= MAX_SORT_SIZE);
static size_t call_count = 0;
static const char *str_buffer[MAX_SORT_SIZE];
static const char *str_tmp_buffer[MAX_SORT_SIZE];
static char str_data_buffer[MAX_SORT_SIZE * 32];
char *data_ptr = str_data_buffer, *data_end = data_ptr + sizeof(str_data_buffer);
for (size_t i = 0; i < size; i++) {
int len = snprintf(data_ptr, data_end - data_ptr, "%u", data[i]);
test_assert(len > 0);
str_buffer[i] = data_ptr;
data_ptr += len + 1;
}
sort_strings(str_buffer, str_tmp_buffer, size);
for (size_t i = 1; i < size; i++) {
test_assert(strcmp(str_buffer[i - 1], str_buffer[i]) <= 0);
}
char find_str[128];
for (size_t i = 0; i < size; i++) {
int len = snprintf(find_str, sizeof(find_str), "%u", data[i]);
test_assert(len > 0);
size_t index = find_first_string(str_buffer, size, find_str);
test_assert(index < size);
test_assert(strcmp(str_buffer[index], find_str) == 0);
if (index > 0) {
test_assert(strcmp(str_buffer[index - 1], find_str) < 0);
}
}
}
void test_sorts()
{
static uint32_t sort_buffer[MAX_SORT_SIZE];
while (g_linear_size <= 64) {
printf("%zu\n", g_linear_size);
for (size_t size = 0; size < MAX_SORT_SIZE; size += 1+ size/128 + size/512*32) {
generate_linear(sort_buffer, size, 0, +1);
test_sort(sort_buffer, size);
generate_linear(sort_buffer, size, (uint32_t)size, -1);
test_sort(sort_buffer, size);
generate_random(sort_buffer, size, (uint32_t)size, 1+size%10);
test_sort(sort_buffer, size);
generate_random(sort_buffer, size, (uint32_t)size, UINT32_MAX);
test_sort(sort_buffer, size);
}
{
size_t size = MAX_SORT_SIZE;
generate_linear(sort_buffer, size, 0, +1);
test_sort_strings(sort_buffer, size);
generate_linear(sort_buffer, size, (uint32_t)size, -1);
test_sort_strings(sort_buffer, size);
generate_random(sort_buffer, size, (uint32_t)size, 1+size%10);
test_sort_strings(sort_buffer, size);
generate_random(sort_buffer, size, (uint32_t)size, UINT32_MAX);
test_sort_strings(sort_buffer, size);
}
g_linear_size += 1+g_linear_size/8;
}
}
int main(int argc, char **argv)
{
test_sorts();
test_quats();
return 0;
}