@richardmcquiston01/more-open-scad 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -0
  4. package/dist/csg/boolean.d.ts +22 -0
  5. package/dist/csg/boolean.d.ts.map +1 -0
  6. package/dist/csg/bsp-tree.d.ts +70 -0
  7. package/dist/csg/bsp-tree.d.ts.map +1 -0
  8. package/dist/csg/split-polygon.d.ts +62 -0
  9. package/dist/csg/split-polygon.d.ts.map +1 -0
  10. package/dist/geometry/plane.d.ts +31 -0
  11. package/dist/geometry/plane.d.ts.map +1 -0
  12. package/dist/geometry/polygon.d.ts +24 -0
  13. package/dist/geometry/polygon.d.ts.map +1 -0
  14. package/dist/geometry/solid.d.ts +41 -0
  15. package/dist/geometry/solid.d.ts.map +1 -0
  16. package/dist/geometry/triangulate.d.ts +16 -0
  17. package/dist/geometry/triangulate.d.ts.map +1 -0
  18. package/dist/geometry/vertex.d.ts +20 -0
  19. package/dist/geometry/vertex.d.ts.map +1 -0
  20. package/dist/index.d.ts +20 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +1016 -0
  23. package/dist/io/manifold-check.d.ts +20 -0
  24. package/dist/io/manifold-check.d.ts.map +1 -0
  25. package/dist/io/stl-ascii.d.ts +14 -0
  26. package/dist/io/stl-ascii.d.ts.map +1 -0
  27. package/dist/io/stl-binary.d.ts +13 -0
  28. package/dist/io/stl-binary.d.ts.map +1 -0
  29. package/dist/io/triangulate-solid.d.ts +25 -0
  30. package/dist/io/triangulate-solid.d.ts.map +1 -0
  31. package/dist/math/epsilon.d.ts +10 -0
  32. package/dist/math/epsilon.d.ts.map +1 -0
  33. package/dist/math/mat4.d.ts +75 -0
  34. package/dist/math/mat4.d.ts.map +1 -0
  35. package/dist/math/vec3.d.ts +43 -0
  36. package/dist/math/vec3.d.ts.map +1 -0
  37. package/dist/primitives/cube.d.ts +21 -0
  38. package/dist/primitives/cube.d.ts.map +1 -0
  39. package/dist/primitives/cylinder.d.ts +32 -0
  40. package/dist/primitives/cylinder.d.ts.map +1 -0
  41. package/dist/primitives/polyhedron.d.ts +36 -0
  42. package/dist/primitives/polyhedron.d.ts.map +1 -0
  43. package/dist/primitives/sphere.d.ts +35 -0
  44. package/dist/primitives/sphere.d.ts.map +1 -0
  45. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,1016 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true,
