#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