舊文件

此處文件僅供參考,請自行考量時效性與適用程度,其他庫藏文件請參考文件頁面
我們亟需您的協助,進行共筆系統搬移、及文件整理工作,詳情請查閱參與我們

「C3dl」修訂間的差異

出自 MozTW Wiki

Scene
觀景窗 Scene
行 179: 行 179:
  
 
== 觀景窗 Scene ==
 
== 觀景窗 Scene ==
 +
<pre>
 +
/*
 +
  Copyright (c) 2008 Seneca College
 +
  Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
 +
*/
 +
 +
/**
 +
@class A Scene should be thought of as a scene on a movie set.  A scene
 +
would typically contain objects which are moving and a current camera,
 +
lights, etc.
 +
*/
 +
function Scene()
 +
{
 +
// Engine Variables
 +
var glCanvas3D = null; // OpenGL Context (Canvas)
 +
var renderer = null; // GLES 1.1 or 2.0
 +
var camera = null; // Reference to a Camera type
 +
 +
// A reference to a model which will actually act as a
 +
// SkyBox, except any Model can be used, not just a box.
 +
var skyModel = null;
 +
 +
var objList = new Array(); // An array of Objects to draw
 +
var exitFlag = false; // Exits the mail loop
 +
var cvs = null; // Canvas Tag
 +
var canvasHeight = 0;
 +
var canvasWidth = 0;
 +
var canvas2Dlist = new Array();
 +
 +
// Input Handler Variables
 +
var kybdHandler = null;
 +
var mouseHandler = null;
 +
var updateHandler = null;
 +
 +
// Performance Variables
 +
var lastTimeTaken = Date.now();
 +
var timerID = 0;
 +
var lastTimePrinted = Date.now(); // performance logging
 +
var frameCount = 0;
 +
var drewOnce = false;
 +
var totalFrameCount = 0;
 +
var backgroundColor = new Array(0.4, 0.4, 0.6, 1.0);
 +
var ambientLight = new Array(1,1,1,1);
 +
 +
// Shaders
 +
this.sp = null;
 +
var textureManager = null;
 +
 +
var thisScn = null;
 +
 +
 +
// -------------------------------------------------------
 +
 +
// Getters
 +
 +
/**
 +
Get the camera of the scene.
 +
 +
@returns {Camera} The camera of the scene.
 +
*/
 +
this.getCamera = function()
 +
{
 +
return camera;
 +
}
 +
 +
/**
 +
Get the number of objects in the scene.
 +
 +
@returns {int} The number of objects in the Object list.
 +
*/
 +
this.getObjListSize = function(){
 +
return objList.length;
 +
}
 +
 +
/**
 +
Get the context.
 +
 +
@returns {Context}
 +
*/
 +
this.getGL = function(){
 +
return glCanvas3D;
 +
}
 +
 +
/**
 +
*/
 +
this.getTotalFrameCount = function(){
 +
return totalFrameCount;
 +
}
 +
 +
/**
 +
Get the scene's Renderer
 +
 +
 +
*/
 +
this.getRenderer = function(){
 +
return renderer;
 +
}
 +
 +
/**
 +
Get the TextureManager of the scene.  One of the primary
 +
responsibilities of a TextureManager is to ensure textures
 +
are only loaded once.
 +
 +
@returns {TextureManager} The TextureManager of the scene.
 +
*/
 +
this.getTextureManager = function(){
 +
return textureManager;
 +
}
 +
 +
/**
 +
Get the Scene.
 +
 +
@returns {Scene}
 +
*/
 +
this.getScene = function(){
 +
return thisScn;
 +
}
 +
 +
/**
 +
Get the SkyModel.
 +
 +
@returns {Model} The Scene's SkyModel.
 +
*/
 +
this.getSkyModel = function()
 +
{
 +
return skyModel;
 +
}
 +
 +
/**
 +
Get the ambient light of the scene.
 +
 +
@returns {Array} An Array of four values.
 +
*/
 +
this.getAmbientLight = function()
 +
{
 +
return new Array(ambientLight[0]/5,ambientLight[1]/5,ambientLight[2]/5,ambientLight[3]);
 +
}
 +
 +
/**
 +
Get a reference of a particular object in the scene.
 +
 +
@param indxNum The index number of the object.
 +
 +
@return the reference to the object at index number indxNum or null
 +
if indxNum was out of bounds.
 +
*/
 +
this.getObj = function(indxNum)
 +
{
 +
if (isNaN(indxNum))
 +
{
 +
logWarning('Scene::getObj() called with a parameter that\'s not a number');
 +
return null;
 +
}
 +
// Check if the index that was asked for is inside the bounds of our array
 +
if (indxNum < 0 || indxNum >= objList.length)
 +
{
 +
logWarning('Scene::getObj() called with ' + indxNum +', which is not betwen 0 and ' + objList.length);
 +
return null;
 +
}
 +
 +
// We do this because we dont want outsiders modifying the object list,
 +
// just the object themselves (ie. changing position, orientation, etc)
 +
return objList[indxNum];
 +
}
 +
 +
// -------------------------------------------------------
 +
 +
// Setters
 +
/**
 +
Set the functions to call when a key is pressed or released.
 +
 +
@param {function} keyUpCB The callback function for the up key.
 +
@param {function} keyDownCD The callback function for the down key.
 +
*/
 +
this.setKeyboardCallback = function(keyUpCB, keyDownCB)
 +
{
 +
if (cvs)
 +
{
 +
// Register True keyboard listeners
 +
if (keyUpCB != null) document.addEventListener("keyup", keyUpCB, false);
 +
if (keyDownCB != null) document.addEventListener("keydown", keyDownCB, false);
 +
}
 +
}
 +
 +
/**
 +
Pass in the functions to call when mouse event occur such as when
 +
a button is pressed, released or the mousewheel is scrolled.  The
 +
scene will call these functions when the event occur.
 +
 +
@param {function} mouseUpCB
 +
@param {function} mouseDownCB
 +
@param {function} mouseMoveCB
 +
@param {function} mouseScrollCB
 +
*/
 +
this.setMouseCallback = function(mouseUpCB, mouseDownCB, mouseMoveCB, mouseScrollCB)
 +
{
 +
if (cvs)
 +
{
 +
// Register all Mouse listeners
 +
if (mouseMoveCB != null) cvs.addEventListener("mousemove", mouseMoveCB, false);
 +
if (mouseUpCB != null) cvs.addEventListener("mouseup", mouseUpCB, false);
 +
if (mouseDownCB != null) cvs.addEventListener("mousedown", mouseDownCB, false);
 +
if (mouseScrollCB != null) cvs.addEventListener("DOMMouseScroll", mouseScrollCB, false);
 +
}
 +
}
 +
 +
 +
/**
 +
Set the SkyModel. A SkyModel acts like a skybox, when the camera
 +
moves in the scene the SkyModel maintains the same distance from
 +
the camera.  This creates the illusion that there are parts to
 +
the scene that are very far away which cannot be reached. 
 +
Applications of this would include creating clouds, or mountain
 +
ranges, starfields, etc.  Any Model can be passed in and is not
 +
restricted to a Cube.  Whatever model that is appropirate should
 +
be used.
 +
 +
@param {Model} sky A Model which will maintain the same distance
 +
from the Scene's camera.
 +
*/
 +
this.setSkyModel = function(sky)
 +
{
 +
if( sky instanceof Model)
 +
{
 +
skyModel = sky;
 +
}
 +
}
 +
 +
/**
 +
Set the function to call everytime the scene is updated.
 +
 +
@param {function} updateCB The function to call everytime the
 +
scene is updated.
 +
*/
 +
this.setUpdateCallback = function(updateCB)
 +
{
 +
if (cvs)
 +
{
 +
if (updateCB != null) updateHandler = updateCB;
 +
}
 +
}
 +
 +
/**
 +
Set the renderer.  Currently only 2 renderers are supported:
 +
OpemGLES11 and OpenGLES20.
 +
 +
@param {OpenGLES11|OpenGLES20}
 +
*/
 +
this.setRenderer = function(renderType)
 +
{
 +
// Set the type of renderer to use
 +
if (renderType instanceof OpenGLES11 ||
 +
renderType instanceof OpenGLES20)
 +
{
 +
renderer = renderType;
 +
}
 +
}
 +
 +
/**
 +
@param canvasTag The name of the canvas, that is the value which is
 +
assigned to the id property of the canvas tag in the html file.
 +
*/
 +
this.setCanvasTag = function(canvasTag)
 +
{
 +
// Get the Canvas tag
 +
cvs = document.getElementById(canvasTag);
 +
 +
if (cvs == null)
 +
{
 +
logWarning('Scene::createScene() No canvas tag with name ' + canvasTag + ' was found.');
 +
}
 +
}
 +
 +
/**
 +
Set the Scene's camera.
 +
 +
@param {Camera} cam The camera.
 +
*/
 +
this.setCamera = function(cam)
 +
{
 +
// Check to see if we were passed a correct Camera class
 +
if (cam instanceof ChaseCamera ||
 +
cam instanceof FreeCamera ||
 +
cam instanceof FixedCamera ||
 +
cam instanceof PanCamera)
 +
{
 +
camera = cam;
 +
return true;
 +
}
 +
 +
logWarning('Scene::setCamera() invalid type of camera.');
 +
return false;
 +
}
 +
 +
/**
 +
  This one just calls addTextToModel()
 +
  //!! need dest as a parameter, probably in pixels, where to put the text
 +
*/
 +
this.addFloatingText = function(text, fontStyle, fontColour, backgroundColour)
 +
{
 +
var box = this.addTextToModel(null, text, fontStyle, fontColour, backgroundColour);
 +
box.stayInFrontOfCamera = true;
 +
this.addObjectToScene(box);
 +
}
 +
 +
/**
 +
  Create a 2D canvas, render the text into it, and use that as a texture for model.
 +
  If model is null, create a rectangle and stick the text onto it.
 +
*/
 +
this.addTextToModel = function(model, text, fontStyle, fontColour, backgroundColour)
 +
{
 +
// Create a SPAN element with the string and style matching what the user asked
 +
// for the floating text.
 +
var tempSpan = document.createElement('span');
 +
var tempSpanStyle = document.createElement('style');
 +
var tempSpanStyleContent = document.createTextNode('span{' +
 +
'font: ' + fontStyle + ';' +
 +
'color: ' + fontColour + '; ' +
 +
'background: ' + backgroundColour +
 +
  ';}');
 +
var tempText = document.createTextNode(text);
 +
tempSpanStyle.appendChild(tempSpanStyleContent);
 +
tempSpan.appendChild(tempSpanStyle);
 +
tempSpan.appendChild(tempText);
 +
 +
// Append it to the body so it's momentarily displayed. I couldn't find a way to measure
 +
// the text box's size without displaying it.
 +
document.body.appendChild(tempSpan);
 +
 +
var actualStringWidth = tempSpan.offsetWidth;
 +
var actualStringHeight = tempSpan.offsetHeight;
 +
var stringWidth = roundUpToNextPowerOfTwo(tempSpan.offsetWidth);
 +
var stringHeight = roundUpToNextPowerOfTwo(tempSpan.offsetHeight);
 +
 +
// Now get rid of that element, we only needed it to measure it
 +
tempSpan.removeChild(tempSpanStyle);
 +
document.body.removeChild(tempSpan);
 +
 +
var box;
 +
if (model == null)
 +
{
 +
var whRatio = stringWidth / stringHeight;
 +
 +
// Model for the plane with the text, size based on whRatio
 +
var smallCanvasVertices =
 +
[
 +
[-1.0 * (whRatio / 2), -1.0, 0.0], // 0 - bottom left
 +
[-1.0 * (whRatio / 2),  1.0, 0.0], // 1 - top left
 +
[ 1.0 * (whRatio / 2),  1.0, 0.0], // 2 - top right
 +
[ 1.0 * (whRatio / 2), -1.0, 0.0], // 3 - bottom right
 +
];
 +
var smallCanvasNormals =
 +
[
 +
[0,0,-1]
 +
];
 +
var smallCanvasUVs =
 +
[
 +
[0.0,1.0], // 0 - bottom left
 +
[0.0,0.0], // 1 - top left
 +
[1.0,0.0], // 2 - top right
 +
[1.0,1.0] // 3 - bottom right
 +
];
 +
var smallCanvasFaces =
 +
[
 +
[0,0,0], [3,3,0], [2,2,0],
 +
[0,0,0], [2,2,0], [1,1,0]
 +
];
 +
 +
box = new Model();
 +
box.init(smallCanvasVertices, smallCanvasNormals, smallCanvasUVs, smallCanvasFaces);
 +
//box.setAngularVel(new Array(0.003, 0.000, 0.000));
 +
//box.pitch(-0.4);
 +
 +
//!! need something user-specified
 +
box.setPosition(new Array(5, 0, 5));
 +
}
 +
else
 +
box = model;
 +
 +
// Draw the text into the 2D canvas and use it for the above model's texture
 +
var textureCanvas = this.create2Dcanvas(stringWidth, stringHeight);
 +
if (textureCanvas.getContext)
 +
{
 +
var ctx = textureCanvas.getContext('2d');
 +
 +
if (fontStyle)
 +
ctx.mozTextStyle = fontStyle;
 +
 +
// Fill everything with backgroundColour if it's specified
 +
if (backgroundColour)
 +
{
 +
ctx.fillStyle = backgroundColour;
 +
ctx.fillRect(0, 0, stringWidth, stringHeight);
 +
}
 +
 +
// Center the text in the 2D canvas
 +
ctx.translate((stringWidth - actualStringWidth) / 2,
 +
  stringHeight - (stringHeight - actualStringHeight));
 +
 +
if (fontColour)
 +
ctx.fillStyle = fontColour;
 +
else
 +
ctx.fillStyle = 'black';
 +
 +
ctx.mozDrawText(text);
 +
 +
box.setTextureFromCanvas2D(textureCanvas.id);
 +
textureManager.addTextureFromCanvas2D(textureCanvas.id);
 +
}
 +
else
 +
logWarning("addFloatingText(): call to create2Dcanvas() failed");
 +
 +
return box;
 +
}
 +
 +
/**
 +
  Create a 2D canvas for drawing text and other stuff. Keep a
 +
  reference to it.
 +
 +
  @return {CanvasTag}
 +
*/
 +
this.create2Dcanvas = function(width, height)
 +
{
 +
var newCanvas = document.createElement('canvas');
 +
newCanvas.id = 'changemetorandomstring';
 +
newCanvas.width = width;
 +
newCanvas.height = height;
 +
cvs.appendChild(newCanvas);
 +
 +
canvas2Dlist.push(newCanvas);
 +
 +
return newCanvas;
 +
}
 +
 +
/**
 +
Set the color of the background. Values are clamped to the
 +
range [0,1].
 +
 +
@param {Array} bgColor Four values in the order: red, green,
 +
blue, alpha/intensity.
 +
*/
 +
this.setBackgroundColor = function(bgColor)
 +
{
 +
if( bgColor.length == 4 )
 +
{
 +
for(var i=0; i < 4; i++)
 +
{
 +
backgroundColor[i] = bgColor[i];
 +
}
 +
}
 +
}
 +
 +
/**
 +
Set the ambient light of the scene.
 +
 +
@param {Array} light An array of 4 floating point values
 +
ranging from 0 to 1.
 +
*/
 +
this.setAmbientLight = function(light)
 +
{
 +
if( light.length == 4 )
 +
{
 +
// convert the values passed in which range from 0 to 1 to a range of
 +
// 0 to 5, which OpenGL wants.
 +
ambientLight = new Array(light[0]*5, light[1]*5, light[2]* 5, 1);
 +
// set the ambient light for the scene
 +
// note the values passed to lightModel are not clamped
 +
// also only available in 1.1
 +
if( renderer instanceof OpenGLES11 && glCanvas3D != null)
 +
{
 +
glCanvas3D.lightModel(glCanvas3D.LIGHT_MODEL_AMBIENT, ambientLight);
 +
}
 +
}
 +
}
 +
 +
 +
/**
 +
Acquire the OpenGL Context
 +
 +
@param {string} name
 +
 +
@returns {boolean}
 +
*/
 +
this.init = function(name)
 +
{
 +
if (renderer != null && cvs != null)
 +
{
 +
// Initialize the renderer
 +
if (!renderer.createRenderer(cvs))
 +
{
 +
logWarning('Scene::createScene() Renderer failed to initialize.');
 +
return false;
 +
}
 +
 +
// Get the Canvas
 +
glCanvas3D = renderer.getGLContext();
 +
 +
// Set our global (fake static variable) to be used in rendering
 +
thisScn = this;
 +
 +
textureManager = new TextureManager(glCanvas3D);
 +
 +
// Get the size of the Canvas Space for Aspect Ratio calculation
 +
canvasWidth = cvs.width;
 +
canvasHeight = cvs.height;
 +
 +
// Initialize the renderer
 +
return renderer.init(canvasWidth, canvasHeight);
 +
}
 +
 +
logWarning('Scene::createScene() No renderer was specified.');
 +
return false;
 +
}
 +
 +
 +
/**
 +
Add the object 'obj' to the scene.  Currently only objects which are
 +
Models, Primitives or Cubes can be added to the scene.
 +
 +
@param {Model|Primitive|Cube} obj A reference to an object, such as a Model or a Cube.
 +
 +
@return {boolean} True if the object was added to the scene, false otherwise.
 +
*/
 +
this.addObjectToScene = function(obj)
 +
{
 +
// Check to see if we were passed a correct Camera class
 +
if (obj instanceof Model ||
 +
obj instanceof Primitive ||
 +
obj instanceof Cube)
 +
{
 +
objList.push(obj);
 +
 +
return true;
 +
}
 +
 +
logWarning('Scene::addObjectToScene() called with a parameter that\'s not a Model, Primitive, or Cube');
 +
return false;
 +
}
 +
 +
/**
 +
Remove an object from the scene. This is an O(n) operation.
 +
 +
@param {Model|Primitive|Cube} obj The object to remove from the scene.
 +
 +
@return {boolean} True if the object was found and removed from the scene and
 +
false if the obj argument was not a type of Model, Primitive or Cube
 +
or could not be found.
 +
*/
 +
this.removeObjectFromScene = function(obj)
 +
{
 +
// Check to see if we were passed a correct Camera class
 +
if (obj instanceof Model ||
 +
obj instanceof Primitive ||
 +
obj instanceof Cube )
 +
{
 +
// Check against each item in the list
 +
for (var i = 0; i < objList.length; i++)
 +
{
 +
if (objList[i] == obj)
 +
{
 +
// Remove the item
 +
objList.splice(i, 1);
 +
 +
return true;
 +
}
 +
}
 +
}
 +
 +
logWarning('Scene::removeObjectFromScene() called with a parameter that\'s not a Model or Primitive');
 +
return false;
 +
}
 +
 +
/**
 +
Start scene sets a default ambient light to white with full
 +
intensity.  If this ambient lighting is not desired, call
 +
setAmbientLight(..) after this method, which will undo the
 +
default ambient light values.
 +
*/
 +
this.startScene = function()
 +
{
 +
var timeDate = new Date();
 +
var lastTimeTaken = timeDate.getMilliseconds();
 +
totalFrameCount = 0;
 +
 +
// Safety Checks
 +
if (glCanvas3D == null) return false;
 +
if (renderer == null) return false;
 +
if (camera == null) return false;
 +
 +
// Start the timer
 +
lastTimeTaken = Date.now();
 +
this.render();
 +
 +
// Benchmark hook:
 +
if (typeof(benchmarkSetupDone) == "function") benchmarkSetupDone();
 +
 +
// Create a timer for this object
 +
timerID = setInterval(this.render, 25);
 +
 +
this.setAmbientLight(new Array(ambientLight[0], ambientLight[1],ambientLight[2],ambientLight[3]));
 +
}
 +
 +
/**
 +
Render Loop
 +
*/
 +
this.render = function()
 +
{
 +
// If a user wants to stop rendering, this is where it happens
 +
if (exitFlag)
 +
{
 +
timerID = clearInterval(timerID);
 +
return;
 +
}
 +
 +
// Performance logging
 +
if (0)
 +
{
 +
var currentTime = new Date();
 +
 +
if (currentTime - lastTimePrinted >= 1000)
 +
{
 +
logInfo(frameCount + ' FPS');
 +
lastTimePrinted = currentTime;
 +
 +
frameCount = 0;
 +
}
 +
 +
frameCount++;
 +
}
 +
 +
var cameraUpdated;
 +
var objectsUpdated;
 +
 +
// Update the Camera (If there is animation)
 +
cameraUpdated = camera.update(Date.now() - lastTimeTaken);
 +
 +
// Update the objects
 +
objectsUpdated = thisScn.updateObjects(Date.now() - lastTimeTaken, camera);
 +
lastTimeTaken = Date.now();
 +
 +
if (cameraUpdated || objectsUpdated || !drewOnce)
 +
{
 +
// Set the clearColor which is the color clear() uses when its called
 +
glCanvas3D.clearColor(backgroundColor[0],backgroundColor[1],backgroundColor[2],backgroundColor[3]);
 +
 +
// clear the depth buffer, needs to be done for every
 +
// frame since objects may be moved every frame
 +
glCanvas3D.clear(glCanvas3D.COLOR_BUFFER_BIT | glCanvas3D.DEPTH_BUFFER_BIT);
 +
 +
// Set the camera in world space
 +
camera.applyToWorld(glCanvas3D, thisScn);
 +
 +
// Do Rendering
 +
thisScn.renderObjects(glCanvas3D);
 +
 +
// Swap buffers to render
 +
glCanvas3D.swapBuffers();
 +
 +
// textures need time to load and if the scene is static,
 +
// then the scene is likely to render then load the textures
 +
// leaving the objects without their textures.  For now, draw
 +
// the scene regardless if objects or the camera moves or not.
 +
//drewOnce = true;
 +
}
 +
totalFrameCount++;
 +
}
 +
 +
/**
 +
Updates all objects based on time.
 +
 +
@param {float} timeElapsed
 +
 +
@returns {boolean} True if something updated.
 +
*/
 +
this.updateObjects = function(timeElapsed, camera)
 +
{
 +
var updatedSomething = false;
 +
 +
// Call the User's update callback
 +
if (updateHandler != null)
 +
{
 +
updateHandler(timeElapsed);
 +
}
 +
 +
// Update the rest of the objects individually
 +
for (var i = 0; i < objList.length; i++)
 +
{
 +
if (objList[i].update(timeElapsed, camera))
 +
updatedSomething = true;
 +
}
 +
 +
// update the SkyModel
 +
if(skyModel)
 +
{
 +
// scale it, rotate it, translate it, whatever.
 +
if(skyModel.update(timeElapsed))
 +
{
 +
updatedSomething = true;
 +
}
 +
 +
// but in the end, move it so the camera is at its center.
 +
// Let the user scale it and rotate it if they wish.
 +
skyModel.setPosition(camera.getPosition());
 +
}
 +
 +
return updatedSomething;
 +
}
 +
 +
/**
 +
Renders all objects to the screen.
 +
*/
 +
this.renderObjects = function()
 +
{
 +
// draw the skyModel if there is one.
 +
if(skyModel)
 +
{
 +
// We need to be able to draw the SkyModel, but without occluding any
 +
// objects in the scene.  If the SkyModel is too small, it will occlude
 +
// other objects. To prevent this, we turn off the depth buffer, that
 +
// way ANY object draw will just be drawn ontop of the SkyModel.
 +
glCanvas3D.disable(glCanvas3D.DEPTH_TEST);
 +
 +
// now turn off the lights since the 'skybox' is prelit
 +
// and lights illuminating it would really make no sense.
 +
// But we first have to save the light states so we turn
 +
// on the right ones after the 'skybox' is rendered.
 +
// to do....
 +
 +
// Since we are drawing the 'inside' of a Model in this case, we have
 +
// to cull faces pointing away from the camera.
 +
glCanvas3D.enable(glCanvas3D.CULL_FACE);
 +
glCanvas3D.frontFace(glCanvas3D.CW);
 +
 +
skyModel.render(glCanvas3D, this);
 +
 +
// turn the depth buffer back on.
 +
glCanvas3D.enable(glCanvas3D.DEPTH_TEST);
 +
 +
// turn the lights that were on, back on.
 +
// to do....
 +
}
 +
 +
// don't draw the insides of objects.
 +
glCanvas3D.enable(glCanvas3D.CULL_FACE);
 +
glCanvas3D.frontFace(glCanvas3D.CCW);
 +
 +
// Render each object separately
 +
for (var i = 0; i < objList.length; i++)
 +
{
 +
objList[i].render(glCanvas3D, this);
 +
}
 +
}
 +
 +
/**
 +
Flags the main loop for exit.
 +
*/
 +
this.stopScene = function()
 +
{
 +
// This flags the main loop to exit gracefully
 +
exitFlag = true;
 +
}
 +
 +
/**
 +
Loads images before they are actually used.  If a Model is created
 +
later in the life of the script with a texture which has not yet
 +
been loaded, the Model will be drawn without a texture until the
 +
texture is loaded.  This function prevents this from happening.
 +
Alternatively, the textureManager can be acquired from the Scene
 +
and multiple calls to addTexture() can be called. This method simply
 +
serves as a convenience.
 +
 +
<p><b>This must be called after Scene's init()</b></p>
 +
 +
@param {string[]} imagePaths An array of paths of the images
 +
relative to the html file which holds the main script.
 +
*/
 +
this.preloadImages = function(imagePaths)
 +
{
 +
if(textureManager)
 +
{
 +
for(var i=0; i < imagePaths.length; i++)
 +
{
 +
textureManager.addTexture(imagePaths[i]);
 +
}
 +
}
 +
else
 +
{
 +
logError("preloadImage() must be called after Scene's init()");
 +
}
 +
}
 +
}
 +
