#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "convert"

#if UFBXT_IMPL
ufbx_load_opts ufbxt_geometry_transform_helper_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES;
	return opts;
}

ufbx_load_opts ufbxt_geometry_transform_modify_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY;
	return opts;
}

ufbx_load_opts ufbxt_geometry_transform_modify_no_fallback_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK;
	return opts;
}

ufbx_load_opts ufbxt_geometry_transform_helper_name_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES;
	opts.geometry_transform_helper_name.data = "(ufbxt helper)";
	opts.geometry_transform_helper_name.length = SIZE_MAX;
	return opts;
}

ufbx_load_opts ufbxt_y_up_meters_root_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_axes = ufbx_axes_right_handed_y_up;
	opts.target_unit_meters = 1.0f;
	opts.space_conversion = UFBX_SPACE_CONVERSION_TRANSFORM_ROOT;
	return opts;
}

ufbx_load_opts ufbxt_y_up_meters_adjust_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_axes = ufbx_axes_right_handed_y_up;
	opts.target_unit_meters = 1.0f;
	opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
	return opts;
}

ufbx_load_opts ufbxt_z_up_meters_root_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_axes = ufbx_axes_right_handed_z_up;
	opts.target_unit_meters = 1.0f;
	opts.space_conversion = UFBX_SPACE_CONVERSION_TRANSFORM_ROOT;
	return opts;
}

ufbx_load_opts ufbxt_z_up_meters_adjust_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_axes = ufbx_axes_right_handed_z_up;
	opts.target_unit_meters = 1.0f;
	opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
	return opts;
}

ufbx_load_opts ufbxt_rh_y_camera_light_axes_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_camera_axes = ufbx_axes_right_handed_y_up;
	opts.target_light_axes = ufbx_axes_right_handed_y_up;
	return opts;
}

ufbx_load_opts ufbxt_blender_space_adjust_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_axes = ufbx_axes_right_handed_z_up;
	opts.target_unit_meters = 1.0f;
	opts.target_camera_axes = ufbx_axes_right_handed_y_up;
	opts.target_light_axes = ufbx_axes_right_handed_y_up;
	opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
	return opts;
}

#endif

#if UFBXT_IMPL
typedef struct {
	ufbx_vec3 translation;
	ufbx_vec3 rotation_euler;
	ufbx_vec3 scale;
} ufbxt_ref_transform;

static void ufbxt_check_transform(ufbxt_diff_error *err, const char *name, ufbx_transform transform, ufbxt_ref_transform ref)
{
	ufbx_vec3 rotation_euler = ufbx_quat_to_euler(transform.rotation, UFBX_ROTATION_ORDER_XYZ);
	ufbxt_hintf("%s { { %.2f, %.2f, %.2f }, { %.2f, %.2f, %.2f }, { %.2f, %.2f, %.2f } }", name,
		transform.translation.x, transform.translation.y, transform.translation.z,
		rotation_euler.x, rotation_euler.y, rotation_euler.z,
		transform.scale.x, transform.scale.y, transform.scale.z);

	ufbxt_assert_close_vec3(err, transform.translation, ref.translation);
	ufbxt_assert_close_vec3(err, rotation_euler, ref.rotation_euler);
	ufbxt_assert_close_vec3(err, transform.scale, ref.scale);

	ufbxt_hintf("");
}

static const ufbxt_ref_transform ufbxt_ref_transform_identity = {
	{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f },
};
#endif


