├── .babelrc ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── development ├── app.js ├── gl-matrix.js ├── index.html └── models │ ├── die.blend │ ├── die.mtl │ ├── die.obj │ ├── face1.bmp │ ├── face2.bmp │ ├── face3.bmp │ ├── face4.bmp │ ├── face5.bmp │ ├── face6.bmp │ ├── suzanne.mtl │ └── suzanne.obj ├── dist ├── index.d.ts ├── layout.d.ts ├── material.d.ts ├── mesh.d.ts ├── utils.d.ts ├── webgl-obj-loader.js └── webgl-obj-loader.min.js ├── package.json ├── src ├── index.ts ├── layout.ts ├── material.ts ├── mesh.ts └── utils.ts ├── test ├── layout.ts ├── material.ts ├── mesh.ts ├── mocha.opts ├── tsconfig.json └── tshook.js ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-3" 5 | ], 6 | "retainLines": true 7 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "google", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { "es6": true }, 8 | }; -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist/* 3 | package-lock.json 4 | .nyc_output/* 5 | coverage/* 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | development/* 2 | test/* 3 | 4 | .travis.yml 5 | webpack.* 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "12" -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Aaron Boman and aaronboman.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webgl-obj-loader 2 | [](https://travis-ci.org/frenchtoast747/webgl-obj-loader) 3 | 4 | A simple script to help bring OBJ models to your WebGL world. I originally 5 | wrote this script for my CS Graphics class so that we didn't have to only have 6 | cubes and spheres for models in order to learn WebGL. At the time, the only 7 | sort of model loader for WebGL was [Mr. Doob's ThreeJS](http://threejs.org/). And in order to use the 8 | loaders you had to use the entire framework (or do some very serious hacking 9 | and duct-taping in order get the model information). My main focus in creating 10 | this loader was to easily allow importing models without having to have special 11 | knowledge of a 3D graphics program (like Blender) while keeping it low-level 12 | enough so that the focus was on learning WebGL rather than learning some 13 | framework. 14 | 15 | ## Mesh(objStr) 16 | 17 | The main Mesh class. The constructor will parse through the OBJ file data 18 | and collect the vertex, vertex normal, texture, and face information. This 19 | information can then be used later on when creating your VBOs. Look at the 20 | `initMeshBuffers` source for an example of how to use the newly created Mesh 21 | 22 | ### Attributes: 23 | * **vertices:** an array containing the vertex values that correspond to each unique face index. The array is flat in that each vertex's component is an element of the array. For example: with `verts = [1, -1, 1, ...]`, `verts[0] is x`, `verts[1] is y`, and `verts[2] is z`. Continuing on, `verts[3]` would be the beginning of the next vertex: its x component. This is in preparation for using `gl.ELEMENT_ARRAY_BUFFER` for the `gl.drawElements` call. 24 | * Note that the `vertices` attribute is the [Geometric Vertex](https://en.wikipedia.org/wiki/Wavefront_.obj_file#Geometric_vertex) and denotes the position in 3D space. 25 | * **vertexNormals:** an array containing the vertex normals that correspond to each unique face index. It is flat, just like `vertices`. 26 | * **textures:** an array containing the `s` and `t` (or `u` and `v`) coordinates for this mesh's texture. It is flat just like `vertices` except it goes by groups of 2 instead of 3. 27 | * **indices:** an array containing the indicies to be used in conjunction with the above three arrays in order to draw the triangles that make up faces. See below for more information on element indices. 28 | 29 | #### Element Index 30 | 31 | The `indices` attribute is a list of numbers that represent the indices of the above vertex groups. For example, the Nth index, `mesh.indices[N]`, may contain the value `38`. This points to the 39th (zero indexed) element. For Mesh classes, this points to a unique group vertex, normal, and texture values. However, the `vertices`, `normals`, and `textures` attributes are flattened lists of each attributes' components, e.g. the `vertices` list is a repeating pattern of [X, Y, Z, X, Y, Z, ...], so you cannot directly use the element index in order to look up the corresponding vertex position. That is to say `mesh.vertices[38]` does _not_ point to the 39th vertex's X component. The following diagram illustrates how the element index under the hood: 32 | 33 |  34 | 35 | After describing the attribute data to WebGL via [vertexAttribPointer()](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer), what was once separate array elements in JS is now just one block of data on the graphics card. That block of data in its entirety is considered a single element. 36 | 37 | To use the element index in order to index one of the attribute arrays in JS, you will have to mimic this "chunking" of data by taking into account the number of components in an attribute (e.g. a vertex has 3 components; x, y, and z). Have a look at the following code snippet to see how to correctly use the element index 38 | in order to access an attribute for that index: 39 | 40 | ```javascript 41 | // there are 3 components for a geometric vertex: X, Y, and Z 42 | const NUM_COMPONENTS_FOR_VERTS = 3; 43 | elementIdx = mesh.indices[SOME_IDX]; // e.g. 38 44 | // in order to get the X component of the vertex component of element "38" 45 | elementVertX = mesh.vertices[(elementIdx * NUM_COMPONENTS_FOR_VERTS) + 0] 46 | // in order to get the Y component of the vertex component of element "38" 47 | elementVertY = mesh.vertices[(elementIdx * NUM_COMPONENTS_FOR_VERTS) + 1] 48 | // in order to get the Z component of the vertex component of element "38" 49 | elementVertZ = mesh.vertices[(elementIdx * NUM_COMPONENTS_FOR_VERTS) + 2] 50 | ``` 51 | 52 | ### Params: 53 | 54 | * **objStr** a string representation of an OBJ file with newlines preserved. 55 | 56 | A simple example: 57 | 58 | In your `index.html` file: 59 | ```html 60 | 61 |
62 | 96 | 97 | 98 | ``` 99 | And in your `app.js`: 100 | 101 | ```javascript 102 | var gl = canvas.getContext('webgl'); 103 | var objStr = document.getElementById('my_cube.obj').innerHTML; 104 | var mesh = new OBJ.Mesh(objStr); 105 | 106 | // use the included helper function to initialize the VBOs 107 | // if you don't want to use this function, have a look at its 108 | // source to see how to use the Mesh instance. 109 | OBJ.initMeshBuffers(gl, mesh); 110 | // have a look at the initMeshBuffers docs for an exmample of how to 111 | // render the model at this point 112 | 113 | ``` 114 | 115 | ## Some helper functions 116 | 117 | ### downloadMeshes(nameAndURLs, completionCallback, meshes) 118 | 119 | Takes in a JS Object of `mesh_name`, `'/url/to/OBJ/file'` pairs and a callback 120 | function. Each OBJ file will be ajaxed in and automatically converted to 121 | an OBJ.Mesh. When all files have successfully downloaded the callback 122 | function provided will be called and passed in an object containing 123 | the newly created meshes. 124 | 125 | **Note:** In order to use this function as a way to download meshes, a 126 | webserver of some sort must be used. 127 | 128 | #### Params: 129 | 130 | * **nameAndURLs:** an object where the key is the name of the mesh and the value is the url to that mesh's OBJ file 131 | 132 | * **completionCallback:** should contain a function that will take one parameter: an object array where the keys will be the unique object name and the value will be a Mesh object 133 | 134 | * **meshes:** In case other meshes are loaded separately or if a previously declared variable is desired to be used, pass in a (possibly empty) json object of the pattern: `{ 'mesh_name': OBJ.Mesh }` 135 | 136 | A simple example: 137 | 138 | 139 | ```javascript 140 | var app = {}; 141 | app.meshes = {}; 142 | 143 | var gl = document.getElementById('mycanvas').getContext('webgl'); 144 | 145 | function webGLStart(meshes){ 146 | app.meshes = meshes; 147 | // initialize the VBOs 148 | OBJ.initMeshBuffers(gl, app.meshes.suzanne); 149 | OBJ.initMeshBuffers(gl, app.meshes.sphere); 150 | ... other cool stuff ... 151 | // refer to the initMeshBuffers docs for an example of 152 | // how to render the mesh to the screen after calling 153 | // initMeshBuffers 154 | } 155 | 156 | window.onload = function(){ 157 | OBJ.downloadMeshes({ 158 | 'suzanne': 'models/suzanne.obj', // located in the models folder on the server 159 | 'sphere': 'models/sphere.obj' 160 | }, webGLStart); 161 | } 162 | ``` 163 | 164 | ### initMeshBuffers(gl, mesh) 165 | 166 | Takes in the WebGL context and a Mesh, then creates and appends the buffers 167 | to the mesh object as attributes. 168 | 169 | #### Params: 170 | 171 | * **gl** *WebGLRenderingContext* the `canvas.getContext('webgl')` context instance 172 | 173 | * **mesh** *Mesh* a single `OBJ.Mesh` instance 174 | 175 | The newly created mesh attributes are: 176 | 177 | Attrbute | Description 178 | :--- | --- 179 | **normalBuffer** |contains the model's Vertex Normals 180 | normalBuffer.itemSize |set to 3 items 181 | normalBuffer.numItems |the total number of vertex normals 182 | **textureBuffer** |contains the model's Texture Coordinates 183 | textureBuffer.itemSize |set to 2 items (or 3 if W texture coord is enabled) 184 | textureBuffer.numItems |the number of texture coordinates 185 | **vertexBuffer** |contains the model's Vertex Position Coordinates (does not include w) 186 | vertexBuffer.itemSize |set to 3 items 187 | vertexBuffer.numItems |the total number of vertices 188 | **indexBuffer** |contains the indices of the faces 189 | indexBuffer.itemSize |is set to 1 190 | indexBuffer.numItems |the total number of indices 191 | 192 | A simple example (a lot of steps are missing, so don't copy and paste): 193 | ```javascript 194 | var gl = canvas.getContext('webgl'), 195 | var mesh = new OBJ.Mesh(obj_file_data); 196 | // compile the shaders and create a shader program 197 | var shaderProgram = gl.createProgram(); 198 | // compilation stuff here 199 | ... 200 | // make sure you have vertex, vertex normal, and texture coordinate 201 | // attributes located in your shaders and attach them to the shader program 202 | shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); 203 | gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); 204 | 205 | shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal"); 206 | gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute); 207 | 208 | shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); 209 | gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); 210 | 211 | // create and initialize the vertex, vertex normal, and texture coordinate buffers 212 | // and save on to the mesh object 213 | OBJ.initMeshBuffers(gl, mesh); 214 | 215 | // now to render the mesh 216 | gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexBuffer); 217 | gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, mesh.vertexBuffer.itemSize, gl.FLOAT, false, 0, 0); 218 | 219 | // it's possible that the mesh doesn't contain 220 | // any texture coordinates (e.g. suzanne.obj in the development branch). 221 | // in this case, the texture vertexAttribArray will need to be disabled 222 | // before the call to drawElements 223 | if(!mesh.textures.length){ 224 | gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute); 225 | } 226 | else{ 227 | // if the texture vertexAttribArray has been previously 228 | // disabled, then it needs to be re-enabled 229 | gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); 230 | gl.bindBuffer(gl.ARRAY_BUFFER, mesh.textureBuffer); 231 | gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, mesh.textureBuffer.itemSize, gl.FLOAT, false, 0, 0); 232 | } 233 | 234 | gl.bindBuffer(gl.ARRAY_BUFFER, mesh.normalBuffer); 235 | gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, mesh.normalBuffer.itemSize, gl.FLOAT, false, 0, 0); 236 | 237 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.mesh.indexBuffer); 238 | gl.drawElements(gl.TRIANGLES, model.mesh.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0); 239 | ``` 240 | 241 | ### deleteMeshBuffers(gl, mesh) 242 | Deletes the mesh's buffers, which you would do when deleting an object from a 243 | scene so that you don't leak video memory. Excessive buffer creation and 244 | deletion leads to video memory fragmentation. Beware. 245 | 246 | ## Node.js 247 | `npm install webgl-obj-loader` 248 | 249 | ```javascript 250 | var fs = require('fs'); 251 | var OBJ = require('webgl-obj-loader'); 252 | 253 | var meshPath = './development/models/sphere.obj'; 254 | var opt = { encoding: 'utf8' }; 255 | 256 | fs.readFile(meshPath, opt, function (err, data){ 257 | if (err) return console.error(err); 258 | var mesh = new OBJ.Mesh(data); 259 | }); 260 | ``` 261 | 262 | ## Webpack Support 263 | Thanks to [mentos1386](https://github.com/mentos1386) for the [webpack-obj-loader](https://github.com/mentos1386/webpack-obj-loader)! 264 | 265 | ## Demo 266 | http://frenchtoast747.github.com/webgl-obj-loader/ 267 | This demo is the same thing inside of the gh-pages branch. Do a `git checkout gh-pages` inside of the webgl-obj-loader directory to see how the OBJ loader is used in a project. 268 | 269 | ## ChangeLog 270 | **2.0.3** 271 | * Add simple support for N-Gons (thanks [qtip](https://github.com/qtip)!) 272 | * This uses a very elementary algorithm to triangulate N-gons, but should still produce a full mesh. 273 | * Any help to create a better triangulation algorithm would be greatly appreciated! Please create a [pull request](https://github.com/frenchtoast747/webgl-obj-loader/pulls). 274 | 275 | **2.0.0** 276 | * Updated to TypeScript 277 | * Breaking change: the Mesh option `indicesPerMaterial` has been removed in favor of always providing the indices per material. 278 | * Instead of `mesh.indices` holding an array of arrays of numbers, `mesh.indicesPerMaterial` will now hold the indices where the top 279 | level array index is the index of the material and the inner arrays are the indices for that material. 280 | * Breaking change: the Layout class has changed from directly applying attributes to the Layout instance to creating an attributeMap 281 | 282 | **1.1.0** 283 | * Add Support for separating mesh indices by materials. 284 | * Add calculation for tangents and bitangents 285 | * Add runtime OBJ library version. 286 | 287 | **1.0.1** 288 | * Add support for 3D texture coordinates. By default the third texture 289 | coordinate, w, is truncated. Support can be enabled by passing 290 | `enableWTextureCoord: true` in the options parameter of the Mesh 291 | class. 292 | 293 | **1.0.0** 294 | * Modularized all of the source files into ES6 modules. 295 | * The Mesh, MaterialLibrary, and Material classes are now 296 | actual ES6 classes. 297 | * Added tests for each of the classes 298 | * Found a bug in the Mesh class. Vertex normals would not appear 299 | if the face declaration used the shorthand variant; e.g. `f 1/1` 300 | * Provided Initial MTL file parsing support. 301 | * Still requires Documentation. For now, have a look at the tests in the 302 | test directory for examples of use. 303 | * Use the new downloadModels() function in order to download the OBJ meshes 304 | complete with their MTL files attached. If the MTL files reference images, 305 | by default, those images will be downloaded and attached. 306 | * The downloading functions now use the new `fetch()` API which utilizes 307 | promises. 308 | 309 | **0.1.1** 310 | * Support for NodeJS. 311 | 312 | **0.1.0** 313 | * Dropped jQuery dependency: `downloadMeshes` no longer requires jQuery to ajax in the OBJ files. 314 | * changed namespace to something a little shorter: `OBJ` 315 | * Updated documentation 316 | 317 | **0.0.3** 318 | * Initial support for Quad models 319 | 320 | **0.0.2** 321 | * Texture Coordinates are now loaded into mesh.textures 322 | 323 | **0.0.1** 324 | * Vertex Normals are now loaded into mesh.vertexNormals 325 | 326 | [](https://bitdeli.com/free "Bitdeli Badge") 327 | -------------------------------------------------------------------------------- /development/app.js: -------------------------------------------------------------------------------- 1 | // WebGL context 2 | var gl = {}; 3 | // the canvas element 4 | var canvas = null; 5 | // main shader program 6 | var shaderProgram = null; 7 | // main app object 8 | var app = {}; 9 | app.meshes = {}; 10 | app.models = {}; 11 | app.mvMatrix = mat4.create(); 12 | app.mvMatrixStack = []; 13 | app.pMatrix = mat4.create(); 14 | 15 | window.requestAnimFrame = (function() { 16 | return ( 17 | window.requestAnimationFrame || 18 | window.webkitRequestAnimationFrame || 19 | window.mozRequestAnimationFrame || 20 | window.oRequestAnimationFrame || 21 | window.msRequestAnimationFrame || 22 | function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { 23 | return window.setTimeout(callback, 1000 / 60); 24 | } 25 | ); 26 | })(); 27 | 28 | function initWebGL(canvas) { 29 | try { 30 | // Try to grab the standard context. If it fails, fallback to experimental. 31 | gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); 32 | } catch (e) {} 33 | if (!gl) { 34 | alert("Unable to initialize WebGL. Your browser may not support it."); 35 | gl = null; 36 | } 37 | gl.viewportWidth = canvas.width; 38 | gl.viewportHeight = canvas.height; 39 | gl.viewport(0, 0, canvas.width, canvas.height); 40 | return gl; 41 | } 42 | 43 | function getShader(gl, id) { 44 | var shaderScript = document.getElementById(id); 45 | if (!shaderScript) { 46 | return null; 47 | } 48 | 49 | var str = ""; 50 | var k = shaderScript.firstChild; 51 | while (k) { 52 | if (k.nodeType == 3) { 53 | str += k.textContent; 54 | } 55 | k = k.nextSibling; 56 | } 57 | 58 | var shader; 59 | if (shaderScript.type == "x-shader/x-fragment") { 60 | shader = gl.createShader(gl.FRAGMENT_SHADER); 61 | } else if (shaderScript.type == "x-shader/x-vertex") { 62 | shader = gl.createShader(gl.VERTEX_SHADER); 63 | } else { 64 | return null; 65 | } 66 | 67 | gl.shaderSource(shader, str); 68 | gl.compileShader(shader); 69 | 70 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 71 | alert(gl.getShaderInfoLog(shader)); 72 | return null; 73 | } 74 | 75 | return shader; 76 | } 77 | 78 | function initShaders() { 79 | var fragmentShader = getShader(gl, "shader-fs"); 80 | var vertexShader = getShader(gl, "shader-vs"); 81 | 82 | shaderProgram = gl.createProgram(); 83 | gl.attachShader(shaderProgram, vertexShader); 84 | gl.attachShader(shaderProgram, fragmentShader); 85 | gl.linkProgram(shaderProgram); 86 | 87 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 88 | alert("Could not initialise shaders"); 89 | } 90 | gl.useProgram(shaderProgram); 91 | 92 | const attrs = { 93 | aVertexPosition: OBJ.Layout.POSITION.key, 94 | aVertexNormal: OBJ.Layout.NORMAL.key, 95 | aTextureCoord: OBJ.Layout.UV.key, 96 | aDiffuse: OBJ.Layout.DIFFUSE.key, 97 | aSpecular: OBJ.Layout.SPECULAR.key, 98 | aSpecularExponent: OBJ.Layout.SPECULAR_EXPONENT.key 99 | }; 100 | 101 | shaderProgram.attrIndices = {}; 102 | for (const attrName in attrs) { 103 | if (!attrs.hasOwnProperty(attrName)) { 104 | continue; 105 | } 106 | shaderProgram.attrIndices[attrName] = gl.getAttribLocation(shaderProgram, attrName); 107 | if (shaderProgram.attrIndices[attrName] != -1) { 108 | gl.enableVertexAttribArray(shaderProgram.attrIndices[attrName]); 109 | } else { 110 | console.warn( 111 | 'Shader attribute "' + 112 | attrName + 113 | '" not found in shader. Is it undeclared or unused in the shader code?' 114 | ); 115 | } 116 | } 117 | 118 | shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); 119 | shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); 120 | shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix"); 121 | 122 | shaderProgram.applyAttributePointers = function(model) { 123 | const layout = model.mesh.vertexBuffer.layout; 124 | for (const attrName in attrs) { 125 | if (!attrs.hasOwnProperty(attrName) || shaderProgram.attrIndices[attrName] == -1) { 126 | continue; 127 | } 128 | const layoutKey = attrs[attrName]; 129 | if (shaderProgram.attrIndices[attrName] != -1) { 130 | const attr = layout.attributeMap[layoutKey]; 131 | gl.vertexAttribPointer( 132 | shaderProgram.attrIndices[attrName], 133 | attr.size, 134 | gl[attr.type], 135 | attr.normalized, 136 | attr.stride, 137 | attr.offset 138 | ); 139 | } 140 | } 141 | }; 142 | } 143 | 144 | function drawObject(model) { 145 | /* 146 | Takes in a model that points to a mesh and draws the object on the scene. 147 | Assumes that the setMatrixUniforms function exists 148 | as well as the shaderProgram has a uniform attribute called "samplerUniform" 149 | */ 150 | // gl.useProgram(shaderProgram); 151 | 152 | gl.bindBuffer(gl.ARRAY_BUFFER, model.mesh.vertexBuffer); 153 | shaderProgram.applyAttributePointers(model); 154 | 155 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.mesh.indexBuffer); 156 | setMatrixUniforms(); 157 | gl.drawElements(gl.TRIANGLES, model.mesh.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0); 158 | } 159 | 160 | function mvPushMatrix() { 161 | var copy = mat4.create(); 162 | mat4.copy(copy, app.mvMatrix); 163 | app.mvMatrixStack.push(copy); 164 | } 165 | 166 | function mvPopMatrix() { 167 | if (app.mvMatrixStack.length === 0) { 168 | throw "Invalid popMatrix!"; 169 | } 170 | app.mvMatrix = app.mvMatrixStack.pop(); 171 | } 172 | 173 | function setMatrixUniforms() { 174 | gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, app.pMatrix); 175 | gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, app.mvMatrix); 176 | 177 | var normalMatrix = mat3.create(); 178 | mat3.normalFromMat4(normalMatrix, app.mvMatrix); 179 | gl.uniformMatrix3fv(shaderProgram.nMatrixUniform, false, normalMatrix); 180 | } 181 | 182 | function initBuffers() { 183 | var layout = new OBJ.Layout( 184 | OBJ.Layout.POSITION, 185 | OBJ.Layout.NORMAL, 186 | OBJ.Layout.DIFFUSE, 187 | OBJ.Layout.UV, 188 | OBJ.Layout.SPECULAR, 189 | OBJ.Layout.SPECULAR_EXPONENT 190 | ); 191 | 192 | // initialize the mesh's buffers 193 | for (var mesh in app.meshes) { 194 | // Create the vertex buffer for this mesh 195 | var vertexBuffer = gl.createBuffer(); 196 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 197 | var vertexData = app.meshes[mesh].makeBufferData(layout); 198 | gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); 199 | vertexBuffer.numItems = vertexData.numItems; 200 | vertexBuffer.layout = layout; 201 | app.meshes[mesh].vertexBuffer = vertexBuffer; 202 | 203 | // Create the index buffer for this mesh 204 | var indexBuffer = gl.createBuffer(); 205 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 206 | var indexData = app.meshes[mesh].makeIndexBufferDataForMaterials(...Object.values(app.meshes[mesh].materialIndices)); 207 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW); 208 | indexBuffer.numItems = indexData.numItems; 209 | app.meshes[mesh].indexBuffer = indexBuffer; 210 | 211 | // this loops through the mesh names and creates new 212 | // model objects and setting their mesh to the current mesh 213 | app.models[mesh] = {}; 214 | app.models[mesh].mesh = app.meshes[mesh]; 215 | } 216 | } 217 | 218 | function animate() { 219 | app.timeNow = new Date().getTime(); 220 | app.elapsed = app.timeNow - app.lastTime; 221 | if (!app.time) { 222 | app.time = 0.0; 223 | } 224 | app.time += app.elapsed / 1000.0; 225 | if (app.lastTime !== 0) { 226 | // do animations 227 | } 228 | app.lastTime = app.timeNow; 229 | } 230 | 231 | function drawScene() { 232 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 233 | mat4.perspective(app.pMatrix, 45 * Math.PI / 180.0, gl.viewportWidth / gl.viewportHeight, 0.01, 1000.0); 234 | mat4.identity(app.mvMatrix); 235 | // move the camera 236 | mat4.translate(app.mvMatrix, app.mvMatrix, [0, 0, -5]); 237 | mat4.rotate(app.mvMatrix, app.mvMatrix, app.time * 0.25 * Math.PI, [0, 1, 0]); 238 | // set up the scene 239 | mvPushMatrix(); 240 | drawObject(app.models.suzanne); 241 | mvPopMatrix(); 242 | } 243 | 244 | function tick() { 245 | requestAnimFrame(tick); 246 | drawScene(); 247 | animate(); 248 | } 249 | 250 | function webGLStart(meshes) { 251 | app.meshes = meshes; 252 | canvas = document.getElementById("mycanvas"); 253 | gl = initWebGL(canvas); 254 | initShaders(); 255 | initBuffers(); 256 | gl.clearColor(0.5, 0.5, 0.5, 1.0); 257 | gl.enable(gl.DEPTH_TEST); 258 | 259 | tick(); 260 | // drawScene(); 261 | } 262 | 263 | window.onload = function() { 264 | // OBJ.downloadMeshes({ 265 | // 'suzanne': '/development/models/suzanne.obj' 266 | // }, webGLStart); 267 | let p = OBJ.downloadModels([ 268 | { 269 | name: "die", 270 | obj: "/development/models/die.obj", 271 | mtl: "/development/models/die.mtl" 272 | }, 273 | { 274 | obj: "/development/models/suzanne.obj", 275 | mtl: true 276 | } // , 277 | // { 278 | // obj: '/development/models/suzanne.obj' 279 | // } 280 | ]); 281 | 282 | p.then(models => { 283 | for ([name, mesh] of Object.entries(models)) { 284 | console.log("Name:", name); 285 | console.log("Mesh:", mesh); 286 | } 287 | webGLStart(models); 288 | }); 289 | }; 290 | -------------------------------------------------------------------------------- /development/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |70 | * Layout can sort of be thought of as a C-style struct declaration. 71 | * {@link Mesh}'s TBD(...) method will use the {@link Layout} instance to 72 | * pack an array in the given attribute order. 73 | *
74 | * Layout also is very helpful when calling a WebGL context's
75 | * vertexAttribPointer
method. If you've created a buffer using
76 | * a Layout instance, then the same Layout instance can be used to determine
77 | * the size, type, normalized, stride, and offset parameters for
78 | * vertexAttribPointer
.
79 | *
80 | * For example: 81 | *
82 | *
83 | * const index = glctx.getAttribLocation(shaderProgram, "pos");
84 | * glctx.vertexAttribPointer(
85 | * layout.position.size,
86 | * glctx[layout.position.type],
87 | * layout.position.normalized,
88 | * layout.position.stride,
89 | * layout.position.offset);
90 | *
91 | * @see {@link Mesh}
92 | */
93 | export declare class Layout {
94 | /**
95 | * Attribute layout to pack a vertex's x, y, & z as floats
96 | *
97 | * @see {@link Layout}
98 | */
99 | static POSITION: Attribute;
100 | /**
101 | * Attribute layout to pack a vertex's normal's x, y, & z as floats
102 | *
103 | * @see {@link Layout}
104 | */
105 | static NORMAL: Attribute;
106 | /**
107 | * Attribute layout to pack a vertex's normal's x, y, & z as floats.
108 | * 109 | * This value will be computed on-the-fly based on the texture coordinates. 110 | * If no texture coordinates are available, the generated value will default to 111 | * 0, 0, 0. 112 | * 113 | * @see {@link Layout} 114 | */ 115 | static TANGENT: Attribute; 116 | /** 117 | * Attribute layout to pack a vertex's normal's bitangent x, y, & z as floats. 118 | *
119 | * This value will be computed on-the-fly based on the texture coordinates. 120 | * If no texture coordinates are available, the generated value will default to 121 | * 0, 0, 0. 122 | * @see {@link Layout} 123 | */ 124 | static BITANGENT: Attribute; 125 | /** 126 | * Attribute layout to pack a vertex's texture coordinates' u & v as floats 127 | * 128 | * @see {@link Layout} 129 | */ 130 | static UV: Attribute; 131 | /** 132 | * Attribute layout to pack an unsigned short to be interpreted as a the index 133 | * into a {@link Mesh}'s materials list. 134 | *
135 | * The intention of this value is to send all of the {@link Mesh}'s materials 136 | * into multiple shader uniforms and then reference the current one by this 137 | * vertex attribute. 138 | *
139 | * example glsl code: 140 | * 141 | *
142 | * // this is bound using MATERIAL_INDEX
143 | * attribute int materialIndex;
144 | *
145 | * struct Material {
146 | * vec3 diffuse;
147 | * vec3 specular;
148 | * vec3 specularExponent;
149 | * };
150 | *
151 | * uniform Material materials[MAX_MATERIALS];
152 | *
153 | * // ...
154 | *
155 | * vec3 diffuse = materials[materialIndex];
156 | *
157 | *
158 | * TODO: More description & test to make sure subscripting by attributes even
159 | * works for webgl
160 | *
161 | * @see {@link Layout}
162 | */
163 | static MATERIAL_INDEX: Attribute;
164 | static MATERIAL_ENABLED: Attribute;
165 | static AMBIENT: Attribute;
166 | static DIFFUSE: Attribute;
167 | static SPECULAR: Attribute;
168 | static SPECULAR_EXPONENT: Attribute;
169 | static EMISSIVE: Attribute;
170 | static TRANSMISSION_FILTER: Attribute;
171 | static DISSOLVE: Attribute;
172 | static ILLUMINATION: Attribute;
173 | static REFRACTION_INDEX: Attribute;
174 | static SHARPNESS: Attribute;
175 | static MAP_DIFFUSE: Attribute;
176 | static MAP_AMBIENT: Attribute;
177 | static MAP_SPECULAR: Attribute;
178 | static MAP_SPECULAR_EXPONENT: Attribute;
179 | static MAP_DISSOLVE: Attribute;
180 | static ANTI_ALIASING: Attribute;
181 | static MAP_BUMP: Attribute;
182 | static MAP_DISPLACEMENT: Attribute;
183 | static MAP_DECAL: Attribute;
184 | static MAP_EMISSIVE: Attribute;
185 | stride: number;
186 | attributes: Attribute[];
187 | attributeMap: {
188 | [idx: string]: AttributeInfo;
189 | };
190 | /**
191 | * Create a Layout object. This constructor will throw if any duplicate
192 | * attributes are given.
193 | * @param {Array} ...attributes - An ordered list of attributes that
194 | * describe the desired memory layout for each vertex attribute.
195 | *
196 | *
197 | * @see {@link Mesh}
198 | */
199 | constructor(...attributes: Attribute[]);
200 | }
201 |
--------------------------------------------------------------------------------
/dist/material.d.ts:
--------------------------------------------------------------------------------
1 | export declare type Vec3 = [number, number, number];
2 | export interface UVW {
3 | u: number;
4 | v: number;
5 | w: number;
6 | }
7 | export interface TextureMapData {
8 | colorCorrection: boolean;
9 | horizontalBlending: boolean;
10 | verticalBlending: boolean;
11 | boostMipMapSharpness: number;
12 | modifyTextureMap: {
13 | brightness: number;
14 | contrast: number;
15 | };
16 | offset: UVW;
17 | scale: UVW;
18 | turbulence: UVW;
19 | clamp: boolean;
20 | textureResolution: number | null;
21 | bumpMultiplier: number;
22 | imfChan: string | null;
23 | filename: string;
24 | reflectionType?: string;
25 | texture?: HTMLImageElement;
26 | }
27 | /**
28 | * The Material class.
29 | */
30 | export declare class Material {
31 | name: string;
32 | /**
33 | * Constructor
34 | * @param {String} name the unique name of the material
35 | */
36 | ambient: Vec3;
37 | diffuse: Vec3;
38 | specular: Vec3;
39 | emissive: Vec3;
40 | transmissionFilter: Vec3;
41 | dissolve: number;
42 | specularExponent: number;
43 | transparency: number;
44 | illumination: number;
45 | refractionIndex: number;
46 | sharpness: number;
47 | mapDiffuse: TextureMapData;
48 | mapAmbient: TextureMapData;
49 | mapSpecular: TextureMapData;
50 | mapSpecularExponent: TextureMapData;
51 | mapDissolve: TextureMapData;
52 | antiAliasing: boolean;
53 | mapBump: TextureMapData;
54 | mapDisplacement: TextureMapData;
55 | mapDecal: TextureMapData;
56 | mapEmissive: TextureMapData;
57 | mapReflections: TextureMapData[];
58 | constructor(name: string);
59 | }
60 | /**
61 | * https://en.wikipedia.org/wiki/Wavefront_.obj_file
62 | * http://paulbourke.net/dataformats/mtl/
63 | */
64 | export declare class MaterialLibrary {
65 | data: string;
66 | /**
67 | * Constructs the Material Parser
68 | * @param mtlData the MTL file contents
69 | */
70 | currentMaterial: Material;
71 | materials: {
72 | [k: string]: Material;
73 | };
74 | constructor(data: string);
75 | /**
76 | * Creates a new Material object and adds to the registry.
77 | * @param tokens the tokens associated with the directive
78 | */
79 | parse_newmtl(tokens: string[]): void;
80 | /**
81 | * See the documenation for parse_Ka below for a better understanding.
82 | *
83 | * Given a list of possible color tokens, returns an array of R, G, and B
84 | * color values.
85 | *
86 | * @param tokens the tokens associated with the directive
87 | * @return {*} a 3 element array containing the R, G, and B values
88 | * of the color.
89 | */
90 | parseColor(tokens: string[]): Vec3;
91 | /**
92 | * Parse the ambient reflectivity
93 | *
94 | * A Ka directive can take one of three forms:
95 | * - Ka r g b
96 | * - Ka spectral file.rfl
97 | * - Ka xyz x y z
98 | * These three forms are mutually exclusive in that only one
99 | * declaration can exist per material. It is considered a syntax
100 | * error otherwise.
101 | *
102 | * The "Ka" form specifies the ambient reflectivity using RGB values.
103 | * The "g" and "b" values are optional. If only the "r" value is
104 | * specified, then the "g" and "b" values are assigned the value of
105 | * "r". Values are normally in the range 0.0 to 1.0. Values outside
106 | * of this range increase or decrease the reflectivity accordingly.
107 | *
108 | * The "Ka spectral" form specifies the ambient reflectivity using a
109 | * spectral curve. "file.rfl" is the name of the ".rfl" file containing
110 | * the curve data. "factor" is an optional argument which is a multiplier
111 | * for the values in the .rfl file and defaults to 1.0 if not specified.
112 | *
113 | * The "Ka xyz" form specifies the ambient reflectivity using CIEXYZ values.
114 | * "x y z" are the values of the CIEXYZ color space. The "y" and "z" arguments
115 | * are optional and take on the value of the "x" component if only "x" is
116 | * specified. The "x y z" values are normally in the range of 0.0 to 1.0 and
117 | * increase or decrease ambient reflectivity accordingly outside of that
118 | * range.
119 | *
120 | * @param tokens the tokens associated with the directive
121 | */
122 | parse_Ka(tokens: string[]): void;
123 | /**
124 | * Diffuse Reflectivity
125 | *
126 | * Similar to the Ka directive. Simply replace "Ka" with "Kd" and the rules
127 | * are the same
128 | *
129 | * @param tokens the tokens associated with the directive
130 | */
131 | parse_Kd(tokens: string[]): void;
132 | /**
133 | * Spectral Reflectivity
134 | *
135 | * Similar to the Ka directive. Simply replace "Ks" with "Kd" and the rules
136 | * are the same
137 | *
138 | * @param tokens the tokens associated with the directive
139 | */
140 | parse_Ks(tokens: string[]): void;
141 | /**
142 | * Emissive
143 | *
144 | * The amount and color of light emitted by the object.
145 | *
146 | * @param tokens the tokens associated with the directive
147 | */
148 | parse_Ke(tokens: string[]): void;
149 | /**
150 | * Transmission Filter
151 | *
152 | * Any light passing through the object is filtered by the transmission
153 | * filter, which only allows specific colors to pass through. For example, Tf
154 | * 0 1 0 allows all of the green to pass through and filters out all of the
155 | * red and blue.
156 | *
157 | * Similar to the Ka directive. Simply replace "Ks" with "Tf" and the rules
158 | * are the same
159 | *
160 | * @param tokens the tokens associated with the directive
161 | */
162 | parse_Tf(tokens: string[]): void;
163 | /**
164 | * Specifies the dissolve for the current material.
165 | *
166 | * Statement: d [-halo] `factor`
167 | *
168 | * Example: "d 0.5"
169 | *
170 | * The factor is the amount this material dissolves into the background. A
171 | * factor of 1.0 is fully opaque. This is the default when a new material is
172 | * created. A factor of 0.0 is fully dissolved (completely transparent).
173 | *
174 | * Unlike a real transparent material, the dissolve does not depend upon
175 | * material thickness nor does it have any spectral character. Dissolve works
176 | * on all illumination models.
177 | *
178 | * The dissolve statement allows for an optional "-halo" flag which indicates
179 | * that a dissolve is dependent on the surface orientation relative to the
180 | * viewer. For example, a sphere with the following dissolve, "d -halo 0.0",
181 | * will be fully dissolved at its center and will appear gradually more opaque
182 | * toward its edge.
183 | *
184 | * "factor" is the minimum amount of dissolve applied to the material. The
185 | * amount of dissolve will vary between 1.0 (fully opaque) and the specified
186 | * "factor". The formula is:
187 | *
188 | * dissolve = 1.0 - (N*v)(1.0-factor)
189 | *
190 | * @param tokens the tokens associated with the directive
191 | */
192 | parse_d(tokens: string[]): void;
193 | /**
194 | * The "illum" statement specifies the illumination model to use in the
195 | * material. Illumination models are mathematical equations that represent
196 | * various material lighting and shading effects.
197 | *
198 | * The illumination number can be a number from 0 to 10. The following are
199 | * the list of illumination enumerations and their summaries:
200 | * 0. Color on and Ambient off
201 | * 1. Color on and Ambient on
202 | * 2. Highlight on
203 | * 3. Reflection on and Ray trace on
204 | * 4. Transparency: Glass on, Reflection: Ray trace on
205 | * 5. Reflection: Fresnel on and Ray trace on
206 | * 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
207 | * 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
208 | * 8. Reflection on and Ray trace off
209 | * 9. Transparency: Glass on, Reflection: Ray trace off
210 | * 10. Casts shadows onto invisible surfaces
211 | *
212 | * Example: "illum 2" to specify the "Highlight on" model
213 | *
214 | * @param tokens the tokens associated with the directive
215 | */
216 | parse_illum(tokens: string[]): void;
217 | /**
218 | * Optical Density (AKA Index of Refraction)
219 | *
220 | * Statement: Ni `index`
221 | *
222 | * Example: Ni 1.0
223 | *
224 | * Specifies the optical density for the surface. `index` is the value
225 | * for the optical density. The values can range from 0.001 to 10. A value of
226 | * 1.0 means that light does not bend as it passes through an object.
227 | * Increasing the optical_density increases the amount of bending. Glass has
228 | * an index of refraction of about 1.5. Values of less than 1.0 produce
229 | * bizarre results and are not recommended
230 | *
231 | * @param tokens the tokens associated with the directive
232 | */
233 | parse_Ni(tokens: string[]): void;
234 | /**
235 | * Specifies the specular exponent for the current material. This defines the
236 | * focus of the specular highlight.
237 | *
238 | * Statement: Ns `exponent`
239 | *
240 | * Example: "Ns 250"
241 | *
242 | * `exponent` is the value for the specular exponent. A high exponent results
243 | * in a tight, concentrated highlight. Ns Values normally range from 0 to
244 | * 1000.
245 | *
246 | * @param tokens the tokens associated with the directive
247 | */
248 | parse_Ns(tokens: string[]): void;
249 | /**
250 | * Specifies the sharpness of the reflections from the local reflection map.
251 | *
252 | * Statement: sharpness `value`
253 | *
254 | * Example: "sharpness 100"
255 | *
256 | * If a material does not have a local reflection map defined in its material
257 | * defintions, sharpness will apply to the global reflection map defined in
258 | * PreView.
259 | *
260 | * `value` can be a number from 0 to 1000. The default is 60. A high value
261 | * results in a clear reflection of objects in the reflection map.
262 | *
263 | * Tip: sharpness values greater than 100 introduce aliasing effects in
264 | * flat surfaces that are viewed at a sharp angle.
265 | *
266 | * @param tokens the tokens associated with the directive
267 | */
268 | parse_sharpness(tokens: string[]): void;
269 | /**
270 | * Parses the -cc flag and updates the options object with the values.
271 | *
272 | * @param values the values passed to the -cc flag
273 | * @param options the Object of all image options
274 | */
275 | parse_cc(values: string[], options: TextureMapData): void;
276 | /**
277 | * Parses the -blendu flag and updates the options object with the values.
278 | *
279 | * @param values the values passed to the -blendu flag
280 | * @param options the Object of all image options
281 | */
282 | parse_blendu(values: string[], options: TextureMapData): void;
283 | /**
284 | * Parses the -blendv flag and updates the options object with the values.
285 | *
286 | * @param values the values passed to the -blendv flag
287 | * @param options the Object of all image options
288 | */
289 | parse_blendv(values: string[], options: TextureMapData): void;
290 | /**
291 | * Parses the -boost flag and updates the options object with the values.
292 | *
293 | * @param values the values passed to the -boost flag
294 | * @param options the Object of all image options
295 | */
296 | parse_boost(values: string[], options: TextureMapData): void;
297 | /**
298 | * Parses the -mm flag and updates the options object with the values.
299 | *
300 | * @param values the values passed to the -mm flag
301 | * @param options the Object of all image options
302 | */
303 | parse_mm(values: string[], options: TextureMapData): void;
304 | /**
305 | * Parses and sets the -o, -s, and -t u, v, and w values
306 | *
307 | * @param values the values passed to the -o, -s, -t flag
308 | * @param {Object} option the Object of either the -o, -s, -t option
309 | * @param {Integer} defaultValue the Object of all image options
310 | */
311 | parse_ost(values: string[], option: UVW, defaultValue: number): void;
312 | /**
313 | * Parses the -o flag and updates the options object with the values.
314 | *
315 | * @param values the values passed to the -o flag
316 | * @param options the Object of all image options
317 | */
318 | parse_o(values: string[], options: TextureMapData): void;
319 | /**
320 | * Parses the -s flag and updates the options object with the values.
321 | *
322 | * @param values the values passed to the -s flag
323 | * @param options the Object of all image options
324 | */
325 | parse_s(values: string[], options: TextureMapData): void;
326 | /**
327 | * Parses the -t flag and updates the options object with the values.
328 | *
329 | * @param values the values passed to the -t flag
330 | * @param options the Object of all image options
331 | */
332 | parse_t(values: string[], options: TextureMapData): void;
333 | /**
334 | * Parses the -texres flag and updates the options object with the values.
335 | *
336 | * @param values the values passed to the -texres flag
337 | * @param options the Object of all image options
338 | */
339 | parse_texres(values: string[], options: TextureMapData): void;
340 | /**
341 | * Parses the -clamp flag and updates the options object with the values.
342 | *
343 | * @param values the values passed to the -clamp flag
344 | * @param options the Object of all image options
345 | */
346 | parse_clamp(values: string[], options: TextureMapData): void;
347 | /**
348 | * Parses the -bm flag and updates the options object with the values.
349 | *
350 | * @param values the values passed to the -bm flag
351 | * @param options the Object of all image options
352 | */
353 | parse_bm(values: string[], options: TextureMapData): void;
354 | /**
355 | * Parses the -imfchan flag and updates the options object with the values.
356 | *
357 | * @param values the values passed to the -imfchan flag
358 | * @param options the Object of all image options
359 | */
360 | parse_imfchan(values: string[], options: TextureMapData): void;
361 | /**
362 | * This only exists for relection maps and denotes the type of reflection.
363 | *
364 | * @param values the values passed to the -type flag
365 | * @param options the Object of all image options
366 | */
367 | parse_type(values: string[], options: TextureMapData): void;
368 | /**
369 | * Parses the texture's options and returns an options object with the info
370 | *
371 | * @param tokens all of the option tokens to pass to the texture
372 | * @return {Object} a complete object of objects to apply to the texture
373 | */
374 | parseOptions(tokens: string[]): TextureMapData;
375 | /**
376 | * Parses the given texture map line.
377 | *
378 | * @param tokens all of the tokens representing the texture
379 | * @return a complete object of objects to apply to the texture
380 | */
381 | parseMap(tokens: string[]): TextureMapData;
382 | /**
383 | * Parses the ambient map.
384 | *
385 | * @param tokens list of tokens for the map_Ka direcive
386 | */
387 | parse_map_Ka(tokens: string[]): void;
388 | /**
389 | * Parses the diffuse map.
390 | *
391 | * @param tokens list of tokens for the map_Kd direcive
392 | */
393 | parse_map_Kd(tokens: string[]): void;
394 | /**
395 | * Parses the specular map.
396 | *
397 | * @param tokens list of tokens for the map_Ks direcive
398 | */
399 | parse_map_Ks(tokens: string[]): void;
400 | /**
401 | * Parses the emissive map.
402 | *
403 | * @param tokens list of tokens for the map_Ke direcive
404 | */
405 | parse_map_Ke(tokens: string[]): void;
406 | /**
407 | * Parses the specular exponent map.
408 | *
409 | * @param tokens list of tokens for the map_Ns direcive
410 | */
411 | parse_map_Ns(tokens: string[]): void;
412 | /**
413 | * Parses the dissolve map.
414 | *
415 | * @param tokens list of tokens for the map_d direcive
416 | */
417 | parse_map_d(tokens: string[]): void;
418 | /**
419 | * Parses the anti-aliasing option.
420 | *
421 | * @param tokens list of tokens for the map_aat direcive
422 | */
423 | parse_map_aat(tokens: string[]): void;
424 | /**
425 | * Parses the bump map.
426 | *
427 | * @param tokens list of tokens for the map_bump direcive
428 | */
429 | parse_map_bump(tokens: string[]): void;
430 | /**
431 | * Parses the bump map.
432 | *
433 | * @param tokens list of tokens for the bump direcive
434 | */
435 | parse_bump(tokens: string[]): void;
436 | /**
437 | * Parses the disp map.
438 | *
439 | * @param tokens list of tokens for the disp direcive
440 | */
441 | parse_disp(tokens: string[]): void;
442 | /**
443 | * Parses the decal map.
444 | *
445 | * @param tokens list of tokens for the map_decal direcive
446 | */
447 | parse_decal(tokens: string[]): void;
448 | /**
449 | * Parses the refl map.
450 | *
451 | * @param tokens list of tokens for the refl direcive
452 | */
453 | parse_refl(tokens: string[]): void;
454 | /**
455 | * Parses the MTL file.
456 | *
457 | * Iterates line by line parsing each MTL directive.
458 | *
459 | * This function expects the first token in the line
460 | * to be a valid MTL directive. That token is then used
461 | * to try and run a method on this class. parse_[directive]
462 | * E.g., the `newmtl` directive would try to call the method
463 | * parse_newmtl. Each parsing function takes in the remaining
464 | * list of tokens and updates the currentMaterial class with
465 | * the attributes provided.
466 | */
467 | parse(): void;
468 | }
469 |
--------------------------------------------------------------------------------
/dist/mesh.d.ts:
--------------------------------------------------------------------------------
1 | import { Layout } from "./layout";
2 | import { Material, MaterialLibrary } from "./material";
3 | export interface MeshOptions {
4 | enableWTextureCoord?: boolean;
5 | calcTangentsAndBitangents?: boolean;
6 | materials?: {
7 | [key: string]: Material;
8 | };
9 | }
10 | export interface MaterialNameToIndex {
11 | [k: string]: number;
12 | }
13 | export interface IndexToMaterial {
14 | [k: number]: Material;
15 | }
16 | export interface ArrayBufferWithItemSize extends ArrayBuffer {
17 | numItems?: number;
18 | }
19 | export interface Uint16ArrayWithItemSize extends Uint16Array {
20 | numItems?: number;
21 | }
22 | /**
23 | * The main Mesh class. The constructor will parse through the OBJ file data
24 | * and collect the vertex, vertex normal, texture, and face information. This
25 | * information can then be used later on when creating your VBOs. See
26 | * OBJ.initMeshBuffers for an example of how to use the newly created Mesh
27 | */
28 | export default class Mesh {
29 | vertices: number[];
30 | vertexNormals: number[];
31 | textures: number[];
32 | indices: number[];
33 | name: string;
34 | vertexMaterialIndices: number[];
35 | indicesPerMaterial: number[][];
36 | materialNames: string[];
37 | materialIndices: MaterialNameToIndex;
38 | materialsByIndex: IndexToMaterial;
39 | tangents: number[];
40 | bitangents: number[];
41 | textureStride: number;
42 | /**
43 | * Create a Mesh
44 | * @param {String} objectData - a string representation of an OBJ file with
45 | * newlines preserved.
46 | * @param {Object} options - a JS object containing valid options. See class
47 | * documentation for options.
48 | * @param {bool} options.enableWTextureCoord - Texture coordinates can have
49 | * an optional "w" coordinate after the u and v coordinates. This extra
50 | * value can be used in order to perform fancy transformations on the
51 | * textures themselves. Default is to truncate to only the u an v
52 | * coordinates. Passing true will provide a default value of 0 in the
53 | * event that any or all texture coordinates don't provide a w value.
54 | * Always use the textureStride attribute in order to determine the
55 | * stride length of the texture coordinates when rendering the element
56 | * array.
57 | * @param {bool} options.calcTangentsAndBitangents - Calculate the tangents
58 | * and bitangents when loading of the OBJ is completed. This adds two new
59 | * attributes to the Mesh instance: `tangents` and `bitangents`.
60 | */
61 | constructor(objectData: string, options?: MeshOptions);
62 | /**
63 | * Calculates the tangents and bitangents of the mesh that forms an orthogonal basis together with the
64 | * normal in the direction of the texture coordinates. These are useful for setting up the TBN matrix
65 | * when distorting the normals through normal maps.
66 | * Method derived from: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/
67 | *
68 | * This method requires the normals and texture coordinates to be parsed and set up correctly.
69 | * Adds the tangents and bitangents as members of the class instance.
70 | */
71 | calculateTangentsAndBitangents(): void;
72 | /**
73 | * @param layout - A {@link Layout} object that describes the
74 | * desired memory layout of the generated buffer
75 | * @return The packed array in the ... TODO
76 | */
77 | makeBufferData(layout: Layout): ArrayBufferWithItemSize;
78 | makeIndexBufferData(): Uint16ArrayWithItemSize;
79 | makeIndexBufferDataForMaterials(...materialIndices: Array
89 | * Layout can sort of be thought of as a C-style struct declaration.
90 | * {@link Mesh}'s TBD(...) method will use the {@link Layout} instance to
91 | * pack an array in the given attribute order.
92 | *
93 | * Layout also is very helpful when calling a WebGL context's
94 | *
99 | * For example:
100 | *
131 | * This value will be computed on-the-fly based on the texture coordinates.
132 | * If no texture coordinates are available, the generated value will default to
133 | * 0, 0, 0.
134 | *
135 | * @see {@link Layout}
136 | */
137 | static TANGENT = new Attribute("tangent", 3, TYPES.FLOAT);
138 |
139 | /**
140 | * Attribute layout to pack a vertex's normal's bitangent x, y, & z as floats.
141 | *
142 | * This value will be computed on-the-fly based on the texture coordinates.
143 | * If no texture coordinates are available, the generated value will default to
144 | * 0, 0, 0.
145 | * @see {@link Layout}
146 | */
147 | static BITANGENT = new Attribute("bitangent", 3, TYPES.FLOAT);
148 |
149 | /**
150 | * Attribute layout to pack a vertex's texture coordinates' u & v as floats
151 | *
152 | * @see {@link Layout}
153 | */
154 | static UV = new Attribute("uv", 2, TYPES.FLOAT);
155 |
156 | // Material attributes
157 |
158 | /**
159 | * Attribute layout to pack an unsigned short to be interpreted as a the index
160 | * into a {@link Mesh}'s materials list.
161 | *
162 | * The intention of this value is to send all of the {@link Mesh}'s materials
163 | * into multiple shader uniforms and then reference the current one by this
164 | * vertex attribute.
165 | *
166 | * example glsl code:
167 | *
168 | *
222 | *
223 | * @see {@link Mesh}
224 | */
225 | constructor(...attributes: Attribute[]) {
226 | this.attributes = attributes;
227 | this.attributeMap = {};
228 | let offset = 0;
229 | let maxStrideMultiple = 0;
230 | for (const attribute of attributes) {
231 | if (this.attributeMap[attribute.key]) {
232 | throw new DuplicateAttributeException(attribute);
233 | }
234 | // Add padding to satisfy WebGL's requirement that all
235 | // vertexAttribPointer calls have an offset that is a multiple of
236 | // the type size.
237 | if (offset % attribute.sizeOfType !== 0) {
238 | offset += attribute.sizeOfType - (offset % attribute.sizeOfType);
239 | console.warn("Layout requires padding before " + attribute.key + " attribute");
240 | }
241 | this.attributeMap[attribute.key] = {
242 | attribute: attribute,
243 | size: attribute.size,
244 | type: attribute.type,
245 | normalized: attribute.normalized,
246 | offset: offset,
247 | } as AttributeInfo;
248 | offset += attribute.sizeInBytes;
249 | maxStrideMultiple = Math.max(maxStrideMultiple, attribute.sizeOfType);
250 | }
251 | // Add padding to the end to satisfy WebGL's requirement that all
252 | // vertexAttribPointer calls have a stride that is a multiple of the
253 | // type size. Because we're putting differently sized attributes into
254 | // the same buffer, it must be padded to a multiple of the largest
255 | // type size.
256 | if (offset % maxStrideMultiple !== 0) {
257 | offset += maxStrideMultiple - (offset % maxStrideMultiple);
258 | console.warn("Layout requires padding at the back");
259 | }
260 | this.stride = offset;
261 | for (const attribute of attributes) {
262 | this.attributeMap[attribute.key].stride = this.stride;
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/material.ts:
--------------------------------------------------------------------------------
1 | export type Vec3 = [number, number, number];
2 |
3 | export interface UVW {
4 | u: number;
5 | v: number;
6 | w: number;
7 | }
8 |
9 | export interface TextureMapData {
10 | colorCorrection: boolean;
11 | horizontalBlending: boolean;
12 | verticalBlending: boolean;
13 | boostMipMapSharpness: number;
14 | modifyTextureMap: {
15 | brightness: number;
16 | contrast: number;
17 | };
18 | offset: UVW;
19 | scale: UVW;
20 | turbulence: UVW;
21 | clamp: boolean;
22 | textureResolution: number | null;
23 | bumpMultiplier: number;
24 | imfChan: string | null;
25 | filename: string;
26 | reflectionType?: string;
27 | texture?: HTMLImageElement;
28 | }
29 |
30 | /**
31 | * The Material class.
32 | */
33 | export class Material {
34 | /**
35 | * Constructor
36 | * @param {String} name the unique name of the material
37 | */
38 | // The values for the following attibutes
39 | // are an array of R, G, B normalized values.
40 | // Ka - Ambient Reflectivity
41 | ambient: Vec3 = [0, 0, 0];
42 | // Kd - Defuse Reflectivity
43 | diffuse: Vec3 = [0, 0, 0];
44 | // Ks
45 | specular: Vec3 = [0, 0, 0];
46 | // Ke
47 | emissive: Vec3 = [0, 0, 0];
48 | // Tf
49 | transmissionFilter: Vec3 = [0, 0, 0];
50 | // d
51 | dissolve: number = 0;
52 | // valid range is between 0 and 1000
53 | specularExponent: number = 0;
54 | // either d or Tr; valid values are normalized
55 | transparency: number = 0;
56 | // illum - the enum of the illumination model to use
57 | illumination: number = 0;
58 | // Ni - Set to "normal" (air).
59 | refractionIndex: number = 1;
60 | // sharpness
61 | sharpness: number = 0;
62 | // map_Kd
63 | mapDiffuse: TextureMapData = emptyTextureOptions();
64 | // map_Ka
65 | mapAmbient: TextureMapData = emptyTextureOptions();
66 | // map_Ks
67 | mapSpecular: TextureMapData = emptyTextureOptions();
68 | // map_Ns
69 | mapSpecularExponent: TextureMapData = emptyTextureOptions();
70 | // map_d
71 | mapDissolve: TextureMapData = emptyTextureOptions();
72 | // map_aat
73 | antiAliasing: boolean = false;
74 | // map_bump or bump
75 | mapBump: TextureMapData = emptyTextureOptions();
76 | // disp
77 | mapDisplacement: TextureMapData = emptyTextureOptions();
78 | // decal
79 | mapDecal: TextureMapData = emptyTextureOptions();
80 | // map_Ke
81 | mapEmissive: TextureMapData = emptyTextureOptions();
82 | // refl - when the reflection type is a cube, there will be multiple refl
83 | // statements for each side of the cube. If it's a spherical
84 | // reflection, there should only ever be one.
85 | mapReflections: TextureMapData[] = [];
86 | constructor(public name: string) {}
87 | }
88 |
89 | const SENTINEL_MATERIAL = new Material("sentinel");
90 |
91 | /**
92 | * https://en.wikipedia.org/wiki/Wavefront_.obj_file
93 | * http://paulbourke.net/dataformats/mtl/
94 | */
95 | export class MaterialLibrary {
96 | /**
97 | * Constructs the Material Parser
98 | * @param mtlData the MTL file contents
99 | */
100 | public currentMaterial: Material = SENTINEL_MATERIAL;
101 | public materials: { [k: string]: Material } = {};
102 |
103 | constructor(public data: string) {
104 | this.parse();
105 | }
106 |
107 | /* eslint-disable camelcase */
108 | /* the function names here disobey camelCase conventions
109 | to make parsing/routing easier. see the parse function
110 | documentation for more information. */
111 |
112 | /**
113 | * Creates a new Material object and adds to the registry.
114 | * @param tokens the tokens associated with the directive
115 | */
116 | parse_newmtl(tokens: string[]) {
117 | const name = tokens[0];
118 | // console.info('Parsing new Material:', name);
119 |
120 | this.currentMaterial = new Material(name);
121 | this.materials[name] = this.currentMaterial;
122 | }
123 |
124 | /**
125 | * See the documenation for parse_Ka below for a better understanding.
126 | *
127 | * Given a list of possible color tokens, returns an array of R, G, and B
128 | * color values.
129 | *
130 | * @param tokens the tokens associated with the directive
131 | * @return {*} a 3 element array containing the R, G, and B values
132 | * of the color.
133 | */
134 | parseColor(tokens: string[]): Vec3 {
135 | if (tokens[0] == "spectral") {
136 | throw new Error(
137 | "The MTL parser does not support spectral curve files. You will " +
138 | "need to convert the MTL colors to either RGB or CIEXYZ.",
139 | );
140 | }
141 |
142 | if (tokens[0] == "xyz") {
143 | throw new Error(
144 | "The MTL parser does not currently support XYZ colors. Either convert the " +
145 | "XYZ values to RGB or create an issue to add support for XYZ",
146 | );
147 | }
148 |
149 | // from my understanding of the spec, RGB values at this point
150 | // will either be 3 floats or exactly 1 float, so that's the check
151 | // that i'm going to perform here
152 | if (tokens.length == 3) {
153 | const [x, y, z] = tokens;
154 | return [parseFloat(x), parseFloat(y), parseFloat(z)];
155 | }
156 |
157 | // Since tokens at this point has a length of 3, we're going to assume
158 | // it's exactly 1, skipping the check for 2.
159 | const value = parseFloat(tokens[0]);
160 | // in this case, all values are equivalent
161 | return [value, value, value];
162 | }
163 |
164 | /**
165 | * Parse the ambient reflectivity
166 | *
167 | * A Ka directive can take one of three forms:
168 | * - Ka r g b
169 | * - Ka spectral file.rfl
170 | * - Ka xyz x y z
171 | * These three forms are mutually exclusive in that only one
172 | * declaration can exist per material. It is considered a syntax
173 | * error otherwise.
174 | *
175 | * The "Ka" form specifies the ambient reflectivity using RGB values.
176 | * The "g" and "b" values are optional. If only the "r" value is
177 | * specified, then the "g" and "b" values are assigned the value of
178 | * "r". Values are normally in the range 0.0 to 1.0. Values outside
179 | * of this range increase or decrease the reflectivity accordingly.
180 | *
181 | * The "Ka spectral" form specifies the ambient reflectivity using a
182 | * spectral curve. "file.rfl" is the name of the ".rfl" file containing
183 | * the curve data. "factor" is an optional argument which is a multiplier
184 | * for the values in the .rfl file and defaults to 1.0 if not specified.
185 | *
186 | * The "Ka xyz" form specifies the ambient reflectivity using CIEXYZ values.
187 | * "x y z" are the values of the CIEXYZ color space. The "y" and "z" arguments
188 | * are optional and take on the value of the "x" component if only "x" is
189 | * specified. The "x y z" values are normally in the range of 0.0 to 1.0 and
190 | * increase or decrease ambient reflectivity accordingly outside of that
191 | * range.
192 | *
193 | * @param tokens the tokens associated with the directive
194 | */
195 | parse_Ka(tokens: string[]) {
196 | this.currentMaterial.ambient = this.parseColor(tokens);
197 | }
198 |
199 | /**
200 | * Diffuse Reflectivity
201 | *
202 | * Similar to the Ka directive. Simply replace "Ka" with "Kd" and the rules
203 | * are the same
204 | *
205 | * @param tokens the tokens associated with the directive
206 | */
207 | parse_Kd(tokens: string[]) {
208 | this.currentMaterial.diffuse = this.parseColor(tokens);
209 | }
210 |
211 | /**
212 | * Spectral Reflectivity
213 | *
214 | * Similar to the Ka directive. Simply replace "Ks" with "Kd" and the rules
215 | * are the same
216 | *
217 | * @param tokens the tokens associated with the directive
218 | */
219 | parse_Ks(tokens: string[]) {
220 | this.currentMaterial.specular = this.parseColor(tokens);
221 | }
222 |
223 | /**
224 | * Emissive
225 | *
226 | * The amount and color of light emitted by the object.
227 | *
228 | * @param tokens the tokens associated with the directive
229 | */
230 | parse_Ke(tokens: string[]) {
231 | this.currentMaterial.emissive = this.parseColor(tokens);
232 | }
233 |
234 | /**
235 | * Transmission Filter
236 | *
237 | * Any light passing through the object is filtered by the transmission
238 | * filter, which only allows specific colors to pass through. For example, Tf
239 | * 0 1 0 allows all of the green to pass through and filters out all of the
240 | * red and blue.
241 | *
242 | * Similar to the Ka directive. Simply replace "Ks" with "Tf" and the rules
243 | * are the same
244 | *
245 | * @param tokens the tokens associated with the directive
246 | */
247 | parse_Tf(tokens: string[]) {
248 | this.currentMaterial.transmissionFilter = this.parseColor(tokens);
249 | }
250 |
251 | /**
252 | * Specifies the dissolve for the current material.
253 | *
254 | * Statement: d [-halo] `factor`
255 | *
256 | * Example: "d 0.5"
257 | *
258 | * The factor is the amount this material dissolves into the background. A
259 | * factor of 1.0 is fully opaque. This is the default when a new material is
260 | * created. A factor of 0.0 is fully dissolved (completely transparent).
261 | *
262 | * Unlike a real transparent material, the dissolve does not depend upon
263 | * material thickness nor does it have any spectral character. Dissolve works
264 | * on all illumination models.
265 | *
266 | * The dissolve statement allows for an optional "-halo" flag which indicates
267 | * that a dissolve is dependent on the surface orientation relative to the
268 | * viewer. For example, a sphere with the following dissolve, "d -halo 0.0",
269 | * will be fully dissolved at its center and will appear gradually more opaque
270 | * toward its edge.
271 | *
272 | * "factor" is the minimum amount of dissolve applied to the material. The
273 | * amount of dissolve will vary between 1.0 (fully opaque) and the specified
274 | * "factor". The formula is:
275 | *
276 | * dissolve = 1.0 - (N*v)(1.0-factor)
277 | *
278 | * @param tokens the tokens associated with the directive
279 | */
280 | parse_d(tokens: string[]) {
281 | // this ignores the -halo option as I can't find any documentation on what
282 | // it's supposed to be.
283 | this.currentMaterial.dissolve = parseFloat(tokens.pop() || "0");
284 | }
285 |
286 | /**
287 | * The "illum" statement specifies the illumination model to use in the
288 | * material. Illumination models are mathematical equations that represent
289 | * various material lighting and shading effects.
290 | *
291 | * The illumination number can be a number from 0 to 10. The following are
292 | * the list of illumination enumerations and their summaries:
293 | * 0. Color on and Ambient off
294 | * 1. Color on and Ambient on
295 | * 2. Highlight on
296 | * 3. Reflection on and Ray trace on
297 | * 4. Transparency: Glass on, Reflection: Ray trace on
298 | * 5. Reflection: Fresnel on and Ray trace on
299 | * 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
300 | * 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
301 | * 8. Reflection on and Ray trace off
302 | * 9. Transparency: Glass on, Reflection: Ray trace off
303 | * 10. Casts shadows onto invisible surfaces
304 | *
305 | * Example: "illum 2" to specify the "Highlight on" model
306 | *
307 | * @param tokens the tokens associated with the directive
308 | */
309 | parse_illum(tokens: string[]) {
310 | this.currentMaterial.illumination = parseInt(tokens[0]);
311 | }
312 |
313 | /**
314 | * Optical Density (AKA Index of Refraction)
315 | *
316 | * Statement: Ni `index`
317 | *
318 | * Example: Ni 1.0
319 | *
320 | * Specifies the optical density for the surface. `index` is the value
321 | * for the optical density. The values can range from 0.001 to 10. A value of
322 | * 1.0 means that light does not bend as it passes through an object.
323 | * Increasing the optical_density increases the amount of bending. Glass has
324 | * an index of refraction of about 1.5. Values of less than 1.0 produce
325 | * bizarre results and are not recommended
326 | *
327 | * @param tokens the tokens associated with the directive
328 | */
329 | parse_Ni(tokens: string[]) {
330 | this.currentMaterial.refractionIndex = parseFloat(tokens[0]);
331 | }
332 |
333 | /**
334 | * Specifies the specular exponent for the current material. This defines the
335 | * focus of the specular highlight.
336 | *
337 | * Statement: Ns `exponent`
338 | *
339 | * Example: "Ns 250"
340 | *
341 | * `exponent` is the value for the specular exponent. A high exponent results
342 | * in a tight, concentrated highlight. Ns Values normally range from 0 to
343 | * 1000.
344 | *
345 | * @param tokens the tokens associated with the directive
346 | */
347 | parse_Ns(tokens: string[]) {
348 | this.currentMaterial.specularExponent = parseInt(tokens[0]);
349 | }
350 |
351 | /**
352 | * Specifies the sharpness of the reflections from the local reflection map.
353 | *
354 | * Statement: sharpness `value`
355 | *
356 | * Example: "sharpness 100"
357 | *
358 | * If a material does not have a local reflection map defined in its material
359 | * defintions, sharpness will apply to the global reflection map defined in
360 | * PreView.
361 | *
362 | * `value` can be a number from 0 to 1000. The default is 60. A high value
363 | * results in a clear reflection of objects in the reflection map.
364 | *
365 | * Tip: sharpness values greater than 100 introduce aliasing effects in
366 | * flat surfaces that are viewed at a sharp angle.
367 | *
368 | * @param tokens the tokens associated with the directive
369 | */
370 | parse_sharpness(tokens: string[]) {
371 | this.currentMaterial.sharpness = parseInt(tokens[0]);
372 | }
373 |
374 | /**
375 | * Parses the -cc flag and updates the options object with the values.
376 | *
377 | * @param values the values passed to the -cc flag
378 | * @param options the Object of all image options
379 | */
380 | parse_cc(values: string[], options: TextureMapData) {
381 | options.colorCorrection = values[0] == "on";
382 | }
383 |
384 | /**
385 | * Parses the -blendu flag and updates the options object with the values.
386 | *
387 | * @param values the values passed to the -blendu flag
388 | * @param options the Object of all image options
389 | */
390 | parse_blendu(values: string[], options: TextureMapData) {
391 | options.horizontalBlending = values[0] == "on";
392 | }
393 |
394 | /**
395 | * Parses the -blendv flag and updates the options object with the values.
396 | *
397 | * @param values the values passed to the -blendv flag
398 | * @param options the Object of all image options
399 | */
400 | parse_blendv(values: string[], options: TextureMapData) {
401 | options.verticalBlending = values[0] == "on";
402 | }
403 |
404 | /**
405 | * Parses the -boost flag and updates the options object with the values.
406 | *
407 | * @param values the values passed to the -boost flag
408 | * @param options the Object of all image options
409 | */
410 | parse_boost(values: string[], options: TextureMapData) {
411 | options.boostMipMapSharpness = parseFloat(values[0]);
412 | }
413 |
414 | /**
415 | * Parses the -mm flag and updates the options object with the values.
416 | *
417 | * @param values the values passed to the -mm flag
418 | * @param options the Object of all image options
419 | */
420 | parse_mm(values: string[], options: TextureMapData) {
421 | options.modifyTextureMap.brightness = parseFloat(values[0]);
422 | options.modifyTextureMap.contrast = parseFloat(values[1]);
423 | }
424 |
425 | /**
426 | * Parses and sets the -o, -s, and -t u, v, and w values
427 | *
428 | * @param values the values passed to the -o, -s, -t flag
429 | * @param {Object} option the Object of either the -o, -s, -t option
430 | * @param {Integer} defaultValue the Object of all image options
431 | */
432 | parse_ost(values: string[], option: UVW, defaultValue: number) {
433 | while (values.length < 3) {
434 | values.push(defaultValue.toString());
435 | }
436 |
437 | option.u = parseFloat(values[0]);
438 | option.v = parseFloat(values[1]);
439 | option.w = parseFloat(values[2]);
440 | }
441 |
442 | /**
443 | * Parses the -o flag and updates the options object with the values.
444 | *
445 | * @param values the values passed to the -o flag
446 | * @param options the Object of all image options
447 | */
448 | parse_o(values: string[], options: TextureMapData) {
449 | this.parse_ost(values, options.offset, 0);
450 | }
451 |
452 | /**
453 | * Parses the -s flag and updates the options object with the values.
454 | *
455 | * @param values the values passed to the -s flag
456 | * @param options the Object of all image options
457 | */
458 | parse_s(values: string[], options: TextureMapData) {
459 | this.parse_ost(values, options.scale, 1);
460 | }
461 |
462 | /**
463 | * Parses the -t flag and updates the options object with the values.
464 | *
465 | * @param values the values passed to the -t flag
466 | * @param options the Object of all image options
467 | */
468 | parse_t(values: string[], options: TextureMapData) {
469 | this.parse_ost(values, options.turbulence, 0);
470 | }
471 |
472 | /**
473 | * Parses the -texres flag and updates the options object with the values.
474 | *
475 | * @param values the values passed to the -texres flag
476 | * @param options the Object of all image options
477 | */
478 | parse_texres(values: string[], options: TextureMapData) {
479 | options.textureResolution = parseFloat(values[0]);
480 | }
481 |
482 | /**
483 | * Parses the -clamp flag and updates the options object with the values.
484 | *
485 | * @param values the values passed to the -clamp flag
486 | * @param options the Object of all image options
487 | */
488 | parse_clamp(values: string[], options: TextureMapData) {
489 | options.clamp = values[0] == "on";
490 | }
491 |
492 | /**
493 | * Parses the -bm flag and updates the options object with the values.
494 | *
495 | * @param values the values passed to the -bm flag
496 | * @param options the Object of all image options
497 | */
498 | parse_bm(values: string[], options: TextureMapData) {
499 | options.bumpMultiplier = parseFloat(values[0]);
500 | }
501 |
502 | /**
503 | * Parses the -imfchan flag and updates the options object with the values.
504 | *
505 | * @param values the values passed to the -imfchan flag
506 | * @param options the Object of all image options
507 | */
508 | parse_imfchan(values: string[], options: TextureMapData) {
509 | options.imfChan = values[0];
510 | }
511 |
512 | /**
513 | * This only exists for relection maps and denotes the type of reflection.
514 | *
515 | * @param values the values passed to the -type flag
516 | * @param options the Object of all image options
517 | */
518 | parse_type(values: string[], options: TextureMapData) {
519 | options.reflectionType = values[0];
520 | }
521 |
522 | /**
523 | * Parses the texture's options and returns an options object with the info
524 | *
525 | * @param tokens all of the option tokens to pass to the texture
526 | * @return {Object} a complete object of objects to apply to the texture
527 | */
528 | parseOptions(tokens: string[]): TextureMapData {
529 | const options = emptyTextureOptions();
530 |
531 | let option;
532 | let values;
533 | const optionsToValues: { [k: string]: string[] } = {};
534 |
535 | tokens.reverse();
536 |
537 | while (tokens.length) {
538 | // token is guaranteed to exists here, hence the explicit "as"
539 | const token = tokens.pop() as string;
540 |
541 | if (token.startsWith("-")) {
542 | option = token.substr(1);
543 | optionsToValues[option] = [];
544 | } else if (option) {
545 | optionsToValues[option].push(token);
546 | }
547 | }
548 |
549 | for (option in optionsToValues) {
550 | if (!optionsToValues.hasOwnProperty(option)) {
551 | continue;
552 | }
553 | values = optionsToValues[option];
554 | const optionMethod = (this as any)[`parse_${option}`];
555 | if (optionMethod) {
556 | optionMethod.bind(this)(values, options);
557 | }
558 | }
559 |
560 | return options;
561 | }
562 |
563 | /**
564 | * Parses the given texture map line.
565 | *
566 | * @param tokens all of the tokens representing the texture
567 | * @return a complete object of objects to apply to the texture
568 | */
569 | parseMap(tokens: string[]): TextureMapData {
570 | // according to wikipedia:
571 | // (https://en.wikipedia.org/wiki/Wavefront_.obj_file#Vendor_specific_alterations)
572 | // there is at least one vendor that places the filename before the options
573 | // rather than after (which is to spec). All options start with a '-'
574 | // so if the first token doesn't start with a '-', we're going to assume
575 | // it's the name of the map file.
576 | let optionsString;
577 | let filename = "";
578 | if (!tokens[0].startsWith("-")) {
579 | [filename, ...optionsString] = tokens;
580 | } else {
581 | filename = tokens.pop() as string;
582 | optionsString = tokens;
583 | }
584 |
585 | const options = this.parseOptions(optionsString);
586 | options.filename = filename.replace(/\\/g, "/");
587 |
588 | return options;
589 | }
590 |
591 | /**
592 | * Parses the ambient map.
593 | *
594 | * @param tokens list of tokens for the map_Ka direcive
595 | */
596 | parse_map_Ka(tokens: string[]) {
597 | this.currentMaterial.mapAmbient = this.parseMap(tokens);
598 | }
599 |
600 | /**
601 | * Parses the diffuse map.
602 | *
603 | * @param tokens list of tokens for the map_Kd direcive
604 | */
605 | parse_map_Kd(tokens: string[]) {
606 | this.currentMaterial.mapDiffuse = this.parseMap(tokens);
607 | }
608 |
609 | /**
610 | * Parses the specular map.
611 | *
612 | * @param tokens list of tokens for the map_Ks direcive
613 | */
614 | parse_map_Ks(tokens: string[]) {
615 | this.currentMaterial.mapSpecular = this.parseMap(tokens);
616 | }
617 |
618 | /**
619 | * Parses the emissive map.
620 | *
621 | * @param tokens list of tokens for the map_Ke direcive
622 | */
623 | parse_map_Ke(tokens: string[]) {
624 | this.currentMaterial.mapEmissive = this.parseMap(tokens);
625 | }
626 |
627 | /**
628 | * Parses the specular exponent map.
629 | *
630 | * @param tokens list of tokens for the map_Ns direcive
631 | */
632 | parse_map_Ns(tokens: string[]) {
633 | this.currentMaterial.mapSpecularExponent = this.parseMap(tokens);
634 | }
635 |
636 | /**
637 | * Parses the dissolve map.
638 | *
639 | * @param tokens list of tokens for the map_d direcive
640 | */
641 | parse_map_d(tokens: string[]) {
642 | this.currentMaterial.mapDissolve = this.parseMap(tokens);
643 | }
644 |
645 | /**
646 | * Parses the anti-aliasing option.
647 | *
648 | * @param tokens list of tokens for the map_aat direcive
649 | */
650 | parse_map_aat(tokens: string[]) {
651 | this.currentMaterial.antiAliasing = tokens[0] == "on";
652 | }
653 |
654 | /**
655 | * Parses the bump map.
656 | *
657 | * @param tokens list of tokens for the map_bump direcive
658 | */
659 | parse_map_bump(tokens: string[]) {
660 | this.currentMaterial.mapBump = this.parseMap(tokens);
661 | }
662 |
663 | /**
664 | * Parses the bump map.
665 | *
666 | * @param tokens list of tokens for the bump direcive
667 | */
668 | parse_bump(tokens: string[]) {
669 | this.parse_map_bump(tokens);
670 | }
671 |
672 | /**
673 | * Parses the disp map.
674 | *
675 | * @param tokens list of tokens for the disp direcive
676 | */
677 | parse_disp(tokens: string[]) {
678 | this.currentMaterial.mapDisplacement = this.parseMap(tokens);
679 | }
680 |
681 | /**
682 | * Parses the decal map.
683 | *
684 | * @param tokens list of tokens for the map_decal direcive
685 | */
686 | parse_decal(tokens: string[]) {
687 | this.currentMaterial.mapDecal = this.parseMap(tokens);
688 | }
689 |
690 | /**
691 | * Parses the refl map.
692 | *
693 | * @param tokens list of tokens for the refl direcive
694 | */
695 | parse_refl(tokens: string[]) {
696 | this.currentMaterial.mapReflections.push(this.parseMap(tokens));
697 | }
698 |
699 | /**
700 | * Parses the MTL file.
701 | *
702 | * Iterates line by line parsing each MTL directive.
703 | *
704 | * This function expects the first token in the line
705 | * to be a valid MTL directive. That token is then used
706 | * to try and run a method on this class. parse_[directive]
707 | * E.g., the `newmtl` directive would try to call the method
708 | * parse_newmtl. Each parsing function takes in the remaining
709 | * list of tokens and updates the currentMaterial class with
710 | * the attributes provided.
711 | */
712 | parse() {
713 | const lines = this.data.split(/\r?\n/);
714 | for (let line of lines) {
715 | line = line.trim();
716 | if (!line || line.startsWith("#")) {
717 | continue;
718 | }
719 |
720 | const [directive, ...tokens] = line.split(/\s/);
721 |
722 | const parseMethod = (this as any)[`parse_${directive}`];
723 |
724 | if (!parseMethod) {
725 | console.warn(`Don't know how to parse the directive: "${directive}"`);
726 | continue;
727 | }
728 |
729 | // console.log(`Parsing "${directive}" with tokens: ${tokens}`);
730 | parseMethod.bind(this)(tokens);
731 | }
732 |
733 | // some cleanup. These don't need to be exposed as public data.
734 | delete this.data;
735 | this.currentMaterial = SENTINEL_MATERIAL;
736 | }
737 |
738 | /* eslint-enable camelcase*/
739 | }
740 |
741 | function emptyTextureOptions(): TextureMapData {
742 | return {
743 | colorCorrection: false,
744 | horizontalBlending: true,
745 | verticalBlending: true,
746 | boostMipMapSharpness: 0,
747 | modifyTextureMap: {
748 | brightness: 0,
749 | contrast: 1,
750 | },
751 | offset: { u: 0, v: 0, w: 0 },
752 | scale: { u: 1, v: 1, w: 1 },
753 | turbulence: { u: 0, v: 0, w: 0 },
754 | clamp: false,
755 | textureResolution: null,
756 | bumpMultiplier: 1,
757 | imfChan: null,
758 | filename: "",
759 | };
760 | }
761 |
--------------------------------------------------------------------------------
/src/mesh.ts:
--------------------------------------------------------------------------------
1 | import { Layout } from "./layout";
2 | import { Material, MaterialLibrary } from "./material";
3 |
4 | export interface MeshOptions {
5 | enableWTextureCoord?: boolean;
6 | calcTangentsAndBitangents?: boolean;
7 | materials?: { [key: string]: Material };
8 | }
9 |
10 | interface UnpackedAttrs {
11 | verts: number[];
12 | norms: number[];
13 | textures: number[];
14 | hashindices: { [k: string]: number };
15 | indices: number[][];
16 | materialIndices: number[];
17 | index: number;
18 | }
19 |
20 | export interface MaterialNameToIndex {
21 | [k: string]: number;
22 | }
23 |
24 | export interface IndexToMaterial {
25 | [k: number]: Material;
26 | }
27 |
28 | export interface ArrayBufferWithItemSize extends ArrayBuffer {
29 | numItems?: number;
30 | }
31 |
32 | export interface Uint16ArrayWithItemSize extends Uint16Array {
33 | numItems?: number;
34 | }
35 |
36 | /**
37 | * The main Mesh class. The constructor will parse through the OBJ file data
38 | * and collect the vertex, vertex normal, texture, and face information. This
39 | * information can then be used later on when creating your VBOs. See
40 | * OBJ.initMeshBuffers for an example of how to use the newly created Mesh
41 | */
42 | export default class Mesh {
43 | public vertices: number[];
44 | public vertexNormals: number[];
45 | public textures: number[];
46 | public indices: number[];
47 | public name: string = "";
48 | public vertexMaterialIndices: number[];
49 | public indicesPerMaterial: number[][] = [];
50 | public materialNames: string[];
51 | public materialIndices: MaterialNameToIndex;
52 | public materialsByIndex: IndexToMaterial = {};
53 | public tangents: number[] = [];
54 | public bitangents: number[] = [];
55 | public textureStride: number;
56 |
57 | /**
58 | * Create a Mesh
59 | * @param {String} objectData - a string representation of an OBJ file with
60 | * newlines preserved.
61 | * @param {Object} options - a JS object containing valid options. See class
62 | * documentation for options.
63 | * @param {bool} options.enableWTextureCoord - Texture coordinates can have
64 | * an optional "w" coordinate after the u and v coordinates. This extra
65 | * value can be used in order to perform fancy transformations on the
66 | * textures themselves. Default is to truncate to only the u an v
67 | * coordinates. Passing true will provide a default value of 0 in the
68 | * event that any or all texture coordinates don't provide a w value.
69 | * Always use the textureStride attribute in order to determine the
70 | * stride length of the texture coordinates when rendering the element
71 | * array.
72 | * @param {bool} options.calcTangentsAndBitangents - Calculate the tangents
73 | * and bitangents when loading of the OBJ is completed. This adds two new
74 | * attributes to the Mesh instance: `tangents` and `bitangents`.
75 | */
76 | constructor(objectData: string, options?: MeshOptions) {
77 | options = options || {};
78 | options.materials = options.materials || {};
79 | options.enableWTextureCoord = !!options.enableWTextureCoord;
80 |
81 | // the list of unique vertex, normal, texture, attributes
82 | this.vertexNormals = [];
83 | this.textures = [];
84 | // the indicies to draw the faces
85 | this.indices = [];
86 | this.textureStride = options.enableWTextureCoord ? 3 : 2;
87 |
88 | /*
89 | The OBJ file format does a sort of compression when saving a model in a
90 | program like Blender. There are at least 3 sections (4 including textures)
91 | within the file. Each line in a section begins with the same string:
92 | * 'v': indicates vertex section
93 | * 'vn': indicates vertex normal section
94 | * 'f': indicates the faces section
95 | * 'vt': indicates vertex texture section (if textures were used on the model)
96 | Each of the above sections (except for the faces section) is a list/set of
97 | unique vertices.
98 |
99 | Each line of the faces section contains a list of
100 | (vertex, [texture], normal) groups.
101 |
102 | **Note:** The following documentation will use a capital "V" Vertex to
103 | denote the above (vertex, [texture], normal) groups whereas a lowercase
104 | "v" vertex is used to denote an X, Y, Z coordinate.
105 |
106 | Some examples:
107 | // the texture index is optional, both formats are possible for models
108 | // without a texture applied
109 | f 1/25 18/46 12/31
110 | f 1//25 18//46 12//31
111 |
112 | // A 3 vertex face with texture indices
113 | f 16/92/11 14/101/22 1/69/1
114 |
115 | // A 4 vertex face
116 | f 16/92/11 40/109/40 38/114/38 14/101/22
117 |
118 | The first two lines are examples of a 3 vertex face without a texture applied.
119 | The second is an example of a 3 vertex face with a texture applied.
120 | The third is an example of a 4 vertex face. Note: a face can contain N
121 | number of vertices.
122 |
123 | Each number that appears in one of the groups is a 1-based index
124 | corresponding to an item from the other sections (meaning that indexing
125 | starts at one and *not* zero).
126 |
127 | For example:
128 | `f 16/92/11` is saying to
129 | - take the 16th element from the [v] vertex array
130 | - take the 92nd element from the [vt] texture array
131 | - take the 11th element from the [vn] normal array
132 | and together they make a unique vertex.
133 | Using all 3+ unique Vertices from the face line will produce a polygon.
134 |
135 | Now, you could just go through the OBJ file and create a new vertex for
136 | each face line and WebGL will draw what appears to be the same model.
137 | However, vertices will be overlapped and duplicated all over the place.
138 |
139 | Consider a cube in 3D space centered about the origin and each side is
140 | 2 units long. The front face (with the positive Z-axis pointing towards
141 | you) would have a Top Right vertex (looking orthogonal to its normal)
142 | mapped at (1,1,1) The right face would have a Top Left vertex (looking
143 | orthogonal to its normal) at (1,1,1) and the top face would have a Bottom
144 | Right vertex (looking orthogonal to its normal) at (1,1,1). Each face
145 | has a vertex at the same coordinates, however, three distinct vertices
146 | will be drawn at the same spot.
147 |
148 | To solve the issue of duplicate Vertices (the `(vertex, [texture], normal)`
149 | groups), while iterating through the face lines, when a group is encountered
150 | the whole group string ('16/92/11') is checked to see if it exists in the
151 | packed.hashindices object, and if it doesn't, the indices it specifies
152 | are used to look up each attribute in the corresponding attribute arrays
153 | already created. The values are then copied to the corresponding unpacked
154 | array (flattened to play nice with WebGL's ELEMENT_ARRAY_BUFFER indexing),
155 | the group string is added to the hashindices set and the current unpacked
156 | index is used as this hashindices value so that the group of elements can
157 | be reused. The unpacked index is incremented. If the group string already
158 | exists in the hashindices object, its corresponding value is the index of
159 | that group and is appended to the unpacked indices array.
160 | */
161 | const verts = [];
162 | const vertNormals = [];
163 | const textures = [];
164 | const materialNamesByIndex = [];
165 | const materialIndicesByName: MaterialNameToIndex = {};
166 | // keep track of what material we've seen last
167 | let currentMaterialIndex = -1;
168 | let currentObjectByMaterialIndex = 0;
169 | // unpacking stuff
170 | const unpacked: UnpackedAttrs = {
171 | verts: [],
172 | norms: [],
173 | textures: [],
174 | hashindices: {},
175 | indices: [[]],
176 | materialIndices: [],
177 | index: 0,
178 | };
179 |
180 | const VERTEX_RE = /^v\s/;
181 | const NORMAL_RE = /^vn\s/;
182 | const TEXTURE_RE = /^vt\s/;
183 | const FACE_RE = /^f\s/;
184 | const WHITESPACE_RE = /\s+/;
185 | const USE_MATERIAL_RE = /^usemtl/;
186 |
187 | // array of lines separated by the newline
188 | const lines = objectData.split("\n");
189 |
190 | for (let line of lines) {
191 | line = line.trim();
192 | if (!line || line.startsWith("#")) {
193 | continue;
194 | }
195 | const elements = line.split(WHITESPACE_RE);
196 | elements.shift();
197 |
198 | if (VERTEX_RE.test(line)) {
199 | // if this is a vertex
200 | verts.push(...elements);
201 | } else if (NORMAL_RE.test(line)) {
202 | // if this is a vertex normal
203 | vertNormals.push(...elements);
204 | } else if (TEXTURE_RE.test(line)) {
205 | let coords = elements;
206 | // by default, the loader will only look at the U and V
207 | // coordinates of the vt declaration. So, this truncates the
208 | // elements to only those 2 values. If W texture coordinate
209 | // support is enabled, then the texture coordinate is
210 | // expected to have three values in it.
211 | if (elements.length > 2 && !options.enableWTextureCoord) {
212 | coords = elements.slice(0, 2);
213 | } else if (elements.length === 2 && options.enableWTextureCoord) {
214 | // If for some reason W texture coordinate support is enabled
215 | // and only the U and V coordinates are given, then we supply
216 | // the default value of 0 so that the stride length is correct
217 | // when the textures are unpacked below.
218 | coords.push("0");
219 | }
220 | textures.push(...coords);
221 | } else if (USE_MATERIAL_RE.test(line)) {
222 | const materialName = elements[0];
223 |
224 | // check to see if we've ever seen it before
225 | if (!(materialName in materialIndicesByName)) {
226 | // new material we've never seen
227 | materialNamesByIndex.push(materialName);
228 | materialIndicesByName[materialName] = materialNamesByIndex.length - 1;
229 | // push new array into indices
230 | // already contains an array at index zero, don't add
231 | if (materialIndicesByName[materialName] > 0) {
232 | unpacked.indices.push([]);
233 | }
234 | }
235 | // keep track of the current material index
236 | currentMaterialIndex = materialIndicesByName[materialName];
237 | // update current index array
238 | currentObjectByMaterialIndex = currentMaterialIndex;
239 | } else if (FACE_RE.test(line)) {
240 | // if this is a face
241 | /*
242 | split this face into an array of Vertex groups
243 | for example:
244 | f 16/92/11 14/101/22 1/69/1
245 | becomes:
246 | ['16/92/11', '14/101/22', '1/69/1'];
247 | */
248 |
249 | const triangles = triangulate(elements);
250 | for (const triangle of triangles) {
251 | for (let j = 0, eleLen = triangle.length; j < eleLen; j++) {
252 | const hash = triangle[j] + "," + currentMaterialIndex;
253 | if (hash in unpacked.hashindices) {
254 | unpacked.indices[currentObjectByMaterialIndex].push(unpacked.hashindices[hash]);
255 | } else {
256 | /*
257 | Each element of the face line array is a Vertex which has its
258 | attributes delimited by a forward slash. This will separate
259 | each attribute into another array:
260 | '19/92/11'
261 | becomes:
262 | Vertex = ['19', '92', '11'];
263 | where
264 | Vertex[0] is the vertex index
265 | Vertex[1] is the texture index
266 | Vertex[2] is the normal index
267 | Think of faces having Vertices which are comprised of the
268 | attributes location (v), texture (vt), and normal (vn).
269 | */
270 | const vertex = triangle[j].split("/");
271 | // it's possible for faces to only specify the vertex
272 | // and the normal. In this case, vertex will only have
273 | // a length of 2 and not 3 and the normal will be the
274 | // second item in the list with an index of 1.
275 | const normalIndex = vertex.length - 1;
276 | /*
277 | The verts, textures, and vertNormals arrays each contain a
278 | flattend array of coordinates.
279 |
280 | Because it gets confusing by referring to Vertex and then
281 | vertex (both are different in my descriptions) I will explain
282 | what's going on using the vertexNormals array:
283 |
284 | vertex[2] will contain the one-based index of the vertexNormals
285 | section (vn). One is subtracted from this index number to play
286 | nice with javascript's zero-based array indexing.
287 |
288 | Because vertexNormal is a flattened array of x, y, z values,
289 | simple pointer arithmetic is used to skip to the start of the
290 | vertexNormal, then the offset is added to get the correct
291 | component: +0 is x, +1 is y, +2 is z.
292 |
293 | This same process is repeated for verts and textures.
294 | */
295 | // Vertex position
296 | unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 0]);
297 | unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 1]);
298 | unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 2]);
299 | // Vertex textures
300 | if (textures.length) {
301 | const stride = options.enableWTextureCoord ? 3 : 2;
302 | unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 0]);
303 | unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 1]);
304 | if (options.enableWTextureCoord) {
305 | unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 2]);
306 | }
307 | }
308 | // Vertex normals
309 | unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 0]);
310 | unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 1]);
311 | unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 2]);
312 | // Vertex material indices
313 | unpacked.materialIndices.push(currentMaterialIndex);
314 | // add the newly created Vertex to the list of indices
315 | unpacked.hashindices[hash] = unpacked.index;
316 | unpacked.indices[currentObjectByMaterialIndex].push(unpacked.hashindices[hash]);
317 | // increment the counter
318 | unpacked.index += 1;
319 | }
320 | }
321 | }
322 | }
323 | }
324 | this.vertices = unpacked.verts;
325 | this.vertexNormals = unpacked.norms;
326 | this.textures = unpacked.textures;
327 | this.vertexMaterialIndices = unpacked.materialIndices;
328 | this.indices = unpacked.indices[currentObjectByMaterialIndex];
329 | this.indicesPerMaterial = unpacked.indices;
330 |
331 | this.materialNames = materialNamesByIndex;
332 | this.materialIndices = materialIndicesByName;
333 | this.materialsByIndex = {};
334 |
335 | if (options.calcTangentsAndBitangents) {
336 | this.calculateTangentsAndBitangents();
337 | }
338 | }
339 |
340 | /**
341 | * Calculates the tangents and bitangents of the mesh that forms an orthogonal basis together with the
342 | * normal in the direction of the texture coordinates. These are useful for setting up the TBN matrix
343 | * when distorting the normals through normal maps.
344 | * Method derived from: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/
345 | *
346 | * This method requires the normals and texture coordinates to be parsed and set up correctly.
347 | * Adds the tangents and bitangents as members of the class instance.
348 | */
349 | calculateTangentsAndBitangents() {
350 | console.assert(
351 | !!(
352 | this.vertices &&
353 | this.vertices.length &&
354 | this.vertexNormals &&
355 | this.vertexNormals.length &&
356 | this.textures &&
357 | this.textures.length
358 | ),
359 | "Missing attributes for calculating tangents and bitangents",
360 | );
361 |
362 | const unpacked = {
363 | tangents: [...new Array(this.vertices.length)].map(_ => 0),
364 | bitangents: [...new Array(this.vertices.length)].map(_ => 0),
365 | };
366 |
367 | // Loop through all faces in the whole mesh
368 | const indices = this.indices;
369 | const vertices = this.vertices;
370 | const normals = this.vertexNormals;
371 | const uvs = this.textures;
372 |
373 | for (let i = 0; i < indices.length; i += 3) {
374 | const i0 = indices[i + 0];
375 | const i1 = indices[i + 1];
376 | const i2 = indices[i + 2];
377 |
378 | const x_v0 = vertices[i0 * 3 + 0];
379 | const y_v0 = vertices[i0 * 3 + 1];
380 | const z_v0 = vertices[i0 * 3 + 2];
381 |
382 | const x_uv0 = uvs[i0 * 2 + 0];
383 | const y_uv0 = uvs[i0 * 2 + 1];
384 |
385 | const x_v1 = vertices[i1 * 3 + 0];
386 | const y_v1 = vertices[i1 * 3 + 1];
387 | const z_v1 = vertices[i1 * 3 + 2];
388 |
389 | const x_uv1 = uvs[i1 * 2 + 0];
390 | const y_uv1 = uvs[i1 * 2 + 1];
391 |
392 | const x_v2 = vertices[i2 * 3 + 0];
393 | const y_v2 = vertices[i2 * 3 + 1];
394 | const z_v2 = vertices[i2 * 3 + 2];
395 |
396 | const x_uv2 = uvs[i2 * 2 + 0];
397 | const y_uv2 = uvs[i2 * 2 + 1];
398 |
399 | const x_deltaPos1 = x_v1 - x_v0;
400 | const y_deltaPos1 = y_v1 - y_v0;
401 | const z_deltaPos1 = z_v1 - z_v0;
402 |
403 | const x_deltaPos2 = x_v2 - x_v0;
404 | const y_deltaPos2 = y_v2 - y_v0;
405 | const z_deltaPos2 = z_v2 - z_v0;
406 |
407 | const x_uvDeltaPos1 = x_uv1 - x_uv0;
408 | const y_uvDeltaPos1 = y_uv1 - y_uv0;
409 |
410 | const x_uvDeltaPos2 = x_uv2 - x_uv0;
411 | const y_uvDeltaPos2 = y_uv2 - y_uv0;
412 |
413 | const rInv = x_uvDeltaPos1 * y_uvDeltaPos2 - y_uvDeltaPos1 * x_uvDeltaPos2;
414 | const r = 1.0 / Math.abs(rInv < 0.0001 ? 1.0 : rInv);
415 |
416 | // Tangent
417 | const x_tangent = (x_deltaPos1 * y_uvDeltaPos2 - x_deltaPos2 * y_uvDeltaPos1) * r;
418 | const y_tangent = (y_deltaPos1 * y_uvDeltaPos2 - y_deltaPos2 * y_uvDeltaPos1) * r;
419 | const z_tangent = (z_deltaPos1 * y_uvDeltaPos2 - z_deltaPos2 * y_uvDeltaPos1) * r;
420 |
421 | // Bitangent
422 | const x_bitangent = (x_deltaPos2 * x_uvDeltaPos1 - x_deltaPos1 * x_uvDeltaPos2) * r;
423 | const y_bitangent = (y_deltaPos2 * x_uvDeltaPos1 - y_deltaPos1 * x_uvDeltaPos2) * r;
424 | const z_bitangent = (z_deltaPos2 * x_uvDeltaPos1 - z_deltaPos1 * x_uvDeltaPos2) * r;
425 |
426 | // Gram-Schmidt orthogonalize
427 | //t = glm::normalize(t - n * glm:: dot(n, t));
428 | const x_n0 = normals[i0 * 3 + 0];
429 | const y_n0 = normals[i0 * 3 + 1];
430 | const z_n0 = normals[i0 * 3 + 2];
431 |
432 | const x_n1 = normals[i1 * 3 + 0];
433 | const y_n1 = normals[i1 * 3 + 1];
434 | const z_n1 = normals[i1 * 3 + 2];
435 |
436 | const x_n2 = normals[i2 * 3 + 0];
437 | const y_n2 = normals[i2 * 3 + 1];
438 | const z_n2 = normals[i2 * 3 + 2];
439 |
440 | // Tangent
441 | const n0_dot_t = x_tangent * x_n0 + y_tangent * y_n0 + z_tangent * z_n0;
442 | const n1_dot_t = x_tangent * x_n1 + y_tangent * y_n1 + z_tangent * z_n1;
443 | const n2_dot_t = x_tangent * x_n2 + y_tangent * y_n2 + z_tangent * z_n2;
444 |
445 | const x_resTangent0 = x_tangent - x_n0 * n0_dot_t;
446 | const y_resTangent0 = y_tangent - y_n0 * n0_dot_t;
447 | const z_resTangent0 = z_tangent - z_n0 * n0_dot_t;
448 |
449 | const x_resTangent1 = x_tangent - x_n1 * n1_dot_t;
450 | const y_resTangent1 = y_tangent - y_n1 * n1_dot_t;
451 | const z_resTangent1 = z_tangent - z_n1 * n1_dot_t;
452 |
453 | const x_resTangent2 = x_tangent - x_n2 * n2_dot_t;
454 | const y_resTangent2 = y_tangent - y_n2 * n2_dot_t;
455 | const z_resTangent2 = z_tangent - z_n2 * n2_dot_t;
456 |
457 | const magTangent0 = Math.sqrt(
458 | x_resTangent0 * x_resTangent0 + y_resTangent0 * y_resTangent0 + z_resTangent0 * z_resTangent0,
459 | );
460 | const magTangent1 = Math.sqrt(
461 | x_resTangent1 * x_resTangent1 + y_resTangent1 * y_resTangent1 + z_resTangent1 * z_resTangent1,
462 | );
463 | const magTangent2 = Math.sqrt(
464 | x_resTangent2 * x_resTangent2 + y_resTangent2 * y_resTangent2 + z_resTangent2 * z_resTangent2,
465 | );
466 |
467 | // Bitangent
468 | const n0_dot_bt = x_bitangent * x_n0 + y_bitangent * y_n0 + z_bitangent * z_n0;
469 | const n1_dot_bt = x_bitangent * x_n1 + y_bitangent * y_n1 + z_bitangent * z_n1;
470 | const n2_dot_bt = x_bitangent * x_n2 + y_bitangent * y_n2 + z_bitangent * z_n2;
471 |
472 | const x_resBitangent0 = x_bitangent - x_n0 * n0_dot_bt;
473 | const y_resBitangent0 = y_bitangent - y_n0 * n0_dot_bt;
474 | const z_resBitangent0 = z_bitangent - z_n0 * n0_dot_bt;
475 |
476 | const x_resBitangent1 = x_bitangent - x_n1 * n1_dot_bt;
477 | const y_resBitangent1 = y_bitangent - y_n1 * n1_dot_bt;
478 | const z_resBitangent1 = z_bitangent - z_n1 * n1_dot_bt;
479 |
480 | const x_resBitangent2 = x_bitangent - x_n2 * n2_dot_bt;
481 | const y_resBitangent2 = y_bitangent - y_n2 * n2_dot_bt;
482 | const z_resBitangent2 = z_bitangent - z_n2 * n2_dot_bt;
483 |
484 | const magBitangent0 = Math.sqrt(
485 | x_resBitangent0 * x_resBitangent0 +
486 | y_resBitangent0 * y_resBitangent0 +
487 | z_resBitangent0 * z_resBitangent0,
488 | );
489 | const magBitangent1 = Math.sqrt(
490 | x_resBitangent1 * x_resBitangent1 +
491 | y_resBitangent1 * y_resBitangent1 +
492 | z_resBitangent1 * z_resBitangent1,
493 | );
494 | const magBitangent2 = Math.sqrt(
495 | x_resBitangent2 * x_resBitangent2 +
496 | y_resBitangent2 * y_resBitangent2 +
497 | z_resBitangent2 * z_resBitangent2,
498 | );
499 |
500 | unpacked.tangents[i0 * 3 + 0] += x_resTangent0 / magTangent0;
501 | unpacked.tangents[i0 * 3 + 1] += y_resTangent0 / magTangent0;
502 | unpacked.tangents[i0 * 3 + 2] += z_resTangent0 / magTangent0;
503 |
504 | unpacked.tangents[i1 * 3 + 0] += x_resTangent1 / magTangent1;
505 | unpacked.tangents[i1 * 3 + 1] += y_resTangent1 / magTangent1;
506 | unpacked.tangents[i1 * 3 + 2] += z_resTangent1 / magTangent1;
507 |
508 | unpacked.tangents[i2 * 3 + 0] += x_resTangent2 / magTangent2;
509 | unpacked.tangents[i2 * 3 + 1] += y_resTangent2 / magTangent2;
510 | unpacked.tangents[i2 * 3 + 2] += z_resTangent2 / magTangent2;
511 |
512 | unpacked.bitangents[i0 * 3 + 0] += x_resBitangent0 / magBitangent0;
513 | unpacked.bitangents[i0 * 3 + 1] += y_resBitangent0 / magBitangent0;
514 | unpacked.bitangents[i0 * 3 + 2] += z_resBitangent0 / magBitangent0;
515 |
516 | unpacked.bitangents[i1 * 3 + 0] += x_resBitangent1 / magBitangent1;
517 | unpacked.bitangents[i1 * 3 + 1] += y_resBitangent1 / magBitangent1;
518 | unpacked.bitangents[i1 * 3 + 2] += z_resBitangent1 / magBitangent1;
519 |
520 | unpacked.bitangents[i2 * 3 + 0] += x_resBitangent2 / magBitangent2;
521 | unpacked.bitangents[i2 * 3 + 1] += y_resBitangent2 / magBitangent2;
522 | unpacked.bitangents[i2 * 3 + 2] += z_resBitangent2 / magBitangent2;
523 |
524 | // TODO: check handedness
525 | }
526 |
527 | this.tangents = unpacked.tangents;
528 | this.bitangents = unpacked.bitangents;
529 | }
530 |
531 | /**
532 | * @param layout - A {@link Layout} object that describes the
533 | * desired memory layout of the generated buffer
534 | * @return The packed array in the ... TODO
535 | */
536 | makeBufferData(layout: Layout): ArrayBufferWithItemSize {
537 | const numItems = this.vertices.length / 3;
538 | const buffer: ArrayBufferWithItemSize = new ArrayBuffer(layout.stride * numItems);
539 | buffer.numItems = numItems;
540 | const dataView = new DataView(buffer);
541 | for (let i = 0, vertexOffset = 0; i < numItems; i++) {
542 | vertexOffset = i * layout.stride;
543 | // copy in the vertex data in the order and format given by the
544 | // layout param
545 | for (const attribute of layout.attributes) {
546 | const offset = vertexOffset + layout.attributeMap[attribute.key].offset;
547 | switch (attribute.key) {
548 | case Layout.POSITION.key:
549 | dataView.setFloat32(offset, this.vertices[i * 3], true);
550 | dataView.setFloat32(offset + 4, this.vertices[i * 3 + 1], true);
551 | dataView.setFloat32(offset + 8, this.vertices[i * 3 + 2], true);
552 | break;
553 | case Layout.UV.key:
554 | dataView.setFloat32(offset, this.textures[i * 2], true);
555 | dataView.setFloat32(offset + 4, this.textures[i * 2 + 1], true);
556 | break;
557 | case Layout.NORMAL.key:
558 | dataView.setFloat32(offset, this.vertexNormals[i * 3], true);
559 | dataView.setFloat32(offset + 4, this.vertexNormals[i * 3 + 1], true);
560 | dataView.setFloat32(offset + 8, this.vertexNormals[i * 3 + 2], true);
561 | break;
562 | case Layout.MATERIAL_INDEX.key:
563 | dataView.setInt16(offset, this.vertexMaterialIndices[i], true);
564 | break;
565 | case Layout.AMBIENT.key: {
566 | const materialIndex = this.vertexMaterialIndices[i];
567 | const material = this.materialsByIndex[materialIndex];
568 | if (!material) {
569 | console.warn(
570 | 'Material "' +
571 | this.materialNames[materialIndex] +
572 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
573 | );
574 | break;
575 | }
576 | dataView.setFloat32(offset, material.ambient[0], true);
577 | dataView.setFloat32(offset + 4, material.ambient[1], true);
578 | dataView.setFloat32(offset + 8, material.ambient[2], true);
579 | break;
580 | }
581 | case Layout.DIFFUSE.key: {
582 | const materialIndex = this.vertexMaterialIndices[i];
583 | const material = this.materialsByIndex[materialIndex];
584 | if (!material) {
585 | console.warn(
586 | 'Material "' +
587 | this.materialNames[materialIndex] +
588 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
589 | );
590 | break;
591 | }
592 | dataView.setFloat32(offset, material.diffuse[0], true);
593 | dataView.setFloat32(offset + 4, material.diffuse[1], true);
594 | dataView.setFloat32(offset + 8, material.diffuse[2], true);
595 | break;
596 | }
597 | case Layout.SPECULAR.key: {
598 | const materialIndex = this.vertexMaterialIndices[i];
599 | const material = this.materialsByIndex[materialIndex];
600 | if (!material) {
601 | console.warn(
602 | 'Material "' +
603 | this.materialNames[materialIndex] +
604 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
605 | );
606 | break;
607 | }
608 | dataView.setFloat32(offset, material.specular[0], true);
609 | dataView.setFloat32(offset + 4, material.specular[1], true);
610 | dataView.setFloat32(offset + 8, material.specular[2], true);
611 | break;
612 | }
613 | case Layout.SPECULAR_EXPONENT.key: {
614 | const materialIndex = this.vertexMaterialIndices[i];
615 | const material = this.materialsByIndex[materialIndex];
616 | if (!material) {
617 | console.warn(
618 | 'Material "' +
619 | this.materialNames[materialIndex] +
620 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
621 | );
622 | break;
623 | }
624 | dataView.setFloat32(offset, material.specularExponent, true);
625 | break;
626 | }
627 | case Layout.EMISSIVE.key: {
628 | const materialIndex = this.vertexMaterialIndices[i];
629 | const material = this.materialsByIndex[materialIndex];
630 | if (!material) {
631 | console.warn(
632 | 'Material "' +
633 | this.materialNames[materialIndex] +
634 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
635 | );
636 | break;
637 | }
638 | dataView.setFloat32(offset, material.emissive[0], true);
639 | dataView.setFloat32(offset + 4, material.emissive[1], true);
640 | dataView.setFloat32(offset + 8, material.emissive[2], true);
641 | break;
642 | }
643 | case Layout.TRANSMISSION_FILTER.key: {
644 | const materialIndex = this.vertexMaterialIndices[i];
645 | const material = this.materialsByIndex[materialIndex];
646 | if (!material) {
647 | console.warn(
648 | 'Material "' +
649 | this.materialNames[materialIndex] +
650 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
651 | );
652 | break;
653 | }
654 | dataView.setFloat32(offset, material.transmissionFilter[0], true);
655 | dataView.setFloat32(offset + 4, material.transmissionFilter[1], true);
656 | dataView.setFloat32(offset + 8, material.transmissionFilter[2], true);
657 | break;
658 | }
659 | case Layout.DISSOLVE.key: {
660 | const materialIndex = this.vertexMaterialIndices[i];
661 | const material = this.materialsByIndex[materialIndex];
662 | if (!material) {
663 | console.warn(
664 | 'Material "' +
665 | this.materialNames[materialIndex] +
666 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
667 | );
668 | break;
669 | }
670 | dataView.setFloat32(offset, material.dissolve, true);
671 | break;
672 | }
673 | case Layout.ILLUMINATION.key: {
674 | const materialIndex = this.vertexMaterialIndices[i];
675 | const material = this.materialsByIndex[materialIndex];
676 | if (!material) {
677 | console.warn(
678 | 'Material "' +
679 | this.materialNames[materialIndex] +
680 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
681 | );
682 | break;
683 | }
684 | dataView.setInt16(offset, material.illumination, true);
685 | break;
686 | }
687 | case Layout.REFRACTION_INDEX.key: {
688 | const materialIndex = this.vertexMaterialIndices[i];
689 | const material = this.materialsByIndex[materialIndex];
690 | if (!material) {
691 | console.warn(
692 | 'Material "' +
693 | this.materialNames[materialIndex] +
694 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
695 | );
696 | break;
697 | }
698 | dataView.setFloat32(offset, material.refractionIndex, true);
699 | break;
700 | }
701 | case Layout.SHARPNESS.key: {
702 | const materialIndex = this.vertexMaterialIndices[i];
703 | const material = this.materialsByIndex[materialIndex];
704 | if (!material) {
705 | console.warn(
706 | 'Material "' +
707 | this.materialNames[materialIndex] +
708 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
709 | );
710 | break;
711 | }
712 | dataView.setFloat32(offset, material.sharpness, true);
713 | break;
714 | }
715 | case Layout.ANTI_ALIASING.key: {
716 | const materialIndex = this.vertexMaterialIndices[i];
717 | const material = this.materialsByIndex[materialIndex];
718 | if (!material) {
719 | console.warn(
720 | 'Material "' +
721 | this.materialNames[materialIndex] +
722 | '" not found in mesh. Did you forget to call addMaterialLibrary(...)?"',
723 | );
724 | break;
725 | }
726 | dataView.setInt16(offset, material.antiAliasing ? 1 : 0, true);
727 | break;
728 | }
729 | }
730 | }
731 | }
732 | return buffer;
733 | }
734 |
735 | makeIndexBufferData(): Uint16ArrayWithItemSize {
736 | const buffer: Uint16ArrayWithItemSize = new Uint16Array(this.indices);
737 | buffer.numItems = this.indices.length;
738 | return buffer;
739 | }
740 |
741 | makeIndexBufferDataForMaterials(...materialIndices: Array
51 | * "BYTE": signed 8-bit integer, with values in [-128, 127]
52 | * "SHORT": signed 16-bit integer, with values in
53 | * [-32768, 32767]
54 | * "UNSIGNED_BYTE": unsigned 8-bit integer, with values in
55 | * [0, 255]
56 | * "UNSIGNED_SHORT": unsigned 16-bit integer, with values in
57 | * [0, 65535]
58 | * "FLOAT": 32-bit floating point number
59 | * @param {boolean} normalized - Whether integer data values should be
60 | * normalized when being casted to a float.
61 | * If true, signed integers are normalized to [-1, 1].
62 | * If true, unsigned integers are normalized to [0, 1].
63 | * For type "FLOAT", this parameter has no effect.
64 | */
65 | constructor(public key: string, public size: number, public type: TYPES, public normalized: boolean = false) {
66 | switch (type) {
67 | case "BYTE":
68 | case "UNSIGNED_BYTE":
69 | this.sizeOfType = 1;
70 | break;
71 | case "SHORT":
72 | case "UNSIGNED_SHORT":
73 | this.sizeOfType = 2;
74 | break;
75 | case "FLOAT":
76 | this.sizeOfType = 4;
77 | break;
78 | default:
79 | throw new Error(`Unknown gl type: ${type}`);
80 | }
81 | this.sizeInBytes = this.sizeOfType * size;
82 | }
83 | }
84 |
85 | /**
86 | * A class to represent the memory layout for a vertex attribute array. Used by
87 | * {@link Mesh}'s TBD(...) method to generate a packed array from mesh data.
88 | * vertexAttribPointer
method. If you've created a buffer using
95 | * a Layout instance, then the same Layout instance can be used to determine
96 | * the size, type, normalized, stride, and offset parameters for
97 | * vertexAttribPointer
.
98 | *
110 | * @see {@link Mesh}
111 | */
112 | export class Layout {
113 | // Geometry attributes
114 | /**
115 | * Attribute layout to pack a vertex's x, y, & z as floats
116 | *
117 | * @see {@link Layout}
118 | */
119 | static POSITION = new Attribute("position", 3, TYPES.FLOAT);
120 |
121 | /**
122 | * Attribute layout to pack a vertex's normal's x, y, & z as floats
123 | *
124 | * @see {@link Layout}
125 | */
126 | static NORMAL = new Attribute("normal", 3, TYPES.FLOAT);
127 |
128 | /**
129 | * Attribute layout to pack a vertex's normal's x, y, & z as floats.
130 | *
101 | *
102 | * const index = glctx.getAttribLocation(shaderProgram, "pos");
103 | * glctx.vertexAttribPointer(
104 | * layout.position.size,
105 | * glctx[layout.position.type],
106 | * layout.position.normalized,
107 | * layout.position.stride,
108 | * layout.position.offset);
109 | *
185 | * TODO: More description & test to make sure subscripting by attributes even
186 | * works for webgl
187 | *
188 | * @see {@link Layout}
189 | */
190 | static MATERIAL_INDEX = new Attribute("materialIndex", 1, TYPES.SHORT);
191 | static MATERIAL_ENABLED = new Attribute("materialEnabled", 1, TYPES.UNSIGNED_SHORT);
192 | static AMBIENT = new Attribute("ambient", 3, TYPES.FLOAT);
193 | static DIFFUSE = new Attribute("diffuse", 3, TYPES.FLOAT);
194 | static SPECULAR = new Attribute("specular", 3, TYPES.FLOAT);
195 | static SPECULAR_EXPONENT = new Attribute("specularExponent", 3, TYPES.FLOAT);
196 | static EMISSIVE = new Attribute("emissive", 3, TYPES.FLOAT);
197 | static TRANSMISSION_FILTER = new Attribute("transmissionFilter", 3, TYPES.FLOAT);
198 | static DISSOLVE = new Attribute("dissolve", 1, TYPES.FLOAT);
199 | static ILLUMINATION = new Attribute("illumination", 1, TYPES.UNSIGNED_SHORT);
200 | static REFRACTION_INDEX = new Attribute("refractionIndex", 1, TYPES.FLOAT);
201 | static SHARPNESS = new Attribute("sharpness", 1, TYPES.FLOAT);
202 | static MAP_DIFFUSE = new Attribute("mapDiffuse", 1, TYPES.SHORT);
203 | static MAP_AMBIENT = new Attribute("mapAmbient", 1, TYPES.SHORT);
204 | static MAP_SPECULAR = new Attribute("mapSpecular", 1, TYPES.SHORT);
205 | static MAP_SPECULAR_EXPONENT = new Attribute("mapSpecularExponent", 1, TYPES.SHORT);
206 | static MAP_DISSOLVE = new Attribute("mapDissolve", 1, TYPES.SHORT);
207 | static ANTI_ALIASING = new Attribute("antiAliasing", 1, TYPES.UNSIGNED_SHORT);
208 | static MAP_BUMP = new Attribute("mapBump", 1, TYPES.SHORT);
209 | static MAP_DISPLACEMENT = new Attribute("mapDisplacement", 1, TYPES.SHORT);
210 | static MAP_DECAL = new Attribute("mapDecal", 1, TYPES.SHORT);
211 | static MAP_EMISSIVE = new Attribute("mapEmissive", 1, TYPES.SHORT);
212 |
213 | public stride: number;
214 | public attributes: Attribute[];
215 | public attributeMap: { [idx: string]: AttributeInfo };
216 | /**
217 | * Create a Layout object. This constructor will throw if any duplicate
218 | * attributes are given.
219 | * @param {Array} ...attributes - An ordered list of attributes that
220 | * describe the desired memory layout for each vertex attribute.
221 | *
169 | * // this is bound using MATERIAL_INDEX
170 | * attribute int materialIndex;
171 | *
172 | * struct Material {
173 | * vec3 diffuse;
174 | * vec3 specular;
175 | * vec3 specularExponent;
176 | * };
177 | *
178 | * uniform Material materials[MAX_MATERIALS];
179 | *
180 | * // ...
181 | *
182 | * vec3 diffuse = materials[materialIndex];
183 | *
184 | *