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