1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4  Copyright (c) 2014 Shengxiang Chen (Nero Chan)
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * The main namespace of Spine, all classes, functions, properties and constants of Spine are defined in this namespace
 29  * @namespace
 30  * @name sp
 31  */
 32 var sp = sp || {};
 33 
 34 /**
 35  * The vertex index of spine.
 36  * @constant
 37  * @type {{X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, X4: number, Y4: number}}
 38  */
 39 sp.VERTEX_INDEX = {
 40     X1: 0,
 41     Y1: 1,
 42     X2: 2,
 43     Y2: 3,
 44     X3: 4,
 45     Y3: 5,
 46     X4: 6,
 47     Y4: 7
 48 };
 49 
 50 /**
 51  * The attachment type of spine.  It contains three type: REGION(0), BOUNDING_BOX(1) and REGION_SEQUENCE(2).
 52  * @constant
 53  * @type {{REGION: number, BOUNDING_BOX: number, REGION_SEQUENCE: number}}
 54  */
 55 sp.ATTACHMENT_TYPE = {
 56     REGION: 0,
 57     BOUNDING_BOX: 1,
 58     REGION_SEQUENCE: 2
 59 };
 60 
 61 /**
 62  * <p>
 63  *     The skeleton of Spine.                                                                          <br/>
 64  *     Skeleton has a reference to a SkeletonData and stores the state for skeleton instance,
 65  *     which consists of the current pose's bone SRT, slot colors, and which slot attachments are visible.           <br/>
 66  *     Multiple skeletons can use the same SkeletonData (which includes all animations, skins, and attachments).     <br/>
 67  * </p>
 68  * @class
 69  * @extends cc.Node
 70  */
 71 sp.Skeleton = cc.Node.extend(/** @lends sp.Skeleton# */{
 72     _skeleton: null,
 73     _rootBone: null,
 74     _timeScale: 1,
 75     _debugSlots: false,
 76     _debugBones: false,
 77     _premultipliedAlpha: false,
 78     _ownsSkeletonData: null,
 79     _atlas: null,
 80     _blendFunc: null,
 81 
 82     /**
 83      * The constructor of sp.Skeleton. override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
 84      */
 85     ctor:function(skeletonDataFile, atlasFile, scale){
 86         cc.Node.prototype.ctor.call(this);
 87         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 88 
 89         if(arguments.length === 0)
 90             this.init();
 91         else
 92             this.initWithArgs(skeletonDataFile, atlasFile, scale);
 93     },
 94 
 95     /**
 96      * Initializes a sp.Skeleton. please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
 97      */
 98     init: function () {
 99         cc.Node.prototype.init.call(this);
100         this.setOpacityModifyRGB(true);
101         this._blendFunc.src = cc.ONE;
102         this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
103         if (cc._renderType === cc._RENDER_TYPE_WEBGL)
104             this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR));
105         this.scheduleUpdate();
106     },
107 
108     /**
109      * Sets whether open debug solots.
110      * @param {boolean} enable true to open, false to close.
111      */
112     setDebugSolots:function(enable){
113         this._debugSlots = enable;
114     },
115 
116     /**
117      * Sets whether open debug bones.
118      * @param {boolean} enable
119      */
120     setDebugBones:function(enable){
121         this._debugBones = enable;
122     },
123 
124     /**
125      * Sets the time scale of sp.Skeleton.
126      * @param {Number} v
127      */
128     setTimeScale:function(v){
129         this._timeScale = v;
130     },
131 
132     /**
133      * Initializes sp.Skeleton with Data.
134      * @param {spine.SkeletonData|String} skeletonDataFile
135      * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
136      * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
137      */
138     initWithArgs: function (skeletonDataFile, atlasFile, scale) {
139         var argSkeletonFile = skeletonDataFile, argAtlasFile = atlasFile,
140             skeletonData, atlas, ownsSkeletonData;
141 
142         if (cc.isString(argSkeletonFile)) {
143             if (cc.isString(argAtlasFile)) {
144                 var data = cc.loader.getRes(argAtlasFile);
145                 sp._atlasLoader.setAtlasFile(argAtlasFile);
146                 atlas = new spine.Atlas(data, sp._atlasLoader);
147             } else {
148                 atlas = atlasFile;
149             }
150             scale = scale || 1 / cc.director.getContentScaleFactor();
151 
152             var attachmentLoader = new spine.AtlasAttachmentLoader(atlas);
153             var skeletonJsonReader = new spine.SkeletonJson(attachmentLoader);
154             skeletonJsonReader.scale = scale;
155 
156             var skeletonJson = cc.loader.getRes(argSkeletonFile);
157             skeletonData = skeletonJsonReader.readSkeletonData(skeletonJson);
158             atlas.dispose(skeletonJsonReader);
159             ownsSkeletonData = true;
160         } else {
161             skeletonData = skeletonDataFile;
162             ownsSkeletonData = atlasFile;
163         }
164         this.setSkeletonData(skeletonData, ownsSkeletonData);
165         this.init();
166     },
167 
168     /**
169      * Returns the bounding box of sp.Skeleton.
170      * @returns {cc.Rect}
171      */
172     boundingBox: function () {
173         var minX = cc.FLT_MAX, minY = cc.FLT_MAX, maxX = cc.FLT_MIN, maxY = cc.FLT_MIN;
174         var scaleX = this.getScaleX(), scaleY = this.getScaleY(), vertices = [],
175             slots = this._skeleton.slots, VERTEX = sp.VERTEX_INDEX;
176 
177         for (var i = 0, slotCount = slots.length; i < slotCount; ++i) {
178             var slot = slots[i];
179             if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION)
180                 continue;
181             var attachment = slot.attachment;
182             sp._regionAttachment_computeWorldVertices(attachment, slot.skeleton.x, slot.skeleton.y, slot.bone, vertices);
183             minX = Math.min(minX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX);
184             minY = Math.min(minY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY);
185             maxX = Math.max(maxX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX);
186             maxY = Math.max(maxY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY);
187         }
188         var position = this.getPosition();
189         return cc.rect(position.x + minX, position.y + minY, maxX - minX, maxY - minY);
190     },
191 
192     /**
193      * Computes the world SRT from the local SRT for each bone.
194      */
195     updateWorldTransform: function () {
196         this._skeleton.updateWorldTransform();
197     },
198 
199     /**
200      * Sets the bones and slots to the setup pose.
201      */
202     setToSetupPose: function () {
203         this._skeleton.setToSetupPose();
204     },
205 
206     /**
207      * Sets the bones to the setup pose, using the values from the `BoneData` list in the `SkeletonData`.
208      */
209     setBonesToSetupPose: function () {
210         this._skeleton.setBonesToSetupPose();
211     },
212 
213     /**
214      * Sets the slots to the setup pose, using the values from the `SlotData` list in the `SkeletonData`.
215      */
216     setSlotsToSetupPose: function () {
217         this._skeleton.setSlotsToSetupPose();
218     },
219 
220     /**
221      * Finds a bone by name. This does a string comparison for every bone.
222      * @param {String} boneName
223      * @returns {spine.Bone}
224      */
225     findBone: function (boneName) {
226         return this._skeleton.findBone(boneName);
227     },
228 
229     /**
230      * Finds a slot by name. This does a string comparison for every slot.
231      * @param {String} slotName
232      * @returns {spine.Slot}
233      */
234     findSlot: function (slotName) {
235         return this._skeleton.findSlot(slotName);
236     },
237 
238     /**
239      * Finds a skin by name and makes it the active skin. This does a string comparison for every skin. Note that setting the skin does not change which attachments are visible.
240      * @param {string} skinName
241      * @returns {spine.Skin}
242      */
243     setSkin: function (skinName) {
244         return this._skeleton.setSkinByName(skinName);
245     },
246 
247     /**
248      * Returns the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
249      * @param {String} slotName
250      * @param {String} attachmentName
251      * @returns {spine.RegionAttachment|spine.BoundingBoxAttachment}
252      */
253     getAttachment: function (slotName, attachmentName) {
254         return this._skeleton.getAttachmentBySlotName(slotName, attachmentName);
255     },
256 
257     /**
258      * Sets the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
259      * @param {String} slotName
260      * @param {String} attachmentName
261      */
262     setAttachment: function (slotName, attachmentName) {
263         this._skeleton.setAttachment(slotName, attachmentName);
264     },
265 
266     /**
267      * Sets the premultiplied alpha value to sp.Skeleton.
268      * @param {Number} alpha
269      */
270     setOpacityModifyRGB: function (alpha) {
271         this._premultipliedAlpha = alpha;
272     },
273 
274     /**
275      * Returns whether to enable premultiplied alpha.
276      * @returns {boolean}
277      */
278     isOpacityModifyRGB: function () {
279         return this._premultipliedAlpha;
280     },
281 
282     /**
283      * Sets skeleton data to sp.Skeleton.
284      * @param {spine.SkeletonData} skeletonData
285      * @param {spine.SkeletonData} ownsSkeletonData
286      */
287     setSkeletonData: function (skeletonData, ownsSkeletonData) {
288         this._skeleton = new spine.Skeleton(skeletonData);
289         this._rootBone = this._skeleton.getRootBone();
290         this._ownsSkeletonData = ownsSkeletonData;
291 
292         if (cc._renderType === cc._RENDER_TYPE_CANVAS) {
293             var locSkeleton = this._skeleton, rendererObject, rect;
294             for (var i = 0, n = locSkeleton.drawOrder.length; i < n; i++) {
295                 var slot = locSkeleton.drawOrder[i];
296                 var attachment = slot.attachment;
297                 if (!(attachment instanceof spine.RegionAttachment))
298                     continue;
299                 rendererObject = attachment.rendererObject;
300                 rect = cc.rect(rendererObject.x, rendererObject.y, rendererObject.width,rendererObject.height);
301                 var sprite = cc.Sprite.create(rendererObject.page._texture, rect, rendererObject.rotate);
302                 this.addChild(sprite,-1);
303                 slot.currentSprite = sprite;
304             }
305         }
306     },
307 
308     /**
309      * Return the renderer of attachment.
310      * @param {spine.RegionAttachment|spine.BoundingBoxAttachment} regionAttachment
311      * @returns {cc.Node}
312      */
313     getTextureAtlas: function (regionAttachment) {
314         return regionAttachment.rendererObject.page.rendererObject;
315     },
316 
317     /**
318      * Returns the blendFunc of sp.Skeleton.
319      * @returns {cc.BlendFunc}
320      */
321     getBlendFunc: function () {
322         return this._blendFunc;
323     },
324 
325     /**
326      * Sets the blendFunc of sp.Skeleton.
327      * @param {cc.BlendFunc|Number} src
328      * @param {Number} [dst]
329      */
330     setBlendFunc: function (src, dst) {
331         var locBlendFunc = this._blendFunc;
332         if (dst === undefined) {
333             locBlendFunc.src = src.src;
334             locBlendFunc.dst = src.dst;
335         } else {
336             locBlendFunc.src = src;
337             locBlendFunc.dst = dst;
338         }
339     },
340 
341     /**
342      * Update will be called automatically every frame if "scheduleUpdate" is called when the node is "live".
343      * @param {Number} dt Delta time since last update
344      */
345     update: function (dt) {
346         this._skeleton.update(dt);
347 
348         if (cc._renderType === cc._RENDER_TYPE_CANVAS) {
349             var locSkeleton = this._skeleton;
350             locSkeleton.updateWorldTransform();
351             var drawOrder = this._skeleton.drawOrder;
352             for (var i = 0, n = drawOrder.length; i < n; i++) {
353                 var slot = drawOrder[i];
354                 var attachment = slot.attachment, selSprite = slot.currentSprite;
355                 if (!(attachment instanceof spine.RegionAttachment)) {
356                     if(selSprite)
357                         selSprite.setVisible(false);
358                     continue;
359                 }
360                 if(!selSprite){
361                     var rendererObject = attachment.rendererObject;
362                     var rect = cc.rect(rendererObject.x, rendererObject.y, rendererObject.width,rendererObject.height);
363                     var sprite = cc.Sprite.create(rendererObject.page._texture, rect, rendererObject.rotate);
364                     this.addChild(sprite,-1);
365                     slot.currentSprite = sprite;
366                 }
367                 selSprite.setVisible(true);
368                 //update color and blendFunc
369                 selSprite.setBlendFunc(cc.BLEND_SRC, slot.data.additiveBlending ? cc.ONE : cc.BLEND_DST);
370 
371                 var bone = slot.bone;
372                 selSprite.setPosition(bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01,
373                     bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11);
374                 selSprite.setScale(bone.worldScaleX, bone.worldScaleY);
375                 selSprite.setRotation(- (slot.bone.worldRotation + attachment.rotation));
376             }
377         }
378     },
379 
380     /**
381      * Render function using the canvas 2d context or WebGL context, internal usage only, please do not call this function
382      * @function
383      * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context
384      */
385     draw: null,
386 
387     _drawForWebGL: function () {
388         cc.nodeDrawSetup(this);
389 //        cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
390         var color = this.getColor(), locSkeleton = this._skeleton;
391         locSkeleton.r = color.r / 255;
392         locSkeleton.g = color.g / 255;
393         locSkeleton.b = color.b / 255;
394         locSkeleton.a = this.getOpacity() / 255;
395         if (this._premultipliedAlpha) {
396             locSkeleton.r *= locSkeleton.a;
397             locSkeleton.g *= locSkeleton.a;
398             locSkeleton.b *= locSkeleton.a;
399         }
400 
401         var additive,textureAtlas,attachment,slot, i, n,
402             quad = new cc.V3F_C4B_T2F_Quad();
403         var locBlendFunc = this._blendFunc;
404 
405         for (i = 0, n = locSkeleton.slots.length; i < n; i++) {
406             slot = locSkeleton.drawOrder[i];
407             if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION)
408                 continue;
409             attachment = slot.attachment;
410             var regionTextureAtlas = this.getTextureAtlas(attachment);
411 
412             if (slot.data.additiveBlending != additive) {
413                 if (textureAtlas) {
414                     textureAtlas.drawQuads();
415                     textureAtlas.removeAllQuads();
416                 }
417                 additive = !additive;
418                 cc.glBlendFunc(locBlendFunc.src, additive ? cc.ONE : locBlendFunc.dst);
419             } else if (regionTextureAtlas != textureAtlas && textureAtlas) {
420                 textureAtlas.drawQuads();
421                 textureAtlas.removeAllQuads();
422             }
423             textureAtlas = regionTextureAtlas;
424 
425             var quadCount = textureAtlas.getTotalQuads();
426             if (textureAtlas.getCapacity() == quadCount) {
427                 textureAtlas.drawQuads();
428                 textureAtlas.removeAllQuads();
429                 if (!textureAtlas.resizeCapacity(textureAtlas.getCapacity() * 2))
430                     return;
431             }
432 
433             sp._regionAttachment_updateQuad(attachment, slot, quad, this._premultipliedAlpha);
434             textureAtlas.updateQuad(quad, quadCount);
435         }
436 
437         if (textureAtlas) {
438             textureAtlas.drawQuads();
439             textureAtlas.removeAllQuads();
440         }
441 
442         var drawingUtil = cc._drawingUtil;
443         if (this._debugSlots) {
444             // Slots.
445             drawingUtil.setDrawColor(0, 0, 255, 255);
446             drawingUtil.setLineWidth(1);
447 
448             for (i = 0, n = locSkeleton.slots.length; i < n; i++) {
449                 slot = locSkeleton.drawOrder[i];
450                 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION)
451                     continue;
452                 attachment = slot.attachment;
453                 quad = new cc.V3F_C4B_T2F_Quad();
454                 sp._regionAttachment_updateQuad(attachment, slot, quad);
455 
456                 var points = [];
457                 points.push(cc.p(quad.bl.vertices.x, quad.bl.vertices.y));
458                 points.push(cc.p(quad.br.vertices.x, quad.br.vertices.y));
459                 points.push(cc.p(quad.tr.vertices.x, quad.tr.vertices.y));
460                 points.push(cc.p(quad.tl.vertices.x, quad.tl.vertices.y));
461                 drawingUtil.drawPoly(points, 4, true);
462             }
463         }
464 
465         if (this._debugBones) {
466             // Bone lengths.
467             var bone;
468             drawingUtil.setLineWidth(2);
469             drawingUtil.setDrawColor(255, 0, 0, 255);
470 
471             for (i = 0, n = locSkeleton.bones.length; i < n; i++) {
472                 bone = locSkeleton.bones[i];
473                 var x = bone.data.length * bone.m00 + bone.worldX;
474                 var y = bone.data.length * bone.m10 + bone.worldY;
475                 drawingUtil.drawLine(cc.p(bone.worldX, bone.worldY), cc.p(x, y));
476             }
477 
478             // Bone origins.
479             drawingUtil.setPointSize(4);
480             drawingUtil.setDrawColor(0, 0, 255, 255); // Root bone is blue.
481 
482             for (i = 0, n = locSkeleton.bones.length; i < n; i++) {
483                 bone = locSkeleton.bones[i];
484                 drawingUtil.drawPoint(cc.p(bone.worldX, bone.worldY));
485                 if (i == 0) {
486                     drawingUtil.setDrawColor(0, 255, 0, 255);
487                 }
488             }
489         }
490     },
491 
492     _drawForCanvas: function () {
493         if(!this._debugSlots && !this._debugBones){
494             return;
495         }
496         var locSkeleton = this._skeleton;
497         var attachment,slot, i, n, drawingUtil = cc._drawingUtil;
498         if (this._debugSlots) {
499             // Slots.
500             drawingUtil.setDrawColor(0, 0, 255, 255);
501             drawingUtil.setLineWidth(1);
502 
503             var points = [];
504             for (i = 0, n = locSkeleton.slots.length; i < n; i++) {
505                 slot = locSkeleton.drawOrder[i];
506                 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION)
507                     continue;
508                 attachment = slot.attachment;
509                 sp._regionAttachment_updateSlotForCanvas(attachment, slot, points);
510                 drawingUtil.drawPoly(points, 4, true);
511             }
512         }
513 
514         if (this._debugBones) {
515             // Bone lengths.
516             var bone;
517             drawingUtil.setLineWidth(2);
518             drawingUtil.setDrawColor(255, 0, 0, 255);
519 
520             for (i = 0, n = locSkeleton.bones.length; i < n; i++) {
521                 bone = locSkeleton.bones[i];
522                 var x = bone.data.length * bone.m00 + bone.worldX;
523                 var y = bone.data.length * bone.m10 + bone.worldY;
524                 drawingUtil.drawLine(cc.p(bone.worldX, bone.worldY), cc.p(x, y));
525             }
526 
527             // Bone origins.
528             drawingUtil.setPointSize(4);
529             drawingUtil.setDrawColor(0, 0, 255, 255); // Root bone is blue.
530 
531             for (i = 0, n = locSkeleton.bones.length; i < n; i++) {
532                 bone = locSkeleton.bones[i];
533                 drawingUtil.drawPoint(cc.p(bone.worldX, bone.worldY));
534                 if (i === 0)
535                     drawingUtil.setDrawColor(0, 255, 0, 255);
536             }
537         }
538     }
539 });
540 
541 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
542     sp.Skeleton.prototype.draw = sp.Skeleton.prototype._drawForWebGL;
543 }else{
544     sp.Skeleton.prototype.draw = sp.Skeleton.prototype._drawForCanvas;
545 }
546 
547 /**
548  * Creates a skeleton object.
549  * @deprecated since v3.0, please use new sp.Skeleton(skeletonDataFile, atlasFile, scale) instead.
550  * @param {spine.SkeletonData|String} skeletonDataFile
551  * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
552  * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
553  * @returns {sp.Skeleton}
554  */
555 sp.Skeleton.create = function (skeletonDataFile, atlasFile/* or atlas*/, scale) {
556     return new sp.Skeleton(skeletonDataFile, atlasFile, scale);
557 };