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