</pre>
  
 
== 虛擬世界的物件 ==
 
== 虛擬世界的物件 ==

於 2008年10月9日 (四) 13:23 的修訂

請見 Canvas3D JS Library

簡介

Canvas 3D JS Library (C3DL) 是 javascript 函式庫,也就是裡頭提供的全部是 Javascript, 必須安裝 Canvas3D extension of firefoxc3dl 程式碼在此。主要目的是讓你在 Firefox/Mozilla 平台用 Canvas/OpenGL 的方式撰寫 3D 的網路應用。

C3DL 提供一系列的數學、景觀、及3D物件類別,讓你在用 Canvas 會更有彈性,當然主要就是要縮短開發時間。

本專案開發人員

  • Catherine Leung
  • Mark Paruzel (CodeBot)
  • Andrew Smith
  • Chris Bishop (Javascript)
  • Andor Salga

有用的連結

類別繼承圖

數學運算

我的感覺是這份 c3dl 並不是非常有效率,可以稍微修正寫法,譬如參考 paperVision3D

Vector 向量類別

一個向量基本上就是在 3D 世界的 X, Y, Z 三個軸的座標系統描述一個「具備大小的方向」。3D 數學存在各種不同的座標系統,離開向量類別的封裝則不復存在所謂的 3D。向量類別具有下列的成員;

  • isValidVector(vecArr) - 判斷參數是否為一有效的向量
  • copyVector(srcVec) - 從 srcVec 複製並傳回
  • copyVectorContents(srcVec, destVec) - 效果相當於(不等於,因為有多作判斷是否為有效向量) destVec = copyVector(srcVec);
  • makeVector(newX, newY, newZ) - copyVector() 就是用這個函數實作向量複製
  • normalizeVector(vec) - 計算與向量相同方向但長度為一的「單位向量」並傳回
  • vectorDotProduct(vecOne, vecTwo) - 傳回兩向量的內積(點積),其值為純量
  • vectorCrossProduct(vecOne, vecTwo, dest) - 將 vecOne 與 vecTwo 兩個向量做外積(叉積)後指定給 dest)
  • vectorLength(vec) - 計算並傳回向量的長度(相當於與自己的內積開根號)
  • vectorLengthSq(vec) - 計算向量長度的平方,相比於直接利用長度的運算上,少了一個根號後再平方的無用計算
  • addVectors(vecOne, vecTwo, dest) - 相當於 dest = vecOne + vecTwo
  • subtractVectors(vecOne, vecTwo, dest) - 相當於 dest = vecOne - vecTwo
  • multiplyVector(vec, scalar, dest) - 相當於 dest = vec * scalar; 效果相當於將向量放大(scalar 小於1的正數則為縮小,負數則為反向)
  • divideVector(vec, scalar, dest) - 相當於 dest = vec / scalar; 效果相當於將向量縮小(scalar 小於1的正數則為放大,負數則為反向)
  • multiplyVectorByVector(vecOne, vecTwo, dest) - 既非內積也非外積,而是相當於 dest = [X1*X2, Y1*Y2, Z1*Z2]; 的乘法
  • isVectorEqual(vecOne, vecTwo) - 判斷兩向量是否相等
  • isVectorZero(vec) - 判斷向量長度是否為 0,正確的說法: 判斷是否極接近 0,每個軸向誤差在 0.00001 以內
  • getAngleBetweenVectors(vecOne, vecTwo) - 計算兩向量間的夾角

