1 | const CSG = require('./CSG')
|
2 | const {parseOption, parseOptionAs3DVector, parseOptionAs2DVector, parseOptionAs3DVectorList, parseOptionAsFloat, parseOptionAsInt} = require('./optionParsers')
|
3 | const {defaultResolution3D, defaultResolution2D, EPS} = require('./constants')
|
4 | const Vector3D = require('./math/Vector3')
|
5 | const Vertex = require('./math/Vertex3')
|
6 | const Polygon = require('./math/Polygon3')
|
7 | const {Connector} = require('./connectors')
|
8 | const Properties = require('./Properties')
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const cube = function (options) {
|
23 | let c
|
24 | let r
|
25 | let corner1
|
26 | let corner2
|
27 | options = options || {}
|
28 | if (('corner1' in options) || ('corner2' in options)) {
|
29 | if (('center' in options) || ('radius' in options)) {
|
30 | throw new Error('cube: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
31 | }
|
32 | corner1 = parseOptionAs3DVector(options, 'corner1', [0, 0, 0])
|
33 | corner2 = parseOptionAs3DVector(options, 'corner2', [1, 1, 1])
|
34 | c = corner1.plus(corner2).times(0.5)
|
35 | r = corner2.minus(corner1).times(0.5)
|
36 | } else {
|
37 | c = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
38 | r = parseOptionAs3DVector(options, 'radius', [1, 1, 1])
|
39 | }
|
40 | r = r.abs()
|
41 | let result = CSG.fromPolygons([
|
42 | [
|
43 | [0, 4, 6, 2],
|
44 | [-1, 0, 0]
|
45 | ],
|
46 | [
|
47 | [1, 3, 7, 5],
|
48 | [+1, 0, 0]
|
49 | ],
|
50 | [
|
51 | [0, 1, 5, 4],
|
52 | [0, -1, 0]
|
53 | ],
|
54 | [
|
55 | [2, 6, 7, 3],
|
56 | [0, +1, 0]
|
57 | ],
|
58 | [
|
59 | [0, 2, 3, 1],
|
60 | [0, 0, -1]
|
61 | ],
|
62 | [
|
63 | [4, 5, 7, 6],
|
64 | [0, 0, +1]
|
65 | ]
|
66 | ].map(function (info) {
|
67 | let vertices = info[0].map(function (i) {
|
68 | let pos = new Vector3D(
|
69 | c.x + r.x * (2 * !!(i & 1) - 1), c.y + r.y * (2 * !!(i & 2) - 1), c.z + r.z * (2 * !!(i & 4) - 1))
|
70 | return new Vertex(pos)
|
71 | })
|
72 | return new Polygon(vertices, null )
|
73 | }))
|
74 | result.properties.cube = new Properties()
|
75 | result.properties.cube.center = new Vector3D(c)
|
76 |
|
77 | result.properties.cube.facecenters = [
|
78 | new Connector(new Vector3D([r.x, 0, 0]).plus(c), [1, 0, 0], [0, 0, 1]),
|
79 | new Connector(new Vector3D([-r.x, 0, 0]).plus(c), [-1, 0, 0], [0, 0, 1]),
|
80 | new Connector(new Vector3D([0, r.y, 0]).plus(c), [0, 1, 0], [0, 0, 1]),
|
81 | new Connector(new Vector3D([0, -r.y, 0]).plus(c), [0, -1, 0], [0, 0, 1]),
|
82 | new Connector(new Vector3D([0, 0, r.z]).plus(c), [0, 0, 1], [1, 0, 0]),
|
83 | new Connector(new Vector3D([0, 0, -r.z]).plus(c), [0, 0, -1], [1, 0, 0])
|
84 | ]
|
85 | return result
|
86 | }
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | const sphere = function (options) {
|
105 | options = options || {}
|
106 | let center = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
107 | let radius = parseOptionAsFloat(options, 'radius', 1)
|
108 | let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
109 | let xvector, yvector, zvector
|
110 | if ('axes' in options) {
|
111 | xvector = options.axes[0].unit().times(radius)
|
112 | yvector = options.axes[1].unit().times(radius)
|
113 | zvector = options.axes[2].unit().times(radius)
|
114 | } else {
|
115 | xvector = new Vector3D([1, 0, 0]).times(radius)
|
116 | yvector = new Vector3D([0, -1, 0]).times(radius)
|
117 | zvector = new Vector3D([0, 0, 1]).times(radius)
|
118 | }
|
119 | if (resolution < 4) resolution = 4
|
120 | let qresolution = Math.round(resolution / 4)
|
121 | let prevcylinderpoint
|
122 | let polygons = []
|
123 | for (let slice1 = 0; slice1 <= resolution; slice1++) {
|
124 | let angle = Math.PI * 2.0 * slice1 / resolution
|
125 | let cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)))
|
126 | if (slice1 > 0) {
|
127 |
|
128 | let vertices = []
|
129 | let prevcospitch, prevsinpitch
|
130 | for (let slice2 = 0; slice2 <= qresolution; slice2++) {
|
131 | let pitch = 0.5 * Math.PI * slice2 / qresolution
|
132 | let cospitch = Math.cos(pitch)
|
133 | let sinpitch = Math.sin(pitch)
|
134 | if (slice2 > 0) {
|
135 | vertices = []
|
136 | vertices.push(new Vertex(center.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
137 | vertices.push(new Vertex(center.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
138 | if (slice2 < qresolution) {
|
139 | vertices.push(new Vertex(center.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
140 | }
|
141 | vertices.push(new Vertex(center.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
142 | polygons.push(new Polygon(vertices))
|
143 | vertices = []
|
144 | vertices.push(new Vertex(center.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
145 | vertices.push(new Vertex(center.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
146 | if (slice2 < qresolution) {
|
147 | vertices.push(new Vertex(center.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
148 | }
|
149 | vertices.push(new Vertex(center.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
150 | vertices.reverse()
|
151 | polygons.push(new Polygon(vertices))
|
152 | }
|
153 | prevcospitch = cospitch
|
154 | prevsinpitch = sinpitch
|
155 | }
|
156 | }
|
157 | prevcylinderpoint = cylinderpoint
|
158 | }
|
159 | let result = CSG.fromPolygons(polygons)
|
160 | result.properties.sphere = new Properties()
|
161 | result.properties.sphere.center = new Vector3D(center)
|
162 | result.properties.sphere.facepoint = center.plus(xvector)
|
163 | return result
|
164 | }
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | const cylinder = function (options) {
|
183 | let s = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
184 | let e = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
185 | let r = parseOptionAsFloat(options, 'radius', 1)
|
186 | let rEnd = parseOptionAsFloat(options, 'radiusEnd', r)
|
187 | let rStart = parseOptionAsFloat(options, 'radiusStart', r)
|
188 | let alpha = parseOptionAsFloat(options, 'sectorAngle', 360)
|
189 | alpha = alpha > 360 ? alpha % 360 : alpha
|
190 |
|
191 | if ((rEnd < 0) || (rStart < 0)) {
|
192 | throw new Error('Radius should be non-negative')
|
193 | }
|
194 | if ((rEnd === 0) && (rStart === 0)) {
|
195 | throw new Error('Either radiusStart or radiusEnd should be positive')
|
196 | }
|
197 |
|
198 | let slices = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
199 | let ray = e.minus(s)
|
200 | let axisZ = ray.unit()
|
201 | let axisX = axisZ.randomNonParallelVector().unit()
|
202 |
|
203 |
|
204 | let axisY = axisX.cross(axisZ).unit()
|
205 | let start = new Vertex(s)
|
206 | let end = new Vertex(e)
|
207 | let polygons = []
|
208 |
|
209 | function point (stack, slice, radius) {
|
210 | let angle = slice * Math.PI * alpha / 180
|
211 | let out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle)))
|
212 | let pos = s.plus(ray.times(stack)).plus(out.times(radius))
|
213 | return new Vertex(pos)
|
214 | }
|
215 | if (alpha > 0) {
|
216 | for (let i = 0; i < slices; i++) {
|
217 | let t0 = i / slices
|
218 | let t1 = (i + 1) / slices
|
219 | if (rEnd === rStart) {
|
220 | polygons.push(new Polygon([start, point(0, t0, rEnd), point(0, t1, rEnd)]))
|
221 | polygons.push(new Polygon([point(0, t1, rEnd), point(0, t0, rEnd), point(1, t0, rEnd), point(1, t1, rEnd)]))
|
222 | polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
223 | } else {
|
224 | if (rStart > 0) {
|
225 | polygons.push(new Polygon([start, point(0, t0, rStart), point(0, t1, rStart)]))
|
226 | polygons.push(new Polygon([point(0, t0, rStart), point(1, t0, rEnd), point(0, t1, rStart)]))
|
227 | }
|
228 | if (rEnd > 0) {
|
229 | polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
230 | polygons.push(new Polygon([point(1, t0, rEnd), point(1, t1, rEnd), point(0, t1, rStart)]))
|
231 | }
|
232 | }
|
233 | }
|
234 | if (alpha < 360) {
|
235 | polygons.push(new Polygon([start, end, point(0, 0, rStart)]))
|
236 | polygons.push(new Polygon([point(0, 0, rStart), end, point(1, 0, rEnd)]))
|
237 | polygons.push(new Polygon([start, point(0, 1, rStart), end]))
|
238 | polygons.push(new Polygon([point(0, 1, rStart), point(1, 1, rEnd), end]))
|
239 | }
|
240 | }
|
241 | let result = CSG.fromPolygons(polygons)
|
242 | result.properties.cylinder = new Properties()
|
243 | result.properties.cylinder.start = new Connector(s, axisZ.negated(), axisX)
|
244 | result.properties.cylinder.end = new Connector(e, axisZ, axisX)
|
245 | let cylCenter = s.plus(ray.times(0.5))
|
246 | let fptVec = axisX.rotate(s, axisZ, -alpha / 2).times((rStart + rEnd) / 2)
|
247 | let fptVec90 = fptVec.cross(axisZ)
|
248 |
|
249 | result.properties.cylinder.facepointH = new Connector(cylCenter.plus(fptVec), fptVec, axisZ)
|
250 | result.properties.cylinder.facepointH90 = new Connector(cylCenter.plus(fptVec90), fptVec90, axisZ)
|
251 | return result
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | const roundedCylinder = function (options) {
|
272 | let p1 = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
273 | let p2 = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
274 | let radius = parseOptionAsFloat(options, 'radius', 1)
|
275 | let direction = p2.minus(p1)
|
276 | let defaultnormal
|
277 | if (Math.abs(direction.x) > Math.abs(direction.y)) {
|
278 | defaultnormal = new Vector3D(0, 1, 0)
|
279 | } else {
|
280 | defaultnormal = new Vector3D(1, 0, 0)
|
281 | }
|
282 | let normal = parseOptionAs3DVector(options, 'normal', defaultnormal)
|
283 | let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
284 | if (resolution < 4) resolution = 4
|
285 | let polygons = []
|
286 | let qresolution = Math.floor(0.25 * resolution)
|
287 | let length = direction.length()
|
288 | if (length < EPS) {
|
289 | return sphere({
|
290 | center: p1,
|
291 | radius: radius,
|
292 | resolution: resolution
|
293 | })
|
294 | }
|
295 | let zvector = direction.unit().times(radius)
|
296 | let xvector = zvector.cross(normal).unit().times(radius)
|
297 | let yvector = xvector.cross(zvector).unit().times(radius)
|
298 | let prevcylinderpoint
|
299 | for (let slice1 = 0; slice1 <= resolution; slice1++) {
|
300 | let angle = Math.PI * 2.0 * slice1 / resolution
|
301 | let cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)))
|
302 | if (slice1 > 0) {
|
303 |
|
304 | let vertices = []
|
305 | vertices.push(new Vertex(p1.plus(cylinderpoint)))
|
306 | vertices.push(new Vertex(p1.plus(prevcylinderpoint)))
|
307 | vertices.push(new Vertex(p2.plus(prevcylinderpoint)))
|
308 | vertices.push(new Vertex(p2.plus(cylinderpoint)))
|
309 | polygons.push(new Polygon(vertices))
|
310 | let prevcospitch, prevsinpitch
|
311 | for (let slice2 = 0; slice2 <= qresolution; slice2++) {
|
312 | let pitch = 0.5 * Math.PI * slice2 / qresolution
|
313 |
|
314 | let cospitch = Math.cos(pitch)
|
315 | let sinpitch = Math.sin(pitch)
|
316 | if (slice2 > 0) {
|
317 | vertices = []
|
318 | vertices.push(new Vertex(p1.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
319 | vertices.push(new Vertex(p1.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
320 | if (slice2 < qresolution) {
|
321 | vertices.push(new Vertex(p1.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
322 | }
|
323 | vertices.push(new Vertex(p1.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
324 | polygons.push(new Polygon(vertices))
|
325 | vertices = []
|
326 | vertices.push(new Vertex(p2.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
327 | vertices.push(new Vertex(p2.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
328 | if (slice2 < qresolution) {
|
329 | vertices.push(new Vertex(p2.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
330 | }
|
331 | vertices.push(new Vertex(p2.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
332 | vertices.reverse()
|
333 | polygons.push(new Polygon(vertices))
|
334 | }
|
335 | prevcospitch = cospitch
|
336 | prevsinpitch = sinpitch
|
337 | }
|
338 | }
|
339 | prevcylinderpoint = cylinderpoint
|
340 | }
|
341 | let result = CSG.fromPolygons(polygons)
|
342 | let ray = zvector.unit()
|
343 | let axisX = xvector.unit()
|
344 | result.properties.roundedCylinder = new Properties()
|
345 | result.properties.roundedCylinder.start = new Connector(p1, ray.negated(), axisX)
|
346 | result.properties.roundedCylinder.end = new Connector(p2, ray, axisX)
|
347 | result.properties.roundedCylinder.facepoint = p1.plus(xvector)
|
348 | return result
|
349 | }
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | const cylinderElliptic = function (options) {
|
372 | let s = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
373 | let e = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
374 | let r = parseOptionAs2DVector(options, 'radius', [1, 1])
|
375 | let rEnd = parseOptionAs2DVector(options, 'radiusEnd', r)
|
376 | let rStart = parseOptionAs2DVector(options, 'radiusStart', r)
|
377 |
|
378 | if ((rEnd._x < 0) || (rStart._x < 0) || (rEnd._y < 0) || (rStart._y < 0)) {
|
379 | throw new Error('Radius should be non-negative')
|
380 | }
|
381 | if ((rEnd._x === 0 || rEnd._y === 0) && (rStart._x === 0 || rStart._y === 0)) {
|
382 | throw new Error('Either radiusStart or radiusEnd should be positive')
|
383 | }
|
384 |
|
385 | let slices = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
386 | let ray = e.minus(s)
|
387 | let axisZ = ray.unit()
|
388 | let axisX = axisZ.randomNonParallelVector().unit()
|
389 |
|
390 |
|
391 | let axisY = axisX.cross(axisZ).unit()
|
392 | let start = new Vertex(s)
|
393 | let end = new Vertex(e)
|
394 | let polygons = []
|
395 |
|
396 | function point (stack, slice, radius) {
|
397 | let angle = slice * Math.PI * 2
|
398 | let out = axisX.times(radius._x * Math.cos(angle)).plus(axisY.times(radius._y * Math.sin(angle)))
|
399 | let pos = s.plus(ray.times(stack)).plus(out)
|
400 | return new Vertex(pos)
|
401 | }
|
402 | for (let i = 0; i < slices; i++) {
|
403 | let t0 = i / slices
|
404 | let t1 = (i + 1) / slices
|
405 |
|
406 | if (rEnd._x === rStart._x && rEnd._y === rStart._y) {
|
407 | polygons.push(new Polygon([start, point(0, t0, rEnd), point(0, t1, rEnd)]))
|
408 | polygons.push(new Polygon([point(0, t1, rEnd), point(0, t0, rEnd), point(1, t0, rEnd), point(1, t1, rEnd)]))
|
409 | polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
410 | } else {
|
411 | if (rStart._x > 0) {
|
412 | polygons.push(new Polygon([start, point(0, t0, rStart), point(0, t1, rStart)]))
|
413 | polygons.push(new Polygon([point(0, t0, rStart), point(1, t0, rEnd), point(0, t1, rStart)]))
|
414 | }
|
415 | if (rEnd._x > 0) {
|
416 | polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
417 | polygons.push(new Polygon([point(1, t0, rEnd), point(1, t1, rEnd), point(0, t1, rStart)]))
|
418 | }
|
419 | }
|
420 | }
|
421 | let result = CSG.fromPolygons(polygons)
|
422 | result.properties.cylinder = new Properties()
|
423 | result.properties.cylinder.start = new Connector(s, axisZ.negated(), axisX)
|
424 | result.properties.cylinder.end = new Connector(e, axisZ, axisX)
|
425 | result.properties.cylinder.facepoint = s.plus(axisX.times(rStart))
|
426 | return result
|
427 | }
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 | const roundedCube = function (options) {
|
446 | let minRR = 1e-2
|
447 | let center
|
448 | let cuberadius
|
449 | let corner1
|
450 | let corner2
|
451 | options = options || {}
|
452 | if (('corner1' in options) || ('corner2' in options)) {
|
453 | if (('center' in options) || ('radius' in options)) {
|
454 | throw new Error('roundedCube: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
455 | }
|
456 | corner1 = parseOptionAs3DVector(options, 'corner1', [0, 0, 0])
|
457 | corner2 = parseOptionAs3DVector(options, 'corner2', [1, 1, 1])
|
458 | center = corner1.plus(corner2).times(0.5)
|
459 | cuberadius = corner2.minus(corner1).times(0.5)
|
460 | } else {
|
461 | center = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
462 | cuberadius = parseOptionAs3DVector(options, 'radius', [1, 1, 1])
|
463 | }
|
464 | cuberadius = cuberadius.abs()
|
465 | let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
466 | if (resolution < 4) resolution = 4
|
467 | if (resolution % 2 === 1 && resolution < 8) resolution = 8
|
468 | let roundradius = parseOptionAs3DVector(options, 'roundradius', [0.2, 0.2, 0.2])
|
469 |
|
470 | roundradius = Vector3D.Create(Math.max(roundradius.x, minRR), Math.max(roundradius.y, minRR), Math.max(roundradius.z, minRR))
|
471 | let innerradius = cuberadius.minus(roundradius)
|
472 | if (innerradius.x < 0 || innerradius.y < 0 || innerradius.z < 0) {
|
473 | throw new Error('roundradius <= radius!')
|
474 | }
|
475 | let res = sphere({radius: 1, resolution: resolution})
|
476 | res = res.scale(roundradius)
|
477 | innerradius.x > EPS && (res = res.stretchAtPlane([1, 0, 0], [0, 0, 0], 2 * innerradius.x))
|
478 | innerradius.y > EPS && (res = res.stretchAtPlane([0, 1, 0], [0, 0, 0], 2 * innerradius.y))
|
479 | innerradius.z > EPS && (res = res.stretchAtPlane([0, 0, 1], [0, 0, 0], 2 * innerradius.z))
|
480 | res = res.translate([-innerradius.x + center.x, -innerradius.y + center.y, -innerradius.z + center.z])
|
481 | res = res.reTesselated()
|
482 | res.properties.roundedCube = new Properties()
|
483 | res.properties.roundedCube.center = new Vertex(center)
|
484 | res.properties.roundedCube.facecenters = [
|
485 | new Connector(new Vector3D([cuberadius.x, 0, 0]).plus(center), [1, 0, 0], [0, 0, 1]),
|
486 | new Connector(new Vector3D([-cuberadius.x, 0, 0]).plus(center), [-1, 0, 0], [0, 0, 1]),
|
487 | new Connector(new Vector3D([0, cuberadius.y, 0]).plus(center), [0, 1, 0], [0, 0, 1]),
|
488 | new Connector(new Vector3D([0, -cuberadius.y, 0]).plus(center), [0, -1, 0], [0, 0, 1]),
|
489 | new Connector(new Vector3D([0, 0, cuberadius.z]).plus(center), [0, 0, 1], [1, 0, 0]),
|
490 | new Connector(new Vector3D([0, 0, -cuberadius.z]).plus(center), [0, 0, -1], [1, 0, 0])
|
491 | ]
|
492 | return res
|
493 | }
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 | const polyhedron = function (options) {
|
501 | options = options || {}
|
502 | if (('points' in options) !== ('faces' in options)) {
|
503 | throw new Error("polyhedron needs 'points' and 'faces' arrays")
|
504 | }
|
505 | let vertices = parseOptionAs3DVectorList(options, 'points', [
|
506 | [1, 1, 0],
|
507 | [1, -1, 0],
|
508 | [-1, -1, 0],
|
509 | [-1, 1, 0],
|
510 | [0, 0, 1]
|
511 | ])
|
512 | .map(function (pt) {
|
513 | return new Vertex(pt)
|
514 | })
|
515 | let faces = parseOption(options, 'faces', [
|
516 | [0, 1, 4],
|
517 | [1, 2, 4],
|
518 | [2, 3, 4],
|
519 | [3, 0, 4],
|
520 | [1, 0, 3],
|
521 | [2, 1, 3]
|
522 | ])
|
523 |
|
524 | faces.forEach(function (face) {
|
525 | face.reverse()
|
526 | })
|
527 | let polygons = faces.map(function (face) {
|
528 | return new Polygon(face.map(function (idx) {
|
529 | return vertices[idx]
|
530 | }))
|
531 | })
|
532 |
|
533 |
|
534 |
|
535 |
|
536 |
|
537 | return CSG.fromPolygons(polygons).reTesselated()
|
538 | }
|
539 |
|
540 | module.exports = {
|
541 | cube,
|
542 | sphere,
|
543 | roundedCube,
|
544 | cylinder,
|
545 | roundedCylinder,
|
546 | cylinderElliptic,
|
547 | polyhedron
|
548 | }
|