UFBXT_FILE_TEST(max_geometry_transform)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 3);

	{
		ufbx_node *node = ufbx_find_node(scene, "Box001");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, -10.0f, 0.0f }, { 0.0f, 0.0f, -90.0f }, { 1.0f, 2.0f, 1.0f },
		};
		ufbxt_ref_transform geometry_transform = {
			{ 0.0f, 0.0f, 10.0f }, { 0.0f, 90.0f, 0.0f }, { 1.0f, 1.0f, 2.0f },
		};

		ufbxt_check_transform(err, "Box001 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box001 geometry", node->geometry_transform, geometry_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Box002");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, 0.0f, 20.0f }, { 0.0f, 0.0f, -180.0f }, { 1.0f, 0.5f, 1.0f },
		};
		ufbxt_ref_transform geometry_transform = {
			{ 10.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f },
		};

		ufbxt_check_transform(err, "Box002 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box002 geometry", node->geometry_transform, geometry_transform);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_helper, max_geometry_transform, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 5);

	{
		ufbx_node *node = ufbx_find_node(scene, "Box001");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper != NULL);
		ufbxt_assert(!node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, -10.0f, 0.0f }, { 0.0f, 0.0f, -90.0f }, { 1.0f, 2.0f, 1.0f },
		};
		ufbxt_ref_transform geometry_transform = {
			{ 0.0f, 0.0f, 10.0f }, { 0.0f, 90.0f, 0.0f }, { 1.0f, 1.0f, 2.0f },
		};

		ufbxt_check_transform(err, "Box001 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);

		node = node->geometry_transform_helper;
		ufbxt_assert(node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_check_transform(err, "Box001 helper local", node->local_transform, geometry_transform);
		ufbxt_check_transform(err, "Box001 helper geometry", node->geometry_transform, ufbxt_ref_transform_identity);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Box002");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper != NULL);
		ufbxt_assert(!node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, 0.0f, 20.0f }, { 0.0f, 0.0f, -180.0f }, { 1.0f, 0.5f, 1.0f },
		};
		ufbxt_ref_transform geometry_transform = {
			{ 10.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f },
		};

		ufbxt_check_transform(err, "Box002 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box002 geometry", node->geometry_transform, ufbxt_ref_transform_identity);

		node = node->geometry_transform_helper;
		ufbxt_assert(node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_check_transform(err, "Box002 helper local", node->local_transform, geometry_transform);
		ufbxt_check_transform(err, "Box002 helper geometry", node->geometry_transform, ufbxt_ref_transform_identity);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_modify, max_geometry_transform, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 3);

	{
		ufbx_node *node = ufbx_find_node(scene, "Box001");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, -10.0f, 0.0f }, { 0.0f, 0.0f, -90.0f }, { 1.0f, 2.0f, 1.0f },
		};

		ufbxt_check_transform(err, "Box001 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Box002");
		ufbxt_assert(node);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(node->geometry_transform_helper == NULL);
		ufbxt_assert(node->mesh);

		ufbxt_ref_transform local_transform = {
			{ 0.0f, 0.0f, 20.0f }, { 0.0f, 0.0f, -180.0f }, { 1.0f, 0.5f, 1.0f },
		};

		ufbxt_check_transform(err, "Box002 local", node->local_transform, local_transform);
		ufbxt_check_transform(err, "Box002 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT(no_unnecessary_geometry_helpers, maya_cube, ufbxt_geometry_transform_helper_opts)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 2);
	ufbx_node *node = ufbx_find_node(scene, "pCube1");
	ufbxt_assert(node);
	ufbxt_assert(!node->is_geometry_transform_helper);
	ufbxt_assert(node->geometry_transform_helper == NULL);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_transformed_skin_helpers, max_transformed_skin, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Box001");
	ufbxt_assert(node);
	ufbxt_assert(!node->mesh);
	ufbxt_assert(!node->is_geometry_transform_helper);
	ufbxt_assert(node->geometry_transform_helper);
	ufbxt_assert(!node->has_geometry_transform);
	ufbx_node *geo_node = node->geometry_transform_helper;
	ufbxt_assert(geo_node->mesh);
	ufbxt_assert(geo_node->is_geometry_transform_helper);
	ufbxt_assert(!geo_node->geometry_transform_helper);
	ufbxt_assert(!geo_node->has_geometry_transform);

	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_OPTS_ALT_FLAGS(max_transformed_skin_modify, max_transformed_skin, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Box001");
	ufbxt_assert(node);
	ufbxt_assert(node->mesh);
	ufbxt_assert(!node->is_geometry_transform_helper);
	ufbxt_assert(!node->geometry_transform_helper);
	ufbxt_assert(!node->has_geometry_transform);

	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(max_geometry_transform_instances)
#if UFBXT_IMPL
{
	if (scene->metadata.version >= 7000) {
		ufbxt_assert(scene->meshes.count == 1);
	} else {
		ufbxt_assert(scene->meshes.count == 4);
	}

	ufbxt_assert(scene->nodes.count == 5);
	for (size_t i = 1; i < scene->nodes.count; i++) {
		ufbx_node *node = scene->nodes.data[i];
		ufbxt_assert(node->has_geometry_transform);
		ufbxt_assert(node->mesh);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(!node->geometry_transform_helper);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_instances_helper, max_geometry_transform_instances, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	if (scene->metadata.version >= 7000) {
		ufbxt_assert(scene->meshes.count == 1);
	} else {
		ufbxt_assert(scene->meshes.count == 4);
	}

	ufbxt_assert(scene->nodes.count == 9);
	for (size_t i = 1; i < scene->nodes.count; i++) {
		ufbx_node *node = scene->nodes.data[i];
		ufbxt_assert(!node->has_geometry_transform);
		if (node->name.length > 0) {
			ufbxt_assert(!node->mesh);
			ufbxt_assert(!node->is_geometry_transform_helper);
			ufbxt_assert(node->geometry_transform_helper);
		} else {
			ufbxt_assert(node->mesh);
			ufbxt_assert(node->is_geometry_transform_helper);
			ufbxt_assert(!node->geometry_transform_helper);
		}
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_instances_modify, max_geometry_transform_instances, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	if (scene->metadata.version >= 7000) {
		ufbxt_assert(scene->meshes.count == 1);
	} else {
		ufbxt_assert(scene->meshes.count == 4);
	}

	if (scene->metadata.version >= 7000) {
		ufbxt_assert(scene->nodes.count == 9);
		for (size_t i = 1; i < scene->nodes.count; i++) {
			ufbx_node *node = scene->nodes.data[i];
			ufbxt_assert(!node->has_geometry_transform);
			if (node->name.length > 0) {
				ufbxt_assert(!node->mesh);
				ufbxt_assert(!node->is_geometry_transform_helper);
				ufbxt_assert(node->geometry_transform_helper);
			} else {
				ufbxt_assert(node->mesh);
				ufbxt_assert(node->is_geometry_transform_helper);
				ufbxt_assert(!node->geometry_transform_helper);
			}
		}
	} else {
		ufbxt_assert(scene->nodes.count == 5);
		for (size_t i = 1; i < scene->nodes.count; i++) {
			ufbx_node *node = scene->nodes.data[i];
			ufbxt_assert(!node->has_geometry_transform);
			ufbxt_assert(node->mesh);
			ufbxt_assert(!node->is_geometry_transform_helper);
			ufbxt_assert(!node->geometry_transform_helper);
		}
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_instances_modify_no_fallback, max_geometry_transform_instances, ufbxt_geometry_transform_modify_no_fallback_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS|UFBXT_FILE_TEST_FLAG_DIFF_EXPECT_FAIL_POST_7000)
#if UFBXT_IMPL
{
	if (scene->metadata.version >= 7000) {
		ufbxt_assert(scene->meshes.count == 1);
	} else {
		ufbxt_assert(scene->meshes.count == 4);
	}

	ufbxt_assert(scene->nodes.count == 5);
	for (size_t i = 1; i < scene->nodes.count; i++) {
		ufbx_node *node = scene->nodes.data[i];
		ufbxt_assert(!node->has_geometry_transform);
		ufbxt_assert(node->mesh);
		ufbxt_assert(!node->is_geometry_transform_helper);
		ufbxt_assert(!node->geometry_transform_helper);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_helper_names, max_geometry_transform, ufbxt_geometry_transform_helper_name_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 5);

	{
		ufbx_node *node = ufbx_find_node(scene, "Box001");
		ufbxt_assert(node->geometry_transform_helper != NULL);
		node = node->geometry_transform_helper;
		ufbxt_assert(!strcmp(node->name.data, "(ufbxt helper)"));
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Box002");
		ufbxt_assert(node->geometry_transform_helper != NULL);
		node = node->geometry_transform_helper;
		ufbxt_assert(!strcmp(node->name.data, "(ufbxt helper)"));
	}
}
#endif

UFBXT_FILE_TEST(max_geometry_transform_types)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "Plane001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Plane001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(node->has_geometry_transform);
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_MESH);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Line001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Line001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(node->has_geometry_transform);
		if (scene->metadata.version >= 7000) {
			ufbxt_assert(node->attrib_type == UFBX_ELEMENT_LINE_CURVE);
		} else {
			ufbxt_assert(node->attrib_type == UFBX_ELEMENT_EMPTY);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Curve001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Curve001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(node->has_geometry_transform);
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Surface001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Surface001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(node->has_geometry_transform);
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_types_helper, max_geometry_transform_types, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "Plane001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Plane001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Plane001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);
		ufbxt_assert(!node->attrib);
		ufbxt_assert(node->geometry_transform_helper);
		node = node->geometry_transform_helper;
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_MESH);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Line001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Line001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Line001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);
		ufbxt_assert(!node->attrib);
		ufbxt_assert(node->geometry_transform_helper);
		node = node->geometry_transform_helper;
		if (scene->metadata.version >= 7000) {
			ufbxt_assert(node->attrib_type == UFBX_ELEMENT_LINE_CURVE);
		} else {
			ufbxt_assert(node->attrib_type == UFBX_ELEMENT_EMPTY);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Curve001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Curve001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Curve001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);
		ufbxt_assert(!node->attrib);
		ufbxt_assert(node->geometry_transform_helper);
		node = node->geometry_transform_helper;
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_NURBS_CURVE);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Surface001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Surface001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Surface001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);
		ufbxt_assert(!node->attrib);
		ufbxt_assert(node->geometry_transform_helper);
		node = node->geometry_transform_helper;
		ufbxt_assert(node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_types_modify, max_geometry_transform_types, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "Plane001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Plane001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Plane001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);

		ufbx_mesh *mesh = node->mesh;
		ufbxt_assert(mesh);

		ufbxt_assert(mesh->vertex_normal.exists);
		ufbxt_assert(mesh->vertex_tangent.exists);
		ufbxt_assert(mesh->vertex_bitangent.exists);

		ufbx_vec3 ref_positions[] = {
			{ 20.0f, 10.0f, 0.0f },
			{ 0.0f, 10.0f, 10.0f },
			{ 20.0f, -10.0f, 0.0f },
		};

		ufbx_vec3 ref_normal = { 0.447213f, 0.0f, 0.894427f };
		ufbx_vec3 ref_tangent = { 0.0f, -1.0f, 0.0f };
		ufbx_vec3 ref_bitangent = { 0.894427f, 0.0f, -0.447213f };

		ufbxt_assert(mesh->num_indices == 3);
		for (size_t i = 0; i < 3; i++) {
			ufbxt_hintf("i=%zu", i);

			ufbx_vec3 position = ufbx_get_vertex_vec3(&mesh->vertex_position, i);
			ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, i);
			ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, i);
			ufbx_vec3 bitangent = ufbx_get_vertex_vec3(&mesh->vertex_bitangent, i);

			ufbxt_assert_close_vec3(err, position, ref_positions[i]);
			ufbxt_assert_close_vec3(err, normal, ref_normal);
			ufbxt_assert_close_vec3(err, tangent, ref_tangent);
			ufbxt_assert_close_vec3(err, bitangent, ref_bitangent);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Line001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Line001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Line001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);

		ufbx_line_curve *line = ufbx_as_line_curve(node->attrib);
		if (scene->metadata.version >= 7000) {
			ufbxt_assert(line);

			ufbx_vec3 ref_positions[] = {
				{ 20.0f, -10.0f, 0.0f },
				{ 20.0f, 10.0f, 0.0f },
				{ 0.0f, 10.0f, 0.0f },
			};

			ufbxt_assert(line->point_indices.count == 3);
			ufbxt_assert(line->segments.count == 1);
			ufbxt_assert(line->segments.data[0].index_begin == 0);
			ufbxt_assert(line->segments.data[0].num_indices == 3);
			for (size_t i = 0; i < 3; i++) {
				ufbxt_hintf("i=%zu", i);

				ufbx_vec3 position = line->control_points.data[line->point_indices.data[i]];
				ufbxt_assert_close_vec3(err, position, ref_positions[i]);
			}
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Curve001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Curve001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Curve001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);

		ufbx_nurbs_curve *curve = ufbx_as_nurbs_curve(node->attrib);
		ufbxt_assert(curve);

		ufbx_vec4 ref_points[] = {
			{ 0.0f, 10.0f, 0.0f, 1.0f },
			{ 6.666666f, 3.333333f, 0.0f, 1.0f },
			{ 13.333333f, -3.333333f, 0.0f, 1.0f },
			{ 20.0f, -10.0f, 0.0f, 1.0f },
		};

		ufbxt_assert(curve->control_points.count == 4);
		for (size_t i = 0; i < 4; i++) {
			ufbxt_hintf("i=%zu", i);

			ufbx_vec4 point = curve->control_points.data[i];
			ufbxt_assert_close_vec4(err, point, ref_points[i]);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Surface001");
		ufbxt_assert(node);
		ufbxt_check_transform(err, "Surface001 local", node->local_transform, ufbxt_ref_transform_identity);
		ufbxt_check_transform(err, "Surface001 geometry", node->geometry_transform, ufbxt_ref_transform_identity);
		ufbxt_assert(!node->has_geometry_transform);

		ufbx_nurbs_surface *surface = ufbx_as_nurbs_surface(node->attrib);
		ufbxt_assert(surface);

		ufbxt_assert(surface->num_control_points_u == 4);
		ufbxt_assert(surface->num_control_points_v == 4);
		for (size_t v = 0; v < 4; v++) {
			for (size_t u = 0; u < 4; u++) {
				ufbxt_hintf("u=%zu, v=%zu", u, v);

				ufbx_vec4 ref_point;
				ref_point.x = (ufbx_real)v * 6.666666f;
				ref_point.y = 10.0f - (ufbx_real)u * 6.666666f;
				ref_point.z = 0.0f;
				ref_point.w = 1.0f;

				ufbx_vec4 point = surface->control_points.data[v*4 + u];
				ufbxt_assert_close_vec4(err, point, ref_point);
			}
		}
	}
}
#endif

