1 module voxelman.graphics.armature;
2 
3 import voxelman.graphics.batch;
4 import voxelman.container.buffer;
5 import voxelman.math;
6 
7 import dlib.math.transformation : fromEuler;
8 
9 struct Armature
10 {
11 	struct Bone
12 	{
13 		Bone* parent;
14 		string name;
15 		Batch mesh;
16 		Matrix4f baseTransform = Matrix4f.identity;
17 		Matrix4f movement = Matrix4f.identity;
18 		Buffer!Bone children;
19 
20 		Matrix4f transform() @property
21 		{
22 			return baseTransform * movement;
23 		}
24 
25 		void reset()
26 		{
27 			movement = Matrix4f.identity;
28 		}
29 
30 		void rotate(vec3 euler)
31 		{
32 			movement *= fromEuler(euler);
33 		}
34 	}
35 
36 	string name;
37 	Bone root;
38 	/// Buffer which has reserved as many items as bones exist in this armature, can be used for recursive iteration.
39 	Buffer!Bone fullBuffer;
40 
41 	this(string name, Matrix4f transform)
42 	{
43 		this.name = name;
44 		root.name = name ~ "_root";
45 		root.baseTransform = transform;
46 		root.reset();
47 		fullBuffer.reserve(1);
48 	}
49 
50 	Bone* findBoneByName(string bone)
51 	{
52 		string fullName = this.name ~ '_' ~ bone;
53 		foreach (ref bone; this)
54 			if (bone.name == fullName)
55 				return &bone;
56 		return null;
57 	}
58 
59 	void addRoot(string childName, Batch mesh, Matrix4f transform)
60 	{
61 		addChild(&root, childName, mesh, transform);
62 	}
63 
64 	void addChild(string bone, string childName, Batch mesh, Matrix4f transform)
65 	{
66 		assert(findBoneByName(childName) == null, "Bone with name " ~ childName ~ " already exists");
67 		addChild(findBoneByName(bone), childName, mesh, transform);
68 	}
69 
70 	void addChild(Bone* bone, string childName, Batch mesh, Matrix4f transform)
71 	{
72 		Bone newBone;
73 		newBone.name = this.name ~ '_' ~ childName;
74 		newBone.parent = bone;
75 		newBone.baseTransform = transform;
76 		newBone.mesh = mesh;
77 		newBone.reset();
78 		bone.children.put(newBone);
79 		fullBuffer.reserve(1);
80 		// don't return pointer to data because data might move on next allocation, use names instead
81 	}
82 
83 	/// Iterates over each bone in the tree structure by ref
84 	int opApply(scope int delegate(ref Bone) dg)
85 	{
86 		auto todo = fullBuffer;
87 		todo.clear();
88 		auto result = dg(root);
89 		if (result)
90 			return result;
91 		todo.put(root);
92 		while (!todo.empty)
93 		{
94 			auto children = todo.back.children.data;
95 			todo.unput(1);
96 			foreach (ref bone; children)
97 			{
98 				result = dg(bone);
99 				if (result)
100 					return result;
101 				todo.put(bone);
102 			}
103 		}
104 		return result;
105 	}
106 }
107 
108 ///
109 unittest
110 {
111 	import voxelman.graphics.color : Colors;
112 
113 	Batch head = Batch.cube(vec3(0.5f, 0.5f, 0.5f), Colors.white, true);
114 	Batch body_ = Batch.cube(vec3(0.5f, 0.8f, 0.5f), Colors.white, true);
115 
116 	Armature armature = Armature("human", translationMatrix(vec3(0, 1.5f, 0)));
117 	armature.addRoot("body", body_, Matrix4f.identity);
118 	armature.addChild("body", "head", head, translationMatrix(vec3(0, 0.65f, 0)));
119 
120 	// after adding all bones
121 
122 	auto bodyBone = armature.findBoneByName("body");
123 	auto headBone = armature.findBoneByName("head");
124 	// bones should not be modified at this point as they might mess up bone references
125 
126 	bodyBone.rotate(vec3(degtorad(90.0f), 0, 0));
127 	headBone.rotate(vec3(0, degtorad(90.0f), 0));
128 
129 	// should be bent down, looking left now
130 }