|
|
行 178: |
行 178: |
| ** applyToWorld(glCanvas3D, scene) - '''應該是「畫」出來的意思''' | | ** applyToWorld(glCanvas3D, scene) - '''應該是「畫」出來的意思''' |
| | | |
− | == 觀景窗 Scene == | + | == 畫布 Scene == |
− | <pre>
| + | * 方法: 讀取 |
− | /*
| + | ** getCamera() |
− | Copyright (c) 2008 Seneca College
| + | ** getObjListSize() |
− | Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
| + | ** getGL() - '''傳回 Canvas3D 引擎 |
− | */
| + | ** getTotalFrameCount() |
− | | + | ** getRenderer() |
− | /**
| + | ** getTextureManager() |
− | @class A Scene should be thought of as a scene on a movie set. A scene
| + | ** getScene() |
− | would typically contain objects which are moving and a current camera,
| + | ** getSkyModel() |
− | lights, etc.
| + | ** getAmbientLight() - '''內部有個 ambientLight 陣列變數均為浮點數,假設為 a, 傳回值卻是 [a[0]/5, a[1]/5, a[2]/5, a[3]] |
− | */
| + | ** getObj(indxNum) - '''傳回 Scene 中的物件''' |
− | function Scene()
| + | * 方法: 設定 |
− | {
| + | ** setKeyboardCallback(keyUpCB, keyDownCB) - '''註冊鍵盤按鍵事件的聆聽函式''' |
− | // Engine Variables
| + | ** setMouseCallback(mouseUpCB, mouseDownCB, mouseMoveCB, mouseScrollCB) - '''註冊滑鼠事件的聆聽函式''' |
− | var glCanvas3D = null; // OpenGL Context (Canvas)
| + | ** setSkyModel(sky) |
− | var renderer = null; // GLES 1.1 or 2.0
| + | ** setUpdateCallback(updateCB) - '''每次 Scene 重刷時叫用''' |
− | var camera = null; // Reference to a Camera type
| + | ** setRenderer(renderType) |
− | | + | ** setCanvasTag(canvasTag) |
− | // A reference to a model which will actually act as a
| + | ** setCamera(cam) |
− | // SkyBox, except any Model can be used, not just a box.
| + | ** addFloatingText(text, fontStyle, fontColour, backgroundColour) |
− | var skyModel = null;
| + | ** addTextToModel(model, text, fontStyle, fontColour, backgroundColour) |
− | | + | ** create2Dcanvas(width, height) - '''Create a 2D canvas for drawing text and other stuff''' |
− | var objList = new Array(); // An array of Objects to draw
| + | ** setBackgroundColor(bgColor) |
− | var exitFlag = false; // Exits the mail loop
| + | ** setAmbientLight(light) |
− | var cvs = null; // Canvas Tag
| + | ** init(name) |
− | var canvasHeight = 0;
| + | ** addObjectToScene(obj) |
− | var canvasWidth = 0;
| + | ** removeObjectFromScene(obj) |
− | var canvas2Dlist = new Array();
| + | ** startScene() |
− |
| + | ** render() |
− | // Input Handler Variables
| + | ** updateObjects(timeElapsed, camera) |
− | var kybdHandler = null;
| + | ** renderObjects() |
− | var mouseHandler = null;
| + | ** stopScene() |
− | var updateHandler = null;
| + | ** preloadImages(imagePaths) |
− |
| |
− | // 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>
| |
| | | |
| == 虛擬世界的物件 == | | == 虛擬世界的物件 == |
請見 Canvas3D JS Library
簡介
Canvas 3D JS Library (C3DL) 是 javascript 函式庫,也就是裡頭提供的全部是 Javascript, 必須安裝 Canvas3D extension of firefox, c3dl 程式碼在此。主要目的是讓你在 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
- 方法: 讀取
- getCamera()
- getObjListSize()
- getGL() - 傳回 Canvas3D 引擎
- getTotalFrameCount()
- getRenderer()
- getTextureManager()
- getScene()
- getSkyModel()
- getAmbientLight() - 內部有個 ambientLight 陣列變數均為浮點數,假設為 a, 傳回值卻是 [a[0]/5, a[1]/5, a[2]/5, a[3]]
- getObj(indxNum) - 傳回 Scene 中的物件
- 方法: 設定
- setKeyboardCallback(keyUpCB, keyDownCB) - 註冊鍵盤按鍵事件的聆聽函式
- setMouseCallback(mouseUpCB, mouseDownCB, mouseMoveCB, mouseScrollCB) - 註冊滑鼠事件的聆聽函式
- setSkyModel(sky)
- setUpdateCallback(updateCB) - 每次 Scene 重刷時叫用
- setRenderer(renderType)
- setCanvasTag(canvasTag)
- setCamera(cam)
- addFloatingText(text, fontStyle, fontColour, backgroundColour)
- addTextToModel(model, text, fontStyle, fontColour, backgroundColour)
- create2Dcanvas(width, height) - Create a 2D canvas for drawing text and other stuff
- setBackgroundColor(bgColor)
- setAmbientLight(light)
- init(name)
- addObjectToScene(obj)
- removeObjectFromScene(obj)
- startScene()
- render()
- updateObjects(timeElapsed, camera)
- renderObjects()
- stopScene()
- preloadImages(imagePaths)
虛擬世界的物件
物體
基本物件 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
];
[
[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
];
[
[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