@jscad/modeling 2.9.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +12 -2
  3. package/dist/jscad-modeling.min.js +148 -151
  4. package/package.json +2 -2
  5. package/src/colors/colorize.d.ts +6 -5
  6. package/src/geometries/geom2/type.d.ts +3 -2
  7. package/src/geometries/geom3/type.d.ts +3 -2
  8. package/src/geometries/path2/appendArc.js +6 -5
  9. package/src/geometries/path2/appendArc.test.js +3 -1
  10. package/src/geometries/path2/appendBezier.js +2 -1
  11. package/src/geometries/path2/appendPoints.js +3 -12
  12. package/src/geometries/path2/appendPoints.test.js +16 -0
  13. package/src/geometries/path2/concat.js +9 -8
  14. package/src/geometries/path2/concat.test.js +13 -7
  15. package/src/geometries/path2/transform.js +1 -1
  16. package/src/geometries/path2/type.d.ts +3 -2
  17. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
  18. package/src/geometries/poly3/measureBoundingSphere.js +46 -8
  19. package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
  20. package/src/geometries/poly3/type.d.ts +3 -2
  21. package/src/geometries/types.d.ts +4 -2
  22. package/src/index.d.ts +1 -0
  23. package/src/maths/constants.js +10 -0
  24. package/src/maths/mat4/fromRotation.js +10 -8
  25. package/src/maths/mat4/fromTaitBryanRotation.js +9 -7
  26. package/src/maths/mat4/fromXRotation.js +5 -3
  27. package/src/maths/mat4/fromYRotation.js +5 -3
  28. package/src/maths/mat4/fromZRotation.js +5 -3
  29. package/src/maths/mat4/invert.test.js +5 -2
  30. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  31. package/src/maths/mat4/isOnlyTransformScale.test.js +3 -1
  32. package/src/maths/mat4/rotate.js +9 -5
  33. package/src/maths/mat4/rotateX.js +4 -2
  34. package/src/maths/mat4/rotateY.js +4 -2
  35. package/src/maths/mat4/rotateZ.js +4 -2
  36. package/src/maths/mat4/translate.test.js +2 -3
  37. package/src/maths/rotation.test.js +5 -2
  38. package/src/maths/utils/index.d.ts +1 -0
  39. package/src/maths/utils/index.js +2 -0
  40. package/src/{utils → maths/utils}/trigonometry.d.ts +0 -0
  41. package/src/{utils → maths/utils}/trigonometry.js +7 -7
  42. package/src/{utils → maths/utils}/trigonometry.test.js +10 -8
  43. package/src/maths/vec2/distance.js +1 -1
  44. package/src/maths/vec2/fromAngleDegrees.js +1 -1
  45. package/src/maths/vec2/fromAngleRadians.js +4 -2
  46. package/src/maths/vec2/fromAngleRadians.test.js +4 -1
  47. package/src/maths/vec2/length.js +1 -1
  48. package/src/maths/vec2/length.test.js +0 -10
  49. package/src/maths/vec2/normal.js +3 -1
  50. package/src/maths/vec2/rotate.test.js +4 -1
  51. package/src/maths/vec3/angle.js +2 -2
  52. package/src/maths/vec3/angle.test.js +0 -12
  53. package/src/maths/vec3/distance.js +1 -1
  54. package/src/maths/vec3/length.js +1 -1
  55. package/src/maths/vec3/length.test.js +0 -10
  56. package/src/maths/vec3/rotateX.test.js +4 -1
  57. package/src/maths/vec3/rotateY.test.js +4 -1
  58. package/src/maths/vec3/rotateZ.test.js +4 -1
  59. package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
  60. package/src/operations/expansions/expand.test.js +3 -1
  61. package/src/operations/expansions/expandShell.js +4 -4
  62. package/src/operations/expansions/offsetFromPoints.js +2 -2
  63. package/src/operations/extrusions/extrudeFromSlices.test.js +3 -2
  64. package/src/operations/extrusions/extrudeLinear.test.js +5 -3
  65. package/src/operations/extrusions/extrudeRectangular.js +1 -1
  66. package/src/operations/extrusions/extrudeRectangular.test.js +5 -3
  67. package/src/operations/extrusions/extrudeRotate.js +14 -13
  68. package/src/operations/extrusions/extrudeRotate.test.js +49 -47
  69. package/src/operations/extrusions/project.test.js +2 -2
  70. package/src/operations/extrusions/slice/calculatePlane.test.js +3 -2
  71. package/src/operations/extrusions/slice/index.js +2 -0
  72. package/src/operations/extrusions/slice/repair.js +1 -1
  73. package/src/operations/modifiers/generalize.d.ts +12 -0
  74. package/src/operations/modifiers/generalize.test.js +3 -1
  75. package/src/operations/modifiers/index.d.ts +2 -0
  76. package/src/operations/modifiers/insertTjunctions.js +34 -35
  77. package/src/operations/modifiers/snap.d.ts +6 -0
  78. package/src/operations/modifiers/snap.test.js +5 -3
  79. package/src/operations/transforms/rotate.js +1 -1
  80. package/src/operations/transforms/rotate.test.js +13 -11
  81. package/src/operations/transforms/transform.js +1 -1
  82. package/src/primitives/arc.js +8 -8
  83. package/src/primitives/arc.test.js +9 -8
  84. package/src/primitives/circle.js +4 -2
  85. package/src/primitives/circle.test.js +12 -4
  86. package/src/primitives/cylinderElliptic.js +13 -11
  87. package/src/primitives/cylinderElliptic.test.js +9 -2
  88. package/src/primitives/ellipse.js +11 -11
  89. package/src/primitives/ellipse.test.js +12 -4
  90. package/src/primitives/ellipsoid.js +4 -3
  91. package/src/primitives/geodesicSphere.js +3 -2
  92. package/src/primitives/roundedCuboid.js +10 -8
  93. package/src/primitives/roundedCylinder.js +4 -4
  94. package/src/primitives/roundedRectangle.js +5 -5
  95. package/src/primitives/star.js +3 -2
  96. package/src/primitives/torus.js +4 -2
  97. package/src/primitives/torus.test.js +11 -5
  98. package/src/primitives/triangle.test.js +2 -1
  99. package/src/utils/degToRad.test.js +5 -5
  100. package/src/utils/index.d.ts +0 -1
  101. package/src/utils/index.js +1 -3
  102. package/src/utils/radToDeg.test.js +6 -6
  103. package/src/utils/radiusToSegments.js +6 -4
  104. package/src/utils/radiusToSegments.test.js +5 -3
  105. package/src/maths/mat4/constants.d.ts +0 -1
  106. package/src/maths/mat4/constants.js +0 -5