Matrix 矩陣類別

c3dl 的矩陣在數學上是一個 4x4 的二維矩陣,但是在 Javascript 實作上是用一維陣列來表達,而不是二維陣列,其索引值如下:

      +-               -+
      |  0,  4,  8, 12  |
      |  1,  5,  9, 13  |
      |  2,  6, 10, 14  |
      |  3,  7, 11, 15  |
      +-               -+
  • isValidMatrix(mat) - 判斷是否為有效的矩陣
  • makeIdentityMatrix() - 產生單位矩陣,也就是斜對角都為 1 其餘為 0 的矩陣
  • makeZeroMatrix() - 產生全部是0 的矩陣
  • makeMatrix(e00, e01, e02, e03, e10, e11, e12, e13, e20, e21, e22, e23, e30, e31, e32, e33) - 利用參數產生矩陣,其索引值順序如上述,或由此處的宣告亦可得知
  • matricesEqual(matrix1, matrix2) - 判斷兩矩陣是否相等
  • makePoseMatrix(vecLeft, vecUp, vecFrwd, vecPos) - 位置矩陣,通常用來處理「手勢」,效果如下:
		 +-                            -+
		 |  Left.x, Up.x, Fwd.x, Pos.x  |
		 |  Left.y, Up.y, Fwd.y, Pos.y  |
		 |  Left.z, Up.z, Fwd.z, Pos.z  |
		 |  0.0,    0.0,  0.0,   1.0    |
		 +-                            -+
  • transposeMatrix(mat) - 傳回轉置矩陣: 型如
	+-            -+ 
	|  A, B, C, D  |
	|  E, F, G, H  |
	|  I, J, K, L  |
	|  M, N, O, P  |
	+-            -+

