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

1193 lines
38 KiB
C

#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