UFBXT_FILE_TEST_FLAGS(blender_340_z_up, UFBXT_FILE_TEST_FLAG_DIFF_EXPECT_FAIL)
#if UFBXT_IMPL
{
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(blender_340_z_up_root, blender_340_z_up, ufbxt_y_up_meters_root_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *root = scene->root_node;
	ufbxt_ref_transform root_transform = {
		{ 0.0f, 0.0f, 0.0f }, { -90.0f, 0.0f, 0.0f }, { 0.01f, 0.01f, 0.01f },
	};
	ufbxt_check_transform(err, "root", root->local_transform, root_transform);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(blender_340_z_up_adjust, blender_340_z_up, ufbxt_y_up_meters_adjust_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *root = scene->root_node;
	ufbxt_check_transform(err, "root", root->local_transform, ufbxt_ref_transform_identity);

	size_t num_adjusted = 0;
	for (size_t i = 0; i < scene->nodes.count; i++) {
		ufbx_node *node = scene->nodes.data[i];
		if (node->node_depth == 1) {
			ufbxt_assert(node->has_adjust_transform);
			ufbx_vec3 rotation = { -90.0f, 0.0f, 0.0f };
			ufbx_vec3 scale = { 0.01f, 0.01f, 0.01f };
			ufbx_quat rotation_quat = ufbx_euler_to_quat(rotation, UFBX_ROTATION_ORDER_XYZ);
			ufbxt_assert_close_quat(err, node->adjust_post_rotation, ufbx_identity_quat);
			ufbxt_assert_close_quat(err, node->adjust_pre_rotation, rotation_quat);
			ufbxt_assert_close_vec3(err, node->adjust_pre_scale, scale);
			num_adjusted++;
		} else {
			ufbxt_assert(!node->has_adjust_transform);
			ufbx_vec3 scale = { 1.0f, 1.0f, 1.0f };
			ufbxt_assert_close_quat(err, node->adjust_post_rotation, ufbx_identity_quat);
			ufbxt_assert_close_quat(err, node->adjust_pre_rotation, ufbx_identity_quat);
			ufbxt_assert_close_vec3(err, node->adjust_pre_scale, scale);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Light");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 4.0f, 5.0f, -1.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Light", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Camera");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 7.0f, 5.0f, -6.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Camera", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Cube");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 1.0f, -1.0f }, { -90.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Camera", node->local_transform, ref_transform);
	}

	ufbxt_assert(scene->nodes.count == 5);
	ufbxt_assert(num_adjusted == 3);
}
#endif

UFBXT_FILE_TEST_FLAGS(blender_340_y_up, UFBXT_FILE_TEST_FLAG_DIFF_EXPECT_FAIL)
#if UFBXT_IMPL
{
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(blender_340_y_up_root, blender_340_y_up, ufbxt_z_up_meters_root_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *root = scene->root_node;
	ufbxt_ref_transform root_transform = {
		{ 0.0f, 0.0f, 0.0f }, { 90.0f, 0.0f, 0.0f }, { 0.01f, 0.01f, 0.01f },
	};
	ufbxt_check_transform(err, "root", root->local_transform, root_transform);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(blender_340_y_up_adjust, blender_340_y_up, ufbxt_z_up_meters_adjust_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *root = scene->root_node;
	ufbxt_check_transform(err, "root", root->local_transform, ufbxt_ref_transform_identity);

	size_t num_adjusted = 0;
	for (size_t i = 0; i < scene->nodes.count; i++) {
		ufbx_node *node = scene->nodes.data[i];
		if (node->node_depth == 1) {
			ufbxt_assert(node->has_adjust_transform);
			ufbx_vec3 rotation = { 90.0f, 0.0f, 0.0f };
			ufbx_vec3 scale = { 0.01f, 0.01f, 0.01f };
			ufbx_quat rotation_quat = ufbx_euler_to_quat(rotation, UFBX_ROTATION_ORDER_XYZ);
			ufbxt_assert_close_quat(err, node->adjust_post_rotation, ufbx_identity_quat);
			ufbxt_assert_close_quat(err, node->adjust_pre_rotation, rotation_quat);
			ufbxt_assert_close_vec3(err, node->adjust_pre_scale, scale);
			num_adjusted++;
		} else {
			ufbxt_assert(!node->has_adjust_transform);
			ufbx_vec3 scale = { 1.0f, 1.0f, 1.0f };
			ufbxt_assert_close_quat(err, node->adjust_post_rotation, ufbx_identity_quat);
			ufbxt_assert_close_quat(err, node->adjust_pre_rotation, ufbx_identity_quat);
			ufbxt_assert_close_vec3(err, node->adjust_pre_scale, scale);
		}
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Light");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 4.0f, 1.0f, 5.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Light", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Camera");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 7.0f, 6.0f, 5.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Camera", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Cube");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 1.0f, 1.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Camera", node->local_transform, ref_transform);
	}

	ufbxt_assert(scene->nodes.count == 5);
	ufbxt_assert(num_adjusted == 3);
}
#endif

UFBXT_FILE_TEST(maya_camera_light_axes_y_up)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "forwardCamera");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 0.0f, 0.0f }, { 0.0f, 90.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "forwardCamera", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "forwardLight");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 0.0f, 0.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "forwardLight", node->local_transform, ref_transform);
	}
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(maya_camera_light_axes_y_up_rh_y, maya_camera_light_axes_y_up, ufbxt_rh_y_camera_light_axes_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "forwardCamera");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "forwardCamera", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "forwardLight");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "forwardLight", node->local_transform, ref_transform);
	}
}
#endif

