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