#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "animation"

#if UFBXT_IMPL
typedef struct {
	int frame;
	double value;
} ufbxt_key_ref;
#endif

UFBXT_FILE_TEST(maya_interpolation_modes)
#if UFBXT_IMPL
{
	// Curve evaluated values at 24fps
	static const ufbx_real values[] = {
		-8.653366, // Start from zero time
		-8.653366,-8.602998,-8.464664,-8.257528,-8.00075,-7.713489,-7.414906,-7.124163,-6.86042,
		-6.642837,-6.490576,-6.388305,-6.306414,-6.242637,-6.19471,-6.160368,-6.137348,-6.123385,
		-6.116215,-6.113573,-6.113196,-5.969524,-5.825851,-5.682179,-5.538507,-5.394835,-5.251163,
		-5.107491,-4.963819,-4.820146,-4.676474,-4.532802,-4.38913,-4.245458,-4.101785,-3.958113,-4.1529,
		-4.347686,-4.542472,-4.737258,-4.932045,-5.126832,-5.321618,-5.516404,-5.71119,-5.905977,-5.767788,
		-5.315578,-4.954943,-4.83559,-4.856855,-4.960766,-5.118543,-4.976541,-4.885909,-4.865979,-4.93845,
		-5.099224,-5.270246,-5.359269,-5.349404,-5.261964,-5.118543,-5.264501,-5.33535,-5.285445,-5.058857,
		-4.69383,-4.357775,-4.124978,-3.981697,-3.904232,-3.875225,-3.875225,-3.875225,-3.875225,-3.875225,
		-3.875225,-3.875225,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,-2.942738,
		-2.942738,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,-1.243537,5.603338,5.603338,
		5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,
		5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338,5.603338
	};

	ufbxt_assert(scene->anim_layers.count == 1);
	ufbx_anim_layer *layer = scene->anim_layers.data[0];
	for (size_t i = 0; i < layer->anim_values.count; i++) {
		ufbx_anim_value *value = layer->anim_values.data[i];
		if (strcmp(value->name.data, "Lcl Translation")) continue;
		ufbx_anim_curve *curve = value->curves[0];

		size_t num_keys = 12;
		ufbxt_assert(curve->keyframes.count == num_keys);
		ufbx_keyframe *keys = curve->keyframes.data;

		static const ufbxt_key_ref key_ref[] = {
			{ 1, -8.653366 },
			{ 11, -6.490576 },
			{ 21, -6.113196 },
			{ 36, -3.958113 },
			{ 46, -5.905977 },
			{ 53, -5.118543 },
			{ 63, -5.118543 },
			{ 73, -3.875225 },
			{ 80, -2.942738 },
			{ 89, -1.927362 },
			{ 96, -1.243537 },
			{ 120, 5.603338 },
		};
		ufbxt_assert(ufbxt_arraycount(key_ref) == num_keys);

		for (size_t i = 0; i < num_keys; i++) {
			ufbxt_assert_close_real(err, keys[i].time, (double)key_ref[i].frame / 24.0);
			ufbxt_assert_close_real(err, keys[i].value, key_ref[i].value);
			if (i > 0) ufbxt_assert(keys[i].left.dx > 0.0f);
			if (i + 1 < num_keys) ufbxt_assert(keys[i].right.dx > 0.0f);
		}

		ufbxt_assert(keys[0].interpolation == UFBX_INTERPOLATION_CUBIC);
		ufbxt_assert(keys[0].right.dy == 0.0f);
		ufbxt_assert(keys[1].interpolation == UFBX_INTERPOLATION_CUBIC);
		ufbxt_assert_close_real(err, keys[1].left.dy/keys[1].left.dx, keys[1].right.dy/keys[1].left.dx);
		ufbxt_assert(keys[2].interpolation == UFBX_INTERPOLATION_LINEAR);
		ufbxt_assert_close_real(err, keys[3].left.dy/keys[3].left.dx, keys[2].right.dy/keys[2].right.dx);
		ufbxt_assert(keys[3].interpolation == UFBX_INTERPOLATION_LINEAR);
		ufbxt_assert_close_real(err, keys[4].left.dy/keys[4].left.dx, keys[3].right.dy/keys[3].right.dx);
		ufbxt_assert(keys[4].interpolation == UFBX_INTERPOLATION_CUBIC);
		ufbxt_assert(keys[4].right.dy == 0.0f);
		ufbxt_assert(keys[5].interpolation == UFBX_INTERPOLATION_CUBIC);
		ufbxt_assert(keys[5].left.dy < 0.0f);
		ufbxt_assert(keys[5].right.dy > 0.0f);
		ufbxt_assert(keys[6].interpolation == UFBX_INTERPOLATION_CUBIC);
		ufbxt_assert(keys[6].left.dy > 0.0f);
		ufbxt_assert(keys[6].right.dy < 0.0f);
		ufbxt_assert(keys[7].interpolation == UFBX_INTERPOLATION_CONSTANT_PREV);
		ufbxt_assert(keys[8].interpolation == UFBX_INTERPOLATION_CONSTANT_PREV);
		ufbxt_assert(keys[9].interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT);
		ufbxt_assert(keys[10].interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT);

		for (size_t i = 0; i < ufbxt_arraycount(values); i++) {
			// Round up to the next frame to make stepped tangents consistent
			double time = (double)i * (1.0/24.0) + 0.000001;
			ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
			ufbxt_assert_close_real(err, value, values[i]);
		}

		size_t num_samples = 64 * 1024;
		for (size_t i = 0; i < num_samples; i++) {
			double time = (double)i * (5.0 / (double)num_samples);
			ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
			ufbxt_assert(value >= -16.0f && value <= 16.0f);
		}
	}
}
#endif

