#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "QF/scene/hierarchy.h"
#include "QF/scene/scene.h"
#include "QF/scene/transform.h"

scene_t *scene;

// NOTE: these are the columns of the matrix! (not that it matters for a
// symmetrical matrix, but...)
mat4f_t identity = {
	{ 1, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 0, 0, 1, 0 },
	{ 0, 0, 0, 1 },
};
vec4f_t one = { 1, 1, 1, 1 };

static int
vec4_equal (vec4f_t a, vec4f_t b)
{
	vec4i_t     res = a != b;
	return !(res[0] || res[1] || res[2] || res[3]);
}

static int
mat4_equal (const mat4f_t a, const mat4f_t b)
{
	vec4i_t     res = {};

	for (int i = 0; i < 4; i++) {
		res |= a[i] != b[i];
	}
	return !(res[0] || res[1] || res[2] || res[3]);
}

static int
check_hierarchy_size (hierarchy_t *h, uint32_t size)
{
	if (h->transform.size != size
		|| h->entity.size != size
		|| h->childCount.size != size
		|| h->childIndex.size != size
		|| h->parentIndex.size != size
		|| h->name.size != size
		|| h->tag.size != size
		|| h->modified.size != size
		|| h->localMatrix.size != size
		|| h->localInverse.size != size
		|| h->worldMatrix.size != size
		|| h->worldInverse.size != size
		|| h->localRotation.size != size
		|| h->localScale.size != size
		|| h->worldRotation.size != size
		|| h->worldScale.size != size) {
		printf ("hierarchy does not have exactly %u"
				" transform or array sizes are inconsistent\n", size);
		return 0;
	}
	for (size_t i = 0; i < h->transform.size; i++) {
		if (h->transform.a[i]->hierarchy != h) {
			printf ("transform %zd (%s) does not point to hierarchy\n",
					i, h->name.a[i]);
		}
	}
	return 1;
}

static void
dump_hierarchy (hierarchy_t *h)
{
	for (size_t i = 0; i < h->transform.size; i++) {
		printf ("%2zd: %5s %2u %2u %2u %2u\n", i, h->name.a[i],
				h->transform.a[i]->index, h->parentIndex.a[i],
				h->childIndex.a[i], h->childCount.a[i]);
	}
	puts ("");
}

static int
check_indices (transform_t *transform, uint32_t index, uint32_t parentIndex,
			   uint32_t childIndex, uint32_t childCount)
{
	hierarchy_t *h = transform->hierarchy;
	if (transform->index != index) {
		printf ("%s/%s index incorrect: expect %u got %u\n",
				h->name.a[transform->index], h->name.a[index],
				index, transform->index);
		return 0;
	}
	if (h->parentIndex.a[index] != parentIndex) {
		printf ("%s parent index incorrect: expect %u got %u\n",
				h->name.a[index], parentIndex, h->parentIndex.a[index]);
		return 0;
	}
	if (h->childIndex.a[index] != childIndex) {
		printf ("%s child index incorrect: expect %u got %u\n",
				h->name.a[index], childIndex, h->childIndex.a[index]);
		return 0;
	}
	if (h->childCount.a[index] != childCount) {
		printf ("%s child count incorrect: expect %u got %u\n",
				h->name.a[index], childCount, h->childCount.a[index]);
		return 0;
	}
	return 1;
}

static int
test_single_transform (void)
{
	transform_t *transform = Transform_New (scene, 0);
	hierarchy_t *h;

	if (!transform) {
		printf ("Transform_New returned null\n");
		return 1;
	}
	if (!(h = transform->hierarchy)) {
		printf ("New transform has no hierarchy\n");
		return 1;
	}
	if (!check_hierarchy_size (h, 1)) { return 1; }
	if (!check_indices (transform, 0, null_transform, 1, 0)) { return 1; }

	if (!mat4_equal (h->localMatrix.a[0], identity)
		|| !mat4_equal (h->localInverse.a[0], identity)
		|| !mat4_equal (h->worldMatrix.a[0], identity)
		|| !mat4_equal (h->worldInverse.a[0], identity)) {
		printf ("New transform matrices not identity\n");
		return 1;
	}

	if (!vec4_equal (h->localRotation.a[0], identity[3])
		|| !vec4_equal (h->localScale.a[0], one)) {
		printf ("New transform rotation or scale not identity\n");
		return 1;
	}

	// Delete the hierarchy directly as setparent isn't fully tested
	Hierarchy_Delete (transform->hierarchy);

	return 0;
}

