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     },
234 
235     /**
236      * update display opacity
237      * @param {Number} opacity
238      */
239     updateDisplayedOpacity: function (opacity) {
240         this._realOpacity = 255;
241         cc.Node.prototype.updateDisplayedOpacity.call(this, opacity);
242     },
243 
244     /**
245      * update display color
246      */
247     updateColor: function () {
248         var display = this._displayManager.getDisplayRenderNode();
249         if (display != null) {
250             display.setColor(
251                 cc.color(
252                         this._displayedColor.r * this._tweenData.r / 255,
253                         this._displayedColor.g * this._tweenData.g / 255,
254                         this._displayedColor.b * this._tweenData.b / 255));
255             display.setOpacity(this._displayedOpacity * this._tweenData.a / 255);
256         }
257     },
258 
259     /**
260      * update display zOrder
261      */
262     updateZOrder: function () {
263         if (this._armature.getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) {
264             var zorder = this._tweenData.zOrder + this._boneData.zOrder;
265             this.setLocalZOrder(zorder);
266         } else {
267             this.setLocalZOrder(this._tweenData.zOrder);
268         }
269     },
270 
271     /**
272      * Add a child to this bone, and it will let this child call setParent(ccs.Bone) function to set self to it's parent
273      * @param {ccs.Bone} child
274      */
275     addChildBone: function (child) {
276         cc.assert(child, "Argument must be non-nil");
277         cc.assert(!child.parentBone, "child already added. It can't be added again");
278 
279         if (this._children.indexOf(child) < 0) {
280             this._children.push(child);
281             child.setParentBone(this);
282         }
283     },
284 
285     /**
286      * Removes a child bone
287      * @param {ccs.Bone} bone
288      * @param {Boolean} recursion
289      */
290     removeChildBone: function (bone, recursion) {
291         if (this._children.length > 0 && this._children.getIndex(bone) != -1 ) {
292             if(recursion) {
293                 var ccbones = bone._children;
294                 for(var i=0; i<ccbones.length; i++){
295                     var ccBone = ccbones[i];
296                     bone.removeChildBone(ccBone, recursion);
297                 }
298             }
299 
300             bone.setParentBone(null);
301             bone.getDisplayManager().setCurrentDecorativeDisplay(null);
302             cc.arrayRemoveObject(this._children, bone);
303         }
304     },
305 
306     /**
307      * Remove itself from its parent CCBone.
308      * @param {Boolean} recursion
309      */
310     removeFromParent: function (recursion) {
311         if (this._parentBone) {
312             this._parentBone.removeChildBone(this, recursion);
313         }
314     },
315 
316     /**
317      * Set parent bone.
318      * If _parent is NUll, then also remove this bone from armature.
319      * It will not set the CCArmature, if you want to add the bone to a CCArmature, you should use ccs.Armature.addBone(bone, parentName).
320      * @param {ccs.Bone}  parent  the parent bone.
321      */
322     setParentBone: function (parent) {
323         this._parentBone = parent;
324     },
325 
326     getParentBone: function(){
327         return this._parentBone;
328     },
329 
330     /**
331      * child armature setter
332      * @param {ccs.Armature} armature
333      */
334     setChildArmature: function (armature) {
335         if (this._childArmature != armature) {
336             if (armature == null && this._childArmature)
337                 this._childArmature.setParentBone(null);
338             this._childArmature = armature;
339         }
340     },
341 
342     /**
343      * child armature getter
344      * @return {ccs.Armature}
345      */
346     getChildArmature: function () {
347         return this._childArmature;
348     },
349 
350     /**
351      * tween getter
352      * @return {ccs.Tween}
353      */
354     getTween: function () {
355         return this._tween;
356     },
357 
358     /**
359      * zOrder setter
360      * @param {Number} zOrder
361      */
362     setLocalZOrder: function (zOrder) {
363         if (this._localZOrder != zOrder)
364             cc.Node.prototype.setLocalZOrder.call(this, zOrder);
365     },
366 
367     getNodeToArmatureTransform: function(){
368         return this._worldTransform;
369     },
370 
371     getNodeToWorldTransform: function(){
372         return cc.affineTransformConcat(this._worldTransform, this._armature.getNodeToWorldTransform());
373     },
374 
375     /**
376      * get render node
377      * @returns {cc.Node}
378      */
379     getDisplayRenderNode: function () {
380         return this._displayManager.getDisplayRenderNode();
381     },
382 
383     /**
384      * get render node type
385      * @returns {Number}
386      */
387     getDisplayRenderNodeType: function () {
388         return this._displayManager.getDisplayRenderNodeType();
389     },
390 
391     /**
392      * Add display and use  _displayData init the display.
393      * If index already have a display, then replace it.
394      * If index is current display index, then also change display to _index
395      * @param {ccs.DisplayData} displayData it include the display information, like DisplayType.
396      *          If you want to create a sprite display, then create a CCSpriteDisplayData param
397      *@param {Number}    index the index of the display you want to replace or add to
398      *          -1 : append display from back
399      */
400     addDisplay: function (displayData, index) {
401         index = index || 0;
402         return this._displayManager.addDisplay(displayData, index);
403     },
404 
405     /**
406      * remove display
407      * @param {Number} index
408      */
409     removeDisplay: function (index) {
410         this._displayManager.removeDisplay(index);
411     },
412 
413     /**
414      * change display by index
415      * @param {Number} index
416      * @param {Boolean} force
417      */
418     changeDisplayByIndex: function (index, force) {
419         cc.log("changeDisplayByIndex is deprecated. Use changeDisplayWithIndex instead.");
420         this.changeDisplayWithIndex(index, force);
421     },
422 
423     changeDisplayByName: function(name, force){
424         this.changeDisplayWithName(name, force);
425     },
426 
427     /**
428      * change display with index
429      * @param {Number} index
430      * @param {Boolean} force
431      */
432     changeDisplayWithIndex: function (index, force) {
433         this._displayManager.changeDisplayWithIndex(index, force);
434     },
435 
436     /**
437      * change display with name
438      * @param {String} name
439      * @param {Boolean} force
440      */
441     changeDisplayWithName: function (name, force) {
442         this._displayManager.changeDisplayWithName(name, force);
443     },
444 
445     getColliderDetector: function(){
446         var decoDisplay = this._displayManager.getCurrentDecorativeDisplay();
447         if (decoDisplay){
448             var detector = decoDisplay.getColliderDetector();
449             if (detector)
450                 return detector;
451         }
452         return null;
453     },
454 
455     /**
456      * collider filter setter
457      * @param {cc.ColliderFilter} filter
458      */
459     setColliderFilter: function (filter) {
460         var displayList = this._displayManager.getDecorativeDisplayList();
461         for (var i = 0; i < displayList.length; i++) {
462             var locDecoDisplay = displayList[i];
463             var locDetector = locDecoDisplay.getColliderDetector();
464             if (locDetector) {
465                 locDetector.setColliderFilter(filter);
466             }
467         }
468     },
469 
470     /**
471      * collider filter getter
472      * @returns {cc.ColliderFilter}
473      */
474     getColliderFilter: function () {
475         var decoDisplay = this.displayManager.getCurrentDecorativeDisplay();
476         if (decoDisplay) {
477             var detector = decoDisplay.getColliderDetector();
478             if (detector)
479                 return detector.getColliderFilter();
480         }
481         return null;
482     },
483 
484     /**
485      * transform dirty setter
486      * @param {Boolean} dirty
487      */
488     setTransformDirty: function (dirty) {
489         this._boneTransformDirty = dirty;
490     },
491 
492     /**
493      * transform dirty getter
494      * @return {Boolean}
495      */
496     isTransformDirty: function () {
497         return this._boneTransformDirty;
498     },
499 
500     /**
501      * displayManager dirty getter
502      * @return {ccs.DisplayManager}
503      */
504     getDisplayManager: function () {
505         return this._displayManager;
506     },
507 
508     /**
509      *    When CCArmature play a animation, if there is not a CCMovementBoneData of this bone in this CCMovementData, this bone will hide.
510      *    Set IgnoreMovementBoneData to true, then this bone will also show.
511      * @param {Boolean} bool
512      */
513     setIgnoreMovementBoneData: function (bool) {
514         this._ignoreMovementBoneData = bool;
515     },
516 
517     isIgnoreMovementBoneData: function(){
518         return this._ignoreMovementBoneData;
519     },
520 
521     /**
522      * blendType  getter
523      * @return {cc.BlendFunc}
524      */
525     getBlendFunc: function () {
526         return this._blendFunc;
527     },
528 
529     setBlendDirty: function (dirty) {
530         this._blendDirty = dirty;
531     },
532 
533     isBlendDirty: function () {
534         return this._blendDirty;
535     },
536 
537     /**
538      * tweenData  getter
539      * @return {ccs.FrameData}
540      */
541     getTweenData: function () {
542         return this._tweenData;
543     },
544 
545     getWorldInfo: function(){
546         return this._worldInfo;
547     },
548 
549     /**
550      * child bone getter
551      * @return {Array}
552      * @deprecated
553      */
554     getChildrenBone: function () {
555         return this._children;
556     },
557 
558     /**
559      * @deprecated
560      * return world transform
561      * @return {{a:0.b:0,c:0,d:0,tx:0,ty:0}}
562      */
563     nodeToArmatureTransform: function () {
564         return this.getNodeToArmatureTransform();
565     },
566 
567     /**
568      * @deprecated
569      * Returns the world affine transform matrix. The matrix is in Pixels.
570      * @returns {cc.AffineTransform}
571      */
572     nodeToWorldTransform: function () {
573         return this.getNodeToWorldTransform();
574     },
575 
576     /**
577      * @deprecated
578      * get the collider body list in this bone.
579      * @returns {*}
580      */
581     getColliderBodyList: function () {
582         var detector = this.getColliderDetector();
583         if(detector)
584             return detector.getColliderBodyList();
585         return null;
586     },
587 
588     /**
589      * ignoreMovementBoneData  getter
590      * @return {Boolean}
591      */
592     getIgnoreMovementBoneData: function () {
593         return this.isIgnoreMovementBoneData();
594     }
595 });
596 
597 var _p = ccs.Bone.prototype;
598 
599 // Extended properties
600 /** @expose */
601 _p.boneData;
602 cc.defineGetterSetter(_p, "boneData", _p.getBoneData, _p.setBoneData);
603 /** @expose */
604 _p.armature;
605 cc.defineGetterSetter(_p, "armature", _p.getArmature, _p.setArmature);
606 /** @expose */
607 _p.childArmature;
608 cc.defineGetterSetter(_p, "childArmature", _p.getChildArmature, _p.setChildArmature);
609 /** @expose */
610 _p.childrenBone;
611 cc.defineGetterSetter(_p, "childrenBone", _p.getChildrenBone);
612 /** @expose */
613 _p.tween;
614 cc.defineGetterSetter(_p, "tween", _p.getTween);
615 /** @expose */
616 _p.tweenData;
617 cc.defineGetterSetter(_p, "tweenData", _p.getTweenData);
618 /** @expose */
619 _p.colliderFilter;
620 cc.defineGetterSetter(_p, "colliderFilter", _p.getColliderFilter, _p.setColliderFilter);
621 
622 _p = null;
623 
624 /**
625  * allocates and initializes a bone.
626  * @constructs
627  * @return {ccs.Bone}
628  * @example
629  * // example
630  * var bone = ccs.Bone.create();
631  */
632 ccs.Bone.create = function (name) {
633     var bone = new ccs.Bone();
634     if (bone && bone.init(name))
635         return bone;
636     return null;
637 };