轉置結果為:

	+-            -+
	|  A, E, I, M  |
	|  B, F, J, N  |
	|  C, G, K, O  |
	|  D, H, L, P  |
	+-            -+
  • inverseMatrix(mat) - 計算並傳回 mat 的反矩陣,使得 res * mat = I, 其中 I 是單位矩陣,參考 反矩陣
  • matrixDeterminant(mat) - 計算並傳回 mat 的行列式,通常用來算面積或體積,其值為純量,參考 行列式
  • matrixAdjoint(mat) - 不知道怎麼翻,伴隨矩陣?共軛矩陣?參考 adjoint matrix
  • multiplyMatrixByScalar(mat, scalar) - 每個元素都乘以 scalar
  • divideMatrixByScalar(mat, scalar) - 每個元素都除以 scalar
  • multiplyMatrixByMatrix(matOne, matTwo) - 矩陣乘法,結果亦為矩陣
  • multiplyMatrixByVector(mat, vec) - 將矩陣乘以向量,通常用來作向量的旋轉之類用途,結果亦為向量(其實是 4x4 矩陣與 4x1 矩陣相乘)
  • addMatrices(matOne, matTwo) - 兩矩陣相對應元素相加
  • subtractMatrices(matOne, matTwo) - 兩矩陣相對應元素相減

Quaternion 四元數

四元數顧名思義就是四個元素的數,請參考 四元數

  • isValidQuat(quat) - 傳回是否為有效的四元數
  • makeQuat(newW, newX, newY, newZ) - 傳回四元數 Quat = W + X * i + Y * j + Z * k, 其中 i, j, k 是虛部
  • quatToMatrix(quat) - 將四元數以矩陣來表示,其轉換如下:
