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