UFBXT_FILE_TEST(maya_auto_clamp)
#if UFBXT_IMPL
{
	// Curve evaluated values at 24fps
	static const ufbx_real values[] = {
		0.000, 0.000, 0.273, 0.515, 0.718, 0.868, 0.945, 0.920, 0.779, 0.611,
		0.591, 0.747, 1.206, 2.059, 3.191, 4.489, 5.837, 7.121, 8.228, 9.042,
		9.449, 9.694, 10.128, 10.610, 10.873, 10.927, 10.854, 10.704, 10.502,
		10.264, 10.000,
	};

	ufbxt_assert(scene->anim_layers.count == 1);
	ufbx_anim_layer *layer = scene->anim_layers.data[0];
	for (size_t i = 0; i < layer->anim_values.count; i++) {
		ufbx_anim_value *value = layer->anim_values.data[i];
		if (strcmp(value->name.data, "Lcl Translation")) continue;
		ufbx_anim_curve *curve = value->curves[0];
		ufbxt_assert(curve->keyframes.count == 4);

		for (size_t i = 0; i < ufbxt_arraycount(values); i++) {
			double time = (double)i * (1.0/24.0);
			ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
			ufbxt_assert_close_real(err, value, values[i]);
		}
	}
}
#endif

UFBXT_FILE_TEST(maya_resampled)
#if UFBXT_IMPL
{
	static const ufbx_real values6[] = {
		0,0,0,0,0,0,0,0,0,
		-0.004, -0.022, -0.056, -0.104, -0.166, -0.241, -0.328, -0.427, -0.536, -0.654, -0.783,
		-0.919, -1.063, -1.214, -1.371, -1.533, -1.700, -1.871, -2.044, -2.220, -2.398, -2.577,
		-2.755, -2.933, -3.109, -3.283, -3.454, -3.621, -3.784, -3.941, -4.093, -4.237, -4.374,
		-4.503, -4.623, -4.733, -4.832, -4.920, -4.996, -5.059, -5.108, -5.143, -5.168, -5.186,
		-5.200, -5.209, -5.215, -5.218, -5.220, -5.220, -5.216, -5.192, -5.151, -5.091, -5.013,
		-4.919, -4.810, -4.686,
	};

	static const ufbx_real values7[] = {
		0,0,0,0,0,0,0,0,
		0.000, -0.004, -0.025, -0.061, -0.112, -0.176, -0.252, -0.337, -0.431, -0.533, -0.648, 
		-0.776, -0.915, -1.064, -1.219, -1.378, -1.539, -1.700, -1.865, -2.037, -2.216, -2.397, -2.580, 
		-2.761, -2.939, -3.111, -3.278, -3.447, -3.615, -3.782, -3.943, -4.098, -4.244, -4.379, 
		-4.500, -4.614, -4.722, -4.821, -4.911, -4.990, -5.056, -5.107, -5.143, -5.168, -5.186, -5.200, 
		-5.209, -5.215, -5.218, -5.220, -5.220, -5.215, -5.190, -5.145, -5.082, -5.002, -4.908, 
		-4.800, -4.680, -4.550, -4.403, -4.239, 
	};

	const ufbx_real *values = scene->metadata.version >= 7000 ? values7 : values6;
	size_t num_values = scene->metadata.version >= 7000 ? ufbxt_arraycount(values7) : ufbxt_arraycount(values6);

	ufbxt_assert(scene->anim_layers.count == 1);
	ufbx_anim_layer *layer = scene->anim_layers.data[0];
	for (size_t i = 0; i < layer->anim_values.count; i++) {
		ufbx_anim_value *value = layer->anim_values.data[i];
		if (strcmp(value->name.data, "Lcl Translation")) continue;
		ufbx_anim_curve *curve = value->curves[0];

		for (size_t i = 0; i < num_values; i++) {
			double time = (double)i * (1.0/200.0);
			ufbx_real value = ufbx_evaluate_curve(curve, time, 0.0);
			ufbxt_assert_close_real(err, value, values[i]);
		}
	}
}
#endif