+ ------------  ----------  ----------  --- +
  1 - 2(YY+ZZ)  2(XY+WZ)    2(XZ-WY)     0
  2(XY-WZ)      1-2(XX+ZZ)  2(YZ+WX)     0
  2(XZ+WY)      2(YZ-WX)    1-2(XX+YY)   0
  0             0           0            1
+ ------------  ----------  ----------  --- +
  • quatToAxisAngle(axisVec, angleScalar) - 用來將四元素的旋轉變成軸角的旋轉,不過似乎有 bug
  • axisAngleToQuat(axisVec, angleScalar) - 轉換軸角的旋轉為四元數的旋轉
  • matrixToQuat(newMat) - 將矩陣轉為四元數
  • quatLengthSq(quat) - 四元數的長度平方,等於 XX+YY+ZZ
  • quatLength(quat) - 四元數的長度 sqrt(XX+YY+ZZ)
  • addQuats(quatOne, quatTwo) - 兩個四元數相加
  • subtractQuats(quatOne, quatTwo) - 兩個四元數相減
  • multiplyQuatByScalar(quatOne, scalar) - 兩個四元數相減
  • getQuatConjugate(quat) - 四元數的共軛四元數(虛部分別為其負值)
  • quatDotProduct(quatOne, quatTwo) - 四元數的內積,其值為純量,但是並不等於 3D 意義中的長度
  • normalizeQuat(quat) - 四元數的正規化
  • inverseQuat(quat) - 反四元數

Camera 相機

Camera 有分 ChaseCamera, FixedCamera, FreeCamera, PanCamera,就讓我們一個一個來看吧

ChaseCamera

尚未實作,空的

FixedCamera

嗚嗚,似乎也是未實作,看 code 似乎也沒人用到

  • 屬性
    • globalPos - 相機位置,向量
    • globalOri - 相機方向,矩陣
    • nearClipping - 預設值 1.0
    • farClipping - 預設值 500
    • fieldView - 預設值 60
    • aspectRatio - 預設值 0
  • 方法
    • setGlobalPos(posVec) - 未實作
    • setGlobalOri(oriMat) - 未實作

PanCamera

尚未實作,空的

FreeCamera

  • 方法: 讀值
    • getPosition() - 傳回位置向量
    • getUp() - 傳回相機的 up vector(老實說我不知道這是啥或是要幹嘛)
    • getDir() - 傳回相機瞧的方向 vector
    • getLeft() - 傳回相機的 left vector(老實說我不知道這是啥或是要幹嘛)
    • getLinearVel() - 註解說是 Animation of positions,其值是向量
    • getAngularVel() - Animations of rotation around (side Vector, up Vector, dir Vector)
  • 方法: 設定
    • setPosition(newVec) - 設定相機位置
    • setLookAtPoint(newVec) - 設定相機看的點,必須移(應該是沒有轉)動相機(其實是轉動所有物件)
    • setUpVector(newVec) - 設定 up vector
    • setLinearVel(newVec) - 設定相機的旋轉速度?,其值是向量
    • setAngularVel(newVec) - 設定相機轉動軸?
  • 其他功能
    • rotateOnAxis(axis, angle) - 讓相機沿著某個向量軸 axis 繞行 angle 角度
    • yaw(angle) - 讓相機沿著其 Up vector 轉動 angle 角度
    • roll(angle) - 讓相機沿著其方向向量轉動 angle 角度
    • pitch(angle) - 讓相機沿著其 left vector 轉動 angle 角度
    • update(timeElapsed) - 這個函數跟設定旋轉速度有關,我猜是供內部呼叫
    • applyToWorld(glCanvas3D, scene) - 應該是「畫」出來的意思

觀景窗 Scene

/*
  Copyright (c) 2008 Seneca College
  Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
*/

