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