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.Bone objects.
 27  * @class
 28  * @extends ccs.NodeRGBA
 29  *
 30  * @property {ccs.BoneData}         boneData                - The bone data
 31  * @property {ccs.Armature}         armature                - The armature
 32  * @property {ccs.Bone}             parentBone              - The parent bone
 33  * @property {ccs.Armature}         childArmature           - The child armature
 34  * @property {Array}                childrenBone            - <@readonly> All children bones
 35  * @property {ccs.Tween}            tween                   - <@readonly> Tween
 36  * @property {ccs.FrameData}        tweenData               - <@readonly> The tween data
 37  * @property {Boolean}              transformDirty          - Indicate whether the transform is dirty
 38  * @property {ccs.ColliderFilter}   colliderFilter          - The collider filter
 39  * @property {ccs.DisplayManager}   displayManager          - The displayManager
 40  * @property {Boolean}              ignoreMovementBoneData  - Indicate whether force the bone to show When CCArmature play a animation and there isn't a CCMovementBoneData of this bone in this CCMovementData.
 41  * @property {String}               name                    - The name of the bone
 42  * @property {Boolean}              blendDirty              - Indicate whether the blend is dirty
 43  *
 44  */
 45 ccs.Bone = ccs.NodeRGBA.extend(/** @lends ccs.Bone# */{
 46     _boneData:null,
 47     _armature:null,
 48     _childArmature:null,
 49     displayManager:null,
 50     ignoreMovementBoneData:false,
 51     _tween:null,
 52     _tweenData:null,
 53     name:"",
 54     _childrenBone:null,
 55     parentBone:null,
 56     boneTransformDirty:false,
 57     _worldTransform:null,
 58     _blendFunc:0,
 59     blendDirty:false,
 60     _worldInfo:null,
 61     _armatureParentBone:null,
 62     _dataVersion:0,
 63     _className:"Bone",
 64     ctor:function () {
 65         cc.NodeRGBA.prototype.ctor.call(this);
 66         this._boneData = null;
 67         this._armature = null;
 68         this._childArmature = null;
 69         this.displayManager = null;
 70         this.ignoreMovementBoneData = false;
 71         this._tween = null;
 72         this._tweenData = null;
 73         this.name = "";
 74         this._childrenBone = [];
 75         this.parentBone = null;
 76         this.boneTransformDirty = true;
 77         this._worldTransform = cc.AffineTransformMake(1, 0, 0, 1, 0, 0);
 78         this._blendFunc = new cc.BlendFunc(cc.BLEND_SRC, cc.BLEND_DST);
 79         this.blendDirty = false;
 80     },
 81 
 82     /**
 83      * release objects
 84      */
 85     release:function () {
 86         CC_SAFE_RELEASE(this._tweenData);
 87         for (var i = 0; i < this._childrenBone.length; i++) {
 88             CC_SAFE_RELEASE(this._childrenBone[i]);
 89         }
 90         this._childrenBone = [];
 91         CC_SAFE_RELEASE(this._tween);
 92         CC_SAFE_RELEASE(this.displayManager);
 93         CC_SAFE_RELEASE(this._boneData);
 94         CC_SAFE_RELEASE(this._childArmature);
 95     },
 96 
 97     /**
 98      * Initializes a CCBone with the specified name
 99      * @param {String} name
100      * @return {Boolean}
101      */
102     init:function (name) {
103         cc.NodeRGBA.prototype.init.call(this);
104         if (name) {
105             this.name = name;
106         }
107         this._tweenData = new ccs.FrameData();
108         this._tween = new ccs.Tween();
109         this._tween.init(this);
110         this.displayManager = new ccs.DisplayManager();
111         this.displayManager.init(this);
112         this._worldInfo = new ccs.BaseData();
113         this._boneData = new ccs.BaseData();
114         return true;
115     },
116 
117     /**
118      * set the boneData
119      * @param {ccs.BoneData} boneData
120      */
121     setBoneData:function (boneData) {
122         if (!boneData) {
123             cc.log("boneData must not be null");
124             return;
125         }
126         this._boneData = boneData;
127         this.name = this._boneData.name;
128         this.setLocalZOrder(this._boneData.zOrder);
129         this.displayManager.initDisplayList(boneData);
130     },
131 
132     /**
133      * boneData getter
134      * @return {ccs.BoneData}
135      */
136     getBoneData:function () {
137         return this._boneData;
138     },
139 
140     /**
141      * set the armature
142      * @param {ccs.Armature} armature
143      */
144     setArmature:function (armature) {
145         this._armature = armature;
146         if(armature){
147             this._tween.setAnimation(this._armature.getAnimation());
148             this._dataVersion = this._armature.getArmatureData().dataVersion;
149             this._armatureParentBone = this._armature.getParentBone();
150         }else{
151             this._armatureParentBone = null;
152         }
153     },
154 
155     /**
156      * armature getter
157      * @return {ccs.Armature}
158      */
159     getArmature:function () {
160         return this._armature;
161     },
162 
163     /**
164      * update worldTransform
165      * @param dt
166      */
167     update:function (dt) {
168         var locParentBone = this.parentBone;
169         var locArmature = this._armature;
170         var locTweenData = this._tweenData;
171         var locWorldTransform = this._worldTransform;
172         var locWorldInfo = this._worldInfo;
173         var locArmatureParentBone = this._armatureParentBone;
174 
175         if (locParentBone) {
176             this.boneTransformDirty = this.boneTransformDirty || locParentBone.isTransformDirty();
177         }
178         if (locArmatureParentBone && !this.boneTransformDirty){
179             this.boneTransformDirty = locArmatureParentBone.isTransformDirty();
180         }
181         if (this.boneTransformDirty) {
182             if (this._dataVersion >= ccs.CONST_VERSION_COMBINED) {
183                 var locBoneData = this._boneData;
184                 locTweenData.x += locBoneData.x;
185                 locTweenData.y += locBoneData.y;
186                 locTweenData.skewX += locBoneData.skewX;
187                 locTweenData.skewY += locBoneData.skewY;
188                 locTweenData.scaleX += locBoneData.scaleX;
189                 locTweenData.scaleY += locBoneData.scaleY;
190 
191                 locTweenData.scaleX -= 1;
192                 locTweenData.scaleY -= 1;
193             }
194 
195             locWorldInfo.x = locTweenData.x + this._position.x;
196             locWorldInfo.y = locTweenData.y + this._position.y;
197             locWorldInfo.scaleX = locTweenData.scaleX * this._scaleX;
198             locWorldInfo.scaleY = locTweenData.scaleY * this._scaleY;
199             locWorldInfo.skewX = locTweenData.skewX + this._skewX + this._rotationX;
200             locWorldInfo.skewY = locTweenData.skewY + this._skewY - this._rotationY;
201 
202             if (this.parentBone) {
203                 this.applyParentTransform(this.parentBone);
204             }
205             else {
206                 if (locArmatureParentBone) {
207                     this.applyParentTransform(locArmatureParentBone);
208                 }
209             }
210 
211             ccs.TransformHelp.nodeToMatrix(locWorldInfo, locWorldTransform);
212 
213             if (locArmatureParentBone) {
214                 this._worldTransform = cc.AffineTransformConcat(locWorldTransform, locArmature.nodeToParentTransform());
215             }
216         }
217         ccs.DisplayFactory.updateDisplay(this, dt, this.boneTransformDirty || locArmature.getArmatureTransformDirty());
218 
219         var locChildrenBone = this._childrenBone;
220         for (var i = 0; i < locChildrenBone.length; i++) {
221             locChildrenBone[i].update(dt);
222         }
223         this.boneTransformDirty = false;
224     },
225 
226     applyParentTransform: function (parent) {
227         var locWorldInfo = this._worldInfo;
228         var locParentWorldTransform = parent._worldTransform;
229         var locParentWorldInfo = parent._worldInfo;
230         var x = locWorldInfo.x;
231         var y = locWorldInfo.y;
232         locWorldInfo.x = x * locParentWorldTransform.a + y * locParentWorldTransform.c + locParentWorldInfo.x;
233         locWorldInfo.y = x * locParentWorldTransform.b + y * locParentWorldTransform.d + locParentWorldInfo.y;
234         locWorldInfo.scaleX = locWorldInfo.scaleX * locParentWorldInfo.scaleX;
235         locWorldInfo.scaleY = locWorldInfo.scaleY * locParentWorldInfo.scaleY;
236         locWorldInfo.skewX = locWorldInfo.skewX + locParentWorldInfo.skewX;
237         locWorldInfo.skewY = locWorldInfo.skewY + locParentWorldInfo.skewY;
238     },
239 
240     /**
241      * Rewrite visit ,when node draw, g_NumberOfDraws is changeless
242      */
243     visit:function (ctx) {
244         var node = this.getDisplayManager().getDisplayRenderNode();
245         if (node) {
246             node.visit(ctx);
247         }
248     },
249 
250     /**
251      * update display color
252      * @param {cc.Color} color
253      */
254     updateDisplayedColor:function (color) {
255         this._realColor = cc.color(255,255,255);
256         cc.NodeRGBA.prototype.updateDisplayedColor.call(this, color);
257         this.updateColor();
258     },
259 
260     /**
261      * update display opacity
262      * @param {Number} opacity
263      */
264     updateDisplayedOpacity:function (opacity) {
265         this._realOpacity = 255;
266         cc.NodeRGBA.prototype.updateDisplayedOpacity.call(this, opacity);
267         this.updateColor();
268     },
269 
270     /**
271      * set display color
272      * @param {cc.Color} color
273      */
274     setColor: function (color) {
275         cc.NodeRGBA.prototype.setColor.call(this, color);
276         this.updateColor();
277     },
278 
279     /**
280      * set display opacity
281      * @param {Number} opacity  0-255
282      */
283     setOpacity: function (opacity) {
284         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
285         this.updateColor();
286     },
287 
288     /**
289      * update display color
290      */
291     updateColor:function () {
292         var display = this.displayManager.getDisplayRenderNode();
293         if (display && display.RGBAProtocol) {
294             var locDisplayedColor = this._displayedColor;
295             var locTweenData = this._tweenData;
296             var locOpacity = this._displayedOpacity * locTweenData.a / 255;
297             var locColor = cc.color(locDisplayedColor.r * locTweenData.r / 255, locDisplayedColor.g * locTweenData.g / 255, locDisplayedColor.b * locTweenData.b / 255);
298             display.setOpacity(locOpacity);
299             display.setColor(locColor);
300         }
301     },
302 
303     /**
304      * update display zOrder
305      */
306     updateZOrder: function () {
307         if (this._armature.getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) {
308             var zorder = this._tweenData.zOrder + this._boneData.zOrder;
309             this.setLocalZOrder(zorder);
310         }
311         else {
312             this.setLocalZOrder(this._tweenData.zOrder);
313         }
314     },
315 
316     /**
317      * Add a child to this bone, and it will let this child call setParent(ccs.Bone) function to set self to it's parent
318      * @param {ccs.Bone} child
319      */
320     addChildBone:function (child) {
321         if (!child) {
322             cc.log("Argument must be non-nil");
323             return;
324         }
325         if (child.parentBone) {
326             cc.log("child already added. It can't be added again");
327             return;
328         }
329         if (this._childrenBone.indexOf(child) < 0) {
330             this._childrenBone.push(child);
331             child.setParentBone(this);
332         }
333     },
334 
335     /**
336      * Removes a child bone
337      * @param {ccs.Bone} bone
338      * @param {Boolean} recursion
339      */
340     removeChildBone:function (bone, recursion) {
341         for (var i = 0; i < this._childrenBone.length; i++) {
342             if (this._childrenBone[i] == bone) {
343                 if (recursion) {
344                     var ccbones = bone._childrenBone;
345                     for (var j = 0; j < ccbones.length; j++) {
346                         bone.removeChildBone(ccbones[j], recursion);
347                     }
348                 }
349                 bone.setParentBone(null);
350                 bone.displayManager.setCurrentDecorativeDisplay(null);
351                 cc.arrayRemoveObject(this._childrenBone, bone);
352             }
353         }
354     },
355 
356     /**
357      * Remove itself from its parent CCBone.
358      * @param {Boolean} recursion
359      */
360     removeFromParent:function (recursion) {
361         if (this.parentBone) {
362             this.parentBone.removeChildBone(this, recursion);
363         }
364     },
365 
366     /**
367      * Set parent bone.
368      * If _parent is NUll, then also remove this bone from armature.
369      * It will not set the CCArmature, if you want to add the bone to a CCArmature, you should use ccs.Armature.addBone(bone, parentName).
370      * @param {ccs.Bone}  parent  the parent bone.
371      */
372     setParentBone:function (parent) {
373         this.parentBone = parent;
374     },
375 
376     /**
377      * parent bone getter
378      * @return {ccs.Bone}
379      */
380     getParentBone:function () {
381         return this.parentBone;
382     },
383 
384     /**
385      * child armature setter
386      * @param {ccs.Armature} armature
387      */
388     setChildArmature:function (armature) {
389         if (this._childArmature != armature) {
390             if (armature == null && this._childArmature)            {
391                 this._childArmature.setParentBone(null);
392             }
393             this._childArmature = armature;
394         }
395     },
396 
397     /**
398      * child armature getter
399      * @return {ccs.Armature}
400      */
401     getChildArmature:function () {
402         return this._childArmature;
403     },
404 
405     /**
406      * child bone getter
407      * @return {Array}
408      */
409     getChildrenBone:function () {
410         return this._childrenBone;
411     },
412 
413     /**
414      * tween getter
415      * @return {ccs.Tween}
416      */
417     getTween:function () {
418         return this._tween;
419     },
420 
421     /**
422      * zOrder setter
423      * @param {Number}
424         */
425     setLocalZOrder:function (zOrder) {
426         if (this._zOrder != zOrder)
427             cc.Node.prototype.setLocalZOrder.call(this, zOrder);
428     },
429 
430     /**
431      * transform dirty setter
432      * @param {Boolean}
433         */
434     setTransformDirty:function (dirty) {
435         this.boneTransformDirty = dirty;
436     },
437 
438     /**
439      * transform dirty getter
440      * @return {Boolean}
441      */
442     isTransformDirty:function () {
443         return this.boneTransformDirty;
444     },
445 
446     /**
447      * return world transform
448      * @return {{a:0.b:0,c:0,d:0,tx:0,ty:0}}
449      */
450     nodeToArmatureTransform:function () {
451         return this._worldTransform;
452     },
453 
454     /**
455      * Returns the world affine transform matrix. The matrix is in Pixels.
456      * @returns {cc.AffineTransform}
457      */
458     nodeToWorldTransform: function () {
459         return cc.AffineTransformConcat(this._worldTransform, this._armature.nodeToWorldTransform());
460     },
461 
462     /**
463      * get render node
464      * @returns {cc.Node}
465      */
466     getDisplayRenderNode: function () {
467         return this.displayManager.getDisplayRenderNode();
468     },
469 
470     /**
471      * get render node type
472      * @returns {Number}
473      */
474     getDisplayRenderNodeType: function () {
475         return this.displayManager.getDisplayRenderNodeType();
476     },
477 
478     /**
479      * Add display and use  _displayData init the display.
480      * If index already have a display, then replace it.
481      * If index is current display index, then also change display to _index
482      * @param {cc.Display} displayData it include the display information, like DisplayType.
483      *          If you want to create a sprite display, then create a CCSpriteDisplayData param
484      *@param {Number}    index the index of the display you want to replace or add to
485      *          -1 : append display from back
486      */
487     addDisplay:function (displayData, index) {
488         index = index || 0;
489         return this.displayManager.addDisplay(displayData, index);
490     },
491 
492     /**
493      * remove display
494      * @param {Number} index
495      */
496     removeDisplay: function (index) {
497         this.displayManager.removeDisplay(index);
498     },
499 
500     addSkin:function (skin, index) {
501         index = index||0;
502         return this.displayManager.addSkin(skin, index);
503     },
504 
505     /**
506      * change display by index
507      * @param {Number} index
508      * @param {Boolean} force
509      */
510     changeDisplayByIndex:function (index, force) {
511         cc.log("changeDisplayByIndex is deprecated. Use changeDisplayWithIndex instead.");
512         this.changeDisplayWithIndex(index, force);
513     },
514 
515     /**
516      * change display with index
517      * @param {Number} index
518      * @param {Boolean} force
519      */
520     changeDisplayWithIndex:function (index, force) {
521         this.displayManager.changeDisplayWithIndex(index, force);
522     },
523 
524     /**
525      * change display with name
526      * @param {String} name
527      * @param {Boolean} force
528      */
529     changeDisplayWithName:function (name, force) {
530         this.displayManager.changeDisplayWithName(name, force);
531     },
532 
533     /**
534      * get the collider body list in this bone.
535      * @returns {*}
536      */
537     getColliderBodyList: function () {
538         var decoDisplay = this.displayManager.getCurrentDecorativeDisplay()
539         if (decoDisplay) {
540             var detector = decoDisplay.getColliderDetector()
541             if (detector) {
542                 return detector.getColliderBodyList();
543             }
544         }
545         return [];
546     },
547 
548     /**
549      * collider filter setter
550      * @param {cc.ColliderFilter} filter
551      */
552     setColliderFilter: function (filter) {
553         var displayList = this.displayManager.getDecorativeDisplayList();
554         for (var i = 0; i < displayList.length; i++) {
555             var locDecoDisplay = displayList[i];
556             var locDetector = locDecoDisplay.getColliderDetector();
557             if (locDetector) {
558                 locDetector.setColliderFilter(filter);
559             }
560         }
561 
562     },
563 
564     /**
565      * collider filter getter
566      * @returns {cc.ColliderFilter}
567      */
568     getColliderFilter: function () {
569         var decoDisplay = this.displayManager.getCurrentDecorativeDisplay();
570         if (decoDisplay) {
571             var detector = decoDisplay.getColliderDetector();
572             if (detector) {
573                 return detector.getColliderFilter();
574             }
575         }
576         return null;
577     },
578 
579     /**
580      * displayManager setter
581      * @param {ccs.DisplayManager}
582         */
583     setDisplayManager:function (displayManager) {
584         this.displayManager = displayManager;
585     },
586 
587     /**
588      * displayManager dirty getter
589      * @return {ccs.DisplayManager}
590      */
591     getDisplayManager:function () {
592         return this.displayManager;
593     },
594 
595     /**
596      *    When CCArmature play a animation, if there is not a CCMovementBoneData of this bone in this CCMovementData, this bone will hide.
597      *    Set IgnoreMovementBoneData to true, then this bone will also show.
598      * @param {Boolean} bool
599      */
600     setIgnoreMovementBoneData:function (bool) {
601         this.ignoreMovementBoneData = bool;
602     },
603 
604     /**
605      * ignoreMovementBoneData  getter
606      * @return {Boolean}
607      */
608     getIgnoreMovementBoneData:function () {
609         return this.ignoreMovementBoneData;
610     },
611 
612     /**
613      * tweenData  getter
614      * @return {ccs.FrameData}
615      */
616     getTweenData:function () {
617         return this._tweenData;
618     },
619 
620     /**
621      * name  setter
622      * @param {String} name
623      */
624     setName:function (name) {
625         this.name = name;
626     },
627 
628     /**
629      * name  getter
630      * @return {String}
631      */
632     getName:function () {
633         return this.name;
634     },
635 
636     /**
637      * BlendFunc  setter
638      * @param {cc.BlendFunc} blendFunc
639      */
640     setBlendFunc:function (blendFunc) {
641         if (this._blendFunc.src != blendFunc.src || this._blendFunc.dst != blendFunc.dst)        {
642             this._blendFunc = blendFunc;
643             this.blendDirty = true;
644         }
645     },
646 
647     /**
648      * blendType  getter
649      * @return {cc.BlendFunc}
650      */
651     getBlendFunc:function () {
652         return this._blendFunc;
653     },
654 
655     setBlendDirty:function(dirty){
656         this.blendDirty = dirty;
657     },
658 
659     isBlendDirty:function(){
660         return this.blendDirty;
661     }
662 });
663 
664 window._p = ccs.Bone.prototype;
665 
666 // Extended properties
667 /** @expose */
668 _p.boneData;
669 cc.defineGetterSetter(_p, "boneData", _p.getBoneData, _p.setBoneData);
670 /** @expose */
671 _p.armature;
672 cc.defineGetterSetter(_p, "armature", _p.getArmature, _p.setArmature);
673 /** @expose */
674 _p.childArmature;
675 cc.defineGetterSetter(_p, "childArmature", _p.getChildArmature, _p.setChildArmature);
676 /** @expose */
677 _p.childrenBone;
678 cc.defineGetterSetter(_p, "childrenBone", _p.getChildrenBone);
679 /** @expose */
680 _p.tween;
681 cc.defineGetterSetter(_p, "tween", _p.getTween);
682 /** @expose */
683 _p.tweenData;
684 cc.defineGetterSetter(_p, "tweenData", _p.getTweenData);
685 /** @expose */
686 _p.colliderFilter;
687 cc.defineGetterSetter(_p, "colliderFilter", _p.getColliderFilter, _p.setColliderFilter);
688 
689 delete window._p;
690 
691 /**
692  * allocates and initializes a bone.
693  * @constructs
694  * @return {ccs.Bone}
695  * @example
696  * // example
697  * var bone = ccs.Bone.create();
698  */
699 ccs.Bone.create = function (name) {
700     var bone = new ccs.Bone();
701     if (bone && bone.init(name)) {
702         return bone;
703     }
704     return null;
705 };