11
+ configurable: true,
12
+ set: __exportSetter.bind(all, name)
13
+ });
14
+ };
15
+
16
+ // src/geometry/polygon.ts
17
+ var exports_polygon = {};
18
+ __export(exports_polygon, {
19
+ flip: () => flip3,
20
+ polygon: () => polygon
21
+ });
22
+
23
+ // src/geometry/plane.ts
24
+ var exports_plane = {};
25
+ __export(exports_plane, {
26
+ equals: () => equals2,
27
+ flip: () => flip,
28
+ fromPoints: () => fromPoints,
29
+ signedDistance: () => signedDistance
30
+ });
31
+
32
+ // src/math/epsilon.ts
33
+ var EPSILON = 0.000000001;
34
+ function almostEqual(a, b, tolerance = EPSILON) {
35
+ return Math.abs(a - b) <= tolerance;
36
+ }
37
+
38
+ // src/math/vec3.ts
39
+ var exports_vec3 = {};
40
+ __export(exports_vec3, {
41
+ ZERO: () => ZERO,
42
+ add: () => add,
43
+ cross: () => cross,
44
+ dot: () => dot,
45
+ equals: () => equals,
46
+ length: () => length,
47
+ lerp: () => lerp,
48
+ negate: () => negate,
49
+ normalize: () => normalize,
50
+ scale: () => scale,
51
+ sub: () => sub,
52
+ vec3: () => vec3
53
+ });
54
+ function vec3(x, y, z) {
55
+ return { x, y, z };
56
+ }
57
+ var ZERO = vec3(0, 0, 0);
58
+ function add(a, b) {
59
+ return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
60
+ }
61
+ function sub(a, b) {
62
+ return vec3(a.x - b.x, a.y - b.y, a.z - b.z);
63
+ }
64
+ function scale(v, s) {
65
+ return vec3(v.x * s, v.y * s, v.z * s);
66
+ }
67
+ function negate(v) {
68
+ return vec3(-v.x, -v.y, -v.z);
69
+ }
70
+ function dot(a, b) {
71
+ return a.x * b.x + a.y * b.y + a.z * b.z;
72
+ }
73
+ function cross(a, b) {
74
+ return vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
75
+ }
76
+ function length(v) {
77
+ return Math.hypot(v.x, v.y, v.z);
78
+ }
79
+ function normalize(v) {
80
+ const len = length(v);
81
+ if (len <= EPSILON) {
82
+ return ZERO;
83
+ }
84
+ return scale(v, 1 / len);
85
+ }
86
+ function lerp(a, b, t) {
87
+ return add(a, scale(sub(b, a), t));
88
+ }
89
+ function equals(a, b, tolerance = EPSILON) {
90
+ return almostEqual(a.x, b.x, tolerance) && almostEqual(a.y, b.y, tolerance) && almostEqual(a.z, b.z, tolerance);
91
+ }
92
+
93
+ // src/geometry/plane.ts
94
+ function fromPoints(a, b, c) {
95
+ const normal = normalize(cross(sub(b, a), sub(c, a)));
96
+ return { normal, w: dot(normal, a) };
97
+ }
98
+ function signedDistance(plane, point) {
99
+ return dot(plane.normal, point) - plane.w;
100
+ }
101
+ function flip(plane) {
102
+ return { normal: negate(plane.normal), w: -plane.w };
103
+ }
104
+ function equals2(a, b, tolerance = EPSILON) {
105
+ return equals(a.normal, b.normal, tolerance) && Math.abs(a.w - b.w) <= tolerance;
106
+ }
107
+
108
+ // src/geometry/vertex.ts
109
+ var exports_vertex = {};
110
+ __export(exports_vertex, {
111
+ equals: () => equals3,
112
+ flip: () => flip2,
113
+ lerp: () => lerp2,
114
+ vertex: () => vertex
115
+ });
116
+ function vertex(pos, normal) {
117
+ return { pos, normal };
118
+ }
119
+ function lerp2(a, b, t) {
120
+ return vertex(lerp(a.pos, b.pos, t), normalize(lerp(a.normal, b.normal, t)));
121
+ }
122
+ function flip2(v) {
123
+ return vertex(v.pos, negate(v.normal));
124
+ }
125
+ function equals3(a, b, tolerance) {
126
+ return equals(a.pos, b.pos, tolerance) && equals(a.normal, b.normal, tolerance);
127
+ }
128
+
129
+ // src/geometry/polygon.ts
130
+ function polygon(vertices) {
131
+ const [v0, v1, v2] = vertices;
132
+ if (!v0 || !v1 || !v2) {
133
+ throw new Error(`Polygon requires at least 3 vertices, got ${vertices.length}`);
134
+ }
135
+ return { vertices, plane: fromPoints(v0.pos, v1.pos, v2.pos) };
136
+ }
137
+ function flip3(p) {
138
+ return {
139
+ vertices: [...p.vertices].reverse().map(flip2),
140
+ plane: flip(p.plane)
141
+ };
142
+ }
143
+
144
+ // src/math/mat4.ts
145
+ var exports_mat4 = {};
146
+ __export(exports_mat4, {
147
+ IDENTITY: () => IDENTITY,
148
+ determinant: () => determinant,
149
+ equals: () => equals4,
150
+ invert: () => invert,
151
+ multiply: () => multiply,
152
+ reflection: () => reflection,
153
+ rotationAxisAngle: () => rotationAxisAngle,
154
+ rotationX: () => rotationX,
155
+ rotationY: () => rotationY,
156
+ rotationZ: () => rotationZ,
157
+ scaling: () => scaling,
158
+ transformDirection: () => transformDirection,
159
+ transformPoint: () => transformPoint,
160
+ translation: () => translation,
161
+ transpose: () => transpose
162
+ });
163
+ function mat4(e) {
164
+ return { e };
165
+ }
166
+ var IDENTITY = mat4([
167
+ 1,
168
+ 0,
169
+ 0,
170
+ 0,
171
+ 0,
172
+ 1,
173
+ 0,
174
+ 0,
175
+ 0,
176
+ 0,
177
+ 1,
178
+ 0,
179
+ 0,
180
+ 0,
181
+ 0,
182
+ 1
183
+ ]);
184
+ function translation(v) {
185
+ return mat4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, v.x, v.y, v.z, 1]);
186
+ }
187
+ function scaling(v) {
188
+ return mat4([v.x, 0, 0, 0, 0, v.y, 0, 0, 0, 0, v.z, 0, 0, 0, 0, 1]);
189
+ }
190
+ function rotationX(radians) {
191
+ const c = Math.cos(radians);
192
+ const s = Math.sin(radians);
193
+ return mat4([1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1]);
194
+ }
195
+ function rotationY(radians) {
196
+ const c = Math.cos(radians);
197
+ const s = Math.sin(radians);
198
+ return mat4([c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1]);
199
+ }
200
+ function rotationZ(radians) {
201
+ const c = Math.cos(radians);
202
+ const s = Math.sin(radians);
203
+ return mat4([c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
204
+ }
205
+ function rotationAxisAngle(axis, radians) {
206
+ const len = Math.hypot(axis.x, axis.y, axis.z);
207
+ if (len <= EPSILON) {
208
+ return IDENTITY;
209
+ }
210
+ const x = axis.x / len;
211
+ const y = axis.y / len;
212
+ const z = axis.z / len;
213
+ const c = Math.cos(radians);
214
+ const s = Math.sin(radians);
215
+ const t = 1 - c;
216
+ return mat4([
217
+ t * x * x + c,
218
+ t * x * y + s * z,
219
+ t * x * z - s * y,
220
+ 0,
221
+ t * x * y - s * z,
222
+ t * y * y + c,
223
+ t * y * z + s * x,
224
+ 0,
225
+ t * x * z + s * y,
226
+ t * y * z - s * x,
227
+ t * z * z + c,
228
+ 0,
229
+ 0,
230
+ 0,
231
+ 0,
232
+ 1
233
+ ]);
234
+ }
235
+ function reflection(normal) {
236
+ const len = Math.hypot(normal.x, normal.y, normal.z);
237
+ if (len <= EPSILON) {
238
+ return IDENTITY;
239
+ }
240
+ const x = normal.x / len;
241
+ const y = normal.y / len;
242
+ const z = normal.z / len;
243
+ return mat4([
244
+ 1 - 2 * x * x,
245
+ -2 * x * y,
246
+ -2 * x * z,
247
+ 0,
248
+ -2 * x * y,
249
+ 1 - 2 * y * y,
250
+ -2 * y * z,
251
+ 0,
252
+ -2 * x * z,
253
+ -2 * y * z,
254
+ 1 - 2 * z * z,
255
+ 0,
256
+ 0,
257
+ 0,
258
+ 0,
259
+ 1
260
+ ]);
261
+ }
262
+ function multiply(a, b) {
263
+ const [
264
+ a00,
265
+ a01,
266
+ a02,
267
+ a03,
268
+ a10,
269
+ a11,
270
+ a12,
271
+ a13,
272
+ a20,
273
+ a21,
274
+ a22,
275
+ a23,
276
+ a30,
277
+ a31,
278
+ a32,
279
+ a33
280
+ ] = a.e;
281
+ const [
282
+ b00,
283
+ b01,
284
+ b02,
285
+ b03,
286
+ b10,
287
+ b11,
288
+ b12,
289
+ b13,
290
+ b20,
291
+ b21,
292
+ b22,
293
+ b23,
294
+ b30,
295
+ b31,
296
+ b32,
297
+ b33
298
+ ] = b.e;
299
+ return mat4([
300
+ a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03,
301
+ a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03,
302
+ a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03,
303
+ a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03,
304
+ a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13,
305
+ a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13,
306
+ a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13,
307
+ a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13,
308
+ a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23,
309
+ a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23,
310
+ a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23,
311
+ a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23,
312
+ a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33,
313
+ a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33,
314
+ a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33,
315
+ a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33
316
+ ]);
317
+ }
318
+ function transpose(m) {
319
+ const [
320
+ m00,
321
+ m01,
322
+ m02,
323
+ m03,
324
+ m10,
325
+ m11,
326
+ m12,
327
+ m13,
328
+ m20,
329
+ m21,
330
+ m22,
331
+ m23,
332
+ m30,
333
+ m31,
334
+ m32,
335
+ m33
336
+ ] = m.e;
337
+ return mat4([
338
+ m00,
339
+ m10,
340
+ m20,
341
+ m30,
342
+ m01,
343
+ m11,
344
+ m21,
345
+ m31,
346
+ m02,
347
+ m12,
348
+ m22,
349
+ m32,
350
+ m03,
351
+ m13,
352
+ m23,
353
+ m33
354
+ ]);
355
+ }
356
+ function minors(m) {
357
+ const [
358
+ m00,
359
+ m01,
360
+ m02,
361
+ m03,
362
+ m10,
363
+ m11,
364
+ m12,
365
+ m13,
366
+ m20,
367
+ m21,
368
+ m22,
369
+ m23,
370
+ m30,
371
+ m31,
372
+ m32,
373
+ m33
374
+ ] = m.e;
375
+ const b00 = m00 * m11 - m01 * m10;
376
+ const b01 = m00 * m12 - m02 * m10;
377
+ const b02 = m00 * m13 - m03 * m10;
378
+ const b03 = m01 * m12 - m02 * m11;
379
+ const b04 = m01 * m13 - m03 * m11;
380
+ const b05 = m02 * m13 - m03 * m12;
381
+ const b06 = m20 * m31 - m21 * m30;
382
+ const b07 = m20 * m32 - m22 * m30;
383
+ const b08 = m20 * m33 - m23 * m30;
384
+ const b09 = m21 * m32 - m22 * m31;
385
+ const b10 = m21 * m33 - m23 * m31;
386
+ const b11 = m22 * m33 - m23 * m32;
387
+ const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
388
+ return {
389
+ m00,
390
+ m01,
391
+ m02,
392
+ m03,
393
+ m10,
394
+ m11,
395
+ m12,
396
+ m13,
397
+ m20,
398
+ m21,
399
+ m22,
400
+ m23,
401
+ m30,
402
+ m31,
403
+ m32,
404
+ m33,
405
+ b00,
406
+ b01,
407
+ b02,
408
+ b03,
409
+ b04,
410
+ b05,
411
+ b06,
412
+ b07,
413
+ b08,
414
+ b09,
415
+ b10,
416
+ b11,
417
+ det
418
+ };
419
+ }
420
+ function determinant(m) {
421
+ return minors(m).det;
422
+ }
423
+ function invert(m) {
424
+ const {
425
+ m00,
426
+ m01,
427
+ m02,
428
+ m03,
429
+ m10,
430
+ m11,
431
+ m12,
432
+ m13,
433
+ m20,
434
+ m21,
435
+ m22,
436
+ m23,
437
+ m30,
438
+ m31,
439
+ m32,
440
+ m33,
441
+ b00,
442
+ b01,
443
+ b02,
444
+ b03,
445
+ b04,
446
+ b05,
447
+ b06,
448
+ b07,
449
+ b08,
450
+ b09,
451
+ b10,
452
+ b11,
453
+ det
454
+ } = minors(m);
455
+ if (Math.abs(det) <= EPSILON) {
456
+ return null;
457
+ }
458
+ const invDet = 1 / det;
459
+ return mat4([
460
+ (m11 * b11 - m12 * b10 + m13 * b09) * invDet,
461
+ (m02 * b10 - m01 * b11 - m03 * b09) * invDet,
462
+ (m31 * b05 - m32 * b04 + m33 * b03) * invDet,
463
+ (m22 * b04 - m21 * b05 - m23 * b03) * invDet,
464
+ (m12 * b08 - m10 * b11 - m13 * b07) * invDet,
465
+ (m00 * b11 - m02 * b08 + m03 * b07) * invDet,
466
+ (m32 * b02 - m30 * b05 - m33 * b01) * invDet,
467
+ (m20 * b05 - m22 * b02 + m23 * b01) * invDet,
468
+ (m10 * b10 - m11 * b08 + m13 * b06) * invDet,
469
+ (m01 * b08 - m00 * b10 - m03 * b06) * invDet,
470
+ (m30 * b04 - m31 * b02 + m33 * b00) * invDet,
471
+ (m21 * b02 - m20 * b04 - m23 * b00) * invDet,
472
+ (m11 * b07 - m10 * b09 - m12 * b06) * invDet,
473
+ (m00 * b09 - m01 * b07 + m02 * b06) * invDet,
474
+ (m31 * b01 - m30 * b03 - m32 * b00) * invDet,
475
+ (m20 * b03 - m21 * b01 + m22 * b00) * invDet
476
+ ]);
477
+ }
478
+ function equals4(a, b, tolerance = EPSILON) {
479
+ return a.e.every((value, i) => Math.abs(value - b.e[i]) <= tolerance);
480
+ }
481
+ function transformPoint(m, v) {
482
+ const [
483
+ m00,
484
+ m01,
485
+ m02,
486
+ _m03,
487
+ m10,
488
+ m11,
489
+ m12,
490
+ _m13,
491
+ m20,
492
+ m21,
493
+ m22,
494
+ _m23,
495
+ m30,
496
+ m31,
497
+ m32,
498
+ _m33
499
+ ] = m.e;
500
+ return vec3(m00 * v.x + m10 * v.y + m20 * v.z + m30, m01 * v.x + m11 * v.y + m21 * v.z + m31, m02 * v.x + m12 * v.y + m22 * v.z + m32);
501
+ }
502
+ function transformDirection(m, v) {
503
+ const [m00, m01, m02, _m03, m10, m11, m12, _m13, m20, m21, m22, _m23] = m.e;
504
+ return vec3(m00 * v.x + m10 * v.y + m20 * v.z, m01 * v.x + m11 * v.y + m21 * v.z, m02 * v.x + m12 * v.y + m22 * v.z);
505
+ }
506
+
507
+ // src/geometry/solid.ts
508
+ function applyMatrix(solid, matrix) {
509
+ const inverse = invert(matrix);
510
+ const normalMatrix = inverse ? transpose(inverse) : matrix;
511
+ const reverseWinding = determinant(matrix) < 0;
512
+ const polygons = solid.polygons.map((polygon2) => {
513
+ const vertices = polygon2.vertices.map((v) => vertex(transformPoint(matrix, v.pos), normalize(transformDirection(normalMatrix, v.normal))));
514
+ return polygon(reverseWinding ? [...vertices].reverse() : vertices);
515
+ });
516
+ return new Solid(polygons);
517
+ }
518
+ function eulerRotation(radians) {
519
+ return multiply(rotationZ(radians.z), multiply(rotationY(radians.y), rotationX(radians.x)));
520
+ }
521
+
522
+ class Solid {
523
+ polygons;
524
+ constructor(polygons) {
525
+ this.polygons = polygons;
526
+ }
527
+ translate(offset) {
528
+ return applyMatrix(this, translation(offset));
529
+ }
530
+ rotate(radians) {
531
+ return applyMatrix(this, eulerRotation(radians));
532
+ }
533
+ rotateAxisAngle(axis, radians) {
534
+ return applyMatrix(this, rotationAxisAngle(axis, radians));
535
+ }
536
+ scale(factor) {
537
+ const v = typeof factor === "number" ? vec3(factor, factor, factor) : factor;
538
+ return applyMatrix(this, scaling(v));
539
+ }
540
+ mirror(normal) {
541
+ return applyMatrix(this, reflection(normal));
542
+ }
543
+ multmatrix(matrix) {
544
+ return applyMatrix(this, matrix);
545
+ }
546
+ }
547
+
548
+ // src/primitives/cube.ts
549
+ function cube(size, options) {
550
+ const dims = typeof size === "number" ? vec3(size, size, size) : size;
551
+ const center = options?.center ?? false;
552
+ const minX = center ? -dims.x / 2 : 0;
553
+ const minY = center ? -dims.y / 2 : 0;
554
+ const minZ = center ? -dims.z / 2 : 0;
555
+ const maxX = minX + dims.x;
556
+ const maxY = minY + dims.y;
557
+ const maxZ = minZ + dims.z;
558
+ const p = (x, y, z) => vec3(x, y, z);
559
+ const face = (normal, ...positions) => polygon(positions.map((pos) => vertex(pos, normal)));
560
+ return new Solid([
561
+ face(vec3(0, 0, -1), p(minX, minY, minZ), p(minX, maxY, minZ), p(maxX, maxY, minZ), p(maxX, minY, minZ)),
562
+ face(vec3(0, 0, 1), p(minX, minY, maxZ), p(maxX, minY, maxZ), p(maxX, maxY, maxZ), p(minX, maxY, maxZ)),
563
+ face(vec3(0, -1, 0), p(minX, minY, minZ), p(maxX, minY, minZ), p(maxX, minY, maxZ), p(minX, minY, maxZ)),
564
+ face(vec3(0, 1, 0), p(maxX, maxY, minZ), p(minX, maxY, minZ), p(minX, maxY, maxZ), p(maxX, maxY, maxZ)),
565
+ face(vec3(-1, 0, 0), p(minX, maxY, minZ), p(minX, minY, minZ), p(minX, minY, maxZ), p(minX, maxY, maxZ)),
566
+ face(vec3(1, 0, 0), p(maxX, minY, minZ), p(maxX, maxY, minZ), p(maxX, maxY, maxZ), p(maxX, minY, maxZ))
567
+ ]);
568
+ }
569
+ // src/primitives/sphere.ts
570
+ function sphere(r, options) {
571
+ const fn = options?.fn ?? 32;
572
+ if (fn < 3) {
573
+ throw new Error(`sphere requires fn >= 3, got ${fn}`);
574
+ }
575
+ const segments = Math.max(3, Math.floor(fn));
576
+ const rings = Math.max(2, Math.floor(fn / 2));
577
+ const northPole = vec3(0, 0, r);
578
+ const southPole = vec3(0, 0, -r);
579
+ const ringPositions = [];
580
+ for (let i = 1;i < rings; i++) {
581
+ const phi = i * Math.PI / rings;
582
+ const row = [];
583
+ for (let j = 0;j < segments; j++) {
584
+ const theta = j * 2 * Math.PI / segments;
585
+ row.push(vec3(r * Math.sin(phi) * Math.cos(theta), r * Math.sin(phi) * Math.sin(theta), r * Math.cos(phi)));
586
+ }
587
+ ringPositions.push(row);
588
+ }
589
+ const face = (...positions) => polygon(positions.map((pos) => vertex(pos, normalize(pos))));
590
+ const polygons = [];
591
+ const firstRing = ringPositions[0];
592
+ for (let j = 0;j < segments; j++) {
593
+ const jNext = (j + 1) % segments;
594
+ polygons.push(face(northPole, firstRing[j], firstRing[jNext]));
595
+ }
596
+ for (let i = 0;i < ringPositions.length - 1; i++) {
597
+ const ringA = ringPositions[i];
598
+ const ringB = ringPositions[i + 1];
599
+ for (let j = 0;j < segments; j++) {
600
+ const jNext = (j + 1) % segments;
601
+ const a0 = ringA[j];
602
+ const a1 = ringA[jNext];
603
+ const b0 = ringB[j];
604
+ const b1 = ringB[jNext];
605
+ polygons.push(face(a0, b0, b1));
606
+ polygons.push(face(a0, b1, a1));
607
+ }
608
+ }
609
+ const lastRing = ringPositions[ringPositions.length - 1];
610
+ for (let j = 0;j < segments; j++) {
611
+ const jNext = (j + 1) % segments;
612
+ polygons.push(face(southPole, lastRing[jNext], lastRing[j]));
613
+ }
614
+ return new Solid(polygons);
615
+ }
616
+ // src/primitives/cylinder.ts
617
+ function faceNormal(a, b, c) {
618
+ return normalize(cross(sub(b, a), sub(c, a)));
619
+ }
620
+ function triangleFace(a, b, c) {
621
+ const normal = faceNormal(a, b, c);
622
+ return polygon([
623
+ vertex(a, normal),
624
+ vertex(b, normal),
625
+ vertex(c, normal)
626
+ ]);
627
+ }
628
+ function ringPoint(r, theta, z) {
629
+ return vec3(r * Math.cos(theta), r * Math.sin(theta), z);
630
+ }
631
+ function cylinder(h, options = {}) {
632
+ const { center = false, fn = 32 } = options;
633
+ const r1 = options.r1 ?? options.r ?? 1;
634
+ const r2 = options.r2 ?? options.r ?? 1;
635
+ if (!(h > 0)) {
636
+ throw new Error(`cylinder height must be > 0, got ${h}`);
637
+ }
638
+ if (r1 < 0 || r2 < 0) {
639
+ throw new Error(`cylinder radii must be >= 0, got r1=${r1}, r2=${r2}`);
640
+ }
641
+ if (almostEqual(r1, 0) && almostEqual(r2, 0)) {
642
+ throw new Error("cylinder radii r1 and r2 cannot both be 0");
643
+ }
644
+ if (fn < 3) {
645
+ throw new Error(`cylinder fn must be >= 3, got ${fn}`);
646
+ }
647
+ const z0 = center ? -h / 2 : 0;
648
+ const z1 = z0 + h;
649
+ const bottomIsPoint = almostEqual(r1, 0);
650
+ const topIsPoint = almostEqual(r2, 0);
651
+ const thetas = Array.from({ length: fn }, (_, i) => i * 2 * Math.PI / fn);
652
+ const bottomRing = thetas.map((theta) => ringPoint(r1, theta, z0));
653
+ const topRing = thetas.map((theta) => ringPoint(r2, theta, z1));
654
+ const polygons = [];
655
+ for (let i = 0;i < fn; i++) {
656
+ const i2 = (i + 1) % fn;
657
+ const b0 = bottomRing[i];
658
+ const b1 = bottomRing[i2];
659
+ const t0 = topRing[i];
660
+ const t1 = topRing[i2];
661
+ if (!bottomIsPoint && !topIsPoint) {
662
+ polygons.push(triangleFace(b0, b1, t1));
663
+ polygons.push(triangleFace(b0, t1, t0));
664
+ } else if (bottomIsPoint) {
665
+ polygons.push(triangleFace(b0, t1, t0));
666
+ } else {
667
+ polygons.push(triangleFace(b0, b1, t1));
668
+ }
669
+ }
670
+ if (!bottomIsPoint) {
671
+ const bottomCapVertices = [...bottomRing].reverse().map((pos) => vertex(pos, vec3(0, 0, -1)));
672
+ polygons.push(polygon(bottomCapVertices));
673
+ }
674
+ if (!topIsPoint) {
675
+ const topCapVertices = topRing.map((pos) => vertex(pos, vec3(0, 0, 1)));
676
+ polygons.push(polygon(topCapVertices));
677
+ }
678
+ return new Solid(polygons);
679
+ }
680
+ // src/primitives/polyhedron.ts
681
+ function polyhedron(points, faces) {
682
+ const polygons = faces.map((face, faceIndex) => {
683
+ if (face.length < 3) {
684
+ throw new Error(`polyhedron: face ${faceIndex} has ${face.length} indices, but each face requires at least 3`);
685
+ }
686
+ for (const index of face) {
687
+ if (index < 0 || index >= points.length) {
688
+ throw new Error(`polyhedron: face ${faceIndex} references index ${index}, out of bounds for ${points.length} points`);
689
+ }
690
+ }
691
+ const [i0, i1, i2] = face;
692
+ const plane = fromPoints(points[i0], points[i1], points[i2]);
693
+ const vertices = face.map((index) => vertex(points[index], plane.normal));
694
+ return polygon(vertices);
695
+ });
696
+ return new Solid(polygons);
697
+ }
698
+ // src/csg/split-polygon.ts
699
+ var COPLANAR = 0;
700
+ var FRONT = 1;
701
+ var BACK = 2;
702
+ var SPANNING = 3;
703
+ var EMPTY = {
704
+ coplanarFront: [],
705
+ coplanarBack: [],
706
+ front: [],
707
+ back: []
708
+ };
709
+ function splitPolygon(plane, polygon2) {
710
+ const distances = polygon2.vertices.map((v) => signedDistance(plane, v.pos));
711
+ const types = distances.map((d) => d < -EPSILON ? BACK : d > EPSILON ? FRONT : COPLANAR);
712
+ const polygonType = types.reduce((acc, t) => acc | t, COPLANAR);
713
+ switch (polygonType) {
714
+ case COPLANAR: {
715
+ const sameDirection = dot(plane.normal, polygon2.plane.normal) > 0;
716
+ return sameDirection ? { ...EMPTY, coplanarFront: [polygon2] } : { ...EMPTY, coplanarBack: [polygon2] };
717
+ }
718
+ case FRONT:
719
+ return { ...EMPTY, front: [polygon2] };
720
+ case BACK:
721
+ return { ...EMPTY, back: [polygon2] };
722
+ case SPANNING: {
723
+ const front = [];
724
+ const back = [];
725
+ const n = polygon2.vertices.length;
726
+ for (let i = 0;i < n; i++) {
727
+ const j = (i + 1) % n;
728
+ const ti = types[i];
729
+ const tj = types[j];
730
+ const vi = polygon2.vertices[i];
731
+ const vj = polygon2.vertices[j];
732
+ if (ti !== BACK)
733
+ front.push(vi);
734
+ if (ti !== FRONT)
735
+ back.push(vi);
736
+ if ((ti | tj) === SPANNING) {
737
+ const di = distances[i];
738
+ const dj = distances[j];
739
+ const t = di / (di - dj);
740
+ const crossing = lerp2(vi, vj, t);
741
+ front.push(crossing);
742
+ back.push(crossing);
743
+ }
744
+ }
745
+ return {
746
+ ...EMPTY,
747
+ front: front.length >= 3 ? [polygon(front)] : [],
748
+ back: back.length >= 3 ? [polygon(back)] : []
749
+ };
750
+ }
751
+ default: {
752
+ const unreachable = polygonType;
753
+ throw new Error(`splitPolygon: impossible classification ${unreachable}`);
754
+ }
755
+ }
756
+ }
757
+
758
+ // src/csg/bsp-tree.ts
759
+ function build(polygons, node = null) {
760
+ if (polygons.length === 0)
761
+ return node;
762
+ const plane = node ? node.plane : polygons[0].plane;
763
+ const coplanar = node ? [...node.polygons] : [];
764
+ const front = [];
765
+ const back = [];
766
+ for (const polygon of polygons) {
767
+ const split = splitPolygon(plane, polygon);
768
+ coplanar.push(...split.coplanarFront, ...split.coplanarBack);
769
+ front.push(...split.front);
770
+ back.push(...split.back);
771
+ }
772
+ return {
773
+ plane,
774
+ polygons: coplanar,
775
+ front: build(front, node ? node.front : null),
776
+ back: build(back, node ? node.back : null)
777
+ };
778
+ }
779
+ function clipPolygons(node, polygons) {
780
+ if (node === null)
781
+ return [...polygons];
782
+ if (polygons.length === 0)
783
+ return [];
784
+ const front = [];
785
+ const back = [];
786
+ for (const polygon of polygons) {
787
+ const split = splitPolygon(node.plane, polygon);
788
+ front.push(...split.front, ...split.coplanarFront);
789
+ back.push(...split.back, ...split.coplanarBack);
790
+ }
791
+ return [
792
+ ...clipPolygons(node.front, front),
793
+ ...node.back ? clipPolygons(node.back, back) : []
794
+ ];
795
+ }
796
+ function clipTo(node, other) {
797
+ if (node === null)
798
+ return null;
799
+ return {
800
+ plane: node.plane,
801
+ polygons: clipPolygons(other, node.polygons),
802
+ front: clipTo(node.front, other),
803
+ back: clipTo(node.back, other)
804
+ };
805
+ }
806
+ function invert2(node) {
807
+ if (node === null)
808
+ return null;
809
+ return {
810
+ plane: flip(node.plane),
811
+ polygons: node.polygons.map(flip3),
812
+ front: invert2(node.back),
813
+ back: invert2(node.front)
814
+ };
815
+ }
816
+ function allPolygons(node) {
817
+ if (node === null)
818
+ return [];
819
+ return [
820
+ ...node.polygons,
821
+ ...allPolygons(node.front),
822
+ ...allPolygons(node.back)
823
+ ];
824
+ }
825
+
826
+ // src/csg/boolean.ts
827
+ function union(a, b) {
828
+ let nodeA = build(a.polygons);
829
+ let nodeB = build(b.polygons);
830
+ nodeA = clipTo(nodeA, nodeB);
831
+ nodeB = clipTo(nodeB, nodeA);
832
+ nodeB = invert2(nodeB);
833
+ nodeB = clipTo(nodeB, nodeA);
834
+ nodeB = invert2(nodeB);
835
+ nodeA = build(allPolygons(nodeB), nodeA);
836
+ return new Solid(allPolygons(nodeA));
837
+ }
838
+ function difference(a, b) {
839
+ let nodeA = build(a.polygons);
840
+ let nodeB = build(b.polygons);
841
+ nodeA = invert2(nodeA);
842
+ nodeA = clipTo(nodeA, nodeB);
843
+ nodeB = clipTo(nodeB, nodeA);
844
+ nodeB = invert2(nodeB);
845
+ nodeB = clipTo(nodeB, nodeA);
846
+ nodeB = invert2(nodeB);
847
+ nodeA = build(allPolygons(nodeB), nodeA);
848
+ nodeA = invert2(nodeA);
849
+ return new Solid(allPolygons(nodeA));
850
+ }
851
+ function intersect(a, b) {
852
+ let nodeA = build(a.polygons);
853
+ let nodeB = build(b.polygons);
854
+ nodeA = invert2(nodeA);
855
+ nodeB = clipTo(nodeB, nodeA);
856
+ nodeB = invert2(nodeB);
857
+ nodeA = clipTo(nodeA, nodeB);
858
+ nodeB = clipTo(nodeB, nodeA);
859
+ nodeA = build(allPolygons(nodeB), nodeA);
860
+ nodeA = invert2(nodeA);
861
+ return new Solid(allPolygons(nodeA));
862
+ }
863
+ // src/geometry/triangulate.ts
864
+ function triangulate(polygon) {
865
+ const { vertices } = polygon;
866
+ const v0 = vertices[0];
867
+ const triangles = [];
868
+ for (let i = 1;i < vertices.length - 1; i++) {
869
+ triangles.push([v0, vertices[i], vertices[i + 1]]);
870
+ }
871
+ return triangles;
872
+ }
873
+
874
+ // src/io/triangulate-solid.ts
875
+ function triangulateSolid(solid) {
876
+ const triangles = [];
877
+ for (const polygon of solid.polygons) {
878
+ for (const [a, b, c] of triangulate(polygon)) {
879
+ const normal = normalize(cross(sub(b.pos, a.pos), sub(c.pos, a.pos)));
880
+ triangles.push({ normal, vertices: [a.pos, b.pos, c.pos] });
881
+ }
882
+ }
883
+ return triangles;
884
+ }
885
+
886
+ // src/io/stl-binary.ts
887
+ var HEADER_SIZE = 80;
888
+ var COUNT_SIZE = 4;
889
+ var TRIANGLE_SIZE = 50;
890
+ var HEADER_TEXT = "MoreOpenSCAD binary STL export";
891
+ function toBinarySTL(solid) {
892
+ const triangles = triangulateSolid(solid);
893
+ const byteLength = HEADER_SIZE + COUNT_SIZE + TRIANGLE_SIZE * triangles.length;
894
+ const buffer = new ArrayBuffer(byteLength);
895
+ const view = new DataView(buffer);
896
+ const bytes = new Uint8Array(buffer);
897
+ const headerBytes = new TextEncoder().encode(HEADER_TEXT);
898
+ bytes.set(headerBytes.subarray(0, HEADER_SIZE), 0);
899
+ view.setUint32(HEADER_SIZE, triangles.length, true);
900
+ let offset = HEADER_SIZE + COUNT_SIZE;
901
+ for (const triangle of triangles) {
902
+ view.setFloat32(offset, triangle.normal.x, true);
903
+ view.setFloat32(offset + 4, triangle.normal.y, true);
904
+ view.setFloat32(offset + 8, triangle.normal.z, true);
905
+ const [v1, v2, v3] = triangle.vertices;
906
+ view.setFloat32(offset + 12, v1.x, true);
907
+ view.setFloat32(offset + 16, v1.y, true);
908
+ view.setFloat32(offset + 20, v1.z, true);
909
+ view.setFloat32(offset + 24, v2.x, true);
910
+ view.setFloat32(offset + 28, v2.y, true);
911
+ view.setFloat32(offset + 32, v2.z, true);
912
+ view.setFloat32(offset + 36, v3.x, true);
913
+ view.setFloat32(offset + 40, v3.y, true);
914
+ view.setFloat32(offset + 44, v3.z, true);
915
+ view.setUint16(offset + 48, 0, true);
916
+ offset += TRIANGLE_SIZE;
917
+ }
918
+ return bytes;
919
+ }
920
+ // src/io/stl-ascii.ts
921
+ var DECIMAL_PLACES = 6;
922
+ var ZERO_STRING = 0 .toFixed(DECIMAL_PLACES);
923
+ function formatNumber(value) {
924
+ const fixed = value.toFixed(DECIMAL_PLACES);
925
+ return fixed === `-${ZERO_STRING}` ? ZERO_STRING : fixed;
926
+ }
927
+ function formatVec3(v) {
928
+ return `${formatNumber(v.x)} ${formatNumber(v.y)} ${formatNumber(v.z)}`;
929
+ }
930
+ function toASCIISTL(solid, name = "") {
931
+ const header = name === "" ? "solid" : `solid ${name}`;
932
+ const footer = name === "" ? "endsolid" : `endsolid ${name}`;
933
+ const lines = [header];
934
+ for (const triangle of triangulateSolid(solid)) {
935
+ lines.push(` facet normal ${formatVec3(triangle.normal)}`);
936
+ lines.push(" outer loop");
937
+ for (const vertex of triangle.vertices) {
938
+ lines.push(` vertex ${formatVec3(vertex)}`);
939
+ }
940
+ lines.push(" endloop");
941
+ lines.push(" endfacet");
942
+ }
943
+ lines.push(footer);
944
+ return `${lines.join(`
945
+ `)}
946
+ `;
947
+ }
948
+ // src/io/manifold-check.ts
949
+ var EDGE_KEY_DECIMALS = 9;
950
+ function quantize(n) {
951
+ return n.toFixed(EDGE_KEY_DECIMALS);
952
+ }
953
+ function pointKey(p) {
954
+ return `${quantize(p.x)},${quantize(p.y)},${quantize(p.z)}`;
955
+ }
956
+ function directedEdgeKey(from, to) {
957
+ return `${pointKey(from)}->${pointKey(to)}`;
958
+ }
959
+ function reverseEdgeKey(from, to) {
960
+ return `${pointKey(to)}->${pointKey(from)}`;
961
+ }
962
+ function isManifold(solid) {
963
+ const triangles = triangulateSolid(solid);
964
+ if (triangles.length === 0) {
965
+ return true;
966
+ }
967
+ const edgeCounts = new Map;
968
+ for (const { vertices } of triangles) {
969
+ const [a, b, c] = vertices;
970
+ for (const [from, to] of [
971
+ [a, b],
972
+ [b, c],
973
+ [c, a]
974
+ ]) {
975
+ const key = directedEdgeKey(from, to);
976
+ edgeCounts.set(key, (edgeCounts.get(key) ?? 0) + 1);
977
+ }
978
+ }
979
+ for (const { vertices } of triangles) {
980
+ const [a, b, c] = vertices;
981
+ for (const [from, to] of [
982
+ [a, b],
983
+ [b, c],
984
+ [c, a]
985
+ ]) {
986
+ const count = edgeCounts.get(directedEdgeKey(from, to));
987
+ if (count !== 1) {
988
+ return false;
989
+ }
990
+ const reverseCount = edgeCounts.get(reverseEdgeKey(from, to)) ?? 0;
991
+ if (reverseCount !== 1) {
992
+ return false;
993
+ }
994
+ }
995
+ }
996
+ return true;
997
+ }
998
+ export {
999
+ exports_mat4 as Mat4,
1000
+ exports_plane as Plane,
1001
+ exports_polygon as Polygon,
1002
+ Solid,
1003
+ exports_vec3 as Vec3,
1004
+ exports_vertex as Vertex,
1005
+ cube,
1006
+ cylinder,
1007
+ difference,
1008
+ intersect,
1009
+ isManifold,
1010
+ polyhedron,
1011
+ sphere,
1012
+ toASCIISTL,
1013
+ toBinarySTL,
1014
+ triangulateSolid,
1015
+ union
1016
+ };