1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3 
  4  http://www.cocos2d-x.org
  5 
  6  Permission is hereby granted, free of charge, to any person obtaining a copy
  7  of this software and associated documentation files (the "Software"), to deal
  8  in the Software without restriction, including without limitation the rights
  9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  copies of the Software, and to permit persons to whom the Software is
 11  furnished to do so, subject to the following conditions:
 12 
 13  The above copyright notice and this permission notice shall be included in
 14  all copies or substantial portions of the Software.
 15 
 16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  THE SOFTWARE.
 23  ****************************************************************************/
 24 
 25 /**
 26  * Base class for ccs.Armature objects.
 27  * @class
 28  * @extends ccs.NodeRGBA
 29  *
 30  * @property {ccs.Bone}                 parentBone      - The parent bone of the armature node
 31  * @property {ccs.ArmatureAnimation}    animation       - The animation
 32  * @property {ccs.ArmatureData}         armatureData    - The armature data
 33  * @property {String}                   name            - The name of the armature
 34  * @property {cc.SpriteBatchNode}       batchNode       - The batch node of the armature
 35  * @property {Number}                   version         - The version
 36  * @property {Object}                   body            - The body of the armature
 37  * @property {ccs.ColliderFilter}       colliderFilter  - <@writeonly> The collider filter of the armature
 38  */
 39 ccs.Armature = ccs.NodeRGBA.extend(/** @lends ccs.Armature# */{
 40     animation:null,
 41     armatureData:null,
 42     batchNode:null,
 43     name:"",
 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     _textureAtlasDic:null,
 54     _blendFunc:null,
 55     _className:"Armature",
 56 
 57 	/**
 58 	 * Create a armature node.
 59 	 * @constructor
 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.NodeRGBA.prototype.ctor.call(this);
 67         this.animation = null;
 68         this.armatureData = null;
 69         this.batchNode = null;
 70         this.name = "";
 71         this._textureAtlas = null;
 72         this._parentBone = null;
 73         this._boneDic = null;
 74         this._topBoneList = null;
 75         this._armatureIndexDic = {};
 76         this._offsetPoint = cc.p(0, 0);
 77         this.version = 0;
 78         this._armatureTransformDirty = true;
 79         this._body = null;
 80         this._textureAtlasDic = null;
 81         this._blendFunc = null;
 82 
 83 		parentBone && ccs.Armature.prototype.init.call(this, name, parentBone);
 84     },
 85 
 86     /**
 87      * Initializes a CCArmature with the specified name and CCBone
 88      * @param {String} name
 89      * @param {ccs.Bone} parentBone
 90      * @return {Boolean}
 91      */
 92     init:function (name, parentBone) {
 93         cc.NodeRGBA.prototype.init.call(this);
 94         if (parentBone) {
 95             this._parentBone = parentBone;
 96         }
 97         this.removeAllChildren();
 98         this.animation = new ccs.ArmatureAnimation();
 99         this.animation.init(this);
100         this._boneDic = {};
101         this._topBoneList = [];
102         this._textureAtlasDic = {};
103         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
104         this.name = (!name) ? "" : name;
105         var armatureDataManager = ccs.armatureDataManager;
106         if (name != "") {
107             //animationData
108             var animationData = armatureDataManager.getAnimationData(name);
109             if (!animationData) {
110                 cc.log("AnimationData not exist! ");
111                 return false;
112             }
113             this.animation.setAnimationData(animationData);
114 
115             //armatureData
116             var armatureData = armatureDataManager.getArmatureData(name);
117             this.armatureData = armatureData;
118 
119             //boneDataDic
120             var boneDataDic = armatureData.getBoneDataDic();
121             for (var key in boneDataDic) {
122                 var bone = this.createBone(String(key));
123                 //! init bone's  Tween to 1st movement's 1st frame
124                 do {
125                     var movData = animationData.getMovement(animationData.movementNames[0]);
126                     if (!movData) {
127                         break;
128                     }
129                     var _movBoneData = movData.getMovementBoneData(bone.getName());
130                     if (!_movBoneData || _movBoneData.frameList.length <= 0) {
131                         break;
132                     }
133                     var frameData = _movBoneData.getFrameData(0);
134                     if (!frameData) {
135                         break;
136                     }
137                     bone.getTweenData().copy(frameData);
138                     bone.changeDisplayWithIndex(frameData.displayIndex, false);
139                 } while (0);
140             }
141             this.update(0);
142             this.updateOffsetPoint();
143         } else {
144             this.name = "new_armature";
145             this.armatureData = new ccs.ArmatureData();
146             this.armatureData.name = this.name;
147 
148             var animationData = new ccs.AnimationData();
149             animationData.name = this.name;
150 
151             armatureDataManager.addArmatureData(this.name, this.armatureData);
152             armatureDataManager.addAnimationData(this.name, animationData);
153 
154             this.animation.setAnimationData(animationData);
155         }
156         if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
157             this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR));
158         }
159 
160         this.setCascadeOpacityEnabled(true);
161         this.setCascadeColorEnabled(true);
162         return true;
163     },
164     onEnter:function(){
165         cc.NodeRGBA.prototype.onEnter.call(this);
166         this.scheduleUpdate();
167     },
168     onExit:function(){
169         cc.NodeRGBA.prototype.onExit.call(this);
170         this.unscheduleUpdate();
171     },
172     /**
173      * create a bone
174      * @param {String} boneName
175      * @return {ccs.Bone}
176      */
177     createBone:function (boneName) {
178         var existedBone = this.getBone(boneName);
179         if (existedBone) {
180             return existedBone;
181         }
182         var boneData = this.armatureData.getBoneData(boneName);
183         var parentName = boneData.parentName;
184         var bone = null;
185         if (parentName != "") {
186             this.createBone(parentName);
187             bone = ccs.Bone.create(boneName);
188             this.addBone(bone, parentName);
189         } else {
190             bone = ccs.Bone.create(boneName);
191             this.addBone(bone, "");
192         }
193 
194         bone.setBoneData(boneData);
195         bone.getDisplayManager().changeDisplayWithIndex(-1, false);
196         return bone;
197     },
198 
199     /**
200      * add a bone
201      * @param {ccs.Bone} bone
202      * @param {String} parentName
203      */
204     addBone:function (bone, parentName) {
205         if (!bone) {
206             cc.log("Argument must be non-nil");
207             return;
208         }
209         if (this._boneDic[bone.getName()]) {
210             cc.log("bone already added. It can't be added again");
211             return;
212         }
213 
214         if (parentName) {
215             var boneParent = this._boneDic[parentName];
216             if (boneParent) {
217                 boneParent.addChildBone(bone);
218             }
219             else {
220                 this._topBoneList.push(bone);
221             }
222         }
223         else {
224             this._topBoneList.push(bone);
225         }
226         bone.setArmature(this);
227         this._boneDic[bone.getName()] = bone;
228         this.addChild(bone);
229     },
230 
231     /**
232      * remove a bone
233      * @param {ccs.Bone} bone
234      * @param {Boolean} recursion
235      */
236     removeBone:function (bone, recursion) {
237         if (!bone) {
238             cc.log("bone must be added to the bone dictionary!");
239             return;
240         }
241 
242         bone.setArmature(null);
243         bone.removeFromParent(recursion);
244         cc.arrayRemoveObject(this._topBoneList, bone);
245         delete  this._boneDic[bone.getName()];
246         this.removeChild(bone, true);
247     },
248 
249     /**
250      * get a bone by name
251      * @param {String} name
252      * @return {ccs.Bone}
253      */
254     getBone:function (name) {
255         return this._boneDic[name];
256     },
257 
258     /**
259      * Change a bone's parent with the specified parent name.
260      * @param {ccs.Bone} bone
261      * @param {String} parentName
262      */
263     changeBoneParent:function (bone, parentName) {
264         if (!bone) {
265             cc.log("bone must be added to the bone dictionary!");
266             return;
267         }
268         var parentBone = bone.getParentBone();
269         if(parentBone){
270             cc.arrayRemoveObject(parentBone.getChildrenBone(), bone);
271             bone.setParentBone(null);
272         }
273 
274         if (parentName) {
275             var boneParent = this._boneDic[parentName];
276             if (boneParent) {
277                 boneParent.addChildBone(bone);
278                 cc.arrayRemoveObject(this._topBoneList,bone);
279             }else{
280                 this._topBoneList.push(bone);
281             }
282         }
283     },
284 
285     /**
286      * Get CCArmature's bone dictionary
287      * @return {Object}
288      */
289     getBoneDic:function () {
290         return this._boneDic;
291     },
292 
293     /**
294      * Set contentSize and Calculate anchor point.
295      */
296     updateOffsetPoint:function () {
297         // Set contentsize and Calculate anchor point.
298         var rect = this.boundingBox();
299         this.setContentSize(rect);
300         var locOffsetPoint = this._offsetPoint;
301         locOffsetPoint.x = -rect.x;
302         locOffsetPoint.y = -rect.y;
303         if (rect.width != 0 && rect.height != 0) {
304             this.setAnchorPoint(locOffsetPoint.x / rect.width, locOffsetPoint.y / rect.height);
305         }
306     },
307 
308     update:function (dt) {
309         this.animation.update(dt);
310         var locTopBoneList = this._topBoneList;
311         for (var i = 0; i < locTopBoneList.length; i++) {
312             locTopBoneList[i].update(dt);
313         }
314         this._armatureTransformDirty = false;
315     },
316 
317 
318     nodeToParentTransform: null,
319 
320     _nodeToParentTransformForWebGL:function () {
321         if (this._transformDirty) {
322             this._armatureTransformDirty = true;
323             // Translate values
324             var x = this._position.x;
325             var y = this._position.y;
326             var apx = this._anchorPointInPoints.x, napx = -apx;
327             var apy = this._anchorPointInPoints.y, napy = -apy;
328             var scx = this._scaleX, scy = this._scaleY;
329 
330             if (this._ignoreAnchorPointForPosition) {
331                 x += apx;
332                 y += apy;
333             }
334 
335             // Rotation values
336             // Change rotation code to handle X and Y
337             // If we skew with the exact same value for both x and y then we're simply just rotating
338             var cx = 1, sx = 0, cy = 1, sy = 0;
339             if (this._rotationX !== 0 || this._rotationY !== 0) {
340                 cx = Math.cos(-this._rotationRadiansX);
341                 sx = Math.sin(-this._rotationRadiansX);
342                 cy = Math.cos(-this._rotationRadiansY);
343                 sy = Math.sin(-this._rotationRadiansY);
344             }
345 
346             // Add offset point
347             x += cy * this._offsetPoint.x * this._scaleX + -sx * this._offsetPoint.y * this._scaleY;
348             y += sy * this._offsetPoint.x * this._scaleX + cx * this._offsetPoint.y * this._scaleY;
349 
350             var needsSkewMatrix = ( this._skewX || this._skewY );
351 
352             // optimization:
353             // inline anchor point calculation if skew is not needed
354             // Adjusted transform calculation for rotational skew
355             if (!needsSkewMatrix && (apx !== 0 || apy !== 0)) {
356                 x += cy * napx * scx + -sx * napy * scy;
357                 y += sy * napx * scx + cx * napy * scy;
358             }
359 
360             // Build Transform Matrix
361             // Adjusted transform calculation for rotational skew
362             var t = {a:cy * scx, b:sy * scx, c:-sx * scy, d:cx * scy, tx:x, ty:y};
363 
364             // XXX: Try to inline skew
365             // If skew is needed, apply skew and then anchor point
366             if (needsSkewMatrix) {
367                 t = cc.AffineTransformConcat({a:1.0, b:Math.tan(cc.degreesToRadians(this._skewY)),
368                     c:Math.tan(cc.degreesToRadians(this._skewX)), d:1.0, tx:0.0, ty:0.0}, t);
369 
370                 // adjust anchor point
371                 if (apx !== 0 || apy !== 0)
372                     t = cc.AffineTransformTranslate(t, napx, napy);
373             }
374 
375             if (this._additionalTransformDirty) {
376                 t = cc.AffineTransformConcat(t, this._additionalTransform);
377                 this._additionalTransformDirty = false;
378             }
379             this._transform = t;
380             this._transformDirty = false;
381         }
382         return this._transform;
383     },
384 
385     _nodeToParentTransformForCanvas:function () {
386         if (!this._transform)
387             this._transform = {a:1, b:0, c:0, d:1, tx:0, ty:0};
388         if (this._transformDirty) {
389             this._armatureTransformDirty = true;
390             var t = this._transform;// quick reference
391             // base position
392             t.tx = this._position.x;
393             t.ty = this._position.y;
394 
395             // rotation Cos and Sin
396             var Cos = 1, Sin = 0;
397             if (this._rotationX) {
398                 Cos = Math.cos(-this._rotationRadiansX);
399                 Sin = Math.sin(-this._rotationRadiansX);
400             }
401 
402             // base abcd
403             t.a = t.d = Cos;
404             t.c = -Sin;
405             t.b = Sin;
406 
407             var lScaleX = this._scaleX, lScaleY = this._scaleY;
408             var appX = this._anchorPointInPoints.x, appY = this._anchorPointInPoints.y;
409 
410             // Firefox on Vista and XP crashes
411             // GPU thread in case of scale(0.0, 0.0)
412             var sx = (lScaleX < 0.000001 && lScaleX > -0.000001) ? 0.000001 : lScaleX,
413                 sy = (lScaleY < 0.000001 && lScaleY > -0.000001) ? 0.000001 : lScaleY;
414 
415             // Add offset point
416             t.tx += Cos * this._offsetPoint.x * lScaleX + -Sin * this._offsetPoint.y * lScaleY;
417             t.ty += Sin * this._offsetPoint.x * lScaleX + Cos * this._offsetPoint.y * lScaleY;
418 
419             // skew
420             if (this._skewX || this._skewY) {
421                 // offset the anchorpoint
422                 var skx = Math.tan(-this._skewX * Math.PI / 180);
423                 var sky = Math.tan(-this._skewY * Math.PI / 180);
424                 var xx = appY * skx * sx;
425                 var yy = appX * sky * sy;
426                 t.a = Cos + -Sin * sky;
427                 t.c = Cos * skx + -Sin;
428                 t.b = Sin + Cos * sky;
429                 t.d = Sin * skx + Cos;
430                 t.tx += Cos * xx + -Sin * yy;
431                 t.ty += Sin * xx + Cos * yy;
432             }
433 
434             // scale
435             if (lScaleX !== 1 || lScaleY !== 1) {
436                 t.a *= sx;
437                 t.b *= sx;
438                 t.c *= sy;
439                 t.d *= sy;
440             }
441 
442             // adjust anchorPoint
443             t.tx += Cos * -appX * sx + -Sin * -appY * sy;
444             t.ty += Sin * -appX * sx + Cos * -appY * sy;
445 
446             // if ignore anchorPoint
447             if (this._ignoreAnchorPointForPosition) {
448                 t.tx += appX
449                 t.ty += appY;
450             }
451 
452             if (this._additionalTransformDirty) {
453                 this._transform = cc.AffineTransformConcat(this._transform, this._additionalTransform);
454                 this._additionalTransformDirty = false;
455             }
456 
457             t.tx = t.tx | 0;
458             t.ty = t.ty | 0;
459             this._transformDirty = false;
460         }
461         return this._transform;
462     },
463 
464     draw:function () {
465         //cc.g_NumberOfDraws++;
466     },
467 
468     /**
469      * conforms to cc.TextureProtocol protocol
470      * @param {cc.BlendFunc} blendFunc
471      */
472     setBlendFunc: function (blendFunc) {
473         this._blendFunc = blendFunc;
474     },
475 
476     /**
477      * blendFunc getter
478      * @returns {cc.BlendFunc}
479      */
480     getBlendFunc: function () {
481         return this._blendFunc;
482     },
483 
484     /**
485      * This boundingBox will calculate all bones' boundingBox every time
486      * @return {cc.rect}
487      */
488     boundingBox:function () {
489         var minx = 0, miny = 0, maxx = 0, maxy = 0;
490         var first = true;
491         var boundingBox = cc.rect(0, 0, 0, 0);
492         for (var i = 0; i < this._children.length; i++) {
493             var bone = this._children[i];
494             if (bone instanceof ccs.Bone) {
495                 var r = bone.getDisplayManager().getBoundingBox();
496                 if (first) {
497                     minx = cc.rectGetMinX(r);
498                     miny = cc.rectGetMinY(r);
499                     maxx = cc.rectGetMaxX(r);
500                     maxy = cc.rectGetMaxY(r);
501 
502                     first = false;
503                 }
504                 else {
505                     minx = cc.rectGetMinX(r) < cc.rectGetMinX(boundingBox) ? cc.rectGetMinX(r) : cc.rectGetMinX(boundingBox);
506                     miny = cc.rectGetMinY(r) < cc.rectGetMinY(boundingBox) ? cc.rectGetMinY(r) : cc.rectGetMinY(boundingBox);
507                     maxx = cc.rectGetMaxX(r) > cc.rectGetMaxX(boundingBox) ? cc.rectGetMaxX(r) : cc.rectGetMaxX(boundingBox);
508                     maxy = cc.rectGetMaxY(r) > cc.rectGetMaxY(boundingBox) ? cc.rectGetMaxY(r) : cc.rectGetMaxY(boundingBox);
509                 }
510                 boundingBox = cc.rect(minx, miny, maxx - minx, maxy - miny);
511             }
512         }
513         return cc.RectApplyAffineTransform(boundingBox, this.nodeToParentTransform());
514     },
515 
516     /**
517      * when bone  contain the point ,then return it.
518      * @param {Number} x
519      * @param {Number} y
520      * @returns {ccs.Bone}
521      */
522     getBoneAtPoint: function (x, y) {
523         for (var i = this._children.length - 1; i >= 0; i--) {
524             var child = this._children[i];
525             if (child instanceof ccs.Bone) {
526                 if (child.getDisplayManager().containPoint(x, y)) {
527                     return child;
528                 }
529             }
530         }
531         return null;
532     },
533 
534     getTexureAtlasWithTexture:function(){
535         return null;
536     },
537 
538     /**
539      * parent bone setter
540      * @param {ccs.Bone} parentBone
541      */
542     setParentBone: function (parentBone) {
543         this._parentBone = parentBone;
544         for (var key in this._boneDic) {
545             var bone = this._boneDic[key];
546             bone.setArmature(this);
547         }
548     },
549 
550     /**
551      * set collider filter
552      * @param {ccs.ColliderFilter} filter
553      */
554     setColliderFilter: function (filter) {
555         for (var key in this._boneDic) {
556             var bone = this._boneDic[key];
557             bone.setColliderFilter(filter);
558         }
559     },
560 
561     /**
562      * draw contour
563      */
564     drawContour: function () {
565         cc._drawingUtil.setDrawColor(255, 255, 255, 255);
566         cc._drawingUtil.setLineWidth(1);
567         for (var key in this._boneDic) {
568             var bone = this._boneDic[key];
569             var bodyList = bone.getColliderBodyList();
570             for (var i = 0; i < bodyList.length; i++) {
571                 var body = bodyList[i];
572                 var vertexList = body.getCalculatedVertexList();
573                 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true);
574             }
575         }
576     },
577 
578     /**
579      * return parent bone
580      * @returns {ccs.Bone}
581      */
582     getParentBone:function(){
583         return this._parentBone;
584     },
585 
586     /**
587      * armatureAnimation getter
588      * @return {ccs.ArmatureAnimation}
589      */
590     getAnimation:function () {
591         return this.animation;
592     },
593 
594     /**
595      * armatureAnimation setter
596      * @param {ccs.ArmatureAnimation} animation
597      */
598     setAnimation:function (animation) {
599         this.animation = animation;
600     },
601 
602     /**
603      * armatureData getter
604      * @return {ccs.ArmatureData}
605      */
606     getArmatureData:function () {
607         return this.armatureData;
608     },
609 
610     /**
611      * armatureData setter
612      * @param {ccs.ArmatureData} armatureData
613      */
614     setArmatureData:function (armatureData) {
615         this.armatureData = armatureData;
616     },
617     getName:function () {
618         return this.name;
619     },
620     setName:function (name) {
621         this.name = name;
622     },
623     getBatchNode:function () {
624         return this.batchNode;
625     },
626     setBatchNode:function (batchNode) {
627         this.batchNode = batchNode;
628     },
629 
630     /**
631      * version getter
632      * @returns {Number}
633      */
634     getVersion:function () {
635         return this.version;
636     },
637 
638     /**
639      * version setter
640      * @param {Number} version
641      */
642     setVersion:function (version) {
643         this.version = version;
644     },
645 
646     /**
647      * armatureTransformDirty getter
648      * @returns {Boolean}
649      */
650     getArmatureTransformDirty:function () {
651         return this._armatureTransformDirty;
652     },
653     getBody:function(){
654         return this._body;
655     },
656 
657     setBody:function(body){
658         if (this._body == body)
659             return;
660 
661         this._body = body;
662         this._body.data = this;
663         var child,displayObject;
664         for (var i = 0; i < this._children.length; i++) {
665             child = this._children[i];
666             if (child instanceof ccs.Bone) {
667                 var displayList = child.getDisplayManager().getDecorativeDisplayList();
668                 for (var j = 0; j < displayList.length; j++) {
669                     displayObject = displayList[j];
670                     var detector = displayObject.getColliderDetector();
671                     if (detector)
672                         detector.setBody(this._body);
673                 }
674             }
675         }
676     },
677     getShapeList:function(){
678         if(this._body)
679             return this._body.shapeList;
680         return [];
681     }
682 
683 });
684 
685 
686 if(cc._renderType == cc._RENDER_TYPE_WEBGL){
687     //WebGL
688     ccs.Armature.prototype.nodeToParentTransform = ccs.Armature.prototype._nodeToParentTransformForWebGL;
689 }else{
690     //Canvas
691     ccs.Armature.prototype.nodeToParentTransform = ccs.Armature.prototype._nodeToParentTransformForCanvas;
692 }
693 
694 window._p = ccs.Armature.prototype;
695 
696 /** @expose */
697 _p.parentBone;
698 cc.defineGetterSetter(_p, "parentBone", _p.getParentBone, _p.setParentBone);
699 /** @expose */
700 _p.body;
701 cc.defineGetterSetter(_p, "body", _p.getBody, _p.setBody);
702 /** @expose */
703 _p.colliderFilter;
704 cc.defineGetterSetter(_p, "colliderFilter", null, _p.setColliderFilter);
705 
706 delete window._p;
707 
708 /**
709  * allocates and initializes a armature.
710  * @param {String} name
711  * @param {ccs.Bone} parentBone
712  * @return {ccs.Armature}
713  * @example
714  * // example
715  * var armature = ccs.Armature.create();
716  */
717 ccs.Armature.create = function (name, parentBone) {
718     var armature = new ccs.Armature();
719     if (armature && armature.init(name, parentBone)) {
720         return armature;
721     }
722     return null;
723 };
724