UFBXT_FILE_TEST_OPTS(blender_340_default_unscaled, ufbxt_blender_space_adjust_opts)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "Cube");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Cube", node->local_transform, ref_transform);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Camera");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 7.3589f, -6.9258f, 4.9583f }, { 63.5593f, 0.0f, 46.6919f }, { 1.0f, 1.0f, 1.0f }
		};
		ufbxt_check_transform(err, "Camera", node->local_transform, ref_transform);

		ufbx_camera *camera = node->camera;
		ufbxt_assert(camera);
		ufbxt_assert(camera->projection_axes.right == UFBX_COORDINATE_AXIS_POSITIVE_X);
		ufbxt_assert(camera->projection_axes.up == UFBX_COORDINATE_AXIS_POSITIVE_Y);
		ufbxt_assert(camera->projection_axes.front == UFBX_COORDINATE_AXIS_POSITIVE_Z);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "Light");
		ufbxt_assert(node);
		ufbxt_ref_transform ref_transform = {
			{ 4.0762f, 1.0055f, 5.9039f }, { 37.261f, 3.16371f, 106.936f }, { 1.0f, 1.0f, 1.0f },
		};
		ufbxt_check_transform(err, "Light", node->local_transform, ref_transform);

		ufbx_light *light = node->light;
		ufbxt_assert(light);

		ufbx_vec3 ref_dir = { 0.0f, 0.0f, -1.0f };
		ufbxt_assert_close_vec3(err, light->local_direction, ref_dir);
	}
}
#endif

