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 }