static int
test_parent_child_init (void)
{
	transform_t *parent = Transform_New (scene, 0);
	transform_t *child = Transform_New (scene, parent);

	if (parent->hierarchy != child->hierarchy) {
		printf ("parent and child transforms have separate hierarchies\n");
		return 1;
	}

	if (!check_hierarchy_size (parent->hierarchy, 2)) { return 1; }

	if (!check_indices (parent, 0, null_transform, 1, 1)) { return 1; }
	if (!check_indices (child,  1, 0, 2, 0)) { return 1; }

	hierarchy_t *h = parent->hierarchy;
	if (!mat4_equal (h->localMatrix.a[0], identity)
		|| !mat4_equal (h->localInverse.a[0], identity)
		|| !mat4_equal (h->worldMatrix.a[0], identity)
		|| !mat4_equal (h->worldInverse.a[0], identity)) {
		printf ("Parent transform matrices not identity\n");
		return 1;
	}

	if (!vec4_equal (h->localRotation.a[0], identity[3])
		|| !vec4_equal (h->localScale.a[0], one)) {
		printf ("Parent transform rotation or scale not identity\n");
		return 1;
	}

	if (!mat4_equal (h->localMatrix.a[1], identity)
		|| !mat4_equal (h->localInverse.a[1], identity)
		|| !mat4_equal (h->worldMatrix.a[1], identity)
		|| !mat4_equal (h->worldInverse.a[1], identity)) {
		printf ("Child transform matrices not identity\n");
		return 1;
	}

	if (!vec4_equal (h->localRotation.a[1], identity[3])
		|| !vec4_equal (h->localScale.a[1], one)) {
		printf ("Child transform rotation or scale not identity\n");
		return 1;
	}

	// Delete the hierarchy directly as setparent isn't fully tested
	Hierarchy_Delete (parent->hierarchy);

	return 0;
}

static int
test_parent_child_setparent (void)
{
	transform_t *parent = Transform_New (scene, 0);
	transform_t *child = Transform_New (scene, 0);

	Transform_SetName (parent, "parent");
	Transform_SetName (child, "child");

	if (!check_indices (parent, 0, null_transform, 1, 0)) { return 1; }
	if (!check_indices (child,  0, null_transform, 1, 0)) { return 1; }

	if (parent->hierarchy == child->hierarchy) {
		printf ("parent and child transforms have same hierarchy before"
				" set paret\n");
		return 1;
	}

	Transform_SetParent (child, parent);

	if (parent->hierarchy != child->hierarchy) {
		printf ("parent and child transforms have separate hierarchies\n");
		return 1;
	}

	if (!check_hierarchy_size (parent->hierarchy, 2)) { return 1; }

	if (!check_indices (parent, 0, null_transform, 1, 1)) { return 1; }
	if (!check_indices (child,  1, 0, 2, 0)) { return 1; }

	hierarchy_t *h = parent->hierarchy;
	if (!mat4_equal (h->localMatrix.a[0], identity)
		|| !mat4_equal (h->localInverse.a[0], identity)
		|| !mat4_equal (h->worldMatrix.a[0], identity)
		|| !mat4_equal (h->worldInverse.a[0], identity)) {
		printf ("Parent transform matrices not identity\n");
		return 1;
	}

	if (!vec4_equal (h->localRotation.a[0], identity[3])
		|| !vec4_equal (h->localScale.a[0], one)) {
		printf ("Parent transform rotation or scale not identity\n");
		return 1;
	}

	if (!mat4_equal (h->localMatrix.a[1], identity)
		|| !mat4_equal (h->localInverse.a[1], identity)
		|| !mat4_equal (h->worldMatrix.a[1], identity)
		|| !mat4_equal (h->worldInverse.a[1], identity)) {
		printf ("Child transform matrices not identity\n");
		return 1;
	}

	if (!vec4_equal (h->localRotation.a[1], identity[3])
		|| !vec4_equal (h->localScale.a[1], one)) {
		printf ("Child transform rotation or scale not identity\n");
		return 1;
	}

	// Delete the hierarchy directly as setparent isn't fully tested
	Hierarchy_Delete (parent->hierarchy);

	return 0;
}

