1 /**************************************************************************** 2 Copyright (c) 2011-2012 cocos2d-x.org 3 Copyright (c) 2013-2014 Chukong Technologies Inc. 4 Copyright (c) 2014 Shengxiang Chen (Nero Chan) 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 /** 28 * The main namespace of Spine, all classes, functions, properties and constants of Spine are defined in this namespace 29 * @namespace 30 * @name sp 31 */ 32 var sp = sp || {}; 33 34 /** 35 * The vertex index of spine. 36 * @constant 37 * @type {{X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, X4: number, Y4: number}} 38 */ 39 sp.VERTEX_INDEX = { 40 X1: 0, 41 Y1: 1, 42 X2: 2, 43 Y2: 3, 44 X3: 4, 45 Y3: 5, 46 X4: 6, 47 Y4: 7 48 }; 49 50 /** 51 * The attachment type of spine. It contains three type: REGION(0), BOUNDING_BOX(1) and REGION_SEQUENCE(2). 52 * @constant 53 * @type {{REGION: number, BOUNDING_BOX: number, REGION_SEQUENCE: number}} 54 */ 55 sp.ATTACHMENT_TYPE = { 56 REGION: 0, 57 BOUNDING_BOX: 1, 58 REGION_SEQUENCE: 2 59 }; 60 61 /** 62 * <p> 63 * The skeleton of Spine. <br/> 64 * Skeleton has a reference to a SkeletonData and stores the state for skeleton instance, 65 * which consists of the current pose's bone SRT, slot colors, and which slot attachments are visible. <br/> 66 * Multiple skeletons can use the same SkeletonData (which includes all animations, skins, and attachments). <br/> 67 * </p> 68 * @class 69 * @extends cc.Node 70 */ 71 sp.Skeleton = cc.Node.extend(/** @lends sp.Skeleton# */{ 72 _skeleton: null, 73 _rootBone: null, 74 _timeScale: 1, 75 _debugSlots: false, 76 _debugBones: false, 77 _premultipliedAlpha: false, 78 _ownsSkeletonData: null, 79 _atlas: null, 80 _blendFunc: null, 81 82 /** 83 * The constructor of sp.Skeleton. override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 84 */ 85 ctor:function(skeletonDataFile, atlasFile, scale){ 86 cc.Node.prototype.ctor.call(this); 87 this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST}; 88 89 if(arguments.length === 0) 90 this.init(); 91 else 92 this.initWithArgs(skeletonDataFile, atlasFile, scale); 93 }, 94 95 /** 96 * Initializes a sp.Skeleton. please do not call this function by yourself, you should pass the parameters to constructor to initialize it. 97 */ 98 init: function () { 99 cc.Node.prototype.init.call(this); 100 this.setOpacityModifyRGB(true); 101 this._blendFunc.src = cc.ONE; 102 this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA; 103 if (cc._renderType === cc._RENDER_TYPE_WEBGL) 104 this.setShaderProgram(cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR)); 105 this.scheduleUpdate(); 106 }, 107 108 /** 109 * Sets whether open debug solots. 110 * @param {boolean} enable true to open, false to close. 111 */ 112 setDebugSolots:function(enable){ 113 this._debugSlots = enable; 114 }, 115 116 /** 117 * Sets whether open debug bones. 118 * @param {boolean} enable 119 */ 120 setDebugBones:function(enable){ 121 this._debugBones = enable; 122 }, 123 124 /** 125 * Sets the time scale of sp.Skeleton. 126 * @param {Number} v 127 */ 128 setTimeScale:function(v){ 129 this._timeScale = v; 130 }, 131 132 /** 133 * Initializes sp.Skeleton with Data. 134 * @param {spine.SkeletonData|String} skeletonDataFile 135 * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData 136 * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations. 137 */ 138 initWithArgs: function (skeletonDataFile, atlasFile, scale) { 139 var argSkeletonFile = skeletonDataFile, argAtlasFile = atlasFile, 140 skeletonData, atlas, ownsSkeletonData; 141 142 if (cc.isString(argSkeletonFile)) { 143 if (cc.isString(argAtlasFile)) { 144 var data = cc.loader.getRes(argAtlasFile); 145 sp._atlasLoader.setAtlasFile(argAtlasFile); 146 atlas = new spine.Atlas(data, sp._atlasLoader); 147 } else { 148 atlas = atlasFile; 149 } 150 scale = scale || 1 / cc.director.getContentScaleFactor(); 151 152 var attachmentLoader = new spine.AtlasAttachmentLoader(atlas); 153 var skeletonJsonReader = new spine.SkeletonJson(attachmentLoader); 154 skeletonJsonReader.scale = scale; 155 156 var skeletonJson = cc.loader.getRes(argSkeletonFile); 157 skeletonData = skeletonJsonReader.readSkeletonData(skeletonJson); 158 atlas.dispose(skeletonJsonReader); 159 ownsSkeletonData = true; 160 } else { 161 skeletonData = skeletonDataFile; 162 ownsSkeletonData = atlasFile; 163 } 164 this.setSkeletonData(skeletonData, ownsSkeletonData); 165 this.init(); 166 }, 167 168 /** 169 * Returns the bounding box of sp.Skeleton. 170 * @returns {cc.Rect} 171 */ 172 boundingBox: function () { 173 var minX = cc.FLT_MAX, minY = cc.FLT_MAX, maxX = cc.FLT_MIN, maxY = cc.FLT_MIN; 174 var scaleX = this.getScaleX(), scaleY = this.getScaleY(), vertices = [], 175 slots = this._skeleton.slots, VERTEX = sp.VERTEX_INDEX; 176 177 for (var i = 0, slotCount = slots.length; i < slotCount; ++i) { 178 var slot = slots[i]; 179 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION) 180 continue; 181 var attachment = slot.attachment; 182 sp._regionAttachment_computeWorldVertices(attachment, slot.skeleton.x, slot.skeleton.y, slot.bone, vertices); 183 minX = Math.min(minX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX); 184 minY = Math.min(minY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY); 185 maxX = Math.max(maxX, vertices[VERTEX.X1] * scaleX, vertices[VERTEX.X4] * scaleX, vertices[VERTEX.X2] * scaleX, vertices[VERTEX.X3] * scaleX); 186 maxY = Math.max(maxY, vertices[VERTEX.Y1] * scaleY, vertices[VERTEX.Y4] * scaleY, vertices[VERTEX.Y2] * scaleY, vertices[VERTEX.Y3] * scaleY); 187 } 188 var position = this.getPosition(); 189 return cc.rect(position.x + minX, position.y + minY, maxX - minX, maxY - minY); 190 }, 191 192 /** 193 * Computes the world SRT from the local SRT for each bone. 194 */ 195 updateWorldTransform: function () { 196 this._skeleton.updateWorldTransform(); 197 }, 198 199 /** 200 * Sets the bones and slots to the setup pose. 201 */ 202 setToSetupPose: function () { 203 this._skeleton.setToSetupPose(); 204 }, 205 206 /** 207 * Sets the bones to the setup pose, using the values from the `BoneData` list in the `SkeletonData`. 208 */ 209 setBonesToSetupPose: function () { 210 this._skeleton.setBonesToSetupPose(); 211 }, 212 213 /** 214 * Sets the slots to the setup pose, using the values from the `SlotData` list in the `SkeletonData`. 215 */ 216 setSlotsToSetupPose: function () { 217 this._skeleton.setSlotsToSetupPose(); 218 }, 219 220 /** 221 * Finds a bone by name. This does a string comparison for every bone. 222 * @param {String} boneName 223 * @returns {spine.Bone} 224 */ 225 findBone: function (boneName) { 226 return this._skeleton.findBone(boneName); 227 }, 228 229 /** 230 * Finds a slot by name. This does a string comparison for every slot. 231 * @param {String} slotName 232 * @returns {spine.Slot} 233 */ 234 findSlot: function (slotName) { 235 return this._skeleton.findSlot(slotName); 236 }, 237 238 /** 239 * Finds a skin by name and makes it the active skin. This does a string comparison for every skin. Note that setting the skin does not change which attachments are visible. 240 * @param {string} skinName 241 * @returns {spine.Skin} 242 */ 243 setSkin: function (skinName) { 244 return this._skeleton.setSkinByName(skinName); 245 }, 246 247 /** 248 * Returns the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin. 249 * @param {String} slotName 250 * @param {String} attachmentName 251 * @returns {spine.RegionAttachment|spine.BoundingBoxAttachment} 252 */ 253 getAttachment: function (slotName, attachmentName) { 254 return this._skeleton.getAttachmentBySlotName(slotName, attachmentName); 255 }, 256 257 /** 258 * Sets the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin. 259 * @param {String} slotName 260 * @param {String} attachmentName 261 */ 262 setAttachment: function (slotName, attachmentName) { 263 this._skeleton.setAttachment(slotName, attachmentName); 264 }, 265 266 /** 267 * Sets the premultiplied alpha value to sp.Skeleton. 268 * @param {Number} alpha 269 */ 270 setOpacityModifyRGB: function (alpha) { 271 this._premultipliedAlpha = alpha; 272 }, 273 274 /** 275 * Returns whether to enable premultiplied alpha. 276 * @returns {boolean} 277 */ 278 isOpacityModifyRGB: function () { 279 return this._premultipliedAlpha; 280 }, 281 282 /** 283 * Sets skeleton data to sp.Skeleton. 284 * @param {spine.SkeletonData} skeletonData 285 * @param {spine.SkeletonData} ownsSkeletonData 286 */ 287 setSkeletonData: function (skeletonData, ownsSkeletonData) { 288 this._skeleton = new spine.Skeleton(skeletonData); 289 this._rootBone = this._skeleton.getRootBone(); 290 this._ownsSkeletonData = ownsSkeletonData; 291 292 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 293 var locSkeleton = this._skeleton, rendererObject, rect; 294 for (var i = 0, n = locSkeleton.drawOrder.length; i < n; i++) { 295 var slot = locSkeleton.drawOrder[i]; 296 var attachment = slot.attachment; 297 if (!(attachment instanceof spine.RegionAttachment)) 298 continue; 299 rendererObject = attachment.rendererObject; 300 rect = cc.rect(rendererObject.x, rendererObject.y, rendererObject.width,rendererObject.height); 301 var sprite = cc.Sprite.create(rendererObject.page._texture, rect, rendererObject.rotate); 302 this.addChild(sprite,-1); 303 slot.currentSprite = sprite; 304 } 305 } 306 }, 307 308 /** 309 * Return the renderer of attachment. 310 * @param {spine.RegionAttachment|spine.BoundingBoxAttachment} regionAttachment 311 * @returns {cc.Node} 312 */ 313 getTextureAtlas: function (regionAttachment) { 314 return regionAttachment.rendererObject.page.rendererObject; 315 }, 316 317 /** 318 * Returns the blendFunc of sp.Skeleton. 319 * @returns {cc.BlendFunc} 320 */ 321 getBlendFunc: function () { 322 return this._blendFunc; 323 }, 324 325 /** 326 * Sets the blendFunc of sp.Skeleton. 327 * @param {cc.BlendFunc|Number} src 328 * @param {Number} [dst] 329 */ 330 setBlendFunc: function (src, dst) { 331 var locBlendFunc = this._blendFunc; 332 if (dst === undefined) { 333 locBlendFunc.src = src.src; 334 locBlendFunc.dst = src.dst; 335 } else { 336 locBlendFunc.src = src; 337 locBlendFunc.dst = dst; 338 } 339 }, 340 341 /** 342 * Update will be called automatically every frame if "scheduleUpdate" is called when the node is "live". 343 * @param {Number} dt Delta time since last update 344 */ 345 update: function (dt) { 346 this._skeleton.update(dt); 347 348 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 349 var locSkeleton = this._skeleton; 350 locSkeleton.updateWorldTransform(); 351 var drawOrder = this._skeleton.drawOrder; 352 for (var i = 0, n = drawOrder.length; i < n; i++) { 353 var slot = drawOrder[i]; 354 var attachment = slot.attachment, selSprite = slot.currentSprite; 355 if (!(attachment instanceof spine.RegionAttachment)) { 356 if(selSprite) 357 selSprite.setVisible(false); 358 continue; 359 } 360 if(!selSprite){ 361 var rendererObject = attachment.rendererObject; 362 var rect = cc.rect(rendererObject.x, rendererObject.y, rendererObject.width,rendererObject.height); 363 var sprite = cc.Sprite.create(rendererObject.page._texture, rect, rendererObject.rotate); 364 this.addChild(sprite,-1); 365 slot.currentSprite = sprite; 366 } 367 selSprite.setVisible(true); 368 //update color and blendFunc 369 selSprite.setBlendFunc(cc.BLEND_SRC, slot.data.additiveBlending ? cc.ONE : cc.BLEND_DST); 370 371 var bone = slot.bone; 372 selSprite.setPosition(bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01, 373 bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11); 374 selSprite.setScale(bone.worldScaleX, bone.worldScaleY); 375 selSprite.setRotation(- (slot.bone.worldRotation + attachment.rotation)); 376 } 377 } 378 }, 379 380 /** 381 * Render function using the canvas 2d context or WebGL context, internal usage only, please do not call this function 382 * @function 383 * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context 384 */ 385 draw: null, 386 387 _drawForWebGL: function () { 388 cc.nodeDrawSetup(this); 389 // cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst); 390 var color = this.getColor(), locSkeleton = this._skeleton; 391 locSkeleton.r = color.r / 255; 392 locSkeleton.g = color.g / 255; 393 locSkeleton.b = color.b / 255; 394 locSkeleton.a = this.getOpacity() / 255; 395 if (this._premultipliedAlpha) { 396 locSkeleton.r *= locSkeleton.a; 397 locSkeleton.g *= locSkeleton.a; 398 locSkeleton.b *= locSkeleton.a; 399 } 400 401 var additive,textureAtlas,attachment,slot, i, n, 402 quad = new cc.V3F_C4B_T2F_Quad(); 403 var locBlendFunc = this._blendFunc; 404 405 for (i = 0, n = locSkeleton.slots.length; i < n; i++) { 406 slot = locSkeleton.drawOrder[i]; 407 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION) 408 continue; 409 attachment = slot.attachment; 410 var regionTextureAtlas = this.getTextureAtlas(attachment); 411 412 if (slot.data.additiveBlending != additive) { 413 if (textureAtlas) { 414 textureAtlas.drawQuads(); 415 textureAtlas.removeAllQuads(); 416 } 417 additive = !additive; 418 cc.glBlendFunc(locBlendFunc.src, additive ? cc.ONE : locBlendFunc.dst); 419 } else if (regionTextureAtlas != textureAtlas && textureAtlas) { 420 textureAtlas.drawQuads(); 421 textureAtlas.removeAllQuads(); 422 } 423 textureAtlas = regionTextureAtlas; 424 425 var quadCount = textureAtlas.getTotalQuads(); 426 if (textureAtlas.getCapacity() == quadCount) { 427 textureAtlas.drawQuads(); 428 textureAtlas.removeAllQuads(); 429 if (!textureAtlas.resizeCapacity(textureAtlas.getCapacity() * 2)) 430 return; 431 } 432 433 sp._regionAttachment_updateQuad(attachment, slot, quad, this._premultipliedAlpha); 434 textureAtlas.updateQuad(quad, quadCount); 435 } 436 437 if (textureAtlas) { 438 textureAtlas.drawQuads(); 439 textureAtlas.removeAllQuads(); 440 } 441 442 var drawingUtil = cc._drawingUtil; 443 if (this._debugSlots) { 444 // Slots. 445 drawingUtil.setDrawColor(0, 0, 255, 255); 446 drawingUtil.setLineWidth(1); 447 448 for (i = 0, n = locSkeleton.slots.length; i < n; i++) { 449 slot = locSkeleton.drawOrder[i]; 450 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION) 451 continue; 452 attachment = slot.attachment; 453 quad = new cc.V3F_C4B_T2F_Quad(); 454 sp._regionAttachment_updateQuad(attachment, slot, quad); 455 456 var points = []; 457 points.push(cc.p(quad.bl.vertices.x, quad.bl.vertices.y)); 458 points.push(cc.p(quad.br.vertices.x, quad.br.vertices.y)); 459 points.push(cc.p(quad.tr.vertices.x, quad.tr.vertices.y)); 460 points.push(cc.p(quad.tl.vertices.x, quad.tl.vertices.y)); 461 drawingUtil.drawPoly(points, 4, true); 462 } 463 } 464 465 if (this._debugBones) { 466 // Bone lengths. 467 var bone; 468 drawingUtil.setLineWidth(2); 469 drawingUtil.setDrawColor(255, 0, 0, 255); 470 471 for (i = 0, n = locSkeleton.bones.length; i < n; i++) { 472 bone = locSkeleton.bones[i]; 473 var x = bone.data.length * bone.m00 + bone.worldX; 474 var y = bone.data.length * bone.m10 + bone.worldY; 475 drawingUtil.drawLine(cc.p(bone.worldX, bone.worldY), cc.p(x, y)); 476 } 477 478 // Bone origins. 479 drawingUtil.setPointSize(4); 480 drawingUtil.setDrawColor(0, 0, 255, 255); // Root bone is blue. 481 482 for (i = 0, n = locSkeleton.bones.length; i < n; i++) { 483 bone = locSkeleton.bones[i]; 484 drawingUtil.drawPoint(cc.p(bone.worldX, bone.worldY)); 485 if (i == 0) { 486 drawingUtil.setDrawColor(0, 255, 0, 255); 487 } 488 } 489 } 490 }, 491 492 _drawForCanvas: function () { 493 if(!this._debugSlots && !this._debugBones){ 494 return; 495 } 496 var locSkeleton = this._skeleton; 497 var attachment,slot, i, n, drawingUtil = cc._drawingUtil; 498 if (this._debugSlots) { 499 // Slots. 500 drawingUtil.setDrawColor(0, 0, 255, 255); 501 drawingUtil.setLineWidth(1); 502 503 var points = []; 504 for (i = 0, n = locSkeleton.slots.length; i < n; i++) { 505 slot = locSkeleton.drawOrder[i]; 506 if (!slot.attachment || slot.attachment.type != sp.ATTACHMENT_TYPE.REGION) 507 continue; 508 attachment = slot.attachment; 509 sp._regionAttachment_updateSlotForCanvas(attachment, slot, points); 510 drawingUtil.drawPoly(points, 4, true); 511 } 512 } 513 514 if (this._debugBones) { 515 // Bone lengths. 516 var bone; 517 drawingUtil.setLineWidth(2); 518 drawingUtil.setDrawColor(255, 0, 0, 255); 519 520 for (i = 0, n = locSkeleton.bones.length; i < n; i++) { 521 bone = locSkeleton.bones[i]; 522 var x = bone.data.length * bone.m00 + bone.worldX; 523 var y = bone.data.length * bone.m10 + bone.worldY; 524 drawingUtil.drawLine(cc.p(bone.worldX, bone.worldY), cc.p(x, y)); 525 } 526 527 // Bone origins. 528 drawingUtil.setPointSize(4); 529 drawingUtil.setDrawColor(0, 0, 255, 255); // Root bone is blue. 530 531 for (i = 0, n = locSkeleton.bones.length; i < n; i++) { 532 bone = locSkeleton.bones[i]; 533 drawingUtil.drawPoint(cc.p(bone.worldX, bone.worldY)); 534 if (i === 0) 535 drawingUtil.setDrawColor(0, 255, 0, 255); 536 } 537 } 538 } 539 }); 540 541 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 542 sp.Skeleton.prototype.draw = sp.Skeleton.prototype._drawForWebGL; 543 }else{ 544 sp.Skeleton.prototype.draw = sp.Skeleton.prototype._drawForCanvas; 545 } 546 547 /** 548 * Creates a skeleton object. 549 * @deprecated since v3.0, please use new sp.Skeleton(skeletonDataFile, atlasFile, scale) instead. 550 * @param {spine.SkeletonData|String} skeletonDataFile 551 * @param {String|spine.Atlas|spine.SkeletonData} atlasFile atlas filename or atlas data or owns SkeletonData 552 * @param {Number} [scale] scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations. 553 * @returns {sp.Skeleton} 554 */ 555 sp.Skeleton.create = function (skeletonDataFile, atlasFile/* or atlas*/, scale) { 556 return new sp.Skeleton(skeletonDataFile, atlasFile, scale); 557 };