UFBXT_FILE_TEST(max_geometry_transform_order)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 3);
	ufbx_node **nodes = scene->nodes.data;

	ufbxt_assert(!strcmp(nodes[0]->name.data, ""));
	ufbxt_assert(nodes[0]->is_root);
	ufbxt_assert(nodes[0]->node_depth == 0);
	ufbxt_assert(nodes[0]->parent == NULL);

	ufbxt_assert(!strcmp(nodes[1]->name.data, "Box001"));
	ufbxt_assert(nodes[1]->has_geometry_transform);
	ufbxt_assert(nodes[1]->node_depth == 1);
	ufbxt_assert(nodes[1]->parent == nodes[0]);

	ufbxt_assert(!strcmp(nodes[2]->name.data, "Box002"));
	ufbxt_assert(nodes[2]->has_geometry_transform);
	ufbxt_assert(nodes[2]->node_depth == 2);
	ufbxt_assert(nodes[2]->parent == nodes[1]);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_order_helper, max_geometry_transform_order, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 5);
	ufbx_node **nodes = scene->nodes.data;

	ufbxt_assert(!strcmp(nodes[0]->name.data, ""));
	ufbxt_assert(nodes[0]->is_root);
	ufbxt_assert(nodes[0]->node_depth == 0);
	ufbxt_assert(nodes[0]->parent == NULL);

	ufbxt_assert(!strcmp(nodes[1]->name.data, "Box001"));
	ufbxt_assert(nodes[1]->geometry_transform_helper);
	ufbxt_assert(nodes[1]->node_depth == 1);
	ufbxt_assert(nodes[1]->parent == nodes[0]);

	ufbxt_assert(!strcmp(nodes[2]->name.data, ""));
	ufbxt_assert(nodes[2]->is_geometry_transform_helper);
	ufbxt_assert(nodes[2]->node_depth == 2);
	ufbxt_assert(nodes[2]->parent == nodes[1]);

	ufbxt_assert(!strcmp(nodes[3]->name.data, "Box002"));
	ufbxt_assert(nodes[3]->geometry_transform_helper);
	ufbxt_assert(nodes[3]->node_depth == 2);
	ufbxt_assert(nodes[3]->parent == nodes[1]);

	ufbxt_assert(!strcmp(nodes[4]->name.data, ""));
	ufbxt_assert(nodes[4]->is_geometry_transform_helper);
	ufbxt_assert(nodes[4]->node_depth == 3);
	ufbxt_assert(nodes[4]->parent == nodes[3]);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(max_geometry_transform_order_modify, max_geometry_transform_order, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbxt_assert(scene->nodes.count == 5);
	ufbx_node **nodes = scene->nodes.data;

	ufbxt_assert(!strcmp(nodes[0]->name.data, ""));
	ufbxt_assert(nodes[0]->is_root);
	ufbxt_assert(nodes[0]->node_depth == 0);
	ufbxt_assert(nodes[0]->parent == NULL);

	ufbxt_assert(!strcmp(nodes[1]->name.data, "Box001"));
	ufbxt_assert(nodes[1]->geometry_transform_helper);
	ufbxt_assert(nodes[1]->node_depth == 1);
	ufbxt_assert(nodes[1]->parent == nodes[0]);

	ufbxt_assert(!strcmp(nodes[2]->name.data, ""));
	ufbxt_assert(nodes[2]->is_geometry_transform_helper);
	ufbxt_assert(nodes[2]->node_depth == 2);
	ufbxt_assert(nodes[2]->parent == nodes[1]);

	ufbxt_assert(!strcmp(nodes[3]->name.data, "Box002"));
	ufbxt_assert(nodes[3]->geometry_transform_helper);
	ufbxt_assert(nodes[3]->node_depth == 2);
	ufbxt_assert(nodes[3]->parent == nodes[1]);

	ufbxt_assert(!strcmp(nodes[4]->name.data, ""));
	ufbxt_assert(nodes[4]->is_geometry_transform_helper);
	ufbxt_assert(nodes[4]->node_depth == 3);
	ufbxt_assert(nodes[4]->parent == nodes[3]);
}
#endif


