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);