/**
	@class A Scene should be thought of as a scene on a movie set.  A scene 
	would typically contain objects which are moving and a current camera,
	lights, etc.
*/
function Scene()
{
	// Engine Variables
	var glCanvas3D		= null; // OpenGL Context (Canvas)
	var renderer		= null; // GLES 1.1 or 2.0
	var camera			= null; // Reference to a Camera type

	// A reference to a model which will actually act as a 
	// SkyBox, except any Model can be used, not just a box.
	var skyModel		= null;

	var objList			= new Array(); // An array of Objects to draw
	var exitFlag		= false; // Exits the mail loop
	var cvs				= null; // Canvas Tag
	var canvasHeight	= 0;
	var canvasWidth		= 0;
	var canvas2Dlist	= new Array();
	
	// Input Handler Variables
	var kybdHandler		= null;
	var mouseHandler	= null;
	var updateHandler	= null;
	
	// Performance Variables
	var lastTimeTaken	= Date.now();
	var timerID			= 0;
	var lastTimePrinted	= Date.now(); // performance logging
	var frameCount		= 0;
	var drewOnce		= false;
	var totalFrameCount = 0;
	var backgroundColor = new Array(0.4, 0.4, 0.6, 1.0);
	var ambientLight	= new Array(1,1,1,1);
	
	// Shaders
	this.sp				= null;
	var textureManager = null;
		
	var thisScn = null;

	
	// -------------------------------------------------------
	
	// Getters

	/**
		Get the camera of the scene.

		@returns {Camera} The camera of the scene.
	*/
	this.getCamera = function()
	{
		return camera; 
	}

	/**
		Get the number of objects in the scene.

		@returns {int} The number of objects in the Object list.
	*/
	this.getObjListSize = function(){
		return objList.length;
	}

	/**
		Get the context.

		@returns {Context}
	*/
	this.getGL = function(){
		return glCanvas3D;
	}

	/**
	*/
	this.getTotalFrameCount = function(){
		return totalFrameCount; 
	}

	/**
		Get the scene's Renderer


	*/
	this.getRenderer = function(){
		return renderer;
	}

	/**
		Get the TextureManager of the scene.  One of the primary 
		responsibilities of a TextureManager is to ensure textures 
		are only loaded once.

		@returns {TextureManager} The TextureManager of the scene.
	*/
	this.getTextureManager = function(){
		return textureManager;
	}

	/**
		Get the Scene.

		@returns {Scene}
	*/	
	this.getScene = function(){
		return thisScn;
	}

	/**
		Get the SkyModel.

		@returns {Model} The Scene's SkyModel.
	*/
	this.getSkyModel = function()
	{
		return skyModel;
	}

	/**
		Get the ambient light of the scene.

		@returns {Array} An Array of four values.
	*/	
	this.getAmbientLight = function()
	{
		return new Array(ambientLight[0]/5,ambientLight[1]/5,ambientLight[2]/5,ambientLight[3]);
	}
	
	/**
		Get a reference of a particular object in the scene.

		@param indxNum The index number of the object.

		@return the reference to the object at index number indxNum or null 
		if indxNum was out of bounds.
	*/
	this.getObj = function(indxNum)
	{
		if (isNaN(indxNum))
		{
			logWarning('Scene::getObj() called with a parameter that\'s not a number');
			return null;
		}
		// Check if the index that was asked for is inside the bounds of our array
		if (indxNum < 0 || indxNum >= objList.length)
		{
			logWarning('Scene::getObj() called with ' + indxNum +', which is not betwen 0 and ' + objList.length);
			return null;
		}
		
		// We do this because we dont want outsiders modifying the object list,
		// just the object themselves (ie. changing position, orientation, etc)
		return objList[indxNum];
	}
	
	// -------------------------------------------------------

	// Setters
	/**
		Set the functions to call when a key is pressed or released.

		@param {function} keyUpCB The callback function for the up key.		
		@param {function} keyDownCD The callback function for the down key.
	*/
	this.setKeyboardCallback = function(keyUpCB, keyDownCB) 
	{
		if (cvs)
		{
			// Register True keyboard listeners
			if (keyUpCB != null) document.addEventListener("keyup", keyUpCB, false);
			if (keyDownCB != null) document.addEventListener("keydown", keyDownCB, false);
		}
	}
	
	/**
		Pass in the functions to call when mouse event occur such as when 
		a button is pressed, released or the mousewheel is scrolled.  The
		scene will call these functions when the event occur.

		@param {function} mouseUpCB
		@param {function} mouseDownCB
		@param {function} mouseMoveCB
		@param {function} mouseScrollCB
	*/
	this.setMouseCallback = function(mouseUpCB, mouseDownCB, mouseMoveCB, mouseScrollCB) 
	{ 
		if (cvs)
		{
			// Register all Mouse listeners
			if (mouseMoveCB != null) cvs.addEventListener("mousemove", mouseMoveCB, false);
			if (mouseUpCB != null) cvs.addEventListener("mouseup", mouseUpCB, false);
			if (mouseDownCB != null) cvs.addEventListener("mousedown", mouseDownCB, false);
			if (mouseScrollCB != null) cvs.addEventListener("DOMMouseScroll", mouseScrollCB, false);
		}
	}
	

	/**
		Set the SkyModel. A SkyModel acts like a skybox, when the camera 
		moves in the scene the SkyModel maintains the same distance from 
		the camera.  This creates the illusion that there are parts to 
		the scene that are very far away which cannot be reached.  
		Applications of this would include creating clouds, or mountain 
		ranges, starfields, etc.  Any Model can be passed in and is not
		restricted to a Cube.  Whatever model that is appropirate should 
		be used.

		@param {Model} sky A Model which will maintain the same distance 
		from the Scene's camera.
	*/
	this.setSkyModel = function(sky)
	{
		if( sky instanceof Model)
		{
			skyModel = sky;
		}
	}

	/**
		Set the function to call everytime the scene is updated.

		@param {function} updateCB The function to call everytime the
		scene is updated.
	*/
	this.setUpdateCallback = function(updateCB)
	{
		if (cvs)
		{
			if (updateCB != null) updateHandler = updateCB;
		}
	}

	/**
		Set the renderer.  Currently only 2 renderers are supported:
		OpemGLES11 and OpenGLES20.

		@param {OpenGLES11|OpenGLES20}
	*/
	this.setRenderer = function(renderType)
	{
		// Set the type of renderer to use
		if (renderType instanceof OpenGLES11 ||
			renderType instanceof OpenGLES20)
		{
			renderer = renderType;
		}
	}
	
	/**
		@param canvasTag The name of the canvas, that is the value which is
		assigned to the id property of the canvas tag in the html file.
	*/
	this.setCanvasTag = function(canvasTag)
	{
		// Get the Canvas tag
		cvs = document.getElementById(canvasTag);

		if (cvs == null)
		{
			logWarning('Scene::createScene() No canvas tag with name ' + canvasTag + ' was found.');
		}
	}

	/**
		Set the Scene's camera.

		@param {Camera} cam The camera.
	*/
	this.setCamera = function(cam)
	{
		// Check to see if we were passed a correct Camera class
		if (cam instanceof ChaseCamera ||
			cam instanceof FreeCamera ||
			cam instanceof FixedCamera ||
			cam instanceof PanCamera)
		{
			camera = cam;
			return true;
		}

		logWarning('Scene::setCamera() invalid type of camera.');
		return false;
	}
	
	/**
	   This one just calls addTextToModel()
	   //!! need dest as a parameter, probably in pixels, where to put the text
	*/
	this.addFloatingText = function(text, fontStyle, fontColour, backgroundColour)
	{
		var box = this.addTextToModel(null, text, fontStyle, fontColour, backgroundColour);
		box.stayInFrontOfCamera = true;
		this.addObjectToScene(box);
	}
	
	/**
	   Create a 2D canvas, render the text into it, and use that as a texture for model.
	   If model is null, create a rectangle and stick the text onto it.
	*/
	this.addTextToModel = function(model, text, fontStyle, fontColour, backgroundColour)
	{
		// Create a SPAN element with the string and style matching what the user asked
		// for the floating text.
		var tempSpan = document.createElement('span');
		var tempSpanStyle = document.createElement('style');
		var tempSpanStyleContent = document.createTextNode('span{' + 
										'font: ' + fontStyle + ';' +
										'color: ' + fontColour + '; ' +
										'background: ' + backgroundColour + 
														   ';}');
		var tempText = document.createTextNode(text);
		tempSpanStyle.appendChild(tempSpanStyleContent);
		tempSpan.appendChild(tempSpanStyle);
		tempSpan.appendChild(tempText);
		
		// Append it to the body so it's momentarily displayed. I couldn't find a way to measure
		// the text box's size without displaying it.
		document.body.appendChild(tempSpan);
		
		var actualStringWidth = tempSpan.offsetWidth;
		var actualStringHeight = tempSpan.offsetHeight;
		var stringWidth = roundUpToNextPowerOfTwo(tempSpan.offsetWidth);
		var stringHeight = roundUpToNextPowerOfTwo(tempSpan.offsetHeight);
		
		// Now get rid of that element, we only needed it to measure it
		tempSpan.removeChild(tempSpanStyle);
		document.body.removeChild(tempSpan);
		
		var box;
		if (model == null)
		{
			var whRatio = stringWidth / stringHeight;

			// Model for the plane with the text, size based on whRatio
			var smallCanvasVertices = 
			[
				[-1.0 * (whRatio / 2), -1.0, 0.0],	// 0 - bottom left
				[-1.0 * (whRatio / 2),  1.0, 0.0],	// 1 - top left
				[ 1.0 * (whRatio / 2),  1.0, 0.0],	// 2 - top right
				[ 1.0 * (whRatio / 2), -1.0, 0.0],	// 3 - bottom right
			];
			var smallCanvasNormals = 
			[
				[0,0,-1]
			];
			var smallCanvasUVs = 
			[
				[0.0,1.0],	// 0 - bottom left
				[0.0,0.0],	// 1 - top left
				[1.0,0.0],	// 2 - top right	
				[1.0,1.0]	// 3 - bottom right
			];
			var smallCanvasFaces =
			[
				[0,0,0], [3,3,0], [2,2,0],
				[0,0,0], [2,2,0], [1,1,0]
			];
			
			box = new Model();
			box.init(smallCanvasVertices, smallCanvasNormals, smallCanvasUVs, smallCanvasFaces);
			//box.setAngularVel(new Array(0.003, 0.000, 0.000));
			//box.pitch(-0.4);
			
			//!! need something user-specified
			box.setPosition(new Array(5, 0, 5));
		}
		else
			box = model;
		
		// Draw the text into the 2D canvas and use it for the above model's texture
		var textureCanvas = this.create2Dcanvas(stringWidth, stringHeight);
		if (textureCanvas.getContext) 
		{
			var ctx = textureCanvas.getContext('2d');
			
			if (fontStyle)
				ctx.mozTextStyle = fontStyle;
			
			// Fill everything with backgroundColour if it's specified
			if (backgroundColour)
			{
				ctx.fillStyle = backgroundColour;
				ctx.fillRect(0, 0, stringWidth, stringHeight);
			}
			
			// Center the text in the 2D canvas
			ctx.translate((stringWidth - actualStringWidth) / 2,
						  stringHeight - (stringHeight - actualStringHeight));
			
			if (fontColour)
				ctx.fillStyle = fontColour;
			else
				ctx.fillStyle = 'black';
			
			ctx.mozDrawText(text);
			
			box.setTextureFromCanvas2D(textureCanvas.id);
			textureManager.addTextureFromCanvas2D(textureCanvas.id);
		}
		else
			logWarning("addFloatingText(): call to create2Dcanvas() failed");
		
		return box;
	}
	
	/**
	   Create a 2D canvas for drawing text and other stuff. Keep a 
	   reference to it.

	   @return {CanvasTag}
	*/
	this.create2Dcanvas = function(width, height)
	{
		var newCanvas = document.createElement('canvas');
		newCanvas.id = 'changemetorandomstring';
		newCanvas.width = width;
		newCanvas.height = height;
		cvs.appendChild(newCanvas);
		
		canvas2Dlist.push(newCanvas);
		
		return newCanvas;
	}
	
	/**
		Set the color of the background. Values are clamped to the 
		range [0,1].
		
		@param {Array} bgColor Four values in the order: red, green, 
		blue, alpha/intensity.
	*/
	this.setBackgroundColor = function(bgColor)
	{
		if( bgColor.length == 4 )
		{
			for(var i=0; i < 4; i++)
			{
				backgroundColor[i] = bgColor[i];
			}
		}
	}
	
	/**
		Set the ambient light of the scene.

		@param {Array} light An array of 4 floating point values 
		ranging from 0 to 1.
	*/
	this.setAmbientLight = function(light)
	{
		if( light.length == 4 )
		{
			// convert the values passed in which range from 0 to 1 to a range of
			// 0 to 5, which OpenGL wants.
			ambientLight = new Array(light[0]*5, light[1]*5, light[2]* 5, 1);			
			// set the ambient light for the scene
			// note the values passed to lightModel are not clamped
			// also only available in 1.1			
			if( renderer instanceof OpenGLES11 && glCanvas3D != null)
			{
				glCanvas3D.lightModel(glCanvas3D.LIGHT_MODEL_AMBIENT, ambientLight);
			}
		}	
	}


	/**
		Acquire the OpenGL Context

		@param {string} name

		@returns {boolean}
	*/
	this.init = function(name)
	{
		if (renderer != null && cvs != null)
		{
			// Initialize the renderer
			if (!renderer.createRenderer(cvs))
			{
				logWarning('Scene::createScene() Renderer failed to initialize.');
				return false;
			}

			// Get the Canvas
			glCanvas3D = renderer.getGLContext();
				
			// Set our global (fake static variable) to be used in rendering
			thisScn = this;

			textureManager = new TextureManager(glCanvas3D);		
			
			// Get the size of the Canvas Space for Aspect Ratio calculation
			canvasWidth = cvs.width;
			canvasHeight = cvs.height;

			// Initialize the renderer
			return renderer.init(canvasWidth, canvasHeight);
		}

		logWarning('Scene::createScene() No renderer was specified.');
		return false;
	}


	/**
		Add the object 'obj' to the scene.  Currently only objects which are
		Models, Primitives or Cubes can be added to the scene.
		
		@param {Model|Primitive|Cube} obj A reference to an object, such as a Model or a Cube.

		@return {boolean} True if the object was added to the scene, false otherwise.
	*/
	this.addObjectToScene = function(obj)
	{
		// Check to see if we were passed a correct Camera class
		if (obj instanceof Model ||
			obj instanceof Primitive ||
			obj instanceof Cube)
		{
			objList.push(obj);

			return true;
		}

		logWarning('Scene::addObjectToScene() called with a parameter that\'s not a Model, Primitive, or Cube');
		return false;
	}

	/**
		Remove an object from the scene. This is an O(n) operation.

		@param {Model|Primitive|Cube} obj The object to remove from the scene.

		@return {boolean} True if the object was found and removed from the scene and
		false if the obj argument was not a type of Model, Primitive or Cube
		or could not be found.
	*/
	this.removeObjectFromScene = function(obj)
	{
		// Check to see if we were passed a correct Camera class
		if (obj instanceof Model ||
			obj instanceof Primitive ||
			obj instanceof Cube	)
		{
			// Check against each item in the list
			for (var i = 0; i < objList.length; i++)
			{
				if (objList[i] == obj)
				{
					// Remove the item
					objList.splice(i, 1);

					return true;
				}
			}
		}

		logWarning('Scene::removeObjectFromScene() called with a parameter that\'s not a Model or Primitive');
		return false;
	}
	
	/**
		Start scene sets a default ambient light to white with full 
		intensity.  If this ambient lighting is not desired, call 
		setAmbientLight(..) after this method, which will undo the
		default ambient light values.
	*/
	this.startScene = function()
	{
		var timeDate = new Date();
		var lastTimeTaken = timeDate.getMilliseconds();
		totalFrameCount = 0;
		
		// Safety Checks
		if (glCanvas3D == null) return false;
		if (renderer == null) return false;
		if (camera == null) return false;
		
		// Start the timer
		lastTimeTaken = Date.now();
		this.render();
		
		// Benchmark hook:
		if (typeof(benchmarkSetupDone) == "function") benchmarkSetupDone();
		
		// Create a timer for this object
		timerID = setInterval(this.render, 25);

		this.setAmbientLight(new Array(ambientLight[0], ambientLight[1],ambientLight[2],ambientLight[3]));
	}
	
	/**
		Render Loop
	*/
	this.render = function()
	{
		// If a user wants to stop rendering, this is where it happens
		if (exitFlag)
		{
			timerID = clearInterval(timerID);
			return;
		}
		
		// Performance logging
		if (0)
		{
			var currentTime = new Date();
			
			if (currentTime - lastTimePrinted >= 1000)
			{
				logInfo(frameCount + ' FPS');
				lastTimePrinted = currentTime;
				
				frameCount = 0;
			}
			
			frameCount++;
		}
			
		var cameraUpdated;
		var objectsUpdated;
		
		// Update the Camera (If there is animation)
		cameraUpdated = camera.update(Date.now() - lastTimeTaken);
		
		// Update the objects
		objectsUpdated = thisScn.updateObjects(Date.now() - lastTimeTaken, camera);
		lastTimeTaken = Date.now();
		
		if (cameraUpdated || objectsUpdated || !drewOnce)
		{
			// Set the clearColor which is the color clear() uses when its called			
			glCanvas3D.clearColor(backgroundColor[0],backgroundColor[1],backgroundColor[2],backgroundColor[3]);
			
			// clear the depth buffer, needs to be done for every 
			// frame since objects may be moved every frame
			glCanvas3D.clear(glCanvas3D.COLOR_BUFFER_BIT | glCanvas3D.DEPTH_BUFFER_BIT);
			
			// Set the camera in world space
			camera.applyToWorld(glCanvas3D, thisScn);
			
			// Do Rendering
			thisScn.renderObjects(glCanvas3D);
			
			// Swap buffers to render
			glCanvas3D.swapBuffers();
			
			// textures need time to load and if the scene is static, 
			// then the scene is likely to render then load the textures
			// leaving the objects without their textures.  For now, draw
			// the scene regardless if objects or the camera moves or not.
			//drewOnce = true;
		}
		totalFrameCount++;
	}
	
	/**
		Updates all objects based on time.

		@param {float} timeElapsed

		@returns {boolean} True if something updated.
	*/
	this.updateObjects = function(timeElapsed, camera)
	{
		var updatedSomething = false;
		
		// Call the User's update callback
		if (updateHandler != null)
		{
			updateHandler(timeElapsed);
		}
		
		// Update the rest of the objects individually
		for (var i = 0; i < objList.length; i++)
		{
			if (objList[i].update(timeElapsed, camera))
				updatedSomething = true;
		}

		// update the SkyModel
		if(skyModel)
		{
			// scale it, rotate it, translate it, whatever.
			if(skyModel.update(timeElapsed))
			{
				updatedSomething = true;
			}

			// but in the end, move it so the camera is at its center.
			// Let the user scale it and rotate it if they wish.
			skyModel.setPosition(camera.getPosition());
		}
		
		return updatedSomething;
	}
	
	/**
		Renders all objects to the screen.
	*/
	this.renderObjects = function()
	{
		// draw the skyModel if there is one.
		if(skyModel)
		{	
			// We need to be able to draw the SkyModel, but without occluding any
			// objects in the scene.  If the SkyModel is too small, it will occlude
			// other objects. To prevent this, we turn off the depth buffer, that
			// way ANY object draw will just be drawn ontop of the SkyModel.
			glCanvas3D.disable(glCanvas3D.DEPTH_TEST);

			// now turn off the lights since the 'skybox' is prelit
			// and lights illuminating it would really make no sense.
			// But we first have to save the light states so we turn
			// on the right ones after the 'skybox' is rendered.
			// to do....

			// Since we are drawing the 'inside' of a Model in this case, we have
			// to cull faces pointing away from the camera.
			glCanvas3D.enable(glCanvas3D.CULL_FACE);
			glCanvas3D.frontFace(glCanvas3D.CW);

			skyModel.render(glCanvas3D, this);

			// turn the depth buffer back on.
			glCanvas3D.enable(glCanvas3D.DEPTH_TEST);

			// turn the lights that were on, back on.
			// to do....
		}

		// don't draw the insides of objects.
		glCanvas3D.enable(glCanvas3D.CULL_FACE);
		glCanvas3D.frontFace(glCanvas3D.CCW);

		// Render each object separately
		for (var i = 0; i < objList.length; i++)
		{
			objList[i].render(glCanvas3D, this);
		}
	}
	
	/**
		Flags the main loop for exit.
	*/
	this.stopScene = function()
	{
		// This flags the main loop to exit gracefully
		exitFlag = true;
	}

	/**
		Loads images before they are actually used.  If a Model is created 
		later in the life of the script with a texture which has not yet 
		been loaded, the Model will be drawn without a texture until the
		texture is loaded.  This function prevents this from happening.
		Alternatively, the textureManager can be acquired from the Scene
		and multiple calls to addTexture() can be called. This method simply 
		serves as a convenience.

		<p><b>This must be called after Scene's init()</b></p>

		@param {string[]} imagePaths An array of paths of the images 
		relative to the html file which holds the main script.
	*/
	this.preloadImages = function(imagePaths)
	{
		if(textureManager)
		{
			for(var i=0; i < imagePaths.length; i++)
			{
				textureManager.addTexture(imagePaths[i]);
			}
		}
		else
		{
			logError("preloadImage() must be called after Scene's init()");
		}
	}
}

