「C3dl」修訂間的差異
出自 MozTW Wiki
(→立方體 Cube) |
(→Model) |
||
| 行 231: | 行 231: | ||
** update(timeStep) - '''依照速度向量 linVel 移動物體的 pos 位置向量,並調整 up, dir, left 等向量值''' | ** update(timeStep) - '''依照速度向量 linVel 移動物體的 pos 位置向量,並調整 up, dir, left 等向量值''' | ||
** render(glCanvas3D) - '''透過 glCanvas3D 這個 plugin 把物件畫出來,這邊會有顏色,只是怪怪的''' | ** render(glCanvas3D) - '''透過 glCanvas3D 這個 plugin 把物件畫出來,這邊會有顏色,只是怪怪的''' | ||
| + | |||
| + | ==== Model ==== | ||
| + | <pre> | ||
| + | /*Copyright (c) 2008 Seneca College | ||
| + | |||
| + | Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/) | ||
| + | */ | ||
| + | Model.prototype = new Primitive; | ||
| + | |||
| + | /** | ||
| + | @class A Model is an object which has a geometric representation composed of | ||
| + | vertices, normals and uv coordinates. Models may also have textures | ||
| + | applied to them. | ||
| + | */ | ||
| + | function Model() | ||
| + | { | ||
| + | var loadedTexture = false; | ||
| + | |||
| + | this.expandedVertices = new Array(); | ||
| + | this.expandedNormals = null; | ||
| + | this.expandedUVs = null; | ||
| + | |||
| + | this.buffers = {}; | ||
| + | this.firstTimeRender = true; | ||
| + | |||
| + | this.stayInFrontOfCamera = false; | ||
| + | |||
| + | /** | ||
| + | <p> | ||
| + | This method should be called after a model object has been | ||
| + | created using: | ||
| + | </p> | ||
| + | |||
| + | <p> | ||
| + | <code> | ||
| + | var model = new Model(); | ||
| + | </code> | ||
| + | </p> | ||
| + | |||
| + | <p> | ||
| + | null values may be provided for the normals and texcoord arguments, | ||
| + | however the vertices and faces arrays are required. | ||
| + | </p> | ||
| + | |||
| + | @param {Array} vertices Array of the unique vertices of the model. | ||
| + | |||
| + | @param {Array} [normals] Array of the unique normals of the | ||
| + | model, if null lighting will not work correctly for this model. | ||
| + | |||
| + | @param {Array} [texcoords] Array of texture coordinates of | ||
| + | the model. If null, textures cannot be applied to this model. | ||
| + | |||
| + | @param {Array of Arrays} faces Array of arrays which contains index | ||
| + | values which refer to values in the previous arrays. | ||
| + | */ | ||
| + | this.init = function(vertices, normals, texcoords, faces) | ||
| + | { | ||
| + | var texCoordsPresent = false; | ||
| + | var normalsPresent = false; | ||
| + | |||
| + | |||
| + | if(typeof vertices == "string") | ||
| + | { | ||
| + | this.initDAE(vertices); | ||
| + | } | ||
| + | |||
| + | else{ | ||
| + | // At the least, vertices and faces must be present | ||
| + | if( vertices instanceof Array && faces instanceof Array) | ||
| + | { | ||
| + | if( texcoords instanceof Array) | ||
| + | { | ||
| + | texCoordsPresent = true; | ||
| + | this.expandedUVs = new Array(); | ||
| + | } | ||
| + | |||
| + | if( normals instanceof Array) | ||
| + | { | ||
| + | normalsPresent = true; | ||
| + | this.expandedNormals = new Array(); | ||
| + | } | ||
| + | |||
| + | for( var i =0; i < faces.length; i++) | ||
| + | { | ||
| + | // get vert index | ||
| + | var vertIndex = faces[i][0]; | ||
| + | var cvert = vertices[vertIndex]; | ||
| + | this.expandedVertices.push(cvert[0]); | ||
| + | this.expandedVertices.push(cvert[1]); | ||
| + | this.expandedVertices.push(cvert[2]); | ||
| + | |||
| + | // push on uvs | ||
| + | if( texcoords instanceof Array) | ||
| + | { | ||
| + | var texIndex = faces[i][1]; | ||
| + | var ctex = texcoords[texIndex]; | ||
| + | this.expandedUVs.push(ctex[0]); | ||
| + | this.expandedUVs.push(ctex[1]); | ||
| + | } | ||
| + | |||
| + | // push on normals | ||
| + | if( normals instanceof Array) | ||
| + | { | ||
| + | var offset = (texCoordsPresent == false) ? 1: 2; | ||
| + | |||
| + | var normIndex = faces[i][offset]; | ||
| + | var cnorm = normals[normIndex]; | ||
| + | this.expandedNormals.push(cnorm[0]); | ||
| + | this.expandedNormals.push(cnorm[1]); | ||
| + | this.expandedNormals.push(cnorm[2]); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | this.initDone = true; | ||
| + | } | ||
| + | |||
| + | |||
| + | this.initDAE = function(path) | ||
| + | { | ||
| + | // add the model to the model pool. | ||
| + | // it is either added or is already present. | ||
| + | ModelManager.addModel(path); | ||
| + | |||
| + | // now, get the data | ||
| + | // this.expandedNormals = ModelManager.getNormals(path); | ||
| + | // this.expandedUVs = ModelManager.getUVs(path); | ||
| + | this.expandedVertices = ModelManager.getModel(path); | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | @private | ||
| + | |||
| + | @param {context} glCanvas3D | ||
| + | */ | ||
| + | this.createBuffers = function(glCanvas3D) | ||
| + | { | ||
| + | this.buffers.vertex = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 3, glCanvas3D.FLOAT, this.expandedVertices); | ||
| + | |||
| + | if( this.expandedUVs ) | ||
| + | { | ||
| + | this.buffers.tex = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 2, glCanvas3D.FLOAT, this.expandedUVs); | ||
| + | } | ||
| + | |||
| + | if( this.expandedNormals ) | ||
| + | { | ||
| + | this.buffers.normal = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 3, glCanvas3D.FLOAT, this.expandedNormals); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | /** | ||
| + | Update animations for linear velocity and angular velocity. | ||
| + | |||
| + | @param {float} timeStep | ||
| + | |||
| + | @returns {boolean} true. | ||
| + | */ | ||
| + | this.update = function(timeStep, camera) | ||
| + | { | ||
| + | if (this.stayInFrontOfCamera) | ||
| + | { | ||
| + | //!! All this math is crap because it does't work when the camera rotates | ||
| + | |||
| + | var camUp = camera.getUp(); | ||
| + | var camLeft = camera.getLeft(); | ||
| + | var camDir = camera.getDir(); | ||
| + | this.up = makeVector(-camUp[0], -camUp[1], -camUp[2]); | ||
| + | this.left = makeVector(-camLeft[0], -camLeft[1], -camLeft[2]); | ||
| + | this.dir = makeVector(-camDir[0], -camDir[1], -camDir[2]); | ||
| + | |||
| + | var camPos = camera.getPosition(); | ||
| + | //!! These hard-coded values are a hack, need to set them based | ||
| + | //!! on where we want the floating thing to be in the canvas | ||
| + | this.pos = makeVector(camPos[0] + 10, camPos[1] - 15, camPos[2] + 10); | ||
| + | |||
| + | //logWarning('camera ' + camPos); | ||
| + | //logWarning('plane ' + this.pos); | ||
| + | |||
| + | return true; | ||
| + | } | ||
| + | |||
| + | // if the texture is loaded, it requires and update. | ||
| + | if (this.loadedTexture == true) | ||
| + | { | ||
| + | //this.loadedTexture = false; | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | if (isVectorZero(this.linVel) && isVectorZero(this.angVel)) | ||
| + | { | ||
| + | // nothing will change | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | if (!isVectorZero(this.linVel)) | ||
| + | { | ||
| + | // Add a velocity to the position | ||
| + | var velVec = new Array(); | ||
| + | velVec = this.linVel; | ||
| + | multiplyVector(velVec, timeStep); | ||
| + | addVectors(this.pos, velVec, this.pos); | ||
| + | } | ||
| + | |||
| + | if (!isVectorZero(this.angVel)) | ||
| + | { | ||
| + | // Apply some rotations to the orientation from the angular velocity | ||
| + | this.pitch(this.angVel[0] * timeStep); | ||
| + | this.yaw(this.angVel[1] * timeStep); | ||
| + | this.roll(this.angVel[2] * timeStep); | ||
| + | } | ||
| + | |||
| + | // force update. | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | Render the model. | ||
| + | |||
| + | @param {context} glCanvas3D | ||
| + | @param {Scene} scene | ||
| + | |||
| + | */ | ||
| + | this.render = function(glCanvas3D, scene) | ||
| + | { | ||
| + | if (glCanvas3D == null) | ||
| + | { | ||
| + | logWarning('Model::render() called with a NULL glCanvas3D'); | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | // render() is passed a reference to the scene, which gives us a chance | ||
| + | // to setup somethings we could not eariler since we don't have the scene | ||
| + | // in some methods. | ||
| + | if( this.firstTimeRender ) | ||
| + | { | ||
| + | if( this.getTextureName()) | ||
| + | { | ||
| + | scene.getTextureManager().addTexture(this.getTextureName()); | ||
| + | } | ||
| + | |||
| + | // if we are using the 1.1 context, we have to setup the buffers | ||
| + | if( scene.getRenderer() instanceof OpenGLES11) | ||
| + | { | ||
| + | this.createBuffers(glCanvas3D); | ||
| + | } | ||
| + | // make sure this code in this block isn't run again. | ||
| + | this.firstTimeRender = false; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | // if the user has changed the texture after we have already | ||
| + | // rendered once, we have to add the new texture to the | ||
| + | // TextureManager | ||
| + | |||
| + | // don't bother to check if the image is already in the | ||
| + | // textureManager, the textureManager does that check already. | ||
| + | if( this.getTextureName() ) | ||
| + | { | ||
| + | scene.getTextureManager().addTexture(this.getTextureName()); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | // only draw the primitive if it is visible | ||
| + | if( this.isVisible() ) | ||
| + | { | ||
| + | if (scene.getRenderer() instanceof OpenGLES20) | ||
| + | { | ||
| + | // world transform of the model | ||
| + | var wtMat = new Array( this.getLeft()[0], this.getLeft()[1], this.getLeft()[2], 0, | ||
| + | this.getUp()[0], this.getUp()[1], this.getUp()[2], 0, | ||
| + | this.getDirection()[0], this.getDirection()[1], this.getDirection()[2], 0, | ||
| + | this.getPosition()[0], this.getPosition()[1], this.getPosition()[2], 1); | ||
| + | |||
| + | // scaling is done seperately for now | ||
| + | var wtScale = new Array( this.getScale()[0],0,0,0, | ||
| + | 0,this.getScale()[1],0,0, | ||
| + | 0,0,this.getScale()[2],0, | ||
| + | 0,0,0,1); | ||
| + | |||
| + | // now add the saling components. | ||
| + | wtMat = multiplyMatrixByMatrix(wtScale,wtMat); | ||
| + | |||
| + | var modelMatrix = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "modelMatrix"); | ||
| + | glCanvas3D.uniformMatrix( modelMatrix, wtMat); | ||
| + | |||
| + | |||
| + | // ambient lighting | ||
| + | var ambientLight = scene.getAmbientLight(); | ||
| + | |||
| + | var lightMatrix = new Array( ambientLight[0],0,0,0, | ||
| + | 0,ambientLight[1],0,0, | ||
| + | 0,0,ambientLight[2],0, | ||
| + | 0,0,0,ambientLight[3]); | ||
| + | |||
| + | var ambientLightUniform = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "ambientLight"); | ||
| + | glCanvas3D.uniformMatrix(ambientLightUniform, lightMatrix); | ||
| + | |||
| + | // Texture | ||
| + | |||
| + | //var tex = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "myTex"); | ||
| + | //glCanvas3D.uniformi(tex, TextureManager.getID(this.getTextureName())); | ||
| + | |||
| + | var ta = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Texture"); | ||
| + | // Only allow textures if the model has a texture and it also has UVs. | ||
| + | if(this.getTextureName() && this.expandedUVs && ta != -1 ) | ||
| + | { | ||
| + | glCanvas3D.activeTexture(glCanvas3D.TEXTURE0); | ||
| + | glCanvas3D.enable(glCanvas3D.TEXTURE_2D); | ||
| + | glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D, scene.getTextureManager().getID(this.getTextureName())); | ||
| + | |||
| + | glCanvas3D.vertexAttribPointer(ta, 2, glCanvas3D.FLOAT, this.expandedUVs); | ||
| + | glCanvas3D.enableVertexAttribArray(ta); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | glCanvas3D.disableVertexAttribArray(ta); | ||
| + | glCanvas3D.disable(glCanvas3D.TEXTURE_2D); | ||
| + | glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D,-1); | ||
| + | } | ||
| + | |||
| + | |||
| + | // NORMALS | ||
| + | var na = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Normal"); | ||
| + | if(this.expandedNormals && na != -1) | ||
| + | { | ||
| + | var N = makeZeroMatrix(); | ||
| + | |||
| + | N = addMatrices(N, wtMat); | ||
| + | N = transposeMatrix(N); | ||
| + | N = inverseMatrix(N); | ||
| + | |||
| + | var normalMatrix = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "normalMatrix"); | ||
| + | glCanvas3D.uniformMatrix(normalMatrix, N); | ||
| + | |||
| + | glCanvas3D.vertexAttribPointer(na, 3, glCanvas3D.FLOAT, this.expandedNormals); | ||
| + | glCanvas3D.enableVertexAttribArray(na); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | glCanvas3D.disableVertexAttribArray(na); | ||
| + | } | ||
| + | |||
| + | // Vertices | ||
| + | var va = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Vertex"); | ||
| + | glCanvas3D.vertexAttribPointer(va, 3, glCanvas3D.FLOAT, this.expandedVertices); | ||
| + | glCanvas3D.enableVertexAttribArray(va); | ||
| + | |||
| + | // still trying to get this to work, | ||
| + | // for now, I had to resort to using drawArrays | ||
| + | //glCanvas3D.drawElements(glCanvas3D.TRIANGLES, ind.length, ind); | ||
| + | glCanvas3D.drawArrays(glCanvas3D.TRIANGLES, 0, (this.expandedVertices.length/3)); | ||
| + | } | ||
| + | else if (scene.getRenderer() instanceof OpenGLES11) | ||
| + | { | ||
| + | // TEXTURES | ||
| + | // only bind the texture if this object was actually assigned one | ||
| + | if(this.getTextureName() && this.buffers.tex) | ||
| + | { | ||
| + | glCanvas3D.enable(glCanvas3D.TEXTURE_2D); | ||
| + | glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D, scene.getTextureManager().getID(this.getTextureName())); | ||
| + | glCanvas3D.texCoordPointer(this.buffers.tex); | ||
| + | glCanvas3D.enableClientState(glCanvas3D.TEXTURE_COORD_ARRAY); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | glCanvas3D.disable(glCanvas3D.TEXTURE_2D); | ||
| + | } | ||
| + | |||
| + | // NORMALS | ||
| + | // only enable the normals if the object actually has them | ||
| + | if( this.buffers.normal) | ||
| + | { | ||
| + | glCanvas3D.normalPointer(this.buffers.normal); | ||
| + | glCanvas3D.enableClientState(glCanvas3D.NORMAL_ARRAY); | ||
| + | } | ||
| + | |||
| + | // VERTICES | ||
| + | glCanvas3D.vertexPointer(this.buffers.vertex); | ||
| + | glCanvas3D.enableClientState(glCanvas3D.VERTEX_ARRAY); | ||
| + | |||
| + | var scale = new Array( this.getScale()[0],0,0,0, | ||
| + | 0,this.getScale()[1],0,0, | ||
| + | 0,0,this.getScale()[2],0, | ||
| + | 0,0,0,1); | ||
| + | |||
| + | var m = new Array( this.getLeft()[0], this.getLeft()[1], this.getLeft()[2], 0, | ||
| + | this.getUp()[0], this.getUp()[1], this.getUp()[2], 0, | ||
| + | this.getDirection()[0], this.getDirection()[1], this.getDirection()[2], 0, | ||
| + | this.getPosition()[0], this.getPosition()[1], this.getPosition()[2], 1); | ||
| + | |||
| + | // Draw | ||
| + | glCanvas3D.pushMatrix(); | ||
| + | glCanvas3D.multMatrix(m); | ||
| + | glCanvas3D.multMatrix(scale); | ||
| + | glCanvas3D.drawArrays(glCanvas3D.TRIANGLES, 0, (this.expandedVertices.length/3)); | ||
| + | glCanvas3D.popMatrix(); | ||
| + | |||
| + | // Turn off Buffers | ||
| + | glCanvas3D.disableClientState(glCanvas3D.VERTEX_ARRAY); | ||
| + | glCanvas3D.disableClientState(glCanvas3D.NORMAL_ARRAY); | ||
| + | glCanvas3D.disableClientState(glCanvas3D.TEXTURE_COORD_ARRAY); | ||
| + | }// close draw with 1.1 | ||
| + | }// if visible | ||
| + | }// render | ||
| + | } | ||
| + | </pre> | ||
==== 立方體 Cube ==== | ==== 立方體 Cube ==== | ||
於 2008年10月9日 (四) 11:39 的修訂
內容大綱
簡介
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
有用的連結
- 我們的網站
- 我們的 blog
- Our SVN Repo: svn://cdot.senecac.on.ca/canvas3d
- 在 Vlad's hg repo 中的 canvas3d 源碼
- OpenGL 文件
- Nehe 的 OpenGL 教學
- .off (object file format) format info
類別繼承圖
數學運算
我的感覺是這份 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) - 應該是「畫」出來的意思
虛擬世界的物件
物體
基本物件 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
/*Copyright (c) 2008 Seneca College
Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
*/
Model.prototype = new Primitive;
/**
@class A Model is an object which has a geometric representation composed of
vertices, normals and uv coordinates. Models may also have textures
applied to them.
*/
function Model()
{
var loadedTexture = false;
this.expandedVertices = new Array();
this.expandedNormals = null;
this.expandedUVs = null;
this.buffers = {};
this.firstTimeRender = true;
this.stayInFrontOfCamera = false;
/**
<p>
This method should be called after a model object has been
created using:
</p>
<p>
<code>
var model = new Model();
</code>
</p>
<p>
null values may be provided for the normals and texcoord arguments,
however the vertices and faces arrays are required.
</p>
@param {Array} vertices Array of the unique vertices of the model.
@param {Array} [normals] Array of the unique normals of the
model, if null lighting will not work correctly for this model.
@param {Array} [texcoords] Array of texture coordinates of
the model. If null, textures cannot be applied to this model.
@param {Array of Arrays} faces Array of arrays which contains index
values which refer to values in the previous arrays.
*/
this.init = function(vertices, normals, texcoords, faces)
{
var texCoordsPresent = false;
var normalsPresent = false;
if(typeof vertices == "string")
{
this.initDAE(vertices);
}
else{
// At the least, vertices and faces must be present
if( vertices instanceof Array && faces instanceof Array)
{
if( texcoords instanceof Array)
{
texCoordsPresent = true;
this.expandedUVs = new Array();
}
if( normals instanceof Array)
{
normalsPresent = true;
this.expandedNormals = new Array();
}
for( var i =0; i < faces.length; i++)
{
// get vert index
var vertIndex = faces[i][0];
var cvert = vertices[vertIndex];
this.expandedVertices.push(cvert[0]);
this.expandedVertices.push(cvert[1]);
this.expandedVertices.push(cvert[2]);
// push on uvs
if( texcoords instanceof Array)
{
var texIndex = faces[i][1];
var ctex = texcoords[texIndex];
this.expandedUVs.push(ctex[0]);
this.expandedUVs.push(ctex[1]);
}
// push on normals
if( normals instanceof Array)
{
var offset = (texCoordsPresent == false) ? 1: 2;
var normIndex = faces[i][offset];
var cnorm = normals[normIndex];
this.expandedNormals.push(cnorm[0]);
this.expandedNormals.push(cnorm[1]);
this.expandedNormals.push(cnorm[2]);
}
}
}
}
this.initDone = true;
}
this.initDAE = function(path)
{
// add the model to the model pool.
// it is either added or is already present.
ModelManager.addModel(path);
// now, get the data
// this.expandedNormals = ModelManager.getNormals(path);
// this.expandedUVs = ModelManager.getUVs(path);
this.expandedVertices = ModelManager.getModel(path);
}
/**
@private
@param {context} glCanvas3D
*/
this.createBuffers = function(glCanvas3D)
{
this.buffers.vertex = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 3, glCanvas3D.FLOAT, this.expandedVertices);
if( this.expandedUVs )
{
this.buffers.tex = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 2, glCanvas3D.FLOAT, this.expandedUVs);
}
if( this.expandedNormals )
{
this.buffers.normal = glCanvas3D.createBuffer(glCanvas3D.STATIC_DRAW, 3, glCanvas3D.FLOAT, this.expandedNormals);
}
}
/**
Update animations for linear velocity and angular velocity.
@param {float} timeStep
@returns {boolean} true.
*/
this.update = function(timeStep, camera)
{
if (this.stayInFrontOfCamera)
{
//!! All this math is crap because it does't work when the camera rotates
var camUp = camera.getUp();
var camLeft = camera.getLeft();
var camDir = camera.getDir();
this.up = makeVector(-camUp[0], -camUp[1], -camUp[2]);
this.left = makeVector(-camLeft[0], -camLeft[1], -camLeft[2]);
this.dir = makeVector(-camDir[0], -camDir[1], -camDir[2]);
var camPos = camera.getPosition();
//!! These hard-coded values are a hack, need to set them based
//!! on where we want the floating thing to be in the canvas
this.pos = makeVector(camPos[0] + 10, camPos[1] - 15, camPos[2] + 10);
//logWarning('camera ' + camPos);
//logWarning('plane ' + this.pos);
return true;
}
// if the texture is loaded, it requires and update.
if (this.loadedTexture == true)
{
//this.loadedTexture = false;
return true;
}
if (isVectorZero(this.linVel) && isVectorZero(this.angVel))
{
// nothing will change
return false;
}
if (!isVectorZero(this.linVel))
{
// Add a velocity to the position
var velVec = new Array();
velVec = this.linVel;
multiplyVector(velVec, timeStep);
addVectors(this.pos, velVec, this.pos);
}
if (!isVectorZero(this.angVel))
{
// Apply some rotations to the orientation from the angular velocity
this.pitch(this.angVel[0] * timeStep);
this.yaw(this.angVel[1] * timeStep);
this.roll(this.angVel[2] * timeStep);
}
// force update.
return true;
}
/**
Render the model.
@param {context} glCanvas3D
@param {Scene} scene
*/
this.render = function(glCanvas3D, scene)
{
if (glCanvas3D == null)
{
logWarning('Model::render() called with a NULL glCanvas3D');
return false;
}
// render() is passed a reference to the scene, which gives us a chance
// to setup somethings we could not eariler since we don't have the scene
// in some methods.
if( this.firstTimeRender )
{
if( this.getTextureName())
{
scene.getTextureManager().addTexture(this.getTextureName());
}
// if we are using the 1.1 context, we have to setup the buffers
if( scene.getRenderer() instanceof OpenGLES11)
{
this.createBuffers(glCanvas3D);
}
// make sure this code in this block isn't run again.
this.firstTimeRender = false;
}
else
{
// if the user has changed the texture after we have already
// rendered once, we have to add the new texture to the
// TextureManager
// don't bother to check if the image is already in the
// textureManager, the textureManager does that check already.
if( this.getTextureName() )
{
scene.getTextureManager().addTexture(this.getTextureName());
}
}
// only draw the primitive if it is visible
if( this.isVisible() )
{
if (scene.getRenderer() instanceof OpenGLES20)
{
// world transform of the model
var wtMat = new Array( this.getLeft()[0], this.getLeft()[1], this.getLeft()[2], 0,
this.getUp()[0], this.getUp()[1], this.getUp()[2], 0,
this.getDirection()[0], this.getDirection()[1], this.getDirection()[2], 0,
this.getPosition()[0], this.getPosition()[1], this.getPosition()[2], 1);
// scaling is done seperately for now
var wtScale = new Array( this.getScale()[0],0,0,0,
0,this.getScale()[1],0,0,
0,0,this.getScale()[2],0,
0,0,0,1);
// now add the saling components.
wtMat = multiplyMatrixByMatrix(wtScale,wtMat);
var modelMatrix = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "modelMatrix");
glCanvas3D.uniformMatrix( modelMatrix, wtMat);
// ambient lighting
var ambientLight = scene.getAmbientLight();
var lightMatrix = new Array( ambientLight[0],0,0,0,
0,ambientLight[1],0,0,
0,0,ambientLight[2],0,
0,0,0,ambientLight[3]);
var ambientLightUniform = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "ambientLight");
glCanvas3D.uniformMatrix(ambientLightUniform, lightMatrix);
// Texture
//var tex = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "myTex");
//glCanvas3D.uniformi(tex, TextureManager.getID(this.getTextureName()));
var ta = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Texture");
// Only allow textures if the model has a texture and it also has UVs.
if(this.getTextureName() && this.expandedUVs && ta != -1 )
{
glCanvas3D.activeTexture(glCanvas3D.TEXTURE0);
glCanvas3D.enable(glCanvas3D.TEXTURE_2D);
glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D, scene.getTextureManager().getID(this.getTextureName()));
glCanvas3D.vertexAttribPointer(ta, 2, glCanvas3D.FLOAT, this.expandedUVs);
glCanvas3D.enableVertexAttribArray(ta);
}
else
{
glCanvas3D.disableVertexAttribArray(ta);
glCanvas3D.disable(glCanvas3D.TEXTURE_2D);
glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D,-1);
}
// NORMALS
var na = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Normal");
if(this.expandedNormals && na != -1)
{
var N = makeZeroMatrix();
N = addMatrices(N, wtMat);
N = transposeMatrix(N);
N = inverseMatrix(N);
var normalMatrix = glCanvas3D.getUniformLocation(scene.getRenderer().sp, "normalMatrix");
glCanvas3D.uniformMatrix(normalMatrix, N);
glCanvas3D.vertexAttribPointer(na, 3, glCanvas3D.FLOAT, this.expandedNormals);
glCanvas3D.enableVertexAttribArray(na);
}
else
{
glCanvas3D.disableVertexAttribArray(na);
}
// Vertices
var va = glCanvas3D.getAttribLocation(scene.getRenderer().sp, "Vertex");
glCanvas3D.vertexAttribPointer(va, 3, glCanvas3D.FLOAT, this.expandedVertices);
glCanvas3D.enableVertexAttribArray(va);
// still trying to get this to work,
// for now, I had to resort to using drawArrays
//glCanvas3D.drawElements(glCanvas3D.TRIANGLES, ind.length, ind);
glCanvas3D.drawArrays(glCanvas3D.TRIANGLES, 0, (this.expandedVertices.length/3));
}
else if (scene.getRenderer() instanceof OpenGLES11)
{
// TEXTURES
// only bind the texture if this object was actually assigned one
if(this.getTextureName() && this.buffers.tex)
{
glCanvas3D.enable(glCanvas3D.TEXTURE_2D);
glCanvas3D.bindTexture(glCanvas3D.TEXTURE_2D, scene.getTextureManager().getID(this.getTextureName()));
glCanvas3D.texCoordPointer(this.buffers.tex);
glCanvas3D.enableClientState(glCanvas3D.TEXTURE_COORD_ARRAY);
}
else
{
glCanvas3D.disable(glCanvas3D.TEXTURE_2D);
}
// NORMALS
// only enable the normals if the object actually has them
if( this.buffers.normal)
{
glCanvas3D.normalPointer(this.buffers.normal);
glCanvas3D.enableClientState(glCanvas3D.NORMAL_ARRAY);
}
// VERTICES
glCanvas3D.vertexPointer(this.buffers.vertex);
glCanvas3D.enableClientState(glCanvas3D.VERTEX_ARRAY);
var scale = new Array( this.getScale()[0],0,0,0,
0,this.getScale()[1],0,0,
0,0,this.getScale()[2],0,
0,0,0,1);
var m = new Array( this.getLeft()[0], this.getLeft()[1], this.getLeft()[2], 0,
this.getUp()[0], this.getUp()[1], this.getUp()[2], 0,
this.getDirection()[0], this.getDirection()[1], this.getDirection()[2], 0,
this.getPosition()[0], this.getPosition()[1], this.getPosition()[2], 1);
// Draw
glCanvas3D.pushMatrix();
glCanvas3D.multMatrix(m);
glCanvas3D.multMatrix(scale);
glCanvas3D.drawArrays(glCanvas3D.TRIANGLES, 0, (this.expandedVertices.length/3));
glCanvas3D.popMatrix();
// Turn off Buffers
glCanvas3D.disableClientState(glCanvas3D.VERTEX_ARRAY);
glCanvas3D.disableClientState(glCanvas3D.NORMAL_ARRAY);
glCanvas3D.disableClientState(glCanvas3D.TEXTURE_COORD_ARRAY);
}// close draw with 1.1
}// if visible
}// render
}
立方體 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] ];
屬性與方法
// when this object is created, make a cube model inside it. this.m = new Model(); this.m.init(cube_transition_Vertices, cube_transition_Normals, cube_transition_UVs, cube_transition_Faces);
this.getPosition = function() { return this.m.getPosition();} this.getUp = function() { return this.m.getUp();} this.getDirection = function() { return this.m.getDirection();} this.getLeft = function() { return this.m.getLeft();} this.getLinearVel = function() { return this.m.getLinearVel();} this.getAngularVel = function() { return this.m.getAngularVel();} this.isVisible = function() { return this.m.isVisible();} this.getScale = function() {return this.m.getScale(); }
this.setTexture = function(imageFilename){ this.m.setTexture(imageFilename);} this.setTextureFromCanvas2D = function(sourceCanvas){this.m.setTextureFromCanvas2D(sourceCanvas);} this.getTextureName = function() {this.m.getTextureName();} this.unsetTexture = function(){this.m.unsetTexture();} this.setVisible = function(show){this.m.setVisible(show);}
// scale the Cube, if only one parameter is specified, consider // it to be a vector|array, otherwise consider it to be 3 scalars. this.scale = function(scaleVec, scaleY, scaleZ) { if( scaleY && scaleZ) { var triplet = new Array(scaleVec, scaleY, scaleZ); this.m.scale(triplet); } else { this.m.scale(scaleVec); } }
this.setPosition = function(vecPos){ this.m.setPosition(vecPos);} this.translate = function(translation){this.m.translate(translation);} this.setForward = function(newVec){this.m.setForward(newVec)}; this.setUpVector = function(newVec){this.m.setUpVector(newVec);} this.setLinearVel = function(newVec){this.m.setLinearVel(newVec);} this.setAngularVel = function(newVec){this.m.setAngularVel(newVec);} this.rotateOnAxis = function(axisVec, angle){this.m.rotateOnAxis(axisVec, angle);} this.yaw = function(angle){this.m.yaw(angle);} this.roll = function(angle){this.m.roll(angle);} this.pitch = function(angle){this.m.pitch(angle);} this.update = function(timeStep){this.m.update(timeStep);} this.render = function(glCanvas3D, scene){this.m.render(glCanvas3D, scene);} }
