├── .gitignore ├── .npmignore ├── index.html ├── demo ├── three-primitive.js └── index.js ├── LICENSE.md ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | three-buffer-vertex-data 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/three-primitive.js: -------------------------------------------------------------------------------- 1 | var buffer = require('../') 2 | 3 | module.exports = setPrimitive 4 | function setPrimitive (geometry, data) { 5 | if (!data.cells || !data.positions) { 6 | throw new TypeError('expected at least { cells, positions }') 7 | } 8 | 9 | buffer.index(geometry, data.cells) 10 | buffer.attr(geometry, 'position', data.positions, 3) 11 | 12 | if (data.uvs) buffer.attr(geometry, 'uv', data.uvs, 2) 13 | else geometry.removeAttribute('uv') 14 | 15 | if (data.normals) buffer.attr(geometry, 'normal', data.normals, 3) 16 | else geometry.removeAttribute('normal') 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Jam3 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three') 2 | 3 | // some mesh primitives from npm 4 | var createSphere = require('primitive-sphere') 5 | var createTorus = require('primitive-torus') 6 | var createQuad = require('primitive-quad') 7 | 8 | // utility function to set a BufferGeometry to a primitive 9 | var setToPrimitive = require('./three-primitive') 10 | var baboon = require('baboon-image-uri') 11 | var loadImage = require('img') 12 | 13 | var createOrbitViewer = require('three-orbit-viewer')(THREE) 14 | var app = createOrbitViewer({ 15 | clearColor: 0x000000, 16 | clearAlpha: 1.0, 17 | fov: 65, 18 | position: new THREE.Vector3(1, 1, -2) 19 | }) 20 | 21 | var primitives = [ 22 | createQuad(), createSphere(), createTorus() 23 | ] 24 | 25 | var tick = 0 26 | var geometry = new THREE.BufferGeometry() 27 | var material = new THREE.MeshBasicMaterial({ 28 | map: new THREE.Texture(), 29 | side: THREE.DoubleSide 30 | }) 31 | var mesh = new THREE.Mesh(geometry, material) 32 | app.scene.add(mesh) 33 | 34 | // test mesh primitive UVs 35 | loadImage(baboon, function (err, image) { 36 | if (err) throw err 37 | var tex = material.map 38 | tex.image = image 39 | tex.wrapS = tex.wrapT = THREE.RepeatWrapping 40 | tex.flipY = false 41 | tex.needsUpdate = true 42 | tex.repeat.set(5, 5) 43 | }) 44 | 45 | next() 46 | setInterval(next, 1000) 47 | 48 | function next () { 49 | var newMesh = primitives[tick++ % primitives.length] 50 | setToPrimitive(geometry, newMesh) 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-buffer-vertex-data", 3 | "version": "1.1.0", 4 | "description": "an easy way to set vertex data on a BufferGeometry", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "dependencies": { 13 | "flatten-vertex-data": "^1.0.0" 14 | }, 15 | "devDependencies": { 16 | "baboon-image-uri": "^1.0.1", 17 | "browserify": "^12.0.1", 18 | "budo": "^7.0.0", 19 | "img": "^1.0.0", 20 | "primitive-quad": "^1.0.1", 21 | "primitive-sphere": "^2.0.0", 22 | "primitive-torus": "^1.0.4", 23 | "three": "^0.73.0", 24 | "three-orbit-viewer": "^69.3.1", 25 | "uglify-js": "^2.6.1" 26 | }, 27 | "scripts": { 28 | "test": "node test.js", 29 | "start": "budo demo/index.js:bundle.js --live", 30 | "build": "browserify demo/index.js | uglifyjs -cm > bundle.js" 31 | }, 32 | "standard": { 33 | "globals": [ 34 | "THREE" 35 | ] 36 | }, 37 | "keywords": [ 38 | "buffer", 39 | "geometry", 40 | "BufferGeometry", 41 | "three", 42 | "webgl", 43 | "js", 44 | "threejs", 45 | "three.js", 46 | "data", 47 | "vertex", 48 | "vert", 49 | "vertices", 50 | "dynamic", 51 | "bufferdata" 52 | ], 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/Jam3/three-buffer-vertex-data.git" 56 | }, 57 | "homepage": "https://github.com/Jam3/three-buffer-vertex-data", 58 | "bugs": { 59 | "url": "https://github.com/Jam3/three-buffer-vertex-data/issues" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-buffer-vertex-data 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | [(demo)](http://jam3.github.io/three-buffer-vertex-data/) - [(source)](./demo/index.js) 6 | 7 | Convenience functions for sending vertex data to an attribtue of a [THREE.BufferGeometry](http://threejs.org/docs/#Reference/Core/BufferGeometry). 8 | 9 | This will flatten vertex data if necessary, attempt to re-use arrays where possible to minimize GC, and handle some compatibility issues across different versions of ThreeJS. It can handle dynamically growing and shrinking buffers. 10 | 11 | The input is expected in simple arrays, and can be one of: 12 | 13 | - a nested array like `[ [ x, y ], [ x, y ] ]` 14 | - a flat array like `[ x, y, z, x, y, z ]` 15 | - a typed array like `new Float32Array([ x, y ])` 16 | 17 | This is particularly useful in new versions of ThreeJS, where BufferGeometry is required for custom shader attributes. 18 | 19 | ## Install 20 | 21 | ```sh 22 | npm install three-buffer-vertex-data --save 23 | ``` 24 | 25 | ## Example 26 | 27 | A simple example using [snowden](https://github.com/stackgl/snowden). 28 | 29 | ```js 30 | var buffer = require('three-buffer-vertex-data') 31 | 32 | // grab a simplicial complex 33 | var snowden = require('snowden') 34 | 35 | // set up our geometry 36 | var geometry = new THREE.BufferGeometry() 37 | buffer.index(geometry, snowden.cells) 38 | buffer.attr(geometry, 'position', snowden.positions) 39 | 40 | // add to scene 41 | var material = new THREE.MeshBasicMaterial() 42 | var mesh = new THREE.Mesh(geometry, material) 43 | scene.add(mesh) 44 | ``` 45 | 46 | The `'position'`, `'uv'` and `'normal'` attributes are built-in to ThreeJS and will work with common materials. Result: 47 | 48 | 49 | 50 | See [demo](./demo/index.js) for a more complex example, which cycles various [mesh primitives](https://github.com/glo-js/mesh-primitives) into the same vertex buffers. 51 | 52 | ## Usage 53 | 54 | [![NPM](https://nodei.co/npm/three-buffer-vertex-data.png)](https://www.npmjs.com/package/three-buffer-vertex-data) 55 | 56 | The `data` passed is sent through [flatten-vertex-data](https://github.com/glo-js/flatten-vertex-data), and can be in the form of: 57 | 58 | - a nested array like `[ [ x, y ], [ x, y ] ]` 59 | - a flat array like `[ x, y, z, x, y, z ]` 60 | - a typed array like `new Float32Array([ x, y ])` 61 | 62 | If the attribute exists, its underlying `array` will try to be re-used. Otherwise, a new typed array will be created from the optional [dtype](https://www.npmjs.com/package/dtype). 63 | 64 | #### `buffer.index(geometry, data, [itemSize], [dtype])` 65 | 66 | Sets the `index` attribute for the given buffer `geometry`, with your list of indices in `data`. The `itemSize` defaults to 1, and `dtype` string defaults to `'uint16'` 67 | 68 | #### `buffer.attr(geometry, key, data, [itemSize], [dtype])` 69 | 70 | Sets a generic attribute on the given buffer `geometry` by the given `key` and vertex `data`. The `itemSize` defaults to 3, and `dtype` string defaults to `'float32'`. 71 | 72 | ## Gotchas 73 | 74 | In r73, ThreeJS breaks when using `wireframe` and attempting to dynamically grow/shrink and add/remove attributes. 75 | 76 | Also, ThreeJS typically expects indices and `positions` to exist. Things may break without these attributes set. 77 | 78 | You will need THREE r82 or newer if you wish to dynamically grow and shrink vertex buffers. 79 | 80 | ## License 81 | 82 | MIT, see [LICENSE.md](http://github.com/Jam3/three-buffer-vertex-data/blob/master/LICENSE.md) for details. 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var flatten = require('flatten-vertex-data') 2 | var warned = false; 3 | 4 | module.exports.attr = setAttribute 5 | module.exports.index = setIndex 6 | 7 | function setIndex (geometry, data, itemSize, dtype) { 8 | if (typeof itemSize !== 'number') itemSize = 1 9 | if (typeof dtype !== 'string') dtype = 'uint16' 10 | 11 | var isR69 = !geometry.index && typeof geometry.setIndex !== 'function' 12 | var attrib = isR69 ? geometry.getAttribute('index') : geometry.index 13 | var newAttrib = updateAttribute(attrib, data, itemSize, dtype) 14 | if (newAttrib) { 15 | if (isR69) geometry.addAttribute('index', newAttrib) 16 | else geometry.index = newAttrib 17 | } 18 | } 19 | 20 | function setAttribute (geometry, key, data, itemSize, dtype) { 21 | if (typeof itemSize !== 'number') itemSize = 3 22 | if (typeof dtype !== 'string') dtype = 'float32' 23 | if (Array.isArray(data) && 24 | Array.isArray(data[0]) && 25 | data[0].length !== itemSize) { 26 | throw new Error('Nested vertex array has unexpected size; expected ' + 27 | itemSize + ' but found ' + data[0].length) 28 | } 29 | 30 | var attrib = geometry.getAttribute(key) 31 | var newAttrib = updateAttribute(attrib, data, itemSize, dtype) 32 | if (newAttrib) { 33 | geometry.addAttribute(key, newAttrib) 34 | } 35 | } 36 | 37 | function updateAttribute (attrib, data, itemSize, dtype) { 38 | data = data || [] 39 | if (!attrib || rebuildAttribute(attrib, data, itemSize)) { 40 | // create a new array with desired type 41 | data = flatten(data, dtype) 42 | 43 | var needsNewBuffer = attrib && typeof attrib.setArray !== 'function' 44 | if (!attrib || needsNewBuffer) { 45 | // We are on an old version of ThreeJS which can't 46 | // support growing / shrinking buffers, so we need 47 | // to build a new buffer 48 | if (needsNewBuffer && !warned) { 49 | warned = true 50 | console.warn([ 51 | 'A WebGL buffer is being updated with a new size or itemSize, ', 52 | 'however this version of ThreeJS only supports fixed-size buffers.', 53 | '\nThe old buffer may still be kept in memory.\n', 54 | 'To avoid memory leaks, it is recommended that you dispose ', 55 | 'your geometries and create new ones, or update to ThreeJS r82 or newer.\n', 56 | 'See here for discussion:\n', 57 | 'https://github.com/mrdoob/three.js/pull/9631' 58 | ].join('')) 59 | } 60 | 61 | // Build a new attribute 62 | attrib = new THREE.BufferAttribute(data, itemSize); 63 | } 64 | 65 | attrib.itemSize = itemSize 66 | attrib.needsUpdate = true 67 | 68 | // New versions of ThreeJS suggest using setArray 69 | // to change the data. It will use bufferData internally, 70 | // so you can change the array size without any issues 71 | if (typeof attrib.setArray === 'function') { 72 | attrib.setArray(data) 73 | } 74 | 75 | return attrib 76 | } else { 77 | // copy data into the existing array 78 | flatten(data, attrib.array) 79 | attrib.needsUpdate = true 80 | return null 81 | } 82 | } 83 | 84 | // Test whether the attribute needs to be re-created, 85 | // returns false if we can re-use it as-is. 86 | function rebuildAttribute (attrib, data, itemSize) { 87 | if (attrib.itemSize !== itemSize) return true 88 | if (!attrib.array) return true 89 | var attribLength = attrib.array.length 90 | if (Array.isArray(data) && Array.isArray(data[0])) { 91 | // [ [ x, y, z ] ] 92 | return attribLength !== data.length * itemSize 93 | } else { 94 | // [ x, y, z ] 95 | return attribLength !== data.length 96 | } 97 | return false 98 | } 99 | --------------------------------------------------------------------------------