├── .gitignore ├── LICENSE ├── README.md ├── examples ├── basic │ └── index.html ├── index.html ├── main.js └── package.json ├── index.js ├── package.json └── shapeFromPathString.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | examples/build.js 3 | examples/node_modules/ 4 | .sw[ponm] 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 José Pedro Dias 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 | ## aframe extrude and lathe components 2 | 3 | Components for [A-Frame](https://aframe.io). 4 | Based on [aframe-component-boilerplate](https://github.com/ngokevin/aframe-component-boilerplate). 5 | 6 | This module offers lathe and extrude components. 7 | 8 | 9 | 10 | ### TODO 11 | 12 | * lathe 13 | * close ends option (for angles < 360) 14 | * choose lathe main axis 15 | * extrude 16 | * expose bevel options 17 | * extrude along path? 18 | * no tests yet 19 | * will probably split the repos in two once both components work properly 20 | 21 | 22 | ### Development 23 | 24 | npm install 25 | npm start 26 | cd examples 27 | npm install 28 | npm run build 29 | 30 | Visit [http://127.0.0.1:5566/examples/basic/index.html](http://127.0.0.1:5566/examples/basic/index.html) 31 | 32 | 33 | 34 | ### Usage 35 | 36 | Install. 37 | 38 | ```bash 39 | npm install aframe-extrude-and-lathe 40 | ``` 41 | 42 | Register. 43 | 44 | ```js 45 | var aframeCore = require('aframe-core'); 46 | var eAndL = require('aframe-extrude-and-lathe'); 47 | aframeCore.registerComponent('extrude', eAndL.extrudeComponent); 48 | aframeCore.registerComponent('lathe', eAndL.latheComponent); 49 | ``` 50 | 51 | Use. 52 | 53 | ```html 54 | 55 | 59 | 60 | 65 | 66 | ``` 67 | 68 | 69 | 70 | #### extrude 71 | 72 | | Property | Description | Default Value | 73 | | -------- | ----------- | ------------- | 74 | | path | define profile shape via syntax akin to [SVG path's d attribute](http://www.w3.org/TR/SVG/paths.html) | empty. must be defined | 75 | | amount | extension of extrusion | 1 | 76 | 77 | 78 | ### lathe 79 | 80 | | Property | Description | Default Value | 81 | | -------- | ----------- | ------------- | 82 | | path | define profile shape via syntax akin to [SVG path's d attribute](http://www.w3.org/TR/SVG/paths.html) | empty. must be defined | 83 | | startAngle | start angle for the revolution | 0 | 84 | | angle | revolution angle (0>angle>360) | 360 | 85 | | steps | number of steps along the angle | 16 | 86 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 24 | 25 | 30 | 35 | 40 | 41 | 42 | 46 | 50 | 54 | 55 | 56 | 60 | 61 | 66 | 67 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame Example Component 4 | 21 | 22 | 23 |

A-Frame Example Component

