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), MESH(2) and SKINNED_MESH.
 52  * @constant
 53  * @type {{REGION: number, BOUNDING_BOX: number, REGION_SEQUENCE: number, MESH: number}}
 54  */
 55 sp.ATTACHMENT_TYPE = {
 56     REGION: 0,
 57     BOUNDING_BOX: 1,
 58     MESH: 2,
 59     SKINNED_MESH:3
 60 };
 61 
 62 /**
 63  * <p>
 64  *     The skeleton of Spine.                                                                          <br/>
 65  *     Skeleton has a reference to a SkeletonData and stores the state for skeleton instance,
 66  *     which consists of the current pose's bone SRT, slot colors, and which slot attachments are visible.           <br/>
 67  *     Multiple skeletons can use the same SkeletonData (which includes all animations, skins, and attachments).     <br/>
 68  * </p>
 69  * @class
 70  * @extends cc.Node
 71  */
 72 sp.Skeleton = cc.Node.extend(/** @lends sp.Skeleton# */{
 73     _skeleton: null,
 74     _rootBone: null,
 75     _timeScale: 1,
 76     _debugSlots: false,
 77     _debugBones: false,
 78     _premultipliedAlpha: false,
 79     _ownsSkeletonData: null,
 80     _atlas: null,
 81     _blendFunc: null,
 82 
 83     /**
 84      * The constructor of sp.Skeleton. override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
 85      */
 86     ctor:function(skeletonDataFile, atlasFile, scale){
 87         cc.Node.prototype.ctor.call(this);
 88         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 89 
 90         if(arguments.length === 0)
 91             this.init();
 92         else
 93             this.initWithArgs(skeletonDataFile, atlasFile, scale);
 94     },
 95 
 96     _createRenderCmd:function () {
 97         if(cc._renderType === cc._RENDER_TYPE_CANVAS)
 98             return new sp.Skeleton.CanvasRenderCmd(this);
 99         else
100             return new sp.Skeleton.WebGLRenderCmd(this);
101     },
102 
103     /**
104      * Initializes a sp.Skeleton. please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
105      */
106     init: function () {
107         cc.Node.prototype.init.call(this);
108         //this.setOpacityModifyRGB(true);
109         this._blendFunc.src = cc.ONE;
110         this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
111         this.scheduleUpdate();
112     },
113 
114     /**
115      * Sets whether open debug slots.
116      * @param {boolean} enable true to open, false to close.
117      */
118     setDebugSolots:function(enable){
119         this._debugSlots = enable;
120     },
121 
122     /**
123      * Sets whether open debug bones.
124      * @param {boolean} enable
125      */
126     setDebugBones:function(enable){
127         this._debugBones = enable;
128     },
129 
130     /**
131      * Sets whether open debug slots.
132      * @param {boolean} enabled true to open, false to close.
133      */
134     setDebugSlotsEnabled: function(enabled) {
135         this._debugSlots = enabled;
136     },
137 
138     /**
139      * Gets whether open debug slots.
140      * @returns {boolean} true to open, false to close.
141      */
142     getDebugSlotsEnabled: function() {
143         return this._debugSlots;
144     },
145 
146     /**
147      * Sets whether open debug bones.
148      * @param {boolean} enabled
149      */
150     setDebugBonesEnabled: function(enabled) {
151         this._debugBones = enabled;
152     },
153 
154     /**
155      * Gets whether open debug bones.
156      * @returns {boolean} true to open, false to close.
157      */
158     getDebugBonesEnabled: function() {
159         return this._debugBones;
160     },
161 
162     /**
163      * Sets the time scale of sp.Skeleton.
164      * @param {Number} scale
165      */
166     setTimeScale:function(scale){
167         this._timeScale = scale;
168     },
169 
170     getTimeScale: function(){
171         return this._timeScale;
172     },
173 
174     /**
175      * Initializes sp.Skeleton with Data.
176      * @param {spine.SkeletonData|String} skeletonDataFile
177      * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
178      * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
179      */
180     initWithArgs: function (skeletonDataFile, atlasFile, scale) {
181         var argSkeletonFile = skeletonDataFile, argAtlasFile = atlasFile,
182             skeletonData, atlas, ownsSkeletonData;
183 
184         if (cc.isString(argSkeletonFile)) {
185             if (cc.isString(argAtlasFile)) {
186                 var data = cc.loader.getRes(argAtlasFile);
187                 sp._atlasLoader.setAtlasFile(argAtlasFile);
188                 atlas = new spine.Atlas(data, sp._atlasLoader);
189             } else {
190                 atlas = atlasFile;
191             }
192             scale = scale || 1 / cc.director.getContentScaleFactor();
193 
194             var attachmentLoader = new spine.AtlasAttachmentLoader(atlas);
195             var skeletonJsonReader = new spine.SkeletonJson(attachmentLoader);
196             skeletonJsonReader.scale = scale;
197 
198             var skeletonJson = cc.loader.getRes(argSkeletonFile);
199             skeletonData = skeletonJsonReader.readSkeletonData(skeletonJson);
200             atlas.dispose(skeletonJsonReader);
201             ownsSkeletonData = true;
202         } else {
203             skeletonData = skeletonDataFile;
204             ownsSkeletonData = atlasFile;
205         }
206         this.setSkeletonData(skeletonData, ownsSkeletonData);
207         this.init();
208     },
209 
210     /**
211      * Returns the bounding box of sp.Skeleton.
212      * @returns {cc.Rect}
213      */
214     getBoundingBox: function () {
215         var minX = cc.FLT_MAX, minY = cc.FLT_MAX, maxX = cc.FLT_MIN, maxY = cc.FLT_MIN;
216         var scaleX = this.getScaleX(), scaleY = this.getScaleY(), vertices = [],
217             slots = this._skeleton.slots, VERTEX = sp.VERTEX_INDEX;
218 
219         for (var i = 0, slotCount = slots.length; i < slotCount; ++i) {
220             var slot = slots[i];
221             if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION)
222                 continue;
223             var attachment = slot.attachment;
224             this._computeRegionAttachmentWorldVertices(attachment, slot.bone.skeleton.x, slot.bone.skeleton.y, slot.bone, vertices);
225             minX = Math.min(minX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX);
226             minY = Math.min(minY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY);
227             maxX = Math.max(maxX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX);
228             maxY = Math.max(maxY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY);
229         }
230         var position = this.getPosition();
231         return cc.rect(position.x + minX, position.y + minY, maxX - minX, maxY - minY);
232     },
233 
234     _computeRegionAttachmentWorldVertices : function(self, x, y, bone, vertices){
235         var offset = self.offset, vertexIndex = sp.VERTEX_INDEX;
236         x += bone.worldX;
237         y += bone.worldY;
238         vertices[vertexIndex.X1] = offset[vertexIndex.X1] * bone.m00 + offset[vertexIndex.Y1] * bone.m01 + x;
239         vertices[vertexIndex.Y1] = offset[vertexIndex.X1] * bone.m10 + offset[vertexIndex.Y1] * bone.m11 + y;
240         vertices[vertexIndex.X2] = offset[vertexIndex.X2] * bone.m00 + offset[vertexIndex.Y2] * bone.m01 + x;
241         vertices[vertexIndex.Y2] = offset[vertexIndex.X2] * bone.m10 + offset[vertexIndex.Y2] * bone.m11 + y;
242         vertices[vertexIndex.X3] = offset[vertexIndex.X3] * bone.m00 + offset[vertexIndex.Y3] * bone.m01 + x;
243         vertices[vertexIndex.Y3] = offset[vertexIndex.X3] * bone.m10 + offset[vertexIndex.Y3] * bone.m11 + y;
244         vertices[vertexIndex.X4] = offset[vertexIndex.X4] * bone.m00 + offset[vertexIndex.Y4] * bone.m01 + x;
245         vertices[vertexIndex.Y4] = offset[vertexIndex.X4] * bone.m10 + offset[vertexIndex.Y4] * bone.m11 + y;
246     },
247 
248     /**
249      * Computes the world SRT from the local SRT for each bone.
250      */
251     updateWorldTransform: function () {
252         this._skeleton.updateWorldTransform();
253     },
254 
255     /**
256      * Sets the bones and slots to the setup pose.
257      */
258     setToSetupPose: function () {
259         this._skeleton.setToSetupPose();
260     },
261 
262     /**
263      * Sets the bones to the setup pose, using the values from the `BoneData` list in the `SkeletonData`.
264      */
265     setBonesToSetupPose: function () {
266         this._skeleton.setBonesToSetupPose();
267     },
268 
269     /**
270      * Sets the slots to the setup pose, using the values from the `SlotData` list in the `SkeletonData`.
271      */
272     setSlotsToSetupPose: function () {
273         this._skeleton.setSlotsToSetupPose();
274     },
275 
276     /**
277      * Finds a bone by name. This does a string comparison for every bone.
278      * @param {String} boneName
279      * @returns {spine.Bone}
280      */
281     findBone: function (boneName) {
282         return this._skeleton.findBone(boneName);
283     },
284 
285     /**
286      * Finds a slot by name. This does a string comparison for every slot.
287      * @param {String} slotName
288      * @returns {spine.Slot}
289      */
290     findSlot: function (slotName) {
291         return this._skeleton.findSlot(slotName);
292     },
293 
294     /**
295      * 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.
296      * @param {string} skinName
297      * @returns {spine.Skin}
298      */
299     setSkin: function (skinName) {
300         return this._skeleton.setSkinByName(skinName);
301     },
302 
303     /**
304      * Returns the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
305      * @param {String} slotName
306      * @param {String} attachmentName
307      * @returns {spine.RegionAttachment|spine.BoundingBoxAttachment}
308      */
309     getAttachment: function (slotName, attachmentName) {
310         return this._skeleton.getAttachmentBySlotName(slotName, attachmentName);
311     },
312 
313     /**
314      * Sets the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
315      * @param {String} slotName
316      * @param {String} attachmentName
317      */
318     setAttachment: function (slotName, attachmentName) {
319         this._skeleton.setAttachment(slotName, attachmentName);
320     },
321 
322     /**
323      * Sets the premultiplied alpha value to sp.Skeleton.
324      * @param {Number} alpha
325      */
326     setOpacityModifyRGB: function (alpha) {
327         this._premultipliedAlpha = alpha;
328     },
329 
330     /**
331      * Returns whether to enable premultiplied alpha.
332      * @returns {boolean}
333      */
334     isOpacityModifyRGB: function () {
335         return this._premultipliedAlpha;
336     },
337 
338     /**
339      * Sets skeleton data to sp.Skeleton.
340      * @param {spine.SkeletonData} skeletonData
341      * @param {spine.SkeletonData} ownsSkeletonData
342      */
343     setSkeletonData: function (skeletonData, ownsSkeletonData) {
344         if(skeletonData.width != null && skeletonData.height != null)
345             this.setContentSize(skeletonData.width / cc.director.getContentScaleFactor(), skeletonData.height / cc.director.getContentScaleFactor());
346 
347         this._skeleton = new spine.Skeleton(skeletonData);
348         this._skeleton.updateWorldTransform();
349         this._rootBone = this._skeleton.getRootBone();
350         this._ownsSkeletonData = ownsSkeletonData;
351 
352         this._renderCmd._createChildFormSkeletonData();
353     },
354 
355     /**
356      * Return the renderer of attachment.
357      * @param {spine.RegionAttachment|spine.BoundingBoxAttachment} regionAttachment
358      * @returns {cc.Node}
359      */
360     getTextureAtlas: function (regionAttachment) {
361         return regionAttachment.rendererObject.page.rendererObject;
362     },
363 
364     /**
365      * Returns the blendFunc of sp.Skeleton.
366      * @returns {cc.BlendFunc}
367      */
368     getBlendFunc: function () {
369         return this._blendFunc;
370     },
371 
372     /**
373      * Sets the blendFunc of sp.Skeleton.
374      * @param {cc.BlendFunc|Number} src
375      * @param {Number} [dst]
376      */
377     setBlendFunc: function (src, dst) {
378         var locBlendFunc = this._blendFunc;
379         if (dst === undefined) {
380             locBlendFunc.src = src.src;
381             locBlendFunc.dst = src.dst;
382         } else {
383             locBlendFunc.src = src;
384             locBlendFunc.dst = dst;
385         }
386     },
387 
388     /**
389      * Update will be called automatically every frame if "scheduleUpdate" is called when the node is "live".
390      * @param {Number} dt Delta time since last update
391      */
392     update: function (dt) {
393         this._skeleton.update(dt);
394     }
395 });
396 
397 /**
398  * Creates a skeleton object.
399  * @deprecated since v3.0, please use new sp.Skeleton(skeletonDataFile, atlasFile, scale) instead.
400  * @param {spine.SkeletonData|String} skeletonDataFile
401  * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
402  * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
403  * @returns {sp.Skeleton}
404  */
405 sp.Skeleton.create = function (skeletonDataFile, atlasFile/* or atlas*/, scale) {
406     return new sp.Skeleton(skeletonDataFile, atlasFile, scale);
407 };