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