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