@@ -1,3 +1,4 @@
1
+ const { TAU } = require('../../maths/constants')
1
2
  const mat4 = require('../../maths/mat4')
2
3
 
3
4
  const { mirrorX } = require('../transforms/mirror')
@@ -12,7 +13,7 @@ const extrudeFromSlices = require('./extrudeFromSlices')
12
13
  * Rotate extrude the given geometry using the given options.
13
14
  *
14
15
  * @param {Object} options - options for extrusion
15
- * @param {Number} [options.angle=PI*2] - angle of the extrusion (RADIANS)
16
+ * @param {Number} [options.angle=TAU] - angle of the extrusion (RADIANS)
16
17
  * @param {Number} [options.startAngle=0] - start angle of the extrusion (RADIANS)
17
18
  * @param {String} [options.overflow='cap'] - what to do with points outside of bounds (+ / - x) :
18
19
  * defaults to capping those points to 0 (only supported behaviour for now)
@@ -22,24 +23,24 @@ const extrudeFromSlices = require('./extrudeFromSlices')
22
23
  * @alias module:modeling/extrusions.extrudeRotate
23
24
  *
24
25
  * @example
25
- * const myshape = extrudeRotate({segments: 8, angle: Math.PI}, circle({size: 3, center: [4, 0]}))
26
+ * const myshape = extrudeRotate({segments: 8, angle: TAU / 2}, circle({size: 3, center: [4, 0]}))
26
27
  */
27
28
  const extrudeRotate = (options, geometry) => {
28
29
  const defaults = {
29
30
  segments: 12,
30
31
  startAngle: 0,
31
- angle: (Math.PI * 2),
32
+ angle: TAU,
32
33
  overflow: 'cap'
33
34
  }
34
35
  let { segments, startAngle, angle, overflow } = Object.assign({}, defaults, options)
35
36
 
36
37
  if (segments < 3) throw new Error('segments must be greater then 3')
37
38
 
38
- startAngle = Math.abs(startAngle) > (Math.PI * 2) ? startAngle % (Math.PI * 2) : startAngle
39
- angle = Math.abs(angle) > (Math.PI * 2) ? angle % (Math.PI * 2) : angle
39
+ startAngle = Math.abs(startAngle) > TAU ? startAngle % TAU : startAngle
40
+ angle = Math.abs(angle) > TAU ? angle % TAU : angle
40
41
 
41
42
  let endAngle = startAngle + angle
42
- endAngle = Math.abs(endAngle) > (Math.PI * 2) ? endAngle % (Math.PI * 2) : endAngle
43
+ endAngle = Math.abs(endAngle) > TAU ? endAngle % TAU : endAngle
43
44
 
44
45
  if (endAngle < startAngle) {
45
46
  const x = startAngle
@@ -47,11 +48,11 @@ const extrudeRotate = (options, geometry) => {
47
48
  endAngle = x
48
49
  }
49
50
  let totalRotation = endAngle - startAngle
50
- if (totalRotation <= 0.0) totalRotation = (Math.PI * 2)
51
+ if (totalRotation <= 0.0) totalRotation = TAU
51
52
 
52
- if (Math.abs(totalRotation) < (Math.PI * 2)) {
53
+ if (Math.abs(totalRotation) < TAU) {
53
54
  // adjust the segments to achieve the total rotation requested
54
- const anglePerSegment = (Math.PI * 2) / segments
55
+ const anglePerSegment = TAU / segments
55
56
  segments = Math.floor(Math.abs(totalRotation) / anglePerSegment)
56
57
  if (Math.abs(totalRotation) > (segments * anglePerSegment)) segments++
57
58
  }
@@ -108,18 +109,18 @@ const extrudeRotate = (options, geometry) => {
108
109
  }
109
110
 
110
111
  const rotationPerSlice = totalRotation / segments
111
- const isCapped = Math.abs(totalRotation) < (Math.PI * 2)
112
+ const isCapped = Math.abs(totalRotation) < TAU
112
113
  const baseSlice = slice.fromSides(geom2.toSides(geometry))
113
114
  slice.reverse(baseSlice, baseSlice)
114
115
 
115
116
  const matrix = mat4.create()
116
117
  const createSlice = (progress, index, base) => {
117
118
  let Zrotation = rotationPerSlice * index + startAngle
118
- // fix rounding error when rotating 2 * PI radians
119
- if (totalRotation === Math.PI * 2 && index === segments) {
119
+ // fix rounding error when rotating TAU radians
120
+ if (totalRotation === TAU && index === segments) {
120
121
  Zrotation = startAngle
121
122
  }
122
- mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(), Math.PI / 2))
123
+ mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(), TAU / 4))
123
124
 
124
125
  return slice.transform(matrix, base)
125
126
  }
