1 module isodi.raylib.model; 2 3 import raylib; 4 5 import std.math; 6 import std.array; 7 import std.typecons; 8 import std.algorithm; 9 10 import isodi.model : Model; 11 import isodi.display; 12 import isodi.resource; 13 import isodi.raylib.internal; 14 import isodi.raylib.resources.bone; 15 16 17 @safe: 18 19 20 /// `Model` implementation for Raylib. 21 final class RaylibModel : Model, WithDrawableResources { 22 23 package { 24 25 Bone*[] bones; 26 Bone*[string] bonesID; 27 28 } 29 30 /// 31 this(Display display, const string type) { 32 33 super(display, type); 34 reload(); 35 36 } 37 38 override void changeVariant(string id, string variant) { 39 40 //auto bone = bonesID[id].changeVariant(variant); 41 assert(0, "unimplemented"); 42 43 } 44 45 /// 46 void reload() { 47 48 // Clear the array first 49 bones = []; 50 51 // Get the bones 52 auto skeleton = display.packs.getSkeleton(type); 53 foreach (node; skeleton.match) { 54 55 // Create a bone 56 auto bone = new Bone(this, node, getBone(node)); 57 bones ~= bone; 58 59 // Save by ID 60 assert(bone.node.id !in bonesID); 61 bonesID[bone.node.id] = bone; 62 63 } 64 65 } 66 67 /// 68 void draw() @trusted { 69 70 const rad = display.camera.angle.x * std.math.PI / 180; 71 72 runAnimations(); 73 74 rlPushMatrix(); 75 scope(exit) rlPopMatrix(); 76 77 // Sort the bones 78 bones.map!((a) => cameraDistance(a, rad)) 79 .array 80 .sort!((a, b) => a[1] > b[1]) 81 82 // Draw them 83 .each!(a => a[0].draw()); 84 85 } 86 87 private void runAnimations() { 88 89 foreach_reverse (i, ref animation; animations) { 90 91 const previousFrame = animation.frame; 92 const delta = (() @trusted => animation.fps * GetFrameTime)(); 93 94 // Increment frame time 95 animation.frame += delta; 96 97 // Run the animation 98 while (true) { 99 100 // Get the next part 101 const part = animation.parts[animation.current]; 102 103 // Frame delta / Frames until the end. Used to calculate changes in animated values 104 const progress = min(1, delta / (part.length - previousFrame)); 105 106 // Run the animation 107 runAnimationPart(part, progress); 108 109 // Stop if the part didn't end 110 if (progress != 1) break; 111 112 // Decrease frame by part length 113 animation.frame -= part.length; 114 115 // Advance to the next part 116 if (animation.advance == Yes.ended) { 117 118 // The animation ended, remove it 119 animations = animations.remove(i); 120 121 // Continue to the next animation 122 break; 123 124 } 125 126 // Break if this is an infinite loop 127 else if (animation.times == 0) break; 128 129 } 130 131 } 132 133 } 134 135 /// Run the current animation part. 136 private void runAnimationPart(const AnimationPart part, float progress) { 137 138 // TODO: offset 139 140 // Check the bones 141 foreach (bone, target; part.bone) { 142 143 // Get the node for this bone 144 auto node = bonesID.get(bone, null); 145 146 // Ignore if the node doesn't exist 147 // This is because models and animations might be provided by different packs and different models may 148 // have different complexity level — less complex models should still work with animations designed for 149 // advanced ones, assuming that basic naming is kept the same. 150 // Detecting mispellings and unknown bones is the job of a resource editor. 151 if (node is null) continue; 152 153 // Changing rotation 154 { 155 156 import std.range : enumerate; 157 158 // Tween each value 159 auto newValues = [node.boneRotation.tupleof] 160 .enumerate 161 .map!((item) { 162 163 const targetRad = target.rotate[item.index] * std.math.PI / 180; 164 165 return tweenAngle(progress, item.value, targetRad); 166 167 }); 168 169 // Assign it 170 node.boneRotation = Vector3(newValues[0], newValues[1], newValues[2]); 171 172 } 173 174 // Changing scale 175 if (target.scale != 0) { 176 177 node.boneScale = tween(progress, node.boneScale, target.scale); 178 179 } 180 181 } 182 183 } 184 185 /// Animate the given rotation property. 186 private T tweenAngle(T)(float progress, T currentValue, T target) { 187 188 // TODO: make this consider wrapping 189 return currentValue + progress * (target - currentValue); 190 191 } 192 193 /// Animate the given property. 194 private T tween(T)(float progress, T currentValue, T target) { 195 196 return currentValue + progress * (target - currentValue); 197 198 } 199 200 private Tuple!(Bone*, float) cameraDistance(Bone* bone, real rad) { 201 202 // Get new matrixes 203 bone.updateMatrixes(); 204 205 const vec = (() @trusted => Vector3Transform(Vector3Zero, bone.boneStart))(); 206 207 return Tuple!(Bone*, float)( 208 bone, 209 -vec.x * sin(rad) 210 - vec.z * cos(rad), 211 ); 212 213 } 214 215 }