1 /* 2 Copyright (c) 2008 Seneca College 3 Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/) 4 */ 5 6 /** 7 @class A Scene should be thought of as a scene on a movie set. A scene 8 would typically contain objects which are moving and a current camera, 9 lights, etc. 10 */ 11 c3dl.Scene = function() 12 { 13 // Engine Variables 14 var glCanvas3D = null; // OpenGL Context (Canvas) 15 var renderer = null; 16 var camera = null; // Reference to a Camera type 17 var projMat = null; 18 19 // Picking is implemented as a class, should be changed 20 // to a function. For now we need to make an instance of the class. 21 // We store this in this.pick. 22 this.pick; 23 this.pickingPrecision = c3dl.PICK_PRECISION_TRIANGLES; 24 25 // This will hold the function the user wants called everytime 26 // there is a mouse down event. 27 this.pickingHandler; 28 29 // This is off by default since users will likely only need it when 30 // trying to debug something. 31 this.boundingVolumesVisible = false; 32 33 // A reference to a model which will actually act as a 34 // SkyBox, except any Model can be used, not just a box. 35 var skyModel = null; 36 37 // list of objects in the scene and list of lights 38 var objList = []; // An array of objects to draw 39 var lightList = [c3dl.MAX_LIGHTS]; 40 41 // each scene has its own point attenuation factors giving the user 42 // the flexibility to have different factors for each scene. 43 var pointAttenuation = [1,0,0]; 44 var pointSize = 5; 45 var pointSmoothing = true; 46 47 // default point rendering to spheres to prevent possible crashing 48 // when users render points which playing a DVD on OS X. 49 var pointRenderingMode = c3dl.POINT_MODE_SPHERE; 50 51 var exitFlag = false; // Exits the render loop 52 var canvasTag = null; 53 var canvas2Dlist = []; 54 55 // Input Handler Variables 56 var kybdHandler = null; 57 var mouseHandler = null; 58 var updateHandler = null; 59 60 // Performance variables 61 var timerID = 0; 62 var lastTimeTaken = Date.now(); 63 var numFramesSinceSceneStart = 0; 64 65 // this is re-calculated every second and queried with getFPS(); 66 var FPS = 0; 67 // will be reset after calculating the FPS. 68 var FPS_Counter = 0; 69 var FPS_LastTimeTaken = Date.now(); 70 71 // This will be the color of the background if the user does not change it. 72 var backgroundColor = [c3dl.DEFAULT_BG_RED, c3dl.DEFAULT_BG_GREEN, c3dl.DEFAULT_BG_BLUE]; 73 var ambientLight = [1,1,1]; 74 75 var thisScn = null; 76 77 // If the user calls addTexture on scene, but the scene does not have 78 // a renderer, there's no was for the texture to be created. In that case 79 // place the texture path in a queue which will be passed to the renderer 80 // once it is set. 81 var textureQueue = []; 82 83 var pointPositions = null; 84 85 86 // ------------------------------------------------------- 87 88 /** 89 Add a texture to this scene to be used used for assigning to a model, 90 particle system etc. 91 92 If the renderer was not set for the scene, the texture will be queued and 93 will begin to load once the renderer is set 94 95 @param {String} path 96 */ 97 /* this.addTexture = function(path) 98 { 99 // check path parameter 100 if(path) 101 { 102 if(renderer && renderer.getGLContext()) 103 { 104 renderer.addTexture(path); 105 } 106 else 107 { 108 textureQueue.push(path); 109 } 110 } 111 else 112 { 113 c3dl.debug.logWarning("Invalid parameter, '" + path + "' was passed to Scene's addTexture()"); 114 } 115 }*/ 116 117 /** 118 119 */ 120 /* this.getTextureID = function(path) 121 { 122 if(renderer) 123 { 124 return renderer.getTextureID(path); 125 } 126 else 127 { 128 return -1; 129 } 130 }*/ 131 132 /** 133 @returns {Array} 134 */ 135 this.getPointAttenuation = function() 136 { 137 return [pointAttenuation[0], pointAttenuation[1], pointAttenuation[2]]; 138 } 139 140 /** 141 If point smoothing is on, points are rendered as circles, otherwise they are rendered as 142 squares. 143 144 @returns {bool} true if points will be rendered as circles, false if points are rendered as 145 squares. 146 */ 147 this.getPointSmooth = function() 148 { 149 return pointSmoothing; 150 } 151 152 /** 153 @private 154 155 When the picking function runs, it needs the projection matrix 156 which was used to render the scene. Since one camera can be used for 157 many scenes, we can't get the projection from the camera because it would 158 change each time a canvas with a different aspect ratio is rendered. 159 160 We have to provide a accessor to the projection matrix at the scene level. 161 */ 162 this.getProjectionMatrix = function() 163 { 164 return projMat; 165 } 166 167 /** 168 @private 169 170 Will the bounding volumes be drawn on render? 171 172 @returns {boolean} true if the bounding volumes will be drawn, false otherwise. 173 */ 174 this.getBoundingVolumeVisibility = function() 175 { 176 return this.boundingVolumesVisible; 177 } 178 179 /** 180 Get the camera of the scene. 181 182 @returns {Camera} The camera of the scene. 183 */ 184 this.getCamera = function() 185 { 186 return camera; 187 } 188 189 /** 190 Get the number of objects in the scene. 191 192 @returns {int} The number of objects in the object list. 193 */ 194 this.getObjListSize = function() 195 { 196 return objList.length; 197 } 198 199 /** 200 Get the context. 201 202 @returns {Context} 203 */ 204 this.getGL = function() 205 { 206 return glCanvas3D; 207 } 208 209 /** 210 Get the amount of frames rendered since the start of the scene. 211 @private 212 */ 213 this.getTotalFrameCount = function() 214 { 215 return numFramesSinceSceneStart; 216 } 217 218 /** 219 Get the number of frames rendered in the last second. 220 221 @returns {float} the number of frames rendered in the 222 last second. 223 */ 224 this.getFPS = function() 225 { 226 return FPS; 227 } 228 229 /** 230 Get the scene's Renderer 231 */ 232 this.getRenderer = function() 233 { 234 return renderer; 235 } 236 237 /** 238 Get the Scene. 239 240 @returns {c3dl.Scene} 241 */ 242 this.getScene = function() 243 { 244 return thisScn; 245 } 246 247 /** 248 Get the SkyModel. 249 250 @returns {c3dl.Collada} The Scene's SkyModel. 251 */ 252 this.getSkyModel = function() 253 { 254 return skyModel; 255 } 256 257 /** 258 Get the ambient light of the scene. 259 260 @returns {Array} An Array of 3 values in the order RGB. 261 */ 262 this.getAmbientLight = function() 263 { 264 return [ambientLight[0],ambientLight[1],ambientLight[2]]; 265 } 266 267 /** 268 Get a reference of a particular object in the scene. 269 270 @param indxNum The index number of the object. 271 272 @return the reference to the object at index number indxNum or null 273 if indxNum was out of bounds. 274 */ 275 this.getObj = function(indxNum) 276 { 277 if (isNaN(indxNum)) 278 { 279 c3dl.debug.logWarning("Scene::getObj() called with a parameter that's not a number"); 280 return null; 281 } 282 // Check if the index that was asked for is inside the bounds of our array 283 if (indxNum < 0 || indxNum >= objList.length) 284 { 285 c3dl.debug.logWarning("Scene::getObj() called with " + indxNum +", which is not betwen 0 and " + objList.length); 286 return null; 287 } 288 289 // We do this because we dont want outsiders modifying the object list, 290 // just the object themselves (ie. changing position, orientation, etc) 291 return objList[indxNum]; 292 } 293 294 295 /** 296 Get the type of test which will run when a user clicks on the canvas. 297 298 @returns c3dl.PICK_PRECISION_BOUNDING_VOLUME or c3dl.PICK_PRECISION_TRIANGLE. 299 */ 300 this.getPickingPrecision = function() 301 { 302 return this.pickingPrecision; 303 } 304 305 306 /** 307 @private 308 309 @param {boolean} visible true if the bounding volumes should be drawn, otherwise 310 set to false. 311 */ 312 this.setBoundingVolumeVisibility = function(visible) 313 { 314 this.boundingVolumesVisible = visible; 315 } 316 317 318 /** 319 Set the functions to call when a key is pressed or released. <br /> 320 TODO: add keyPress event callback as windows and osx versions of firefox 321 handle keyboard events differently. 322 323 @param {function} keyUpCB The callback function for the up key. 324 @param {function} keyDownCD The callback function for the down key. 325 */ 326 this.setKeyboardCallback = function(keyUpCB, keyDownCB) 327 { 328 if (canvasTag) 329 { 330 // Register True keyboard listeners 331 if (keyUpCB != null) document.addEventListener("keyup", keyUpCB, false); 332 if (keyDownCB != null) document.addEventListener("keydown", keyDownCB, false); 333 } 334 } 335 336 /** 337 Pass in the functions to call when mouse event occur such as when 338 a button is pressed, released or the mousewheel is scrolled. The 339 scene will call these functions when the events occur. 340 341 @param {function} mouseUpCB 342 @param {function} mouseDownCB 343 @param {function} mouseMoveCB 344 @param {function} mouseScrollCB 345 */ 346 this.setMouseCallback = function(mouseUpCB, mouseDownCB, mouseMoveCB, mouseScrollCB) 347 { 348 if (canvasTag) 349 { 350 // Register all Mouse listeners 351 if (mouseMoveCB != null) canvasTag.addEventListener("mousemove", mouseMoveCB, false); 352 if (mouseUpCB != null) canvasTag.addEventListener("mouseup", mouseUpCB, false); 353 if (mouseDownCB != null) canvasTag.addEventListener("mousedown", mouseDownCB, false); 354 355 // Firefox uses DOMMouseScroll, Safari and Chrome use mousewheel 356 if (mouseScrollCB != null){ 357 canvasTag.addEventListener("DOMMouseScroll", mouseScrollCB, false); 358 canvasTag.addEventListener ("mousewheel", mouseScrollCB, false); 359 } 360 } 361 } 362 363 /** 364 Tell the scene what function to call when a user clicks on the canvas. 365 366 @param {function} pickingHandler The function to call when the user clicks on the canvas. 367 */ 368 this.setPickingCallback = function(pickingHandler) 369 { 370 if(pickingHandler && pickingHandler instanceof Function) 371 { 372 // for now we need to make an instance, this needs to be changed. 373 this.pick = new c3dl.Picking(this); 374 375 // set the picking handler 376 this.pickingHandler = pickingHandler; 377 canvasTag.addEventListener("mousedown", this.pick.onMouseDown, false); 378 } 379 else 380 { 381 c3dl.debug.logWarning("scene's setPickingCallback() was passed an invalid callback function"); 382 } 383 } 384 385 /** 386 Get the function which will be called when the user clicks on the 387 canvas. 388 389 @returns {Function} the function which is called when the user clicks 390 on the canvas. 391 */ 392 this.getPickingCallback = function() 393 { 394 return this.pickingHandler; 395 } 396 397 /** 398 Set how the points attenuate for this scene. 399 400 @param {Array} attn with three values.<br /> 401 first contains constant attenuation<br /> 402 second contains linear attenuation<br /> 403 third contains quadratic attenuation<br /> 404 At least one of the elements must be greater than one or the 405 argument is ignored. 406 */ 407 this.setPointAttenuation = function(attn) 408 { 409 if (attn.length == 3 && (attn[0] > 0 || attn[1] > 0 || attn[2] > 0)) 410 { 411 pointAttenuation[0] = attn[0]; 412 pointAttenuation[1] = attn[1]; 413 pointAttenuation[2] = attn[2]; 414 } 415 } 416 417 /** 418 If point smoothing is on, points are rendered as circles, otherwise they are rendered as 419 squares. 420 421 @returns {bool} true if points are rendered as circles, false if points are rendered as squares. 422 */ 423 this.setPointSmooth = function(smooth) 424 { 425 pointSmoothing = smooth; 426 } 427 428 /** 429 Get the size of the spheres when they are rendered as points. 430 431 @returns {float} size the points will be when they are rendered as 432 spheres. 433 */ 434 this.getPointSize = function() 435 { 436 return pointSize; 437 } 438 439 /** 440 Sets the point size when rendering points as spheres. 441 To change point size when rendering points as circles, 442 or using the built-in points, use setPointAttenuation. 443 444 @param {float} size Must be greater than zero. 445 */ 446 this.setPointSize = function(size) 447 { 448 if(size > 0) 449 { 450 pointSize = size; 451 } 452 } 453 454 /** 455 Set the SkyModel. A SkyModel acts like a skybox, when the camera 456 moves in the scene the SkyModel maintains the same distance from 457 the camera. This creates the illusion that there are parts to 458 the scene that are very far away which cannot be reached. 459 Applications of this would include creating clouds, or mountain 460 ranges, starfields, etc. Any Model can be passed in and is not 461 restricted to a Cube. Whatever model that is appropirate should 462 be used. 463 464 @param {c3dl.Collada} sky A Model which will maintain the same distance 465 from the Scene's camera. 466 */ 467 this.setSkyModel = function(sky) 468 { 469 if( sky instanceof c3dl.Collada) 470 { 471 skyModel = sky; 472 } 473 else 474 { 475 c3dl.debug.Warning("Scene::setSkyModel() Inavlid argument passed, was not c3dl.Collada."); 476 } 477 } 478 479 /** 480 Set the function to call everytime the scene is updated. 481 482 @param {function} updateCB The function to call everytime the 483 scene is updated. 484 */ 485 this.setUpdateCallback = function(updateCB) 486 { 487 if (canvasTag) 488 { 489 if (updateCB != null) 490 { 491 updateHandler = updateCB; 492 } 493 } 494 } 495 496 /** 497 Set the renderer used to render the scene. 498 499 @param {c3dl.OpenGLES20} renderType 500 */ 501 this.setRenderer = function(renderType) 502 { 503 // Set the type of renderer to use 504 if(renderType instanceof c3dl.WebGL) 505 { 506 renderer = renderType; 507 } 508 } 509 510 /** 511 @param {String} canvasTagID The id of the canvas, which is 512 a property of the canvas tag in the html file. 513 */ 514 this.setCanvasTag = function(canvasTagID) 515 { 516 // Get the Canvas tag 517 canvasTag = document.getElementById(canvasTagID); 518 519 if (canvasTag == null) 520 { 521 c3dl.debug.logWarning('Scene::setCanvasTag() No canvas tag with name ' + canvasTagID + ' was found.'); 522 } 523 } 524 525 /** 526 Return the canvas 527 */ 528 this.getCanvas = function() 529 { 530 return canvasTag; 531 } 532 533 /** 534 Set the Scene's camera. 535 536 @param {c3dl.FreeCamera} cam The camera. 537 */ 538 this.setCamera = function(cam) 539 { 540 // Check to see if we were passed a correct Camera class 541 if (cam instanceof c3dl.FreeCamera || 542 cam instanceof c3dl.OrbitCamera) 543 544 { 545 camera = cam; 546 return true; 547 } 548 549 c3dl.debug.logWarning('Scene::setCamera() invalid type of camera.'); 550 return false; 551 } 552 553 /** 554 If the scene has been provided with a picking callback, when the user clicks the canvas 555 either one or two sets of tests will run. If the bounding volume constant is passed to 556 this function, a fast, approximate test is run for objects which can be picked against the 557 ray generated by the click. If the triangles constant is passed in, both the ray/bounding volume test 558 will run along with a ray/triangle test for each object which can be picked. 559 560 @param {c3dl.PICK_PRECISION_BOUNDING_VOLUME | c3dl.PICK_PRECISION_TRIANGLE} precision The precision test to use when the user clicks the canvas. 561 */ 562 this.setPickingPrecision = function(precision) 563 { 564 if( precision == c3dl.PICK_PRECISION_BOUNDING_VOLUME || 565 precision == c3dl.PICK_PRECISION_TRIANGLE) 566 { 567 this.pickingPrecision = precision; 568 } 569 } 570 571 572 /** 573 @private 574 This one just calls addTextToModel() 575 //!! need dest as a parameter, probably in pixels, where to put the text 576 */ 577 this.addFloatingText = function(text, fontStyle, fontColour, backgroundColour) 578 { 579 var box = this.addTextToModel(null, text, fontStyle, fontColour, backgroundColour); 580 box.stayInFrontOfCamera = true; 581 this.addObjectToScene(box); 582 } 583 584 /** 585 @private 586 Create a 2D canvas, render the text into it, and use that as a texture for model. 587 If model is null, create a rectangle and stick the text onto it. 588 */ 589 this.addTextToModel = function(model, text, fontStyle, fontColour, backgroundColour) 590 { 591 // Create a SPAN element with the string and style matching what the user asked 592 // for the floating text. 593 var tempSpan = document.createElement('span'); 594 var tempSpanStyle = document.createElement('style'); 595 var tempSpanStyleContent = document.createTextNode('span{' + 596 'font: ' + fontStyle + ';' + 597 'color: ' + fontColour + '; ' + 598 'background: ' + backgroundColour + 599 ';}'); 600 var tempText = document.createTextNode(text); 601 tempSpanStyle.appendChild(tempSpanStyleContent); 602 tempSpan.appendChild(tempSpanStyle); 603 tempSpan.appendChild(tempText); 604 605 // Append it to the body so it's momentarily displayed. I couldn't find a way to measure 606 // the text box's size without displaying it. 607 document.body.appendChild(tempSpan); 608 609 var actualStringWidth = tempSpan.offsetWidth; 610 var actualStringHeight = tempSpan.offsetHeight; 611 var stringWidth = c3dl.roundUpToNextPowerOfTwo(tempSpan.offsetWidth); 612 var stringHeight = c3dl.roundUpToNextPowerOfTwo(tempSpan.offsetHeight); 613 614 // Now get rid of that element, we only needed it to measure it 615 tempSpan.removeChild(tempSpanStyle); 616 document.body.removeChild(tempSpan); 617 618 var box; 619 if (model == null) 620 { 621 var whRatio = stringWidth / stringHeight; 622 623 // Model for the plane with the text, size based on whRatio 624 var smallCanvasVertices = 625 [ 626 [-1.0 * (whRatio / 2), -1.0, 0.0], // 0 - bottom left 627 [-1.0 * (whRatio / 2), 1.0, 0.0], // 1 - top left 628 [ 1.0 * (whRatio / 2), 1.0, 0.0], // 2 - top right 629 [ 1.0 * (whRatio / 2), -1.0, 0.0], // 3 - bottom right 630 ]; 631 var smallCanvasNormals = 632 [ 633 [0,0,-1] 634 ]; 635 var smallCanvasUVs = 636 [ 637 [0.0,1.0], // 0 - bottom left 638 [0.0,0.0], // 1 - top left 639 [1.0,0.0], // 2 - top right 640 [1.0,1.0] // 3 - bottom right 641 ]; 642 var smallCanvasFaces = 643 [ 644 [0,0,0], [3,3,0], [2,2,0], 645 [0,0,0], [2,2,0], [1,1,0] 646 ]; 647 648 box = new Model(); 649 box.init(smallCanvasVertices, smallCanvasNormals, smallCanvasUVs, smallCanvasFaces); 650 //box.setAngularVel(new Array(0.003, 0.000, 0.000)); 651 //box.pitch(-0.4); 652 653 //!! need something user-specified 654 box.setPosition([5, 0, 5]); 655 } 656 else 657 box = model; 658 659 // Draw the text into the 2D canvas and use it for the above model's texture 660 var textureCanvas = this.create2Dcanvas(stringWidth, stringHeight); 661 if (textureCanvas.getContext) 662 { 663 var ctx = textureCanvas.getContext('2d'); 664 665 if (fontStyle) 666 ctx.mozTextStyle = fontStyle; 667 668 // Fill everything with backgroundColour if it's specified 669 if (backgroundColour) 670 { 671 ctx.fillStyle = backgroundColour; 672 ctx.fillRect(0, 0, stringWidth, stringHeight); 673 } 674 675 // Center the text in the 2D canvas 676 ctx.translate((stringWidth - actualStringWidth) / 2, 677 stringHeight - (stringHeight - actualStringHeight)); 678 679 if (fontColour) 680 ctx.fillStyle = fontColour; 681 else 682 ctx.fillStyle = 'black'; 683 684 ctx.mozDrawText(text); 685 686 box.setTextureFromCanvas2D(textureCanvas.id); 687 //textureManager.addTextureFromCanvas2D(textureCanvas.id); 688 } 689 else 690 c3dl.debug.logWarning("addFloatingText(): call to create2Dcanvas() failed"); 691 692 return box; 693 } 694 695 /** 696 @private 697 Create a 2D canvas for drawing text and other stuff. Keep a 698 reference to it. 699 700 @return {CanvasTag} 701 */ 702 this.create2Dcanvas = function(width, height) 703 { 704 var newCanvas = document.createElement('canvas'); 705 newCanvas.id = 'changemetorandomstring'; 706 newCanvas.width = width; 707 newCanvas.height = height; 708 canvasTag.appendChild(newCanvas); 709 710 canvas2Dlist.push(newCanvas); 711 712 return newCanvas; 713 } 714 715 /** 716 Set the color of the background. Values are clamped to the 717 range [0,1]. 718 719 @param {Array} bgColor Array of four values in the order [r,g,b,a]. 720 */ 721 this.setBackgroundColor = function(bgColor) 722 { 723 if( bgColor.length >= 3 ) 724 { 725 backgroundColor = bgColor.slice(0,3); 726 727 if(renderer) 728 { 729 renderer.setClearColor(backgroundColor); 730 } 731 } 732 } 733 734 735 /** 736 Set how c3dl.Point objects are rendered. 737 c3dl.POINT_MODE_POINT will render the points using WebGL's built-in 2D billboarded point primitives 738 c3dl.POINT_MODE_SPHERE will render points using sphere objects. 739 740 @param {c3dl.POINT_MODE_POINT | c3dl.POINT_MODE_SPHERE} mode 741 */ 742 this.setPointRenderingMode = function(mode) 743 { 744 if( mode == c3dl.POINT_MODE_POINT || mode == c3dl.POINT_MODE_SPHERE) 745 { 746 pointRenderingMode = mode; 747 } 748 else 749 { 750 c3dl.debug.logWarning("Invalid mode passed to setPointRenderingMode"); 751 } 752 } 753 754 /** 755 Get how the points are rendered in the scene. Either they are rendered using 756 757 WebGL's built-in method, or are rendered as sphere meshes. 758 759 @returns {c3dl.POINT_MODE_POINT | c3dl.POINT_MODE_SPHERE} rendering mode. 760 */ 761 this.getPointRenderingMode = function() 762 { 763 return pointRenderingMode; 764 } 765 766 767 /** 768 Get the color of the background. 769 770 @returns {Array} Array of three values in the order RGB. 771 */ 772 this.getBackgroundColor = function() 773 { 774 return c3dl.copyObj(backgroundColor); 775 } 776 777 /** 778 Set the ambient light of the scene. 779 780 @param {Array} light An array of 3 floating point values 781 ranging from 0 to 1. 782 */ 783 this.setAmbientLight = function(light) 784 { 785 if( light.length >= 3 ) 786 { 787 ambientLight = [light[0], light[1], light[2], 1]; 788 } 789 } 790 791 792 /** 793 Acquire the OpenGL Context 794 795 @returns {boolean} true if the renderer was initialized, otherwise false. 796 */ 797 this.init = function() 798 { 799 if (renderer != null && canvasTag != null) 800 { 801 // Initialize the renderer 802 if (!renderer.createRenderer(canvasTag)) 803 { 804 c3dl.debug.logError("Your browser does not support WebGL.<br />" + 805 "Visit the <a href='http://en.wikipedia.org/wiki/WebGL'>WebGL wiki page</a> for information on downloading a WebGL enabled browser"); 806 return false; 807 } 808 809 // Get the Canvas 810 glCanvas3D = renderer.getGLContext(); 811 812 // tell the renderer the default color the color buffer should be 813 // every render. 814 this.setBackgroundColor(backgroundColor); 815 816 // Set our global (fake static variable) to be used in rendering 817 thisScn = this; 818 819 // setup the lights 820 // we have an array of elements, but they are all undefined, 821 for (var i = 0; i < lightList.length; i++) 822 { 823 lightList[i] = null; 824 } 825 826 // Initialize the renderer 827 return renderer.init(canvasTag.width, canvasTag.height); 828 } 829 830 c3dl.debug.logError('Scene::createScene() No renderer was specified.'); 831 return false; 832 } 833 834 /** 835 Get a reference to a light from the list in the scene. This is an O(n) 836 operation. 837 838 @param {String} lightName the name of the light. 839 840 @returns a reference to a light object or null if it was not found. 841 */ 842 this.getLight = function(name) 843 { 844 for (var i = 0; i < lightList.length; i++) 845 { 846 // if we found a match, since we have 'holes' in the array 847 // check that the value is not null before calling its method. 848 if(lightList[i] && lightList[i].getName() == name) 849 { 850 return lightList[i]; 851 } 852 } 853 return null; 854 } 855 856 /** 857 Adds a light to a scene if the maximum number of lights in the scene 858 has not been exceeded. 859 860 @param {c3dl.PositionalLight|c3dl.DirectionalLight|c3dl.SpotLight} light the light to add. 861 862 @returns {boolean} true if the light could be added, otherwise returns false. 863 */ 864 this.addLight = function(light) 865 { 866 // start from the beginning of the list anf find the first empty spot. 867 for (var i = 0; i < c3dl.MAX_LIGHTS; i++ ) 868 { 869 // either the light was not yet set to null or we are recycling the spot. 870 if( lightList[i] == null) 871 { 872 lightList[i] = light; 873 return true; 874 } 875 } 876 877 // if the iterated over all the lights and didn't find an empty spot, 878 // we end up here, returning to indicate the light was not added. 879 return false; 880 } 881 882 /** 883 Remove a light from the scene. The first light found matching the name 884 lightName will be removed. 885 886 @param {String} lightName the name of the light 887 */ 888 this.removeLight = function(lightName) 889 { 890 // There are 2 copies of the light, one in our js code and one in the opengl 891 // state variable. We need to remove the light object from our list and set 892 // the opengl state variable to all zeros so it will no longer affect the scene. 893 894 // first find the index of the light in our array. 895 var lightID = -1; 896 for (var i = 0; i < lightList.length && lightID == -1; i++) 897 { 898 if(lightList[i] && lightList[i].getName() == lightName) 899 { 900 lightID = i; 901 } 902 } 903 904 // now that we have the index, we have to set the corresponding opengl state 905 // to zeros, which will prevent the light from affecting the scene. 906 // 907 if(lightID != -1) 908 { 909 // place a 'hole' in the array. This can later be populated with another light. 910 // don't delete the light, leave it up to the gc, otherwise 911 // the light seems to stay on and can't be removed. 912 lightList[lightID] = null; 913 914 // we removed the light from our list, but openGL still has 915 // a light state which needs to be cleared. Otherwise the 916 // light will still affect the scene. 917 renderer.clearLight(lightID); 918 } 919 return (lightID == -1 ? false: true); 920 } 921 922 /** 923 @private 924 Update the OpenGL light state variables with our list of lights 925 This happens every frame. 926 */ 927 this.updateLights = function() 928 { 929 renderer.updateAmbientLight(this.getAmbientLight()); 930 renderer.updateLights(lightList); 931 } 932 933 /** 934 Add the object 'obj' to the scene. 935 936 @param {c3dl.Primitive|c3dl.ParticleSystem|c3dl.Point|c3dl.Line} obj A reference to an object. 937 938 @return {boolean} True if the object was added to the scene, false otherwise. 939 */ 940 this.addObjectToScene = function(obj) 941 { 942 var type = obj.getObjectType(); 943 944 switch(type) 945 { 946 case c3dl.LINE: 947 case c3dl.POINT: 948 case c3dl.PARTICLE_SYSTEM: 949 case c3dl.COLLADA: 950 objList.push(obj); 951 return true; 952 } 953 954 c3dl.debug.logWarning("Scene::addObjectToScene() called with an invalid argument."); 955 return false; 956 } 957 958 /** 959 Remove an object from the scene. This is an O(n) operation. 960 961 @param {c3dl.Primitive|c3dl.ParticleSystem|c3dl.Point|c3dl.Line} obj The object to remove from the scene. 962 963 @return {boolean} true if the object was found and removed from the scene or 964 false if the argument 'obj' was not found. 965 */ 966 this.removeObjectFromScene = function(obj) 967 { 968 var isFound = false; 969 970 if (obj instanceof c3dl.Primitive || 971 obj instanceof c3dl.Point || 972 obj instanceof c3dl.Line || 973 obj instanceof c3dl.ParticleSystem) 974 { 975 // Check against each item in the list 976 for (var i = 0; i < objList.length; i++) 977 { 978 if (objList[i] == obj) 979 { 980 // Remove the item 981 objList.splice(i, 1); 982 isFound = true; 983 } 984 } 985 } 986 else 987 { 988 c3dl.debug.logWarning('Scene::removeObjectFromScene() called with an invalid argument.'); 989 } 990 991 return isFound; 992 } 993 994 /** 995 Start scene sets a default ambient light to white with full 996 intensity. If this ambient lighting is not desired, call 997 setAmbientLight(..) after this method, which will undo the 998 default ambient light values. 999 */ 1000 this.startScene = function() 1001 { 1002 if (c3dl.debug.SHARK === true) { 1003 connectShark(); 1004 startShark(); 1005 } 1006 numFramesSinceSceneStart = 0; 1007 frameCounter = 0; 1008 1009 // Safety Checks 1010 if (glCanvas3D == null) return false; 1011 if (renderer == null) return false; 1012 if (camera == null) return false; 1013 1014 // Start the timer 1015 lastTimeTaken = Date.now(); 1016 1017 // Benchmark hook: 1018 if (typeof(benchmarkSetupDone) == "function") benchmarkSetupDone(); 1019 1020 // Create a timer for this object 1021 timerID = setInterval(this.render, 5); 1022 1023 this.setAmbientLight([ambientLight[0], ambientLight[1],ambientLight[2]]); 1024 } 1025 1026 /** 1027 @private 1028 Render Loop 1029 */ 1030 this.render = function() 1031 { 1032 // calculate FPS. 1033 // we update the FPS after a second or more has elapsed. 1034 if (Date.now() - FPS_LastTimeTaken >= 1000) 1035 { 1036 // frames / seconds 1037 FPS = FPS_Counter / (Date.now() - FPS_LastTimeTaken) * 1000; 1038 FPS_Counter = 0; 1039 FPS_LastTimeTaken = Date.now(); 1040 } 1041 1042 // If a user wants to stop rendering, this is where it happens 1043 if (exitFlag) 1044 { 1045 timerID = clearInterval(timerID); 1046 if (c3dl.debug.SHARK === true) { 1047 stopShark(); 1048 disconnectShark(); 1049 } 1050 return; 1051 } 1052 1053 // update the camera and objects 1054 camera.update(Date.now() - lastTimeTaken); 1055 thisScn.updateObjects(Date.now() - lastTimeTaken); 1056 lastTimeTaken = Date.now(); 1057 1058 // The user may have added a texture to the scene in 1059 // which case, the renderer needs to create them. 1060 if(textureQueue.length > 0) 1061 { 1062 for(var i = 0; i < textureQueue.length; i++) 1063 { 1064 renderer.addTexture(textureQueue[i]); 1065 } 1066 // clear out the queue. It will be empty until the 1067 // user adds new textures. 1068 textureQueue = []; 1069 } 1070 1071 1072 // clear the depth buffer and color buffer. This needs to be 1073 // done every frame since objects likely have moved. 1074 renderer.clearBuffers(); 1075 1076 // we could have 2 canvases with different dimensions, but the same camera. 1077 // Therefore we need to update the camera with the aspect ratio since it's the 1078 // camera that creates the projection matrix. 1079 // 1080 // creates the projection matrix 1081 // this will place the view matrix at the bottom of the matrix stack. 1082 camera.applyToWorld(canvasTag.width/canvasTag.height); 1083 1084 // save the projection matrix so if the picking code needs to know what 1085 // projection matrix was used, it can query the scene. 1086 projMat = camera.getProjectionMatrix(); 1087 1088 // now that the view matrix has been pushed onto the opengl matrix stack, 1089 // we can specify the locations of the lights, which will use the top of the 1090 // matrix stack. 1091 thisScn.updateLights(); 1092 1093 // render objects in the scene. 1094 thisScn.renderObjects(glCanvas3D); 1095 1096 // we just rendered to the back buffer, so to see the changes, swap 1097 // the front and back buffers. 1098 //renderer.swapBuffers(); 1099 1100 numFramesSinceSceneStart++; 1101 FPS_Counter++; 1102 } 1103 1104 /** 1105 @private 1106 Updates all objects based on time. 1107 1108 @param {float} timeElapsed 1109 1110 @returns {boolean} True if something updated. 1111 */ 1112 this.updateObjects = function(timeElapsed) 1113 { 1114 // Call the User's update callback 1115 if (updateHandler != null) 1116 { 1117 updateHandler(timeElapsed); 1118 } 1119 1120 // update the rest of the objects individually 1121 for (var i = 0; i < objList.length; i++) 1122 { 1123 // we don't need to update lines or points since their 1124 // positions/coords are controlled by the user in the 1125 // update callback they write. 1126 switch(objList[i].getObjectType()) 1127 { 1128 case c3dl.PARTICLE_SYSTEM: 1129 case c3dl.COLLADA: 1130 objList[i].update(timeElapsed); 1131 } 1132 1133 1134 } 1135 1136 // update the SkyModel 1137 if(skyModel) 1138 { 1139 // 1140 skyModel.update(timeElapsed); 1141 1142 // move skymodel so the camera is at its center. 1143 // Let the user scale it and rotate it if they wish. 1144 skyModel.setPosition(camera.getPosition()); 1145 } 1146 } 1147 1148 /** 1149 @private 1150 Renders all objects to the screen. 1151 */ 1152 this.renderObjects = function() 1153 { 1154 // draw the skyModel if there is one. 1155 if(skyModel) 1156 { 1157 //glCanvas3D.disable(glCanvas3D.CULL_FACE); 1158 glCanvas3D.frontFace(glCanvas3D.CW); 1159 glCanvas3D.cullFace(glCanvas3D.BACK); 1160 1161 // We need to be able to draw the SkyModel, but without occluding any 1162 // objects in the scene. If the SkyModel is too small, it will occlude 1163 // other objects. To prevent this, we turn off the depth buffer, that 1164 // way ANY object drawn will just be drawn ontop of the SkyModel. 1165 glCanvas3D.disable(glCanvas3D.DEPTH_TEST); 1166 1167 // When rendering a 'skybox', the lights in the scene must 1168 // not light the model, since that would destroy the illusion 1169 // of the geometry being extremely far away. So all lights 1170 // must be turned off and texture of the 'skybox' needs to be 1171 // set to full intensity so the texture is rendered. 1172 1173 // get renderer's ambient light state 1174 var prevAmbient = this.getAmbientLight(); 1175 1176 // get renderer's lighting state 1177 var lightState = renderer.getLighting(); 1178 1179 // turn off renderer's light state 1180 renderer.setLighting(false); 1181 1182 // turn rendering ambient to full 1183 renderer.updateAmbientLight([1,1,1]); 1184 1185 // render skyModel 1186 skyModel.render(glCanvas3D, this); 1187 1188 // restore previous ambient light state 1189 renderer.setLighting(lightState); 1190 1191 // restore previous lighting state 1192 renderer.updateAmbientLight(prevAmbient); 1193 1194 // turn depth buffer back on so other object properly occlude each other. 1195 glCanvas3D.enable(glCanvas3D.DEPTH_TEST); 1196 } 1197 1198 glCanvas3D.enable(glCanvas3D.CULL_FACE); 1199 glCanvas3D.frontFace(glCanvas3D.CCW); 1200 glCanvas3D.cullFace(glCanvas3D.BACK); 1201 1202 // particle systems need to be rendered last, so first render 1203 // all opaque objects, then render particle systems. This is a bit 1204 // wasteful since we could have reordered the object list when the 1205 // particle system was inserted, but the getObj(index) would then have 1206 // been invalidated. 1207 var particleSystems = []; 1208 1209 for (var i = 0; i < objList.length; i++) 1210 { 1211 if(objList[i].getObjectType() == c3dl.PARTICLE_SYSTEM) 1212 { 1213 particleSystems.push(objList[i]); 1214 } 1215 1216 if(objList[i].getObjectType() == c3dl.COLLADA) 1217 { 1218 objList[i].render(glCanvas3D, this); 1219 } 1220 1221 } 1222 1223 // glCanvas3D.frontFace(glCanvas3D.CW); 1224 for(var i = 0; i < particleSystems.length; i++) 1225 { 1226 particleSystems[i].render(glCanvas3D, this); 1227 } 1228 1229 1230 // POINTS 1231 1232 // if first time 1233 //if(pointPositions == null) 1234 //{ 1235 pointPositions = new Array(); 1236 pointColors = new Array(); 1237 //} 1238 1239 var currPoint = 0; 1240 1241 // find all the points and group them together so we can make 1242 // only 1 call to drawArrays instead of once for each point. 1243 for(var i = 0; i < objList.length; i++) 1244 { 1245 if(objList[i].getObjectType() == c3dl.POINT && objList[i].isVisible()) 1246 { 1247 /* 1248 // if the array was already filled once before 1249 // only need to assign, not push, prevents the need to realloc array 1250 if( pointPositions.length > 0 && currPoint < pointPositions.length/3) 1251 { 1252 pointPositions[currPoint*3] = objList[i].getPosition()[0]; 1253 pointPositions[(currPoint*3)+1] = objList[i].getPosition()[1]; 1254 pointPositions[(currPoint*3)+2] = objList[i].getPosition()[2]; 1255 1256 pointColors[currPoint*3] = objList[i].getColor()[0]; 1257 pointColors[(currPoint*3)+1] = objList[i].getColor()[1]; 1258 pointColors[(currPoint*3)+2] = objList[i].getColor()[2]; 1259 currPoint++; 1260 } 1261 else 1262 {*/ 1263 pointPositions.push( objList[i].getPosition()[0] ); 1264 pointPositions.push( objList[i].getPosition()[1] ); 1265 pointPositions.push( objList[i].getPosition()[2] ); 1266 1267 pointColors.push( objList[i].getColor()[0] ); 1268 pointColors.push( objList[i].getColor()[1] ); 1269 pointColors.push( objList[i].getColor()[2] ); 1270 //} 1271 } 1272 } 1273 1274 renderer.renderPoints(pointPositions, pointColors, pointAttenuation, 1275 this.getPointSmooth(), this.getPointRenderingMode(), pointSize); 1276 1277 1278 // LINES 1279 1280 // collect all the lines from the scene, place them into this array 1281 // and pass the lines to the renderer. 1282 var lines = new Array(); 1283 1284 for(var j = 0; j < objList.length; j++) 1285 { 1286 if(objList[j].getObjectType() == c3dl.LINE && objList[j].isVisible()) 1287 { 1288 lines.push(objList[j]); 1289 } 1290 } 1291 renderer.renderLines(lines); 1292 } 1293 1294 /** 1295 Flags the main loop for exit. 1296 */ 1297 this.stopScene = function() 1298 { 1299 // This flags the main loop to exit gracefully 1300 exitFlag = true; 1301 } 1302 1303 /** 1304 @private 1305 Loads images before they are actually used. If a Model is created 1306 later in the life of the script with a texture which has not yet 1307 been loaded, the Model will be drawn without a texture until the 1308 texture is loaded. This function prevents this from happening. 1309 Alternatively, the textureManager can be acquired from the Scene 1310 and multiple calls to addTexture() can be called. This method simply 1311 serves as a convenience. 1312 1313 <p><b>This must be called after Scene's init()</b></p> 1314 1315 @param {string[]} imagePaths An array of paths of the images 1316 relative to the html file which holds the main script. 1317 */ 1318 this.preloadImages = function(imagePaths) 1319 { 1320 if(textureManager) 1321 { 1322 for (var i=0; i < imagePaths.length; i++) 1323 { 1324 textureManager.addTexture(imagePaths[i]); 1325 } 1326 } 1327 else 1328 { 1329 c3dl.debug.logError("preloadImage() must be called after Scene's init()"); 1330 } 1331 } 1332 } 1333