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