├── .editorconfig ├── .gitignore ├── .nojekyll ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── examples └── index.js ├── index.html ├── index.js ├── package-lock.json ├── package.json └── screenshot.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | types 4 | lib 5 | web_modules 6 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vorg/geom-revolve/270f13645a167605a72ddeaf7e8aa787409e050e/.nojekyll -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | web_modules 2 | examples 3 | docs 4 | coverage 5 | test 6 | .github 7 | screenshot.* 8 | index.html 9 | tsconfig.json 10 | .editorconfig 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. 4 | 5 | ## [2.0.2](https://github.com/vorg/geom-revolve/compare/v2.0.1...v2.0.2) (2024-03-14) 6 | 7 | 8 | 9 | ## [2.0.1](https://github.com/vorg/geom-revolve/compare/v2.0.0...v2.0.1) (2024-03-14) 10 | 11 | 12 | 13 | # [2.0.0](https://github.com/vorg/geom-revolve/compare/v1.0.4...v2.0.0) (2024-03-14) 14 | 15 | 16 | ### Features 17 | 18 | * add support for typed arrays ([51e1081](https://github.com/vorg/geom-revolve/commit/51e10816c0a65b3888dd34dfe3b55c50423b1694)) 19 | * use typed-array-constructor for cells ([fa2253b](https://github.com/vorg/geom-revolve/commit/fa2253b336afe4b0455eb21df4ffdc369cc854a1)) 20 | 21 | 22 | ### BREAKING CHANGES 23 | 24 | * use ES modules 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Marcin Ignac 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geom-revolve 2 | 3 | Create a simplicial complex geometry by revolving a path around Y axis. 4 | 5 | ![](screenshot.jpg) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install geom-revolve 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | import revolve from "geom-revolve"; 17 | 18 | const path = new Float32Array([ 19 | 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.2, 0.2, 0.0, 0.3, 0.5, 0.0, 0.3, 0.7, 0.0, 20 | 0.1, 0.8, 0.0, 0.1, 1.0, 0.0, 0.0, 1.0, 0.0, 21 | ]); 22 | 23 | const geometry = revolve(path); 24 | // => { positions: Float32Array(384), cells: Uint32Array(672) } 25 | ``` 26 | 27 | ## API 28 | 29 | #### `revolve(path, [numSteps], [angle]): geometry` 30 | 31 | **Parameters** 32 | 33 | - path: `TypedArray | Array | Array<[x, y, z]>` - positions defining the path to revolve. 34 | - numSteps: `number` (default: `16`) - rotation subdivisions. 35 | - angle: `number` (default: `Math.PI * 2`) - angle to rotate by. 36 | 37 | **Returns** 38 | 39 | geometry: `{ positions: TypedArray | Array<[x, y, z]>, cells: TypedArray | Array<[x, y, z]> }` - the revolved geometry. 40 | 41 | Returned cells will be either Array of Arrays or TypedArray depending on the path data. 42 | 43 | ## License 44 | 45 | MIT, see [LICENSE.md](http://github.com/vorg/geom-revolve/blob/master/LICENSE.md) for details. 46 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import revolve from "../index.js"; 2 | import createContext from "pex-context"; 3 | import createGUI from "pex-gui"; 4 | import { mat4 } from "pex-math"; 5 | // import computeEdges from "geom-edges"; 6 | 7 | const State = { 8 | angle: Math.PI * 2, 9 | steps: 16, 10 | pause: false, 11 | }; 12 | 13 | const W = 1280; 14 | const H = 720; 15 | const ctx = createContext({ 16 | width: W, 17 | height: H, 18 | element: document.querySelector("main"), 19 | pixelRatio: devicePixelRatio, 20 | }); 21 | 22 | const projectionMatrix = mat4.create(); 23 | const viewMatrix = mat4.create(); 24 | const modelMatrix = mat4.create(); 25 | mat4.perspective(projectionMatrix, Math.PI / 4, W / H, 0.1, 100); 26 | mat4.lookAt(viewMatrix, [0, 0.5, 2], [0, 0.5, 0]); 27 | 28 | // vase profile 29 | // _ 30 | // | 31 | // \ 32 | // | 33 | // / 34 | // _/ 35 | // 36 | 37 | // prettier-ignore 38 | const path = [ 39 | [0.0, 0.0, 0.0], 40 | [0.1, 0.0, 0.0], 41 | [0.2, 0.2, 0.0], 42 | [0.3, 0.5, 0.0], 43 | [0.3, 0.7, 0.0], 44 | [0.1, 0.8, 0.0], 45 | [0.1, 1.0, 0.0], 46 | [0.0, 1.0, 0.0], 47 | ].flat(); 48 | 49 | const cmdOptions = { 50 | attributes: { 51 | aPosition: ctx.vertexBuffer([]), 52 | }, 53 | indices: ctx.indexBuffer([]), 54 | uniforms: { 55 | uProjectionMatrix: projectionMatrix, 56 | uViewMatrix: viewMatrix, 57 | uModelMatrix: modelMatrix, 58 | }, 59 | }; 60 | 61 | const wireframeCmdOptions = { 62 | indices: ctx.indexBuffer([]), 63 | }; 64 | 65 | function computeEdges(cells, stride = 3) { 66 | const edges = new Uint32Array(cells.length * 2); 67 | 68 | let cellIndex = 0; 69 | 70 | for (let i = 0; i < cells.length; i += stride) { 71 | for (let j = 0; j < stride; j++) { 72 | const a = cells[i + j]; 73 | const b = cells[i + ((j + 1) % stride)]; 74 | edges[cellIndex] = Math.min(a, b); 75 | edges[cellIndex + 1] = Math.max(a, b); 76 | cellIndex += 2; 77 | } 78 | } 79 | return edges; 80 | } 81 | 82 | const update = () => { 83 | const g = revolve(path, State.steps, State.angle); 84 | g.edges = computeEdges(g.cells); 85 | console.log(g); 86 | 87 | ctx.update(cmdOptions.attributes.aPosition, { 88 | data: g.positions, 89 | }); 90 | ctx.update(cmdOptions.indices, { 91 | data: g.cells, 92 | }); 93 | ctx.update(wireframeCmdOptions.indices, { 94 | data: g.edges, 95 | }); 96 | }; 97 | update(); 98 | 99 | const clearCmd = { 100 | pass: ctx.pass({ 101 | clearColor: [0.2, 0.2, 0.2, 1], 102 | clearDepth: 1, 103 | }), 104 | }; 105 | 106 | const basicVert = /* glsl */ `#version 300 es 107 | uniform mat4 uProjectionMatrix; 108 | uniform mat4 uViewMatrix; 109 | uniform mat4 uModelMatrix; 110 | 111 | in vec3 aPosition; 112 | 113 | out vec4 vColor; 114 | out vec3 vPositionWorld; 115 | 116 | void main () { 117 | vColor = vec4(1.0, 0.0, 0.0, 1.0); 118 | 119 | vPositionWorld = (uModelMatrix * vec4(aPosition, 1.0)).xyz; 120 | 121 | gl_Position = uProjectionMatrix * uViewMatrix * vec4(vPositionWorld, 1.0); 122 | }`; 123 | 124 | const drawMeshCmd = { 125 | pipeline: ctx.pipeline({ 126 | vert: basicVert, 127 | frag: /* glsl */ `#version 300 es 128 | precision highp float; 129 | 130 | in vec4 vColor; 131 | in vec3 vPositionWorld; 132 | out vec4 fragColor; 133 | 134 | void main() { 135 | vec3 fdx = vec3(dFdx(vPositionWorld.x), dFdx(vPositionWorld.y), dFdx(vPositionWorld.z)); 136 | vec3 fdy = vec3(dFdy(vPositionWorld.x), dFdy(vPositionWorld.y), dFdy(vPositionWorld.z)); 137 | vec3 normal = normalize(cross(fdx, fdy)); 138 | fragColor = vec4(normal * 0.5 + 0.5, 1.0); 139 | } 140 | `, 141 | depthTest: true, 142 | }), 143 | }; 144 | 145 | const drawMeshWireframeCmd = { 146 | pipeline: ctx.pipeline({ 147 | vert: basicVert, 148 | frag: /* glsl */ `#version 300 es 149 | precision highp float; 150 | 151 | out vec4 fragColor; 152 | 153 | void main() { 154 | fragColor = vec4(1.0, 1.0, 1.0, 1.0); 155 | } 156 | `, 157 | depthTest: true, 158 | primitive: ctx.Primitive.Lines, 159 | }), 160 | }; 161 | 162 | const gui = createGUI(ctx); 163 | 164 | gui.addParam("Angle", State, "angle", { min: 0, max: Math.PI * 2 }, update); 165 | gui.addParam("Steps", State, "steps", { min: 0, max: 64, step: 1 }, update); 166 | gui.addParam("Pause", State, "pause"); 167 | 168 | let dt = 0; 169 | 170 | ctx.frame(() => { 171 | if (!State.pause) { 172 | dt += 0.005; 173 | mat4.rotate(modelMatrix, dt % 0.02, [0, 1, 0]); 174 | mat4.lookAt(viewMatrix, [0, 0.5 + Math.sin(dt) * 2, 2], [0, 0.5, 0]); 175 | } 176 | 177 | ctx.submit(clearCmd); 178 | ctx.submit(drawMeshCmd, cmdOptions); 179 | ctx.submit(drawMeshWireframeCmd, { ...cmdOptions, ...wireframeCmdOptions }); 180 | 181 | gui.draw(); 182 | }); 183 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | geom-revolve by vorg (https://github.com/vorg) 8 | 27 | 28 | 29 |
30 |

