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_TEXTURE_UCOLOR));
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) {
365                 var node = selBone.getDisplayRenderNode();
366 
367                 if (null == node)
368                     continue;
369 
370                 switch (selBone.getDisplayRenderNodeType()) {
371                     case ccs.DISPLAY_TYPE_SPRITE:
372                         if(node instanceof ccs.Skin){
373                             if(cc._renderType === cc._RENDER_TYPE_WEBGL){
374                                 node.updateTransform();
375 
376                                 var func = selBone.getBlendFunc();
377                                 if (func.src != alphaPremultiplied.src || func.dst != alphaPremultiplied.dst)
378                                     node.setBlendFunc(selBone.getBlendFunc());
379                                 else {
380                                     if ((this._blendFunc.src == alphaPremultiplied.src && this._blendFunc.dst == alphaPremultiplied.dst)
381                                         && !node.getTexture().hasPremultipliedAlpha())
382                                         node.setBlendFunc(alphaNonPremultipled);
383                                     else
384                                         node.setBlendFunc(this._blendFunc);
385                                 }
386                                 node.draw(ctx);
387                             } else{
388                                 node.visit(ctx);
389                             }
390                         }
391                         break;
392                     case ccs.DISPLAY_TYPE_ARMATURE:
393                         node.draw(ctx);
394                         break;
395                     default:
396                         node.visit(ctx);
397                         break;
398                 }
399             } else if(selBone instanceof cc.Node) {
400                 selBone.visit(ctx);
401                 //            CC_NODE_DRAW_SETUP();
402             }
403         }
404     },
405 
406     onEnter: function () {
407         cc.Node.prototype.onEnter.call(this);
408         this.scheduleUpdate();
409     },
410 
411     onExit: function () {
412         cc.Node.prototype.onExit.call(this);
413         this.unscheduleUpdate();
414     },
415 
416     visit: null,
417 
418     _visitForCanvas: function(ctx){
419         var context = ctx || cc._renderContext;
420         // quick return if not visible. children won't be drawn.
421         if (!this._visible)
422             return;
423 
424         context.save();
425         this.transform(context);
426 
427         this.sortAllChildren();
428         this.draw(ctx);
429 
430         // reset for next frame
431         this._cacheDirty = false;
432         this.arrivalOrder = 0;
433 
434         context.restore();
435     },
436 
437     _visitForWebGL: function(){
438         // quick return if not visible. children won't be drawn.
439         if (!this._visible)
440             return;
441 
442         var context = cc._renderContext, currentStack = cc.current_stack;
443 
444         currentStack.stack.push(currentStack.top);
445         cc.kmMat4Assign(this._stackMatrix, currentStack.top);
446         currentStack.top = this._stackMatrix;
447 
448         this.transform();
449 
450         this.sortAllChildren();
451         this.draw(context);
452 
453         // reset for next frame
454         this.arrivalOrder = 0;
455         currentStack.top = currentStack.stack.pop();
456     },
457 
458     /**
459      * This boundingBox will calculate all bones' boundingBox every time
460      * @returns {cc.Rect}
461      */
462     getBoundingBox: function(){
463         var minX, minY, maxX, maxY = 0;
464         var first = true;
465 
466         var boundingBox = cc.rect(0, 0, 0, 0), locChildren = this._children;
467 
468         var len = locChildren.length;
469         for (var i=0; i<len; i++) {
470             var bone = locChildren[i];
471             if (bone) {
472                 var r = bone.getDisplayManager().getBoundingBox();
473                 if (r.x == 0 && r.y == 0 && r.width == 0 && r.height == 0)
474                     continue;
475 
476                 if(first) {
477                     minX = r.x;
478                     minY = r.y;
479                     maxX = r.x + r.width;
480                     maxY = r.y + r.height;
481                     first = false;
482                 } else {
483                     minX = r.x < boundingBox.x ? r.x : boundingBox.x;
484                     minY = r.y < boundingBox.y ? r.y : boundingBox.y;
485                     maxX = r.x + r.width > boundingBox.x + boundingBox.width ?
486                         r.x + r.width : boundingBox.x + boundingBox.width;
487                     maxY = r.y + r.height > boundingBox.y + boundingBox.height ?
488                         r.y + r.height : boundingBox.y + boundingBox.height;
489                 }
490 
491                 boundingBox.x = minX;
492                 boundingBox.y = minY;
493                 boundingBox.width = maxX - minX;
494                 boundingBox.height = maxY - minY;
495             }
496         }
497         return cc.rectApplyAffineTransform(boundingBox, this.getNodeToParentTransform());
498     },
499 
500     /**
501      * when bone  contain the point ,then return it.
502      * @param {Number} x
503      * @param {Number} y
504      * @returns {ccs.Bone}
505      */
506     getBoneAtPoint: function (x, y) {
507         var locChildren = this._children;
508         for (var i = locChildren.length - 1; i >= 0; i--) {
509             var child = locChildren[i];
510             if (child instanceof ccs.Bone && child.getDisplayManager().containPoint(x, y))
511                 return child;
512         }
513         return null;
514     },
515 
516     /**
517      * Sets parent bone of this Armature
518      * @param {ccs.Bone} parentBone
519      */
520     setParentBone: function (parentBone) {
521         this._parentBone = parentBone;
522         var locBoneDic = this._boneDic;
523         for (var key in locBoneDic) {
524             locBoneDic[key].setArmature(this);
525         }
526     },
527 
528     /**
529      * return parent bone
530      * @returns {ccs.Bone}
531      */
532     getParentBone: function () {
533         return this._parentBone;
534     },
535 
536     /**
537      * draw contour
538      */
539     drawContour: function () {
540         cc._drawingUtil.setDrawColor(255, 255, 255, 255);
541         cc._drawingUtil.setLineWidth(1);
542         var locBoneDic = this._boneDic;
543         for (var key in locBoneDic) {
544             var bone = locBoneDic[key];
545             var detector = bone.getColliderDetector();
546             if(!detector)
547                 continue;
548             var bodyList = detector.getColliderBodyList();
549             for (var i = 0; i < bodyList.length; i++) {
550                 var body = bodyList[i];
551                 var vertexList = body.getCalculatedVertexList();
552                 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true);
553             }
554         }
555     },
556 
557     setBody: function (body) {
558         if (this._body == body)
559             return;
560 
561         this._body = body;
562         this._body.data = this;
563         var child, displayObject, locChildren = this._children;
564         for (var i = 0; i < locChildren.length; i++) {
565             child = locChildren[i];
566             if (child instanceof ccs.Bone) {
567                 var displayList = child.getDisplayManager().getDecorativeDisplayList();
568                 for (var j = 0; j < displayList.length; j++) {
569                     displayObject = displayList[j];
570                     var detector = displayObject.getColliderDetector();
571                     if (detector)
572                         detector.setBody(this._body);
573                 }
574             }
575         }
576     },
577 
578     getShapeList: function () {
579         if (this._body)
580             return this._body.shapeList;
581         return null;
582     },
583 
584     getBody: function () {
585         return this._body;
586     },
587 
588     /**
589      * conforms to cc.TextureProtocol protocol
590      * @param {cc.BlendFunc} blendFunc
591      */
592     setBlendFunc: function (blendFunc) {
593         this._blendFunc = blendFunc;
594     },
595 
596     /**
597      * blendFunc getter
598      * @returns {cc.BlendFunc}
599      */
600     getBlendFunc: function () {
601         return this._blendFunc;
602     },
603 
604     /**
605      * set collider filter
606      * @param {ccs.ColliderFilter} filter
607      */
608     setColliderFilter: function (filter) {
609         var locBoneDic = this._boneDic;
610         for (var key in locBoneDic)
611             locBoneDic[key].setColliderFilter(filter);
612     },
613 
614     /**
615      * Gets the armatureData of this Armature
616      * @return {ccs.ArmatureData}
617      */
618     getArmatureData: function () {
619         return this.armatureData;
620     },
621 
622     /**
623      * Sets armatureData to this Armature
624      * @param {ccs.ArmatureData} armatureData
625      */
626     setArmatureData: function (armatureData) {
627         this.armatureData = armatureData;
628     },
629 
630     getBatchNode: function () {
631         return this.batchNode;
632     },
633 
634     setBatchNode: function (batchNode) {
635         this.batchNode = batchNode;
636     },
637 
638     /**
639      * version getter
640      * @returns {Number}
641      */
642     getVersion: function () {
643         return this.version;
644     },
645 
646     /**
647      * version setter
648      * @param {Number} version
649      */
650     setVersion: function (version) {
651         this.version = version;
652     }
653 });
654 
655 if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
656     ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForWebGL;
657 } else {
658     ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForCanvas;
659 }
660 
661 var _p = ccs.Armature.prototype;
662 
663 /** @expose */
664 _p.parentBone;
665 cc.defineGetterSetter(_p, "parentBone", _p.getParentBone, _p.setParentBone);
666 /** @expose */
667 _p.body;
668 cc.defineGetterSetter(_p, "body", _p.getBody, _p.setBody);
669 /** @expose */
670 _p.colliderFilter;
671 cc.defineGetterSetter(_p, "colliderFilter", null, _p.setColliderFilter);
672 
673 _p = null;
674 
675 /**
676  * Allocates an armature, and use the ArmatureData named name in ArmatureDataManager to initializes the armature.
677  * @param {String} [name] Bone name
678  * @param {ccs.Bone} [parentBone] the parent bone
679  * @return {ccs.Armature}
680  * @example
681  * // example
682  * var armature = ccs.Armature.create();
683  */
684 ccs.Armature.create = function (name, parentBone) {
685     var armature = new ccs.Armature();
686     if (armature.init(name, parentBone))
687         return armature;
688     return null;
689 };
690