24 | Basic 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | var comps = require('../index.js'); 2 | 3 | require('aframe-core').registerComponent('lathe', comps.latheComponent); 4 | require('aframe-core').registerComponent('extrude', comps.extrudeComponent); 5 | 6 | setTimeout(function() { 7 | var els = document.querySelectorAll('a-entity'); 8 | window.els = els; 9 | 10 | var camEl = document.querySelector('[camera]'); 11 | window.camEl = camEl; 12 | 13 | var latheEl = document.querySelector('[lathe]'); 14 | window.latheEl = latheEl; 15 | 16 | var extrudeEl = document.querySelector('[extrude]'); 17 | window.extrudeEl = extrudeEl; 18 | 19 | // a.getAttribute('position') -> prop assigned to the el 20 | // a.getComputedAttribute('position') -> prop assigned to the el or inherited from default or other 21 | // a.setAttribute('position', '0 0 -10') 22 | }, 1000); 23 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "aframe-core": "*", 4 | "browserify": "^12.0.1", 5 | "watchify": "^3.6.1" 6 | }, 7 | "scripts": { 8 | "build": "browserify main.js -o build.js", 9 | "dev": "watchify main.js -o build.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var shapeFromPathString = require('./shapeFromPathString'); 2 | 3 | 4 | 5 | var DEG2RAD = Math.PI / 180; 6 | 7 | 8 | 9 | var getLathe = function(data) { 10 | // http://threejs.org/docs/#Reference/Extras.Geometries/LatheGeometry 11 | var shape = shapeFromPathString(data.path); 12 | var points = shape.getPoints(); 13 | //console.log(points); 14 | points = points.map(function(p) { 15 | return new THREE.Vector3(p.x, 0, p.y); // TODO: support different axes 16 | }); 17 | //console.log(points); 18 | 19 | return new THREE.LatheGeometry( 20 | points, // points 21 | Math.round(data.steps), // segments (round so it can be animated) 22 | data.startAngle * DEG2RAD, // phi start 23 | data.angle * DEG2RAD // phi length 24 | ); 25 | }; 26 | 27 | 28 | 29 | var getExtrude = function(data) { 30 | // http://threejs.org/docs/#Reference/Extras.Geometries/ExtrudeGeometry 31 | // http://threejs.org/examples/webgl_geometry_extrude_shapes2.html 32 | // http://stackoverflow.com/questions/25626171/threejs-extrudegeometry-depth-gives-different-result-than-extrudepath 33 | var shape = shapeFromPathString(data.path); 34 | console.log(shape); 35 | return new THREE.ExtrudeGeometry( 36 | shape, 37 | { 38 | amount : data.amount, 39 | steps : Math.round(data.steps), 40 | bevelEnabled : false 41 | } 42 | ); 43 | }; 44 | 45 | 46 | 47 | /** 48 | * Lathe component for A-Frame. 49 | * Use this to create revolutions 50 | */ 51 | module.exports.latheComponent = { 52 | schema: { 53 | path : { default:'m 0.1 -0.3 l 0.3 0.3 l -0.3 0.3' }, 54 | startAngle : { default: 0, min:-360, max:360 }, 55 | angle : { default:360, min:-360, max:360}, 56 | steps : { default: 16, min:1 } 57 | }, 58 | 59 | init: function() {}, 60 | 61 | update: function(oldData) { 62 | /*console.log('update', 63 | '\noldData', oldData, 64 | '\nnewData', this.data, 65 | '\nelement', this.el);*/ 66 | 67 | //if (!oldData) { 68 | var geo = getLathe(this.data); 69 | this.el.object3D.geometry = geo; 70 | //} 71 | }, 72 | 73 | remove: function() {} 74 | }; 75 | 76 | 77 | 78 | /** 79 | * Extrude component for A-Frame. 80 | * Use this to create extrusions 81 | */ 82 | module.exports.extrudeComponent = { 83 | schema: { 84 | path : { default:'m 1 1 l -2 0 l 0 -2 l 2 0 l 0 2' }, 85 | amount : { default:1, min:0 }, 86 | steps : { default:1, min:1 } 87 | }, 88 | 89 | init: function() {}, 90 | 91 | update: function(oldData) { 92 | /*console.log('update', 93 | '\noldData', oldData, 94 | '\nnewData', this.data, 95 | '\nelement', this.el);*/ 96 | 97 | //if (!oldData) { 98 | var geo = getExtrude(this.data); 99 | this.el.object3D.geometry = geo; 100 | //} 101 | }, 102 | 103 | remove: function() {} 104 | }; 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-extrude-and-lathe", 3 | "version": "0.1.0", 4 | "description": "extrude and lathe components for aframevr", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "http-server -c-1 -p 5566", 8 | "test": "karma start ./tests/karma.conf.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/JosePedroDias/aframe-example-component.git" 13 | }, 14 | "keywords": [ 15 | "aframe", 16 | "aframe-component", 17 | "aframe-vr", 18 | "vr", 19 | "mozvr", 20 | "webvr", 21 | "extrude", 22 | "lathe" 23 | ], 24 | "author": "José Pedro Dias ", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "aframe-core": "^0.1.3", 28 | "browserify-css": "^0.8.3", 29 | "chai": "^3.4.1", 30 | "chai-shallow-deep-equal": "^1.3.0", 31 | "karma": "^0.13.15", 32 | "karma-browserify": "^4.4.2", 33 | "karma-chai-shallow-deep-equal": "0.0.4", 34 | "karma-firefox-launcher": "^0.1.7", 35 | "karma-mocha": "^0.2.1", 36 | "karma-mocha-reporter": "^1.1.3", 37 | "karma-sinon-chai": "^1.1.0", 38 | "mocha": "^2.3.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /shapeFromPathString.js: -------------------------------------------------------------------------------- 1 | // adapted from d3-threeD.js 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | var DEGS_TO_RADS = Math.PI / 180, UNIT_SIZE = 100; 7 | 8 | var DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, MINUS = 45; 9 | 10 | var shapeFromPathString = function(pathStr) { 11 | if (pathStr[0] === '"' || pathStr[0] === "'") { 12 | pathStr = pathStr.substring(1, pathStr.length-1); // get rid of string delimiters 13 | } 14 | 15 | 16 | var path = new THREE.Shape(); 17 | 18 | var idx = 1, len = pathStr.length, activeCmd, 19 | x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null, 20 | x1 = 0, x2 = 0, y1 = 0, y2 = 0, 21 | rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy; 22 | 23 | function eatNum() { 24 | var sidx, c, isFloat = false, s; 25 | // eat delims 26 | while (idx < len) { 27 | c = pathStr.charCodeAt(idx); 28 | if (c !== COMMA && c !== SPACE) break; 29 | idx++; 30 | } 31 | if (c === MINUS) sidx = idx++; 32 | else sidx = idx; 33 | // eat number 34 | while (idx < len) { 35 | c = pathStr.charCodeAt(idx); 36 | if (DIGIT_0 <= c && c <= DIGIT_9) { 37 | idx++; 38 | continue; 39 | } 40 | else if (c === PERIOD) { 41 | idx++; 42 | isFloat = true; 43 | continue; 44 | } 45 | 46 | s = pathStr.substring(sidx, idx); 47 | return isFloat ? parseFloat(s) : parseInt(s); 48 | } 49 | 50 | s = pathStr.substring(sidx); 51 | return isFloat ? parseFloat(s) : parseInt(s); 52 | } 53 | 54 | function nextIsNum() { 55 | var c; 56 | // do permanently eat any delims... 57 | while (idx < len) { 58 | c = pathStr.charCodeAt(idx); 59 | if (c !== COMMA && c !== SPACE) break; 60 | idx++; 61 | } 62 | c = pathStr.charCodeAt(idx); 63 | return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9)); 64 | } 65 | 66 | var canRepeat; 67 | activeCmd = pathStr[0]; 68 | while (idx <= len) { 69 | canRepeat = true; 70 | switch (activeCmd) { 71 | // moveto commands, become lineto's if repeated 72 | case 'M': 73 | x = eatNum(); 74 | y = eatNum(); 75 | path.moveTo(x, y); 76 | activeCmd = 'L'; 77 | firstX = x; 78 | firstY = y; 79 | break; 80 | 81 | case 'm': 82 | x += eatNum(); 83 | y += eatNum(); 84 | path.moveTo(x, y); 85 | activeCmd = 'l'; 86 | firstX = x; 87 | firstY = y; 88 | break; 89 | 90 | case 'Z': 91 | case 'z': 92 | canRepeat = false; 93 | if (x !== firstX || y !== firstY) 94 | path.lineTo(firstX, firstY); 95 | break; 96 | 97 | // - lines! 98 | case 'L': 99 | case 'H': 100 | case 'V': 101 | nx = (activeCmd === 'V') ? x : eatNum(); 102 | ny = (activeCmd === 'H') ? y : eatNum(); 103 | path.lineTo(nx, ny); 104 | x = nx; 105 | y = ny; 106 | break; 107 | 108 | case 'l': 109 | case 'h': 110 | case 'v': 111 | nx = (activeCmd === 'v') ? x : (x + eatNum()); 112 | ny = (activeCmd === 'h') ? y : (y + eatNum()); 113 | path.lineTo(nx, ny); 114 | x = nx; 115 | y = ny; 116 | break; 117 | 118 | // - cubic bezier 119 | case 'C': 120 | x1 = eatNum(); y1 = eatNum(); 121 | 122 | case 'S': 123 | if (activeCmd === 'S') { 124 | x1 = 2 * x - x2; y1 = 2 * y - y2; 125 | } 126 | x2 = eatNum(); 127 | y2 = eatNum(); 128 | nx = eatNum(); 129 | ny = eatNum(); 130 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 131 | x = nx; y = ny; 132 | break; 133 | 134 | case 'c': 135 | x1 = x + eatNum(); 136 | y1 = y + eatNum(); 137 | 138 | case 's': 139 | if (activeCmd === 's') { 140 | x1 = 2 * x - x2; 141 | y1 = 2 * y - y2; 142 | } 143 | x2 = x + eatNum(); 144 | y2 = y + eatNum(); 145 | nx = x + eatNum(); 146 | ny = y + eatNum(); 147 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 148 | x = nx; y = ny; 149 | break; 150 | 151 | // - quadratic bezier 152 | case 'Q': 153 | x1 = eatNum(); y1 = eatNum(); 154 | 155 | case 'T': 156 | if (activeCmd === 'T') { 157 | x1 = 2 * x - x1; 158 | y1 = 2 * y - y1; 159 | } 160 | nx = eatNum(); 161 | ny = eatNum(); 162 | path.quadraticCurveTo(x1, y1, nx, ny); 163 | x = nx; 164 | y = ny; 165 | break; 166 | 167 | case 'q': 168 | x1 = x + eatNum(); 169 | y1 = y + eatNum(); 170 | 171 | case 't': 172 | if (activeCmd === 't') { 173 | x1 = 2 * x - x1; 174 | y1 = 2 * y - y1; 175 | } 176 | nx = x + eatNum(); 177 | ny = y + eatNum(); 178 | path.quadraticCurveTo(x1, y1, nx, ny); 179 | x = nx; y = ny; 180 | break; 181 | 182 | // - elliptical arc 183 | case 'A': 184 | rx = eatNum(); 185 | ry = eatNum(); 186 | xar = eatNum() * DEGS_TO_RADS; 187 | laf = eatNum(); 188 | sf = eatNum(); 189 | nx = eatNum(); 190 | ny = eatNum(); 191 | if (rx !== ry) { 192 | console.warn("Forcing elliptical arc to be a circular one :(", 193 | rx, ry); 194 | } 195 | 196 | // SVG implementation notes does all the math for us! woo! 197 | // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 198 | // step1, using x1 as x1' 199 | x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2; 200 | y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2; 201 | // step 2, using x2 as cx' 202 | var norm = Math.sqrt( 203 | (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) / 204 | (rx*rx * y1*y1 + ry*ry * x1*x1)); 205 | 206 | if (laf === sf) norm = -norm; 207 | x2 = norm * rx * y1 / ry; 208 | y2 = norm * -ry * x1 / rx; 209 | // step 3 210 | cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2; 211 | cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2; 212 | 213 | var u = new THREE.Vector2(1, 0), 214 | v = new THREE.Vector2((x1 - x2) / rx, 215 | (y1 - y2) / ry); 216 | var startAng = Math.acos(u.dot(v) / u.length() / v.length()); 217 | if (u.x * v.y - u.y * v.x < 0) startAng = -startAng; 218 | 219 | // we can reuse 'v' from start angle as our 'u' for delta angle 220 | u.x = (-x1 - x2) / rx; 221 | u.y = (-y1 - y2) / ry; 222 | 223 | var deltaAng = Math.acos(v.dot(u) / v.length() / u.length()); 224 | // This normalization ends up making our curves fail to triangulate... 225 | if (v.x * u.y - v.y * u.x < 0) deltaAng = -deltaAng; 226 | if (!sf && deltaAng > 0) deltaAng -= Math.PI * 2; 227 | if (sf && deltaAng < 0) deltaAng += Math.PI * 2; 228 | 229 | path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf); 230 | x = nx; 231 | y = ny; 232 | break; 233 | 234 | default: 235 | throw new Error("weird path command: " + activeCmd); 236 | } 237 | 238 | // just reissue the command 239 | if (canRepeat && nextIsNum()) continue; 240 | activeCmd = pathStr[idx++]; 241 | 242 | } 243 | 244 | return path; 245 | }; 246 | 247 | 248 | module.exports = shapeFromPathString; 249 | --------------------------------------------------------------------------------