static int
test_build_hierarchy (void)
{
	printf ("test_build_hierarchy\n");

	transform_t *root = Transform_NewNamed (scene, 0, "root");
	transform_t *A = Transform_NewNamed (scene, root, "A");
	transform_t *B = Transform_NewNamed (scene, root, "B");
	transform_t *C = Transform_NewNamed (scene, root, "C");

	if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; }
	if (!check_indices (A, 1, 0, 4, 0)) { return 1; }
	if (!check_indices (B, 2, 0, 4, 0)) { return 1; }
	if (!check_indices (C, 3, 0, 4, 0)) { return 1; }

	transform_t *B1 = Transform_NewNamed (scene, B, "B1");

	if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; }
	if (!check_indices ( A, 1, 0, 4, 0)) { return 1; }
	if (!check_indices ( B, 2, 0, 4, 1)) { return 1; }
	if (!check_indices ( C, 3, 0, 5, 0)) { return 1; }
	if (!check_indices (B1, 4, 2, 5, 0)) { return 1; }

	transform_t *A1 = Transform_NewNamed (scene, A, "A1");

	if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; }
	if (!check_indices ( A, 1, 0, 4, 1)) { return 1; }
	if (!check_indices ( B, 2, 0, 5, 1)) { return 1; }
	if (!check_indices ( C, 3, 0, 6, 0)) { return 1; }
	if (!check_indices (A1, 4, 1, 6, 0)) { return 1; }
	if (!check_indices (B1, 5, 2, 6, 0)) { return 1; }
	transform_t *A1a = Transform_NewNamed (scene, A1, "A1a");
	transform_t *B2 = Transform_NewNamed (scene, B, "B2");
	transform_t *A2 = Transform_NewNamed (scene, A, "A2");
	transform_t *B3 = Transform_NewNamed (scene, B, "B3");
	transform_t *B2a = Transform_NewNamed (scene, B2, "B2a");

	if (!check_hierarchy_size (root->hierarchy, 11)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; }
	if (!check_indices (  A,  1, 0,  4, 2)) { return 1; }
	if (!check_indices (  B,  2, 0,  6, 3)) { return 1; }
	if (!check_indices (  C,  3, 0,  9, 0)) { return 1; }
	if (!check_indices ( A1,  4, 1,  9, 1)) { return 1; }
	if (!check_indices ( A2,  5, 1, 10, 0)) { return 1; }
	if (!check_indices ( B1,  6, 2, 10, 0)) { return 1; }
	if (!check_indices ( B2,  7, 2, 10, 1)) { return 1; }
	if (!check_indices ( B3,  8, 2, 11, 0)) { return 1; }
	if (!check_indices (A1a,  9, 4, 11, 0)) { return 1; }
	if (!check_indices (B2a, 10, 7, 11, 0)) { return 1; }

	transform_t *D = Transform_NewNamed (scene, root, "D");

	if (!check_hierarchy_size (root->hierarchy, 12)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; }
	if (!check_indices (  A,  1, 0,  5, 2)) { return 1; }
	if (!check_indices (  B,  2, 0,  7, 3)) { return 1; }
	if (!check_indices (  C,  3, 0, 10, 0)) { return 1; }
	if (!check_indices (  D,  4, 0, 10, 0)) { return 1; }
	if (!check_indices ( A1,  5, 1, 10, 1)) { return 1; }
	if (!check_indices ( A2,  6, 1, 11, 0)) { return 1; }
	if (!check_indices ( B1,  7, 2, 11, 0)) { return 1; }
	if (!check_indices ( B2,  8, 2, 11, 1)) { return 1; }
	if (!check_indices ( B3,  9, 2, 12, 0)) { return 1; }
	if (!check_indices (A1a, 10, 5, 12, 0)) { return 1; }
	if (!check_indices (B2a, 11, 8, 12, 0)) { return 1; }

	dump_hierarchy (root->hierarchy);
	transform_t *C1 = Transform_NewNamed (scene, C, "C1");
	dump_hierarchy (root->hierarchy);
	if (!check_hierarchy_size (root->hierarchy, 13)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; }
	if (!check_indices (  A,  1, 0,  5, 2)) { return 1; }
	if (!check_indices (  B,  2, 0,  7, 3)) { return 1; }
	if (!check_indices (  C,  3, 0, 10, 1)) { return 1; }
	if (!check_indices (  D,  4, 0, 11, 0)) { return 1; }
	if (!check_indices ( A1,  5, 1, 11, 1)) { return 1; }
	if (!check_indices ( A2,  6, 1, 12, 0)) { return 1; }
	if (!check_indices ( B1,  7, 2, 12, 0)) { return 1; }
	if (!check_indices ( B2,  8, 2, 12, 1)) { return 1; }
	if (!check_indices ( B3,  9, 2, 13, 0)) { return 1; }
	if (!check_indices ( C1, 10, 3, 13, 0)) { return 1; }
	if (!check_indices (A1a, 11, 5, 13, 0)) { return 1; }
	if (!check_indices (B2a, 12, 8, 13, 0)) { return 1; }

	// Delete the hierarchy directly as setparent isn't fully tested
	Hierarchy_Delete (root->hierarchy);

	return 0;
}

