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             sp._regionAttachment_computeWorldVertices(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     /**
235      * Computes the world SRT from the local SRT for each bone.
236      */
237     updateWorldTransform: function () {
238         this._skeleton.updateWorldTransform();
239     },
240 
241     /**
242      * Sets the bones and slots to the setup pose.
243      */
244     setToSetupPose: function () {
245         this._skeleton.setToSetupPose();
246     },
247 
248     /**
249      * Sets the bones to the setup pose, using the values from the `BoneData` list in the `SkeletonData`.
250      */
251     setBonesToSetupPose: function () {
252         this._skeleton.setBonesToSetupPose();
253     },
254 
255     /**
256      * Sets the slots to the setup pose, using the values from the `SlotData` list in the `SkeletonData`.
257      */
258     setSlotsToSetupPose: function () {
259         this._skeleton.setSlotsToSetupPose();
260     },
261 
262     /**
263      * Finds a bone by name. This does a string comparison for every bone.
264      * @param {String} boneName
265      * @returns {spine.Bone}
266      */
267     findBone: function (boneName) {
268         return this._skeleton.findBone(boneName);
269     },
270 
271     /**
272      * Finds a slot by name. This does a string comparison for every slot.
273      * @param {String} slotName
274      * @returns {spine.Slot}
275      */
276     findSlot: function (slotName) {
277         return this._skeleton.findSlot(slotName);
278     },
279 
280     /**
281      * 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.
282      * @param {string} skinName
283      * @returns {spine.Skin}
284      */
285     setSkin: function (skinName) {
286         return this._skeleton.setSkinByName(skinName);
287     },
288 
289     /**
290      * Returns the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
291      * @param {String} slotName
292      * @param {String} attachmentName
293      * @returns {spine.RegionAttachment|spine.BoundingBoxAttachment}
294      */
295     getAttachment: function (slotName, attachmentName) {
296         return this._skeleton.getAttachmentBySlotName(slotName, attachmentName);
297     },
298 
299     /**
300      * Sets the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin.
301      * @param {String} slotName
302      * @param {String} attachmentName
303      */
304     setAttachment: function (slotName, attachmentName) {
305         this._skeleton.setAttachment(slotName, attachmentName);
306     },
307 
308     /**
309      * Sets the premultiplied alpha value to sp.Skeleton.
310      * @param {Number} alpha
311      */
312     setOpacityModifyRGB: function (alpha) {
313         this._premultipliedAlpha = alpha;
314     },
315 
316     /**
317      * Returns whether to enable premultiplied alpha.
318      * @returns {boolean}
319      */
320     isOpacityModifyRGB: function () {
321         return this._premultipliedAlpha;
322     },
323 
324     /**
325      * Sets skeleton data to sp.Skeleton.
326      * @param {spine.SkeletonData} skeletonData
327      * @param {spine.SkeletonData} ownsSkeletonData
328      */
329     setSkeletonData: function (skeletonData, ownsSkeletonData) {
330         if(skeletonData.width != null && skeletonData.height != null)
331             this.setContentSize(skeletonData.width / cc.director.getContentScaleFactor(), skeletonData.height / cc.director.getContentScaleFactor());
332 
333         this._skeleton = new spine.Skeleton(skeletonData);
334         this._rootBone = this._skeleton.getRootBone();
335         this._ownsSkeletonData = ownsSkeletonData;
336 
337         this._renderCmd._createChildFormSkeletonData();
338     },
339 
340     /**
341      * Return the renderer of attachment.
342      * @param {spine.RegionAttachment|spine.BoundingBoxAttachment} regionAttachment
343      * @returns {cc.Node}
344      */
345     getTextureAtlas: function (regionAttachment) {
346         return regionAttachment.rendererObject.page.rendererObject;
347     },
348 
349     /**
350      * Returns the blendFunc of sp.Skeleton.
351      * @returns {cc.BlendFunc}
352      */
353     getBlendFunc: function () {
354         return this._blendFunc;
355     },
356 
357     /**
358      * Sets the blendFunc of sp.Skeleton.
359      * @param {cc.BlendFunc|Number} src
360      * @param {Number} [dst]
361      */
362     setBlendFunc: function (src, dst) {
363         var locBlendFunc = this._blendFunc;
364         if (dst === undefined) {
365             locBlendFunc.src = src.src;
366             locBlendFunc.dst = src.dst;
367         } else {
368             locBlendFunc.src = src;
369             locBlendFunc.dst = dst;
370         }
371     },
372 
373     /**
374      * Update will be called automatically every frame if "scheduleUpdate" is called when the node is "live".
375      * @param {Number} dt Delta time since last update
376      */
377     update: function (dt) {
378         this._skeleton.update(dt);
379         this._renderCmd._updateChild();
380     }
381 });
382 
383 /**
384  * Creates a skeleton object.
385  * @deprecated since v3.0, please use new sp.Skeleton(skeletonDataFile, atlasFile, scale) instead.
386  * @param {spine.SkeletonData|String} skeletonDataFile
387  * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData
388  * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations.
389  * @returns {sp.Skeleton}
390  */
391 sp.Skeleton.create = function (skeletonDataFile, atlasFile/* or atlas*/, scale) {
392     return new sp.Skeleton(skeletonDataFile, atlasFile, scale);
393 };