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 main class of Armature, it plays armature animation, manages and updates bones' state. 28 * @class 29 * @extends ccs.Node 30 * 31 * @property {ccs.Bone} parentBone - The parent bone of the armature node 32 * @property {ccs.ArmatureAnimation} animation - The animation 33 * @property {ccs.ArmatureData} armatureData - The armature data 34 * @property {String} name - The name of the armature 35 * @property {cc.SpriteBatchNode} batchNode - The batch node of the armature 36 * @property {Number} version - The version 37 * @property {Object} body - The body of the armature 38 * @property {ccs.ColliderFilter} colliderFilter - <@writeonly> The collider filter of the armature 39 */ 40 ccs.Armature = ccs.Node.extend(/** @lends ccs.Armature# */{ 41 animation: null, 42 armatureData: null, 43 batchNode: null, 44 _textureAtlas: null, 45 _parentBone: null, 46 _boneDic: null, 47 _topBoneList: null, 48 _armatureIndexDic: null, 49 _offsetPoint: null, 50 version: 0, 51 _armatureTransformDirty: true, 52 _body: null, 53 _blendFunc: null, 54 _className: "Armature", 55 _realAnchorPointInPoints: null, 56 57 /** 58 * Create a armature node. 59 * Constructor of ccs.Armature 60 * @param {String} name 61 * @param {ccs.Bone} parentBone 62 * @example 63 * var armature = new ccs.Armature(); 64 */ 65 ctor: function (name, parentBone) { 66 cc.Node.prototype.ctor.call(this); 67 this._name = ""; 68 this._topBoneList = []; 69 this._armatureIndexDic = {}; 70 this._offsetPoint = cc.p(0, 0); 71 this._armatureTransformDirty = true; 72 this._realAnchorPointInPoints = cc.p(0, 0); 73 name && ccs.Armature.prototype.init.call(this, name, parentBone); 74 }, 75 76 /** 77 * Initializes a CCArmature with the specified name and CCBone 78 * @param {String} [name] 79 * @param {ccs.Bone} [parentBone] 80 * @return {Boolean} 81 */ 82 init: function (name, parentBone) { 83 cc.Node.prototype.init.call(this); 84 if (parentBone) 85 this._parentBone = parentBone; 86 this.removeAllChildren(); 87 this.animation = new ccs.ArmatureAnimation(); 88 this.animation.init(this); 89 90 this._boneDic = {}; 91 this._topBoneList.length = 0; 92 93 this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST}; 94 this._name = name || ""; 95 var armatureDataManager = ccs.armatureDataManager; 96 97 var animationData; 98 if (name != "") { 99 //animationData 100 animationData = armatureDataManager.getAnimationData(name); 101 cc.assert(animationData, "AnimationData not exist!"); 102 103 this.animation.setAnimationData(animationData); 104 105 //armatureData 106 var armatureData = armatureDataManager.getArmatureData(name); 107 cc.assert(armatureData, "ArmatureData not exist!"); 108 109 this.armatureData = armatureData; 110 111 //boneDataDic 112 var boneDataDic = armatureData.getBoneDataDic(); 113 for (var key in boneDataDic) { 114 var bone = this.createBone(String(key)); 115 116 //! init bone's Tween to 1st movement's 1st frame 117 do { 118 var movData = animationData.getMovement(animationData.movementNames[0]); 119 if (!movData) break; 120 121 var _movBoneData = movData.getMovementBoneData(bone.getName()); 122 if (!_movBoneData || _movBoneData.frameList.length <= 0) break; 123 124 var frameData = _movBoneData.getFrameData(0); 125 if (!frameData) break; 126 127 bone.getTweenData().copy(frameData); 128 bone.changeDisplayWithIndex(frameData.displayIndex, false); 129 } while (0); 130 } 131 132 this.update(0); 133 this.updateOffsetPoint(); 134 } else { 135 this._name = "new_armature"; 136 this.armatureData = ccs.ArmatureData.create(); 137 this.armatureData.name = this._name; 138 139 animationData = ccs.AnimationData.create(); 140 animationData.name = this._name; 141 142 armatureDataManager.addArmatureData(this._name, this.armatureData); 143 armatureDataManager.addAnimationData(this._name, animationData); 144 145 this.animation.setAnimationData(animationData); 146 } 147 if (cc._renderType === cc._RENDER_TYPE_WEBGL) 148 this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR)); 149 150 this.setCascadeOpacityEnabled(true); 151 this.setCascadeColorEnabled(true); 152 return true; 153 }, 154 155 /** 156 * create a bone with name 157 * @param {String} boneName 158 * @return {ccs.Bone} 159 */ 160 createBone: function (boneName) { 161 var existedBone = this.getBone(boneName); 162 if (existedBone) 163 return existedBone; 164 165 var boneData = this.armatureData.getBoneData(boneName); 166 var parentName = boneData.parentName; 167 168 var bone = null; 169 if (parentName) { 170 this.createBone(parentName); 171 bone = ccs.Bone.create(boneName); 172 this.addBone(bone, parentName); 173 } else { 174 bone = ccs.Bone.create(boneName); 175 this.addBone(bone, ""); 176 } 177 178 bone.setBoneData(boneData); 179 bone.getDisplayManager().changeDisplayWithIndex(-1, false); 180 return bone; 181 }, 182 183 /** 184 * Add a Bone to this Armature 185 * @param {ccs.Bone} bone The Bone you want to add to Armature 186 * @param {String} parentName The parent Bone's name you want to add to. If it's null, then set Armature to its parent 187 */ 188 addBone: function (bone, parentName) { 189 cc.assert(bone, "Argument must be non-nil"); 190 var locBoneDic = this._boneDic; 191 if(bone.getName()) 192 cc.assert(!locBoneDic[bone.getName()], "bone already added. It can't be added again"); 193 194 if (parentName) { 195 var boneParent = locBoneDic[parentName]; 196 if (boneParent) 197 boneParent.addChildBone(bone); 198 else 199 this._topBoneList.push(bone); 200 } else 201 this._topBoneList.push(bone); 202 bone.setArmature(this); 203 204 locBoneDic[bone.getName()] = bone; 205 this.addChild(bone); 206 }, 207 208 /** 209 * Remove a bone with the specified name. If recursion it will also remove child Bone recursively. 210 * @param {ccs.Bone} bone The bone you want to remove 211 * @param {Boolean} recursion Determine whether remove the bone's child recursion. 212 */ 213 removeBone: function (bone, recursion) { 214 cc.assert(bone, "bone must be added to the bone dictionary!"); 215 216 bone.setArmature(null); 217 bone.removeFromParent(recursion); 218 cc.arrayRemoveObject(this._topBoneList, bone); 219 220 delete this._boneDic[bone.getName()]; 221 this.removeChild(bone, true); 222 }, 223 224 /** 225 * Gets a bone with the specified name 226 * @param {String} name The bone's name you want to get 227 * @return {ccs.Bone} 228 */ 229 getBone: function (name) { 230 return this._boneDic[name]; 231 }, 232 233 /** 234 * Change a bone's parent with the specified parent name. 235 * @param {ccs.Bone} bone The bone you want to change parent 236 * @param {String} parentName The new parent's name 237 */ 238 changeBoneParent: function (bone, parentName) { 239 cc.assert(bone, "bone must be added to the bone dictionary!"); 240 241 var parentBone = bone.getParentBone(); 242 if (parentBone) { 243 cc.arrayRemoveObject(parentBone.getChildren(), bone); 244 bone.setParentBone(null); 245 } 246 247 if (parentName) { 248 var boneParent = this._boneDic[parentName]; 249 if (boneParent) { 250 boneParent.addChildBone(bone); 251 cc.arrayRemoveObject(this._topBoneList, bone); 252 } else 253 this._topBoneList.push(bone); 254 } 255 }, 256 257 /** 258 * Get CCArmature's bone dictionary 259 * @return {Object} Armature's bone dictionary 260 */ 261 getBoneDic: function () { 262 return this._boneDic; 263 }, 264 265 /** 266 * Set contentSize and Calculate anchor point. 267 */ 268 updateOffsetPoint: function () { 269 // Set contentsize and Calculate anchor point. 270 var rect = this.getBoundingBox(); 271 this.setContentSize(rect); 272 var locOffsetPoint = this._offsetPoint; 273 locOffsetPoint.x = -rect.x; 274 locOffsetPoint.y = -rect.y; 275 if (rect.width != 0 && rect.height != 0) 276 this.setAnchorPoint(locOffsetPoint.x / rect.width, locOffsetPoint.y / rect.height); 277 }, 278 279 /** 280 * Sets armature's anchor point, because it need to consider offset point, so here is the override function. 281 * @override 282 * @param {cc.Point|Number} point point or x of point 283 * @param {Number} [y] y of point 284 */ 285 setAnchorPoint: function(point, y){ 286 var ax, ay; 287 if(y !== undefined){ 288 ax = point; 289 ay = y; 290 }else{ 291 ax = point.x; 292 ay = point.y; 293 } 294 var locAnchorPoint = this._anchorPoint; 295 if(ax != locAnchorPoint.x || ay != locAnchorPoint.y){ 296 var contentSize = this._contentSize ; 297 locAnchorPoint.x = ax; 298 locAnchorPoint.y = ay; 299 this._anchorPointInPoints.x = contentSize.width * locAnchorPoint.x - this._offsetPoint.x; 300 this._anchorPointInPoints.y = contentSize.height * locAnchorPoint.y - this._offsetPoint.y; 301 302 this._realAnchorPointInPoints.x = contentSize.width * locAnchorPoint.x; 303 this._realAnchorPointInPoints.y = contentSize.height * locAnchorPoint.y; 304 this.setNodeDirty(); 305 } 306 }, 307 308 _setAnchorX: function (x) { 309 if (this._anchorPoint.x === x) return; 310 this._anchorPoint.x = x; 311 this._anchorPointInPoints.x = this._contentSize.width * x - this._offsetPoint.x; 312 this._realAnchorPointInPoints.x = this._contentSize.width * x; 313 this.setNodeDirty(); 314 }, 315 316 _setAnchorY: function (y) { 317 if (this._anchorPoint.y === y) return; 318 this._anchorPoint.y = y; 319 this._anchorPointInPoints.y = this._contentSize.height * y - this._offsetPoint.y; 320 this._realAnchorPointInPoints.y = this._contentSize.height * y; 321 this.setNodeDirty(); 322 }, 323 324 /** 325 * Returns the anchor point in points of ccs.Armature. 326 * @override 327 * @returns {cc.Point} 328 */ 329 getAnchorPointInPoints: function(){ 330 return this._realAnchorPointInPoints; 331 }, 332 333 /** 334 * Sets animation to this Armature 335 * @param {ccs.ArmatureAnimation} animation 336 */ 337 setAnimation: function (animation) { 338 this.animation = animation; 339 }, 340 341 /** 342 * Gets the animation of this Armature. 343 * @return {ccs.ArmatureAnimation} 344 */ 345 getAnimation: function () { 346 return this.animation; 347 }, 348 349 /** 350 * armatureTransformDirty getter 351 * @returns {Boolean} 352 */ 353 getArmatureTransformDirty: function () { 354 return this._armatureTransformDirty; 355 }, 356 357 /** 358 * The update callback of ccs.Armature, it updates animation's state and updates bone's state. 359 * @override 360 * @param {Number} dt 361 */ 362 update: function (dt) { 363 this.animation.update(dt); 364 var locTopBoneList = this._topBoneList; 365 for (var i = 0; i < locTopBoneList.length; i++) 366 locTopBoneList[i].update(dt); 367 this._armatureTransformDirty = false; 368 }, 369 370 /** 371 * Draws armature's display render node. 372 * @override 373 * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context 374 */ 375 draw: function(ctx){ 376 if (this._parentBone == null && this._batchNode == null) { 377 // CC_NODE_DRAW_SETUP(); 378 } 379 380 var locChildren = this._children; 381 var alphaPremultiplied = cc.BlendFunc.ALPHA_PREMULTIPLIED, alphaNonPremultipled = cc.BlendFunc.ALPHA_NON_PREMULTIPLIED; 382 for (var i = 0, len = locChildren.length; i< len; i++) { 383 var selBone = locChildren[i]; 384 if (selBone && selBone.getDisplayRenderNode) { 385 var node = selBone.getDisplayRenderNode(); 386 387 if (null == node) 388 continue; 389 390 if(cc._renderType === cc._RENDER_TYPE_WEBGL) 391 node.setShaderProgram(this._shaderProgram); 392 393 switch (selBone.getDisplayRenderNodeType()) { 394 case ccs.DISPLAY_TYPE_SPRITE: 395 if(node instanceof ccs.Skin){ 396 if(cc._renderType === cc._RENDER_TYPE_WEBGL){ 397 node.updateTransform(); 398 399 var func = selBone.getBlendFunc(); 400 if (func.src != alphaPremultiplied.src || func.dst != alphaPremultiplied.dst) 401 node.setBlendFunc(selBone.getBlendFunc()); 402 else { 403 if ((this._blendFunc.src == alphaPremultiplied.src && this._blendFunc.dst == alphaPremultiplied.dst) 404 && !node.getTexture().hasPremultipliedAlpha()) 405 node.setBlendFunc(alphaNonPremultipled); 406 else 407 node.setBlendFunc(this._blendFunc); 408 } 409 node.draw(ctx); 410 } else{ 411 node.visit(ctx); 412 } 413 } 414 break; 415 case ccs.DISPLAY_TYPE_ARMATURE: 416 node.draw(ctx); 417 break; 418 default: 419 node.visit(ctx); 420 break; 421 } 422 } else if(selBone instanceof cc.Node) { 423 if(cc._renderType === cc._RENDER_TYPE_WEBGL) 424 selBone.setShaderProgram(this._shaderProgram); 425 selBone.visit(ctx); 426 // CC_NODE_DRAW_SETUP(); 427 } 428 } 429 }, 430 431 /** 432 * The callback when ccs.Armature enter stage. 433 * @override 434 */ 435 onEnter: function () { 436 cc.Node.prototype.onEnter.call(this); 437 this.scheduleUpdate(); 438 }, 439 440 /** 441 * The callback when ccs.Armature exit stage. 442 * @override 443 */ 444 onExit: function () { 445 cc.Node.prototype.onExit.call(this); 446 this.unscheduleUpdate(); 447 }, 448 449 visit: null, 450 451 _visitForCanvas: function(ctx){ 452 var context = ctx || cc._renderContext; 453 // quick return if not visible. children won't be drawn. 454 if (!this._visible) 455 return; 456 457 context.save(); 458 this.transform(context); 459 460 this.sortAllChildren(); 461 this.draw(ctx); 462 463 // reset for next frame 464 this._cacheDirty = false; 465 this.arrivalOrder = 0; 466 467 context.restore(); 468 }, 469 470 _visitForWebGL: function(){ 471 // quick return if not visible. children won't be drawn. 472 if (!this._visible) 473 return; 474 475 var context = cc._renderContext, currentStack = cc.current_stack; 476 477 currentStack.stack.push(currentStack.top); 478 cc.kmMat4Assign(this._stackMatrix, currentStack.top); 479 currentStack.top = this._stackMatrix; 480 481 this.transform(); 482 483 this.sortAllChildren(); 484 this.draw(context); 485 486 // reset for next frame 487 this.arrivalOrder = 0; 488 currentStack.top = currentStack.stack.pop(); 489 }, 490 491 /** 492 * This boundingBox will calculate all bones' boundingBox every time 493 * @returns {cc.Rect} 494 */ 495 getBoundingBox: function(){ 496 var minX, minY, maxX, maxY = 0; 497 var first = true; 498 499 var boundingBox = cc.rect(0, 0, 0, 0), locChildren = this._children; 500 501 var len = locChildren.length; 502 for (var i=0; i<len; i++) { 503 var bone = locChildren[i]; 504 if (bone) { 505 var r = bone.getDisplayManager().getBoundingBox(); 506 if (r.x == 0 && r.y == 0 && r.width == 0 && r.height == 0) 507 continue; 508 509 if(first) { 510 minX = r.x; 511 minY = r.y; 512 maxX = r.x + r.width; 513 maxY = r.y + r.height; 514 first = false; 515 } else { 516 minX = r.x < boundingBox.x ? r.x : boundingBox.x; 517 minY = r.y < boundingBox.y ? r.y : boundingBox.y; 518 maxX = r.x + r.width > boundingBox.x + boundingBox.width ? 519 r.x + r.width : boundingBox.x + boundingBox.width; 520 maxY = r.y + r.height > boundingBox.y + boundingBox.height ? 521 r.y + r.height : boundingBox.y + boundingBox.height; 522 } 523 524 boundingBox.x = minX; 525 boundingBox.y = minY; 526 boundingBox.width = maxX - minX; 527 boundingBox.height = maxY - minY; 528 } 529 } 530 return cc.rectApplyAffineTransform(boundingBox, this.getNodeToParentTransform()); 531 }, 532 533 /** 534 * when bone contain the point ,then return it. 535 * @param {Number} x 536 * @param {Number} y 537 * @returns {ccs.Bone} 538 */ 539 getBoneAtPoint: function (x, y) { 540 var locChildren = this._children; 541 for (var i = locChildren.length - 1; i >= 0; i--) { 542 var child = locChildren[i]; 543 if (child instanceof ccs.Bone && child.getDisplayManager().containPoint(x, y)) 544 return child; 545 } 546 return null; 547 }, 548 549 /** 550 * Sets parent bone of this Armature 551 * @param {ccs.Bone} parentBone 552 */ 553 setParentBone: function (parentBone) { 554 this._parentBone = parentBone; 555 var locBoneDic = this._boneDic; 556 for (var key in locBoneDic) { 557 locBoneDic[key].setArmature(this); 558 } 559 }, 560 561 /** 562 * Return parent bone of ccs.Armature. 563 * @returns {ccs.Bone} 564 */ 565 getParentBone: function () { 566 return this._parentBone; 567 }, 568 569 /** 570 * draw contour 571 */ 572 drawContour: function () { 573 cc._drawingUtil.setDrawColor(255, 255, 255, 255); 574 cc._drawingUtil.setLineWidth(1); 575 var locBoneDic = this._boneDic; 576 for (var key in locBoneDic) { 577 var bone = locBoneDic[key]; 578 var detector = bone.getColliderDetector(); 579 if(!detector) 580 continue; 581 var bodyList = detector.getColliderBodyList(); 582 for (var i = 0; i < bodyList.length; i++) { 583 var body = bodyList[i]; 584 var vertexList = body.getCalculatedVertexList(); 585 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true); 586 } 587 } 588 }, 589 590 591 setBody: function (body) { 592 if (this._body == body) 593 return; 594 595 this._body = body; 596 this._body.data = this; 597 var child, displayObject, locChildren = this._children; 598 for (var i = 0; i < locChildren.length; i++) { 599 child = locChildren[i]; 600 if (child instanceof ccs.Bone) { 601 var displayList = child.getDisplayManager().getDecorativeDisplayList(); 602 for (var j = 0; j < displayList.length; j++) { 603 displayObject = displayList[j]; 604 var detector = displayObject.getColliderDetector(); 605 if (detector) 606 detector.setBody(this._body); 607 } 608 } 609 } 610 }, 611 612 getShapeList: function () { 613 if (this._body) 614 return this._body.shapeList; 615 return null; 616 }, 617 618 getBody: function () { 619 return this._body; 620 }, 621 622 /** 623 * Sets the blendFunc to ccs.Armature 624 * @param {cc.BlendFunc} blendFunc 625 */ 626 setBlendFunc: function (blendFunc) { 627 this._blendFunc = blendFunc; 628 }, 629 630 /** 631 * Returns the blendFunc of ccs.Armature 632 * @returns {cc.BlendFunc} 633 */ 634 getBlendFunc: function () { 635 return this._blendFunc; 636 }, 637 638 /** 639 * set collider filter 640 * @param {ccs.ColliderFilter} filter 641 */ 642 setColliderFilter: function (filter) { 643 var locBoneDic = this._boneDic; 644 for (var key in locBoneDic) 645 locBoneDic[key].setColliderFilter(filter); 646 }, 647 648 /** 649 * Returns the armatureData of ccs.Armature 650 * @return {ccs.ArmatureData} 651 */ 652 getArmatureData: function () { 653 return this.armatureData; 654 }, 655 656 /** 657 * Sets armatureData to this Armature 658 * @param {ccs.ArmatureData} armatureData 659 */ 660 setArmatureData: function (armatureData) { 661 this.armatureData = armatureData; 662 }, 663 664 getBatchNode: function () { 665 return this.batchNode; 666 }, 667 668 setBatchNode: function (batchNode) { 669 this.batchNode = batchNode; 670 }, 671 672 /** 673 * version getter 674 * @returns {Number} 675 */ 676 getVersion: function () { 677 return this.version; 678 }, 679 680 /** 681 * version setter 682 * @param {Number} version 683 */ 684 setVersion: function (version) { 685 this.version = version; 686 } 687 }); 688 689 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 690 ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForWebGL; 691 } else { 692 ccs.Armature.prototype.visit = ccs.Armature.prototype._visitForCanvas; 693 } 694 695 var _p = ccs.Armature.prototype; 696 697 /** @expose */ 698 _p.parentBone; 699 cc.defineGetterSetter(_p, "parentBone", _p.getParentBone, _p.setParentBone); 700 /** @expose */ 701 _p.body; 702 cc.defineGetterSetter(_p, "body", _p.getBody, _p.setBody); 703 /** @expose */ 704 _p.colliderFilter; 705 cc.defineGetterSetter(_p, "colliderFilter", null, _p.setColliderFilter); 706 707 _p = null; 708 709 /** 710 * Allocates an armature, and use the ArmatureData named name in ArmatureDataManager to initializes the armature. 711 * @param {String} [name] Bone name 712 * @param {ccs.Bone} [parentBone] the parent bone 713 * @return {ccs.Armature} 714 * @example 715 * // example 716 * var armature = ccs.Armature.create(); 717 */ 718 ccs.Armature.create = function (name, parentBone) { 719 var armature = new ccs.Armature(); 720 if (armature.init(name, parentBone)) 721 return armature; 722 return null; 723 }; 724