static int
test_build_hierarchy2 (void)
{
	printf ("test_build_hierarchy2\n");

	transform_t *root = Transform_NewNamed (scene, 0, "root");
	transform_t *A = Transform_NewNamed (scene, root, "A");
	transform_t *B = Transform_NewNamed (scene, root, "B");
	transform_t *C = Transform_NewNamed (scene, root, "C");
	transform_t *B1 = Transform_NewNamed (scene, B, "B1");
	transform_t *A1 = Transform_NewNamed (scene, A, "A1");
	transform_t *A1a = Transform_NewNamed (scene, A1, "A1a");
	transform_t *B2 = Transform_NewNamed (scene, B, "B2");
	transform_t *A2 = Transform_NewNamed (scene, A, "A2");
	transform_t *B3 = Transform_NewNamed (scene, B, "B3");
	transform_t *B2a = Transform_NewNamed (scene, B2, "B2a");
	transform_t *D = Transform_NewNamed (scene, root, "D");
	transform_t *C1 = Transform_NewNamed (scene, C, "C1");

	if (!check_hierarchy_size (root->hierarchy, 13)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; }
	if (!check_indices (  A,  1, 0,  5, 2)) { return 1; }
	if (!check_indices (  B,  2, 0,  7, 3)) { return 1; }
	if (!check_indices (  C,  3, 0, 10, 1)) { return 1; }
	if (!check_indices (  D,  4, 0, 11, 0)) { return 1; }
	if (!check_indices ( A1,  5, 1, 11, 1)) { return 1; }
	if (!check_indices ( A2,  6, 1, 12, 0)) { return 1; }
	if (!check_indices ( B1,  7, 2, 12, 0)) { return 1; }
	if (!check_indices ( B2,  8, 2, 12, 1)) { return 1; }
	if (!check_indices ( B3,  9, 2, 13, 0)) { return 1; }
	if (!check_indices ( C1, 10, 3, 13, 0)) { return 1; }
	if (!check_indices (A1a, 11, 5, 13, 0)) { return 1; }
	if (!check_indices (B2a, 12, 8, 13, 0)) { return 1; }

	transform_t *T = Transform_NewNamed (scene, 0, "T");
	transform_t *X = Transform_NewNamed (scene, T, "X");
	transform_t *Y = Transform_NewNamed (scene, T, "Y");
	transform_t *Z = Transform_NewNamed (scene, T, "Z");
	transform_t *Y1 = Transform_NewNamed (scene, Y, "Y1");
	transform_t *X1 = Transform_NewNamed (scene, X, "X1");
	transform_t *X1a = Transform_NewNamed (scene, X1, "X1a");
	transform_t *Y2 = Transform_NewNamed (scene, Y, "Y2");
	transform_t *X2 = Transform_NewNamed (scene, X, "X2");
	transform_t *Y3 = Transform_NewNamed (scene, Y, "Y3");
	transform_t *Y2a = Transform_NewNamed (scene, Y2, "Y2a");
	transform_t *Z1 = Transform_NewNamed (scene, Z, "Z1");

	dump_hierarchy (T->hierarchy);
	if (!check_hierarchy_size (T->hierarchy, 12)) { return 1; }

	if (!check_indices (  T,  0, null_transform, 1, 3)) { return 1; }
	if (!check_indices (  X,  1, 0,  4, 2)) { return 1; }
	if (!check_indices (  Y,  2, 0,  6, 3)) { return 1; }
	if (!check_indices (  Z,  3, 0,  9, 1)) { return 1; }
	if (!check_indices ( X1,  4, 1, 10, 1)) { return 1; }
	if (!check_indices ( X2,  5, 1, 11, 0)) { return 1; }
	if (!check_indices ( Y1,  6, 2, 11, 0)) { return 1; }
	if (!check_indices ( Y2,  7, 2, 11, 1)) { return 1; }
	if (!check_indices ( Y3,  8, 2, 12, 0)) { return 1; }
	if (!check_indices ( Z1,  9, 3, 12, 0)) { return 1; }
	if (!check_indices (X1a, 10, 4, 12, 0)) { return 1; }
	if (!check_indices (Y2a, 11, 7, 12, 0)) { return 1; }

	Transform_SetParent (T, B);

	dump_hierarchy (root->hierarchy);

	if (!check_hierarchy_size (root->hierarchy, 25)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; }
	if (!check_indices (  A,  1,  0,  5, 2)) { return 1; }
	if (!check_indices (  B,  2,  0,  7, 4)) { return 1; }
	if (!check_indices (  C,  3,  0, 11, 1)) { return 1; }
	if (!check_indices (  D,  4,  0, 12, 0)) { return 1; }
	if (!check_indices ( A1,  5,  1, 12, 1)) { return 1; }
	if (!check_indices ( A2,  6,  1, 13, 0)) { return 1; }
	if (!check_indices ( B1,  7,  2, 13, 0)) { return 1; }
	if (!check_indices ( B2,  8,  2, 13, 1)) { return 1; }
	if (!check_indices ( B3,  9,  2, 14, 0)) { return 1; }
	if (!check_indices (  T, 10,  2, 14, 3)) { return 1; }
	if (!check_indices ( C1, 11,  3, 17, 0)) { return 1; }
	if (!check_indices (A1a, 12,  5, 17, 0)) { return 1; }
	if (!check_indices (B2a, 13,  8, 17, 0)) { return 1; }
	if (!check_indices (  X, 14, 10, 17, 2)) { return 1; }
	if (!check_indices (  Y, 15, 10, 19, 3)) { return 1; }
	if (!check_indices (  Z, 16, 10, 22, 1)) { return 1; }
	if (!check_indices ( X1, 17, 14, 23, 1)) { return 1; }
	if (!check_indices ( X2, 18, 14, 24, 0)) { return 1; }
	if (!check_indices ( Y1, 19, 15, 24, 0)) { return 1; }
	if (!check_indices ( Y2, 20, 15, 24, 1)) { return 1; }
	if (!check_indices ( Y3, 21, 15, 25, 0)) { return 1; }
	if (!check_indices ( Z1, 22, 16, 25, 0)) { return 1; }
	if (!check_indices (X1a, 23, 17, 25, 0)) { return 1; }
	if (!check_indices (Y2a, 24, 20, 25, 0)) { return 1; }

	Transform_SetParent (Y, 0);

	dump_hierarchy (root->hierarchy);
	dump_hierarchy (Y->hierarchy);
	if (!check_hierarchy_size (root->hierarchy, 20)) { return 1; }
	if (!check_hierarchy_size (Y->hierarchy, 5)) { return 1; }

	if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; }
	if (!check_indices (  A,  1,  0,  5, 2)) { return 1; }
	if (!check_indices (  B,  2,  0,  7, 4)) { return 1; }
	if (!check_indices (  C,  3,  0, 11, 1)) { return 1; }
	if (!check_indices (  D,  4,  0, 12, 0)) { return 1; }
	if (!check_indices ( A1,  5,  1, 12, 1)) { return 1; }
	if (!check_indices ( A2,  6,  1, 13, 0)) { return 1; }
	if (!check_indices ( B1,  7,  2, 13, 0)) { return 1; }
	if (!check_indices ( B2,  8,  2, 13, 1)) { return 1; }
	if (!check_indices ( B3,  9,  2, 14, 0)) { return 1; }
	if (!check_indices (  T, 10,  2, 14, 3)) { return 1; }
	if (!check_indices ( C1, 11,  3, 17, 0)) { return 1; }
	if (!check_indices (A1a, 12,  5, 17, 0)) { return 1; }
	if (!check_indices (B2a, 13,  8, 17, 0)) { return 1; }
	if (!check_indices (  X, 14, 10, 16, 2)) { return 1; }
	if (!check_indices (  Z, 15, 10, 18, 1)) { return 1; }
	if (!check_indices ( X1, 16, 14, 19, 1)) { return 1; }
	if (!check_indices ( X2, 17, 14, 20, 0)) { return 1; }
	if (!check_indices ( Z1, 18, 15, 20, 0)) { return 1; }
	if (!check_indices (X1a, 19, 16, 20, 0)) { return 1; }

	if (!check_indices (  Y, 0, null_transform, 1, 3)) { return 1; }
	if (!check_indices ( Y1, 1, 0, 4, 0)) { return 1; }
	if (!check_indices ( Y2, 2, 0, 4, 1)) { return 1; }
	if (!check_indices ( Y3, 3, 0, 5, 0)) { return 1; }
	if (!check_indices (Y2a, 4, 2, 5, 0)) { return 1; }

	// Delete the hierarchy directly as setparent isn't fully tested
	Hierarchy_Delete (root->hierarchy);
	Hierarchy_Delete (Y->hierarchy);

	return 0;
}

