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     _realAnchorPointInPoints: null,
 56 
 57     /**
 58      * Create a armature node.
 59      * Constructor of ccs.Armature
 60      * @param {String} name
 61      * @param {ccs.Bone} parentBone
 62      * @example
 63      * var armature = new ccs.Armature();
 64      */
 65     ctor: function (name, parentBone) {
 66         cc.Node.prototype.ctor.call(this);
 67         this._name = "";
 68         this._topBoneList = [];
 69         this._armatureIndexDic = {};
 70         this._offsetPoint = cc.p(0, 0);
 71         this._armatureTransformDirty = true;
 72         this._realAnchorPointInPoints = cc.p(0, 0);
 73         name && ccs.Armature.prototype.init.call(this, name, parentBone);
 74     },
 75 
 76     /**
 77      * Initializes a CCArmature with the specified name and CCBone
 78      * @param {String} [name]
 79      * @param {ccs.Bone} [parentBone]
 80      * @return {Boolean}
 81      */
 82     init: function (name, parentBone) {
 83         cc.Node.prototype.init.call(this);
 84         if (parentBone)
 85             this._parentBone = parentBone;
 86         this.removeAllChildren();
 87         this.animation = new ccs.ArmatureAnimation();
 88         this.animation.init(this);
 89 
 90         this._boneDic = {};
 91         this._topBoneList.length = 0;
 92 
 93         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 94         this._name = name || "";
 95         var armatureDataManager = ccs.armatureDataManager;
 96 
 97         var animationData;
 98         if (name != "") {
 99             //animationData
100             animationData = armatureDataManager.getAnimationData(name);
101             cc.assert(animationData, "AnimationData not exist!");
102 
103             this.animation.setAnimationData(animationData);
104 
105             //armatureData
106             var armatureData = armatureDataManager.getArmatureData(name);
107             cc.assert(armatureData, "ArmatureData not exist!");
108 
109             this.armatureData = armatureData;
110 
111             //boneDataDic
112             var boneDataDic = armatureData.getBoneDataDic();
113             for (var key in boneDataDic) {
114                 var bone = this.createBone(String(key));
115 
116                 //! init bone's  Tween to 1st movement's 1st frame
117                 do {
118                     var movData = animationData.getMovement(animationData.movementNames[0]);
119                     if (!movData) break;
120 
121                     var _movBoneData = movData.getMovementBoneData(bone.getName());
122                     if (!_movBoneData || _movBoneData.frameList.length <= 0) break;
123 
124                     var frameData = _movBoneData.getFrameData(0);
125                     if (!frameData) break;
126 
127                     bone.getTweenData().copy(frameData);
128                     bone.changeDisplayWithIndex(frameData.displayIndex, false);
129                 } while (0);
130             }
131 
132             this.update(0);
133             this.updateOffsetPoint();
134         } else {
135             this._name = "new_armature";
136             this.armatureData = ccs.ArmatureData.create();
137             this.armatureData.name = this._name;
138 
139             animationData = ccs.AnimationData.create();
140             animationData.name = this._name;
141 
142             armatureDataManager.addArmatureData(this._name, this.armatureData);
143             armatureDataManager.addAnimationData(this._name, animationData);
144 
145             this.animation.setAnimationData(animationData);
146         }
147         if (cc._renderType === cc._RENDER_TYPE_WEBGL)
148             this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR));
149 
150         this.setCascadeOpacityEnabled(true);
151         this.setCascadeColorEnabled(true);
152         return true;
153     },
154 
155     /**
156      * create a bone with name
157      * @param {String} boneName
158      * @return {ccs.Bone}
159      */
160     createBone: function (boneName) {
161         var existedBone = this.getBone(boneName);
162         if (existedBone)
163             return existedBone;
164 
165         var boneData = this.armatureData.getBoneData(boneName);
166         var parentName = boneData.parentName;
167 
168         var bone = null;
169         if (parentName) {
170             this.createBone(parentName);
171             bone = ccs.Bone.create(boneName);
172             this.addBone(bone, parentName);
173         } else {
174             bone = ccs.Bone.create(boneName);
175             this.addBone(bone, "");
176         }
177 
178         bone.setBoneData(boneData);
179         bone.getDisplayManager().changeDisplayWithIndex(-1, false);
180         return bone;
181     },
182 
183     /**
184      * Add a Bone to this Armature
185      * @param {ccs.Bone} bone  The Bone you want to add to Armature
186      * @param {String} parentName The parent Bone's name you want to add to. If it's  null, then set Armature to its parent
187      */
188     addBone: function (bone, parentName) {
189         cc.assert(bone, "Argument must be non-nil");
190         var locBoneDic = this._boneDic;
191         if(bone.getName())
192             cc.assert(!locBoneDic[bone.getName()], "bone already added. It can't be added again");
193 
194         if (parentName) {
195             var boneParent = locBoneDic[parentName];
196             if (boneParent)
197                 boneParent.addChildBone(bone);
198             else
199                 this._topBoneList.push(bone);
200         } else
201             this._topBoneList.push(bone);
202         bone.setArmature(this);
203 
204         locBoneDic[bone.getName()] = bone;
205         this.addChild(bone);
206     },
207 
208     /**
209      * Remove a bone with the specified name. If recursion it will also remove child Bone recursively.
210      * @param {ccs.Bone} bone The bone you want to remove
211      * @param {Boolean} recursion Determine whether remove the bone's child  recursion.
212      */
213     removeBone: function (bone, recursion) {
214         cc.assert(bone, "bone must be added to the bone dictionary!");
215 
216         bone.setArmature(null);
217         bone.removeFromParent(recursion);
218         cc.arrayRemoveObject(this._topBoneList, bone);
219 
220         delete  this._boneDic[bone.getName()];
221         this.removeChild(bone, true);
222     },
223 
224     /**
225      * Gets a bone with the specified name
226      * @param {String} name The bone's name you want to get
227      * @return {ccs.Bone}
228      */
229     getBone: function (name) {
230         return this._boneDic[name];
231     },
232 
233     /**
234      * Change a bone's parent with the specified parent name.
235      * @param {ccs.Bone} bone The bone you want to change parent
236      * @param {String} parentName The new parent's name
237      */
238     changeBoneParent: function (bone, parentName) {
239         cc.assert(bone, "bone must be added to the bone dictionary!");
240 
241         var parentBone = bone.getParentBone();
242         if (parentBone) {
243             cc.arrayRemoveObject(parentBone.getChildren(), bone);
244             bone.setParentBone(null);
245         }
246 
247         if (parentName) {
248             var boneParent = this._boneDic[parentName];
249             if (boneParent) {
250                 boneParent.addChildBone(bone);
251                 cc.arrayRemoveObject(this._topBoneList, bone);
252             } else
253                 this._topBoneList.push(bone);
254         }
255     },
256 
257     /**
258      * Get CCArmature's bone dictionary
259      * @return {Object} Armature's bone dictionary
260      */
261     getBoneDic: function () {
262         return this._boneDic;
263     },
264 
265     /**
266      * Set contentSize and Calculate anchor point.
267      */
268     updateOffsetPoint: function () {
269         // Set contentsize and Calculate anchor point.
270         var rect = this.getBoundingBox();
271         this.setContentSize(rect);
272         var locOffsetPoint = this._offsetPoint;
273         locOffsetPoint.x = -rect.x;
274         locOffsetPoint.y = -rect.y;
275         if (rect.width != 0 && rect.height != 0)
276             this.setAnchorPoint(locOffsetPoint.x / rect.width, locOffsetPoint.y / rect.height);
277     },
278 
279     /**
280      * Sets armature's anchor point, because it need to consider offset point, so here is the override function.
281      * @override
282      * @param {cc.Point|Number} point point or x of point
283      * @param {Number} [y] y of point
284      */
285     setAnchorPoint: function(point, y){
286         var ax, ay;
287         if(y !== undefined){
288             ax = point;
289             ay = y;
290         }else{
291             ax = point.x;
292             ay = point.y;
293         }
294         var locAnchorPoint = this._anchorPoint;
295         if(ax != locAnchorPoint.x || ay != locAnchorPoint.y){
296             var contentSize = this._contentSize ;
297             locAnchorPoint.x = ax;
298             locAnchorPoint.y = ay;
299             this._anchorPointInPoints.x = contentSize.width * locAnchorPoint.x - this._offsetPoint.x;
300             this._anchorPointInPoints.y = contentSize.height * locAnchorPoint.y - this._offsetPoint.y;
301 
302             this._realAnchorPointInPoints.x = contentSize.width * locAnchorPoint.x;
303             this._realAnchorPointInPoints.y = contentSize.height * locAnchorPoint.y;
304             this.setNodeDirty();
305         }
306     },
307 
308     _setAnchorX: function (x) {
309         if (this._anchorPoint.x === x) return;
310         this._anchorPoint.x = x;
311         this._anchorPointInPoints.x = this._contentSize.width * x - this._offsetPoint.x;
312         this._realAnchorPointInPoints.x = this._contentSize.width * x;
313         this.setNodeDirty();
314     },
315 
316     _setAnchorY: function (y) {
317         if (this._anchorPoint.y === y) return;
318         this._anchorPoint.y = y;
319         this._anchorPointInPoints.y = this._contentSize.height * y - this._offsetPoint.y;
320         this._realAnchorPointInPoints.y = this._contentSize.height * y;
321         this.setNodeDirty();
322     },
323 
324     /**
325      * Returns the anchor point in points of ccs.Armature.
326      * @override
327      * @returns {cc.Point}
328      */
329     getAnchorPointInPoints: function(){
330         return this._realAnchorPointInPoints;
331     },
332 
333     /**
334      * Sets animation to this Armature
335      * @param {ccs.ArmatureAnimation} animation
336      */
337     setAnimation: function (animation) {
338         this.animation = animation;
339     },
340 
341     /**
342      * Gets the animation of this Armature.
343      * @return {ccs.ArmatureAnimation}
344      */
345     getAnimation: function () {
346         return this.animation;
347     },
348 
349     /**
350      * armatureTransformDirty getter
351      * @returns {Boolean}
352      */
353     getArmatureTransformDirty: function () {
354         return this._armatureTransformDirty;
355     },
356 
357     /**
358      * The update callback of ccs.Armature, it updates animation's state and updates bone's state.
359      * @override
360      * @param {Number} dt
361      */
362     update: function (dt) {
363         this.animation.update(dt);
364         var locTopBoneList = this._topBoneList;
365         for (var i = 0; i < locTopBoneList.length; i++)
366             locTopBoneList[i].update(dt);
367         this._armatureTransformDirty = false;
368     },
369 
370     /**
371      * Draws armature's display render node.
372      * @override
373      * @param  {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context
374      */
375     draw: function(ctx){
376         if (this._parentBone == null && this._batchNode == null) {
377             //        CC_NODE_DRAW_SETUP();
378         }
379 
380         var locChildren = this._children;
381         var alphaPremultiplied = cc.BlendFunc.ALPHA_PREMULTIPLIED, alphaNonPremultipled = cc.BlendFunc.ALPHA_NON_PREMULTIPLIED;
382         for (var i = 0, len = locChildren.length; i< len; i++) {
383             var selBone = locChildren[i];
384             if (selBone && selBone.getDisplayRenderNode) {
385                 var node = selBone.getDisplayRenderNode();
386 
387                 if (null == node)
388                     continue;
389 
390                 if(cc._renderType === cc._RENDER_TYPE_WEBGL)
391                     node.setShaderProgram(this._shaderProgram);
392 
393                 switch (selBone.getDisplayRenderNodeType()) {
394                     case ccs.DISPLAY_TYPE_SPRITE:
395                         if(node instanceof ccs.Skin){
396                             if(cc._renderType === cc._RENDER_TYPE_WEBGL){
397                                 node.updateTransform();
398 
399                                 var func = selBone.getBlendFunc();
400                                 if (func.src != alphaPremultiplied.src || func.dst != alphaPremultiplied.dst)
401                                     node.setBlendFunc(selBone.getBlendFunc());
402                                 else {
403                                     if ((this._blendFunc.src == alphaPremultiplied.src && this._blendFunc.dst == alphaPremultiplied.dst)
404                                         && !node.getTexture().hasPremultipliedAlpha())
405                                         node.setBlendFunc(alphaNonPremultipled);
406                                     else
407                                         node.setBlendFunc(this._blendFunc);
408                                 }
409                                 node.draw(ctx);
410                             } else{
411                                 node.visit(ctx);
412                             }
413                         }
414                         break;
415                     case ccs.DISPLAY_TYPE_ARMATURE:
416                         node.draw(ctx);
417                         break;
418                     default:
419                         node.visit(ctx);
420                         break;
421                 }
422             } else if(selBone instanceof cc.Node) {
423                 if(cc._renderType === cc._RENDER_TYPE_WEBGL)
424                     selBone.setShaderProgram(this._shaderProgram);
425                 selBone.visit(ctx);
426                 //            CC_NODE_DRAW_SETUP();
427             }
428         }
429     },
430 
431     /**
432      * The callback when ccs.Armature enter stage.
433      * @override
434      */
435     onEnter: function () {
436         cc.Node.prototype.onEnter.call(this);
437         this.scheduleUpdate();
438     },
439 
440     /**
441      * The callback when ccs.Armature exit stage.
442      * @override
443      */
444     onExit: function () {
445         cc.Node.prototype.onExit.call(this);
446         this.unscheduleUpdate();
447     },
448 
449     visit: null,
450 
451     _visitForCanvas: function(ctx){
452         var context = ctx || cc._renderContext;
453         // quick return if not visible. children won't be drawn.
454         if (!this._visible)
455             return;
456 
457         context.save();
458         this.transform(context);
459 
460         this.sortAllChildren();
461         this.draw(ctx);
462 
463         // reset for next frame
464         this._cacheDirty = false;
465         this.arrivalOrder = 0;
466 
467         context.restore();
468     },
469 
470     _visitForWebGL: function(){
471         // quick return if not visible. children won't be drawn.
472         if (!this._visible)
473             return;
474 
475         var context = cc._renderContext, currentStack = cc.current_stack;
476 
477         currentStack.stack.push(currentStack.top);
478         cc.kmMat4Assign(this._stackMatrix, currentStack.top);
479         currentStack.top = this._stackMatrix;
480 
481         this.transform();
482 
483         this.sortAllChildren();
484         this.draw(context);
485 
486         // reset for next frame
487         this.arrivalOrder = 0;
488         currentStack.top = currentStack.stack.pop();
489     },
490 
491     /**
492      * This boundingBox will calculate all bones' boundingBox every time
493      * @returns {cc.Rect}
494      */
495     getBoundingBox: function(){
496         var minX, minY, maxX, maxY = 0;
497         var first = true;
498 
499         var boundingBox = cc.rect(0, 0, 0, 0), locChildren = this._children;
500 
501         var len = locChildren.length;
502         for (var i=0; i<len; i++) {
503             var bone = locChildren[i];
504             if (bone) {
505                 var r = bone.getDisplayManager().getBoundingBox();
506                 if (r.x == 0 && r.y == 0 && r.width == 0 && r.height == 0)
507                     continue;
508 
509                 if(first) {
510                     minX = r.x;
511                     minY = r.y;
512                     maxX = r.x + r.width;
513                     maxY = r.y + r.height;
514                     first = false;
515                 } else {
516                     minX = r.x < boundingBox.x ? r.x : boundingBox.x;
517                     minY = r.y < boundingBox.y ? r.y : boundingBox.y;
518                     maxX = r.x + r.width > boundingBox.x + boundingBox.width ?
519                         r.x + r.width : boundingBox.x + boundingBox.width;
520                     maxY = r.y + r.height > boundingBox.y + boundingBox.height ?
521                         r.y + r.height : boundingBox.y + boundingBox.height;
522                 }
523 
524                 boundingBox.x = minX;
525                 boundingBox.y = minY;
526                 boundingBox.width = maxX - minX;
527                 boundingBox.height = maxY - minY;
528             }
529         }
530         return cc.rectApplyAffineTransform(boundingBox, this.getNodeToParentTransform());
531     },
532 
533     /**
534      * when bone  contain the point ,then return it.
535      * @param {Number} x
536      * @param {Number} y
537      * @returns {ccs.Bone}
538      */
539     getBoneAtPoint: function (x, y) {
540         var locChildren = this._children;
541         for (var i = locChildren.length - 1; i >= 0; i--) {
542             var child = locChildren[i];
543             if (child instanceof ccs.Bone && child.getDisplayManager().containPoint(x, y))
544                 return child;
545         }
546         return null;
547     },
548 
549     /**
550      * Sets parent bone of this Armature
551      * @param {ccs.Bone} parentBone
552      */
553     setParentBone: function (parentBone) {
554         this._parentBone = parentBone;
555         var locBoneDic = this._boneDic;
556         for (var key in locBoneDic) {
557             locBoneDic[key].setArmature(this);
558         }
559     },
560 
561     /**
562      * Return parent bone of ccs.Armature.
563      * @returns {ccs.Bone}
564      */
565     getParentBone: function () {
566         return this._parentBone;
567     },
568 
569     /**
570      * draw contour
571      */
572     drawContour: function () {
573         cc._drawingUtil.setDrawColor(255, 255, 255, 255);
574         cc._drawingUtil.setLineWidth(1);
575         var locBoneDic = this._boneDic;
576         for (var key in locBoneDic) {
577             var bone = locBoneDic[key];
578             var detector = bone.getColliderDetector();
579             if(!detector)
580                 continue;
581             var bodyList = detector.getColliderBodyList();
582             for (var i = 0; i < bodyList.length; i++) {
583                 var body = bodyList[i];
584                 var vertexList = body.getCalculatedVertexList();
585                 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true);
586             }
587         }
588     },
589 
590 
591     setBody: function (body) {
592         if (this._body == body)
593             return;
594 
595         this._body = body;
596         this._body.data = this;
597         var child, displayObject, locChildren = this._children;
598         for (var i = 0; i < locChildren.length; i++) {
599             child = locChildren[i];
600             if (child instanceof ccs.Bone) {
601                 var displayList = child.getDisplayManager().getDecorativeDisplayList();
602                 for (var j = 0; j < displayList.length; j++) {
603                     displayObject = displayList[j];
604                     var detector = displayObject.getColliderDetector();
605                     if (detector)
606                         detector.setBody(this._body);
607                 }
608             }
609         }
610     },
611 
612     getShapeList: function () {
613         if (this._body)
614             return this._body.shapeList;
615         return null;
616     },
617 
618     getBody: function () {
619         return this._body;
620     },
621 
622     /**
623      * Sets the blendFunc to ccs.Armature
624      * @param {cc.BlendFunc} blendFunc
625      */
626     setBlendFunc: function (blendFunc) {
627         this._blendFunc = blendFunc;
628     },
629 
630     /**
631      * Returns the blendFunc of ccs.Armature
632      * @returns {cc.BlendFunc}
633      */
634     getBlendFunc: function () {
635         return this._blendFunc;
636     },
637 
638     /**
639      * set collider filter
640      * @param {ccs.ColliderFilter} filter
641      */
642     setColliderFilter: function (filter) {
643         var locBoneDic = this._boneDic;
644         for (var key in locBoneDic)
645             locBoneDic[key].setColliderFilter(filter);
646     },
647 
648     /**
649      * Returns the armatureData of ccs.Armature
650      * @return {ccs.ArmatureData}
651      */
652     getArmatureData: function () {
653         return this.armatureData;
654     },
655 
656     /**
657      * Sets armatureData to this Armature
658      * @param {ccs.ArmatureData} armatureData
659      */
660     setArmatureData: function (armatureData) {
661         this.armatureData = armatureData;
662     },
663 
664     getBatchNode: function () {
665         return this.batchNode;
666     },
667 
668     setBatchNode: function (batchNode) {
669         this.batchNode = batchNode;
670     },
671 
672     /**
673      * version getter
674      * @returns {Number}
675      */
676     getVersion: function () {
677         return this.version;
678     },
679 
680     /**
681      * version setter
682      * @param {Number} version
683      */
684     setVersion: function (version) {
685         this.version = version;
686     }
687 });
688 
689 if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
690     ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForWebGL;
691 } else {
692     ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForCanvas;
693 }
694 
695 var _p = ccs.Armature.prototype;
696 
697 /** @expose */
698 _p.parentBone;
699 cc.defineGetterSetter(_p, "parentBone", _p.getParentBone, _p.setParentBone);
700 /** @expose */
701 _p.body;
702 cc.defineGetterSetter(_p, "body", _p.getBody, _p.setBody);
703 /** @expose */
704 _p.colliderFilter;
705 cc.defineGetterSetter(_p, "colliderFilter", null, _p.setColliderFilter);
706 
707 _p = null;
708 
709 /**
710  * Allocates an armature, and use the ArmatureData named name in ArmatureDataManager to initializes the armature.
711  * @param {String} [name] Bone name
712  * @param {ccs.Bone} [parentBone] the parent bone
713  * @return {ccs.Armature}
714  * @example
715  * // example
716  * var armature = ccs.Armature.create();
717  */
718 ccs.Armature.create = function (name, parentBone) {
719     var armature = new ccs.Armature();
720     if (armature.init(name, parentBone))
721         return armature;
722     return null;
723 };
724