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