@@ -2,6 +2,8 @@ const test = require('ava')
2
2
 
3
3
  const { comparePoints, comparePolygonsAsPoints } = require('../../../test/helpers')
4
4
 
5
+ const { TAU } = require('../../maths/constants')
6
+
5
7
  const { geom2, geom3 } = require('../../geometries')
6
8
 
7
9
  const { extrudeRotate } = require('./index')
@@ -19,21 +21,21 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
19
21
  const geometry2 = geom2.fromPoints([[10, 8], [10, -8], [26, -8], [26, 8]])
20
22
 
21
23
  // test angle
22
- let geometry3 = extrudeRotate({ segments: 4, angle: Math.PI / 4 }, geometry2)
24
+ let geometry3 = extrudeRotate({ segments: 4, angle: TAU / 8 }, geometry2)
23
25
  let pts = geom3.toPoints(geometry3)
24
26
  const exp = [
25
- [[10, 4.898587196589413e-16, 8], [26, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, 8]],
26
- [[10, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
27
- [[10, -4.898587196589413e-16, -8], [10, 4.898587196589413e-16, 8], [7.0710678118654755, 7.071067811865475, 8]],
28
- [[10, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
29
- [[26, -4.898587196589413e-16, -8], [10, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, -8]],
30
- [[26, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
31
- [[26, 4.898587196589413e-16, 8], [26, -4.898587196589413e-16, -8], [18.38477631085024, 18.384776310850235, -8]],
32
- [[26, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
33
- [[7.071067811865476, 7.0710678118654755, -8], [7.071067811865476, 7.0710678118654755, 8], [18.384776310850242, 18.384776310850235, 8]],
34
- [[18.384776310850242, 18.384776310850235, 8], [18.384776310850242, 18.384776310850235, -8], [7.071067811865476, 7.0710678118654755, -8]],
35
- [[26, 4.898587196589413e-16, 8], [10, 4.898587196589413e-16, 8], [10, -4.898587196589413e-16, -8]],
36
- [[10, -4.898587196589413e-16, -8], [26, -4.898587196589413e-16, -8], [26, 4.898587196589413e-16, 8]]
27
+ [[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
28
+ [[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
29
+ [[10, 0, -8], [10, 0, 8], [7.0710678118654755, 7.071067811865475, 8]],
30
+ [[10, 0, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
31
+ [[26, 0, -8], [10, 0, -8], [7.0710678118654755, 7.071067811865475, -8]],
32
+ [[26, 0, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
33
+ [[26, 0, 8], [26, 0, -8], [18.38477631085024, 18.384776310850235, -8]],
34
+ [[26, 0, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
35
+ [[7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8]],
36
+ [[18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8]],
37
+ [[26, 0, 8], [10, 0, 8], [10, 0, -8]],
38
+ [[10, 0, -8], [26, 0, -8], [26, 0, 8]]
37
39
  ]
38
40
  t.notThrows(() => geom3.validate(geometry3))
39
41
  t.is(pts.length, 12)
@@ -54,7 +56,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
54
56
  const geometry2 = geom2.fromPoints([[10, 8], [10, -8], [26, -8], [26, 8]])
55
57
 
56
58
  // test startAngle
57
- let geometry3 = extrudeRotate({ segments: 5, startAngle: Math.PI / 4 }, geometry2)
59
+ let geometry3 = extrudeRotate({ segments: 5, startAngle: TAU / 8 }, geometry2)
58
60
  let pts = geom3.toPoints(geometry3)
59
61
  let exp = [
60
62
  [7.0710678118654755, 7.071067811865475, 8],
@@ -65,7 +67,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
65
67
  t.is(pts.length, 40)
66
68
  t.true(comparePoints(pts[0], exp))
67
69
 
68
- geometry3 = extrudeRotate({ segments: 5, startAngle: Math.PI / -4 }, geometry2)
70
+ geometry3 = extrudeRotate({ segments: 5, startAngle: -TAU / 8 }, geometry2)
69
71
  pts = geom3.toPoints(geometry3)
70
72
  exp = [
71
73
  [7.0710678118654755, -7.071067811865475, 8],
@@ -95,14 +97,14 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
95
97
  geometry2 = geom2.fromPoints([[0, 0], [2, 1], [1, 2], [1, 3], [3, 4], [0, 5]])
96
98
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
97
99
  pts = geom3.toPoints(geometry3)
98
- t.notThrows.skip(() => geom3.validate(geometry3))
100
+ t.notThrows(() => geom3.validate(geometry3))
99
101
  t.is(pts.length, 64)
100
102
 
101
103
  // test overlapping edges that produce hollow shape
102
104
  geometry2 = geom2.fromPoints([[30, 0], [30, 60], [0, 60], [0, 50], [10, 40], [10, 30], [0, 20], [0, 10], [10, 0]])
103
105
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
104
106
  pts = geom3.toPoints(geometry3)
105
- t.notThrows.skip(() => geom3.validate(geometry3))
107
+ t.notThrows(() => geom3.validate(geometry3))
106
108
  t.is(pts.length, 80)
107
109
  })
108
110
 
@@ -110,48 +112,48 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
110
112
  // overlap of Y axis; even number of + and - points
111
113
  let geometry = geom2.fromPoints([[-1, 8], [-1, -8], [7, -8], [7, 8]])
112
114
 
113
- let obs = extrudeRotate({ segments: 4, angle: Math.PI / 2 }, geometry)
115
+ let obs = extrudeRotate({ segments: 4, angle: TAU / 4 }, geometry)
114
116
  let pts = geom3.toPoints(obs)
115
117
  let exp = [
116
- [[0, 4.898587196589413e-16, 8], [7, 4.898587196589413e-16, 8], [-6.123233995736767e-17, 7, 8]],
117
- [[7, -4.898587196589413e-16, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [9.184850993605148e-16, 7, -8]],
118
- [[7, 4.898587196589413e-16, 8], [7, -4.898587196589413e-16, -8], [9.184850993605148e-16, 7, -8]],
119
- [[7, 4.898587196589413e-16, 8], [9.184850993605148e-16, 7, -8], [-6.123233995736767e-17, 7, 8]],
120
- [[4.898587196589413e-16, -2.999519565323715e-32, -8], [-4.898587196589413e-16, 2.999519565323715e-32, 8], [-6.123233995736767e-17, 7, 8]],
121
- [[-6.123233995736767e-17, 7, 8], [9.184850993605148e-16, 7, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8]],
122
- [[7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8], [0, -4.898587196589413e-16, -8]],
123
- [[0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8], [7, 4.898587196589413e-16, 8]]
118
+ [[0, 0, 8], [7, 0, 8], [0, 7, 8]],
119
+ [[7, 0, -8], [0, 0, -8], [0, 7, -8]],
120
+ [[7, 0, 8], [7, 0, -8], [0, 7, -8]],
121
+ [[7, 0, 8], [0, 7, -8], [0, 7, 8]],
122
+ [[0, 0, -8], [0, 0, 8], [0, 7, 8]],
123
+ [[0, 7, 8], [0, 7, -8], [0, 0, -8]],
124
+ [[7, 0, 8], [0, 0, 8], [0, 0, -8]],
125
+ [[0, 0, -8], [7, 0, -8], [7, 0, 8]]
124
126
  ]
125
- t.notThrows.skip(() => geom3.validate(obs))
127
+ t.notThrows(() => geom3.validate(obs))
126
128
  t.is(pts.length, 8)
127
129
  t.true(comparePolygonsAsPoints(pts, exp))
128
130
 
129
131
  // overlap of Y axis; larger number of - points
130
132
  geometry = geom2.fromPoints([[-1, 8], [-2, 4], [-1, -8], [7, -8], [7, 8]])
131
133
 
132
- obs = extrudeRotate({ segments: 8, angle: Math.PI / 2 }, geometry)
134
+ obs = extrudeRotate({ segments: 8, angle: TAU / 4 }, geometry)
133
135
  pts = geom3.toPoints(obs)
134
136
  exp = [
135
- [[1, -4.898587196589413e-16, -8], [3.4638242249419727e-16, -3.4638242249419736e-16, -8], [0.7071067811865479, 0.7071067811865471, -8]],
136
- [[2, 2.4492935982947064e-16, 4], [1, -4.898587196589413e-16, -8], [0.7071067811865479, 0.7071067811865471, -8]],
137
- [[2, 2.4492935982947064e-16, 4], [0.7071067811865479, 0.7071067811865471, -8], [1.414213562373095, 1.4142135623730951, 4]],
138
- [[1, 4.898587196589413e-16, 8], [2, 2.4492935982947064e-16, 4], [1.414213562373095, 1.4142135623730951, 4]],
139
- [[1, 4.898587196589413e-16, 8], [1.414213562373095, 1.4142135623730951, 4], [0.7071067811865472, 0.7071067811865478, 8]],
140
- [[0, 4.898587196589413e-16, 8], [1, 4.898587196589413e-16, 8], [0.7071067811865472, 0.7071067811865478, 8]],
141
- [[0.7071067811865479, 0.7071067811865471, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [5.51091059616309e-16, 1, -8]],
142
- [[1.414213562373095, 1.4142135623730951, 4], [0.7071067811865479, 0.7071067811865471, -8], [5.51091059616309e-16, 1, -8]],
143
- [[1.414213562373095, 1.4142135623730951, 4], [5.51091059616309e-16, 1, -8], [-1.2246467991473532e-16, 2, 4]],
144
- [[0.7071067811865472, 0.7071067811865478, 8], [1.414213562373095, 1.4142135623730951, 4], [-1.2246467991473532e-16, 2, 4]],
145
- [[0.7071067811865472, 0.7071067811865478, 8], [-1.2246467991473532e-16, 2, 4], [-4.286263797015736e-16, 1, 8]],
146
- [[-3.4638242249419727e-16, 3.4638242249419736e-16, 8], [0.7071067811865472, 0.7071067811865478, 8], [-4.286263797015736e-16, 1, 8]],
147
- [[5.51091059616309e-16, 1, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [-4.898587196589415e-16, 2.9995195653237163e-32, 8]],
148
- [[-4.898587196589415e-16, 2.9995195653237163e-32, 8], [-4.286263797015738e-16, 1, 8], [-1.2246467991473544e-16, 2, 4]],
149
- [[-1.2246467991473544e-16, 2, 4], [5.51091059616309e-16, 1, -8], [-4.898587196589415e-16, 2.9995195653237163e-32, 8]],
150
- [[0, 4.898587196589413e-16, 8], [0, -4.898587196589413e-16, -8], [1, -4.898587196589413e-16, -8]],
151
- [[2, 2.4492935982947064e-16, 4], [1, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8]],
152
- [[0, 4.898587196589413e-16, 8], [1, -4.898587196589413e-16, -8], [2, 2.4492935982947064e-16, 4]]
137
+ [[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
138
+ [[2, 0, 4], [1, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
139
+ [[2, 0, 4], [0.7071067811865476, 0.7071067811865475, -8], [1.4142135623730951, 1.414213562373095, 4]],
140
+ [[1, 0, 8], [2, 0, 4], [1.4142135623730951, 1.414213562373095, 4]],
141
+ [[1, 0, 8], [1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, 8]],
142
+ [[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]],
143
+ [[0.7071067811865476, 0.7071067811865475, -8], [0, 0, -8], [0, 1, -8]],
144
+ [[1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, -8], [0, 1, -8]],
145
+ [[1.4142135623730951, 1.414213562373095, 4], [0, 1, -8], [0, 2, 4]],
146
+ [[0.7071067811865476, 0.7071067811865475, 8], [1.4142135623730951, 1.414213562373095, 4], [0, 2, 4]],
147
+ [[0.7071067811865476, 0.7071067811865475, 8], [0, 2, 4], [0, 1, 8]],
148
+ [[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]],
149
+ [[0, 1, -8], [0, 0, -8], [0, 0, 8]],
150
+ [[0, 0, 8], [0, 1, 8], [0, 2, 4]],
151
+ [[0, 2, 4], [0, 1, -8], [0, 0, 8]],
152
+ [[0, 0, 8], [0, 0, -8], [1, 0, -8]],
153
+ [[2, 0, 4], [1, 0, 8], [0, 0, 8]],
154
+ [[0, 0, 8], [1, 0, -8], [2, 0, 4]]
153
155
  ]
154
- t.notThrows.skip(() => geom3.validate(obs))
156
+ t.notThrows(() => geom3.validate(obs))
155
157
  t.is(pts.length, 18)
156
158
  t.true(comparePolygonsAsPoints(pts, exp))
157
159
  })
@@ -29,10 +29,10 @@ test('project (defaults)', (t) => {
29
29
  [0, -5.000013333333333],
30
30
  [5.000013333333333, 0],
31
31
  [-5.000013333333333, 0],
32
- [-2.9999933333333333, 0],
33
- [2.9999933333333333, 0],
34
32
  [0, 2.9999933333333333],
33
+ [-2.9999933333333333, 0],
35
34
  [0, -2.9999933333333333],
35
+ [2.9999933333333333, 0],
36
36
  [0, 5.000013333333333]
37
37
  ]
38
38
  t.true(comparePoints(pts, exp))
@@ -1,5 +1,6 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../../../maths/constants')
3
4
  const { mat4 } = require('../../../maths')
4
5
 
5
6
  const { calculatePlane, fromPoints, transform } = require('./index')
@@ -15,11 +16,11 @@ test('slice: calculatePlane() returns correct plans for various slices', (t) =>
15
16
  const plane2 = calculatePlane(slice2)
16
17
  t.true(compareVectors(plane2, [0, 0, 1, 0]))
17
18
 
18
- const slice3 = transform(mat4.fromXRotation(mat4.create(), Math.PI / 2), slice2)
19
+ const slice3 = transform(mat4.fromXRotation(mat4.create(), TAU / 4), slice2)
19
20
  const plane3 = calculatePlane(slice3)
20
21
  t.true(compareVectors(plane3, [0, -1, 0, 0]))
21
22
 
22
- const slice4 = transform(mat4.fromZRotation(mat4.create(), Math.PI / 2), slice3)
23
+ const slice4 = transform(mat4.fromZRotation(mat4.create(), TAU / 4), slice3)
23
24
  const plane4 = calculatePlane(slice4)
24
25
  t.true(compareVectors(plane4, [1, 0, 0, 0]))
25
26
 
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * Represents a 3D geometry consisting of a list of edges.
3
+ * @see {@link slice} for data structure information.
2
4
  * @module modeling/extrusions/slice
3
5
  */
4
6
  module.exports = {
@@ -40,7 +40,7 @@ const repair = (slice) => {
40
40
  let bestReplacement
41
41
  missingOut.forEach((key2) => {
42
42
  const v2 = vertexMap.get(key2)
43
- const distance = Math.hypot(v1[0] - v2[0], v1[1] - v2[1])
43
+ const distance = vec3.distance(v1, v2)
44
44
  if (distance < bestDistance) {
45
45
  bestDistance = distance
46
46
  bestReplacement = v2
@@ -0,0 +1,12 @@
1
+ import { Geometry } from '../../geometries/types'
2
+ import RecursiveArray from '../../utils/recursiveArray'
3
+
4
+ export interface GeneralizeOptions {
5
+ snap?: boolean
6
+ simplify?: boolean
7
+ triangulate?: boolean
8
+ }
9
+
10
+ export function generalize<T extends Geometry>(options: GeneralizeOptions, geometry: T): T
11
+ export function generalize<T extends Geometry>(options: GeneralizeOptions, ...geometries: RecursiveArray<T>): Array<T>
12
+ export function generalize(options: GeneralizeOptions, ...geometries: RecursiveArray<Geometry>): Array<Geometry>
@@ -2,6 +2,8 @@ const test = require('ava')
2
2
 
3
3
  const { comparePolygonsAsPoints } = require('../../../test/helpers')
4
4
 
5
+ const { TAU } = require('../../maths/constants')
6
+
5
7
  const { geom3 } = require('../../geometries')
6
8
 
7
9
  const { cuboid } = require('../../primitives')
@@ -9,7 +11,7 @@ const { cuboid } = require('../../primitives')
9
11
  const { generalize } = require('./index')
10
12
 
11
13
  test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
12
- const geometry1 = cuboid({ size: [Math.PI, Math.PI / 2, Math.PI * 2] })
14
+ const geometry1 = cuboid({ size: [TAU / 2, TAU / 4, TAU] })
13
15
 
14
16
  // apply no modifications
15
17
  let result = generalize({}, geometry1)
@@ -0,0 +1,2 @@
1
+ export { default as generalize } from './generalize'
2
+ export { default as snap } from './snap'
@@ -28,15 +28,15 @@ const addSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, verte
28
28
  } else {
29
29
  sidemap.get(newsidetag).push(newsideobj)
30
30
  }
31
- if (starttag in vertextag2sidestart) {
32
- vertextag2sidestart[starttag].push(newsidetag)
31
+ if (vertextag2sidestart.has(starttag)) {
32
+ vertextag2sidestart.get(starttag).push(newsidetag)
33
33
  } else {
34
- vertextag2sidestart[starttag] = [newsidetag]
34
+ vertextag2sidestart.set(starttag, [newsidetag])
35
35
  }
36
- if (endtag in vertextag2sideend) {
37
- vertextag2sideend[endtag].push(newsidetag)
36
+ if (vertextag2sideend.has(endtag)) {
37
+ vertextag2sideend.get(endtag).push(newsidetag)
38
38
  } else {
39
- vertextag2sideend[endtag] = [newsidetag]
39
+ vertextag2sideend.set(endtag, [newsidetag])
40
40
  }
41
41
  return newsidetag
42
42
  }
@@ -67,18 +67,18 @@ const deleteSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, ve
67
67
  }
68
68
 
69
69
  // adjust start and end lists
70
- idx = vertextag2sidestart[starttag].indexOf(sidetag)
70
+ idx = vertextag2sidestart.get(starttag).indexOf(sidetag)
71
71
  if (assert && idx < 0) throw new Error('assert failed')
72
- vertextag2sidestart[starttag].splice(idx, 1)
73
- if (vertextag2sidestart[starttag].length === 0) {
74
- delete vertextag2sidestart[starttag]
72
+ vertextag2sidestart.get(starttag).splice(idx, 1)
73
+ if (vertextag2sidestart.get(starttag).length === 0) {
74
+ vertextag2sidestart.delete(starttag)
75
75
  }
76
76
 
77
- idx = vertextag2sideend[endtag].indexOf(sidetag)
77
+ idx = vertextag2sideend.get(endtag).indexOf(sidetag)
78
78
  if (assert && idx < 0) throw new Error('assert failed')
79
- vertextag2sideend[endtag].splice(idx, 1)
80
- if (vertextag2sideend[endtag].length === 0) {
81
- delete vertextag2sideend[endtag]
79
+ vertextag2sideend.get(endtag).splice(idx, 1)
80
+ if (vertextag2sideend.get(endtag).length === 0) {
81
+ vertextag2sideend.delete(endtag)
82
82
  }
83
83
  }
84
84
 
@@ -158,25 +158,24 @@ const insertTjunctions = (polygons) => {
158
158
  }
159
159
 
160
160
  if (sidemap.size > 0) {
161
- // console.log('insertTjunctions',sidemap.size)
162
161
  // STEP 2 : create a list of starting sides and ending sides
163
- const vertextag2sidestart = {}
164
- const vertextag2sideend = {}
165
- const sidestocheck = {}
162
+ const vertextag2sidestart = new Map()
163
+ const vertextag2sideend = new Map()
164
+ const sidesToCheck = new Map()
166
165
  for (const [sidetag, sideobjs] of sidemap) {
167
- sidestocheck[sidetag] = true
166
+ sidesToCheck.set(sidetag, true)
168
167
  sideobjs.forEach((sideobj) => {
169
168
  const starttag = getTag(sideobj.vertex0)
170
169
  const endtag = getTag(sideobj.vertex1)
171
- if (starttag in vertextag2sidestart) {
172
- vertextag2sidestart[starttag].push(sidetag)
170
+ if (vertextag2sidestart.has(starttag)) {
171
+ vertextag2sidestart.get(starttag).push(sidetag)
173
172
  } else {
174
- vertextag2sidestart[starttag] = [sidetag]
173
+ vertextag2sidestart.set(starttag, [sidetag])
175
174
  }
176
- if (endtag in vertextag2sideend) {
177
- vertextag2sideend[endtag].push(sidetag)
175
+ if (vertextag2sideend.has(endtag)) {
176
+ vertextag2sideend.get(endtag).push(sidetag)
178
177
  } else {
179
- vertextag2sideend[endtag] = [sidetag]
178
+ vertextag2sideend.set(endtag, [sidetag])
180
179
  }
181
180
  })
182
181
  }
@@ -187,13 +186,13 @@ const insertTjunctions = (polygons) => {
187
186
  if (sidemap.size === 0) break
188
187
 
189
188
  for (const sidetag of sidemap.keys()) {
190
- sidestocheck[sidetag] = true
189
+ sidesToCheck.set(sidetag, true)
191
190
  }
192
191
 
193
192
  let donesomething = false
194
193
  while (true) {
195
- const sidetags = Object.keys(sidestocheck)
196
- if (sidetags.length === 0) break // sidestocheck is empty, we're done!
194
+ const sidetags = Array.from(sidesToCheck.keys())
195
+ if (sidetags.length === 0) break // sidesToCheck is empty, we're done!
197
196
  const sidetagtocheck = sidetags[0]
198
197
  let donewithside = true
199
198
  if (sidemap.has(sidetagtocheck)) {
@@ -207,12 +206,12 @@ const insertTjunctions = (polygons) => {
207
206
  const endvertextag = getTag(endvertex)
208
207
  let matchingsides = []
209
208
  if (directionindex === 0) {
210
- if (startvertextag in vertextag2sideend) {
211
- matchingsides = vertextag2sideend[startvertextag]
209
+ if (vertextag2sideend.has(startvertextag)) {
210
+ matchingsides = vertextag2sideend.get(startvertextag)
212
211
  }
213
212
  } else {
214
- if (startvertextag in vertextag2sidestart) {
215
- matchingsides = vertextag2sidestart[startvertextag]
213
+ if (vertextag2sidestart.has(startvertextag)) {
214
+ matchingsides = vertextag2sidestart.get(startvertextag)
216
215
  }
217
216
  }
218
217
  for (let matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
@@ -267,8 +266,8 @@ const insertTjunctions = (polygons) => {
267
266
  deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, matchingside.vertex1, polygonindex)
268
267
  const newsidetag1 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, endvertex, polygonindex)
269
268
  const newsidetag2 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, matchingside.vertex1, polygonindex)
270
- if (newsidetag1 !== null) sidestocheck[newsidetag1] = true
271
- if (newsidetag2 !== null) sidestocheck[newsidetag2] = true
269
+ if (newsidetag1 !== null) sidesToCheck.set(newsidetag1, true)
270
+ if (newsidetag2 !== null) sidesToCheck.set(newsidetag2, true)
272
271
  donewithside = false
273
272
  directionindex = 2 // skip reverse direction check
274
273
  donesomething = true
@@ -280,7 +279,7 @@ const insertTjunctions = (polygons) => {
280
279
  } // for directionindex
281
280
  } // if(sidetagtocheck in sidemap)
282
281
  if (donewithside) {
283
- delete sidestocheck[sidetagtocheck]
282
+ sidesToCheck.delete(sidetagtocheck)
284
283
  }
285
284
  }
286
285
  if (!donesomething) break
@@ -0,0 +1,6 @@
1
+ import { Geometry } from '../../geometries/types'
2
+ import RecursiveArray from '../../utils/recursiveArray'
3
+
4
+ export function snap<T extends Geometry>(geometry: T): T
5
+ export function snap<T extends Geometry>(...geometries: RecursiveArray<T>): Array<T>
6
+ export function snap(...geometries: RecursiveArray<Geometry>): Array<Geometry>
@@ -2,6 +2,8 @@ const test = require('ava')
2
2
 
3
3
  const { comparePoints, comparePolygonsAsPoints } = require('../../../test/helpers')
4
4
 
5
+ const { TAU } = require('../../maths/constants')
6
+
5
7
  const { geom2, geom3, path2 } = require('../../geometries')
6
8
 
7
9
  const { arc, rectangle, cuboid } = require('../../primitives')
@@ -12,7 +14,7 @@ test('snap: snap of a path2 produces an expected path2', (t) => {
12
14
  const geometry1 = path2.create()
13
15
  const geometry2 = arc({ radius: 1 / 2, segments: 8 })
14
16
  const geometry3 = arc({ radius: 1.3333333333333333 / 2, segments: 8 })
15
- const geometry4 = arc({ radius: Math.PI * 1000 / 2, segments: 8 })
17
+ const geometry4 = arc({ radius: TAU / 4 * 1000, segments: 8 })
16
18
 
17
19
  const results = snap(geometry1, geometry2, geometry3, geometry4)
18
20
  t.is(results.length, 4)
@@ -56,7 +58,7 @@ test('snap: snap of a geom2 produces an expected geom2', (t) => {
56
58
  const geometry1 = geom2.create()
57
59
  const geometry2 = rectangle({ size: [1, 1, 1] })
58
60
  const geometry3 = rectangle({ size: [1.3333333333333333, 1.3333333333333333, 1.3333333333333333] })
59
- const geometry4 = rectangle({ size: [Math.PI * 1000, Math.PI * 1000, Math.PI * 1000] })
61
+ const geometry4 = rectangle({ size: [TAU / 2 * 1000, TAU / 2 * 1000, TAU / 2 * 1000] })
60
62
 
61
63
  const results = snap(geometry1, geometry2, geometry3, geometry4)
62
64
  t.is(results.length, 4)
@@ -88,7 +90,7 @@ test('snap: snap of a geom3 produces an expected geom3', (t) => {
88
90
  const geometry1 = geom3.create()
89
91
  const geometry2 = cuboid({ size: [1, 1, 1] })
90
92
  const geometry3 = cuboid({ size: [1.3333333333333333, 1.3333333333333333, 1.3333333333333333] })
91
- const geometry4 = cuboid({ size: [Math.PI * 1000, Math.PI * 1000, Math.PI * 1000] })
93
+ const geometry4 = cuboid({ size: [TAU / 2 * 1000, TAU / 2 * 1000, TAU / 2 * 1000] })
92
94
 
93
95
  const results = snap(geometry1, geometry2, geometry3, geometry4)
94
96
  t.is(results.length, 4)
@@ -14,7 +14,7 @@ const path2 = require('../../geometries/path2')
14
14
  * @alias module:modeling/transforms.rotate
15
15
  *
16
16
  * @example
17
- * const newsphere = rotate([Math.PI / 4, 0, 0], sphere())
17
+ * const newsphere = rotate([TAU / 8, 0, 0], sphere())
18
18
  */
19
19
  const rotate = (angles, ...objects) => {
20
20
  if (!Array.isArray(angles)) throw new Error('angles must be an array')
@@ -1,5 +1,7 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../../maths/constants')
4
+
3
5
  const { geom2, geom3, path2 } = require('../../geometries')
4
6
 
5
7
  const { rotate, rotateX, rotateY, rotateZ } = require('./index')
@@ -10,7 +12,7 @@ test('rotate: rotating of a path2 produces expected changes to points', (t) => {
10
12
  const geometry = path2.fromPoints({}, [[1, 0], [0, 1], [-1, 0]])
11
13
 
12
14
  // rotate about Z
13
- let rotated = rotate([0, 0, Math.PI / 2], geometry)
15
+ let rotated = rotate([0, 0, TAU / 4], geometry)
14
16
  let obs = path2.toPoints(rotated)
15
17
  const exp = [
16
18
  new Float32Array([0, 1]),
@@ -19,7 +21,7 @@ test('rotate: rotating of a path2 produces expected changes to points', (t) => {
19
21
  ]
20
22
  t.true(comparePoints(obs, exp))
21
23
 
22
- rotated = rotateZ(Math.PI / 2, geometry)
24
+ rotated = rotateZ(TAU / 4, geometry)
23
25
  obs = path2.toPoints(rotated)
24
26
  t.notThrows(() => path2.validate(rotated))
25
27
  t.true(comparePoints(obs, exp))
@@ -29,7 +31,7 @@ test('rotate: rotating of a geom2 produces expected changes to points', (t) => {
29
31
  const geometry = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
30
32
 
31
33
  // rotate about Z
32
- let rotated = rotate([0, 0, -Math.PI / 2], geometry)
34
+ let rotated = rotate([0, 0, -TAU / 4], geometry)
33
35
  let obs = geom2.toPoints(rotated)
34
36
  const exp = [
35
37
  new Float32Array([0, 0]),
@@ -39,7 +41,7 @@ test('rotate: rotating of a geom2 produces expected changes to points', (t) => {
39
41
  t.notThrows(() => geom2.validate(rotated))
40
42
  t.true(comparePoints(obs, exp))
41
43
 
42
- rotated = rotateZ(-Math.PI / 2, geometry)
44
+ rotated = rotateZ(-TAU / 4, geometry)
43
45
  obs = geom2.toPoints(rotated)
44
46
  t.notThrows(() => geom2.validate(rotated))
45
47
  t.true(comparePoints(obs, exp))
@@ -57,7 +59,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
57
59
  const geometry = geom3.fromPoints(points)
58
60
 
59
61
  // rotate about X
60
- let rotated = rotate([Math.PI / 2], geometry)
62
+ let rotated = rotate([TAU / 4], geometry)
61
63
  let obs = geom3.toPoints(rotated)
62
64
  let exp = [
63
65
  [[-2, 12, -7.000000000000001], [-2, -18, -6.999999999999999],
@@ -76,13 +78,13 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
76
78
  t.notThrows(() => geom3.validate(rotated))
77
79
  t.true(comparePolygonsAsPoints(obs, exp))
78
80
 
79
- rotated = rotateX(Math.PI / 2, geometry)
81
+ rotated = rotateX(TAU / 4, geometry)
80
82
  obs = geom3.toPoints(rotated)
81
83
  t.notThrows(() => geom3.validate(rotated))
82
84
  t.true(comparePolygonsAsPoints(obs, exp))
83
85
 
84
86
  // rotate about Y
85
- rotated = rotate([0, -Math.PI / 2], geometry)
87
+ rotated = rotate([0, -TAU / 4], geometry)
86
88
  obs = geom3.toPoints(rotated)
87
89
  exp = [
88
90
  [[12, -7, -2.000000000000001], [-18, -7, -1.999999999999999],
@@ -101,12 +103,12 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
101
103
  t.notThrows(() => geom3.validate(rotated))
102
104
  t.true(comparePolygonsAsPoints(obs, exp))
103
105
 
104
- rotated = rotateY(-Math.PI / 2, geometry)
106
+ rotated = rotateY(-TAU / 4, geometry)
105
107
  obs = geom3.toPoints(rotated)
106
108
  t.true(comparePolygonsAsPoints(obs, exp))
107
109
 
108
110
  // rotate about Z
109
- rotated = rotate([0, 0, Math.PI], geometry)
111
+ rotated = rotate([0, 0, TAU / 2], geometry)
110
112
  obs = geom3.toPoints(rotated)
111
113
  exp = [
112
114
  [[2.000000000000001, 7, -12], [2.000000000000001, 7, 18],
@@ -125,7 +127,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
125
127
  t.notThrows(() => geom3.validate(rotated))
126
128
  t.true(comparePolygonsAsPoints(obs, exp))
127
129
 
128
- rotated = rotateZ(Math.PI, geometry)
130
+ rotated = rotateZ(TAU / 2, geometry)
129
131
  obs = geom3.toPoints(rotated)
130
132
  t.notThrows(() => geom3.validate(rotated))
131
133
  t.true(comparePolygonsAsPoints(obs, exp))
@@ -136,7 +138,7 @@ test('rotate: rotating of multiple objects produces expected changes', (t) => {
136
138
  const geometry1 = path2.fromPoints({}, [[-5, 5], [5, 5], [-5, -5], [10, -5]])
137
139
  const geometry2 = geom2.fromPoints([[-5, -5], [0, 5], [10, -5]])
138
140
 
139
- const rotated = rotate([0, 0, Math.PI / 2], junk, geometry1, geometry2)
141
+ const rotated = rotate([0, 0, TAU / 4], junk, geometry1, geometry2)
140
142
 
141
143
  t.is(rotated[0], junk)
142
144
 
@@ -12,7 +12,7 @@ const path2 = require('../../geometries/path2')
12
12
  * @alias module:modeling/transforms.transform
13
13
  *
14
14
  * @example
15
- * const newsphere = transform(mat4.rotateX(Math.PI/4), sphere())
15
+ * const newsphere = transform(mat4.rotateX(TAU / 8), sphere())
16
16
  */
17
17
  const transform = (matrix, ...objects) => {
18
18
  // TODO how to check that the matrix is REAL?