1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * The main class of Armature, it plays armature animation, manages and updates bones' state.
 28  * @class
 29  * @extends ccs.Node
 30  *
 31  * @property {ccs.Bone}                 parentBone      - The parent bone of the armature node
 32  * @property {ccs.ArmatureAnimation}    animation       - The animation
 33  * @property {ccs.ArmatureData}         armatureData    - The armature data
 34  * @property {String}                   name            - The name of the armature
 35  * @property {cc.SpriteBatchNode}       batchNode       - The batch node of the armature
 36  * @property {Number}                   version         - The version
 37  * @property {Object}                   body            - The body of the armature
 38  * @property {ccs.ColliderFilter}       colliderFilter  - <@writeonly> The collider filter of the armature
 39  */
 40 ccs.Armature = ccs.Node.extend(/** @lends ccs.Armature# */{
 41     animation: null,
 42     armatureData: null,
 43     batchNode: null,
 44     _textureAtlas: null,
 45     _parentBone: null,
 46     _boneDic: null,
 47     _topBoneList: null,
 48     _armatureIndexDic: null,
 49     _offsetPoint: null,
 50     version: 0,
 51     _armatureTransformDirty: true,
 52     _body: null,
 53     _blendFunc: null,
 54     _className: "Armature",
 55 
 56     /**
 57      * Create a armature node.
 58      * Constructor of ccs.Armature
 59      * @param {String} name
 60      * @param {ccs.Bone} parentBone
 61      * @example
 62      * var armature = new ccs.Armature();
 63      */
 64     ctor: function (name, parentBone) {
 65         cc.Node.prototype.ctor.call(this);
 66         this._name = "";
 67         this._topBoneList = [];
 68         this._armatureIndexDic = {};
 69         this._offsetPoint = cc.p(0, 0);
 70         this._armatureTransformDirty = true;
 71         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 72         name && ccs.Armature.prototype.init.call(this, name, parentBone);
 73     },
 74 
 75     /**
 76      * Initializes a CCArmature with the specified name and CCBone
 77      * @param {String} [name]
 78      * @param {ccs.Bone} [parentBone]
 79      * @return {Boolean}
 80      */
 81     init: function (name, parentBone) {
 82         cc.Node.prototype.init.call(this);
 83         if (parentBone)
 84             this._parentBone = parentBone;
 85         this.removeAllChildren();
 86         this.animation = new ccs.ArmatureAnimation();
 87         this.animation.init(this);
 88 
 89         this._boneDic = {};
 90         this._topBoneList.length = 0;
 91 
 92         //this._name = name || "";
 93         var armatureDataManager = ccs.armatureDataManager;
 94 
 95         var animationData;
 96         if (name !== "") {
 97             //animationData
 98             animationData = armatureDataManager.getAnimationData(name);
 99             cc.assert(animationData, "AnimationData not exist!");
100 
101             this.animation.setAnimationData(animationData);
102 
103             //armatureData
104             var armatureData = armatureDataManager.getArmatureData(name);
105             cc.assert(armatureData, "ArmatureData not exist!");
106 
107             this.armatureData = armatureData;
108 
109             //boneDataDic
110             var boneDataDic = armatureData.getBoneDataDic();
111             for (var key in boneDataDic) {
112                 var bone = this.createBone(String(key));
113 
114                 //! init bone's  Tween to 1st movement's 1st frame
115                 do {
116                     var movData = animationData.getMovement(animationData.movementNames[0]);
117                     if (!movData) break;
118 
119                     var _movBoneData = movData.getMovementBoneData(bone.getName());
120                     if (!_movBoneData || _movBoneData.frameList.length <= 0) break;
121 
122                     var frameData = _movBoneData.getFrameData(0);
123                     if (!frameData) break;
124 
125                     bone.getTweenData().copy(frameData);
126                     bone.changeDisplayWithIndex(frameData.displayIndex, false);
127                 } while (0);
128             }
129 
130             this.update(0);
131             this.updateOffsetPoint();
132         } else {
133             name = "new_armature";
134             this.armatureData = new ccs.ArmatureData();
135             this.armatureData.name = name;
136 
137             animationData = new ccs.AnimationData();
138             animationData.name = name;
139 
140             armatureDataManager.addArmatureData(name, this.armatureData);
141             armatureDataManager.addAnimationData(name, animationData);
142 
143             this.animation.setAnimationData(animationData);
144         }
145 
146         this._renderCmd.initShaderCache();
147 
148         this.setCascadeOpacityEnabled(true);
149         this.setCascadeColorEnabled(true);
150         return true;
151     },
152 
153     addChild: function (child, localZOrder, tag) {
154         if(child instanceof ccui.Widget){
155             cc.log("Armature doesn't support to add Widget as its child, it will be fix soon.");
156             return;
157         }
158         cc.Node.prototype.addChild.call(this, child, localZOrder, tag);
159     },
160 
161     /**
162      * create a bone with name
163      * @param {String} boneName
164      * @return {ccs.Bone}
165      */
166     createBone: function (boneName) {
167         var existedBone = this.getBone(boneName);
168         if (existedBone)
169             return existedBone;
170 
171         var boneData = this.armatureData.getBoneData(boneName);
172         var parentName = boneData.parentName;
173 
174         var bone = null;
175         if (parentName) {
176             this.createBone(parentName);
177             bone = new ccs.Bone(boneName);
178             this.addBone(bone, parentName);
179         } else {
180             bone = new ccs.Bone(boneName);
181             this.addBone(bone, "");
182         }
183 
184         bone.setBoneData(boneData);
185         bone.getDisplayManager().changeDisplayWithIndex(-1, false);
186         return bone;
187     },
188 
189     /**
190      * Add a Bone to this Armature
191      * @param {ccs.Bone} bone  The Bone you want to add to Armature
192      * @param {String} parentName The parent Bone's name you want to add to. If it's  null, then set Armature to its parent
193      */
194     addBone: function (bone, parentName) {
195         cc.assert(bone, "Argument must be non-nil");
196         var locBoneDic = this._boneDic;
197         if(bone.getName())
198             cc.assert(!locBoneDic[bone.getName()], "bone already added. It can't be added again");
199 
200         if (parentName) {
201             var boneParent = locBoneDic[parentName];
202             if (boneParent)
203                 boneParent.addChildBone(bone);
204             else
205                 this._topBoneList.push(bone);
206         } else
207             this._topBoneList.push(bone);
208         bone.setArmature(this);
209 
210         locBoneDic[bone.getName()] = bone;
211         this.addChild(bone);
212     },
213 
214     /**
215      * Remove a bone with the specified name. If recursion it will also remove child Bone recursively.
216      * @param {ccs.Bone} bone The bone you want to remove
217      * @param {Boolean} recursion Determine whether remove the bone's child  recursion.
218      */
219     removeBone: function (bone, recursion) {
220         cc.assert(bone, "bone must be added to the bone dictionary!");
221 
222         bone.setArmature(null);
223         bone.removeFromParent(recursion);
224         cc.arrayRemoveObject(this._topBoneList, bone);
225 
226         delete  this._boneDic[bone.getName()];
227         this.removeChild(bone, true);
228     },
229 
230     /**
231      * Gets a bone with the specified name
232      * @param {String} name The bone's name you want to get
233      * @return {ccs.Bone}
234      */
235     getBone: function (name) {
236         return this._boneDic[name];
237     },
238 
239     /**
240      * Change a bone's parent with the specified parent name.
241      * @param {ccs.Bone} bone The bone you want to change parent
242      * @param {String} parentName The new parent's name
243      */
244     changeBoneParent: function (bone, parentName) {
245         cc.assert(bone, "bone must be added to the bone dictionary!");
246 
247         var parentBone = bone.getParentBone();
248         if (parentBone) {
249             cc.arrayRemoveObject(parentBone.getChildren(), bone);
250             bone.setParentBone(null);
251         }
252 
253         if (parentName) {
254             var boneParent = this._boneDic[parentName];
255             if (boneParent) {
256                 boneParent.addChildBone(bone);
257                 cc.arrayRemoveObject(this._topBoneList, bone);
258             } else
259                 this._topBoneList.push(bone);
260         }
261     },
262 
263     /**
264      * Get CCArmature's bone dictionary
265      * @return {Object} Armature's bone dictionary
266      */
267     getBoneDic: function () {
268         return this._boneDic;
269     },
270 
271     /**
272      * Set contentSize and Calculate anchor point.
273      */
274     updateOffsetPoint: function () {
275         // Set contentsize and Calculate anchor point.
276         var rect = this.getBoundingBox();
277         this.setContentSize(rect);
278         var locOffsetPoint = this._offsetPoint;
279         locOffsetPoint.x = -rect.x;
280         locOffsetPoint.y = -rect.y;
281         if (rect.width !== 0 && rect.height !== 0)
282             this.setAnchorPoint(locOffsetPoint.x / rect.width, locOffsetPoint.y / rect.height);
283     },
284 
285     getOffsetPoints: function(){
286         return {x: this._offsetPoint.x, y: this._offsetPoint.y};
287     },
288 
289     /**
290      * Sets animation to this Armature
291      * @param {ccs.ArmatureAnimation} animation
292      */
293     setAnimation: function (animation) {
294         this.animation = animation;
295     },
296 
297     /**
298      * Gets the animation of this Armature.
299      * @return {ccs.ArmatureAnimation}
300      */
301     getAnimation: function () {
302         return this.animation;
303     },
304 
305     /**
306      * armatureTransformDirty getter
307      * @returns {Boolean}
308      */
309     getArmatureTransformDirty: function () {
310         return this._armatureTransformDirty;
311     },
312 
313     /**
314      * The update callback of ccs.Armature, it updates animation's state and updates bone's state.
315      * @override
316      * @param {Number} dt
317      */
318     update: function (dt) {
319         this.animation.update(dt);
320         var locTopBoneList = this._topBoneList;
321         for (var i = 0; i < locTopBoneList.length; i++)
322             locTopBoneList[i].update(dt);
323         this._armatureTransformDirty = false;
324     },
325 
326     /**
327      * The callback when ccs.Armature enter stage.
328      * @override
329      */
330     onEnter: function () {
331         cc.Node.prototype.onEnter.call(this);
332         this.scheduleUpdate();
333     },
334 
335     /**
336      * The callback when ccs.Armature exit stage.
337      * @override
338      */
339     onExit: function () {
340         cc.Node.prototype.onExit.call(this);
341         this.unscheduleUpdate();
342     },
343 
344     /**
345      * This boundingBox will calculate all bones' boundingBox every time
346      * @returns {cc.Rect}
347      */
348     getBoundingBox: function(){
349         var minX, minY, maxX, maxY = 0;
350         var first = true;
351 
352         var boundingBox = cc.rect(0, 0, 0, 0), locChildren = this._children;
353 
354         var len = locChildren.length;
355         for (var i=0; i<len; i++) {
356             var bone = locChildren[i];
357             if (bone) {
358                 var r = bone.getDisplayManager().getBoundingBox();
359                 if (r.x === 0 && r.y === 0 && r.width === 0 && r.height === 0)
360                     continue;
361 
362                 if(first) {
363                     minX = r.x;
364                     minY = r.y;
365                     maxX = r.x + r.width;
366                     maxY = r.y + r.height;
367                     first = false;
368                 } else {
369                     minX = r.x < boundingBox.x ? r.x : boundingBox.x;
370                     minY = r.y < boundingBox.y ? r.y : boundingBox.y;
371                     maxX = r.x + r.width > boundingBox.x + boundingBox.width ?
372                         r.x + r.width : boundingBox.x + boundingBox.width;
373                     maxY = r.y + r.height > boundingBox.y + boundingBox.height ?
374                         r.y + r.height : boundingBox.y + boundingBox.height;
375                 }
376 
377                 boundingBox.x = minX;
378                 boundingBox.y = minY;
379                 boundingBox.width = maxX - minX;
380                 boundingBox.height = maxY - minY;
381             }
382         }
383         return cc.rectApplyAffineTransform(boundingBox, this.getNodeToParentTransform());
384     },
385 
386     /**
387      * when bone  contain the point ,then return it.
388      * @param {Number} x
389      * @param {Number} y
390      * @returns {ccs.Bone}
391      */
392     getBoneAtPoint: function (x, y) {
393         var locChildren = this._children;
394         for (var i = locChildren.length - 1; i >= 0; i--) {
395             var child = locChildren[i];
396             if (child instanceof ccs.Bone && child.getDisplayManager().containPoint(x, y))
397                 return child;
398         }
399         return null;
400     },
401 
402     /**
403      * Sets parent bone of this Armature
404      * @param {ccs.Bone} parentBone
405      */
406     setParentBone: function (parentBone) {
407         this._parentBone = parentBone;
408         var locBoneDic = this._boneDic;
409         for (var key in locBoneDic) {
410             locBoneDic[key].setArmature(this);
411         }
412     },
413 
414     /**
415      * Return parent bone of ccs.Armature.
416      * @returns {ccs.Bone}
417      */
418     getParentBone: function () {
419         return this._parentBone;
420     },
421 
422     /**
423      * draw contour
424      */
425     drawContour: function () {
426         cc._drawingUtil.setDrawColor(255, 255, 255, 255);
427         cc._drawingUtil.setLineWidth(1);
428         var locBoneDic = this._boneDic;
429         for (var key in locBoneDic) {
430             var bone = locBoneDic[key];
431             var detector = bone.getColliderDetector();
432             if(!detector)
433                 continue;
434             var bodyList = detector.getColliderBodyList();
435             for (var i = 0; i < bodyList.length; i++) {
436                 var body = bodyList[i];
437                 var vertexList = body.getCalculatedVertexList();
438                 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true);
439             }
440         }
441     },
442 
443     setBody: function (body) {
444         if (this._body === body)
445             return;
446 
447         this._body = body;
448         this._body.data = this;
449         var child, displayObject, locChildren = this._children;
450         for (var i = 0; i < locChildren.length; i++) {
451             child = locChildren[i];
452             if (child instanceof ccs.Bone) {
453                 var displayList = child.getDisplayManager().getDecorativeDisplayList();
454                 for (var j = 0; j < displayList.length; j++) {
455                     displayObject = displayList[j];
456                     var detector = displayObject.getColliderDetector();
457                     if (detector)
458                         detector.setBody(this._body);
459                 }
460             }
461         }
462     },
463 
464     getShapeList: function () {
465         if (this._body)
466             return this._body.shapeList;
467         return null;
468     },
469 
470     getBody: function () {
471         return this._body;
472     },
473 
474     /**
475      * Sets the blendFunc to ccs.Armature
476      * @param {cc.BlendFunc|Number} blendFunc
477      * @param {Number} [dst]
478      */
479     setBlendFunc: function (blendFunc, dst) {
480         if(dst === undefined){
481             this._blendFunc.src = blendFunc.src;
482             this._blendFunc.dst = blendFunc.dst;
483         } else {
484             this._blendFunc.src = blendFunc;
485             this._blendFunc.dst = dst;
486         }
487     },
488 
489     /**
490      * Returns the blendFunc of ccs.Armature
491      * @returns {cc.BlendFunc}
492      */
493     getBlendFunc: function () {
494         return new cc.BlendFunc(this._blendFunc.src, this._blendFunc.dst);
495     },
496 
497     /**
498      * set collider filter
499      * @param {ccs.ColliderFilter} filter
500      */
501     setColliderFilter: function (filter) {
502         var locBoneDic = this._boneDic;
503         for (var key in locBoneDic)
504             locBoneDic[key].setColliderFilter(filter);
505     },
506 
507     /**
508      * Returns the armatureData of ccs.Armature
509      * @return {ccs.ArmatureData}
510      */
511     getArmatureData: function () {
512         return this.armatureData;
513     },
514 
515     /**
516      * Sets armatureData to this Armature
517      * @param {ccs.ArmatureData} armatureData
518      */
519     setArmatureData: function (armatureData) {
520         this.armatureData = armatureData;
521     },
522 
523     getBatchNode: function () {
524         return this.batchNode;
525     },
526 
527     setBatchNode: function (batchNode) {
528         this.batchNode = batchNode;
529     },
530 
531     /**
532      * version getter
533      * @returns {Number}
534      */
535     getVersion: function () {
536         return this.version;
537     },
538 
539     /**
540      * version setter
541      * @param {Number} version
542      */
543     setVersion: function (version) {
544         this.version = version;
545     },
546 
547     _createRenderCmd: function(){
548         if(cc._renderType === cc.game.RENDER_TYPE_CANVAS)
549             return new ccs.Armature.CanvasRenderCmd(this);
550         else
551             return new ccs.Armature.WebGLRenderCmd(this);
552     }
553 });
554 
555 var _p = ccs.Armature.prototype;
556 
557 /** @expose */
558 _p.parentBone;
559 cc.defineGetterSetter(_p, "parentBone", _p.getParentBone, _p.setParentBone);
560 /** @expose */
561 _p.body;
562 cc.defineGetterSetter(_p, "body", _p.getBody, _p.setBody);
563 /** @expose */
564 _p.colliderFilter;
565 cc.defineGetterSetter(_p, "colliderFilter", null, _p.setColliderFilter);
566 
567 _p = null;
568 
569 /**
570  * Allocates an armature, and use the ArmatureData named name in ArmatureDataManager to initializes the armature.
571  * @param {String} [name] Bone name
572  * @param {ccs.Bone} [parentBone] the parent bone
573  * @return {ccs.Armature}
574  * @deprecated since v3.1, please use new construction instead
575  */
576 ccs.Armature.create = function (name, parentBone) {
577     return new ccs.Armature(name, parentBone);
578 };
579