#if UFBXT_IMPL

typedef struct {
	int frame;
	ufbx_real intensity;
	ufbx_vec3 color;
} ufbxt_anim_light_ref;

typedef struct {
	int frame;
	ufbx_vec3 translation;
	ufbx_vec3 rotation_euler;
	ufbx_vec3 scale;
} ufbxt_anim_transform_ref;

#endif

UFBXT_FILE_TEST(maya_anim_light)
#if UFBXT_IMPL
{
	static const ufbxt_anim_light_ref refs[] = {
		{  0, 3.072, { 0.148, 0.095, 0.440 } },
		{ 12, 1.638, { 0.102, 0.136, 0.335 } },
		{ 24, 1.948, { 0.020, 0.208, 0.149 } },
		{ 32, 3.676, { 0.010, 0.220, 0.113 } },
		{ 40, 4.801, { 0.118, 0.195, 0.115 } },
		{ 48, 3.690, { 0.288, 0.155, 0.117 } },
		{ 56, 1.565, { 0.421, 0.124, 0.119 } },
		{ 60, 1.145, { 0.442, 0.119, 0.119 } },
	};

	for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
		const ufbxt_anim_light_ref *ref = &refs[i];

		double time = ref->frame * (1.0/24.0);
		ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
		ufbxt_assert(state);

		ufbxt_check_scene(state);

		ufbx_node *light_node = ufbx_find_node(state, "pointLight1");
		ufbxt_assert(light_node);
		ufbx_light *light = light_node->light;
		ufbxt_assert(light);

		ufbxt_assert_close_real(err, light->intensity, ref->intensity);
		ufbxt_assert_close_vec3(err, light->color, ref->color);

		ufbx_free_scene(state);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "pointLight1");
		ufbxt_assert(node && node->light);
		uint32_t element_id = node->light->element.element_id;

		ufbx_prop_override overrides[] = {
			{ element_id, "Intensity", { (ufbx_real)10.0 } },
			{ element_id, "Color", { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 } },
			{ element_id, "|NewProp", { 10, 20, 30 }, "Test" },
			{ element_id, "IntProp", { 0, 0, 0 }, "", 15 },
		};

		ufbx_anim anim = scene->anim;
		anim.prop_overrides = ufbx_prepare_prop_overrides(overrides, ufbxt_arraycount(overrides));

		ufbx_scene *state = ufbx_evaluate_scene(scene, &anim, 1.0f, NULL, NULL);
		ufbxt_assert(state);

		ufbxt_check_scene(state);

		ufbx_node *light_node = ufbx_find_node(state, "pointLight1");
		ufbxt_assert(light_node);
		ufbx_light *light = light_node->light;
		ufbxt_assert(light);

		ufbx_vec3 ref_color = { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 };
		ufbxt_assert_close_real(err, light->intensity, 0.1f);
		ufbxt_assert_close_vec3(err, light->color, ref_color);

		{
			ufbx_vec3 ref_new = { 10, 20, 30 };
			ufbx_prop *new_prop = ufbx_find_prop(&light->props, "|NewProp");
			ufbxt_assert(new_prop);
			ufbxt_assert((new_prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert(!strcmp(new_prop->value_str.data, "Test"));
			ufbxt_assert(new_prop->value_int == 10);
			ufbxt_assert_close_vec3(err, new_prop->value_vec3, ref_new);

			ufbx_prop *int_prop = ufbx_find_prop(&light->props, "IntProp");
			ufbxt_assert(int_prop);
			ufbxt_assert((int_prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert_close_real(err, int_prop->value_real, 15.0f);
			ufbxt_assert(int_prop->value_int == 15);
		}

		{
			ufbx_element *original_light = &node->light->element;

			ufbx_prop color = ufbx_evaluate_prop(&anim, original_light, "Color", 1.0);
			ufbxt_assert((color.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert_close_vec3(err, color.value_vec3, ref_color);

			ufbx_prop intensity = ufbx_evaluate_prop(&anim, original_light, "Intensity", 1.0);
			ufbxt_assert((intensity.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert_close_real(err, intensity.value_real, 10.0f);

			ufbx_vec3 ref_new = { 10, 20, 30 };
			ufbx_prop new_prop = ufbx_evaluate_prop(&anim, original_light, "|NewProp", 1.0);
			ufbxt_assert((new_prop.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert(!strcmp(new_prop.value_str.data, "Test"));
			ufbxt_assert(new_prop.value_int == 10);
			ufbxt_assert_close_vec3(err, new_prop.value_vec3, ref_new);

			ufbx_prop int_prop = ufbx_evaluate_prop(&anim, original_light, "IntProp", 1.0);
			ufbxt_assert((int_prop.flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0);
			ufbxt_assert_close_real(err, int_prop.value_real, 15.0f);
			ufbxt_assert(int_prop.value_int == 15);
		}

		ufbx_free_scene(state);
	}

	{
		ufbx_anim_layer *layer = scene->anim_layers.data[0];
		ufbx_node *node = ufbx_find_node(scene, "pointLight1");
		ufbxt_assert(node && node->light);
		ufbx_light *light = node->light;

		{
			ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &node->element);
			ufbxt_assert(props.count == 3);
			ufbxt_assert(!strcmp(props.data[0].prop_name.data, "Lcl Rotation"));
			ufbxt_assert(!strcmp(props.data[1].prop_name.data, "Lcl Scaling"));
			ufbxt_assert(!strcmp(props.data[2].prop_name.data, "Lcl Translation"));

			ufbx_anim_prop *prop;
			prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Rotation");
			ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Rotation"));
			prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Scaling");
			ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Scaling"));
			prop = ufbx_find_anim_prop(layer, &node->element, "Lcl Translation");
			ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Lcl Translation"));
		}

		{
			ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &light->element);
			ufbxt_assert(props.count == 2);
			ufbxt_assert(!strcmp(props.data[0].prop_name.data, "Color"));
			ufbxt_assert(!strcmp(props.data[1].prop_name.data, "Intensity"));

			ufbx_anim_prop *prop;
			prop = ufbx_find_anim_prop(layer, &light->element, "Color");
			ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Color"));
			prop = ufbx_find_anim_prop(layer, &light->element, "Intensity");
			ufbxt_assert(prop && !strcmp(prop->prop_name.data, "Intensity"));

			prop = ufbx_find_anim_prop(layer, &light->element, "Nonexistent");
			ufbxt_assert(prop == NULL);
		}

		{
			ufbx_anim_prop_list props = ufbx_find_anim_props(layer, &layer->element);
			ufbxt_assert(props.count == 0);

			ufbx_anim_prop *prop = ufbx_find_anim_prop(layer, &layer->element, "Weight");
			ufbxt_assert(prop == NULL);
		}
	}
}
#endif

UFBXT_FILE_TEST(maya_transform_animation)
#if UFBXT_IMPL
{
	static const ufbxt_anim_transform_ref refs[] = {
		{  1, {  0.000f,  0.000f,  0.000f }, {   0.000f,   0.000f,   0.000f }, { 1.000f, 1.000f, 1.000f } },
		{  5, {  0.226f,  0.452f,  0.677f }, {   2.258f,   4.515f,   6.773f }, { 1.023f, 1.045f, 1.068f } },
		{ 14, {  1.000f,  2.000f,  3.000f }, {  10.000f,  20.000f,  30.000f }, { 1.100f, 1.200f, 1.300f } },
		{ 20, { -0.296f, -0.592f, -0.888f }, {  -2.960f,  -5.920f,  -8.880f }, { 0.970f, 0.941f, 0.911f } },
		{ 24, { -1.000f, -2.000f, -3.000f }, { -10.000f, -20.000f, -30.000f }, { 0.900f, 0.800f, 0.700f } },
	};

	ufbx_node *node = ufbx_find_node(scene, "pCube1");

	for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
		const ufbxt_anim_transform_ref *ref = &refs[i];
		double time = ref->frame * (1.0/24.0);

		ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
		ufbxt_assert(state);
		ufbxt_check_scene(state);

		ufbx_transform t1 = state->nodes.data[node->element.typed_id]->local_transform;
		ufbx_transform t2 = ufbx_evaluate_transform(&scene->anim, node, time);

		ufbx_vec3 t1_euler = ufbx_quat_to_euler(t1.rotation, UFBX_ROTATION_ORDER_XYZ);
		ufbx_vec3 t2_euler = ufbx_quat_to_euler(t2.rotation, UFBX_ROTATION_ORDER_XYZ);

		ufbxt_assert_close_vec3(err, ref->translation, t1.translation);
		ufbxt_assert_close_vec3(err, ref->translation, t2.translation);
		ufbxt_assert_close_vec3(err, ref->rotation_euler, t1_euler);
		ufbxt_assert_close_vec3(err, ref->rotation_euler, t2_euler);
		ufbxt_assert_close_vec3(err, ref->scale, t1.scale);
		ufbxt_assert_close_vec3(err, ref->scale, t2.scale);

		ufbx_free_scene(state);
	}

	{
		uint32_t element_id = node->element.element_id;
		ufbxt_anim_transform_ref ref = refs[2];
		ref.translation.x -= 0.1f;
		ref.translation.y -= 0.2f;
		ref.translation.z -= 0.3f;
		ref.scale.x = 2.0f;
		ref.scale.y = 3.0f;
		ref.scale.z = 4.0f;

		ufbx_prop_override overrides[] = {
			{ element_id, "Color", { (ufbx_real)0.3, (ufbx_real)0.6, (ufbx_real)0.9 } },
			{ element_id, "|NewProp", { 10, 20, 30 }, "Test", },
			{ element_id, "Lcl Scaling", { 2.0f, 3.0f, 4.0f } },
			{ element_id, "RotationOffset", { -0.1f, -0.2f, -0.3f } },
		};

		double time = 14.0/24.0;
		ufbx_anim anim = scene->anim;
		anim.prop_overrides = ufbx_prepare_prop_overrides(overrides, ufbxt_arraycount(overrides));
		ufbx_scene *state = ufbx_evaluate_scene(scene, &anim, time, NULL, NULL);
		ufbxt_assert(state);
		ufbxt_check_scene(state);

		ufbx_transform t1 = state->nodes.data[node->element.typed_id]->local_transform;
		ufbx_transform t2 = ufbx_evaluate_transform(&anim, node, time);

		ufbx_vec3 t1_euler = ufbx_quat_to_euler(t1.rotation, UFBX_ROTATION_ORDER_XYZ);
		ufbx_vec3 t2_euler = ufbx_quat_to_euler(t2.rotation, UFBX_ROTATION_ORDER_XYZ);

		ufbxt_assert_close_vec3(err, ref.translation, t1.translation);
		ufbxt_assert_close_vec3(err, ref.translation, t2.translation);
		ufbxt_assert_close_vec3(err, ref.rotation_euler, t1_euler);
		ufbxt_assert_close_vec3(err, ref.rotation_euler, t2_euler);
		ufbxt_assert_close_vec3(err, ref.scale, t1.scale);
		ufbxt_assert_close_vec3(err, ref.scale, t2.scale);

		ufbx_free_scene(state);
	}
}
#endif


UFBXT_FILE_TEST(maya_anim_layers)
#if UFBXT_IMPL
{
	ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
	ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
	ufbxt_assert(x && y);
	ufbxt_assert(y->compose_rotation == false);
	ufbxt_assert(y->compose_scale == false);
}
#endif

UFBXT_FILE_TEST(maya_anim_layers_acc)
#if UFBXT_IMPL
{
	ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
	ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
	ufbxt_assert(x && y);
	ufbxt_assert(y->compose_rotation == true);
	ufbxt_assert(y->compose_scale == true);
}
#endif

UFBXT_FILE_TEST(maya_anim_layers_over)
#if UFBXT_IMPL
{
	ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
	ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
	ufbxt_assert(x && y);
	ufbxt_assert(y->compose_rotation == false);
	ufbxt_assert(y->compose_scale == false);
}
#endif

UFBXT_FILE_TEST(maya_anim_layers_over_acc)
#if UFBXT_IMPL
{
	ufbx_anim_layer *x = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "X");
	ufbx_anim_layer *y = (ufbx_anim_layer*)ufbx_find_element(scene, UFBX_ELEMENT_ANIM_LAYER, "Y");
	ufbxt_assert(x && y);
	ufbxt_assert(y->compose_rotation == true);
	ufbxt_assert(y->compose_scale == true);
}
#endif

#if UFBXT_IMPL
typedef struct {
	double time;
	bool visible;
} ufbxt_visibility_ref;
#endif

UFBXT_FILE_TEST(maya_cube_blinky)
#if UFBXT_IMPL
{
	ufbxt_visibility_ref refs[] = {
		{ 1.0, false },
		{ 9.5, false },
		{ 10.5, true },
		{ 11.5, false },
		{ 15.0, false },
		{ 19.5, false },
		{ 20.5, false },
		{ 25.0, false },
		{ 29.5, false },
		{ 30.5, true },
		{ 40.0, true },
		{ 50.0, true },
	};

	for (size_t i = 0; i < ufbxt_arraycount(refs); i++) {
		double time = refs[i].time / 24.0;
		ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, time, NULL, NULL);
		ufbxt_assert(state);

		ufbxt_check_scene(state);

		ufbx_node *node = ufbx_find_node(state, "pCube1");
		ufbxt_assert(node);
		ufbxt_assert(node->visible == refs[i].visible);

		ufbx_free_scene(state);
	}
}
#endif

#if UFBXT_IMPL
typedef struct {
	double time;
	ufbx_real value;
} ufbxt_anim_ref;
#endif

UFBXT_FILE_TEST(maya_anim_interpolation)
#if UFBXT_IMPL
{
	ufbxt_anim_ref anim_ref[] = {
		// Cubic
		{ 0.0 / 30.0, 0.0 },
		{ 1.0 / 30.0, -0.855245 },
		{ 2.0 / 30.0, -1.13344 },
		{ 3.0 / 30.0, -1.17802 },
		{ 4.0 / 30.0, -1.10882 },
		{ 5.0 / 30.0, -0.991537 },
		{ 6.0 / 30.0, -0.875223 },
		{ 7.0 / 30.0, -0.808958 },
		{ 8.0 / 30.0, -0.858419 },
		{ 9.0 / 30.0, -1.14293 },
		// Linear
		{ 10.0 / 30.0, -2.0 },
		// Constant previous
		{ 20.0 / 30.0, -4.0 },
		{ 25.0 / 30.0 - 0.001, -4.0 },
		// Constant next
		{ 25.0 / 30.0, -6.0 },
		{ 25.0 / 30.0 + 0.001, -8.0 },
		// Constant previous
		{ 30.0 / 30.0, -8.0 },
		{ 35.0 / 30.0 - 0.001, -8.0 },
		// Linear
		{ 35.0 / 30.0, -10.0 },
		// Constant next
		{ 40.0 / 30.0, -12.0 },
		{ 40.0 / 30.0 + 0.001, -14.0 },
		// Constant previous
		{ 45.0 / 30.0, -14.0 },
		{ 50.0 / 30.0 - 0.001, -14.0 },
		// Constant next
		{ 50.0 / 30.0, -16.0 },
		{ 50.0 / 30.0 + 0.001, -14.0 },
		// inal
		{ 55.0 / 30.0, -14.0, },
	};

	ufbx_node *node = ufbx_find_node(scene, "pCube1");
	ufbxt_assert(node);

	for (size_t i = 0; i < ufbxt_arraycount(anim_ref); i++) {
		ufbxt_anim_ref ref = anim_ref[i];
		ufbxt_hintf("%zu: %f (frame %.2f)", i, ref.time, ref.time * 30.0f);

		ufbx_prop p = ufbx_evaluate_prop(&scene->anim, &node->element, "Lcl Translation", ref.time);
		ufbxt_assert_close_real(err, p.value_vec3.x, ref.value);
	}
}
#endif

