#undef UFBXT_TEST_GROUP #define UFBXT_TEST_GROUP "mesh" UFBXT_FILE_TEST(blender_279_default) #if UFBXT_IMPL { if (scene->metadata.ascii) { ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_BLENDER_ASCII); ufbxt_assert(scene->metadata.exporter_version == ufbx_pack_version(2, 79, 0)); } else { ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_BLENDER_BINARY); ufbxt_assert(scene->metadata.exporter_version == ufbx_pack_version(3, 7, 13)); } ufbx_node *node = ufbx_find_node(scene, "Lamp"); ufbxt_assert(node); ufbx_light *light = node->light; ufbxt_assert(light); // Light attribute properties ufbx_vec3 color_ref = { 1.0, 1.0, 1.0 }; ufbx_prop *color = ufbx_find_prop(&light->props, "Color"); ufbxt_assert(color && color->type == UFBX_PROP_COLOR); ufbxt_assert_close_vec3(err, color->value_vec3, color_ref); ufbx_prop *intensity = ufbx_find_prop(&light->props, "Intensity"); ufbxt_assert(intensity && intensity->type == UFBX_PROP_NUMBER); ufbxt_assert_close_real(err, intensity->value_real, 100.0); // Model properties ufbx_vec3 translation_ref = { 4.076245307922363, 5.903861999511719, -1.0054539442062378 }; ufbx_prop *translation = ufbx_find_prop(&node->props, "Lcl Translation"); ufbxt_assert(translation && translation->type == UFBX_PROP_TRANSLATION); ufbxt_assert_close_vec3(err, translation->value_vec3, translation_ref); // Model defaults ufbx_vec3 scaling_ref = { 1.0, 1.0, 1.0 }; ufbx_prop *scaling = ufbx_find_prop(&node->props, "GeometricScaling"); ufbxt_assert(scaling && scaling->type == UFBX_PROP_VECTOR); ufbxt_assert_close_vec3(err, scaling->value_vec3, scaling_ref); } #endif UFBXT_FILE_TEST(blender_282_suzanne) #if UFBXT_IMPL { } #endif UFBXT_FILE_TEST(blender_282_suzanne_and_transform) #if UFBXT_IMPL { } #endif UFBXT_FILE_TEST(maya_cube) #if UFBXT_IMPL { ufbxt_assert(scene->metadata.exporter == UFBX_EXPORTER_FBX_SDK); ufbxt_assert(scene->metadata.exporter_version == ufbx_pack_version(2019, 2, 0)); ufbxt_assert(!strcmp(scene->metadata.original_application.vendor.data, "Autodesk")); ufbxt_assert(!strcmp(scene->metadata.original_application.name.data, "Maya")); ufbxt_assert(!strcmp(scene->metadata.original_application.version.data, "201900")); ufbxt_assert(!strcmp(scene->metadata.latest_application.vendor.data, "Autodesk")); ufbxt_assert(!strcmp(scene->metadata.latest_application.name.data, "Maya")); ufbxt_assert(!strcmp(scene->metadata.latest_application.version.data, "201900")); ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(!mesh->generated_normals); for (size_t face_i = 0; face_i < mesh->num_faces; face_i++) { ufbx_face face = mesh->faces.data[face_i]; for (size_t i = face.index_begin; i < face.index_begin + face.num_indices; i++) { ufbx_vec3 n = ufbx_get_vertex_vec3(&mesh->vertex_normal, i); ufbx_vec3 b = ufbx_get_vertex_vec3(&mesh->vertex_bitangent, i); ufbx_vec3 t = ufbx_get_vertex_vec3(&mesh->vertex_tangent, i); ufbxt_assert_close_real(err, ufbxt_dot3(n, n), 1.0); ufbxt_assert_close_real(err, ufbxt_dot3(b, b), 1.0); ufbxt_assert_close_real(err, ufbxt_dot3(t, t), 1.0); ufbxt_assert_close_real(err, ufbxt_dot3(n, b), 0.0); ufbxt_assert_close_real(err, ufbxt_dot3(n, t), 0.0); ufbxt_assert_close_real(err, ufbxt_dot3(b, t), 0.0); for (size_t j = 0; j < face.num_indices; j++) { ufbx_vec3 p0 = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + j); ufbx_vec3 p1 = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + (j + 1) % face.num_indices); ufbx_vec3 edge; edge.x = p1.x - p0.x; edge.y = p1.y - p0.y; edge.z = p1.z - p0.z; ufbxt_assert_close_real(err, ufbxt_dot3(edge, edge), 1.0); ufbxt_assert_close_real(err, ufbxt_dot3(n, edge), 0.0); } } } } #endif UFBXT_FILE_TEST(maya_color_sets) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->color_sets.count == 4); ufbxt_assert(!strcmp(mesh->color_sets.data[0].name.data, "RGBCube")); ufbxt_assert(!strcmp(mesh->color_sets.data[1].name.data, "White")); ufbxt_assert(!strcmp(mesh->color_sets.data[2].name.data, "Black")); ufbxt_assert(!strcmp(mesh->color_sets.data[3].name.data, "Alpha")); for (size_t i = 0; i < mesh->num_indices; i++) { ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, i); ufbx_vec4 refs[4] = { { 0.0, 0.0, 0.0, 1.0 }, { 1.0, 1.0, 1.0, 1.0 }, { 0.0, 0.0, 0.0, 1.0 }, { 1.0, 1.0, 1.0, 0.0 }, }; refs[0].x = pos.x + 0.5; refs[0].y = pos.y + 0.5; refs[0].z = pos.z + 0.5; refs[3].w = (pos.x + 0.5) * 0.1 + (pos.y + 0.5) * 0.2 + (pos.z + 0.5) * 0.4; for (size_t set_i = 0; set_i < 4; set_i++) { ufbx_vec4 color = ufbx_get_vertex_vec4(&mesh->color_sets.data[set_i].vertex_color, i); ufbxt_assert_close_vec4(err, color, refs[set_i]); } } } #endif UFBXT_FILE_TEST(maya_uv_sets) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->uv_sets.count == 3); ufbxt_assert(!strcmp(mesh->uv_sets.data[0].name.data, "Default")); ufbxt_assert(!strcmp(mesh->uv_sets.data[1].name.data, "PerFace")); ufbxt_assert(!strcmp(mesh->uv_sets.data[2].name.data, "Row")); size_t counts1[2][2] = { 0 }; size_t counts2[7][2] = { 0 }; for (size_t i = 0; i < mesh->num_indices; i++) { ufbx_vec2 uv0 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[0].vertex_uv, i); ufbx_vec2 uv1 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[1].vertex_uv, i); ufbx_vec2 uv2 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[2].vertex_uv, i); ufbxt_assert(uv0.x > 0.05f && uv0.y > 0.05f && uv0.x < 0.95f && uv0.y < 0.95f); int x1 = (int)(uv1.x + 0.5f), y1 = (int)(uv1.y + 0.5f); int x2 = (int)(uv2.x + 0.5f), y2 = (int)(uv2.y + 0.5f); ufbxt_assert_close_real(err, uv1.x - (ufbx_real)x1, 0.0); ufbxt_assert_close_real(err, uv1.y - (ufbx_real)y1, 0.0); ufbxt_assert_close_real(err, uv2.x - (ufbx_real)x2, 0.0); ufbxt_assert_close_real(err, uv2.y - (ufbx_real)y2, 0.0); ufbxt_assert(x1 >= 0 && x1 <= 1 && y1 >= 0 && y1 <= 1); ufbxt_assert(x2 >= 0 && x2 <= 6 && y2 >= 0 && y2 <= 1); counts1[x1][y1]++; counts2[x2][y2]++; } ufbxt_assert(counts1[0][0] == 6); ufbxt_assert(counts1[0][1] == 6); ufbxt_assert(counts1[1][0] == 6); ufbxt_assert(counts1[1][1] == 6); for (size_t i = 0; i < 7; i++) { size_t n = (i == 0 || i == 6) ? 1 : 2; ufbxt_assert(counts2[i][0] == n); ufbxt_assert(counts2[i][1] == n); } } #endif UFBXT_FILE_TEST(blender_279_color_sets) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Cube"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->color_sets.count == 3); ufbxt_assert(!strcmp(mesh->color_sets.data[0].name.data, "RGBCube")); ufbxt_assert(!strcmp(mesh->color_sets.data[1].name.data, "White")); ufbxt_assert(!strcmp(mesh->color_sets.data[2].name.data, "Black")); for (size_t i = 0; i < mesh->num_indices; i++) { ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, i); ufbx_vec4 refs[3] = { { 0.0, 0.0, 0.0, 1.0 }, { 1.0, 1.0, 1.0, 1.0 }, { 0.0, 0.0, 0.0, 1.0 }, }; refs[0].x = pos.x + 0.5; refs[0].y = pos.y + 0.5; refs[0].z = pos.z + 0.5; for (size_t set_i = 0; set_i < 3; set_i++) { ufbx_vec4 color = ufbx_get_vertex_vec4(&mesh->color_sets.data[set_i].vertex_color, i); ufbxt_assert_close_vec4(err, color, refs[set_i]); } } } #endif UFBXT_FILE_TEST(blender_279_uv_sets) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Cube"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->uv_sets.count == 3); ufbxt_assert(!strcmp(mesh->uv_sets.data[0].name.data, "Default")); ufbxt_assert(!strcmp(mesh->uv_sets.data[1].name.data, "PerFace")); ufbxt_assert(!strcmp(mesh->uv_sets.data[2].name.data, "Row")); size_t counts1[2][2] = { 0 }; size_t counts2[7][2] = { 0 }; for (size_t i = 0; i < mesh->num_indices; i++) { ufbx_vec2 uv0 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[0].vertex_uv, i); ufbx_vec2 uv1 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[1].vertex_uv, i); ufbx_vec2 uv2 = ufbx_get_vertex_vec2(&mesh->uv_sets.data[2].vertex_uv, i); ufbxt_assert(uv0.x > 0.05f && uv0.y > 0.05f && uv0.x < 0.95f && uv0.y < 0.95f); int x1 = (int)(uv1.x + 0.5f), y1 = (int)(uv1.y + 0.5f); int x2 = (int)(uv2.x + 0.5f), y2 = (int)(uv2.y + 0.5f); ufbxt_assert_close_real(err, uv1.x - (ufbx_real)x1, 0.0); ufbxt_assert_close_real(err, uv1.y - (ufbx_real)y1, 0.0); ufbxt_assert_close_real(err, uv2.x - (ufbx_real)x2, 0.0); ufbxt_assert_close_real(err, uv2.y - (ufbx_real)y2, 0.0); ufbxt_assert(x1 >= 0 && x1 <= 1 && y1 >= 0 && y1 <= 1); ufbxt_assert(x2 >= 0 && x2 <= 6 && y2 >= 0 && y2 <= 1); counts1[x1][y1]++; counts2[x2][y2]++; } ufbxt_assert(counts1[0][0] == 6); ufbxt_assert(counts1[0][1] == 6); ufbxt_assert(counts1[1][0] == 6); ufbxt_assert(counts1[1][1] == 6); for (size_t i = 0; i < 7; i++) { size_t n = (i == 0 || i == 6) ? 1 : 2; ufbxt_assert(counts2[i][0] == n); ufbxt_assert(counts2[i][1] == n); } } #endif UFBXT_FILE_TEST(synthetic_sets_reorder) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->color_sets.count == 4); ufbxt_assert(!strcmp(mesh->color_sets.data[0].name.data, "RGBCube")); ufbxt_assert(!strcmp(mesh->color_sets.data[1].name.data, "White")); ufbxt_assert(!strcmp(mesh->color_sets.data[2].name.data, "Black")); ufbxt_assert(!strcmp(mesh->color_sets.data[3].name.data, "Alpha")); ufbxt_assert(!strcmp(mesh->uv_sets.data[0].name.data, "Default")); ufbxt_assert(!strcmp(mesh->uv_sets.data[1].name.data, "PerFace")); ufbxt_assert(!strcmp(mesh->uv_sets.data[2].name.data, "Row")); } #endif UFBXT_FILE_TEST(maya_cone) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCone1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertex_crease.exists); ufbxt_assert(mesh->edges.count); ufbxt_assert(mesh->edge_crease.count); ufbxt_assert(mesh->edge_smoothing.count); ufbxt_assert(mesh->faces.data[0].num_indices == 16); for (size_t i = 0; i < mesh->num_indices; i++) { ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, i); ufbx_real crease = ufbx_get_vertex_real(&mesh->vertex_crease, i); ufbxt_assert_close_real(err, crease, pos.y > 0.0 ? 0.998 : 0.0); } for (size_t i = 0; i < mesh->num_edges; i++) { ufbx_edge edge = mesh->edges.data[i]; ufbx_real crease = mesh->edge_crease.data[i]; bool smoothing = mesh->edge_smoothing.data[i]; ufbx_vec3 a = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.a); ufbx_vec3 b = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.b); if (a.y < 0.0 && b.y < 0.0) { ufbxt_assert_close_real(err, crease, 0.583); ufbxt_assert(!smoothing); } else { ufbxt_assert(a.y > 0.0 || b.y > 0.0); ufbxt_assert_close_real(err, crease, 0.0); ufbxt_assert(smoothing); } } } #endif #if UFBXT_IMPL static void ufbxt_check_tangent_space(ufbxt_diff_error *err, ufbx_mesh *mesh) { for (size_t set_i = 0; set_i < mesh->uv_sets.count; set_i++) { ufbx_uv_set set = mesh->uv_sets.data[set_i]; ufbxt_assert(set.vertex_uv.exists); ufbxt_assert(set.vertex_bitangent.exists); ufbxt_assert(set.vertex_tangent.exists); for (size_t face_i = 0; face_i < mesh->num_faces; face_i++) { ufbx_face face = mesh->faces.data[face_i]; for (size_t i = 0; i < face.num_indices; i++) { size_t a = face.index_begin + i; size_t b = face.index_begin + (i + 1) % face.num_indices; ufbx_vec3 pa = ufbx_get_vertex_vec3(&mesh->vertex_position, a); ufbx_vec3 pb = ufbx_get_vertex_vec3(&mesh->vertex_position, b); ufbx_vec3 ba = ufbx_get_vertex_vec3(&set.vertex_bitangent, a); ufbx_vec3 bb = ufbx_get_vertex_vec3(&set.vertex_bitangent, b); ufbx_vec3 ta = ufbx_get_vertex_vec3(&set.vertex_tangent, a); ufbx_vec3 tb = ufbx_get_vertex_vec3(&set.vertex_tangent, b); ufbx_vec2 ua = ufbx_get_vertex_vec2(&set.vertex_uv, a); ufbx_vec2 ub = ufbx_get_vertex_vec2(&set.vertex_uv, b); ufbx_vec3 dp = ufbxt_sub3(pb, pa); ufbx_vec2 du = ufbxt_sub2(ua, ub); ufbx_real dp_len = sqrt(ufbxt_dot3(dp, dp)); dp.x /= dp_len; dp.y /= dp_len; dp.z /= dp_len; ufbx_real du_len = sqrt(ufbxt_dot2(du, du)); du.x /= du_len; du.y /= du_len; ufbx_real dba = ufbxt_dot3(dp, ba); ufbx_real dbb = ufbxt_dot3(dp, bb); ufbx_real dta = ufbxt_dot3(dp, ta); ufbx_real dtb = ufbxt_dot3(dp, tb); ufbxt_assert_close_real(err, dba, dbb); ufbxt_assert_close_real(err, dta, dtb); ufbxt_assert_close_real(err, ub.x - ua.x, dta); ufbxt_assert_close_real(err, ub.y - ua.y, dba); } } } } #endif UFBXT_FILE_TEST(maya_uv_set_tangents) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_check_tangent_space(err, mesh); } #endif UFBXT_FILE_TEST(blender_279_uv_set_tangents) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Plane"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_check_tangent_space(err, mesh); } #endif UFBXT_FILE_TEST(synthetic_tangents_reorder) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_check_tangent_space(err, mesh); } #endif UFBXT_FILE_TEST(blender_279_ball) #if UFBXT_IMPL { ufbx_material *red = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "Red"); ufbx_material *white = (ufbx_material*)ufbx_find_element(scene, UFBX_ELEMENT_MATERIAL, "White"); ufbxt_assert(red && !strcmp(red->name.data, "Red")); ufbxt_assert(white && !strcmp(white->name.data, "White")); ufbx_vec3 red_ref = { 0.8, 0.0, 0.0 }; ufbx_vec3 white_ref = { 0.8, 0.8, 0.8 }; ufbxt_assert_close_vec3(err, red->fbx.diffuse_color.value_vec3, red_ref); ufbxt_assert_close_vec3(err, white->fbx.diffuse_color.value_vec3, white_ref); ufbx_node *node = ufbx_find_node(scene, "Icosphere"); ufbxt_assert(node); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh); ufbxt_assert(mesh->face_material.count); ufbxt_assert(mesh->face_smoothing.count); ufbxt_assert(mesh->materials.count == 2); ufbxt_assert(mesh->materials.data[0].material == red); ufbxt_assert(mesh->materials.data[1].material == white); for (size_t face_i = 0; face_i < mesh->num_faces; face_i++) { ufbx_face face = mesh->faces.data[face_i]; ufbx_vec3 mid = { 0 }; for (size_t i = 0; i < face.num_indices; i++) { mid = ufbxt_add3(mid, ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i)); } mid.x /= (ufbx_real)face.num_indices; mid.y /= (ufbx_real)face.num_indices; mid.z /= (ufbx_real)face.num_indices; bool smoothing = mesh->face_smoothing.data[face_i]; int32_t material = mesh->face_material.data[face_i]; ufbxt_assert(smoothing == (mid.x > 0.0)); ufbxt_assert(material == (mid.z < 0.0 ? 1 : 0)); } } #endif UFBXT_FILE_TEST(synthetic_broken_material) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->materials.count == 0); ufbxt_assert(mesh->face_material.data == NULL); } #endif UFBXT_FILE_TEST(maya_uv_and_color_sets) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->uv_sets.count == 2); ufbxt_assert(mesh->color_sets.count == 2); ufbxt_assert(!strcmp(mesh->uv_sets.data[0].name.data, "UVA")); ufbxt_assert(!strcmp(mesh->uv_sets.data[1].name.data, "UVB")); ufbxt_assert(!strcmp(mesh->color_sets.data[0].name.data, "ColorA")); ufbxt_assert(!strcmp(mesh->color_sets.data[1].name.data, "ColorB")); } #endif UFBXT_FILE_TEST(maya_bad_face) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_empty_faces == 0); ufbxt_assert(mesh->num_point_faces == 1); ufbxt_assert(mesh->num_line_faces == 1); ufbxt_assert(mesh->num_triangles == 7); ufbxt_assert(mesh->num_faces == 6); ufbxt_assert(mesh->faces.data[0].num_indices == 1); ufbxt_assert(mesh->faces.data[1].num_indices == 2); ufbxt_assert(mesh->faces.data[2].num_indices == 3); ufbxt_assert(mesh->faces.data[3].num_indices == 4); ufbxt_assert(mesh->faces.data[0].index_begin == 0); ufbxt_assert(mesh->faces.data[1].index_begin == 1); ufbxt_assert(mesh->faces.data[2].index_begin == 3); ufbxt_assert(mesh->faces.data[3].index_begin == 6); // ??? Maya exports an edge for the single point ufbxt_assert(mesh->num_edges == 12); ufbxt_assert(mesh->edges.data[0].a == 0); ufbxt_assert(mesh->edges.data[0].b == 0); ufbxt_assert(mesh->edges.data[1].a == 2); ufbxt_assert(mesh->edges.data[1].b == 1); } #endif UFBXT_FILE_TEST(blender_279_edge_vertex) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Plane"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_empty_faces == 0); ufbxt_assert(mesh->num_point_faces == 0); ufbxt_assert(mesh->num_line_faces == 1); ufbxt_assert(mesh->num_triangles == 0); ufbxt_assert(mesh->num_faces == 1); ufbxt_assert(mesh->faces.data[0].index_begin == 0); ufbxt_assert(mesh->faces.data[0].num_indices == 2); // ??? Maya exports an edge for the single point if (scene->metadata.version == 7400) { ufbxt_assert(mesh->num_edges == 1); ufbxt_assert(mesh->edges.data[0].a == 0); ufbxt_assert(mesh->edges.data[0].b == 1); } else { // 6100 has an edge for both directions ufbxt_assert(mesh->num_edges == 2); ufbxt_assert(mesh->edges.data[0].a == 0); ufbxt_assert(mesh->edges.data[0].b == 1); ufbxt_assert(mesh->edges.data[1].a == 1); ufbxt_assert(mesh->edges.data[1].b == 0); } } #endif UFBXT_FILE_TEST(blender_279_edge_circle) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Circle"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_indices == 512); // ??? The 7400_binary export starts off with individual edges but has a // massive N-gon for the rest of it. if (scene->metadata.version == 7400) { ufbxt_assert(mesh->num_line_faces == 127); ufbxt_assert(mesh->num_faces == 128); for (size_t i = 0; i < 127; i++) { ufbxt_assert(mesh->faces.data[i].num_indices == 2); } ufbxt_assert(mesh->faces.data[127].num_indices == 258); ufbxt_assert(mesh->num_edges == 256); } else { ufbxt_assert(mesh->num_line_faces == 256); ufbxt_assert(mesh->num_faces == 256); for (size_t i = 0; i < 256; i++) { ufbxt_assert(mesh->faces.data[i].num_indices == 2); } // 6100 has an edge for both directions ufbxt_assert(mesh->num_edges == 512); } } #endif UFBXT_FILE_TEST(blender_293_instancing) #if UFBXT_IMPL { ufbxt_assert(scene->meshes.count == 1); ufbx_mesh *mesh = scene->meshes.data[0]; ufbxt_assert(mesh->instances.count == 8); } #endif UFBXT_FILE_TEST(synthetic_indexed_by_vertex) #if UFBXT_IMPL { ufbxt_assert(scene->meshes.count == 1); ufbx_mesh *mesh = scene->meshes.data[0]; for (size_t vi = 0; vi < mesh->num_vertices; vi++) { int32_t ii = mesh->vertex_first_index.data[vi]; ufbx_vec3 pos = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[ii]]; ufbx_vec3 normal = mesh->vertex_normal.values.data[mesh->vertex_normal.indices.data[ii]]; ufbx_vec3 ref_normal = { 0.0f, pos.y > 0.0f ? 1.0f : -1.0f, 0.0f }; ufbxt_assert_close_vec3(err, normal, ref_normal); } for (size_t ii = 0; ii < mesh->num_indices; ii++) { ufbx_vec3 pos = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[ii]]; ufbx_vec3 normal = mesh->vertex_normal.values.data[mesh->vertex_normal.indices.data[ii]]; ufbx_vec3 ref_normal = { 0.0f, pos.y > 0.0f ? 1.0f : -1.0f, 0.0f }; ufbxt_assert_close_vec3(err, normal, ref_normal); } } #endif UFBXT_FILE_TEST(synthetic_by_vertex_bad_index) #if UFBXT_IMPL { ufbxt_check_warning(scene, UFBX_WARNING_INDEX_CLAMPED, 9, NULL); ufbxt_assert(scene->meshes.count == 1); ufbx_mesh *mesh = scene->meshes.data[0]; for (size_t vi = 0; vi < mesh->num_vertices; vi++) { int32_t ii = mesh->vertex_first_index.data[vi]; ufbx_vec3 pos = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[ii]]; ufbx_vec3 normal = mesh->vertex_normal.values.data[mesh->vertex_normal.indices.data[ii]]; ufbx_vec3 ref_normal = { 0.0f, pos.y > 0.0f ? 1.0f : -1.0f, 0.0f }; ufbxt_assert_close_vec3(err, normal, ref_normal); } for (size_t ii = 0; ii < mesh->num_indices; ii++) { ufbx_vec3 pos = mesh->vertex_position.values.data[mesh->vertex_position.indices.data[ii]]; ufbx_vec3 normal = mesh->vertex_normal.values.data[mesh->vertex_normal.indices.data[ii]]; ufbx_vec3 ref_normal = { 0.0f, pos.y > 0.0f ? 1.0f : -1.0f, 0.0f }; ufbxt_assert_close_vec3(err, normal, ref_normal); } } #endif UFBXT_FILE_TEST(synthetic_by_vertex_overflow) #if UFBXT_IMPL { ufbxt_check_warning(scene, UFBX_WARNING_INDEX_CLAMPED, 12, NULL); ufbxt_assert(scene->meshes.count == 1); ufbx_mesh *mesh = scene->meshes.data[0]; ufbxt_assert(mesh->vertex_normal.values.count == 4); for (size_t ii = 0; ii < mesh->num_indices; ii++) { uint32_t vertex_ix = mesh->vertex_position.indices.data[ii]; uint32_t normal_ix = mesh->vertex_normal.indices.data[ii]; if (vertex_ix < mesh->vertex_normal.values.count) { ufbxt_assert(normal_ix == vertex_ix); } else { ufbxt_assert(normal_ix == (uint32_t)mesh->vertex_normal.values.count - 1); } } } #endif UFBXT_FILE_TEST(maya_lod_group) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "LOD_Group_1"); ufbxt_assert(node); ufbxt_assert(node->attrib_type == UFBX_ELEMENT_LOD_GROUP); ufbx_lod_group *lod_group = (ufbx_lod_group*)node->attrib; ufbxt_assert(lod_group->element.type == UFBX_ELEMENT_LOD_GROUP); ufbxt_assert(node->children.count == 3); ufbxt_assert(lod_group->lod_levels.count == 3); ufbxt_assert(lod_group->relative_distances); ufbxt_assert(!lod_group->use_distance_limit); ufbxt_assert(!lod_group->ignore_parent_transform); ufbxt_assert(lod_group->lod_levels.data[0].display == UFBX_LOD_DISPLAY_USE_LOD); ufbxt_assert(lod_group->lod_levels.data[1].display == UFBX_LOD_DISPLAY_USE_LOD); ufbxt_assert(lod_group->lod_levels.data[2].display == UFBX_LOD_DISPLAY_USE_LOD); if (scene->metadata.version >= 7000) { ufbxt_assert_close_real(err, lod_group->lod_levels.data[0].distance, 100.0f); ufbxt_assert_close_real(err, lod_group->lod_levels.data[1].distance, 64.0f); ufbxt_assert_close_real(err, lod_group->lod_levels.data[2].distance, 32.0f); } } { ufbx_node *node = ufbx_find_node(scene, "LOD_Group_2"); ufbxt_assert(node); ufbxt_assert(node->attrib_type == UFBX_ELEMENT_LOD_GROUP); ufbx_lod_group *lod_group = (ufbx_lod_group*)node->attrib; ufbxt_assert(lod_group->element.type == UFBX_ELEMENT_LOD_GROUP); ufbxt_assert(node->children.count == 3); ufbxt_assert(lod_group->lod_levels.count == 3); ufbxt_assert(!lod_group->relative_distances); ufbxt_assert(!lod_group->use_distance_limit); ufbxt_assert(lod_group->ignore_parent_transform); ufbxt_assert(lod_group->lod_levels.data[0].display == UFBX_LOD_DISPLAY_USE_LOD); ufbxt_assert(lod_group->lod_levels.data[1].display == UFBX_LOD_DISPLAY_SHOW); ufbxt_assert(lod_group->lod_levels.data[2].display == UFBX_LOD_DISPLAY_HIDE); if (scene->metadata.version >= 7000) { ufbxt_assert_close_real(err, lod_group->lod_levels.data[0].distance, 0.0f); ufbxt_assert_close_real(err, lod_group->lod_levels.data[1].distance, 4.520276f); ufbxt_assert_close_real(err, lod_group->lod_levels.data[2].distance, 18.081102f); } } } #endif UFBXT_FILE_TEST(synthetic_missing_normals) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(!mesh->vertex_normal.exists); ufbxt_assert(!mesh->skinned_normal.exists); } #endif #if UFBXT_IMPL static ufbx_load_opts ufbxt_generate_normals_opts() { ufbx_load_opts opts = { 0 }; opts.generate_missing_normals = true; return opts; } #endif UFBXT_FILE_TEST_OPTS_ALT(synthetic_missing_normals_generated, synthetic_missing_normals, ufbxt_generate_normals_opts) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertex_normal.exists); ufbxt_assert(mesh->skinned_normal.exists); ufbxt_assert(mesh->generated_normals); for (size_t face_ix = 0; face_ix < mesh->faces.count; face_ix++) { ufbx_face face = mesh->faces.data[face_ix]; ufbx_vec3 normal = ufbx_get_weighted_face_normal(&mesh->vertex_position, face); normal = ufbxt_normalize(normal); for (size_t i = 0; i < face.num_indices; i++) { ufbx_vec3 mesh_normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, face.index_begin + i); ufbx_vec3 skinned_normal = ufbx_get_vertex_vec3(&mesh->skinned_normal, face.index_begin + i); ufbxt_assert_close_vec3(err, normal, mesh_normal); ufbxt_assert_close_vec3(err, normal, skinned_normal); } } } #endif UFBXT_FILE_TEST(blender_279_nested_meshes) #if UFBXT_IMPL { // Diff to .obj file with nested objects and FBXASC escaped names } #endif UFBXT_FILE_TEST(max_edge_visibility) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "Box001"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->edge_visibility.count > 0); { size_t num_visible = 0; // Diagonal edges should be hidden for (size_t i = 0; i < mesh->num_edges; i++) { ufbx_edge edge = mesh->edges.data[i]; ufbx_vec3 a = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.a); ufbx_vec3 b = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.b); ufbx_real len = ufbxt_length3(ufbxt_sub3(a, b)); bool expected = len < 21.0f; bool visible = mesh->edge_visibility.data[i]; ufbxt_assert(visible == expected); num_visible += visible ? 1u : 0u; } ufbxt_assert(mesh->num_edges == 18); ufbxt_assert(num_visible == 12); } ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, NULL, NULL); ufbxt_assert(sub_mesh); ufbxt_check_mesh(scene, sub_mesh); { size_t num_visible = 0; for (size_t i = 0; i < sub_mesh->num_edges; i++) { if (sub_mesh->edge_visibility.data[i]) { num_visible++; } } ufbxt_assert(num_visible == 12 * 4); } ufbx_free_mesh(sub_mesh); } { ufbx_node *node = ufbx_find_node(scene, "Cylinder001"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->edge_visibility.count > 0); size_t num_visible = 0; // Diagonal and edges to the center should be hidden for (size_t i = 0; i < mesh->num_edges; i++) { ufbx_edge edge = mesh->edges.data[i]; ufbx_vec3 a = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.a); ufbx_vec3 b = ufbx_get_vertex_vec3(&mesh->vertex_position, edge.b); ufbx_vec2 a2 = { a.x, a.y }; ufbx_vec2 b2 = { b.x, b.y }; ufbx_real len = ufbxt_length3(ufbxt_sub3(a, b)); ufbx_real len_a2 = ufbxt_length2(a2); ufbx_real len_b2 = ufbxt_length2(b2); bool expected = len < 20.7f && len_a2 > 0.1f && len_b2 > 0.1f; bool visible = mesh->edge_visibility.data[i]; ufbxt_assert(visible == expected); num_visible += visible ? 1u : 0u; } ufbxt_assert(mesh->num_edges == 54); ufbxt_assert(num_visible == 27); } } #endif UFBXT_FILE_TEST(zbrush_d20) #if UFBXT_IMPL { ufbxt_assert(scene->nodes.count == 3); { ufbx_node *node = ufbx_find_node(scene, "20 Sided"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_faces == 20); ufbxt_assert(mesh->face_group.count == 20); for (int32_t i = 0; i < 20; i++) { uint32_t group_ix = mesh->face_group.data[i]; ufbx_face_group *group = &mesh->face_groups.data[group_ix]; ufbxt_assert(group->id == 10 + i * 5); } } { ufbx_node *node = ufbx_find_node(scene, "PolyMesh3D1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_faces == 24); ufbxt_assert(mesh->face_group.count == 24); { uint32_t num_front = 0; for (size_t i = 0; i < 24; i++) { ufbx_face face = mesh->faces.data[i]; ufbx_vec3 normal = ufbx_get_weighted_face_normal(&mesh->vertex_position, face); int32_t group = normal.z < 0.0f ? 9598 : 15349; num_front += normal.z < 0.0f ? 1 : 0; uint32_t group_ix = mesh->face_group.data[i]; ufbxt_assert(mesh->face_groups.data[group_ix].id == group); } ufbxt_assert(num_front == mesh->num_faces / 2); } ufbxt_assert(mesh->blend_deformers.count > 0); ufbx_blend_deformer *blend = mesh->blend_deformers.data[0]; // WHAT? The 6100 version has duplicated blend shapes // and 7500 has duplicated blend deformers... if (scene->metadata.version == 6100) { ufbxt_assert(mesh->blend_deformers.count == 1); ufbxt_assert(blend->channels.count == 4); } else { ufbxt_assert(mesh->blend_deformers.count == 2); ufbxt_assert(blend->channels.count == 2); } // Check that poly groups work in subdivision ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, NULL, NULL); ufbxt_assert(sub_mesh); ufbxt_check_mesh(scene, sub_mesh); // Check that we didn't break the original mesh ufbxt_check_mesh(scene, mesh); { uint32_t num_front = 0; ufbxt_assert(sub_mesh->face_group.count == sub_mesh->num_faces); for (size_t i = 0; i < sub_mesh->num_faces; i++) { ufbx_face face = sub_mesh->faces.data[i]; ufbx_vec3 normal = ufbx_get_weighted_face_normal(&sub_mesh->vertex_position, face); int32_t group = normal.z < 0.0f ? 9598 : 15349; num_front += normal.z < 0.0f ? 1 : 0; uint32_t group_ix = sub_mesh->face_group.data[i]; ufbxt_assert(sub_mesh->face_groups.data[group_ix].id == group); } ufbxt_assert(num_front == sub_mesh->num_faces / 2); } ufbx_free_mesh(sub_mesh); } } #endif UFBXT_FILE_TEST(zbrush_d20_selection_set) #if UFBXT_IMPL { { ufbx_node *node = ufbx_find_node(scene, "PolyMesh3D1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; for (size_t i = 0; i < 2; i++) { bool front = i == 0; const char *name = front ? "PolyMesh3D1_9598" : "PolyMesh3D1_15349"; ufbx_selection_set *set = (ufbx_selection_set*)ufbx_find_element(scene, UFBX_ELEMENT_SELECTION_SET, name); ufbxt_assert(set); ufbxt_assert(set->nodes.count == 1); ufbx_selection_node *sel_node = set->nodes.data[0]; ufbxt_assert(sel_node->target_node == node); ufbxt_assert(sel_node->target_mesh == mesh); ufbxt_assert(sel_node->faces.count == 12); for (size_t i = 0; i < sel_node->faces.count; i++) { uint32_t index = sel_node->faces.data[i]; ufbxt_assert(index < mesh->faces.count); ufbx_face face = mesh->faces.data[index]; ufbx_vec3 normal = ufbx_get_weighted_face_normal(&mesh->vertex_position, face); ufbxt_assert((normal.z < 0.0f) == front); } } } } #endif UFBXT_FILE_TEST(maya_polygon_hole) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pCube1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_faces == 6); { size_t num_holes = 0; ufbxt_assert(mesh->face_hole.count == mesh->num_faces); for (size_t i = 0; i < mesh->num_faces; i++) { ufbx_face face = mesh->faces.data[i]; ufbx_vec3 avg_pos = ufbx_zero_vec3; for (size_t j = 0; j < face.num_indices; j++) { ufbx_vec3 p = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + j); avg_pos = ufbxt_add3(avg_pos, p); } avg_pos = ufbxt_mul3(avg_pos, 1.0f / (ufbx_real)face.num_indices); bool hole = fabs(avg_pos.y) > 0.49f; num_holes += hole ? 1 : 0; ufbxt_assert(mesh->face_hole.data[i] == hole); } ufbxt_assert(num_holes == 2); } ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, NULL, NULL); ufbxt_assert(sub_mesh); ufbxt_check_mesh(scene, sub_mesh); { size_t num_holes = 0; ufbxt_assert(sub_mesh->face_hole.count == sub_mesh->num_faces); for (size_t i = 0; i < sub_mesh->num_faces; i++) { ufbx_face face = sub_mesh->faces.data[i]; ufbx_vec3 avg_pos = ufbx_zero_vec3; for (size_t j = 0; j < face.num_indices; j++) { ufbx_vec3 p = ufbx_get_vertex_vec3(&sub_mesh->vertex_position, face.index_begin + j); avg_pos = ufbxt_add3(avg_pos, p); } avg_pos = ufbxt_mul3(avg_pos, 1.0f / (ufbx_real)face.num_indices); bool hole = fabs(avg_pos.y) > 0.49f; num_holes += hole ? 1 : 0; ufbxt_assert(sub_mesh->face_hole.data[i] == hole); } ufbxt_assert(num_holes == 32); } ufbx_free_mesh(sub_mesh); } #endif UFBXT_FILE_TEST_OPTS(synthetic_cursed_geometry, ufbxt_generate_normals_opts) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pDisc1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; uint32_t indices[64]; for (size_t i = 0; i < mesh->num_faces; i++) { ufbx_face face = mesh->faces.data[i]; size_t num_tris = ufbx_triangulate_face(indices, ufbxt_arraycount(indices), mesh, face); ufbxt_assert(num_tris == 0 || num_tris == face.num_indices - 2); } ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, 2, NULL, NULL); ufbx_free_mesh(sub_mesh); } #endif UFBXT_FILE_TEST(synthetic_vertex_gaps) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->vertices.count == 7); ufbx_vec3 gap_values[] = { { -1.0f, -1.1f, -1.2f }, { -2.0f, -2.1f, -2.2f }, { -3.0f, -3.1f, -3.2f }, { -4.0f, -4.1f, -4.2f }, }; ufbxt_assert_close_vec3(err, mesh->vertices.data[0], gap_values[0]); ufbxt_assert_close_vec3(err, mesh->vertices.data[3], gap_values[1]); ufbxt_assert_close_vec3(err, mesh->vertices.data[6], gap_values[2]); ufbxt_assert(mesh->vertex_normal.values.count == 8); ufbxt_assert_close_vec3(err, mesh->vertex_normal.values.data[0], gap_values[0]); ufbxt_assert_close_vec3(err, mesh->vertex_normal.values.data[3], gap_values[1]); ufbxt_assert_close_vec3(err, mesh->vertex_normal.values.data[6], gap_values[2]); ufbxt_assert_close_vec3(err, mesh->vertex_normal.values.data[7], gap_values[3]); ufbx_vec2 gap_uvs[] = { { -1.3f, -1.4f }, { -2.3f, -2.4f }, { -3.3f, -3.4f }, { -4.3f, -4.4f }, }; ufbxt_assert(mesh->vertex_uv.values.count == 8); ufbxt_assert_close_vec2(err, mesh->vertex_uv.values.data[0], gap_uvs[0]); ufbxt_assert_close_vec2(err, mesh->vertex_uv.values.data[2], gap_uvs[1]); ufbxt_assert_close_vec2(err, mesh->vertex_uv.values.data[5], gap_uvs[2]); ufbxt_assert_close_vec2(err, mesh->vertex_uv.values.data[7], gap_uvs[3]); } #endif #if UFBXT_IMPL typedef struct { size_t num_faces; size_t num_groups; } ufbxt_groups_per_face_count; #endif UFBXT_FILE_TEST(zbrush_polygroup_mess) #if UFBXT_IMPL { ufbxt_assert(scene->meshes.count == 1); ufbx_mesh *mesh = scene->meshes.data[0]; ufbxt_groups_per_face_count groups_per_face_count_ref[] = { { 1, 2 }, { 2, 1164 }, { 3, 1 }, { 4, 482 }, { 6, 181 }, { 8, 90 }, { 10, 26 }, { 12, 21 }, { 14, 8 }, { 16, 10 }, { 18, 5 }, { 20, 6 }, { 22, 4 }, { 24, 7 }, { 26, 1 }, { 28, 1 }, { 30, 1 }, { 32, 5 }, { 34, 2 }, { 40, 1 }, { 48, 3 }, { 50, 1 }, { 60, 1 }, { 64, 2 }, { 72, 1 }, { 104, 1 }, { 128, 1 }, { 328, 1 }, }; uint32_t groups_per_face_count[512] = { 0 }; ufbxt_assert(mesh->face_groups.count == 2029); for (size_t i = 0; i < mesh->face_groups.count; i++) { ufbx_face_group *group = &mesh->face_groups.data[i]; ufbxt_assert(group->num_faces < ufbxt_arraycount(groups_per_face_count)); groups_per_face_count[group->num_faces]++; } for (size_t i = 0; i < ufbxt_arraycount(groups_per_face_count_ref); i++) { ufbxt_groups_per_face_count ref = groups_per_face_count_ref[i]; ufbxt_assert(groups_per_face_count[ref.num_faces] == ref.num_groups); } } #endif UFBXT_FILE_TEST(zbrush_cut_sphere) #if UFBXT_IMPL { } #endif #if UFBXT_IMPL typedef struct { int32_t id; uint32_t face_count; } ufbxt_face_group_ref; #endif UFBXT_FILE_TEST(synthetic_face_group_id) #if UFBXT_IMPL { const ufbxt_face_group_ref ref_groups[] = { { -2147483647 - 1, 1 }, { -2000, 2 }, { -2, 1 }, { -1, 1 }, { 0, 3 }, { 1, 2 }, { 2, 2 }, { 10, 1 }, { 20, 1 }, { 30, 1 }, { 40, 1 }, { 50, 1 }, { 3000, 2 }, { 2147483647, 1 }, }; ufbx_node *node = ufbx_find_node(scene, "20 Sided"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; for (size_t i = 0; i < ufbxt_arraycount(ref_groups); i++) { ufbxt_hintf("i = %zu", i); ufbxt_assert(i < mesh->face_groups.count); ufbx_face_group *group = &mesh->face_groups.data[i]; ufbxt_assert(group->id == ref_groups[i].id); ufbxt_assert(group->num_faces == ref_groups[i].face_count); } } #endif UFBXT_FILE_TEST(blender_279_empty_cube) #if UFBXT_IMPL { ufbx_node *node = ufbx_find_node(scene, "Cube"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->num_faces == 0); ufbxt_assert(mesh->num_indices == 0); ufbxt_assert(mesh->num_vertices == 0); ufbxt_assert(mesh->materials.count == 1); ufbxt_assert(mesh->materials.data[0].material); ufbxt_assert(mesh->materials.data[0].num_faces == 0); } #endif UFBXT_FILE_TEST(synthetic_truncated_crease_partial) #if UFBXT_IMPL { ufbxt_check_warning(scene, UFBX_WARNING_TRUNCATED_ARRAY, 1, "Truncated array: EdgeCrease"); ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->edge_crease.count == 4); ufbxt_assert_close_real(err, mesh->edge_crease.data[0], 0.25f); ufbxt_assert_close_real(err, mesh->edge_crease.data[1], 0.5f); ufbxt_assert_close_real(err, mesh->edge_crease.data[2], 0.5f); ufbxt_assert_close_real(err, mesh->edge_crease.data[3], 0.5f); } #endif UFBXT_FILE_TEST(synthetic_truncated_crease_full) #if UFBXT_IMPL { ufbxt_check_warning(scene, UFBX_WARNING_TRUNCATED_ARRAY, 1, "Truncated array: EdgeCrease"); ufbx_node *node = ufbx_find_node(scene, "pPlane1"); ufbxt_assert(node && node->mesh); ufbx_mesh *mesh = node->mesh; ufbxt_assert(mesh->edge_crease.count == 4); ufbxt_assert_close_real(err, mesh->edge_crease.data[0], 0.0f); ufbxt_assert_close_real(err, mesh->edge_crease.data[1], 0.0f); ufbxt_assert_close_real(err, mesh->edge_crease.data[2], 0.0f); ufbxt_assert_close_real(err, mesh->edge_crease.data[3], 0.0f); } #endif