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 Bone of Armature, it has bone data, display manager and transform data for armature. 28 * @class 29 * @extends ccs.Node 30 * 31 * @param {String} [name] The name of the bone 32 * @example 33 * 34 * var bone = new ccs.Bone("head"); 35 * 36 * @property {ccs.BoneData} boneData - The bone data 37 * @property {ccs.Armature} armature - The armature 38 * @property {ccs.Bone} parentBone - The parent bone 39 * @property {ccs.Armature} childArmature - The child armature 40 * @property {Array} childrenBone - <@readonly> All children bones 41 * @property {ccs.Tween} tween - <@readonly> Tween 42 * @property {ccs.FrameData} tweenData - <@readonly> The tween data 43 * @property {ccs.ColliderFilter} colliderFilter - The collider filter 44 * @property {ccs.DisplayManager} displayManager - The displayManager 45 * @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. 46 * @property {String} name - The name of the bone 47 * @property {Boolean} blendDirty - Indicate whether the blend is dirty 48 * 49 */ 50 ccs.Bone = ccs.Node.extend(/** @lends ccs.Bone# */{ 51 _boneData: null, 52 _armature: null, 53 _childArmature: null, 54 _displayManager: null, 55 ignoreMovementBoneData: false, 56 _tween: null, 57 _tweenData: null, 58 _parentBone: null, 59 _boneTransformDirty: false, 60 _worldTransform: null, 61 _blendFunc: null, 62 blendDirty: false, 63 _worldInfo: null, 64 _armatureParentBone: null, 65 _dataVersion: 0, 66 _className: "Bone", 67 68 ctor: function (name) { 69 cc.Node.prototype.ctor.call(this); 70 this._tweenData = null; 71 this._parentBone = null; 72 this._armature = null; 73 this._childArmature = null; 74 this._boneData = null; 75 this._tween = null; 76 this._displayManager = null; 77 this.ignoreMovementBoneData = false; 78 79 this._worldTransform = cc.affineTransformMake(1, 0, 0, 1, 0, 0); 80 this._boneTransformDirty = true; 81 this._blendFunc = new cc.BlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 82 this.blendDirty = false; 83 this._worldInfo = null; 84 85 this._armatureParentBone = null; 86 this._dataVersion = 0; 87 88 ccs.Bone.prototype.init.call(this, name); 89 }, 90 91 /** 92 * Initializes a ccs.Bone with the specified name 93 * @param {String} name bone name 94 * @return {Boolean} 95 */ 96 init: function (name) { 97 // cc.Node.prototype.init.call(this); 98 if (name) 99 this._name = name; 100 this._tweenData = new ccs.FrameData(); 101 102 this._tween = new ccs.Tween(this); 103 104 this._displayManager = new ccs.DisplayManager(this); 105 106 this._worldInfo = new ccs.BaseData(); 107 this._boneData = new ccs.BaseData(); 108 109 return true; 110 }, 111 112 /** 113 * Sets the boneData to ccs.Bone. 114 * @param {ccs.BoneData} boneData 115 */ 116 setBoneData: function (boneData) { 117 cc.assert(boneData, "_boneData must not be null"); 118 119 if(this._boneData !== boneData) 120 this._boneData = boneData; 121 122 this.setName(this._boneData.name); 123 this._localZOrder = this._boneData.zOrder; 124 this._displayManager.initDisplayList(boneData); 125 }, 126 127 /** 128 * Returns boneData of ccs.Bone. 129 * @return {ccs.BoneData} 130 */ 131 getBoneData: function () { 132 return this._boneData; 133 }, 134 135 /** 136 * Sets the armature reference to ccs.Bone. 137 * @param {ccs.Armature} armature 138 */ 139 setArmature: function (armature) { 140 this._armature = armature; 141 if (armature) { 142 this._tween.setAnimation(this._armature.getAnimation()); 143 this._dataVersion = this._armature.getArmatureData().dataVersion; 144 this._armatureParentBone = this._armature.getParentBone(); 145 } else 146 this._armatureParentBone = null; 147 }, 148 149 /** 150 * Returns the armature reference of ccs.Bone. 151 * @return {ccs.Armature} 152 */ 153 getArmature: function () { 154 return this._armature; 155 }, 156 157 /** 158 * Updates worldTransform by tween data and updates display state 159 * @param {Number} delta 160 */ 161 update: function (delta) { 162 if (this._parentBone) 163 this._boneTransformDirty = this._boneTransformDirty || this._parentBone.isTransformDirty(); 164 165 if (this._armatureParentBone && !this._boneTransformDirty) 166 this._boneTransformDirty = this._armatureParentBone.isTransformDirty(); 167 168 if (this._boneTransformDirty){ 169 var locTweenData = this._tweenData; 170 if (this._dataVersion >= ccs.CONST_VERSION_COMBINED){ 171 ccs.TransformHelp.nodeConcat(locTweenData, this._boneData); 172 locTweenData.scaleX -= 1; 173 locTweenData.scaleY -= 1; 174 } 175 176 var locWorldInfo = this._worldInfo; 177 locWorldInfo.copy(locTweenData); 178 locWorldInfo.x = locTweenData.x + this._position.x; 179 locWorldInfo.y = locTweenData.y + this._position.y; 180 locWorldInfo.scaleX = locTweenData.scaleX * this._scaleX; 181 locWorldInfo.scaleY = locTweenData.scaleY * this._scaleY; 182 locWorldInfo.skewX = locTweenData.skewX + this._skewX + cc.degreesToRadians(this._rotationX); 183 locWorldInfo.skewY = locTweenData.skewY + this._skewY - cc.degreesToRadians(this._rotationY); 184 185 if(this._parentBone) 186 this._applyParentTransform(this._parentBone); 187 else { 188 if (this._armatureParentBone) 189 this._applyParentTransform(this._armatureParentBone); 190 } 191 192 ccs.TransformHelp.nodeToMatrix(locWorldInfo, this._worldTransform); 193 if (this._armatureParentBone) 194 this._worldTransform = cc.affineTransformConcat(this._worldTransform, this._armature.getNodeToParentTransform()); //TODO TransformConcat 195 } 196 197 ccs.displayFactory.updateDisplay(this, delta, this._boneTransformDirty || this._armature.getArmatureTransformDirty()); 198 for(var i=0; i<this._children.length; i++) { 199 var childBone = this._children[i]; 200 childBone.update(delta); 201 } 202 this._boneTransformDirty = false; 203 }, 204 205 _applyParentTransform: function (parent) { 206 var locWorldInfo = this._worldInfo; 207 var locParentWorldTransform = parent._worldTransform; 208 var locParentWorldInfo = parent._worldInfo; 209 var x = locWorldInfo.x; 210 var y = locWorldInfo.y; 211 locWorldInfo.x = x * locParentWorldTransform.a + y * locParentWorldTransform.c + locParentWorldInfo.x; 212 locWorldInfo.y = x * locParentWorldTransform.b + y * locParentWorldTransform.d + locParentWorldInfo.y; 213 locWorldInfo.scaleX = locWorldInfo.scaleX * locParentWorldInfo.scaleX; 214 locWorldInfo.scaleY = locWorldInfo.scaleY * locParentWorldInfo.scaleY; 215 locWorldInfo.skewX = locWorldInfo.skewX + locParentWorldInfo.skewX; 216 locWorldInfo.skewY = locWorldInfo.skewY + locParentWorldInfo.skewY; 217 }, 218 219 /** 220 * Sets BlendFunc to ccs.Bone. 221 * @param {cc.BlendFunc|Number} blendFunc blendFunc or src of blendFunc 222 * @param {Number} [dst] dst of blendFunc 223 */ 224 setBlendFunc: function (blendFunc, dst) { 225 var locBlendFunc = this._blendFunc, srcValue, dstValue; 226 if(dst === undefined){ 227 srcValue = blendFunc.src; 228 dstValue = blendFunc.dst; 229 } else { 230 srcValue = blendFunc; 231 dstValue = dst; 232 } 233 if (locBlendFunc.src !== srcValue || locBlendFunc.dst !== dstValue) { 234 locBlendFunc.src = srcValue; 235 locBlendFunc.dst = dstValue; 236 this.blendDirty = true; 237 } 238 }, 239 240 /** 241 * Updates display color 242 */ 243 updateColor: function () { 244 var display = this._displayManager.getDisplayRenderNode(); 245 if (display !== null) { 246 var cmd = this._renderCmd; 247 display.setColor( 248 cc.color( 249 cmd._displayedColor.r * this._tweenData.r / 255, 250 cmd._displayedColor.g * this._tweenData.g / 255, 251 cmd._displayedColor.b * this._tweenData.b / 255)); 252 display.setOpacity(cmd._displayedOpacity * this._tweenData.a / 255); 253 } 254 }, 255 256 /** 257 * Updates display zOrder 258 */ 259 updateZOrder: function () { 260 if (this._armature.getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) { 261 this.setLocalZOrder(this._tweenData.zOrder + this._boneData.zOrder); 262 } else { 263 this.setLocalZOrder(this._tweenData.zOrder); 264 } 265 }, 266 267 /** 268 * Adds a child to this bone, and it will let this child call setParent(ccs.Bone) function to set self to it's parent 269 * @param {ccs.Bone} child 270 */ 271 addChildBone: function (child) { 272 cc.assert(child, "Argument must be non-nil"); 273 cc.assert(!child.parentBone, "child already added. It can't be added again"); 274 275 if (this._children.indexOf(child) < 0) { 276 this._children.push(child); 277 child.setParentBone(this); 278 } 279 }, 280 281 /** 282 * Removes a child bone 283 * @param {ccs.Bone} bone 284 * @param {Boolean} recursion 285 */ 286 removeChildBone: function (bone, recursion) { 287 if (this._children.length > 0 && this._children.getIndex(bone) !== -1 ) { 288 if(recursion) { 289 var ccbones = bone._children; 290 for(var i=0; i<ccbones.length; i++){ 291 var ccBone = ccbones[i]; 292 bone.removeChildBone(ccBone, recursion); 293 } 294 } 295 296 bone.setParentBone(null); 297 bone.getDisplayManager().setCurrentDecorativeDisplay(null); 298 cc.arrayRemoveObject(this._children, bone); 299 } 300 }, 301 302 /** 303 * Removes itself from its parent ccs.Bone. 304 * @param {Boolean} recursion 305 */ 306 removeFromParent: function (recursion) { 307 if (this._parentBone) 308 this._parentBone.removeChildBone(this, recursion); 309 }, 310 311 /** 312 * Sets parent bone to ccs.Bone. 313 * If _parent is NUll, then also remove this bone from armature. 314 * It will not set the ccs.Armature, if you want to add the bone to a ccs.Armature, you should use ccs.Armature.addBone(bone, parentName). 315 * @param {ccs.Bone} parent the parent bone. 316 */ 317 setParentBone: function (parent) { 318 this._parentBone = parent; 319 }, 320 321 /** 322 * Returns the parent bone of ccs.Bone. 323 * @returns {ccs.Bone} 324 */ 325 getParentBone: function(){ 326 return this._parentBone; 327 }, 328 329 /** 330 * Sets ccs.Bone's child armature 331 * @param {ccs.Armature} armature 332 */ 333 setChildArmature: function (armature) { 334 if (this._childArmature !== armature) { 335 if (armature == null && this._childArmature) 336 this._childArmature.setParentBone(null); 337 this._childArmature = armature; 338 } 339 }, 340 341 /** 342 * Returns ccs.Bone's child armature. 343 * @return {ccs.Armature} 344 */ 345 getChildArmature: function () { 346 return this._childArmature; 347 }, 348 349 /** 350 * Return the tween of ccs.Bone 351 * @return {ccs.Tween} 352 */ 353 getTween: function () { 354 return this._tween; 355 }, 356 357 /** 358 * Sets the local zOrder to ccs.Bone. 359 * @param {Number} zOrder 360 */ 361 setLocalZOrder: function (zOrder) { 362 if (this._localZOrder !== zOrder) 363 cc.Node.prototype.setLocalZOrder.call(this, zOrder); 364 }, 365 366 /** 367 * Return the worldTransform of ccs.Bone. 368 * @returns {cc.AffineTransform} 369 */ 370 getNodeToArmatureTransform: function(){ 371 return this._worldTransform; 372 }, 373 374 /** 375 * Returns the world transform of ccs.Bone. 376 * @override 377 * @returns {cc.AffineTransform} 378 */ 379 getNodeToWorldTransform: function(){ 380 return cc.affineTransformConcat(this._worldTransform, this._armature.getNodeToWorldTransform()); 381 }, 382 383 /** 384 * Returns the display render node. 385 * @returns {cc.Node} 386 */ 387 getDisplayRenderNode: function () { 388 return this._displayManager.getDisplayRenderNode(); 389 }, 390 391 /** 392 * Returns the type of display render node 393 * @returns {Number} 394 */ 395 getDisplayRenderNodeType: function () { 396 return this._displayManager.getDisplayRenderNodeType(); 397 }, 398 399 /** 400 * Add display and use _displayData init the display. 401 * If index already have a display, then replace it. 402 * If index is current display index, then also change display to _index 403 * @param {ccs.DisplayData} displayData it include the display information, like DisplayType. 404 * If you want to create a sprite display, then create a CCSpriteDisplayData param 405 *@param {Number} index the index of the display you want to replace or add to 406 * -1 : append display from back 407 */ 408 addDisplay: function (displayData, index) { 409 index = index || 0; 410 return this._displayManager.addDisplay(displayData, index); 411 }, 412 413 /** 414 * Removes display by index. 415 * @param {Number} index display renderer's index 416 */ 417 removeDisplay: function (index) { 418 this._displayManager.removeDisplay(index); 419 }, 420 421 /** 422 * Changes display by index 423 * @deprecated since v3.0, please use changeDisplayWithIndex instead. 424 * @param {Number} index 425 * @param {Boolean} force 426 */ 427 changeDisplayByIndex: function (index, force) { 428 cc.log("changeDisplayByIndex is deprecated. Use changeDisplayWithIndex instead."); 429 this.changeDisplayWithIndex(index, force); 430 }, 431 432 /** 433 * Changes display by name 434 * @deprecated since v3.0, please use changeDisplayWithName instead. 435 * @param {String} name 436 * @param {Boolean} force 437 */ 438 changeDisplayByName: function(name, force){ 439 cc.log("changeDisplayByName is deprecated. Use changeDisplayWithName instead."); 440 this.changeDisplayWithName(name, force); 441 }, 442 443 /** 444 * Changes display with index 445 * @param {Number} index 446 * @param {Boolean} force 447 */ 448 changeDisplayWithIndex: function (index, force) { 449 this._displayManager.changeDisplayWithIndex(index, force); 450 }, 451 452 /** 453 * Changes display with name 454 * @param {String} name 455 * @param {Boolean} force 456 */ 457 changeDisplayWithName: function (name, force) { 458 this._displayManager.changeDisplayWithName(name, force); 459 }, 460 461 /** 462 * Returns the collide detector of ccs.Bone. 463 * @returns {*} 464 */ 465 getColliderDetector: function(){ 466 var decoDisplay = this._displayManager.getCurrentDecorativeDisplay(); 467 if (decoDisplay){ 468 var detector = decoDisplay.getColliderDetector(); 469 if (detector) 470 return detector; 471 } 472 return null; 473 }, 474 475 /** 476 * Sets collider filter to ccs.Bone. 477 * @param {ccs.ColliderFilter} filter 478 */ 479 setColliderFilter: function (filter) { 480 var displayList = this._displayManager.getDecorativeDisplayList(); 481 for (var i = 0; i < displayList.length; i++) { 482 var locDecoDisplay = displayList[i]; 483 var locDetector = locDecoDisplay.getColliderDetector(); 484 if (locDetector) 485 locDetector.setColliderFilter(filter); 486 } 487 }, 488 489 /** 490 * Returns collider filter of ccs.Bone. 491 * @returns {cc.ColliderFilter} 492 */ 493 getColliderFilter: function () { 494 var decoDisplay = this.displayManager.getCurrentDecorativeDisplay(); 495 if (decoDisplay) { 496 var detector = decoDisplay.getColliderDetector(); 497 if (detector) 498 return detector.getColliderFilter(); 499 } 500 return null; 501 }, 502 503 /** 504 * Sets ccs.Bone's transform dirty flag. 505 * @param {Boolean} dirty 506 */ 507 setTransformDirty: function (dirty) { 508 this._boneTransformDirty = dirty; 509 }, 510 511 /** 512 * Returns ccs.Bone's transform dirty flag whether is dirty. 513 * @return {Boolean} 514 */ 515 isTransformDirty: function () { 516 return this._boneTransformDirty; 517 }, 518 519 /** 520 * displayManager dirty getter 521 * @return {ccs.DisplayManager} 522 */ 523 getDisplayManager: function () { 524 return this._displayManager; 525 }, 526 527 /** 528 * When CCArmature play a animation, if there is not a CCMovementBoneData of this bone in this CCMovementData, this bone will hide. <br/> 529 * Set IgnoreMovementBoneData to true, then this bone will also show. 530 * @param {Boolean} bool 531 */ 532 setIgnoreMovementBoneData: function (bool) { 533 this._ignoreMovementBoneData = bool; 534 }, 535 536 /** 537 * Returns whether is ignore movement bone data. 538 * @returns {Boolean} 539 */ 540 isIgnoreMovementBoneData: function(){ 541 return this._ignoreMovementBoneData; 542 }, 543 544 /** 545 * Returns the blendFunc of ccs.Bone. 546 * @return {cc.BlendFunc} 547 */ 548 getBlendFunc: function () { 549 return new cc.BlendFunc(this._blendFunc.src, this._blendFunc.dst); 550 }, 551 552 /** 553 * Sets blend dirty flag 554 * @param {Boolean} dirty 555 */ 556 setBlendDirty: function (dirty) { 557 this._blendDirty = dirty; 558 }, 559 560 /** 561 * Returns the blend dirty flag whether is dirty. 562 * @returns {Boolean|*|ccs.Bone._blendDirty} 563 */ 564 isBlendDirty: function () { 565 return this._blendDirty; 566 }, 567 568 /** 569 * Returns the tweenData of ccs.Bone. 570 * @return {ccs.FrameData} 571 */ 572 getTweenData: function () { 573 return this._tweenData; 574 }, 575 576 /** 577 * Returns the world information of ccs.Bone. 578 * @returns {ccs.BaseData} 579 */ 580 getWorldInfo: function(){ 581 return this._worldInfo; 582 }, 583 584 /** 585 * Returns the children of ccs.Bone 586 * @return {Array} 587 * @deprecated since v3.0, please use getChildren instead. 588 */ 589 getChildrenBone: function () { 590 return this._children; 591 }, 592 593 /** 594 * Returns the worldTransform of ccs.Bone. 595 * @return {cc.AffineTransform} 596 * @deprecated since v3.0, please use getNodeToArmatureTransform instead. 597 */ 598 nodeToArmatureTransform: function () { 599 return this.getNodeToArmatureTransform(); 600 }, 601 602 /** 603 * @deprecated 604 * Returns the world affine transform matrix. The matrix is in Pixels. 605 * @returns {cc.AffineTransform} 606 */ 607 nodeToWorldTransform: function () { 608 return this.getNodeToWorldTransform(); 609 }, 610 611 /** 612 * Returns the collider body list in this bone. 613 * @returns {Array|null} 614 * @deprecated since v3.0, please use getColliderDetector to get a delector, and calls its getColliderBodyList instead. 615 */ 616 getColliderBodyList: function () { 617 var detector = this.getColliderDetector(); 618 if(detector) 619 return detector.getColliderBodyList(); 620 return null; 621 }, 622 623 /** 624 * Returns whether is ignore movement bone data. 625 * @return {Boolean} 626 * @deprecated since v3.0, please isIgnoreMovementBoneData instead. 627 */ 628 getIgnoreMovementBoneData: function () { 629 return this.isIgnoreMovementBoneData(); 630 }, 631 632 _createRenderCmd: function(){ 633 if(cc._renderType === cc.game.RENDER_TYPE_CANVAS) 634 return new ccs.Bone.CanvasRenderCmd(this); 635 else 636 return new ccs.Bone.WebGLRenderCmd(this); 637 } 638 }); 639 640 var _p = ccs.Bone.prototype; 641 642 // Extended properties 643 /** @expose */ 644 _p.boneData; 645 cc.defineGetterSetter(_p, "boneData", _p.getBoneData, _p.setBoneData); 646 /** @expose */ 647 _p.armature; 648 cc.defineGetterSetter(_p, "armature", _p.getArmature, _p.setArmature); 649 /** @expose */ 650 _p.childArmature; 651 cc.defineGetterSetter(_p, "childArmature", _p.getChildArmature, _p.setChildArmature); 652 /** @expose */ 653 _p.childrenBone; 654 cc.defineGetterSetter(_p, "childrenBone", _p.getChildrenBone); 655 /** @expose */ 656 _p.tween; 657 cc.defineGetterSetter(_p, "tween", _p.getTween); 658 /** @expose */ 659 _p.tweenData; 660 cc.defineGetterSetter(_p, "tweenData", _p.getTweenData); 661 /** @expose */ 662 _p.colliderFilter; 663 cc.defineGetterSetter(_p, "colliderFilter", _p.getColliderFilter, _p.setColliderFilter); 664 665 _p = null; 666 667 /** 668 * Allocates and initializes a bone. 669 * @return {ccs.Bone} 670 * @deprecated since v3.1, please use new construction instead 671 */ 672 ccs.Bone.create = function (name) { 673 return new ccs.Bone(name); 674 }; 675 676 ccs.Bone.RenderCmd = { 677 _updateColor: function(){ 678 var node = this._node; 679 var display = node._displayManager.getDisplayRenderNode(); 680 if (display !== null) { 681 var displayCmd = display._renderCmd; 682 display.setColor(cc.color( node._tweenData.r, node._tweenData.g, node._tweenData.g)); 683 display.setOpacity(node._tweenData.a); 684 displayCmd._syncDisplayColor(this._displayedColor); 685 displayCmd._syncDisplayOpacity(this._displayedOpacity); 686 displayCmd._updateColor(); 687 } 688 } 689 }; 690 691 (function(){ 692 ccs.Bone.CanvasRenderCmd = function(renderable){ 693 cc.Node.CanvasRenderCmd.call(this, renderable); 694 this._needDraw = false; 695 }; 696 697 var proto = ccs.Bone.CanvasRenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype); 698 cc.inject(ccs.Bone.RenderCmd, proto); 699 proto.constructor = ccs.Bone.CanvasRenderCmd; 700 })(); 701 702 (function(){ 703 if(!cc.Node.WebGLRenderCmd) 704 return; 705 ccs.Bone.WebGLRenderCmd = function(renderable){ 706 cc.Node.WebGLRenderCmd.call(this, renderable); 707 this._needDraw = false; 708 }; 709 710 var proto = ccs.Bone.WebGLRenderCmd.prototype = Object.create(cc.Node.WebGLRenderCmd.prototype); 711 cc.inject(ccs.Bone.RenderCmd, proto); 712 proto.constructor = ccs.Bone.WebGLRenderCmd; 713 })(); 714