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