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