1 module spine.dsfml.drawable_skeleton; 2 3 import spine.atlas; 4 import spine.dsfml.textures_storage; 5 import spine.skeleton; 6 import spine.skeleton_attach; 7 import spine.animation; 8 import spine.color; 9 import dsfml.graphics; 10 import dsfml.graphics.drawable; 11 import std.conv: to; 12 debug import std.math: isNaN; 13 debug import std.stdio; 14 15 enum SPINE_MESH_VERTEX_COUNT_MAX = 1000; 16 17 class SkeletonDrawable : Skeleton, Drawable 18 { 19 private VertexArray vertexArray; 20 private float[SPINE_MESH_VERTEX_COUNT_MAX] worldVertices; 21 22 this(SkeletonData sd) 23 { 24 super(sd); 25 26 vertexArray = new VertexArray(PrimitiveType.Triangles, sp_skeleton_protected.bonesCount * 4); 27 } 28 29 void draw(RenderTarget target, RenderStates states = RenderStates.Default) 30 { 31 debug(spine_dsfml) writeln("spine.dsfml.SkeletonDrawable.draw()"); 32 vertexArray.clear(); 33 34 Vertex[4] vertices; 35 Vertex vertex; 36 37 foreach(i; 0 .. sp_skeleton_protected.slotsCount) 38 { 39 debug(spine_dsfml) writeln("slot num=", i); 40 41 const spSlot* slot = sp_skeleton_protected.drawOrder[i]; 42 debug(spine_dsfml) writeln("slot=", *slot); 43 debug(spine_dsfml) writeln("slot.bone=", *slot.bone); 44 assert(!slot.bone.a.isNaN); 45 assert(!slot.bone.b.isNaN); 46 assert(!slot.bone.c.isNaN); 47 assert(!slot.bone.d.isNaN); 48 assert(!slot.bone.worldX.isNaN); 49 assert(!slot.bone.worldY.isNaN); 50 51 const spAttachment* attachment = slot.attachment; 52 53 if(attachment is null) continue; 54 55 BlendMode blend; 56 57 switch(slot.data.blendMode) 58 { 59 case spBlendMode.ADDITIVE: 60 blend = BlendMode.Add; 61 break; 62 63 case spBlendMode.MULTIPLY: 64 blend = BlendMode.Multiply; 65 break; 66 67 case spBlendMode.SCREEN: // Unsupported, fall through. 68 default: 69 blend = BlendMode.Alpha; 70 } 71 72 if(states.blendMode != blend) 73 { 74 target.draw(vertexArray, states); 75 vertexArray.clear(); 76 states.blendMode = blend; 77 } 78 79 Texture texture; 80 81 switch(attachment.type) 82 { 83 case spAttachmentType.REGION: 84 debug(spine_dsfml) writeln("draw region"); 85 86 spRegionAttachment* regionAttachment = cast(spRegionAttachment*) attachment; 87 88 size_t textureNum = cast(size_t)(cast(spAtlasRegion*)regionAttachment.rendererObject).page.rendererObject; 89 texture = loadedTextures[textureNum]; 90 assert(texture); 91 92 debug(spine_dsfml) writeln("call computeWorldVertices, args:"); 93 debug(spine_dsfml) writeln("regionAttachment=", *regionAttachment); 94 debug(spine_dsfml) writeln("and slot.bone=", *slot.bone); 95 spRegionAttachment_computeWorldVertices(regionAttachment, slot.bone, worldVertices.ptr, 0 , 2); 96 97 debug(spine_dsfml) writeln("call colorize"); 98 Color _c = colorize(sp_skeleton_protected, slot); 99 100 debug(spine_dsfml) writeln("call texture.getSize()"); 101 Vector2u size = texture.getSize(); 102 debug(spine_dsfml) writeln("size=", size); 103 104 debug(spine_dsfml) writeln("fill vertices"); 105 106 with(spVertexIndex) 107 { 108 with(vertices[0]) 109 { 110 color = _c; 111 position.x = worldVertices[X1]; 112 position.y = worldVertices[Y1]; 113 debug(spine_dsfml) writeln("worldVertices[X1]=", worldVertices[X1]); 114 assert(!worldVertices[X1].isNaN); 115 texCoords.x = regionAttachment.uvs[X1] * size.x; 116 texCoords.y = regionAttachment.uvs[Y1] * size.y; 117 assert(worldVertices[X1] != float.nan); 118 assert(position.x != float.nan); 119 } 120 121 with(vertices[1]) 122 { 123 color = _c; 124 position.x = worldVertices[X2]; 125 position.y = worldVertices[Y2]; 126 texCoords.x = regionAttachment.uvs[X2] * size.x; 127 texCoords.y = regionAttachment.uvs[Y2] * size.y; 128 } 129 130 with(vertices[2]) 131 { 132 color = _c; 133 position.x = worldVertices[X3]; 134 position.y = worldVertices[Y3]; 135 texCoords.x = regionAttachment.uvs[X3] * size.x; 136 texCoords.y = regionAttachment.uvs[Y3] * size.y; 137 } 138 139 with(vertices[3]) { 140 color = _c; 141 position.x = worldVertices[X4]; 142 position.y = worldVertices[Y4]; 143 texCoords.x = regionAttachment.uvs[X4] * size.x; 144 texCoords.y = regionAttachment.uvs[Y4] * size.y; 145 } 146 } 147 148 with(vertexArray) 149 { 150 append(vertices[0]); 151 append(vertices[1]); 152 append(vertices[2]); 153 append(vertices[0]); 154 append(vertices[2]); 155 append(vertices[3]); 156 } 157 break; 158 159 //~ case spAttachmentType.MESH: 160 //~ debug(spine_dsfml) writeln("draw mesh"); 161 162 //~ spMeshAttachment* mesh = cast(spMeshAttachment*) attachment; 163 164 //~ if (mesh._super.worldVerticesLength > SPINE_MESH_VERTEX_COUNT_MAX) continue; 165 //~ texture = cast(size_t)(cast(spAtlasRegion*)mesh.rendererObject).page.rendererObject; 166 //~ spMeshAttachment_computeWorldVertices(mesh, slot, worldVertices.ptr); 167 168 //~ vertex.color = colorize(sp_skeleton_protected, slot); 169 //~ Vector2u size = texture.getSize(); 170 171 //~ foreach(_i; 0 .. mesh.trianglesCount) 172 //~ { 173 //~ int index = mesh.triangles[_i] << 1; 174 //~ vertex.position.x = worldVertices[index]; 175 //~ vertex.position.y = worldVertices[index + 1]; 176 //~ vertex.texCoords.x = mesh.uvs[index] * size.x; 177 //~ vertex.texCoords.y = mesh.uvs[index + 1] * size.y; 178 //~ vertexArray.append(vertex); 179 //~ } 180 //~ break; 181 182 case spAttachmentType.BOUNDING_BOX: 183 break; 184 185 case spAttachmentType.SKELETON: 186 target.draw(vertexArray, states); 187 vertexArray.clear(); 188 189 spSkeletonAttachment_unofficial* att = cast(spSkeletonAttachment_unofficial*) attachment; 190 debug(spine_dsfml_skeleton) writeln("Skeleton ", att._super.name.to!string, " draw: attachedSkeletonIdx=", att.attachedSkeletonIdx); 191 192 SkeletonDrawable si = cast(SkeletonDrawable) attachedSkeletons[att.attachedSkeletonIdx]; 193 194 auto boneStates = states; 195 boneStates.transform.translate(slot.bone.worldX, slot.bone.worldY); 196 boneStates.transform.rotate(slot.bone.rotation); 197 198 debug(spine_dsfml_skeleton) writeln("spBone=", *slot.bone); 199 200 si.draw(target, boneStates); 201 break; 202 203 default: 204 assert(0, "Attachment type "~attachment.type.to!string~" isn't implementded"); 205 } 206 207 debug(spine_dsfml) writeln("vertexArray.getVertexCount=", vertexArray.getVertexCount); 208 209 if(texture !is null) 210 { 211 // SMFL doesn't handle batching for us, so we'll just force a single texture per skeleton. 212 states.texture = texture; 213 debug(spine_dsfml) writeln("Used texture at ", &texture); 214 } 215 } 216 217 debug 218 { 219 debug(spine_dsfml) writeln("vertexArray:"); 220 221 foreach(j; 0 .. vertexArray.getVertexCount) 222 { 223 debug(spine_dsfml) writeln(vertexArray[j]); 224 225 assert(!vertexArray[j].position.x.isNaN); 226 assert(!vertexArray[j].position.y.isNaN); 227 } 228 } 229 230 debug(spine_dsfml) writeln("call SFML draw"); 231 target.draw(vertexArray, states); 232 } 233 } 234 235 unittest 236 { 237 import spine.atlas; 238 import spine.skeleton; 239 import spine.skeleton_bounds; 240 import spine.skeleton_attach: setAttachment; 241 242 auto a = new Atlas("spine-runtimes/examples/spineboy/export/spineboy.atlas"); 243 auto sd = new SkeletonData("spine-runtimes/examples/spineboy/export/spineboy-ess.json", a); 244 sd.defaultSkin = sd.findSkin("default"); 245 246 auto si1 = new Skeleton(sd); 247 auto si2 = new SkeletonDrawable(sd); 248 249 auto bounds = new SkeletonBounds; 250 bounds.update(si2, true); 251 252 int boneIdx = sd.findBoneIndex("gun-tip"); 253 auto bone = si1.getBoneByIndex(boneIdx); 254 255 // attaching check 256 { 257 int slotIdx = sd.findSlotIndex("front-fist"); 258 spSlot* slot = si2.getSlotByIndex(slotIdx); 259 auto att = si2.getAttachmentForSlotIndex(slotIdx, "front-fist-open"); 260 261 si2.setAttachment("front-fist", "front-fist-open"); 262 263 { 264 // skeleton attached to skeleton test 265 266 auto attSkData = new SkeletonData("spine-runtimes/examples/spineboy/export/spineboy-ess.json", a); 267 auto attSk = new SkeletonDrawable(attSkData); 268 269 auto oldNum = attachedSkeletons.length; 270 271 setAttachment(si2, "some-name", slot, attSk); 272 273 assert(oldNum + 1 == attachedSkeletons.length); // new attached skeleton added to array 274 assert(slot.attachment !is null); 275 276 setAttachment(si2, "some-name", slot, null); // remove attach 277 278 assert(slot.attachment is null); 279 assert(oldNum == attachedSkeletons.length); // detached skeleton removed from array 280 } 281 } 282 283 destroy(a); 284 destroy(sd); 285 destroy(si1); 286 destroy(si2); 287 } 288 289 Color spColor2sfmlColor(spColor spc) 290 { 291 spc *= 255.0f; 292 293 Color ret; 294 295 with(ret) 296 { 297 r = spc.r.to!ubyte; 298 g = spc.g.to!ubyte; 299 b = spc.b.to!ubyte; 300 a = spc.a.to!ubyte; 301 } 302 303 return ret; 304 } 305 306 private: 307 308 Color colorize(in spSkeleton* skeleton, in spSlot* slot) 309 { 310 import std.conv: to; 311 312 spColor spc = skeleton.color; 313 spc *= slot.color; 314 315 return spColor2sfmlColor(spc); 316 } 317 318 extern(C): 319 320 void spRegionAttachment_computeWorldVertices (spRegionAttachment* self, const(spBone)* bone, float* vertices, int offset, int stride); 321 322 void spMeshAttachment_computeWorldVertices (spMeshAttachment* self, const(spSlot)* slot, float* worldVertices);