虛擬世界的物件

物體

基本物件 Primitive

  • 基本屬性
    • visible: true - 物件是否可見
    • textureName - 紋理,譬如木頭?鐵?
    • name - 每個物件都有自己的名字
  • 原始位置,所有物件含相機都有 left, up, dir, pos 等資訊
    • left: (1, 0, 0) - left vector
    • up: (0, 1, 0) - up vector
    • dir: (0, 0, 1) - forward vector
    • pos: (0, 0, 0) - 位置
    • scaleVec: (1, 1, 1) - 放大率,預設就是 1
  • 移動資訊
    • linVel: (0, 0, 0) - 移動"速度"為 0, 預設是靜止
    • angVel: (0, 0, 0) - 轉動方向為 0, 預設是不轉動
  • 取值
    • getPosition() - 傳回 pos 值
    • getUp() - 傳回 up vector
    • getDirection() - 傳回 dir vector
    • getLeft() - 傳回 left vector
    • getLinearVel() - 傳回速度 linVel 向量值
    • getAngularVel() - 傳迴旋轉向量 angVel
    • isVisible() - 傳回是否可見
    • getScale() - 傳回放大率
    • getName() - 傳回物件名稱
    • getTextureName() - 傳迴紋理名稱
  • 設定
    • setTexture(imageFilename) - 設定紋理檔案名稱
    • setTextureFromCanvas2D(sourceCanvas) - 從 Canvas2D 設定紋理
    • unsetTexture() - 重設紋理為空的
    • setName(name) - 設定物件名稱
    • setVisible(show) - 設定物件是否可見
    • scale(scaleVec) - 設定物件放大比例
    • setPosition(vecPos) - 設定物件位置 pos 值
    • translate(translation) - 將物件放到新位置(相當於 pos+translation)
    • setForward(newVec) - 將物件往 newVec 方向移動,這會影響到 up, left 等向量
    • setUpVector(newVec) - 設定 up vector 成 newVec 值
    • setLinearVel(newVec) - 設定速度值為 newVec 值
    • setAngularVel(newVec) - 設定旋轉方向為 newVec 值
    • rotateOnAxis(axisVec, angle) - 以四元數及矩陣來計算物件的旋轉, 先移動 dir, 再計算相對應的 left, up 值
    • yaw(angle) - 效果是 rotateOnAxis(up, angle)
    • roll(angle) - 效果是 rotateOnAxis(dir, angle)
    • pitch(angle) - 效果是 rotateOnAxis(left, angle)
    • update(timeStep) - 依照速度向量 linVel 移動物體的 pos 位置向量,並調整 up, dir, left 等向量值
    • render(glCanvas3D) - 透過 glCanvas3D 這個 plugin 把物件畫出來,這邊會有顏色,只是怪怪的