UFBXT_FILE_TEST(synthetic_geometry_transform_camera)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Camera001");
	ufbxt_assert(node);

	ufbxt_ref_transform geo_transform = {
		{ 10.0f, 20.0f, 30.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 2.0f, 3.0f },
	};
	ufbxt_check_transform(err, "Camera geometry", node->geometry_transform, geo_transform);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_geometry_transform_camera_helper, synthetic_geometry_transform_camera, ufbxt_geometry_transform_helper_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Camera001");
	ufbxt_assert(node);
	ufbxt_assert(!node->has_geometry_transform);

	ufbx_node *geo_node = node->geometry_transform_helper;
	ufbxt_assert(geo_node);

	ufbxt_ref_transform geo_transform = {
		{ 10.0f, 20.0f, 30.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 2.0f, 3.0f },
	};
	ufbxt_check_transform(err, "Camera geometry", geo_node->local_transform, geo_transform);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_geometry_transform_camera_modify, synthetic_geometry_transform_camera, ufbxt_geometry_transform_modify_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Camera001");
	ufbxt_assert(node);
	ufbxt_assert(!node->has_geometry_transform);

	ufbx_node *geo_node = node->geometry_transform_helper;
	ufbxt_assert(geo_node);

	ufbxt_ref_transform geo_transform = {
		{ 10.0f, 20.0f, 30.0f }, { 90.0f, 0.0f, 0.0f }, { 1.0f, 2.0f, 3.0f },
	};
	ufbxt_check_transform(err, "Camera geometry", geo_node->local_transform, geo_transform);
}
#endif