geom-revolve

31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { avec3 } from "pex-math"; 2 | import typedArrayConstructor from "typed-array-constructor"; 3 | 4 | function revolve(path, numSteps = 16, angle = 2 * Math.PI) { 5 | const isFlatArray = !path[0]?.length; 6 | 7 | let loop = false; 8 | if (Math.abs(angle - 2 * Math.PI) < Number.EPSILON) { 9 | angle = 2 * Math.PI; 10 | loop = true; 11 | } 12 | 13 | const numPoints = path.length / (isFlatArray ? 3 : 1); 14 | const positionCount = numSteps * numPoints; 15 | const cellCount = 2 * (numSteps - (loop ? 0 : 1)) * (numPoints - 1); 16 | 17 | const positions = isFlatArray ? new path.constructor(positionCount * 3) : []; 18 | const cells = isFlatArray 19 | ? new (typedArrayConstructor(positionCount))(cellCount * 3) 20 | : []; 21 | 22 | const n = loop ? numSteps : numSteps > 1 ? numSteps - 1 : 1; 23 | 24 | for (let i = 0; i < numSteps; i++) { 25 | const t = (angle * i) / n; 26 | const sina = Math.sin(t); 27 | const cosa = Math.cos(t); 28 | 29 | if (isFlatArray) { 30 | for (let j = 0; j < numPoints; j++) { 31 | const x = path[j * 3]; 32 | const y = path[j * 3 + 1]; 33 | 34 | avec3.set3(positions, i * numPoints + j, cosa * x, y, sina * x); 35 | 36 | if (j === numPoints - 1) continue; 37 | 38 | const a = i * numPoints + j; 39 | const k = 2 * i * (numPoints - 1) + j * 2; 40 | 41 | if (loop) { 42 | if (i < numSteps) { 43 | const b = ((i + 1) % numSteps) * numPoints + j; 44 | const c = b + 1; 45 | avec3.set3(cells, k, a, b, c); 46 | avec3.set3(cells, k + 1, a, c, a + 1); 47 | } 48 | } else { 49 | if (i < numSteps - 1) { 50 | const b = (i + 1) * numPoints + j; 51 | const c = b + 1; 52 | 53 | avec3.set3(cells, k, a, b, c); 54 | avec3.set3(cells, k + 1, a, c, a + 1); 55 | } 56 | } 57 | } 58 | } else { 59 | for (let j = 0; j < numPoints; j++) { 60 | const [x, y] = path[j]; 61 | 62 | positions.push([cosa * x, y, sina * x]); 63 | 64 | if (j === numPoints - 1) continue; 65 | 66 | const a = i * numPoints + j; 67 | 68 | if (loop) { 69 | if (i < numSteps) { 70 | const b = ((i + 1) % numSteps) * numPoints + j; 71 | const c = b + 1; 72 | 73 | cells.push([a, b, c]); 74 | cells.push([a, c, a + 1]); 75 | } 76 | } else { 77 | if (i < numSteps - 1) { 78 | const b = (i + 1) * numPoints + j; 79 | const c = b + 1; 80 | 81 | cells.push([a, b, c]); 82 | cells.push([a, c, a + 1]); 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | return { positions, cells }; 90 | } 91 | 92 | export default revolve; 93 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geom-revolve", 3 | "version": "2.0.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "geom-revolve", 9 | "version": "2.0.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "pex-geom": "^3.0.1", 13 | "pex-math": "^4.1.0", 14 | "typed-array-constructor": "^1.0.1" 15 | }, 16 | "devDependencies": { 17 | "angle-normals": "^1.0.0", 18 | "es-module-shims": "^1.8.3", 19 | "geom-edges": "^1.2.0", 20 | "pex-context": "^3.0.0-alpha.8", 21 | "pex-gui": "^3.0.0" 22 | }, 23 | "engines": { 24 | "node": ">=20.0.0", 25 | "npm": ">=9.6.4" 26 | } 27 | }, 28 | "node_modules/angle-normals": { 29 | "version": "1.0.0", 30 | "resolved": "https://registry.npmjs.org/angle-normals/-/angle-normals-1.0.0.tgz", 31 | "integrity": "sha512-QF3h4vmrAmlFHF8Lwzgdz6Z/Yb5a2/klWxpqUpRCbFgXKe2d7Ey5PRMi4gS73PP+SYjZa+S46HtjuHlHv4w6BA==", 32 | "dev": true 33 | }, 34 | "node_modules/clone": { 35 | "version": "1.0.4", 36 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 37 | "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", 38 | "dev": true, 39 | "engines": { 40 | "node": ">=0.8" 41 | } 42 | }, 43 | "node_modules/es-module-shims": { 44 | "version": "1.8.3", 45 | "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.8.3.tgz", 46 | "integrity": "sha512-NOE2WomYkoXeae8FcwPwZApxDwKerEWGOiqNo/P2JCBkJgKCLA5+PMZs8sr/1SPk9a8E+hUE9Wc+YZyGEiWHkw==", 47 | "dev": true 48 | }, 49 | "node_modules/geom-edges": { 50 | "version": "1.2.0", 51 | "resolved": "https://registry.npmjs.org/geom-edges/-/geom-edges-1.2.0.tgz", 52 | "integrity": "sha512-aBwoYLldrbvXPjlqWUISYy5Ac4NpN5CvprD6M9TIJuJK+HK1Spriu1iInDZY9Cg5L4tEKhug8dQ0fjvy7oF85w==", 53 | "dev": true, 54 | "dependencies": { 55 | "clone": "^1.0.2" 56 | } 57 | }, 58 | "node_modules/pex-color": { 59 | "version": "2.1.0", 60 | "resolved": "https://registry.npmjs.org/pex-color/-/pex-color-2.1.0.tgz", 61 | "integrity": "sha512-N/AyN1sHmXBI4Gss9+EIEBcJB4D0j3El5qrNDJANKsJYU1ZOfiCk38rQ9XSCOfZWboBDKqLd6arNvRrytKtgkw==", 62 | "dev": true, 63 | "engines": { 64 | "node": ">=20.0.0", 65 | "npm": ">=9.6.4" 66 | } 67 | }, 68 | "node_modules/pex-context": { 69 | "version": "3.0.0-alpha.8", 70 | "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-3.0.0-alpha.8.tgz", 71 | "integrity": "sha512-8ub8SX0mskdcsnvV2Hz1FGTvLyIto4UY20re/taGMdfalt/obqa/qeVNLF2F8WshBOaDdHOmSFtMYdrHuDfC9Q==", 72 | "dev": true, 73 | "dependencies": { 74 | "pex-gl": "^3.0.0-alpha.0" 75 | }, 76 | "engines": { 77 | "node": ">=16.0.0", 78 | "npm": ">=7.0.0" 79 | } 80 | }, 81 | "node_modules/pex-geom": { 82 | "version": "3.0.1", 83 | "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-3.0.1.tgz", 84 | "integrity": "sha512-5wDQnn103lyuisUIuJgDt+ZonvCcIbcfAG5wDr+hJcohSphJlPvtLc1LkneSqt/XESIetKkDaVw3bW+talYPXw==", 85 | "dependencies": { 86 | "pex-math": "^4.0.0" 87 | }, 88 | "engines": { 89 | "node": ">=20.0.0", 90 | "npm": ">=9.6.4" 91 | } 92 | }, 93 | "node_modules/pex-gl": { 94 | "version": "3.0.1", 95 | "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.1.tgz", 96 | "integrity": "sha512-RSbWPhWSjBLc+9tkfMeTR+G9rg9ESXHRyK7t2Qa3Nuupo4SDPiVw0RuZkgDfTpu4RiDZV6nBdGof7J/XIqTP2Q==", 97 | "dev": true, 98 | "engines": { 99 | "node": ">=18.0.0", 100 | "npm": ">=8.6.0" 101 | } 102 | }, 103 | "node_modules/pex-gui": { 104 | "version": "3.0.0", 105 | "resolved": "https://registry.npmjs.org/pex-gui/-/pex-gui-3.0.0.tgz", 106 | "integrity": "sha512-YY4vDFk7FhyCoOHI6gf2R9lB4QaM3DMVsTOY0l8sqtDTr46t96Iwl4YKUyi2r3OMEi6hMXVBxkGzvGe+XeoP0w==", 107 | "dev": true, 108 | "dependencies": { 109 | "pex-color": "^2.1.0", 110 | "pex-geom": "^3.0.1", 111 | "pex-math": "^4.0.0" 112 | }, 113 | "engines": { 114 | "node": ">=16.0.0", 115 | "npm": ">=7.0.0" 116 | } 117 | }, 118 | "node_modules/pex-math": { 119 | "version": "4.1.0", 120 | "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-4.1.0.tgz", 121 | "integrity": "sha512-rpb92xRnGan4qOh0cXk8kD4GXXvt6ntQykkegjSU186pJ6uGJ/UJJ3amt9SQenmiEQHU2SXKvZImyzo1RTX61w==", 122 | "engines": { 123 | "node": ">=20.0.0", 124 | "npm": ">=9.6.4" 125 | } 126 | }, 127 | "node_modules/typed-array-constructor": { 128 | "version": "1.0.1", 129 | "resolved": "https://registry.npmjs.org/typed-array-constructor/-/typed-array-constructor-1.0.1.tgz", 130 | "integrity": "sha512-u0C7NyHOh4nPdFy5BsjEQuDXsuCg8PvinJ7b35IXdQ/dcOrwavzMh3YYo0Gj2Oq+69nNDOlxwKcVAV++x+m/8A==", 131 | "funding": [ 132 | { 133 | "type": "individual", 134 | "url": "https://paypal.me/dmnsgn" 135 | }, 136 | { 137 | "type": "individual", 138 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3" 139 | } 140 | ], 141 | "engines": { 142 | "node": ">=18.0.0", 143 | "npm": ">=8.0.0" 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geom-revolve", 3 | "version": "2.0.2", 4 | "description": "Create a simplicial complex geometry by revolving a path around Y axis.", 5 | "keywords": [ 6 | "geom", 7 | "revolve", 8 | "simplicial", 9 | "complex", 10 | "3d", 11 | "pex", 12 | "geometry", 13 | "webgl" 14 | ], 15 | "homepage": "https://github.com/vorg/geom-revolve", 16 | "bugs": "https://github.com/vorg/geom-revolve/issues", 17 | "repository": "vorg/geom-revolve", 18 | "license": "MIT", 19 | "author": "Marcin Ignac (https://github.com/vorg)", 20 | "contributors": [ 21 | "Damien Seguin (https://github.com/dmnsgn)" 22 | ], 23 | "sideEffects": false, 24 | "type": "module", 25 | "exports": "./index.js", 26 | "main": "index.js", 27 | "types": "types/index.d.ts", 28 | "scripts": { 29 | "dev": "snowdev dev", 30 | "release": "snowdev release" 31 | }, 32 | "dependencies": { 33 | "pex-geom": "^3.0.1", 34 | "pex-math": "^4.1.0", 35 | "typed-array-constructor": "^1.0.1" 36 | }, 37 | "devDependencies": { 38 | "angle-normals": "^1.0.0", 39 | "es-module-shims": "^1.8.3", 40 | "geom-edges": "^1.2.0", 41 | "pex-context": "^3.0.0-alpha.8", 42 | "pex-gui": "^3.0.0" 43 | }, 44 | "engines": { 45 | "node": ">=20.0.0", 46 | "npm": ">=9.6.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vorg/geom-revolve/270f13645a167605a72ddeaf7e8aa787409e050e/screenshot.jpg --------------------------------------------------------------------------------