├── .gitignore ├── LICENSE ├── README.md ├── demo.js ├── example.js ├── gif ├── hexagon.gif ├── square.gif └── triangle.gif ├── index.html ├── index.js ├── package.json ├── shaders ├── demo.frag ├── demo.vert ├── example.frag └── example.vert ├── shapes ├── circle.js ├── heart.js ├── hexagon.js ├── square.js └── triangle.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | bundle.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jeremy Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # extrude 2 | 3 | Use extrusion to turn a 2d shape into a 3d mesh. Extrusion is the process of "pulling" a 2d shape through space to make it 3d. This module contains a single function that accepts a collection of 2d points, and returns a 3d mesh in the form of a [`simplicial complex`](https://github.com/mikolalysenko/simplicial-complex), a data structure that works well with the [`stack.gl`](http://stack.gl/) ecosystem. The implementation uses seidel's algorithm to triangulate the top and bottom faces, and simple triangulated rectangles for the sides. 4 | 5 | View a [demo](http://freeman-lab.github.io/extrude). 6 | 7 | 8 | ![hex](gif/triangle.gif)![hex](gif/square.gif)![hex](gif/hexagon.gif) 9 | 10 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 11 | 12 | 13 | ## install 14 | 15 | To use in your project 16 | 17 | ```javascript 18 | npm install extrude 19 | ``` 20 | 21 | To see an example, clone this repo, then call 22 | 23 | ```javascript 24 | npm install 25 | npm start 26 | ``` 27 | and it should open a browser with a floating square. You can also try 28 | 29 | ```javascript 30 | npm run demo 31 | ``` 32 | for a demo with several shapes. 33 | 34 | ## example 35 | 36 | Assuming you already have a stack.gl context `gl`, make a cube like this! 37 | 38 | ```javascript 39 | var extrude = require('extrude') 40 | 41 | var points = [[-1, -1], [1, -1], [1, 1], [-1, 1]] 42 | var cube = extrude(points, {bottom: -1, top: 1}) 43 | 44 | var geometry = require('gl-geometry')(gl) 45 | geometry.attr('position', cube.positions) 46 | geometry.faces(cube.cells) 47 | ``` 48 | 49 | See [`example.js`](example.js) for a complete end-to-end example. 50 | 51 | ## usage 52 | 53 | #### `complex = extrude(points, opts)` 54 | 55 | Create a simplicial complex from a set of points. 56 | 57 | `points` should be a list in the form `[[x, y], [x, y], ...]` 58 | 59 | `complex` has two attributes: 60 | - `complex.position` : array of 3d vertices `[[x, y, z], [x, y, z], ...]` 61 | - `complex.cells` : array of tuples that index into the vertices `[[i, j, k], [i, j, k], ...]` 62 | 63 | `opts` can include the following options: 64 | - `opts.bottom` : bottom of the extruded object `default: 0` 65 | - `opts.top` : top of the extruded object `default: 1` 66 | - `opts.closed` : whether to close the top and bottom of the mesh `default: true` 67 | 68 | If `top` and `bottom` are equal it will result in a single-sided 3d surface. 69 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | var Geometry = require('gl-geometry') 2 | var Shader = require('gl-shader') 3 | var context = require('gl-context') 4 | var mat4 = require('gl-mat4') 5 | var vignette = require('gl-vignette-background') 6 | var glslify = require('glslify') 7 | var orbit = require('canvas-orbit-camera') 8 | var fit = require('canvas-fit') 9 | var unindex = require('unindex-mesh') 10 | var reindex = require('mesh-reindex') 11 | var eye = require('eye-vector') 12 | var time = require('right-now') 13 | var normals = require('normals') 14 | var css = require('dom-css') 15 | var extrude = require('./index.js') 16 | var soundcloud = require('soundcloud-badge') 17 | var issafari = require('is-safari') 18 | var ismobile = require('is-mobile') 19 | var Analyser = require('web-audio-analyser') 20 | 21 | var canvas = document.body.appendChild(document.createElement('canvas')) 22 | css(canvas, {zIndex: -1000}) 23 | var gl = context(canvas, render) 24 | 25 | var message 26 | 27 | if (!ismobile() & issafari) { 28 | message = document.body.appendChild(document.createElement('div')) 29 | css(message, { 30 | position: 'absolute', left: '4%', top: '3%', 31 | color: 'white', fontFamily: 'GlacialIndifferenceRegular' 32 | }) 33 | message.innerHTML = 'for music view in Chrome or Firefox' 34 | } 35 | 36 | if (ismobile()) { 37 | message = document.body.appendChild(document.createElement('div')) 38 | css(message, { 39 | position: 'absolute', left: '4%', top: '3%', 40 | color: 'white', fontFamily: 'GlacialIndifferenceRegular', 41 | fontSize: 30 42 | }) 43 | message.innerHTML = 'for music view on Desktop' 44 | } 45 | 46 | if (!ismobile() & !issafari) { 47 | var audio = new Audio() 48 | audio.crossOrigin = 'Anonymous' 49 | var analyser = Analyser(audio) 50 | 51 | soundcloud({ 52 | client_id: 'cc4fb3b1e4b84004455321ad04a16580', 53 | song: 'https://soundcloud.com/constellation-records/cst025_track05', 54 | dark: false, 55 | getFonts: true 56 | }, function (err, src, data, div) { 57 | if (err) throw err 58 | audio.src = src 59 | audio.loop = true 60 | audio.addEventListener('canplay', function () { 61 | audio.play() 62 | }, false) 63 | }) 64 | } 65 | 66 | var camera = orbit(canvas) 67 | 68 | var shapes = ['triangle', 'square', 'hexagon', 'circle', 'heart'] 69 | var options = document.body.appendChild(document.createElement('div')) 70 | css(options, {position: 'absolute', right: '6%', top: '2%'}) 71 | 72 | var link = document.body.appendChild(document.createElement('div')) 73 | css(link, {position: 'absolute', right: '6%', bottom: '4.5%', textDecoration: 'none'}) 74 | link.innerHTML = 'github' 75 | link.addEventListener('click', function () { 76 | window.location.href = 'http://github.com/freeman-lab/extrude' 77 | }) 78 | var type = { 79 | fontFamily: 'GlacialIndifferenceRegular', 80 | borderBottom: 'solid 3px rgb(20,20,20)', 81 | borderLeft: 'solid 3px rgba(0, 0, 0, 0)', 82 | paddingBottom: 2, 83 | paddingLeft: 8, 84 | transition: '0.15s', 85 | width: '100%', 86 | cursor: 'pointer' 87 | } 88 | css(link, type) 89 | mouseover(link) 90 | mouseout(link) 91 | 92 | function mouseover (el) { 93 | el.addEventListener('mouseover', function () { 94 | css(el, {borderLeft: 'solid 3px rgb(20,20,20)'}) 95 | }) 96 | } 97 | 98 | function mouseout (el) { 99 | el.addEventListener('mouseout', function () { 100 | css(el, {borderLeft: 'solid 3px rgba(0, 0, 0, 0)'}) 101 | }) 102 | } 103 | 104 | var items = [] 105 | shapes.forEach(function (shape, i) { 106 | items[i] = options.appendChild(document.createElement('div')) 107 | items[i].innerHTML = shape 108 | css(items[i], type) 109 | mouseover(items[i]) 110 | mouseout(items[i]) 111 | items[i].addEventListener('click', function () { 112 | selection = i 113 | reload() 114 | }) 115 | }) 116 | 117 | function resize () { 118 | var w = Math.sqrt(window.innerWidth * 20) 119 | var s = Math.sqrt(window.innerWidth * 1.2) 120 | if (ismobile()) { 121 | w *= 1.4 122 | s *= 1.4 123 | } 124 | var m = window.innerWidth * 0.01 125 | css(options, {width: w}) 126 | css(link, {width: w, fontSize: s}) 127 | items.forEach(function (item) { 128 | css(item, {fontSize: s, marginBottom: m}) 129 | }) 130 | } 131 | 132 | resize() 133 | window.addEventListener('resize', resize, false) 134 | window.addEventListener('resize', fit(canvas), false) 135 | 136 | camera.lookAt([3, 3, 4], [0, 0, 0], [1, 0, 0]) 137 | 138 | var selection = 2 139 | var flattened, geometry, shape 140 | 141 | reload() 142 | 143 | function reload () { 144 | if (selection === 0) { 145 | shape = require('./shapes/triangle.js') 146 | } 147 | 148 | if (selection === 1) { 149 | shape = require('./shapes/square.js') 150 | } 151 | 152 | if (selection === 2) { 153 | shape = require('./shapes/hexagon.js') 154 | } 155 | 156 | if (selection === 3) { 157 | shape = require('./shapes/circle.js') 158 | } 159 | 160 | if (selection === 4) { 161 | shape = require('./shapes/heart.js') 162 | } 163 | 164 | var complex = extrude(shape.points, {top: 0.5, bottom: -0.5, closed: true}) 165 | 166 | geometry = Geometry(gl) 167 | flattened = unindex(complex.positions, complex.cells) 168 | complex = reindex(flattened) 169 | complex.normals = normals.vertexNormals(complex.cells, complex.positions) 170 | geometry.attr('position', complex.positions) 171 | geometry.attr('normal', complex.normals) 172 | geometry.faces(complex.cells) 173 | } 174 | 175 | var shader = Shader(gl, 176 | glslify('./shaders/demo.vert'), 177 | glslify('./shaders/demo.frag') 178 | ) 179 | 180 | var projection = mat4.create() 181 | var view = mat4.create() 182 | 183 | var background = vignette(gl) 184 | 185 | var rotate = 0.005 186 | var freq, scale 187 | 188 | function render () { 189 | var width = gl.drawingBufferWidth 190 | var height = gl.drawingBufferHeight 191 | 192 | var now = time() * 0.001 193 | var axis = Math.sin(now) * 2 194 | 195 | var aspect = width / height 196 | var fov = Math.PI / 4 197 | var near = 0.01 198 | var far = 1000 199 | mat4.perspective(projection, fov, aspect, near, far) 200 | 201 | if (!ismobile() & !issafari) { 202 | freq = analyser.frequencies().reduce(function (x, y) { return x + y }) 203 | rotate = freq / 1000000 204 | } 205 | 206 | camera.rotate([0, 0, 0], [axis * rotate, -rotate, 0]) 207 | 208 | camera.view(view) 209 | camera.tick() 210 | 211 | gl.viewport(0, 0, width, height) 212 | gl.clear(gl.COLOR_BUFFER_BIT) 213 | gl.disable(gl.DEPTH_TEST) 214 | 215 | if (ismobile()) { 216 | scale = [0.00038 * width, 0.00026 * height] 217 | } else { 218 | scale = [0.0007 * width, 0.0007 * height] 219 | } 220 | 221 | background.style({ 222 | scale: scale, 223 | smoothing: [-0.4, 0.6], 224 | aspect: aspect, 225 | color1: [0.95, 0.95, 0.95], 226 | color2: shape.colors[0], 227 | coloredNoise: false, 228 | noiseAlpha: 0.2, 229 | offset: [0, 0] 230 | }) 231 | 232 | background.draw() 233 | 234 | gl.enable(gl.DEPTH_TEST) 235 | 236 | geometry.bind(shader) 237 | shader.uniforms.projection = projection 238 | shader.uniforms.view = view 239 | shader.uniforms.eye = eye(view) 240 | shader.uniforms.color1 = shape.colors[0] 241 | shader.uniforms.color2 = shape.colors[1] 242 | geometry.draw(gl.TRIANGLES) 243 | geometry.unbind() 244 | } 245 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var Geometry = require('gl-geometry') 2 | var Shader = require('gl-shader') 3 | var context = require('gl-context') 4 | var mat4 = require('gl-mat4') 5 | var glslify = require('glslify') 6 | var orbit = require('canvas-orbit-camera') 7 | var fit = require('canvas-fit') 8 | var unindex = require('unindex-mesh') 9 | var reindex = require('mesh-reindex') 10 | var eye = require('eye-vector') 11 | var time = require('right-now') 12 | var normals = require('normals') 13 | var extrude = require('./index.js') 14 | 15 | var canvas = document.body.appendChild(document.createElement('canvas')) 16 | var camera = orbit(canvas) 17 | var gl = context(canvas, render) 18 | 19 | window.addEventListener('resize', fit(canvas), false) 20 | camera.lookAt([3, 3, 4], [0, 0, 0], [1, 0, 0]) 21 | 22 | var points = [[-1, -1], [1, -1], [1, 1], [-1, 1]] 23 | 24 | var complex = extrude(points, {top: 1, bottom: -1, closed: true}) 25 | 26 | var geometry = Geometry(gl) 27 | 28 | var flattened = unindex(complex.positions, complex.cells) 29 | complex = reindex(flattened) 30 | complex.normals = normals.vertexNormals(complex.cells, complex.positions) 31 | geometry.attr('position', complex.positions) 32 | geometry.attr('normal', complex.normals) 33 | geometry.faces(complex.cells) 34 | 35 | var shader = Shader(gl, 36 | glslify('./shaders/example.vert'), 37 | glslify('./shaders/example.frag') 38 | ) 39 | 40 | var projection = mat4.create() 41 | var view = mat4.create() 42 | 43 | function render () { 44 | var width = gl.drawingBufferWidth 45 | var height = gl.drawingBufferHeight 46 | 47 | var now = time() * 0.001 48 | var axis = Math.sin(now) * 2 49 | 50 | var aspect = width / height 51 | var fov = Math.PI / 4 52 | var near = 0.01 53 | var far = 1000 54 | mat4.perspective(projection, fov, aspect, near, far) 55 | 56 | camera.rotate([0, 0, 0], [axis * 0.005, -0.005, 0]) 57 | camera.view(view) 58 | camera.tick() 59 | 60 | gl.viewport(0, 0, width, height) 61 | gl.enable(gl.DEPTH_TEST) 62 | 63 | geometry.bind(shader) 64 | shader.uniforms.projection = projection 65 | shader.uniforms.view = view 66 | shader.uniforms.eye = eye(view) 67 | geometry.draw(gl.TRIANGLES) 68 | geometry.unbind() 69 | } 70 | -------------------------------------------------------------------------------- /gif/hexagon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-lab/extrude/4345ea1cb03b4c764e60b81b49cd74a282d27e8b/gif/hexagon.gif -------------------------------------------------------------------------------- /gif/square.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-lab/extrude/4345ea1cb03b4c764e60b81b49cd74a282d27e8b/gif/square.gif -------------------------------------------------------------------------------- /gif/triangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-lab/extrude/4345ea1cb03b4c764e60b81b49cd74a282d27e8b/gif/triangle.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | extrude 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var pnltri = require('pnltri') 2 | var defaults = require('lodash.defaults') 3 | 4 | module.exports = function (points, opts) { 5 | opts = opts || {} 6 | defaults(opts, {top: 1, bottom: 0, closed: true}) 7 | 8 | var n = points.length 9 | var positions 10 | var cells 11 | 12 | (opts.top === opts.bottom) ? flat() : full() 13 | 14 | function triangulate (points) { 15 | points = points.map(function (p) { return {x: p[0], y: p[1]} }) 16 | return new pnltri.Triangulator().triangulate_polygon([points]) 17 | } 18 | 19 | function flat () { 20 | positions = points.map(function (p) { return [p[0], p[1], opts.top] }) 21 | cells = triangulate(points) 22 | } 23 | 24 | function full () { 25 | positions = [] 26 | points.forEach(function (p) { positions.push([p[0], p[1], opts.top]) }) 27 | points.forEach(function (p) { positions.push([p[0], p[1], opts.bottom]) }) 28 | 29 | cells = [] 30 | for (var i = 0; i < n; i++) { 31 | if (i === (n - 1)) { 32 | cells.push([i + n, n, i]) 33 | cells.push([0, i, n]) 34 | } else { 35 | cells.push([i + n, i + n + 1, i]) 36 | cells.push([i + 1, i, i + n + 1]) 37 | } 38 | } 39 | 40 | if (opts.closed) { 41 | var top = triangulate(points) 42 | var bottom = top.map(function (p) { return p.map(function (v) { return v + n }) }) 43 | bottom = bottom.map(function (p) { return [p[0], p[2], p[1]] }) 44 | cells = cells.concat(top).concat(bottom) 45 | } 46 | } 47 | 48 | return { 49 | positions: positions, 50 | cells: cells 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extrude", 3 | "version": "1.0.2", 4 | "description": "turn a 2d shape into a 3d mesh with extrusion", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run tape & standard", 8 | "tape": "tape test.js | tap-spec", 9 | "start": "budo example.js:bundle.js --live --open", 10 | "demo": "budo demo.js:bundle.js --live --open", 11 | "build": "browserify demo.js -o bundle.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/freeman-lab/extrude.git" 16 | }, 17 | "keywords": [ 18 | "extrude", 19 | "extrusion", 20 | "2d", 21 | "3d", 22 | "geometry", 23 | "stackgl", 24 | "opengl", 25 | "graphics", 26 | "games" 27 | ], 28 | "author": "freeman-lab", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/freeman-lab/extrude/issues" 32 | }, 33 | "homepage": "https://github.com/freeman-lab/extrude#readme", 34 | "dependencies": { 35 | "lodash.defaults": "^4.0.0", 36 | "pnltri": "^2.1.1" 37 | }, 38 | "devDependencies": { 39 | "budo": "^7.1.0", 40 | "canvas-fit": "^1.5.0", 41 | "canvas-orbit-camera": "^1.0.2", 42 | "dom-css": "^2.0.0", 43 | "eye-vector": "0.0.0", 44 | "face-normals": "0.0.0", 45 | "gl-audio-analyser": "^1.0.0", 46 | "gl-context": "^0.1.1", 47 | "gl-geometry": "^2.0.0", 48 | "gl-mat3": "^1.0.0", 49 | "gl-mat4": "^1.1.4", 50 | "gl-shader": "^4.2.0", 51 | "gl-vignette-background": "^2.0.1", 52 | "glsl-diffuse-oren-nayar": "^1.0.2", 53 | "glsl-specular-gaussian": "^1.0.0", 54 | "glslify": "^5.0.2", 55 | "is-mobile": "^0.2.2", 56 | "is-safari": "^1.0.0", 57 | "lodash.range": "^3.1.0", 58 | "mesh-reindex": "^1.0.0", 59 | "normals": "^1.0.1", 60 | "right-now": "^1.0.0", 61 | "soundcloud-badge": "^1.0.0", 62 | "tap-spec": "^4.1.1", 63 | "tape": "^4.4.0", 64 | "test-allclose": "^1.0.0", 65 | "unindex-mesh": "^2.0.0", 66 | "web-audio-analyser": "^2.0.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /shaders/demo.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec3 vposition; 4 | varying vec3 vnormal; 5 | 6 | uniform vec3 color1; 7 | uniform vec3 color2; 8 | uniform vec3 eye; 9 | 10 | #pragma glslify: orenn = require('glsl-diffuse-oren-nayar') 11 | 12 | void main() { 13 | vec3 viewdiff = eye - vposition; 14 | vec3 lightdir1 = (eye - vposition) + vec3(-2.0, 0.0, 1.0); 15 | vec3 lightdir2 = (eye - vposition) + vec3(1.0, 0.0, 1.0); 16 | 17 | float diff1 = orenn(normalize(lightdir1), normalize(viewdiff), vnormal, 0.9, 1.0); 18 | float diff2 = orenn(normalize(lightdir2), normalize(viewdiff), vnormal, 0.9, 1.0); 19 | 20 | vec3 lcol1 = color1; 21 | vec3 lcol2 = color2; 22 | 23 | vec3 material = vec3(1.0, 1.0, 1.0); 24 | 25 | vec3 result = lcol1 * vec3(material * diff1) + lcol2 * vec3(material * diff2); 26 | gl_FragColor = vec4(result, 1.0); 27 | } -------------------------------------------------------------------------------- /shaders/demo.vert: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | attribute vec3 position; 4 | attribute vec3 normal; 5 | 6 | varying vec3 vnormal; 7 | varying vec3 vposition; 8 | 9 | uniform mat4 projection; 10 | uniform mat4 view; 11 | 12 | void main() { 13 | vposition = position; 14 | vnormal = normalize(normal); 15 | gl_Position = projection * view * vec4(position, 1.0); 16 | } -------------------------------------------------------------------------------- /shaders/example.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec3 vposition; 4 | varying vec3 vnormal; 5 | 6 | uniform vec3 eye; 7 | 8 | #pragma glslify: orenn = require('glsl-diffuse-oren-nayar') 9 | 10 | void main() { 11 | vec3 viewdiff = eye - vposition; 12 | vec3 lightdir1 = (eye - vposition) + vec3(-2.0, 0.0, 1.0); 13 | vec3 lightdir2 = (eye - vposition) + vec3(1.0, 0.0, 1.0); 14 | 15 | float diff1 = orenn(normalize(lightdir1), normalize(viewdiff), vnormal, 0.9, 1.0); 16 | float diff2 = orenn(normalize(lightdir2), normalize(viewdiff), vnormal, 0.9, 1.0); 17 | 18 | vec3 lcol1 = vec3(0.4, 1.0, 0.6); 19 | vec3 lcol2 = vec3(0.6, 0.9, 0.2); 20 | 21 | vec3 material = vec3(1.0, 1.0, 1.0); 22 | 23 | vec3 result = lcol1 * vec3(material * diff1) + lcol2 * vec3(material * diff2); 24 | gl_FragColor = vec4(result, 1.0); 25 | } -------------------------------------------------------------------------------- /shaders/example.vert: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | attribute vec3 position; 4 | attribute vec3 normal; 5 | 6 | varying vec3 vnormal; 7 | varying vec3 vposition; 8 | 9 | uniform mat4 projection; 10 | uniform mat4 view; 11 | 12 | void main() { 13 | vposition = position; 14 | vnormal = normalize(normal); 15 | gl_Position = projection * view * vec4(position, 1.0); 16 | } -------------------------------------------------------------------------------- /shapes/circle.js: -------------------------------------------------------------------------------- 1 | var range = require('lodash.range') 2 | var line = range(0, Math.PI * 2, Math.PI * 2 / 30) 3 | 4 | var points = line.map(function (t) { 5 | var x = Math.sin(t) 6 | var y = Math.cos(t) 7 | return [x, y] 8 | }) 9 | 10 | module.exports = { 11 | points: points.reverse(), 12 | colors: [[0.9, 0.8, 0.1], [0.8, 0.6, 0.2]] 13 | } 14 | -------------------------------------------------------------------------------- /shapes/heart.js: -------------------------------------------------------------------------------- 1 | var range = require('lodash.range') 2 | var line = range(0, Math.PI * 2, Math.PI * 2 / 50) 3 | 4 | var points = line.map(function (t) { 5 | var x = 16 * Math.pow(Math.sin(t), 3) 6 | var y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t) 7 | return [x / 13, y / 13] 8 | }) 9 | 10 | module.exports = { 11 | points: points.reverse(), 12 | colors: [[0.9, 0.3, 0.1], [1.0, 0.2, 0.3]] 13 | } 14 | -------------------------------------------------------------------------------- /shapes/hexagon.js: -------------------------------------------------------------------------------- 1 | var points = [] 2 | 3 | for (var i = 0; i < 6; i++) { 4 | points.push([ 5 | Math.cos(i * 2 * Math.PI / 6), 6 | Math.sin(i * 2 * Math.PI / 6) 7 | ]) 8 | } 9 | 10 | module.exports = { 11 | points: points, 12 | colors: [[0.5, 0.9, 0.1], [0.3, 1.0, 0.5]] 13 | } 14 | -------------------------------------------------------------------------------- /shapes/square.js: -------------------------------------------------------------------------------- 1 | var d = 0.75 2 | var points = [[-d, -d], [d, -d], [d, d], [-d, d]] 3 | 4 | module.exports = { 5 | points: points, 6 | colors: [[0.7, 0.2, 0.8], [0.6, 0.4, 0.9]] 7 | } 8 | -------------------------------------------------------------------------------- /shapes/triangle.js: -------------------------------------------------------------------------------- 1 | var d = 1 2 | var points = [[-d, -0.4 * d * Math.sqrt(3)], [d, -0.4 * d * Math.sqrt(3)], [0, 0.6 * d * Math.sqrt(3)]] 3 | 4 | module.exports = { 5 | points: points, 6 | colors: [[0.1, 0.6, 0.9], [0.2, 0.5, 0.7]] 7 | } 8 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var allclose = require('test-allclose') 3 | var extrude = require('./index.js') 4 | 5 | test('cube: cells', function (t) { 6 | var opts = {top: 1, bottom: -1, closed: true} 7 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]], opts) 8 | var truth = [ 9 | [4, 5, 0], 10 | [1, 0, 5], 11 | [5, 6, 1], 12 | [2, 1, 6], 13 | [6, 7, 2], 14 | [3, 2, 7], 15 | [7, 4, 3], 16 | [0, 3, 4], 17 | [3, 0, 1], 18 | [3, 1, 2], 19 | [7, 5, 4], 20 | [7, 6, 5] 21 | ] 22 | allclose(t)(complex.cells, truth) 23 | t.end() 24 | }) 25 | 26 | test('cube: cells (open)', function (t) { 27 | var opts = {top: 1, bottom: -1, closed: false} 28 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]], opts) 29 | var truth = [ 30 | [4, 5, 0], 31 | [1, 0, 5], 32 | [5, 6, 1], 33 | [2, 1, 6], 34 | [6, 7, 2], 35 | [3, 2, 7], 36 | [7, 4, 3], 37 | [0, 3, 4] 38 | ] 39 | allclose(t)(complex.cells, truth) 40 | t.end() 41 | }) 42 | 43 | test('cube: positions', function (t) { 44 | var opts = {top: 1, bottom: -1} 45 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]], opts) 46 | var truth = [ 47 | [-1, -1, 1], 48 | [1, -1, 1], 49 | [1, 1, 1], 50 | [-1, 1, 1], 51 | [-1, -1, -1], 52 | [1, -1, -1], 53 | [1, 1, -1], 54 | [-1, 1, -1] 55 | ] 56 | allclose(t)(complex.positions, truth) 57 | t.end() 58 | }) 59 | 60 | test('cube: positions (defaults)', function (t) { 61 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]]) 62 | var truth = [ 63 | [-1, -1, 1], 64 | [1, -1, 1], 65 | [1, 1, 1], 66 | [-1, 1, 1], 67 | [-1, -1, 0], 68 | [1, -1, 0], 69 | [1, 1, 0], 70 | [-1, 1, 0] 71 | ] 72 | allclose(t)(complex.positions, truth) 73 | t.end() 74 | }) 75 | 76 | test('cube: positions (flat)', function (t) { 77 | var opts = {top: 0, bottom: 0} 78 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]], opts) 79 | var truth = [ 80 | [-1, -1, 0], 81 | [1, -1, 0], 82 | [1, 1, 0], 83 | [-1, 1, 0] 84 | ] 85 | allclose(t)(complex.positions, truth) 86 | t.end() 87 | }) 88 | 89 | test('cube: cells (flat)', function (t) { 90 | var opts = {top: 0, bottom: 0} 91 | var complex = extrude([[-1, -1], [1, -1], [1, 1], [-1, 1]], opts) 92 | var truth = [ 93 | [3, 0, 1], 94 | [3, 1, 2] 95 | ] 96 | allclose(t)(complex.cells, truth) 97 | t.end() 98 | }) 99 | --------------------------------------------------------------------------------