├── images └── screenshot.png ├── .gitignore ├── .npmignore ├── example └── example.js ├── LICENSE ├── package.json ├── lib └── mesh.js ├── README.md └── index.js /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackgl/gl-mesh/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.DS_Store 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | 15 | npm-debug.log 16 | node_modules/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.DS_Store 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | 15 | npm-debug.log 16 | node_modules/* 17 | example/* 18 | images/* -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var shell = require("gl-now")() 4 | var createMesh = require("../index.js") 5 | var simple2DShader = require("simple-2d-shader") 6 | 7 | var mesh, shader 8 | 9 | shell.on("gl-init", function() { 10 | shader = simple2DShader(shell.gl) 11 | mesh = createMesh(shell.gl, 12 | [[0, 1, 2], 13 | [2, 1, 3]], 14 | { "position": [[-1,-1], [0, 1], [0, 0], [1, -1]], 15 | "color": [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 1]] }) 16 | }) 17 | 18 | shell.on("gl-render", function(t) { 19 | shader.bind() 20 | mesh.bind(shader) 21 | mesh.draw() 22 | mesh.unbind() 23 | }) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-mesh", 3 | "version": "0.1.0", 4 | "description": "Static indexed mesh drawing for WebGL", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "dependencies": { 10 | "gl-vao": "~0.0.1", 11 | "ndarray-ops": "^1.2.2", 12 | "typedarray-pool": "~0.1.1", 13 | "webglew": "~0.0.0", 14 | "ndarray": "~1.0.0", 15 | "gl-buffer": "~0.1.0" 16 | }, 17 | "devDependencies": { 18 | "gl-now": "~0.0.0", 19 | "simple-2d-shader": "~0.0.0" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/mikolalysenko/gl-mesh.git" 27 | }, 28 | "keywords": [ 29 | "webgl", 30 | "mesh", 31 | "static", 32 | "indexed", 33 | "triangle", 34 | "point", 35 | "line", 36 | "wrapper", 37 | "gl", 38 | "drawing", 39 | "cell", 40 | "complex", 41 | "simplicial", 42 | "complex" 43 | ], 44 | "author": "Mikola Lysenko", 45 | "license": "MIT", 46 | "readmeFilename": "README.md", 47 | "gitHead": "529cb8e9b5634304847a3e0f810db777c1235986", 48 | "bugs": { 49 | "url": "https://github.com/mikolalysenko/gl-mesh/issues" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/mesh.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | function Mesh(gl, mode, numElements, vao, elements, attributes, attributeNames) { 4 | this.gl = gl 5 | this.mode = mode 6 | this.numElements = numElements 7 | this.vao = vao 8 | this.elements = elements 9 | this.attributes = attributes 10 | this.attributeNames = attributeNames 11 | } 12 | 13 | Mesh.prototype.bind = function(shader) { 14 | this.vao.bind() 15 | var attributeNames = this.attributeNames 16 | var nattribs = attributeNames.length 17 | for(var i=0; i 36 | 37 | # Install 38 | 39 | Use [npm](https://npmjs.org/) to install it locally: 40 | 41 | npm install gl-mesh 42 | 43 | Then you can build/run the client using any tool that compiles CommonJS modules, for example [browserify](https://github.com/substack/node-browserify) or [beefy](https://github.com/chrisdickinson/beefy). 44 | 45 | # API 46 | 47 | ```javascript 48 | var createMesh = require("gl-mesh") 49 | ``` 50 | 51 | ## Constructor 52 | 53 | ### `var mesh = createMesh(gl, cells, attributes)` 54 | Creates a static mesh. 55 | 56 | * `gl` is a webgl context 57 | * `cells` is a list of representing indices into the geometry 58 | * `attributes` is an object of attributes to pass to the mesh 59 | 60 | Each of these objects can be encoded as either an array-of-native-arrays or as a typed array using [ndarrays](https://github.com/mikolalysenko/ndarray). The first dimension in the shape is interepreted as the number of vertices in the attribute while the second dimension is interpreted as the size. For example, to pass in a packed array of 3d vertices in a typed array you could do: 61 | 62 | ```javascript 63 | var mesh = createMesh(gl, cells, { "positions": ndarray(position_data, [numVertices, 3]) }) 64 | ``` 65 | 66 | The drawing mode for the mesh is determined by the shape of the cells according to the following rule: 67 | 68 | * `cells.length == 0` : empty mesh 69 | * `cells[0].length == 1` : `gl.POINTS` 70 | * `cells[0].length == 2` : `gl.LINES` 71 | * `cells[0].length == 3` : `gl.TRIANGLES` 72 | 73 | You can also skip the `cells` parameter, in which case the resulting mesh is drawn as a point cloud. 74 | 75 | 76 | Also you can pass a single object with a `cells` field. For example, here is the quickest way to create a Stanford bunny test mesh: 77 | 78 | ```javascript 79 | var bunnyMesh = createMesh(gl, require("bunny")) 80 | ``` 81 | 82 | Where the module comes from the [`bunny`](https://npmjs.org/package/bunny) package 83 | 84 | **Returns** A `Mesh` object 85 | 86 | ## Methods 87 | Each `Mesh` object has the following methods: 88 | 89 | ### `mesh.bind(shader)` 90 | Binds the mesh to the given shader updating attributes accordingly. 91 | 92 | * `shader` is an instance of a shader created using [`gl-shader`](https://github.com/mikolalysenko/gl-shader) 93 | 94 | ### `mesh.draw()` 95 | Draws an instance of the mesh once it is bound to a shader 96 | 97 | ### `mesh.unbind()` 98 | Unbinds the mesh releasing the current vertex attribute state 99 | 100 | ### `mesh.dispose()` 101 | Destroys the mesh and releases all of its associated resources 102 | 103 | # Credits 104 | (c) 2013 Mikola Lysenko. MIT License -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var webglew = require("webglew") 4 | var createBuffer = require("gl-buffer") 5 | var createVAO = require("gl-vao") 6 | var ndarray = require("ndarray") 7 | var ops = require("ndarray-ops") 8 | var pool = require("typedarray-pool") 9 | 10 | var Mesh = require("./lib/mesh.js") 11 | 12 | function EmptyMesh(gl) { 13 | this.gl = gl 14 | this.mode = gl.POINTS 15 | this.numElements = 0 16 | this.elements = null 17 | this.attributes = {} 18 | } 19 | EmptyMesh.prototype.dispose = function() {} 20 | EmptyMesh.prototype.bind = function() {} 21 | EmptyMesh.prototype.unbind = function() {} 22 | EmptyMesh.prototype.draw = function() {} 23 | 24 | 25 | function MeshAttribute(buffer, size, type, normalized) { 26 | this.buffer = buffer 27 | this.size = size 28 | this.type = type 29 | this.normalized = normalized 30 | } 31 | 32 | function getGLType(gl, array) { 33 | if((array instanceof Uint8Array) || 34 | (array instanceof Uint8ClampedArray)) { 35 | return gl.UNSIGNED_BYTE 36 | } else if(array instanceof Int8Array) { 37 | return gl.BYTE 38 | } else if(array instanceof Uint16Array) { 39 | return gl.UNSIGNED_SHORT 40 | } else if(array instanceof Int16Array) { 41 | return gl.SHORT 42 | } else if(array instanceof Uint32Array) { 43 | return gl.UNSIGNED_INT 44 | } else if(array instanceof Int32Array) { 45 | return gl.INT 46 | } else if(array instanceof Float32Array) { 47 | return gl.FLOAT 48 | } 49 | return 0 50 | } 51 | 52 | function createTmpArray(gl, type, n) { 53 | if(type === gl.UNSIGNED_BYTE) { 54 | return pool.mallocUint8(n) 55 | } else if(type === gl.BYTE) { 56 | return pool.mallocInt8(n) 57 | } else if(type === gl.UNSIGNED_SHORT) { 58 | return pool.mallocUint16(n) 59 | } else if(type === gl.SHORT) { 60 | return pool.mallocInt16(n) 61 | } else if(type === gl.UNSIGNED_INT) { 62 | return pool.mallocUint32(n) 63 | } else if(type === gl.INT) { 64 | return pool.mallocInt32(n) 65 | } 66 | return pool.mallocFloat32(n) 67 | } 68 | 69 | function packAttributes(gl, numVertices, attributes) { 70 | var attrNames = [] 71 | var attrVals = [] 72 | for(var name in attributes) { 73 | var attr = attributes[name] 74 | var buffer, type, normalized, size 75 | if(typeof attr.length === "number") { 76 | if(attr.length !== numVertices) { 77 | throw new Error("Incorrect vertex count for attribute " + name) 78 | } 79 | if(typeof attr[0] === "number") { 80 | var gltype = getGLType(attr) 81 | if(gltype) { 82 | //Case: typed array 83 | size = 1 84 | type = gltype 85 | normalized = (gltype === gl.BYTE || gltype === gl.UNSIGNED_BYTE) 86 | buffer = createBuffer(gl, attr) 87 | } else { 88 | //Case: native array of numbers 89 | var tmp_buf = pool.mallocFloat32(numVertices) 90 | for(var i=0; i 1 && stride[1] !== 1)) { 131 | packed = false 132 | } 133 | //Check if array has to be normalized 134 | normalized = (type === gl.BYTE || type === gl.UNSIGNED_BYTE) 135 | size = attr.shape[1] 136 | if(packed) { 137 | //Case: packed ndarray, directly blit into buffer 138 | if(attr.offset === 0 && attr.data.length === size*numVertices) { 139 | buffer = createBuffer(gl, attr.data) 140 | } else { 141 | buffer = createBuffer(gl, attr.data.subarray(0, size*numVertices)) 142 | } 143 | } else { 144 | //Case: unpacked ndarray, create new array and blit 145 | var tmp_buf = createTmpArray(gl, type, size*numVertices) 146 | var tmp = ndarray(tmp_buf, attr.shape) 147 | ops.assign(tmp, attr) 148 | buffer = createBuffer(gl, tmp.data.subarray(0, size*numVertices)) 149 | pool.free(tmp_buf) 150 | } 151 | } else { 152 | throw new Error("Invalid vertex attribute " + name) 153 | } 154 | attrNames.push(name) 155 | attrVals.push(new MeshAttribute(buffer, size, type, normalized)) 156 | } 157 | return { 158 | names: attrNames, 159 | values: attrVals 160 | } 161 | } 162 | 163 | function packAttributesFromNDArray(gl, numVertices, attributes, elements) { 164 | var n = elements.shape[0]|0 165 | var d = elements.shape[1]|0 166 | var numElements = (n*d)|0 167 | var attrNames = [], attrVals = [] 168 | for(var name in attributes) { 169 | var attr = attributes[name] 170 | var type, size, normalized, buffer 171 | if(typeof attr.length === "number") { 172 | if(attr.length !== numVertices) { 173 | throw new Error("Invalid attribute size for attribute " + name) 174 | } 175 | if(typeof attr[0] === "number") { 176 | //Case: 1D array attribute 177 | size = 1 178 | type = getGLType(gl, attr) 179 | var pack_buf 180 | if(!type) { 181 | type = gl.FLOAT 182 | } 183 | normalized = (type === gl.BYTE || gl.UNSIGNED_BYTE) 184 | pack_buf = createTmpArray(gl, type, numElements) 185 | var ptr = 0 186 | for(var i=0; i 4) { 197 | throw new Error("Invalid attribute size for attribute " + name) 198 | } 199 | type = gl.FLOAT 200 | normalized = false 201 | var pack_buf = pool.mallocFloat32(numElements * size) 202 | var ptr = 0 203 | for(var i=0; i 4) { 240 | throw new Error("Invalid attribute size for attribute " + name) 241 | } 242 | type = getGLType(gl, array.data) 243 | if(!type) { 244 | type = gl.FLOAT 245 | } 246 | normalized = (type === gl.BYTE || gl.UNSIGNED_BYTE) 247 | var pack_buf = createTmpArray(gl, type, numElements * size) 248 | var ptr = 0 249 | for(var i=0; i coerce to ndarray 397 | if(typeof elements[0] === "number") { 398 | //Special case: point cloud 399 | element_count = elements.length|0 400 | var point_buf = pool.mallocUint16(element_count) 401 | for(var i=0; i check packing: 425 | if(elements.stride[1] === 1 && 426 | elements.stride[0] === elements.shape[1] && 427 | elements.data instanceof Uint16Array) { 428 | // 1b.1 array is packed => use directly 429 | if(elements.offset === 0 && 430 | elements.data.length === element_count) { 431 | element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, elements.data) 432 | } else { 433 | element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, elements.data.subarray(elements.offset, elements.offset + element_count)) 434 | } 435 | } else { 436 | // 1b.2 array not packed => copy to packed array 437 | var packed_buf = pool.mallocUint16(element_count) 438 | var packed_view = ndarray(packed_buf, elements.shape) 439 | ops.assign(packed_view, elements) 440 | element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, packed_buf.subarray(0, element_count)) 441 | pool.freeUint16(packed_buf) 442 | } 443 | } 444 | 445 | //Finally we are done 446 | return buildMeshObject(gl, mode, element_count, element_buffer, packAttributes(gl, numVertices, attributes)) 447 | 448 | } else { 449 | //Otherwise we have to use gl.drawArrays, and so the mesh needs to be repacked 450 | if(elements instanceof Array) { 451 | if(typeof elements[0] === "number") { 452 | return buildMeshObject(gl, mode, elements.length, null, packAttributesFrom1DArray(gl, numVertices, attributes, elements)) 453 | } else { 454 | return buildMeshObject(gl, mode, elements.length * elements[0].length, null, packAttributesFromArray(gl, numVertices, attributes, elements)) 455 | } 456 | } else { 457 | return buildMeshObject(gl, mode, elements.shape[0] * elements.shape[1], null, packAttributesFromNDArray(gl, numVertices, attributes, elements)) 458 | } 459 | } 460 | throw new Error("Error building mesh object") 461 | } 462 | 463 | module.exports = createMesh --------------------------------------------------------------------------------