UFBXT_FILE_TEST_OPTS_ALT_FLAGS(synthetic_geometry_transform_camera_modify_no_fallback, synthetic_geometry_transform_camera, ufbxt_geometry_transform_modify_no_fallback_opts,
	UFBXT_FILE_TEST_FLAG_FUZZ_ALWAYS|UFBXT_FILE_TEST_FLAG_FUZZ_OPTS|UFBXT_FILE_TEST_FLAG_DIFF_ALWAYS)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Camera001");
	ufbxt_assert(node);
	ufbxt_assert(!node->has_geometry_transform);
}
#endif

UFBXT_TEST(synthetic_unnormalized)
#if UFBXT_IMPL
{
	char path[512];
	ufbxt_diff_error err = { 0 };

	ufbxt_file_iterator iter = { "synthetic_unnormalized" };
	while (ufbxt_next_file(&iter, path, sizeof(path))) {
		for (int normalize_normals = 0; normalize_normals <= 1; normalize_normals++)
		for (int normalize_tangents = 0; normalize_tangents <= 1; normalize_tangents++) {
			ufbxt_hintf("normalize_normals=%d normalize_tangets=%d", normalize_normals, normalize_tangents);
			ufbx_load_opts opts = { 0 };
			opts.normalize_normals = normalize_normals != 0;
			opts.normalize_tangents = normalize_tangents != 0;
			ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
			ufbxt_assert(scene);
			ufbxt_check_scene(scene);

			ufbx_node *node = ufbx_find_node(scene, "pPlane1");
			ufbxt_assert(node && node->mesh);
			ufbx_mesh *mesh = node->mesh;

			ufbxt_assert(mesh->vertex_normal.values.count == 4);
			for (size_t i = 0; i < 4; i++) {
				ufbx_vec3 ref = ufbx_zero_vec3;
				if (normalize_normals) {
					ref.y = i > 0 ? 1.0f : 0.0f;
				} else {
					ref.y = (ufbx_real)i;
				}
				ufbxt_assert_close_vec3(&err, mesh->vertex_normal.values.data[i], ref);
			}

			ufbxt_assert(mesh->vertex_tangent.values.count == 4);
			for (size_t i = 0; i < 4; i++) {
				ufbx_vec3 ref = ufbx_zero_vec3;
				if (normalize_tangents) {
					ref.x = i > 0 ? 1.0f : 0.0f;
				} else {
					ref.x = (ufbx_real)i;
				}
				ufbxt_assert_close_vec3(&err, mesh->vertex_tangent.values.data[i], ref);
			}

			ufbxt_assert(mesh->vertex_bitangent.values.count == 4);
			for (size_t i = 0; i < 4; i++) {
				ufbx_vec3 ref = ufbx_zero_vec3;
				if (normalize_tangents) {
					ref.z = i > 0 ? -1.0f : 0.0f;
				} else {
					ref.z = -(ufbx_real)i;
				}
				ufbxt_assert_close_vec3(&err, mesh->vertex_bitangent.values.data[i], ref);
			}

			ufbx_free_scene(scene);
		}
	}

	if (err.num > 0) {
		ufbx_real avg = err.sum / (ufbx_real)err.num;
		ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", avg, err.max, err.num);
	}
}
#endif
