Files
coven/modules/ufbx/test/test_obj.h

1532 lines
51 KiB
C

#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "obj"
UFBXT_FILE_TEST(zbrush_vertex_color)
#if UFBXT_IMPL
{
ufbxt_assert(scene->nodes.count == 2);
ufbx_node *node = scene->nodes.data[1];
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->vertex_color.exists);
ufbxt_assert(mesh->vertex_color.unique_per_vertex);
ufbxt_assert(mesh->num_vertices == 6);
for (size_t i = 0; i < mesh->num_vertices; i++) {
ufbx_vec3 pos = mesh->vertex_position.values.data[i];
ufbx_vec4 color = ufbx_get_vertex_vec4(&mesh->vertex_color, mesh->vertex_first_index.data[i]);
ufbx_vec4 ref = { 0.0f, 0.0f, 0.0f, 1.0f };
pos.y -= 1.0f;
if (pos.x < -0.5f) {
ref.x = 1.0f;
} else if (pos.x > 0.5f) {
ref.y = ref.z = 1.0f;
} else if (pos.y > 0.5f) {
ref.y = 1.0f;
} else if (pos.y < -0.5f) {
ref.x = ref.z = 1.0f;
} else if (pos.z > 0.5f) {
ref.z = 1.0f;
} else if (pos.z < -0.5f) {
ref.x = ref.y = 1.0f;
}
ufbxt_assert_close_vec4(err, color, ref);
ufbxt_assert_close_vec4(err, color, ref);
}
}
#endif
UFBXT_FILE_TEST(synthetic_color_suzanne)
#if UFBXT_IMPL
{
ufbxt_assert(scene->nodes.count == 2);
ufbx_node *node = scene->nodes.data[1];
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->vertex_color.exists);
ufbxt_assert(mesh->vertex_color.unique_per_vertex);
ufbxt_assert(mesh->num_faces == 500);
ufbxt_assert(mesh->num_triangles == 968);
for (size_t i = 0; i < mesh->num_indices; i++) {
ufbx_vec3 position = ufbx_get_vertex_vec3(&mesh->vertex_position, i);
ufbx_vec4 color = ufbx_get_vertex_vec4(&mesh->vertex_color, i);
ufbx_vec3 col = { color.x, color.y, color.z };
ufbx_vec3 ref;
ref.x = ufbxt_clamp(position.x * 0.5f + 0.5f, 0.0f, 1.0f);
ref.y = ufbxt_clamp(position.y * 0.5f + 0.5f, 0.0f, 1.0f);
ref.z = ufbxt_clamp(position.z * 0.5f + 0.5f, 0.0f, 1.0f);
ufbxt_assert_close_vec3_threshold(err, col, ref, (ufbx_real)(1.0/256.0));
}
}
#endif
#if UFBXT_IMPL
static void ufbxt_check_obj_elements(ufbxt_diff_error *err, ufbx_scene *scene, int32_t v, int32_t vt, int32_t vn, int32_t vc, const char *name)
{
ufbxt_hintf("name = \"%s\"", name);
ufbx_node *node = ufbx_find_node(scene, name);
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(!strcmp(mesh->name.data, name));
ufbxt_assert(mesh->num_faces == 1);
ufbxt_assert(mesh->num_triangles == 1);
ufbx_face face = mesh->faces.data[0];
ufbxt_assert(face.index_begin == 0);
ufbxt_assert(face.num_indices == 3);
if (v > 0) {
ufbxt_assert(mesh->vertex_position.exists);
ufbxt_assert(mesh->vertex_position.indices.count == 3);
const ufbx_vec3 refs[] = {
{ (ufbx_real)-v, 0.0f, (ufbx_real)(v - 1) },
{ (ufbx_real)+v, 0.0f, (ufbx_real)(v - 1) },
{ 0.0f, (ufbx_real)+v, (ufbx_real)(v - 1) },
};
for (size_t ix = 0; ix < 3; ix++) {
ufbx_vec3 val = ufbx_get_vertex_vec3(&mesh->vertex_position, ix);
ufbxt_assert_close_vec3(err, val, refs[ix]);
}
} else {
ufbxt_assert(!mesh->vertex_position.exists);
}
if (vt > 0) {
ufbxt_assert(mesh->vertex_uv.exists);
ufbxt_assert(mesh->vertex_uv.indices.count == 3);
const ufbx_vec2 refs[] = {
{ 0.0f, 0.0f },
{ (ufbx_real)vt, 0.0f },
{ 0.0f, (ufbx_real)vt },
};
for (size_t ix = 0; ix < 3; ix++) {
ufbx_vec2 val = ufbx_get_vertex_vec2(&mesh->vertex_uv, ix);
ufbxt_assert_close_vec2(err, val, refs[ix]);
}
} else {
ufbxt_assert(!mesh->vertex_uv.exists);
}
if (vn > 0) {
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_normal.indices.count == 3);
const ufbx_vec3 refs[] = {
{ 0.0f, (ufbx_real)-vn, 0.0f },
{ 0.0f, (ufbx_real)-vn, 0.0f },
{ 0.0f, (ufbx_real)+vn, 0.0f },
};
for (size_t ix = 0; ix < 3; ix++) {
ufbx_vec3 val = ufbx_get_vertex_vec3(&mesh->vertex_normal, ix);
ufbxt_assert_close_vec3(err, val, refs[ix]);
}
} else {
ufbxt_assert(!mesh->vertex_normal.exists);
}
if (vc > 0) {
ufbxt_assert(mesh->vertex_color.exists);
ufbxt_assert(mesh->vertex_color.indices.count == 3);
const ufbx_vec4 refs[] = {
{ (ufbx_real)vc, 0.0f, 0.0f, 1.0f },
{ 0.0f, (ufbx_real)vc, 0.0f, 1.0f },
{ 0.0f, 0.0f, (ufbx_real)vc, 1.0f },
};
for (size_t ix = 0; ix < 3; ix++) {
ufbx_vec4 val = ufbx_get_vertex_vec4(&mesh->vertex_color, ix);
ufbxt_assert_close_vec4(err, val, refs[ix]);
}
} else {
ufbxt_assert(!mesh->vertex_color.exists);
}
}
#endif
UFBXT_FILE_TEST(synthetic_mixed_attribs)
#if UFBXT_IMPL
{
ufbxt_assert(scene->nodes.count == 9);
ufbxt_assert(scene->meshes.count == 8);
ufbxt_check_obj_elements(err, scene, 1, 0, 0, 0, "V");
ufbxt_check_obj_elements(err, scene, 2, 1, 0, 0, "VT");
ufbxt_check_obj_elements(err, scene, 3, 0, 1, 0, "VN");
ufbxt_check_obj_elements(err, scene, 4, 2, 2, 0, "VTN");
ufbxt_check_obj_elements(err, scene, 5, 0, 0, 1, "VC");
ufbxt_check_obj_elements(err, scene, 6, 3, 0, 2, "VTC");
ufbxt_check_obj_elements(err, scene, 7, 0, 3, 3, "VNC");
ufbxt_check_obj_elements(err, scene, 8, 4, 4, 4, "VTNC");
}
#endif
UFBXT_FILE_TEST(synthetic_mixed_attribs_reverse)
#if UFBXT_IMPL
{
ufbxt_assert(scene->nodes.count == 9);
ufbxt_assert(scene->meshes.count == 8);
ufbxt_check_obj_elements(err, scene, 1, 0, 0, 0, "V");
ufbxt_check_obj_elements(err, scene, 2, 1, 0, 0, "VT");
ufbxt_check_obj_elements(err, scene, 3, 0, 1, 0, "VN");
ufbxt_check_obj_elements(err, scene, 4, 2, 2, 0, "VTN");
ufbxt_check_obj_elements(err, scene, 5, 0, 0, 1, "VC");
ufbxt_check_obj_elements(err, scene, 6, 3, 0, 2, "VTC");
ufbxt_check_obj_elements(err, scene, 7, 0, 3, 3, "VNC");
ufbxt_check_obj_elements(err, scene, 8, 4, 4, 4, "VTNC");
}
#endif
UFBXT_FILE_TEST(synthetic_mixed_attribs_reuse)
#if UFBXT_IMPL
{
ufbxt_assert(scene->nodes.count == 9);
ufbxt_assert(scene->meshes.count == 8);
ufbxt_check_obj_elements(err, scene, 1, 0, 0, 0, "V");
ufbxt_check_obj_elements(err, scene, 1, 1, 0, 0, "VT");
ufbxt_check_obj_elements(err, scene, 1, 0, 1, 0, "VN");
ufbxt_check_obj_elements(err, scene, 1, 1, 1, 0, "VTN");
ufbxt_check_obj_elements(err, scene, 2, 0, 0, 1, "VC");
ufbxt_check_obj_elements(err, scene, 2, 1, 0, 1, "VTC");
ufbxt_check_obj_elements(err, scene, 2, 0, 1, 1, "VNC");
ufbxt_check_obj_elements(err, scene, 2, 1, 1, 1, "VTNC");
}
#endif
#if UFBXT_IMPL
static ufbx_load_opts ufbxt_no_index_opts()
{
ufbx_load_opts opts = { 0 };
opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_NO_INDEX;
return opts;
}
static ufbx_load_opts ufbxt_abort_index_opts()
{
ufbx_load_opts opts = { 0 };
opts.index_error_handling = UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING;
return opts;
}
static void ufbxt_check_obj_face(ufbx_mesh *mesh, size_t face_ix, int32_t v, int32_t vt, int32_t vn, int32_t vc, bool no_index)
{
ufbxt_hintf("face_ix = %zu", face_ix);
ufbxt_assert(face_ix < mesh->faces.count);
ufbx_face face = mesh->faces.data[face_ix];
uint32_t a = face.index_begin + 0;
uint32_t b = face.index_begin + 1;
uint32_t c = face.index_begin + 2;
if (v) {
ufbxt_assert(mesh->vertex_position.indices.data[a] == v - 1 + 0);
ufbxt_assert(mesh->vertex_position.indices.data[b] == v - 1 + 1);
ufbxt_assert(mesh->vertex_position.indices.data[c] == v - 1 + 2);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_position.values.count - 1;
ufbxt_assert(mesh->vertex_position.indices.data[a] == sentinel);
ufbxt_assert(mesh->vertex_position.indices.data[b] == sentinel);
ufbxt_assert(mesh->vertex_position.indices.data[c] == sentinel);
}
if (vt) {
ufbxt_assert(mesh->vertex_uv.indices.data[a] == vt - 1 + 0);
ufbxt_assert(mesh->vertex_uv.indices.data[b] == vt - 1 + 1);
ufbxt_assert(mesh->vertex_uv.indices.data[c] == vt - 1 + 2);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_uv.values.count - 1;
ufbxt_assert(mesh->vertex_uv.indices.data[a] == sentinel);
ufbxt_assert(mesh->vertex_uv.indices.data[b] == sentinel);
ufbxt_assert(mesh->vertex_uv.indices.data[c] == sentinel);
}
if (vn) {
ufbxt_assert(mesh->vertex_normal.indices.data[a] == vn - 1 + 0);
ufbxt_assert(mesh->vertex_normal.indices.data[b] == vn - 1 + 1);
ufbxt_assert(mesh->vertex_normal.indices.data[c] == vn - 1 + 2);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_normal.values.count - 1;
ufbxt_assert(mesh->vertex_normal.indices.data[a] == sentinel);
ufbxt_assert(mesh->vertex_normal.indices.data[b] == sentinel);
ufbxt_assert(mesh->vertex_normal.indices.data[c] == sentinel);
}
if (vc) {
ufbxt_assert(mesh->vertex_color.indices.data[a] == vc - 1 + 0);
ufbxt_assert(mesh->vertex_color.indices.data[b] == vc - 1 + 1);
ufbxt_assert(mesh->vertex_color.indices.data[c] == vc - 1 + 2);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_color.values.count - 1;
ufbxt_assert(mesh->vertex_color.indices.data[a] == sentinel);
ufbxt_assert(mesh->vertex_color.indices.data[b] == sentinel);
ufbxt_assert(mesh->vertex_color.indices.data[c] == sentinel);
}
}
static void ufbxt_check_obj_index(ufbx_mesh *mesh, size_t index, int32_t v, int32_t vt, int32_t vn, int32_t vc, bool no_index)
{
ufbxt_hintf("index = %zu", index);
ufbxt_assert(index < mesh->num_indices);
if (v) {
ufbxt_assert(mesh->vertex_position.indices.data[index] == v - 1);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_position.values.count - 1;
ufbxt_assert(mesh->vertex_position.indices.data[index] == sentinel);
}
if (vt) {
ufbxt_assert(mesh->vertex_uv.indices.data[index] == vt - 1);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_uv.values.count - 1;
ufbxt_assert(mesh->vertex_uv.indices.data[index] == sentinel);
}
if (vn) {
ufbxt_assert(mesh->vertex_normal.indices.data[index] == vn - 1);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_normal.values.count - 1;
ufbxt_assert(mesh->vertex_normal.indices.data[index] == sentinel);
}
if (vc) {
ufbxt_assert(mesh->vertex_color.indices.data[index] == vc - 1);
} else {
uint32_t sentinel = no_index ? UFBX_NO_INDEX : (uint32_t)mesh->vertex_color.values.count - 1;
ufbxt_assert(mesh->vertex_color.indices.data[index] == sentinel);
}
}
#endif
UFBXT_FILE_TEST(synthetic_partial_attrib)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Mesh");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 12);
ufbxt_assert(mesh->num_triangles == 12);
ufbxt_assert(mesh->vertex_position.exists);
ufbxt_assert(mesh->vertex_uv.exists);
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_color.exists);
ufbxt_check_obj_face(mesh, 0, 1, 0, 0, 0, false);
ufbxt_check_obj_face(mesh, 1, 1, 1, 0, 0, false);
ufbxt_check_obj_face(mesh, 2, 1, 0, 1, 0, false);
ufbxt_check_obj_face(mesh, 3, 1, 1, 1, 0, false);
ufbxt_check_obj_face(mesh, 4, 4, 0, 0, 4, false);
ufbxt_check_obj_face(mesh, 5, 4, 1, 0, 4, false);
ufbxt_check_obj_face(mesh, 6, 4, 0, 1, 4, false);
ufbxt_check_obj_face(mesh, 7, 4, 1, 1, 4, false);
ufbxt_check_obj_face(mesh, 8, 0, 0, 0, 0, false);
ufbxt_check_obj_face(mesh, 9, 0, 1, 0, 0, false);
ufbxt_check_obj_face(mesh, 10, 0, 0, 1, 0, false);
ufbxt_check_obj_face(mesh, 11, 0, 1, 1, 0, false);
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_partial_attrib_no_index, synthetic_partial_attrib, ufbxt_no_index_opts)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Mesh");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 12);
ufbxt_assert(mesh->num_triangles == 12);
ufbxt_assert(mesh->vertex_position.exists);
ufbxt_assert(mesh->vertex_uv.exists);
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_color.exists);
ufbxt_check_obj_face(mesh, 0, 1, 0, 0, 0, true);
ufbxt_check_obj_face(mesh, 1, 1, 1, 0, 0, true);
ufbxt_check_obj_face(mesh, 2, 1, 0, 1, 0, true);
ufbxt_check_obj_face(mesh, 3, 1, 1, 1, 0, true);
ufbxt_check_obj_face(mesh, 4, 4, 0, 0, 4, true);
ufbxt_check_obj_face(mesh, 5, 4, 1, 0, 4, true);
ufbxt_check_obj_face(mesh, 6, 4, 0, 1, 4, true);
ufbxt_check_obj_face(mesh, 7, 4, 1, 1, 4, true);
ufbxt_check_obj_face(mesh, 8, 0, 0, 0, 0, true);
ufbxt_check_obj_face(mesh, 9, 0, 1, 0, 0, true);
ufbxt_check_obj_face(mesh, 10, 0, 0, 1, 0, true);
ufbxt_check_obj_face(mesh, 11, 0, 1, 1, 0, true);
}
#endif
UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_partial_attrib_strict, synthetic_partial_attrib, ufbxt_abort_index_opts, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR)
#if UFBXT_IMPL
{
ufbxt_assert(!scene);
ufbxt_assert(load_error);
ufbxt_assert(load_error->type == UFBX_ERROR_BAD_INDEX);
}
#endif
UFBXT_FILE_TEST(synthetic_partial_attrib_face)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Mesh");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbxt_assert(mesh->num_triangles == 10);
ufbxt_assert(mesh->num_indices == 12);
ufbxt_assert(mesh->vertex_position.exists);
ufbxt_assert(mesh->vertex_uv.exists);
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_color.exists);
ufbx_face face = mesh->faces.data[0];
ufbxt_assert(face.index_begin == 0);
ufbxt_assert(face.num_indices == 12);
ufbxt_check_obj_index(mesh, 0, 1, 0, 0, 0, false);
ufbxt_check_obj_index(mesh, 1, 2, 1, 0, 0, false);
ufbxt_check_obj_index(mesh, 2, 3, 0, 1, 0, false);
ufbxt_check_obj_index(mesh, 3, 4, 2, 2, 0, false);
ufbxt_check_obj_index(mesh, 4, 5, 0, 0, 5, false);
ufbxt_check_obj_index(mesh, 5, 6, 3, 0, 6, false);
ufbxt_check_obj_index(mesh, 6, 7, 0, 3, 7, false);
ufbxt_check_obj_index(mesh, 7, 8, 4, 4, 8, false);
ufbxt_check_obj_index(mesh, 8, 0, 0, 0, 0, false);
ufbxt_check_obj_index(mesh, 9, 0, 5, 0, 0, false);
ufbxt_check_obj_index(mesh, 10, 0, 0, 5, 0, false);
ufbxt_check_obj_index(mesh, 11, 0, 6, 6, 0, false);
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_partial_attrib_face_no_index, synthetic_partial_attrib_face, ufbxt_no_index_opts)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Mesh");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbxt_assert(mesh->num_triangles == 10);
ufbxt_assert(mesh->num_indices == 12);
ufbxt_assert(mesh->vertex_position.exists);
ufbxt_assert(mesh->vertex_uv.exists);
ufbxt_assert(mesh->vertex_normal.exists);
ufbxt_assert(mesh->vertex_color.exists);
ufbx_face face = mesh->faces.data[0];
ufbxt_assert(face.index_begin == 0);
ufbxt_assert(face.num_indices == 12);
ufbxt_check_obj_index(mesh, 0, 1, 0, 0, 0, true);
ufbxt_check_obj_index(mesh, 1, 2, 1, 0, 0, true);
ufbxt_check_obj_index(mesh, 2, 3, 0, 1, 0, true);
ufbxt_check_obj_index(mesh, 3, 4, 2, 2, 0, true);
ufbxt_check_obj_index(mesh, 4, 5, 0, 0, 5, true);
ufbxt_check_obj_index(mesh, 5, 6, 3, 0, 6, true);
ufbxt_check_obj_index(mesh, 6, 7, 0, 3, 7, true);
ufbxt_check_obj_index(mesh, 7, 8, 4, 4, 8, true);
ufbxt_check_obj_index(mesh, 8, 0, 0, 0, 0, true);
ufbxt_check_obj_index(mesh, 9, 0, 5, 0, 0, true);
ufbxt_check_obj_index(mesh, 10, 0, 0, 5, 0, true);
ufbxt_check_obj_index(mesh, 11, 0, 6, 6, 0, true);
}
#endif
UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_partial_attrib_face_strict, synthetic_partial_attrib_face, ufbxt_abort_index_opts, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR)
#if UFBXT_IMPL
{
ufbxt_assert(!scene);
ufbxt_assert(load_error);
ufbxt_assert(load_error->type == UFBX_ERROR_BAD_INDEX);
}
#endif
UFBXT_FILE_TEST(synthetic_simple_materials)
#if UFBXT_IMPL
{
ufbxt_assert(scene->materials.count == 3);
{
ufbx_material *mat = ufbx_find_material(scene, "RGB");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbx_vec3 ka = { 1.0f, 0.0f, 0.0f };
ufbx_vec3 kd = { 0.0f, 1.0f, 0.0f };
ufbx_vec3 ks = { 0.0f, 0.0f, 1.0f };
ufbx_vec3 ke = { 1.0f, 0.0f, 1.0f };
ufbx_real ns = 99.0f;
ufbx_real d = 0.25f;
ufbxt_assert_close_vec3(err, mat->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(err, mat->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(err, mat->fbx.specular_color.value_vec3, ks);
ufbxt_assert_close_vec3(err, mat->fbx.emission_color.value_vec3, ke);
ufbxt_assert_close_real(err, mat->fbx.specular_exponent.value_real, ns);
ufbxt_assert_close_real(err, mat->fbx.transparency_factor.value_real, 1.0f - d);
ufbxt_assert(mat->fbx.ambient_factor.value_real == 1.0f);
ufbxt_assert(mat->fbx.diffuse_factor.value_real == 1.0f);
ufbxt_assert(mat->fbx.specular_factor.value_real == 1.0f);
ufbxt_assert(mat->fbx.emission_factor.value_real == 1.0f);
ufbxt_assert_close_vec3(err, mat->pbr.base_color.value_vec3, kd);
ufbxt_assert_close_vec3(err, mat->pbr.specular_color.value_vec3, ks);
ufbxt_assert_close_vec3(err, mat->pbr.emission_color.value_vec3, ke);
ufbxt_assert_close_real(err, mat->pbr.roughness.value_real, 0.00501256289f);
ufbxt_assert_close_real(err, mat->pbr.opacity.value_real, d);
ufbxt_assert(mat->pbr.base_factor.value_real == 1.0f);
ufbxt_assert(mat->pbr.specular_factor.value_real == 1.0f);
ufbxt_assert(mat->pbr.emission_factor.value_real == 1.0f);
ufbxt_assert(mat->features.diffuse.enabled);
ufbxt_assert(mat->features.specular.enabled);
ufbxt_assert(mat->features.opacity.enabled);
ufbxt_assert(!mat->features.pbr.enabled);
ufbxt_assert(!mat->features.metalness.enabled);
ufbxt_assert(!mat->features.sheen.enabled);
ufbxt_assert(!mat->features.coat.enabled);
ufbxt_assert(!mat->features.transmission.enabled);
}
{
ufbx_material *mat = ufbx_find_material(scene, "PBR");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbx_real pr = 0.1f;
ufbx_real pm = 0.2f;
ufbx_vec3 ps = { 0.3f, 0.4f, 0.5f };
ufbx_real pc = 0.6f;
ufbx_real pcr = 0.7f;
ufbx_real ni = 1.33f;
ufbx_vec3 tf = { 0.8f, 0.9f, 1.0f };
ufbx_real d = 0.75f;
ufbxt_assert_close_real(err, mat->pbr.roughness.value_real, pr);
ufbxt_assert_close_real(err, mat->pbr.metalness.value_real, pm);
ufbxt_assert_close_vec3(err, mat->pbr.sheen_color.value_vec3, ps);
ufbxt_assert_close_real(err, mat->pbr.coat_factor.value_real, pc);
ufbxt_assert_close_real(err, mat->pbr.coat_roughness.value_real, pcr);
ufbxt_assert_close_real(err, mat->pbr.specular_ior.value_real, ni);
ufbxt_assert_close_vec3(err, mat->pbr.transmission_color.value_vec3, tf);
ufbxt_assert_close_real(err, mat->pbr.opacity.value_real, d);
ufbxt_assert(mat->pbr.sheen_factor.value_real == 1.0f);
ufbxt_assert(mat->pbr.transmission_factor.value_real == 1.0f);
ufbxt_assert(mat->features.pbr.enabled);
ufbxt_assert(mat->features.metalness.enabled);
ufbxt_assert(mat->features.diffuse.enabled);
ufbxt_assert(mat->features.specular.enabled);
ufbxt_assert(mat->features.sheen.enabled);
ufbxt_assert(mat->features.coat.enabled);
ufbxt_assert(mat->features.transmission.enabled);
ufbxt_assert(mat->features.opacity.enabled);
}
{
ufbx_material *mat = ufbx_find_material(scene, "Wide");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbx_vec3 ka = { 0.1f, 0.1f, 0.1f };
ufbx_vec3 kd = { 0.2f, 0.2f, 0.2f };
ufbx_vec3 ks = { 0.3f, 0.3f, 0.3f };
ufbx_vec3 ke = { 0.4f, 0.4f, 0.4f };
ufbx_vec3 ps = { 0.5f, 0.5f, 0.5f };
ufbx_vec3 tf = { 0.6f, 0.6f, 0.6f };
ufbxt_assert_close_vec3(err, mat->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(err, mat->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(err, mat->fbx.specular_color.value_vec3, ks);
ufbxt_assert_close_vec3(err, mat->fbx.emission_color.value_vec3, ke);
ufbxt_assert_close_vec3(err, mat->pbr.base_color.value_vec3, kd);
ufbxt_assert_close_vec3(err, mat->pbr.specular_color.value_vec3, ks);
ufbxt_assert_close_vec3(err, mat->pbr.emission_color.value_vec3, ke);
ufbxt_assert_close_vec3(err, mat->pbr.sheen_color.value_vec3, ps);
ufbxt_assert_close_vec3(err, mat->pbr.transmission_color.value_vec3, tf);
}
}
#endif
#if UFBXT_IMPL
void ufbxt_check_obj_texture(ufbx_scene *scene, ufbx_texture *texture, const char *filename)
{
char rel_path[256];
snprintf(rel_path, sizeof(rel_path), "textures/%s", filename);
ufbxt_assert(texture);
ufbxt_assert(!strcmp(texture->relative_filename.data, rel_path));
}
#endif
UFBXT_FILE_TEST(synthetic_simple_textures)
#if UFBXT_IMPL
{
ufbxt_assert(scene->materials.count == 3);
{
ufbx_material *mat = ufbx_find_material(scene, "RGB");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_check_obj_texture(scene, mat->fbx.ambient_color.texture, "checkerboard_ambient.png");
ufbxt_check_obj_texture(scene, mat->fbx.diffuse_color.texture, "checkerboard_diffuse.png");
ufbxt_check_obj_texture(scene, mat->fbx.specular_color.texture, "checkerboard_specular.png");
ufbxt_check_obj_texture(scene, mat->fbx.emission_color.texture, "checkerboard_emissive.png");
ufbxt_check_obj_texture(scene, mat->fbx.specular_exponent.texture, "checkerboard_roughness.png");
ufbxt_check_obj_texture(scene, mat->fbx.transparency_factor.texture, "checkerboard_transparency.png");
ufbxt_check_obj_texture(scene, mat->fbx.bump.texture, "checkerboard_bump.png");
ufbxt_check_obj_texture(scene, mat->pbr.base_color.texture, "checkerboard_diffuse.png");
ufbxt_check_obj_texture(scene, mat->pbr.specular_color.texture, "checkerboard_specular.png");
ufbxt_check_obj_texture(scene, mat->pbr.emission_color.texture, "checkerboard_emissive.png");
ufbxt_check_obj_texture(scene, mat->pbr.roughness.texture, "checkerboard_roughness.png");
ufbxt_check_obj_texture(scene, mat->pbr.normal_map.texture, "checkerboard_bump.png");
}
{
ufbx_material *mat = ufbx_find_material(scene, "PBR");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_check_obj_texture(scene, mat->pbr.roughness.texture, "checkerboard_roughness.png");
ufbxt_check_obj_texture(scene, mat->pbr.metalness.texture, "checkerboard_metallic.png");
ufbxt_check_obj_texture(scene, mat->pbr.sheen_color.texture, "checkerboard_reflection.png");
ufbxt_check_obj_texture(scene, mat->pbr.coat_factor.texture, "checkerboard_specular.png");
ufbxt_check_obj_texture(scene, mat->pbr.coat_roughness.texture, "checkerboard_weight.png");
ufbxt_check_obj_texture(scene, mat->pbr.transmission_color.texture, "checkerboard_transparency.png");
ufbxt_check_obj_texture(scene, mat->pbr.opacity.texture, "checkerboard_weight.png");
ufbxt_check_obj_texture(scene, mat->pbr.specular_ior.texture, "checkerboard_specular.png");
ufbxt_check_obj_texture(scene, mat->pbr.normal_map.texture, "checkerboard_normal.png");
ufbxt_check_obj_texture(scene, mat->pbr.displacement_map.texture, "checkerboard_displacement.png");
ufbxt_check_obj_texture(scene, mat->fbx.transparency_factor.texture, "checkerboard_weight.png");
ufbxt_check_obj_texture(scene, mat->fbx.normal_map.texture, "checkerboard_normal.png");
ufbxt_check_obj_texture(scene, mat->fbx.displacement.texture, "checkerboard_displacement.png");
}
{
ufbx_material *mat = ufbx_find_material(scene, "NonMap");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_assert(mat->textures.count == 3);
ufbxt_check_obj_texture(scene, mat->pbr.normal_map.texture, "checkerboard_normal.png");
ufbxt_check_obj_texture(scene, mat->pbr.displacement_map.texture, "checkerboard_displacement.png");
ufbxt_check_obj_texture(scene, mat->fbx.normal_map.texture, "checkerboard_normal.png");
ufbxt_check_obj_texture(scene, mat->fbx.displacement.texture, "checkerboard_displacement.png");
ufbxt_check_obj_texture(scene, ufbx_find_prop_texture(mat, "norm"), "checkerboard_normal.png");
ufbxt_check_obj_texture(scene, ufbx_find_prop_texture(mat, "disp"), "checkerboard_displacement.png");
ufbxt_check_obj_texture(scene, ufbx_find_prop_texture(mat, "bump"), "checkerboard_bump.png");
}
}
#endif
#if UFBXT_IMPL
void ufbxt_check_obj_prop(ufbxt_diff_error *err, ufbx_props *props, const char *name, const char *str, int64_t i, ufbx_real x, ufbx_real y, ufbx_real z)
{
ufbxt_hintf("name = \"%s\"", name);
ufbx_prop *prop = ufbx_find_prop(props, name);
ufbxt_assert(prop);
if (str) {
ufbxt_assert(!strcmp(prop->value_str.data, str));
}
ufbxt_assert(prop->value_int == i);
ufbxt_assert_close_real(err, prop->value_vec3.x, x);
ufbxt_assert_close_real(err, prop->value_vec3.y, y);
ufbxt_assert_close_real(err, prop->value_vec3.z, z);
}
#endif
UFBXT_FILE_TEST(synthetic_texture_opts)
#if UFBXT_IMPL
{
ufbxt_assert(scene->materials.count == 1);
ufbx_material *mat = ufbx_find_material(scene, "Opts");
ufbxt_assert(mat);
ufbxt_assert(mat->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
{
ufbx_texture *tex = mat->fbx.diffuse_color.texture;
ufbxt_assert(tex);
ufbxt_assert(!strcmp(tex->relative_filename.data, "textures/checkerboard_diffuse.png"));
ufbxt_check_obj_prop(err, &tex->props, "blendu", "off", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "blendv", "on", 1, 1.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "clamp", "off", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "imfchan", "r", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "mm", "1 2", 1, 1.0f, 2.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "o", "0.1 0.2 0.3", 0, 0.1f, 0.2f, 0.3f);
ufbxt_check_obj_prop(err, &tex->props, "s", "0.4 0.5 0.6", 0, 0.4f, 0.5f, 0.6f);
ufbxt_check_obj_prop(err, &tex->props, "t", "0.7 0.8 0.9", 0, 0.7f, 0.8f, 0.9f);
ufbxt_check_obj_prop(err, &tex->props, "texres", "512", 512, 512.0f, 0.0f, 0.0f);
}
{
ufbx_texture *tex = mat->fbx.specular_color.texture;
ufbxt_assert(tex);
ufbxt_assert(!strcmp(tex->relative_filename.data, "textures/checkerboard_specular.png"));
ufbxt_check_obj_prop(err, &tex->props, "blendu", "on", 1, 1.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "blendv", "off", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "clamp", "on", 1, 1.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "imfchan", "g", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "mm", "3 4", 3, 3.0f, 4.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "o", "-0.1 -.2 1.3", 0, -0.1f, -0.2f, 1.3f);
ufbxt_check_obj_prop(err, &tex->props, "s", NULL, 1, 1.4f, 1.5f, 1.6f);
ufbxt_check_obj_prop(err, &tex->props, "t", "1.7 1.8 1.9", 1, 1.7f, 1.8f, 1.9f);
ufbxt_check_obj_prop(err, &tex->props, "texres", "1024", 1024, 1024.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "unknown", "hello world", 0, 0.0f, 0.0f, 0.0f);
ufbxt_check_obj_prop(err, &tex->props, "single-unknown", "", 0, 0.0f, 0.0f, 0.0f);
}
}
#endif
UFBXT_FILE_TEST_PATH(blender_331_space_texture, "blender_331_space texture")
#if UFBXT_IMPL
{
ufbxt_assert(scene->materials.count == 1);
ufbx_material *material = ufbx_find_material(scene, "Material");
ufbxt_assert(material);
ufbx_texture *texture = material->fbx.diffuse_color.texture;
ufbxt_assert(texture);
ufbxt_assert(!strcmp(texture->relative_filename.data, "space dir/space tex.png"));
if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ) {
ufbx_node *node = ufbx_find_node(scene, "Cube");
ufbxt_assert(node && node->mesh);
ufbxt_assert(node->materials.count == 1);
ufbxt_assert(node->materials.data[0] == material);
}
}
#endif
UFBXT_FILE_TEST(synthetic_obj_zoo)
#if UFBXT_IMPL
{
ufbxt_check_warning(scene, UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, 24, "Unknown .obj directive, skipped line");
ufbx_node *node = ufbx_find_node(scene, "Object");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 7);
ufbxt_assert(mesh->num_line_faces == 2);
ufbxt_assert(mesh->num_point_faces == 4);
}
#endif
#if UFBXT_IMPL
static ufbx_mesh_material *ufbxt_find_mesh_material(ufbx_mesh *mesh, const char *name)
{
for (size_t i = 0; i < mesh->materials.count; i++) {
if (!strcmp(mesh->materials.data[i].material->name.data, name)) {
return &mesh->materials.data[i];
}
}
return NULL;
}
static ufbx_face_group *ufbxt_find_face_group(ufbx_mesh *mesh, const char *name)
{
for (size_t i = 0; i < mesh->face_groups.count; i++) {
if (!strcmp(mesh->face_groups.data[i].name.data, name)) {
return &mesh->face_groups.data[i];
}
}
return NULL;
}
static void ufbxt_check_planar_face(ufbxt_diff_error *err, ufbx_mesh *mesh, ufbx_face face, ufbx_vec3 normal, ufbx_real w)
{
for (size_t i = 0; i < face.num_indices; i++) {
ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i);
ufbx_real ref = ufbxt_dot3(normal, pos);
ufbxt_assert_close_real(err, ref, w);
}
}
static void ufbxt_check_planar_face_ix(ufbxt_diff_error *err, ufbx_mesh *mesh, size_t face_ix, ufbx_vec3 normal, ufbx_real w)
{
ufbxt_assert(face_ix < mesh->faces.count);
ufbxt_check_planar_face(err, mesh, mesh->faces.data[face_ix], normal, w);
}
#endif
UFBXT_FILE_TEST(synthetic_face_groups)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Cube");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbx_vec3 pos_x = { +1.0f, 0.0f, 0.0f };
ufbx_vec3 neg_x = { -1.0f, 0.0f, 0.0f };
ufbx_vec3 pos_y = { 0.0f, +1.0f, 0.0f };
ufbx_vec3 neg_y = { 0.0f, -1.0f, 0.0f };
ufbx_vec3 pos_z = { 0.0f, 0.0f, +1.0f };
ufbx_vec3 neg_z = { 0.0f, 0.0f, -1.0f };
{
ufbx_face_group *group = ufbxt_find_face_group(mesh, "");
ufbxt_assert(group);
ufbxt_assert(group->num_faces == 2);
ufbxt_assert(group->face_indices.data[0] == 0);
ufbxt_assert(group->face_indices.data[1] == 5);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[0], pos_y, 1.0f);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[1], neg_y, 1.0f);
}
{
ufbx_face_group *group = ufbxt_find_face_group(mesh, "Front");
ufbxt_assert(group);
ufbxt_assert(group->num_faces == 1);
ufbxt_assert(group->face_indices.data[0] == 1);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[0], neg_z, 1.0f);
}
{
ufbx_face_group *group = ufbxt_find_face_group(mesh, "Sides");
ufbxt_assert(group);
ufbxt_assert(group->num_faces == 2);
ufbxt_assert(group->face_indices.data[0] == 2);
ufbxt_assert(group->face_indices.data[1] == 4);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[0], neg_x, 1.0f);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[1], pos_x, 1.0f);
}
{
ufbx_face_group *group = ufbxt_find_face_group(mesh, "Back");
ufbxt_assert(group);
ufbxt_assert(group->num_faces == 1);
ufbxt_assert(group->face_indices.data[0] == 3);
ufbxt_check_planar_face_ix(err, mesh, group->face_indices.data[0], pos_z, 1.0f);
}
{
ufbx_mesh_material *mat = ufbxt_find_mesh_material(mesh, "A");
ufbxt_assert(mat);
ufbxt_assert(mat->num_faces == 3);
ufbxt_assert(mat->face_indices.data[0] == 0);
ufbxt_assert(mat->face_indices.data[1] == 1);
ufbxt_assert(mat->face_indices.data[2] == 2);
}
{
ufbx_mesh_material *mat = ufbxt_find_mesh_material(mesh, "B");
ufbxt_assert(mat);
ufbxt_assert(mat->num_faces == 3);
ufbxt_assert(mat->face_indices.data[0] == 3);
ufbxt_assert(mat->face_indices.data[1] == 4);
ufbxt_assert(mat->face_indices.data[2] == 5);
}
}
#endif
#if UFBXT_IMPL
static ufbx_load_opts ufbxt_split_groups_opts()
{
ufbx_load_opts opts = { 0 };
opts.obj_split_groups = true;
return opts;
}
static ufbx_load_opts ufbxt_merge_objects_opts()
{
ufbx_load_opts opts = { 0 };
opts.obj_merge_objects = true;
return opts;
}
static ufbx_load_opts ufbxt_allow_null_material_opts()
{
ufbx_load_opts opts = { 0 };
opts.allow_null_material = true;
return opts;
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_face_groups_split, synthetic_face_groups, ufbxt_split_groups_opts)
#if UFBXT_IMPL
{
ufbx_vec3 pos_x = { +1.0f, 0.0f, 0.0f };
ufbx_vec3 neg_x = { -1.0f, 0.0f, 0.0f };
ufbx_vec3 pos_y = { 0.0f, +1.0f, 0.0f };
ufbx_vec3 neg_y = { 0.0f, -1.0f, 0.0f };
ufbx_vec3 pos_z = { 0.0f, 0.0f, +1.0f };
ufbx_vec3 neg_z = { 0.0f, 0.0f, -1.0f };
ufbx_material *mat_a = ufbx_find_material(scene, "A");
ufbx_material *mat_b = ufbx_find_material(scene, "B");
size_t num_a_found = 0;
size_t num_b_found = 0;
for (size_t i = 0; i < scene->nodes.count; i++) {
ufbx_node *node = scene->nodes.data[i];
if (strcmp(node->name.data, "Cube") != 0) continue;
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh);
ufbxt_assert(mesh->materials.count == 1);
ufbxt_assert(mesh->num_faces == 1);
ufbx_material *material = mesh->materials.data[0].material;
if (material == mat_a) {
ufbxt_check_planar_face_ix(err, mesh, 0, pos_y, 1.0f);
num_a_found++;
} else {
ufbxt_assert(material == mat_b);
ufbxt_check_planar_face_ix(err, mesh, 0, neg_y, 1.0f);
num_b_found++;
}
}
ufbxt_assert(num_a_found == 1);
ufbxt_assert(num_b_found == 1);
{
ufbx_node *node = ufbx_find_node(scene, "Front");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbxt_check_planar_face_ix(err, mesh, 0, neg_z, 1.0f);
ufbxt_assert(mesh->materials.count == 1);
ufbxt_assert(mesh->materials.data[0].material == mat_a);
}
{
ufbx_node *node = ufbx_find_node(scene, "Back");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbxt_check_planar_face_ix(err, mesh, 0, pos_z, 1.0f);
ufbxt_assert(mesh->materials.count == 1);
ufbxt_assert(mesh->materials.data[0].material == mat_b);
}
num_a_found = 0;
num_b_found = 0;
for (size_t i = 0; i < scene->nodes.count; i++) {
ufbx_node *node = scene->nodes.data[i];
if (strcmp(node->name.data, "Sides") != 0) continue;
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh);
ufbxt_assert(mesh->materials.count == 1);
ufbxt_assert(mesh->num_faces == 1);
ufbx_material *material = mesh->materials.data[0].material;
if (material == mat_a) {
ufbxt_check_planar_face_ix(err, mesh, 0, neg_x, 1.0f);
num_a_found++;
} else {
ufbxt_assert(material == mat_b);
ufbxt_check_planar_face_ix(err, mesh, 0, pos_x, 1.0f);
num_b_found++;
}
}
ufbxt_assert(num_a_found == 1);
ufbxt_assert(num_b_found == 1);
}
#endif
UFBXT_FILE_TEST(synthetic_partial_material)
#if UFBXT_IMPL
{
ufbxt_assert(!scene->metadata.may_contain_null_materials);
ufbxt_assert(scene->materials.count == 8);
ufbxt_assert(ufbx_find_material(scene, "A"));
ufbxt_assert(ufbx_find_material(scene, "B"));
ufbxt_assert(ufbx_find_material(scene, "C"));
ufbxt_assert(ufbx_find_material(scene, "D"));
ufbxt_assert(ufbx_find_material(scene, "E"));
ufbxt_assert(ufbx_find_material(scene, "F"));
ufbxt_assert(ufbx_find_material(scene, "G"));
ufbxt_assert(ufbx_find_material(scene, "H"));
{
ufbx_node *node = ufbx_find_node(scene, "First");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 0);
ufbxt_assert(mesh->face_material.count == 0);
}
{
ufbx_node *node = ufbx_find_node(scene, "Second");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 3);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "A"));
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "B"));
ufbxt_assert(!strcmp(mesh->materials.data[2].material->name.data, "D"));
ufbxt_assert(mesh->face_material.count == 5);
ufbxt_assert(mesh->face_material.data[0] == 0);
ufbxt_assert(mesh->face_material.data[1] == 0);
ufbxt_assert(mesh->face_material.data[2] == 1);
ufbxt_assert(mesh->face_material.data[3] == 2);
ufbxt_assert(mesh->face_material.data[4] == 0);
}
{
ufbx_node *node = ufbx_find_node(scene, "Third");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 2);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "F"));
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "A"));
ufbxt_assert(mesh->face_material.count == 2);
ufbxt_assert(mesh->face_material.data[0] == 0);
ufbxt_assert(mesh->face_material.data[1] == 1);
}
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_partial_material_allow_null, synthetic_partial_material, ufbxt_allow_null_material_opts)
#if UFBXT_IMPL
{
ufbxt_assert(scene->metadata.may_contain_null_materials);
ufbxt_assert(scene->materials.count == 8);
ufbxt_assert(ufbx_find_material(scene, "A"));
ufbxt_assert(ufbx_find_material(scene, "B"));
ufbxt_assert(ufbx_find_material(scene, "C"));
ufbxt_assert(ufbx_find_material(scene, "D"));
ufbxt_assert(ufbx_find_material(scene, "E"));
ufbxt_assert(ufbx_find_material(scene, "F"));
ufbxt_assert(ufbx_find_material(scene, "G"));
ufbxt_assert(ufbx_find_material(scene, "H"));
{
ufbx_node *node = ufbx_find_node(scene, "First");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->face_material.count == 1);
ufbxt_assert(mesh->materials.count == 1);
ufbxt_assert(mesh->materials.data[0].material == NULL);
}
{
ufbx_node *node = ufbx_find_node(scene, "Second");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 3);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "A"));
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "B"));
ufbxt_assert(!strcmp(mesh->materials.data[2].material->name.data, "D"));
ufbxt_assert(mesh->face_material.count == 5);
ufbxt_assert(mesh->face_material.data[0] == 0);
ufbxt_assert(mesh->face_material.data[1] == 0);
ufbxt_assert(mesh->face_material.data[2] == 1);
ufbxt_assert(mesh->face_material.data[3] == 2);
ufbxt_assert(mesh->face_material.data[4] == 0);
}
{
ufbx_node *node = ufbx_find_node(scene, "Third");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 2);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "F"));
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "A"));
ufbxt_assert(mesh->face_material.count == 2);
ufbxt_assert(mesh->face_material.data[0] == 0);
ufbxt_assert(mesh->face_material.data[1] == 1);
}
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_partial_material_merged, synthetic_partial_material, ufbxt_merge_objects_opts)
#if UFBXT_IMPL
{
ufbxt_assert(!scene->metadata.may_contain_null_materials);
ufbxt_assert(scene->materials.count == 8);
ufbxt_assert(ufbx_find_material(scene, "A"));
ufbxt_assert(ufbx_find_material(scene, "B"));
ufbxt_assert(ufbx_find_material(scene, "C"));
ufbxt_assert(ufbx_find_material(scene, "D"));
ufbxt_assert(ufbx_find_material(scene, "E"));
ufbxt_assert(ufbx_find_material(scene, "F"));
ufbxt_assert(ufbx_find_material(scene, "G"));
ufbxt_assert(ufbx_find_material(scene, "H"));
ufbxt_assert(scene->nodes.count == 2);
ufbx_node *node = scene->nodes.data[1];
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->materials.count == 4);
ufbxt_assert(!strcmp(mesh->materials.data[0].material->name.data, "A"));
ufbxt_assert(!strcmp(mesh->materials.data[1].material->name.data, "B"));
ufbxt_assert(!strcmp(mesh->materials.data[2].material->name.data, "D"));
ufbxt_assert(!strcmp(mesh->materials.data[3].material->name.data, "F"));
ufbxt_assert(mesh->face_material.count == 8);
ufbxt_assert(mesh->face_material.data[0] == 0);
ufbxt_assert(mesh->face_material.data[1] == 0);
ufbxt_assert(mesh->face_material.data[2] == 0);
ufbxt_assert(mesh->face_material.data[3] == 1);
ufbxt_assert(mesh->face_material.data[4] == 2);
ufbxt_assert(mesh->face_material.data[5] == 0);
ufbxt_assert(mesh->face_material.data[6] == 3);
ufbxt_assert(mesh->face_material.data[7] == 0);
}
#endif
#if UFBXT_IMPL
static ufbx_load_opts ufbxt_search_mtl_by_filename_opts()
{
ufbx_load_opts opts = { 0 };
opts.obj_search_mtl_by_filename = true;
return opts;
}
#endif
UFBXT_FILE_TEST_OPTS(synthetic_filename_mtl, ufbxt_search_mtl_by_filename_opts)
#if UFBXT_IMPL
{
ufbxt_check_warning(scene, UFBX_WARNING_MISSING_EXTERNAL_FILE, 1, "materials.mtl");
ufbxt_check_warning(scene, UFBX_WARNING_IMPLICIT_MTL, 1, "synthetic_filename_mtl_0_obj.mtl");
{
ufbx_vec3 ka = { 1.0f, 0.0f, 0.0f };
ufbx_vec3 kd = { 0.0f, 1.0f, 0.0f };
ufbx_vec3 ks = { 0.0f, 0.0f, 1.0f };
ufbx_material *material = ufbx_find_material(scene, "Material");
ufbxt_assert(material);
ufbxt_assert_close_vec3(err, material->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(err, material->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(err, material->fbx.specular_color.value_vec3, ks);
}
{
ufbx_material *material = ufbx_find_material(scene, "Other");
ufbxt_assert(material);
}
}
#endif
UFBXT_FILE_TEST_FLAGS(synthetic_missing_position_fail, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR)
#if UFBXT_IMPL
{
}
#endif
UFBXT_TEST(obj_opts_mtl_data)
#if UFBXT_IMPL
{
const char obj[] =
"v 0 0 0\n"
"v 1 0 0\n"
"v 0 1 0\n"
"usemtl Material\n"
"f 0 1 2\n";
const char mtl[] =
"newmtl Material\n"
"Ka 1 0 0\n"
"Kd 0 1 0\n"
"Ks 0 0 1\n";
ufbx_load_opts opts = { 0 };
opts.obj_mtl_data.data = mtl;
opts.obj_mtl_data.size = sizeof(mtl) - 1;
ufbx_scene *scene = ufbx_load_memory(obj, sizeof(obj) - 1, &opts, NULL);
ufbxt_assert(scene);
ufbxt_assert(scene->meshes.count == 1);
ufbx_mesh *mesh = scene->meshes.data[0];
ufbxt_assert(mesh->faces.count == 1);
ufbxt_assert(mesh->materials.count == 1);
ufbx_material *material = mesh->materials.data[0].material;
ufbxt_assert(!strcmp(material->name.data, "Material"));
ufbx_vec3 ka = { 1.0f, 0.0f, 0.0f };
ufbx_vec3 kd = { 0.0f, 1.0f, 0.0f };
ufbx_vec3 ks = { 0.0f, 0.0f, 1.0f };
ufbxt_diff_error err = { 0 };
ufbxt_assert_close_vec3(&err, material->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(&err, material->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(&err, material->fbx.specular_color.value_vec3, ks);
if (err.num > 0) {
ufbx_real avg = err.sum / (ufbx_real)err.num;
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", avg, err.max, err.num);
}
ufbx_free_scene(scene);
}
#endif
UFBXT_TEST(obj_opts_mtl_path)
#if UFBXT_IMPL
{
const char obj[] =
"v 0 0 0\n"
"v 1 0 0\n"
"v 0 1 0\n"
"usemtl RGB\n"
"f 0 1 2\n";
char buf[512];
snprintf(buf, sizeof(buf), "%ssynthetic_simple_materials_0_mtl.mtl", data_root);
ufbx_load_opts opts = { 0 };
opts.obj_mtl_path.data = buf;
opts.obj_mtl_path.length = SIZE_MAX;
ufbx_scene *scene = ufbx_load_memory(obj, sizeof(obj) - 1, &opts, NULL);
ufbxt_assert(scene);
ufbxt_assert(scene->meshes.count == 1);
ufbx_mesh *mesh = scene->meshes.data[0];
ufbxt_assert(mesh->faces.count == 1);
ufbxt_assert(mesh->materials.count == 1);
ufbx_material *material = mesh->materials.data[0].material;
ufbxt_assert(!strcmp(material->name.data, "RGB"));
ufbx_vec3 ka = { 1.0f, 0.0f, 0.0f };
ufbx_vec3 kd = { 0.0f, 1.0f, 0.0f };
ufbx_vec3 ks = { 0.0f, 0.0f, 1.0f };
ufbxt_diff_error err = { 0 };
ufbxt_assert_close_vec3(&err, material->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(&err, material->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(&err, material->fbx.specular_color.value_vec3, ks);
if (err.num > 0) {
ufbx_real avg = err.sum / (ufbx_real)err.num;
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", avg, err.max, err.num);
}
ufbx_free_scene(scene);
}
#endif
UFBXT_TEST(obj_opts_no_extrnal_files)
#if UFBXT_IMPL
{
char path[512];
ufbxt_diff_error err = { 0 };
ufbxt_file_iterator iter = { "blender_279_ball" };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
for (int load_external = 0; load_external <= 1; load_external++) {
ufbx_load_opts opts = { 0 };
opts.load_external_files = load_external != 0;
ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ) {
ufbx_material *red = ufbx_find_material(scene, "Red");
ufbx_material *white = ufbx_find_material(scene, "White");
ufbxt_assert(red);
ufbxt_assert(white);
if (load_external) {
ufbx_vec3 ref_red = { 0.8f, 0.0f, 0.0f };
ufbx_vec3 ref_white = { 0.8f, 0.8f, 0.8f };
ufbxt_assert_close_vec3(&err, red->fbx.diffuse_color.value_vec3, ref_red);
ufbxt_assert_close_vec3(&err, white->fbx.diffuse_color.value_vec3, ref_white);
} else {
ufbxt_assert(red->props.props.count == 0);
ufbxt_assert(white->props.props.count == 0);
}
}
ufbx_free_scene(scene);
}
}
if (err.num > 0) {
ufbx_real avg = err.sum / (ufbx_real)err.num;
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", avg, err.max, err.num);
}
}
#endif
UFBXT_TEST(obj_opts_no_extrnal_files_by_filename)
#if UFBXT_IMPL
{
char path[512];
ufbxt_diff_error err = { 0 };
ufbxt_file_iterator iter = { "synthetic_filename_mtl" };
while (ufbxt_next_file(&iter, path, sizeof(path))) {
for (int load_by_filename = 0; load_by_filename <= 1; load_by_filename++)
for (int load_external = 0; load_external <= 1; load_external++) {
ufbx_load_opts opts = { 0 };
opts.load_external_files = load_external != 0;
opts.obj_search_mtl_by_filename = load_by_filename != 0;
ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
ufbxt_assert(scene);
ufbxt_check_scene(scene);
if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ) {
ufbx_material *material = ufbx_find_material(scene, "Material");
ufbxt_assert(material);
if (load_external && load_by_filename) {
ufbxt_check_warning(scene, UFBX_WARNING_IMPLICIT_MTL, 1, "synthetic_filename_mtl_0_obj.mtl");
ufbx_vec3 ka = { 1.0f, 0.0f, 0.0f };
ufbx_vec3 kd = { 0.0f, 1.0f, 0.0f };
ufbx_vec3 ks = { 0.0f, 0.0f, 1.0f };
ufbxt_assert_close_vec3(&err, material->fbx.ambient_color.value_vec3, ka);
ufbxt_assert_close_vec3(&err, material->fbx.diffuse_color.value_vec3, kd);
ufbxt_assert_close_vec3(&err, material->fbx.specular_color.value_vec3, ks);
} else {
ufbxt_assert(material->props.props.count == 0);
}
}
ufbx_free_scene(scene);
}
}
if (err.num > 0) {
ufbx_real avg = err.sum / (ufbx_real)err.num;
ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", avg, err.max, err.num);
}
}
#endif
#if UFBXT_IMPL
static void ufbxt_check_only_texture(ufbx_material_map *map, const char *filename)
{
ufbxt_assert(!map->has_value);
ufbxt_assert(map->texture);
ufbxt_assert(!strcmp(map->texture->relative_filename.data, filename));
}
#endif
UFBXT_FILE_TEST(synthetic_map_feature)
#if UFBXT_IMPL
{
{
ufbx_material *material = ufbx_find_material(scene, "Phong");
ufbxt_assert(material->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_check_only_texture(&material->fbx.diffuse_color, "diffuse.png");
ufbxt_check_only_texture(&material->fbx.specular_color, "specular.png");
ufbxt_assert(!material->features.pbr.enabled);
ufbxt_assert(!material->features.sheen.enabled);
ufbxt_assert(!material->features.coat.enabled);
ufbxt_assert(!material->features.metalness.enabled);
ufbxt_assert(!material->features.ior.enabled);
ufbxt_assert(!material->features.opacity.enabled);
ufbxt_assert(!material->features.transmission.enabled);
ufbxt_assert(!material->features.emission.enabled);
}
{
ufbx_material *material = ufbx_find_material(scene, "PBR");
ufbxt_assert(material->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_check_only_texture(&material->pbr.roughness, "roughness.png");
ufbxt_check_only_texture(&material->pbr.metalness, "metalness.png");
ufbxt_assert(material->features.pbr.enabled);
ufbxt_assert(!material->features.sheen.enabled);
ufbxt_assert(!material->features.coat.enabled);
ufbxt_assert(material->features.metalness.enabled);
ufbxt_assert(!material->features.ior.enabled);
ufbxt_assert(!material->features.opacity.enabled);
ufbxt_assert(!material->features.transmission.enabled);
ufbxt_assert(!material->features.emission.enabled);
}
{
ufbx_material *material = ufbx_find_material(scene, "Extended");
ufbxt_assert(material->shader_type == UFBX_SHADER_WAVEFRONT_MTL);
ufbxt_check_only_texture(&material->pbr.sheen_color, "sheen.png");
ufbxt_check_only_texture(&material->pbr.coat_factor, "coat.png");
ufbxt_check_only_texture(&material->pbr.metalness, "metalness.png");
ufbxt_check_only_texture(&material->pbr.specular_ior, "ior.png");
ufbxt_check_only_texture(&material->pbr.opacity, "opacity.png");
ufbxt_check_only_texture(&material->pbr.transmission_color, "transmission.png");
ufbxt_check_only_texture(&material->pbr.emission_color, "emission.png");
ufbxt_assert(material->features.pbr.enabled);
ufbxt_assert(material->features.sheen.enabled);
ufbxt_assert(material->features.coat.enabled);
ufbxt_assert(material->features.metalness.enabled);
ufbxt_assert(material->features.ior.enabled);
ufbxt_assert(material->features.opacity.enabled);
ufbxt_assert(material->features.transmission.enabled);
ufbxt_assert(material->features.emission.enabled);
}
}
#endif
UFBXT_FILE_TEST(blender_340_line_point)
#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_faces == 6);
ufbxt_assert(mesh->num_empty_faces == 0);
ufbxt_assert(mesh->num_point_faces == 0);
ufbxt_assert(mesh->num_line_faces == 5);
}
#endif
UFBXT_FILE_TEST(synthetic_extended_line)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Line");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 4);
ufbxt_assert(mesh->num_empty_faces == 0);
ufbxt_assert(mesh->num_point_faces == 0);
ufbxt_assert(mesh->num_line_faces == 4);
ufbxt_assert(mesh->num_indices == 8);
ufbx_vec3 pos_ref[] = {
{ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f },
{ 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 0.0f },
{ 1.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 0.0f },
};
ufbx_vec2 uv_ref[] = {
{ 0.0f, 0.0f }, { 2.0f, 0.0f },
{ 2.0f, 0.0f }, { 0.0f, 2.0f },
{ 0.0f, 2.0f }, { 2.0f, 2.0f },
{ 2.0f, 2.0f }, { 0.0f, 0.0f },
};
ufbx_vec4 col_ref[] = {
{ 1.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f },
{ 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f },
{ 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f },
{ 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f },
};
for (size_t i = 0; i < mesh->num_indices; i++) {
ufbxt_hintf("i=%zu", i);
ufbxt_assert_close_vec3(err, pos_ref[i], ufbx_get_vertex_vec3(&mesh->vertex_position, i));
ufbxt_assert_close_vec2(err, uv_ref[i], ufbx_get_vertex_vec2(&mesh->vertex_uv, i));
ufbxt_assert_close_vec4(err, col_ref[i], ufbx_get_vertex_vec4(&mesh->vertex_color, i));
}
}
#endif
UFBXT_FILE_TEST(synthetic_extended_points)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Points");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 3);
ufbxt_assert(mesh->num_empty_faces == 0);
ufbxt_assert(mesh->num_point_faces == 3);
ufbxt_assert(mesh->num_line_faces == 0);
ufbxt_assert(mesh->num_indices == 3);
ufbx_vec3 pos_ref[] = {
{ 0.0f, 0.0f, 0.0f },
{ 1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
};
ufbx_vec4 col_ref[] = {
{ 1.0f, 0.0f, 0.0f, 1.0f },
{ 0.0f, 1.0f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 1.0f, 1.0f },
};
for (size_t i = 0; i < mesh->num_indices; i++) {
ufbxt_hintf("i=%zu", i);
ufbxt_assert_close_vec3(err, pos_ref[i], ufbx_get_vertex_vec3(&mesh->vertex_position, i));
ufbxt_assert_close_vec4(err, col_ref[i], ufbx_get_vertex_vec4(&mesh->vertex_color, i));
}
}
#endif
UFBXT_FILE_TEST(synthetic_empty_face)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Object");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 0);
ufbxt_assert(mesh->num_empty_faces == 0);
}
#endif
#if UFBXT_IMPL
static ufbx_load_opts ufbxt_allow_empty_faces_opts()
{
ufbx_load_opts opts = { 0 };
opts.allow_empty_faces = true;
return opts;
}
#endif
UFBXT_FILE_TEST_OPTS_ALT(synthetic_empty_face_allow, synthetic_empty_face, ufbxt_allow_empty_faces_opts)
#if UFBXT_IMPL
{
ufbx_node *node = ufbx_find_node(scene, "Object");
ufbxt_assert(node && node->mesh);
ufbx_mesh *mesh = node->mesh;
ufbxt_assert(mesh->num_faces == 1);
ufbxt_assert(mesh->num_empty_faces == 1);
ufbxt_assert(mesh->faces.data[0].index_begin == 0);
ufbxt_assert(mesh->faces.data[0].num_indices == 0);
}
#endif