UNPKG

gl-mesh

Version:

Static indexed mesh drawing for WebGL

463 lines (441 loc) 15.5 kB
"use strict" var webglew = require("webglew") var createBuffer = require("gl-buffer") var createVAO = require("gl-vao") var ndarray = require("ndarray") var ops = require("ndarray-ops") var pool = require("typedarray-pool") var Mesh = require("./lib/mesh.js") function EmptyMesh(gl) { this.gl = gl this.mode = gl.POINTS this.numElements = 0 this.elements = null this.attributes = {} } EmptyMesh.prototype.dispose = function() {} EmptyMesh.prototype.bind = function() {} EmptyMesh.prototype.unbind = function() {} EmptyMesh.prototype.draw = function() {} function MeshAttribute(buffer, size, type, normalized) { this.buffer = buffer this.size = size this.type = type this.normalized = normalized } function getGLType(gl, array) { if((array instanceof Uint8Array) || (array instanceof Uint8ClampedArray)) { return gl.UNSIGNED_BYTE } else if(array instanceof Int8Array) { return gl.BYTE } else if(array instanceof Uint16Array) { return gl.UNSIGNED_SHORT } else if(array instanceof Int16Array) { return gl.SHORT } else if(array instanceof Uint32Array) { return gl.UNSIGNED_INT } else if(array instanceof Int32Array) { return gl.INT } else if(array instanceof Float32Array) { return gl.FLOAT } return 0 } function createTmpArray(gl, type, n) { if(type === gl.UNSIGNED_BYTE) { return pool.mallocUint8(n) } else if(type === gl.BYTE) { return pool.mallocInt8(n) } else if(type === gl.UNSIGNED_SHORT) { return pool.mallocUint16(n) } else if(type === gl.SHORT) { return pool.mallocInt16(n) } else if(type === gl.UNSIGNED_INT) { return pool.mallocUint32(n) } else if(type === gl.INT) { return pool.mallocInt32(n) } return pool.mallocFloat32(n) } function packAttributes(gl, numVertices, attributes) { var attrNames = [] var attrVals = [] for(var name in attributes) { var attr = attributes[name] var buffer, type, normalized, size if(typeof attr.length === "number") { if(attr.length !== numVertices) { throw new Error("Incorrect vertex count for attribute " + name) } if(typeof attr[0] === "number") { var gltype = getGLType(attr) if(gltype) { //Case: typed array size = 1 type = gltype normalized = (gltype === gl.BYTE || gltype === gl.UNSIGNED_BYTE) buffer = createBuffer(gl, attr) } else { //Case: native array of numbers var tmp_buf = pool.mallocFloat32(numVertices) for(var i=0; i<numVertices; ++i) { tmp_buf[i] = attr[i] } size = 1 type = gl.FLOAT normalized = false buffer = createBuffer(gl, tmp_buf.subarray(0, numVertices)) pool.freeUint32(tmp_buf) } } else if(attr[0].length) { //Case: native array of arrays size = attr[0].length|0 var ptr = 0 var tmp_buf = pool.mallocFloat32(size * numVertices) for(var i=0; i<numVertices; ++i) { var vert = attr[i] for(var j=0; j<size; ++j) { tmp_buf[ptr++] = vert[j] } } type = gl.FLOAT normalized = false buffer = createBuffer(gl, tmp_buf.subarray(0, ptr)) pool.freeFloat32(tmp_buf) } } else if(attr.shape) { if(attr.shape[0] !== numVertices) { throw new Error("Invalid shape for attribute " + name) } //Convert 1D array to 2D if(attr.shape.length === 1) { attr = ndarray(attr.data, [attr.shape[0], 1], [attr.stride[0], 1], attr.offset) } //Check if type is compatible var packed = true type = getGLType(gl, attr.data) if(!type) { packed = false type = gl.FLOAT } if(stride[0] !== 1 || (shape[1] > 1 && stride[1] !== 1)) { packed = false } //Check if array has to be normalized normalized = (type === gl.BYTE || type === gl.UNSIGNED_BYTE) size = attr.shape[1] if(packed) { //Case: packed ndarray, directly blit into buffer if(attr.offset === 0 && attr.data.length === size*numVertices) { buffer = createBuffer(gl, attr.data) } else { buffer = createBuffer(gl, attr.data.subarray(0, size*numVertices)) } } else { //Case: unpacked ndarray, create new array and blit var tmp_buf = createTmpArray(gl, type, size*numVertices) var tmp = ndarray(tmp_buf, attr.shape) ops.assign(tmp, attr) buffer = createBuffer(gl, tmp.data.subarray(0, size*numVertices)) pool.free(tmp_buf) } } else { throw new Error("Invalid vertex attribute " + name) } attrNames.push(name) attrVals.push(new MeshAttribute(buffer, size, type, normalized)) } return { names: attrNames, values: attrVals } } function packAttributesFromNDArray(gl, numVertices, attributes, elements) { var n = elements.shape[0]|0 var d = elements.shape[1]|0 var numElements = (n*d)|0 var attrNames = [], attrVals = [] for(var name in attributes) { var attr = attributes[name] var type, size, normalized, buffer if(typeof attr.length === "number") { if(attr.length !== numVertices) { throw new Error("Invalid attribute size for attribute " + name) } if(typeof attr[0] === "number") { //Case: 1D array attribute size = 1 type = getGLType(gl, attr) var pack_buf if(!type) { type = gl.FLOAT } normalized = (type === gl.BYTE || gl.UNSIGNED_BYTE) pack_buf = createTmpArray(gl, type, numElements) var ptr = 0 for(var i=0; i<n; ++i) { for(var j=0; j<d; ++j) { pack_buf[ptr++] = attr[elements.get(i,j)] } } buffer = createBuffer(gl, pack_buf.subarray(0, ptr)) pool.free(pack_buf) } else { //Case: Array-of-arrays attribute size = attr[0].length|0 if(size < 1 || size > 4) { throw new Error("Invalid attribute size for attribute " + name) } type = gl.FLOAT normalized = false var pack_buf = pool.mallocFloat32(numElements * size) var ptr = 0 for(var i=0; i<n; ++i) { for(var j=0; j<d; ++j) { var vert = attr[elements.get(i,j)] for(var k=0; k<size; ++k) { pack_buf[ptr++] = vert[k] } } } buffer = createBuffer(gl, pack_buf.subarray(0, numElements*size)) pool.freeFloat32(pack_buf) } } else if(attr.shape) { //Case: ndarray attribute if(attr.shape[0] !== numVertices) { throw new Error("Invalid number of vertices for attribute " + name) } if(attr.shape.length === 1) { //Case: 1D ndarray size = 1 type = getGLType(gl, array.data) if(!type) { type = gl.FLOAT } normalized = (type === gl.BYTE || type === gl.UNSIGNED_BYTE) var pack_buf = createTmpArray(gl, type, numElements) var ptr = 0 for(var i=0; i<n; ++i) { for(var j=0; j<d; ++j) { pack_buf[ptr++] = attr.get(elements.get(i,j)) } } buffer = createBuffer(gl, pack_buf.subarray(0, ptr)) pool.free(pack_buf) } else if(attr.shape.length === 2) { //Case: 2D ndarray size = attr.shape[1]|0 if(size < 1 || size > 4) { throw new Error("Invalid attribute size for attribute " + name) } type = getGLType(gl, array.data) if(!type) { type = gl.FLOAT } normalized = (type === gl.BYTE || gl.UNSIGNED_BYTE) var pack_buf = createTmpArray(gl, type, numElements * size) var ptr = 0 for(var i=0; i<n; ++i) { for(var j=0; j<d; ++j) { var vert_num = elements.get(i,j) for(var k=0; k<size; ++k) { pack_buf[ptr++] = attr.get(vert_num, k) } } } buffer = createBuffer(gl, pack_buf.subarray(0, ptr)) pool.free(pack_buf) } else { throw new Error("Invalid attribute " + name + ", shape is too big") } } else { throw new Error("Invalid attribute " + name + ", unrecognized type") } attrNames.push(name) attrVals.push(new MeshAttribute(buffer, size, type, normalized)) } return { names: attrNames, values: attrVals } } function packAttributesFrom1DArray(gl, numVertices, attributes, elements) { var n = elements.length|0 var buf = pool.mallocUint32(n) for(var i=0; i<n; ++i) { buf[i] = elements[i] } var result = packAttributesFromNDArray(gl, numVertices, attributes, ndarray(buf, [n, 1])) pool.freeUint32(buf) return result } function packAttributesFromArray(gl, numVertices, attributes, elements) { var n = elements.length|0 var d = elements[0].length|0 var ptr = 0 var buf = pool.mallocUint32(n*d) for(var i=0; i<n; ++i) { var list = elements[i] for(var j=0; j<d; ++j) { buf[ptr++] = list[j] } } var result = packAttributesFromNDArray(gl, numVertices, attributes, ndarray(buf, [n, d])) pool.freeUint32(buf) return result } //Builds the actual mesh object, selecting appropriate implementation depending on if vertex attribute objects are supported function buildMeshObject(gl, mode, numElements, elements, attributes) { if(numElements === 0) { return new EmptyMesh(gl) } var vao = createVAO(gl, elements, attributes.values) return new Mesh(gl, mode, numElements, vao, elements, attributes.values, attributes.names) } //Creates a mesh function createMesh(gl, elements, attributes) { //Special case: handle object with cells/positions/etc. for attributes if(arguments.length === 2) { if(elements.cells) { attributes = {} for(var id in elements) { if(id === "cells") { continue } attributes[id] = elements[id] } elements = elements.cells } } //First figure out what mode we are using (POINTS, LINES or TRIANGLES) var mode if(attributes === undefined) { //Special case for point clouds attributes = elements elements = undefined mode = gl.POINTS } else if(elements instanceof Array) { if(elements.length === 0) { //Empty array, return empty mesh return buildMeshObject(gl, gl.POINTS, 0, null, []) } else if(typeof elements[0] === "number" || elements[0].length === 1) { mode = gl.POINTS } else if(elements[0].length === 2) { mode = gl.LINES } else if(elements[0].length === 3) { mode = gl.TRIANGLES } else { throw new Error("Invalid shape for element array") } } else if(elements.shape) { if(elements.shape.length === 1) { mode = gl.POINTS //Special case: convert to 2d element array for point cloud automatically to simplify things later on elements = ndarray(elements.data, [elements.shape[0], 1], [elements.stride[0], 1], elements.offset) } else if(elements.shape.length !== 2) { throw new Error("Invalid shape for element array") } else if(elements.shape[1] === 1) { mode = gl.POINTS } else if(elements.shape[1] === 2) { mode = gl.LINES } else if(elements.shape[1] === 3) { mode = gl.TRIANGLES } else { throw new Error("Invalid shape for elment array") } //Special case for empty meshes if(elements.shape[0] === 0) { return buildMeshObject(gl, mode, 0, null, []) } } else { throw new Error("Invalid data type for element array") } //Next figure out how many vertices we are using var attr_names = Object.keys(attributes) if(attr_names.length < 1) { throw new Error("Invalid number of vertex attributes") } var numVertices, attr0 = attributes[attr_names[0]] if(attr0.length) { numVertices = attr0.length } else if(attr0.shape) { numVertices = attr0.shape[0] } else { throw new Error("Invalid vertex attribute: " + attr_names[0]) } //We now have three basic cases: if(elements === undefined) { //First special case is a point cloud: this is easy return buildMeshObject(gl, gl.POINTS, numVertices, null, packAttributes(gl, numVertices, attributes)) } else if((numVertices <= 1<<16) && mode !== gl.POINTS) { //Indices fit in 16-bit unsigned int, so we can use gl.drawElements //First thing is to repackage element buffer var element_buffer, element_count if(elements instanceof Array) { // 1a. elements is array => coerce to ndarray if(typeof elements[0] === "number") { //Special case: point cloud element_count = elements.length|0 var point_buf = pool.mallocUint16(element_count) for(var i=0; i<element_count; ++i) { point_buf[i] = elements[i] } element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, point_buf.subarray(0, element_count)) pool.freeUint16(point_buf) } else { //Otherwise we pack data into a uint16 array var n = elements.length|0 var d = elements[0].length|0 var ptr = 0 element_count = (n*d)|0 var packed_buf = pool.mallocUint16(element_count) for(var i=0; i<n; ++i) { var prim = elements[i] for(var j=0; j<d; ++j) { packed_buf[ptr++] = prim[j] } } element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, packed_buf.subarray(0, element_count)) pool.freeUint16(packed_buf) } } else { element_count = elements.shape[0] * elements.shape[1] // 1b. elements is ndarray => check packing: if(elements.stride[1] === 1 && elements.stride[0] === elements.shape[1] && elements.data instanceof Uint16Array) { // 1b.1 array is packed => use directly if(elements.offset === 0 && elements.data.length === element_count) { element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, elements.data) } else { element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, elements.data.subarray(elements.offset, elements.offset + element_count)) } } else { // 1b.2 array not packed => copy to packed array var packed_buf = pool.mallocUint16(element_count) var packed_view = ndarray(packed_buf, elements.shape) ops.assign(packed_view, elements) element_buffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, packed_buf.subarray(0, element_count)) pool.freeUint16(packed_buf) } } //Finally we are done return buildMeshObject(gl, mode, element_count, element_buffer, packAttributes(gl, numVertices, attributes)) } else { //Otherwise we have to use gl.drawArrays, and so the mesh needs to be repacked if(elements instanceof Array) { if(typeof elements[0] === "number") { return buildMeshObject(gl, mode, elements.length, null, packAttributesFrom1DArray(gl, numVertices, attributes, elements)) } else { return buildMeshObject(gl, mode, elements.length * elements[0].length, null, packAttributesFromArray(gl, numVertices, attributes, elements)) } } else { return buildMeshObject(gl, mode, elements.shape[0] * elements.shape[1], null, packAttributesFromNDArray(gl, numVertices, attributes, elements)) } } throw new Error("Error building mesh object") } module.exports = createMesh