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.Armature objects. 28 * @class 29 * @extends ccs.NodeRGBA 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.NodeRGBA.extend(/** @lends ccs.Armature# */{ 41 animation: null, 42 armatureData: null, 43 batchNode: null, 44 name: "", 45 _textureAtlas: null, 46 _parentBone: null, 47 _boneDic: null, 48 _topBoneList: null, 49 _armatureIndexDic: null, 50 _offsetPoint: null, 51 version: 0, 52 _armatureTransformDirty: true, 53 _body: null, 54 _textureAtlasDic: null, 55 _blendFunc: null, 56 _className: "Armature", 57 58 /** 59 * Create a armature node. 60 * Constructor of ccs.Armature 61 * @param {String} name 62 * @param {ccs.Bone} parentBone 63 * @example 64 * var armature = new ccs.Armature(); 65 */ 66 ctor: function (name, parentBone) { 67 cc.NodeRGBA.prototype.ctor.call(this); 68 this.animation = null; 69 this.armatureData = null; 70 this.batchNode = null; 71 this.name = ""; 72 this._textureAtlas = null; 73 this._parentBone = null; 74 this._boneDic = null; 75 this._topBoneList = null; 76 this._armatureIndexDic = {}; 77 this._offsetPoint = cc.p(0, 0); 78 this.version = 0; 79 this._armatureTransformDirty = true; 80 this._body = null; 81 this._textureAtlasDic = null; 82 this._blendFunc = null; 83 84 parentBone && ccs.Armature.prototype.init.call(this, name, parentBone); 85 }, 86 87 /** 88 * Initializes a CCArmature with the specified name and CCBone 89 * @param {String} name 90 * @param {ccs.Bone} parentBone 91 * @return {Boolean} 92 */ 93 init: function (name, parentBone) { 94 cc.NodeRGBA.prototype.init.call(this); 95 if (parentBone) { 96 this._parentBone = parentBone; 97 } 98 this.removeAllChildren(); 99 this.animation = new ccs.ArmatureAnimation(); 100 this.animation.init(this); 101 this._boneDic = {}; 102 this._topBoneList = []; 103 this._textureAtlasDic = {}; 104 this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST}; 105 this.name = (!name) ? "" : name; 106 var armatureDataManager = ccs.armatureDataManager; 107 if (name != "") { 108 //animationData 109 var animationData = armatureDataManager.getAnimationData(name); 110 if (!animationData) { 111 cc.log("AnimationData not exist! "); 112 return false; 113 } 114 this.animation.setAnimationData(animationData); 115 116 //armatureData 117 var armatureData = armatureDataManager.getArmatureData(name); 118 this.armatureData = armatureData; 119 120 //boneDataDic 121 var boneDataDic = armatureData.getBoneDataDic(); 122 for (var key in boneDataDic) { 123 var bone = this.createBone(String(key)); 124 //! init bone's Tween to 1st movement's 1st frame 125 do { 126 var movData = animationData.getMovement(animationData.movementNames[0]); 127 if (!movData) { 128 break; 129 } 130 var _movBoneData = movData.getMovementBoneData(bone.getName()); 131 if (!_movBoneData || _movBoneData.frameList.length <= 0) { 132 break; 133 } 134 var frameData = _movBoneData.getFrameData(0); 135 if (!frameData) { 136 break; 137 } 138 bone.getTweenData().copy(frameData); 139 bone.changeDisplayWithIndex(frameData.displayIndex, false); 140 } while (0); 141 } 142 this.update(0); 143 this.updateOffsetPoint(); 144 } else { 145 this.name = "new_armature"; 146 this.armatureData = new ccs.ArmatureData(); 147 this.armatureData.name = this.name; 148 149 var animationData = new ccs.AnimationData(); 150 animationData.name = this.name; 151 152 armatureDataManager.addArmatureData(this.name, this.armatureData); 153 armatureDataManager.addAnimationData(this.name, animationData); 154 155 this.animation.setAnimationData(animationData); 156 } 157 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 158 this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR)); 159 } 160 161 this.setCascadeOpacityEnabled(true); 162 this.setCascadeColorEnabled(true); 163 return true; 164 }, 165 onEnter: function () { 166 cc.NodeRGBA.prototype.onEnter.call(this); 167 this.scheduleUpdate(); 168 }, 169 onExit: function () { 170 cc.NodeRGBA.prototype.onExit.call(this); 171 this.unscheduleUpdate(); 172 }, 173 /** 174 * create a bone 175 * @param {String} boneName 176 * @return {ccs.Bone} 177 */ 178 createBone: function (boneName) { 179 var existedBone = this.getBone(boneName); 180 if (existedBone) { 181 return existedBone; 182 } 183 var boneData = this.armatureData.getBoneData(boneName); 184 var parentName = boneData.parentName; 185 var bone = null; 186 if (parentName != "") { 187 this.createBone(parentName); 188 bone = ccs.Bone.create(boneName); 189 this.addBone(bone, parentName); 190 } else { 191 bone = ccs.Bone.create(boneName); 192 this.addBone(bone, ""); 193 } 194 195 bone.setBoneData(boneData); 196 bone.getDisplayManager().changeDisplayWithIndex(-1, false); 197 return bone; 198 }, 199 200 /** 201 * add a bone 202 * @param {ccs.Bone} bone 203 * @param {String} parentName 204 */ 205 addBone: function (bone, parentName) { 206 if (!bone) { 207 cc.log("Argument must be non-nil"); 208 return; 209 } 210 if (this._boneDic[bone.getName()]) { 211 cc.log("bone already added. It can't be added again"); 212 return; 213 } 214 215 if (parentName) { 216 var boneParent = this._boneDic[parentName]; 217 if (boneParent) { 218 boneParent.addChildBone(bone); 219 } 220 else { 221 this._topBoneList.push(bone); 222 } 223 } 224 else { 225 this._topBoneList.push(bone); 226 } 227 bone.setArmature(this); 228 this._boneDic[bone.getName()] = bone; 229 this.addChild(bone); 230 }, 231 232 /** 233 * remove a bone 234 * @param {ccs.Bone} bone 235 * @param {Boolean} recursion 236 */ 237 removeBone: function (bone, recursion) { 238 if (!bone) { 239 cc.log("bone must be added to the bone dictionary!"); 240 return; 241 } 242 243 bone.setArmature(null); 244 bone.removeFromParent(recursion); 245 cc.arrayRemoveObject(this._topBoneList, bone); 246 delete this._boneDic[bone.getName()]; 247 this.removeChild(bone, true); 248 }, 249 250 /** 251 * get a bone by name 252 * @param {String} name 253 * @return {ccs.Bone} 254 */ 255 getBone: function (name) { 256 return this._boneDic[name]; 257 }, 258 259 /** 260 * Change a bone's parent with the specified parent name. 261 * @param {ccs.Bone} bone 262 * @param {String} parentName 263 */ 264 changeBoneParent: function (bone, parentName) { 265 if (!bone) { 266 cc.log("bone must be added to the bone dictionary!"); 267 return; 268 } 269 var parentBone = bone.getParentBone(); 270 if (parentBone) { 271 cc.arrayRemoveObject(parentBone.getChildrenBone(), bone); 272 bone.setParentBone(null); 273 } 274 275 if (parentName) { 276 var boneParent = this._boneDic[parentName]; 277 if (boneParent) { 278 boneParent.addChildBone(bone); 279 cc.arrayRemoveObject(this._topBoneList, bone); 280 } else { 281 this._topBoneList.push(bone); 282 } 283 } 284 }, 285 286 /** 287 * Get CCArmature's bone dictionary 288 * @return {Object} 289 */ 290 getBoneDic: function () { 291 return this._boneDic; 292 }, 293 294 /** 295 * Set contentSize and Calculate anchor point. 296 */ 297 updateOffsetPoint: function () { 298 // Set contentsize and Calculate anchor point. 299 var rect = this.boundingBox(); 300 this.setContentSize(rect); 301 var locOffsetPoint = this._offsetPoint; 302 locOffsetPoint.x = -rect.x; 303 locOffsetPoint.y = -rect.y; 304 if (rect.width != 0 && rect.height != 0) { 305 this.setAnchorPoint(locOffsetPoint.x / rect.width, locOffsetPoint.y / rect.height); 306 } 307 }, 308 309 update: function (dt) { 310 this.animation.update(dt); 311 var locTopBoneList = this._topBoneList; 312 for (var i = 0; i < locTopBoneList.length; i++) { 313 locTopBoneList[i].update(dt); 314 } 315 this._armatureTransformDirty = false; 316 }, 317 318 319 nodeToParentTransform: null, 320 321 _nodeToParentTransformForWebGL: function () { 322 if (this._transformDirty) { 323 this._armatureTransformDirty = true; 324 // Translate values 325 var x = this._position.x; 326 var y = this._position.y; 327 var apx = this._anchorPointInPoints.x, napx = -apx; 328 var apy = this._anchorPointInPoints.y, napy = -apy; 329 var scx = this._scaleX, scy = this._scaleY; 330 331 if (this._ignoreAnchorPointForPosition) { 332 x += apx; 333 y += apy; 334 } 335 336 // Rotation values 337 // Change rotation code to handle X and Y 338 // If we skew with the exact same value for both x and y then we're simply just rotating 339 var cx = 1, sx = 0, cy = 1, sy = 0; 340 if (this._rotationX !== 0 || this._rotationY !== 0) { 341 cx = Math.cos(-this._rotationRadiansX); 342 sx = Math.sin(-this._rotationRadiansX); 343 cy = Math.cos(-this._rotationRadiansY); 344 sy = Math.sin(-this._rotationRadiansY); 345 } 346 347 // Add offset point 348 x += cy * this._offsetPoint.x * this._scaleX + -sx * this._offsetPoint.y * this._scaleY; 349 y += sy * this._offsetPoint.x * this._scaleX + cx * this._offsetPoint.y * this._scaleY; 350 351 var needsSkewMatrix = ( this._skewX || this._skewY ); 352 353 // optimization: 354 // inline anchor point calculation if skew is not needed 355 // Adjusted transform calculation for rotational skew 356 if (!needsSkewMatrix && (apx !== 0 || apy !== 0)) { 357 x += cy * napx * scx + -sx * napy * scy; 358 y += sy * napx * scx + cx * napy * scy; 359 } 360 361 // Build Transform Matrix 362 // Adjusted transform calculation for rotational skew 363 var t = {a: cy * scx, b: sy * scx, c: -sx * scy, d: cx * scy, tx: x, ty: y}; 364 365 // XXX: Try to inline skew 366 // If skew is needed, apply skew and then anchor point 367 if (needsSkewMatrix) { 368 t = cc.AffineTransformConcat({a: 1.0, b: Math.tan(cc.degreesToRadians(this._skewY)), 369 c: Math.tan(cc.degreesToRadians(this._skewX)), d: 1.0, tx: 0.0, ty: 0.0}, t); 370 371 // adjust anchor point 372 if (apx !== 0 || apy !== 0) 373 t = cc.AffineTransformTranslate(t, napx, napy); 374 } 375 376 if (this._additionalTransformDirty) { 377 t = cc.AffineTransformConcat(t, this._additionalTransform); 378 this._additionalTransformDirty = false; 379 } 380 this._transform = t; 381 this._transformDirty = false; 382 } 383 return this._transform; 384 }, 385 386 _nodeToParentTransformForCanvas: function () { 387 if (!this._transform) 388 this._transform = {a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0}; 389 if (this._transformDirty) { 390 this._armatureTransformDirty = true; 391 var t = this._transform;// quick reference 392 // base position 393 t.tx = this._position.x; 394 t.ty = this._position.y; 395 396 // rotation Cos and Sin 397 var Cos = 1, Sin = 0; 398 if (this._rotationX) { 399 Cos = Math.cos(-this._rotationRadiansX); 400 Sin = Math.sin(-this._rotationRadiansX); 401 } 402 403 // base abcd 404 t.a = t.d = Cos; 405 t.c = -Sin; 406 t.b = Sin; 407 408 var lScaleX = this._scaleX, lScaleY = this._scaleY; 409 var appX = this._anchorPointInPoints.x, appY = this._anchorPointInPoints.y; 410 411 // Firefox on Vista and XP crashes 412 // GPU thread in case of scale(0.0, 0.0) 413 var sx = (lScaleX < 0.000001 && lScaleX > -0.000001) ? 0.000001 : lScaleX, 414 sy = (lScaleY < 0.000001 && lScaleY > -0.000001) ? 0.000001 : lScaleY; 415 416 // Add offset point 417 t.tx += Cos * this._offsetPoint.x * lScaleX + -Sin * this._offsetPoint.y * lScaleY; 418 t.ty += Sin * this._offsetPoint.x * lScaleX + Cos * this._offsetPoint.y * lScaleY; 419 420 // skew 421 if (this._skewX || this._skewY) { 422 // offset the anchorpoint 423 var skx = Math.tan(-this._skewX * Math.PI / 180); 424 var sky = Math.tan(-this._skewY * Math.PI / 180); 425 var xx = appY * skx * sx; 426 var yy = appX * sky * sy; 427 t.a = Cos + -Sin * sky; 428 t.c = Cos * skx + -Sin; 429 t.b = Sin + Cos * sky; 430 t.d = Sin * skx + Cos; 431 t.tx += Cos * xx + -Sin * yy; 432 t.ty += Sin * xx + Cos * yy; 433 } 434 435 // scale 436 if (lScaleX !== 1 || lScaleY !== 1) { 437 t.a *= sx; 438 t.b *= sx; 439 t.c *= sy; 440 t.d *= sy; 441 } 442 443 // adjust anchorPoint 444 t.tx += Cos * -appX * sx + -Sin * -appY * sy; 445 t.ty += Sin * -appX * sx + Cos * -appY * sy; 446 447 // if ignore anchorPoint 448 if (this._ignoreAnchorPointForPosition) { 449 t.tx += appX 450 t.ty += appY; 451 } 452 453 if (this._additionalTransformDirty) { 454 this._transform = cc.AffineTransformConcat(this._transform, this._additionalTransform); 455 this._additionalTransformDirty = false; 456 } 457 458 t.tx = t.tx | 0; 459 t.ty = t.ty | 0; 460 this._transformDirty = false; 461 } 462 return this._transform; 463 }, 464 465 draw: function () { 466 //cc.g_NumberOfDraws++; 467 }, 468 469 /** 470 * conforms to cc.TextureProtocol protocol 471 * @param {cc.BlendFunc} blendFunc 472 */ 473 setBlendFunc: function (blendFunc) { 474 this._blendFunc = blendFunc; 475 }, 476 477 /** 478 * blendFunc getter 479 * @returns {cc.BlendFunc} 480 */ 481 getBlendFunc: function () { 482 return this._blendFunc; 483 }, 484 485 /** 486 * This boundingBox will calculate all bones' boundingBox every time 487 * @return {cc.rect} 488 */ 489 boundingBox: function () { 490 var minx = 0, miny = 0, maxx = 0, maxy = 0; 491 var first = true; 492 var boundingBox = cc.rect(0, 0, 0, 0); 493 for (var i = 0; i < this._children.length; i++) { 494 var bone = this._children[i]; 495 if (bone instanceof ccs.Bone) { 496 var r = bone.getDisplayManager().getBoundingBox(); 497 if (first) { 498 minx = cc.rectGetMinX(r); 499 miny = cc.rectGetMinY(r); 500 maxx = cc.rectGetMaxX(r); 501 maxy = cc.rectGetMaxY(r); 502 503 first = false; 504 } 505 else { 506 minx = cc.rectGetMinX(r) < cc.rectGetMinX(boundingBox) ? cc.rectGetMinX(r) : cc.rectGetMinX(boundingBox); 507 miny = cc.rectGetMinY(r) < cc.rectGetMinY(boundingBox) ? cc.rectGetMinY(r) : cc.rectGetMinY(boundingBox); 508 maxx = cc.rectGetMaxX(r) > cc.rectGetMaxX(boundingBox) ? cc.rectGetMaxX(r) : cc.rectGetMaxX(boundingBox); 509 maxy = cc.rectGetMaxY(r) > cc.rectGetMaxY(boundingBox) ? cc.rectGetMaxY(r) : cc.rectGetMaxY(boundingBox); 510 } 511 boundingBox = cc.rect(minx, miny, maxx - minx, maxy - miny); 512 } 513 } 514 return cc.RectApplyAffineTransform(boundingBox, this.nodeToParentTransform()); 515 }, 516 517 /** 518 * when bone contain the point ,then return it. 519 * @param {Number} x 520 * @param {Number} y 521 * @returns {ccs.Bone} 522 */ 523 getBoneAtPoint: function (x, y) { 524 for (var i = this._children.length - 1; i >= 0; i--) { 525 var child = this._children[i]; 526 if (child instanceof ccs.Bone) { 527 if (child.getDisplayManager().containPoint(x, y)) { 528 return child; 529 } 530 } 531 } 532 return null; 533 }, 534 535 getTexureAtlasWithTexture: function () { 536 return null; 537 }, 538 539 /** 540 * parent bone setter 541 * @param {ccs.Bone} parentBone 542 */ 543 setParentBone: function (parentBone) { 544 this._parentBone = parentBone; 545 for (var key in this._boneDic) { 546 var bone = this._boneDic[key]; 547 bone.setArmature(this); 548 } 549 }, 550 551 /** 552 * set collider filter 553 * @param {ccs.ColliderFilter} filter 554 */ 555 setColliderFilter: function (filter) { 556 for (var key in this._boneDic) { 557 var bone = this._boneDic[key]; 558 bone.setColliderFilter(filter); 559 } 560 }, 561 562 /** 563 * draw contour 564 */ 565 drawContour: function () { 566 cc._drawingUtil.setDrawColor(255, 255, 255, 255); 567 cc._drawingUtil.setLineWidth(1); 568 for (var key in this._boneDic) { 569 var bone = this._boneDic[key]; 570 var bodyList = bone.getColliderBodyList(); 571 for (var i = 0; i < bodyList.length; i++) { 572 var body = bodyList[i]; 573 var vertexList = body.getCalculatedVertexList(); 574 cc._drawingUtil.drawPoly(vertexList, vertexList.length, true); 575 } 576 } 577 }, 578 579 /** 580 * return parent bone 581 * @returns {ccs.Bone} 582 */ 583 getParentBone: function () { 584 return this._parentBone; 585 }, 586 587 /** 588 * armatureAnimation getter 589 * @return {ccs.ArmatureAnimation} 590 */ 591 getAnimation: function () { 592 return this.animation; 593 }, 594 595 /** 596 * armatureAnimation setter 597 * @param {ccs.ArmatureAnimation} animation 598 */ 599 setAnimation: function (animation) { 600 this.animation = animation; 601 }, 602 603 /** 604 * armatureData getter 605 * @return {ccs.ArmatureData} 606 */ 607 getArmatureData: function () { 608 return this.armatureData; 609 }, 610 611 /** 612 * armatureData setter 613 * @param {ccs.ArmatureData} armatureData 614 */ 615 setArmatureData: function (armatureData) { 616 this.armatureData = armatureData; 617 }, 618 getName: function () { 619 return this.name; 620 }, 621 setName: function (name) { 622 this.name = name; 623 }, 624 getBatchNode: function () { 625 return this.batchNode; 626 }, 627 setBatchNode: function (batchNode) { 628 this.batchNode = batchNode; 629 }, 630 631 /** 632 * version getter 633 * @returns {Number} 634 */ 635 getVersion: function () { 636 return this.version; 637 }, 638 639 /** 640 * version setter 641 * @param {Number} version 642 */ 643 setVersion: function (version) { 644 this.version = version; 645 }, 646 647 /** 648 * armatureTransformDirty getter 649 * @returns {Boolean} 650 */ 651 getArmatureTransformDirty: function () { 652 return this._armatureTransformDirty; 653 }, 654 getBody: function () { 655 return this._body; 656 }, 657 658 setBody: function (body) { 659 if (this._body == body) 660 return; 661 662 this._body = body; 663 this._body.data = this; 664 var child, displayObject; 665 for (var i = 0; i < this._children.length; i++) { 666 child = this._children[i]; 667 if (child instanceof ccs.Bone) { 668 var displayList = child.getDisplayManager().getDecorativeDisplayList(); 669 for (var j = 0; j < displayList.length; j++) { 670 displayObject = displayList[j]; 671 var detector = displayObject.getColliderDetector(); 672 if (detector) 673 detector.setBody(this._body); 674 } 675 } 676 } 677 }, 678 getShapeList: function () { 679 if (this._body) 680 return this._body.shapeList; 681 return []; 682 } 683 684 }); 685 686 687 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 688 //WebGL 689 ccs.Armature.prototype.nodeToParentTransform = ccs.Armature.prototype._nodeToParentTransformForWebGL; 690 } else { 691 //Canvas 692 ccs.Armature.prototype.nodeToParentTransform = ccs.Armature.prototype._nodeToParentTransformForCanvas; 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 and initializes a armature. 711 * @param {String} name 712 * @param {ccs.Bone} parentBone 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 && armature.init(name, parentBone)) { 721 return armature; 722 } 723 return null; 724 }; 725