Model (繼承自 Primitive)

  • 屬性
    • loadedTexture: false - 是否已載入紋理
    • expandedVertices - 似乎要用來放大縮小用的
    • expandedNormals
    • expandedUVs
    • buffers
    • firstTimeRender: true - 第一次畫出來
    • stayInFrontOfCamera: false - 是否在相機前,應該是與是否可見有關
  • 方法
    • init(vertices, normals, texcoords, faces) - 必要參數是 vertices, faces。normal是單位大小,或許與燈光有關,texcoords 是紋理座標(不懂),而 faces 則是引索陣列,引用 texcoords 來當其表面紋理
    • initDAE(path) - dae是Collada的檔案副檔名,是用xml去儲存3d的模型
    • createBuffers(glCanvas3D) - 應該是在根據 expandedVertices 準備畫布,若有設定 expandedUVs, expandedNormals, 也會準備相對應的 Buffer
    • update(timeStep, camera) - 會先根據相機移動相對位置後,再根據自己的速度與旋轉來移動自己
    • render(glCanvas3D, scene) - 要把物體畫出來,相對上麻煩的多

立方體 Cube

繼承自基本物件 Primitive

轉換矩陣

為了效率考量,定義了幾個轉換用的矩陣:

  • cube_transition_Vertices =
	[
		[-1, -1, 1],	// 0 - front, bottom, left
		[-1,  1, 1],	// 1 - front, top, left
		[ 1,  1, 1],	// 2 - front, top, right
		[ 1, -1, 1],	// 3 - front, bottom, right
		
		[-1, -1, -1],	// 4 - back, bottom, left
		[-1,  1, -1],	// 5 - back, top, left
		[ 1,  1, -1],	// 6 - back, top, right
		[ 1, -1, -1]	// 7 - back, bottom, right	
	];
  • cube_transition_Normals =
	[
		[-0.57735,-0.57735, 0.57735],	// front, bottom, left
		[-0.57735, 0.57735, 0.57735],	// front, top, left	
		[ 0.57735, 0.57735, 0.57735],	// front, top, right	
		[ 0.57735,-0.57735, 0.57735],	// front, bottom, right		
		
		[-0.57735,-0.57735, -0.57735],	// back, bottom, left
		[-0.57735, 0.57735, -0.57735],	// back, top, left
		[ 0.57735, 0.57735, -0.57735],	// back, top, right	
		[ 0.57735,-0.57735, -0.57735]	// back, bottom, right	
	];
  • cube_transition_UVs =
	[
		[0.0,1.0],	// 0 - bottom left
		[0.0,0.0],	// 1 - top left
		[1.0,0.0],	// 2 - top right
		[1.0,1.0]		// 3 - bottom right
	];
  • cube_transition_Faces =
	[
		[0,0,0], [3,3,3], [2,2,2],	// front
		[0,0,0], [2,2,2], [1,1,1],
			
		[5,2,5], [6,1,6], [7,0,7],	// back
		[5,2,5], [7,0,7], [4,3,4],

		[4,0,4], [7,3,7], [3,2,3],	// bottom
		[4,0,4], [3,2,3], [0,1,0],

		[1,0,1], [2,3,2], [6,2,6],	// top
		[1,0,1], [6,2,6], [5,1,5],
		
		[4,0,4], [0,3,0], [1,2,1],	// left side
		[4,0,4], [1,2,1], [5,1,5],
		
		[3,0,3], [7,3,7], [6,2,6],	// right side
		[3,0,3], [6,2,6], [2,1,2]
	];
屬性與方法
  • 屬性
    • m - 就是 Model 物件
    • 其餘均繼承自 Model
  • 方法 - 除 scale 外,全部繼承自 Model
    • init(), isVisible()
    • getPosition(), getUp(), getDirection(), getLeft(), getLinearVel(), getAngularVel(), getScale(), getTextureName(),
    • setTexture(imageFilename), setTextureFromCanvas2D(sourceCanvas), unsetTexture(), setVisible(show)
    • setPosition(vecPos), setForward(newVec), setUpVector(newVec),
    • translate(translation), setLinearVel(newVec), setAngularVel(newVec), rotateOnAxis(axisVec, angle)
    • yaw(angle), roll(angle), pitch(angle), update(timeStep), render(glCanvas3D, scene)
    • scale(scaleVec, scaleY, scaleZ) - 多了 Y, Z 二個軸向的放大縮小?忽略的話就跟 Model 一樣

Primitive Class

Model Class

個人工具