1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 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 //Possible OpenGL projections used by director 28 /** 29 * sets a 2D projection (orthogonal projection) 30 * @constant 31 * @type Number 32 */ 33 cc.DIRECTOR_PROJECTION_2D = 0; 34 35 cc.g_NumberOfDraws = 0; 36 37 /** 38 * sets a 3D projection with a fovy=60, znear=0.5f and zfar=1500. 39 * @constant 40 * @type Number 41 */ 42 cc.DIRECTOR_PROJECTION_3D = 1; 43 44 /** 45 * it calls "updateProjection" on the projection delegate. 46 * @constant 47 * @type Number 48 */ 49 cc.DIRECTOR_PROJECTION_CUSTOM = 3; 50 51 /** 52 * Default projection is 3D projection 53 * @constant 54 * @type Number 55 */ 56 cc.DIRECTOR_PROJECTION_DEFAULT = cc.DIRECTOR_PROJECTION_3D; 57 58 //---------------------------------------------------------------------------------------------------------------------- 59 //Possible device orientations 60 /** 61 * Device oriented vertically, home button on the bottom (UIDeviceOrientationPortrait) 62 * @constant 63 * @type Number 64 */ 65 cc.DEVICE_ORIENTATION_PORTRAIT = 0; 66 67 /** 68 * Device oriented horizontally, home button on the right (UIDeviceOrientationLandscapeLeft) 69 * @constant 70 * @type Number 71 */ 72 cc.DEVICE_ORIENTATION_LANDSCAPE_LEFT = 1; 73 74 /** 75 * Device oriented vertically, home button on the top (UIDeviceOrientationPortraitUpsideDown) 76 * @constant 77 * @type Number 78 */ 79 cc.DEVICE_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 2; 80 81 /** 82 * Device oriented horizontally, home button on the left (UIDeviceOrientationLandscapeRight) 83 * @constant 84 * @type Number 85 */ 86 cc.DEVICE_ORIENTATION_LANDSCAPE_RIGHT = 3; 87 88 /** 89 * In browsers, we only support 2 orientations by change window size. 90 * @constant 91 * @type Number 92 */ 93 cc.DEVICE_MAX_ORIENTATIONS = 2; 94 95 /** 96 * OpenGL projection protocol 97 * @class 98 * @extends cc.Class 99 */ 100 cc.DirectorDelegate = cc.Class.extend(/** @lends cc.DirectorDelegate# */{ 101 /** 102 * Called by CCDirector when the projection is updated, and "custom" projection is used 103 */ 104 updateProjection:function () { 105 } 106 }); 107 108 cc.GLToClipTransform = function (transformOut) { 109 var projection = new cc.kmMat4(); 110 cc.kmGLGetMatrix(cc.KM_GL_PROJECTION, projection); 111 112 var modelview = new cc.kmMat4(); 113 cc.kmGLGetMatrix(cc.KM_GL_MODELVIEW, modelview); 114 115 cc.kmMat4Multiply(transformOut, projection, modelview); 116 }; 117 //---------------------------------------------------------------------------------------------------------------------- 118 119 /** 120 * @namespace <p> 121 * cc.director is a singleton of DisplayLinkDirector type director.<br/> 122 * Since the cc.director is a singleton, you don't need to call any constructor or create functions,<br/> 123 * the standard way to use it is by calling:<br/> 124 * - cc.director.methodName(); <br/> 125 * 126 * It creates and handle the main Window and manages how and when to execute the Scenes.<br/> 127 * <br/> 128 * The cc.Director is also responsible for:<br/> 129 * - initializing the OpenGL context<br/> 130 * - setting the OpenGL pixel format (default on is RGB565)<br/> 131 * - setting the OpenGL pixel format (default on is RGB565)<br/> 132 * - setting the OpenGL buffer depth (default one is 0-bit)<br/> 133 * - setting the projection (default one is 3D)<br/> 134 * - setting the orientation (default one is Protrait)<br/> 135 * <br/> 136 * <br/> 137 * The cc.director also sets the default OpenGL context:<br/> 138 * - GL_TEXTURE_2D is enabled<br/> 139 * - GL_VERTEX_ARRAY is enabled<br/> 140 * - GL_COLOR_ARRAY is enabled<br/> 141 * - GL_TEXTURE_COORD_ARRAY is enabled<br/> 142 * </p> 143 * <p> 144 * With DisplayLinkDirector functionality, cc.director synchronizes timers with the refresh rate of the display.<br/> 145 * Features and Limitations:<br/> 146 * - Scheduled timers & drawing are synchronizes with the refresh rate of the display<br/> 147 * - Only supports animation intervals of 1/60 1/30 & 1/15<br/> 148 * </p> 149 * @name cc.director 150 */ 151 cc.Director = cc.Class.extend(/** @lends cc.director# */{ 152 //Variables 153 _landscape:false, 154 _nextDeltaTimeZero:false, 155 _paused:false, 156 _purgeDirectorInNextLoop:false, 157 _sendCleanupToScene:false, 158 _animationInterval:0.0, 159 _oldAnimationInterval:0.0, 160 _projection:0, 161 _accumDt:0.0, 162 _contentScaleFactor:1.0, 163 164 _displayStats:false, 165 _deltaTime:0.0, 166 _frameRate:0.0, 167 168 _FPSLabel:null, 169 _SPFLabel:null, 170 _drawsLabel:null, 171 172 _winSizeInPoints:null, 173 174 _lastUpdate:null, 175 _nextScene:null, 176 _notificationNode:null, 177 _openGLView:null, 178 _scenesStack:null, 179 _projectionDelegate:null, 180 _runningScene:null, 181 182 _frames:0, 183 _totalFrames:0, 184 _secondsPerFrame:0, 185 186 _dirtyRegion:null, 187 188 _scheduler:null, 189 _actionManager:null, 190 _eventProjectionChanged: null, 191 _eventAfterDraw: null, 192 _eventAfterVisit: null, 193 _eventAfterUpdate: null, 194 195 ctor:function () { 196 var self = this; 197 self._lastUpdate = Date.now(); 198 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function(){ 199 self._lastUpdate = Date.now(); 200 }); 201 }, 202 203 /** 204 * initializes cc.director 205 * @return {Boolean} 206 */ 207 init:function () { 208 // scenes 209 this._oldAnimationInterval = this._animationInterval = 1.0 / cc.defaultFPS; 210 this._scenesStack = []; 211 // Set default projection (3D) 212 this._projection = cc.DIRECTOR_PROJECTION_DEFAULT; 213 // projection delegate if "Custom" projection is used 214 this._projectionDelegate = null; 215 216 //FPS 217 this._accumDt = 0; 218 this._frameRate = 0; 219 this._displayStats = false;//can remove 220 this._totalFrames = this._frames = 0; 221 this._lastUpdate = Date.now(); 222 223 //Paused? 224 this._paused = false; 225 226 //purge? 227 this._purgeDirectorInNextLoop = false; 228 229 this._winSizeInPoints = cc.size(0, 0); 230 231 this._openGLView = null; 232 this._contentScaleFactor = 1.0; 233 234 //scheduler 235 this._scheduler = new cc.Scheduler(); 236 //action manager 237 this._actionManager = new cc.ActionManager(); 238 this._scheduler.scheduleUpdateForTarget(this._actionManager, cc.Scheduler.PRIORITY_SYSTEM, false); 239 240 this._eventAfterDraw = new cc.EventCustom(cc.Director.EVENT_AFTER_DRAW); 241 this._eventAfterDraw.setUserData(this); 242 this._eventAfterVisit = new cc.EventCustom(cc.Director.EVENT_AFTER_VISIT); 243 this._eventAfterVisit.setUserData(this); 244 this._eventAfterUpdate = new cc.EventCustom(cc.Director.EVENT_AFTER_UPDATE); 245 this._eventAfterUpdate.setUserData(this); 246 this._eventProjectionChanged = new cc.EventCustom(cc.Director.EVENT_PROJECTION_CHANGED); 247 this._eventProjectionChanged.setUserData(this); 248 249 return true; 250 }, 251 252 /** 253 * calculates delta time since last time it was called 254 */ 255 calculateDeltaTime:function () { 256 var now = Date.now(); 257 258 // new delta time. 259 if (this._nextDeltaTimeZero) { 260 this._deltaTime = 0; 261 this._nextDeltaTimeZero = false; 262 } else { 263 this._deltaTime = (now - this._lastUpdate) / 1000; 264 } 265 266 if ((cc.game.config[cc.game.CONFIG_KEY.debugMode] > 0) && (this._deltaTime > 0.2)) 267 this._deltaTime = 1 / 60.0; 268 269 this._lastUpdate = now; 270 }, 271 272 /** 273 * <p> 274 * converts a UIKit coordinate to an OpenGL coordinate<br/> 275 * Useful to convert (multi) touches coordinates to the current layout (portrait or landscape) 276 * </p> 277 * @param {cc.Point} uiPoint 278 * @return {cc.Point} 279 */ 280 convertToGL:function (uiPoint) { 281 var transform = new cc.kmMat4(); 282 cc.GLToClipTransform(transform); 283 284 var transformInv = new cc.kmMat4(); 285 cc.kmMat4Inverse(transformInv, transform); 286 287 // Calculate z=0 using -> transform*[0, 0, 0, 1]/w 288 var zClip = transform.mat[14] / transform.mat[15]; 289 290 var glSize = this._openGLView.getDesignResolutionSize(); 291 var clipCoord = new cc.kmVec3(2.0 * uiPoint.x / glSize.width - 1.0, 1.0 - 2.0 * uiPoint.y / glSize.height, zClip); 292 293 var glCoord = new cc.kmVec3(); 294 cc.kmVec3TransformCoord(glCoord, clipCoord, transformInv); 295 296 return cc.p(glCoord.x, glCoord.y); 297 }, 298 299 /** 300 * <p>converts an OpenGL coordinate to a UIKit coordinate<br/> 301 * Useful to convert node points to window points for calls such as glScissor</p> 302 * @param {cc.Point} glPoint 303 * @return {cc.Point} 304 */ 305 convertToUI:function (glPoint) { 306 var transform = new cc.kmMat4(); 307 cc.GLToClipTransform(transform); 308 309 var clipCoord = new cc.kmVec3(); 310 // Need to calculate the zero depth from the transform. 311 var glCoord = new cc.kmVec3(glPoint.x, glPoint.y, 0.0); 312 cc.kmVec3TransformCoord(clipCoord, glCoord, transform); 313 314 var glSize = this._openGLView.getDesignResolutionSize(); 315 return cc.p(glSize.width * (clipCoord.x * 0.5 + 0.5), glSize.height * (-clipCoord.y * 0.5 + 0.5)); 316 }, 317 318 /** 319 * Draw the scene. This method is called every frame. Don't call it manually. 320 */ 321 drawScene: function() { 322 // calculate "global" dt 323 this.calculateDeltaTime(); 324 325 //tick before glClear: issue #533 326 if (!this._paused) { 327 this._scheduler.update(this._deltaTime); 328 cc.eventManager.dispatchEvent(this._eventAfterUpdate); 329 } 330 331 this._clear(); 332 333 /* to avoid flickr, nextScene MUST be here: after tick and before draw. 334 XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ 335 if (this._nextScene) { 336 this.setNextScene(); 337 } 338 339 if (this._beforeVisitScene) this._beforeVisitScene(); 340 341 // draw the scene 342 if (this._runningScene) { 343 this._runningScene.visit(); 344 cc.eventManager.dispatchEvent(this._eventAfterVisit); 345 } 346 347 // draw the notifications node 348 if (this._notificationNode) 349 this._notificationNode.visit(); 350 351 if (this._displayStats) 352 this._showStats(); 353 354 if (this._afterVisitScene) this._afterVisitScene(); 355 356 //TODO 357 cc.eventManager.dispatchEvent(this._eventAfterDraw); 358 this._totalFrames++; 359 360 if (this._displayStats) 361 this._calculateMPF(); 362 }, 363 364 _clearCanvas: function() { 365 var viewport = this._openGLView.getViewPortRect(); 366 cc._renderContext.clearRect(-viewport.x, viewport.y, viewport.width, -viewport.height); 367 }, 368 369 _clearWebGL: function() { 370 var gl = cc._renderContext; 371 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 372 }, 373 374 _beforeVisitScene: null, 375 _afterVisitScene: null, 376 377 _beforeVisitSceneWebGL: function() { 378 cc.kmGLPushMatrix(); 379 }, 380 381 _afterVisitSceneWebGL: function() { 382 cc.kmGLPopMatrix(); 383 }, 384 385 /** 386 * end director 387 */ 388 end:function () { 389 this._purgeDirectorInNextLoop = true; 390 }, 391 392 /** 393 * <p>get the size in pixels of the surface. It could be different than the screen size.<br/> 394 * High-res devices might have a higher surface size than the screen size.<br/> 395 * Only available when compiled using SDK >= 4.0. 396 * </p> 397 * @return {Number} 398 */ 399 getContentScaleFactor:function () { 400 return this._contentScaleFactor; 401 }, 402 403 /** 404 * <p> 405 * This object will be visited after the main scene is visited.<br/> 406 * This object MUST implement the "visit" selector.<br/> 407 * Useful to hook a notification object, like CCNotifications (http://github.com/manucorporat/CCNotifications) 408 * </p> 409 * @return {cc.Node} 410 */ 411 getNotificationNode:function () { 412 return this._notificationNode; 413 }, 414 415 /** 416 * <p> 417 * returns the size of the OpenGL view in points.<br/> 418 * It takes into account any possible rotation (device orientation) of the window 419 * </p> 420 * @return {cc.Size} 421 */ 422 getWinSize:function () { 423 return this._winSizeInPoints; 424 }, 425 426 /** 427 * <p> 428 * returns the size of the OpenGL view in pixels.<br/> 429 * It takes into account any possible rotation (device orientation) of the window.<br/> 430 * On Mac winSize and winSizeInPixels return the same value. 431 * </p> 432 * @return {cc.Size} 433 */ 434 getWinSizeInPixels:function () { 435 return cc.size(this._winSizeInPoints.width * this._contentScaleFactor, this._winSizeInPoints.height * this._contentScaleFactor); 436 }, 437 438 getVisibleSize:function () { 439 if (this._openGLView) { 440 return this._openGLView.getVisibleSize(); 441 } else { 442 return this.getWinSize(); 443 } 444 }, 445 446 getVisibleOrigin:function () { 447 if (this._openGLView) { 448 return this._openGLView.getVisibleOrigin(); 449 } else { 450 return cc.p(0,0); 451 } 452 }, 453 454 getZEye:function () { 455 return (this._winSizeInPoints.height / 1.1566 ); 456 }, 457 458 /** 459 * pause director 460 */ 461 pause:function () { 462 if (this._paused) 463 return; 464 465 this._oldAnimationInterval = this._animationInterval; 466 // when paused, don't consume CPU 467 this.setAnimationInterval(1 / 4.0); 468 this._paused = true; 469 }, 470 471 /** 472 * <p> 473 * Pops out a scene from the queue.<br/> 474 * This scene will replace the running one.<br/> 475 * The running scene will be deleted. If there are no more scenes in the stack the execution is terminated.<br/> 476 * ONLY call it if there is a running scene. 477 * </p> 478 */ 479 popScene:function () { 480 if(!this._runningScene) 481 throw "running scene should not null"; 482 483 this._scenesStack.pop(); 484 var c = this._scenesStack.length; 485 486 if (c == 0) 487 this.end(); 488 else { 489 this._sendCleanupToScene = true; 490 this._nextScene = this._scenesStack[c - 1]; 491 } 492 }, 493 494 /** 495 * Removes cached all cocos2d cached data. It will purge the cc.textureCache, cc.spriteFrameCache, cc.animationCache 496 */ 497 purgeCachedData:function () { 498 cc.animationCache._clear(); 499 cc.spriteFrameCache._clear(); 500 cc.textureCache._clear(); 501 }, 502 503 /** 504 * purge Director 505 */ 506 purgeDirector:function () { 507 //cleanup scheduler 508 this.getScheduler().unscheduleAllCallbacks(); 509 510 // don't release the event handlers 511 // They are needed in case the director is run again 512 513 if (this._runningScene) { 514 this._runningScene.onExitTransitionDidStart(); 515 this._runningScene.onExit(); 516 this._runningScene.cleanup(); 517 } 518 519 this._runningScene = null; 520 this._nextScene = null; 521 522 // remove all objects, but don't release it. 523 // runScene might be executed after 'end'. 524 this._scenesStack.length = 0; 525 526 this.stopAnimation(); 527 528 // Clear all caches 529 this.purgeCachedData(); 530 531 cc.CHECK_GL_ERROR_DEBUG(); 532 }, 533 534 /** 535 * <p> 536 * Suspends the execution of the running scene, pushing it on the stack of suspended scenes.<br/> 537 * The new scene will be executed.<br/> 538 * Try to avoid big stacks of pushed scenes to reduce memory allocation.<br/> 539 * ONLY call it if there is a running scene. 540 * </p> 541 * @param {cc.Scene} scene 542 */ 543 pushScene:function (scene) { 544 if(!scene) 545 throw "the scene should not null"; 546 547 this._sendCleanupToScene = false; 548 549 this._scenesStack.push(scene); 550 this._nextScene = scene; 551 }, 552 553 /** 554 * Run a scene. Replaces the running scene with a new one when the scene is running. 555 * @param {cc.Scene} scene 556 */ 557 runScene:function(scene){ 558 if(!scene) 559 throw "the scene should not be null"; 560 if(!this._runningScene){ 561 //start scene 562 this.pushScene(scene); 563 this.startAnimation(); 564 }else{ 565 //replace scene 566 var i = this._scenesStack.length; 567 if(i === 0){ 568 this._sendCleanupToScene = true; 569 this._scenesStack[i] = scene; 570 this._nextScene = scene; 571 } else { 572 this._sendCleanupToScene = true; 573 this._scenesStack[i - 1] = scene; 574 this._nextScene = scene; 575 } 576 } 577 }, 578 579 /** 580 * resume director 581 */ 582 resume:function () { 583 if (!this._paused) { 584 return; 585 } 586 587 this.setAnimationInterval(this._oldAnimationInterval); 588 this._lastUpdate = Date.now(); 589 if (!this._lastUpdate) { 590 cc.log("cocos2d: Director: Error in gettimeofday"); 591 } 592 593 this._paused = false; 594 this._deltaTime = 0; 595 }, 596 597 /** 598 * enables/disables OpenGL alpha blending 599 * @param {Boolean} on 600 */ 601 setAlphaBlending:function (on) { 602 if (on) 603 cc.glBlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 604 else 605 cc.glBlendFunc(cc._renderContext.ONE, cc._renderContext.ZERO); 606 //cc.CHECK_GL_ERROR_DEBUG(); 607 }, 608 609 /** 610 * <p> 611 * The size in pixels of the surface. It could be different than the screen size.<br/> 612 * High-res devices might have a higher surface size than the screen size.<br/> 613 * Only available when compiled using SDK >= 4.0. 614 * </p> 615 * @param {Number} scaleFactor 616 */ 617 setContentScaleFactor:function (scaleFactor) { 618 if (scaleFactor != this._contentScaleFactor) { 619 this._contentScaleFactor = scaleFactor; 620 this._createStatsLabel(); 621 } 622 }, 623 624 /** 625 * enables/disables OpenGL depth test 626 * @param {Boolean} on 627 */ 628 setDepthTest:function (on) { 629 if(cc._renderType === cc._RENDER_TYPE_CANVAS) 630 return; 631 632 var loc_gl= cc._renderContext; 633 if (on) { 634 loc_gl.clearDepth(1.0); 635 loc_gl.enable(loc_gl.DEPTH_TEST); 636 loc_gl.depthFunc(loc_gl.LEQUAL); 637 //cc._renderContext.hint(cc._renderContext.PERSPECTIVE_CORRECTION_HINT, cc._renderContext.NICEST); 638 } else { 639 loc_gl.disable(loc_gl.DEPTH_TEST); 640 } 641 //cc.CHECK_GL_ERROR_DEBUG(); 642 }, 643 644 /** 645 * sets the default values based on the CCConfiguration info 646 */ 647 setDefaultValues:function(){ 648 649 }, 650 651 /** 652 * sets the OpenGL default values 653 */ 654 setGLDefaultValues:function () { 655 this.setAlphaBlending(true); 656 // XXX: Fix me, should enable/disable depth test according the depth format as cocos2d-iphone did 657 // [self setDepthTest: view_.depthFormat]; 658 this.setDepthTest(false); 659 this.setProjection(this._projection); 660 661 // set other opengl default values 662 cc._renderContext.clearColor(0.0, 0.0, 0.0, 1.0); 663 }, 664 665 /** 666 * set next delta time is zero 667 * @param {Boolean} nextDeltaTimeZero 668 */ 669 setNextDeltaTimeZero:function (nextDeltaTimeZero) { 670 this._nextDeltaTimeZero = nextDeltaTimeZero; 671 }, 672 673 /** 674 * set next scene 675 */ 676 setNextScene:function () { 677 var runningIsTransition = false, newIsTransition = false; 678 if(cc.TransitionScene){ 679 runningIsTransition = this._runningScene ? this._runningScene instanceof cc.TransitionScene : false; 680 newIsTransition = this._nextScene ? this._nextScene instanceof cc.TransitionScene : false; 681 } 682 683 // If it is not a transition, call onExit/cleanup 684 if (!newIsTransition) { 685 var locRunningScene = this._runningScene; 686 if (locRunningScene) { 687 locRunningScene.onExitTransitionDidStart(); 688 locRunningScene.onExit(); 689 } 690 691 // issue #709. the root node (scene) should receive the cleanup message too 692 // otherwise it might be leaked. 693 if (this._sendCleanupToScene && locRunningScene) 694 locRunningScene.cleanup(); 695 } 696 697 this._runningScene = this._nextScene; 698 699 this._nextScene = null; 700 if ((!runningIsTransition) && (this._runningScene != null)) { 701 this._runningScene.onEnter(); 702 this._runningScene.onEnterTransitionDidFinish(); 703 } 704 }, 705 706 /** 707 * set Notification Node 708 * @param {cc.Node} node 709 */ 710 setNotificationNode:function (node) { 711 this._notificationNode = node; 712 }, 713 714 /** 715 * CCDirector delegate. It shall implemente the CCDirectorDelegate protocol 716 * @return {cc.DirectorDelegate} 717 */ 718 getDelegate:function () { 719 return this._projectionDelegate; 720 }, 721 722 setDelegate:function (delegate) { 723 this._projectionDelegate = delegate; 724 }, 725 726 /** 727 * Set the CCEGLView, where everything is rendered 728 * @param {*} openGLView 729 */ 730 setOpenGLView:function (openGLView) { 731 // set size 732 this._winSizeInPoints.width = cc._canvas.width; //this._openGLView.getDesignResolutionSize(); 733 this._winSizeInPoints.height = cc._canvas.height; 734 this._openGLView = openGLView || cc.view; 735 736 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 737 return; 738 739 // Configuration. Gather GPU info 740 var conf = cc.configuration; 741 conf.gatherGPUInfo(); 742 conf.dumpInfo(); 743 744 // set size 745 //this._winSizeInPoints = this._openGLView.getDesignResolutionSize(); 746 //this._winSizeInPixels = cc.size(this._winSizeInPoints.width * this._contentScaleFactor, this._winSizeInPoints.height * this._contentScaleFactor); 747 748 //if (this._openGLView != openGLView) { 749 // because EAGLView is not kind of CCObject 750 751 this._createStatsLabel(); 752 753 //if (this._openGLView) 754 this.setGLDefaultValues(); 755 756 /* if (this._contentScaleFactor != 1) { 757 this.updateContentScaleFactor(); 758 }*/ 759 760 //} 761 }, 762 763 /** 764 * Sets the glViewport 765 */ 766 setViewport:function(){ 767 if(this._openGLView) { 768 var locWinSizeInPoints = this._winSizeInPoints; 769 this._openGLView.setViewPortInPoints(0,0, locWinSizeInPoints.width, locWinSizeInPoints.height); 770 } 771 }, 772 773 /** 774 * Sets an OpenGL projection 775 * @param {Number} projection 776 */ 777 setProjection: function (projection) { 778 var size = this._winSizeInPoints; 779 780 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 781 this._projection = projection; 782 cc.eventManager.dispatchEvent(this._eventProjectionChanged); 783 return; 784 } 785 786 this.setViewport(); 787 788 switch (projection) { 789 case cc.DIRECTOR_PROJECTION_2D: 790 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 791 cc.kmGLLoadIdentity(); 792 var orthoMatrix = new cc.kmMat4(); 793 cc.kmMat4OrthographicProjection(orthoMatrix, 0, size.width, 0, size.height, -1024, 1024); 794 cc.kmGLMultMatrix(orthoMatrix); 795 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 796 cc.kmGLLoadIdentity(); 797 break; 798 case cc.DIRECTOR_PROJECTION_3D: 799 var zeye = this.getZEye(); 800 var matrixPerspective = new cc.kmMat4(), matrixLookup = new cc.kmMat4(); 801 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 802 cc.kmGLLoadIdentity(); 803 804 // issue #1334 805 cc.kmMat4PerspectiveProjection(matrixPerspective, 60, size.width / size.height, 0.1, zeye * 2); 806 807 cc.kmGLMultMatrix(matrixPerspective); 808 809 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 810 cc.kmGLLoadIdentity(); 811 var eye = cc.kmVec3Fill(null, size.width / 2, size.height / 2, zeye); 812 var center = cc.kmVec3Fill(null, size.width / 2, size.height / 2, 0.0); 813 var up = cc.kmVec3Fill(null, 0.0, 1.0, 0.0); 814 cc.kmMat4LookAt(matrixLookup, eye, center, up); 815 cc.kmGLMultMatrix(matrixLookup); 816 break; 817 case cc.DIRECTOR_PROJECTION_CUSTOM: 818 if (this._projectionDelegate) 819 this._projectionDelegate.updateProjection(); 820 break; 821 default: 822 cc.log("cocos2d: Director: unrecognized projection"); 823 break; 824 } 825 this._projection = projection; 826 cc.eventManager.dispatchEvent(this._eventProjectionChanged); 827 cc.setProjectionMatrixDirty(); 828 }, 829 830 /** 831 * shows the FPS in the screen 832 */ 833 _showStats: function () { 834 this._frames++; 835 this._accumDt += this._deltaTime; 836 if (this._FPSLabel && this._SPFLabel && this._drawsLabel) { 837 if (this._accumDt > cc.DIRECTOR_FPS_INTERVAL) { 838 this._SPFLabel.string = this._secondsPerFrame.toFixed(3); 839 840 this._frameRate = this._frames / this._accumDt; 841 this._frames = 0; 842 this._accumDt = 0; 843 844 this._FPSLabel.string = this._frameRate.toFixed(1); 845 this._drawsLabel.string = (0 | cc.g_NumberOfDraws).toString(); 846 } 847 this._FPSLabel.visit(); 848 this._SPFLabel.visit(); 849 this._drawsLabel.visit(); 850 } else 851 this._createStatsLabel(); 852 cc.g_NumberOfDraws = 0; 853 }, 854 855 /** 856 * <p> 857 * Whether or not the replaced scene will receive the cleanup message.<br> 858 * If the new scene is pushed, then the old scene won't receive the "cleanup" message.<br/> 859 * If the new scene replaces the old one, the it will receive the "cleanup" message. 860 * </p> 861 * @return {Boolean} 862 */ 863 isSendCleanupToScene:function () { 864 return this._sendCleanupToScene; 865 }, 866 867 /** 868 * Get current running Scene. Director can only run one Scene at the time 869 * @return {cc.Scene} 870 */ 871 getRunningScene:function () { 872 return this._runningScene; 873 }, 874 875 /** 876 * Get the FPS value 877 * @return {Number} 878 */ 879 getAnimationInterval:function () { 880 return this._animationInterval; 881 }, 882 883 /** 884 * Whether or not to display the FPS on the bottom-left corner 885 * @return {Boolean} 886 */ 887 isDisplayStats:function () { 888 return this._displayStats; 889 }, 890 891 /** 892 * Display the FPS on the bottom-left corner 893 * @param {Boolean} displayStats 894 */ 895 setDisplayStats:function (displayStats) { 896 this._displayStats = displayStats; 897 }, 898 899 /** 900 * seconds per frame 901 * @return {Number} 902 */ 903 getSecondsPerFrame:function () { 904 return this._secondsPerFrame; 905 }, 906 907 /** 908 * Get the CCEGLView, where everything is rendered 909 * @return {*} 910 */ 911 getOpenGLView:function () { 912 return this._openGLView; 913 }, 914 915 /** 916 * is next delta time zero 917 * @return {Boolean} 918 */ 919 isNextDeltaTimeZero:function () { 920 return this._nextDeltaTimeZero; 921 }, 922 923 /** 924 * Whether or not the Director is paused 925 * @return {Boolean} 926 */ 927 isPaused:function () { 928 return this._paused; 929 }, 930 931 /** 932 * How many frames were called since the director started 933 * @return {Number} 934 */ 935 getTotalFrames:function () { 936 return this._totalFrames; 937 }, 938 939 /** 940 * Sets an OpenGL projection 941 * @return {Number} 942 */ 943 getProjection:function () { 944 return this._projection; 945 }, 946 947 /** 948 * <p> 949 * Pops out all scenes from the queue until the root scene in the queue. <br/> 950 * This scene will replace the running one. <br/> 951 * Internally it will call `popToSceneStackLevel(1)` 952 * </p> 953 */ 954 popToRootScene:function () { 955 this.popToSceneStackLevel(1); 956 }, 957 958 /** 959 * <p> 960 * Pops out all scenes from the queue until it reaches `level`. <br/> 961 * If level is 0, it will end the director. <br/> 962 * If level is 1, it will pop all scenes until it reaches to root scene. <br/> 963 * If level is <= than the current stack level, it won't do anything. 964 * </p> 965 * @param {Number} level 966 */ 967 popToSceneStackLevel: function (level) { 968 if(!this._runningScene) 969 throw "A running Scene is needed"; 970 971 var locScenesStack = this._scenesStack; 972 var c = locScenesStack.length; 973 974 if (c == 0) { 975 this.end(); 976 return; 977 } 978 // current level or lower -> nothing 979 if (level > c) 980 return; 981 982 // pop stack until reaching desired level 983 while (c > level) { 984 var current = locScenesStack.pop(); 985 if (current.running) { 986 current.onExitTransitionDidStart(); 987 current.onExit(); 988 } 989 current.cleanup(); 990 c--; 991 } 992 this._nextScene = locScenesStack[locScenesStack.length - 1]; 993 this._sendCleanupToScene = false; 994 }, 995 996 /** 997 * (cc.Scheduler associated with this director) 998 */ 999 getScheduler:function () { 1000 return this._scheduler; 1001 }, 1002 1003 setScheduler:function (scheduler) { 1004 if (this._scheduler != scheduler) { 1005 this._scheduler = scheduler; 1006 } 1007 }, 1008 1009 getActionManager:function () { 1010 return this._actionManager; 1011 }, 1012 setActionManager:function (actionManager) { 1013 if (this._actionManager != actionManager) { 1014 this._actionManager = actionManager; 1015 } 1016 }, 1017 1018 getDeltaTime:function(){ 1019 return this._deltaTime; 1020 }, 1021 1022 _createStatsLabel: null, 1023 1024 _createStatsLabelForWebGL:function(){ 1025 if(!cc.LabelAtlas) 1026 return this._createStatsLabelForCanvas(); 1027 1028 if((cc.Director._fpsImageLoaded == null) || (cc.Director._fpsImageLoaded == false)) 1029 return; 1030 1031 var texture = new cc.Texture2D(); 1032 texture.initWithElement(cc.Director._fpsImage); 1033 texture.handleLoadedTexture(); 1034 1035 /* 1036 We want to use an image which is stored in the file named ccFPSImage.c 1037 for any design resolutions and all resource resolutions. 1038 1039 To achieve this, 1040 1041 Firstly, we need to ignore 'contentScaleFactor' in 'CCAtlasNode' and 'CCLabelAtlas'. 1042 So I added a new method called 'setIgnoreContentScaleFactor' for 'CCAtlasNode', 1043 this is not exposed to game developers, it's only used for displaying FPS now. 1044 1045 Secondly, the size of this image is 480*320, to display the FPS label with correct size, 1046 a factor of design resolution ratio of 480x320 is also needed. 1047 */ 1048 var factor = cc.view.getDesignResolutionSize().height / 320.0; 1049 if(factor === 0) 1050 factor = this._winSizeInPoints.height / 320.0; 1051 1052 var tmpLabel = new cc.LabelAtlas(); 1053 tmpLabel._setIgnoreContentScaleFactor(true); 1054 tmpLabel.initWithString("00.0", texture, 12, 32 , '.'); 1055 tmpLabel.scale = factor; 1056 this._FPSLabel = tmpLabel; 1057 1058 tmpLabel = new cc.LabelAtlas(); 1059 tmpLabel._setIgnoreContentScaleFactor(true); 1060 tmpLabel.initWithString("0.000", texture, 12, 32, '.'); 1061 tmpLabel.scale = factor; 1062 this._SPFLabel = tmpLabel; 1063 1064 tmpLabel = new cc.LabelAtlas(); 1065 tmpLabel._setIgnoreContentScaleFactor(true); 1066 tmpLabel.initWithString("000", texture, 12, 32, '.'); 1067 tmpLabel.scale = factor; 1068 this._drawsLabel = tmpLabel; 1069 1070 var locStatsPosition = cc.DIRECTOR_STATS_POSITION; 1071 this._drawsLabel.setPosition(locStatsPosition.x, 34 * factor + locStatsPosition.y); 1072 this._SPFLabel.setPosition(locStatsPosition.x, 17 * factor + locStatsPosition.y); 1073 this._FPSLabel.setPosition(locStatsPosition); 1074 }, 1075 1076 _createStatsLabelForCanvas:function(){ 1077 var fontSize = 0; 1078 if (this._winSizeInPoints.width > this._winSizeInPoints.height) 1079 fontSize = 0 | (this._winSizeInPoints.height / 320 * 24); 1080 else 1081 fontSize = 0 | (this._winSizeInPoints.width / 320 * 24); 1082 1083 this._FPSLabel = cc.LabelTTF.create("000.0", "Arial", fontSize); 1084 this._SPFLabel = cc.LabelTTF.create("0.000", "Arial", fontSize); 1085 this._drawsLabel = cc.LabelTTF.create("0000", "Arial", fontSize); 1086 1087 var locStatsPosition = cc.DIRECTOR_STATS_POSITION; 1088 this._drawsLabel.setPosition(this._drawsLabel.width / 2 + locStatsPosition.x, this._drawsLabel.height * 5 / 2 + locStatsPosition.y); 1089 this._SPFLabel.setPosition(this._SPFLabel.width / 2 + locStatsPosition.x, this._SPFLabel.height * 3 / 2 + locStatsPosition.y); 1090 this._FPSLabel.setPosition(this._FPSLabel.width / 2 + locStatsPosition.x, this._FPSLabel.height / 2 + locStatsPosition.y); 1091 }, 1092 1093 _calculateMPF: function () { 1094 var now = Date.now(); 1095 this._secondsPerFrame = (now - this._lastUpdate) / 1000; 1096 } 1097 }); 1098 1099 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 1100 cc.Director.prototype._clear = cc.Director.prototype._clearWebGL; 1101 cc.Director.prototype._beforeVisitScene = cc.Director.prototype._beforeVisitSceneWebGL; 1102 cc.Director.prototype._afterVisitScene = cc.Director.prototype._afterVisitSceneWebGL; 1103 cc.Director.prototype._createStatsLabel = cc.Director.prototype._createStatsLabelForWebGL; 1104 } else { 1105 cc.Director.prototype._clear = cc.Director.prototype._clearCanvas; 1106 cc.Director.prototype._createStatsLabel = cc.Director.prototype._createStatsLabelForCanvas; 1107 } 1108 1109 cc.Director.EVENT_PROJECTION_CHANGED = "director_projection_changed"; 1110 cc.Director.EVENT_AFTER_DRAW = "director_after_draw"; 1111 cc.Director.EVENT_AFTER_VISIT = "director_after_visit"; 1112 cc.Director.EVENT_AFTER_UPDATE = "director_after_update"; 1113 1114 /*************************************************** 1115 * implementation of DisplayLinkDirector 1116 **************************************************/ 1117 cc.DisplayLinkDirector = cc.Director.extend(/** @lends cc.director# */{ 1118 invalid:false, 1119 1120 /** 1121 * start Animation 1122 */ 1123 startAnimation:function () { 1124 this._nextDeltaTimeZero = true; 1125 this.invalid = false; 1126 }, 1127 1128 /** 1129 * main loop of director 1130 */ 1131 mainLoop:function () { 1132 if (this._purgeDirectorInNextLoop) { 1133 this._purgeDirectorInNextLoop = false; 1134 this.purgeDirector(); 1135 } 1136 else if (!this.invalid) { 1137 this.drawScene(); 1138 } 1139 }, 1140 1141 /** 1142 * stop animation 1143 */ 1144 stopAnimation:function () { 1145 this.invalid = true; 1146 }, 1147 1148 /** 1149 * set Animation Interval 1150 * @param {Number} value 1151 */ 1152 setAnimationInterval:function (value) { 1153 this._animationInterval = value; 1154 if (!this.invalid) { 1155 this.stopAnimation(); 1156 this.startAnimation(); 1157 } 1158 } 1159 }); 1160 1161 cc.Director.sharedDirector = null; 1162 cc.Director.firstUseDirector = true; 1163 1164 cc.Director._getInstance = function () { 1165 if (cc.Director.firstUseDirector) { 1166 cc.Director.firstUseDirector = false; 1167 cc.Director.sharedDirector = new cc.DisplayLinkDirector(); 1168 cc.Director.sharedDirector.init(); 1169 } 1170 return cc.Director.sharedDirector; 1171 }; 1172 1173 /** 1174 * set default fps to 60 1175 * @type Number 1176 */ 1177 cc.defaultFPS = 60; 1178 1179 cc.Director._fpsImage = new Image(); 1180 cc.Director._fpsImage.addEventListener("load", function () { 1181 cc.Director._fpsImageLoaded = true; 1182 }); 1183 cc.Director._fpsImage.src = "data:image/png;base64,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"; 1184 1185