├── .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 |
--------------------------------------------------------------------------------