static int
check_vector (const transform_t *transform,
			  vec4f_t (*func) (const transform_t *t),
			  vec4f_t expect, const char *msg)
{
	vec4f_t     res = func(transform);
	if (!vec4_equal (res, expect)) {
		printf ("%s %s: expected "VEC4F_FMT" got "VEC4F_FMT"\n",
				Transform_GetName (transform), msg,
				VEC4_EXP (expect), VEC4_EXP (res));
		return 0;
	}
	return 1;
}

static int
test_frames (void)
{
	transform_t *root = Transform_NewNamed (scene, 0, "root");
	transform_t *A = Transform_NewNamed (scene, root, "A");
	transform_t *B = Transform_NewNamed (scene, root, "B");
	transform_t *A1 = Transform_NewNamed (scene, A, "A1");
	transform_t *B1 = Transform_NewNamed (scene, B, "B1");

	Transform_SetLocalPosition (root, (vec4f_t) { 0, 0, 1, 1 });
	Transform_SetLocalPosition (A, (vec4f_t) { 1, 0, 0, 1 });
	Transform_SetLocalRotation (A, (vec4f_t) { 0.5, 0.5, 0.5, 0.5 });
	Transform_SetLocalPosition (B, (vec4f_t) { 0, 1, 0, 1 });
	Transform_SetLocalRotation (B, (vec4f_t) { 0.5, -0.5, 0.5, 0.5 });
	Transform_SetLocalPosition (A1, (vec4f_t) { 1, 0, 0, 1 });
	Transform_SetLocalRotation (A1, (vec4f_t) { -0.5, -0.5, -0.5, 0.5 });
	Transform_SetLocalPosition (B1, (vec4f_t) { 0, 1, 0, 1 });
	Transform_SetLocalRotation (B1, (vec4f_t) { -0.5, 0.5, -0.5, 0.5 });

	hierarchy_t *h = root->hierarchy;
	for (size_t i = 0; i < h->transform.size; i++) {
		mat4f_t     res;
		mmulf (res, h->localMatrix.a[i], h->localInverse.a[i]);
		if (!mat4_equal (res, identity)) {
			printf ("%s: localInverse not inverse of localMatrix\n",
					h->name.a[i]);
			printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 3));
			printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 3));
			printf ("r: " VEC4F_FMT "\n", MAT4_ROW(res, 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 3));
			return 1;
		}
		puts (h->name.a[i]);
		printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 0));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 1));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 2));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 3));
		printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 0));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 1));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 2));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 3));
	}
	for (size_t i = 0; i < h->transform.size; i++) {
		mat4f_t     res;
		mmulf (res, h->worldMatrix.a[i], h->worldInverse.a[i]);
		if (!mat4_equal (res, identity)) {
			printf ("%s: worldInverse not inverse of worldMatrix\n",
					h->name.a[i]);
			printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 3));
			printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 3));
			printf ("r: " VEC4F_FMT "\n", MAT4_ROW(res, 0));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 1));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 2));
			printf ("   " VEC4F_FMT "\n", MAT4_ROW(res, 3));
			return 1;
		}
		puts (h->name.a[i]);
		printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 0));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 1));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 2));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 3));
		printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 0));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 1));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 2));
		printf ("   " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 3));
	}

	if (!check_vector (root, Transform_GetLocalPosition,
					   (vec4f_t) { 0, 0, 1, 1 }, "local position")) {
		return 1;
	}
	if (!check_vector (root, Transform_GetWorldPosition,
					   (vec4f_t) { 0, 0, 1, 1 }, "world position")) {
		return 1;
	}
	if (!check_vector (root, Transform_Forward, (vec4f_t) { 1, 0, 0, 0 },
					   "forward")) {
		return 1;
	}
	if (!check_vector (root, Transform_Right, (vec4f_t) { 0, -1, 0, 0 },
					   "right")) {
		return 1;
	}
	if (!check_vector (root, Transform_Up, (vec4f_t) { 0, 0, 1, 0 },
					   "up")) {
		return 1;
	}

	if (!check_vector (A, Transform_GetLocalPosition, (vec4f_t) { 1, 0, 0, 1 },
					   "local position")) {
		return 1;
	}
	if (!check_vector (A, Transform_GetWorldPosition, (vec4f_t) { 1, 0, 1, 1 },
					   "world position")) {
		return 1;
	}
	if (!check_vector (A, Transform_Forward, (vec4f_t) { 0, 1, 0, 0 },
					   "forward")) {
		return 1;
	}
	if (!check_vector (A, Transform_Right, (vec4f_t) { 0, 0, -1, 0 },
					   "right")) {
		return 1;
	}
	if (!check_vector (A, Transform_Up, (vec4f_t) { 1, 0, 0, 0 },
					   "up")) {
		return 1;
	}
	if (!check_vector (A1, Transform_GetLocalPosition, (vec4f_t) { 1, 0, 0, 1 },
					   "local position")) {
		return 1;
	}
	if (!check_vector (A1, Transform_GetWorldPosition, (vec4f_t) { 1, 1, 1, 1 },
					   "world position")) {
		return 1;
	}
	if (!check_vector (A1, Transform_Forward, (vec4f_t) { 1, 0, 0, 0 },
					   "forward")) {
		return 1;
	}
	if (!check_vector (A1, Transform_Right, (vec4f_t) { 0, -1, 0, 0 },
					   "right")) {
		return 1;
	}
	if (!check_vector (A1, Transform_Up, (vec4f_t) { 0, 0, 1, 0 },
					   "up")) {
		return 1;
	}

	if (!check_vector (B, Transform_GetLocalPosition, (vec4f_t) { 0, 1, 0, 1 },
					   "local position")) {
		return 1;
	}
	if (!check_vector (B, Transform_GetWorldPosition, (vec4f_t) { 0, 1, 1, 1 },
					   "world position")) {
		return 1;
	}
	if (!check_vector (B, Transform_Forward, (vec4f_t) { 0, 0, 1, 0 },
					   "forward")) {
		return 1;
	}
	if (!check_vector (B, Transform_Right, (vec4f_t) { 1, 0, 0, 0 },
					   "right")) {
		return 1;
	}
	if (!check_vector (B, Transform_Up, (vec4f_t) { 0,-1, 0, 0 },
					   "up")) {
		return 1;
	}
	if (!check_vector (B1, Transform_GetLocalPosition, (vec4f_t) { 0, 1, 0, 1 },
					   "local position")) {
		return 1;
	}
	if (!check_vector (B1, Transform_GetWorldPosition, (vec4f_t) {-1, 1, 1, 1 },
					   "world position")) {
		return 1;
	}
	if (!check_vector (B1, Transform_Forward, (vec4f_t) { 1, 0, 0, 0 },
					   "forward")) {
		return 1;
	}
	if (!check_vector (B1, Transform_Right, (vec4f_t) { 0, -1, 0, 0 },
					   "right")) {
		return 1;
	}
	if (!check_vector (B1, Transform_Up, (vec4f_t) { 0, 0, 1, 0 },
					   "up")) {
		return 1;
	}

	Transform_Delete (root);

	return 0;
}

int
main (void)
{
	scene = Scene_NewScene ();

	if (test_single_transform ()) { return 1; }
	if (test_parent_child_init ()) { return 1; }
	if (test_parent_child_setparent ()) { return 1; }
	if (test_build_hierarchy ()) { return 1; }
	if (test_build_hierarchy2 ()) { return 1; }
	if (test_frames ()) { return 1; }

	Scene_DeleteScene (scene);

	return 0;
}
