@jscad/modeling 2.9.6 → 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 (67) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +12 -2
  3. package/dist/jscad-modeling.min.js +28 -28
  4. package/package.json +2 -2
  5. package/src/geometries/path2/appendArc.js +6 -5
  6. package/src/geometries/path2/appendArc.test.js +3 -1
  7. package/src/geometries/path2/appendBezier.js +2 -1
  8. package/src/geometries/path2/transform.js +1 -1
  9. package/src/index.d.ts +1 -0
  10. package/src/maths/constants.js +10 -0
  11. package/src/maths/mat4/fromRotation.js +1 -1
  12. package/src/maths/mat4/fromTaitBryanRotation.js +1 -1
  13. package/src/maths/mat4/fromXRotation.js +1 -1
  14. package/src/maths/mat4/fromYRotation.js +1 -1
  15. package/src/maths/mat4/fromZRotation.js +1 -1
  16. package/src/maths/mat4/invert.test.js +5 -2
  17. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  18. package/src/maths/mat4/isOnlyTransformScale.test.js +3 -1
  19. package/src/maths/rotation.test.js +5 -2
  20. package/src/maths/utils/trigonometry.js +6 -6
  21. package/src/maths/utils/trigonometry.test.js +10 -8
  22. package/src/maths/vec2/fromAngleDegrees.js +1 -1
  23. package/src/maths/vec2/fromAngleRadians.test.js +4 -1
  24. package/src/maths/vec2/normal.js +3 -1
  25. package/src/maths/vec2/rotate.test.js +4 -1
  26. package/src/maths/vec3/rotateX.test.js +4 -1
  27. package/src/maths/vec3/rotateY.test.js +4 -1
  28. package/src/maths/vec3/rotateZ.test.js +4 -1
  29. package/src/operations/expansions/expand.test.js +3 -1
  30. package/src/operations/expansions/expandShell.js +4 -4
  31. package/src/operations/expansions/offsetFromPoints.js +2 -2
  32. package/src/operations/extrusions/extrudeFromSlices.test.js +3 -2
  33. package/src/operations/extrusions/extrudeLinear.test.js +5 -3
  34. package/src/operations/extrusions/extrudeRectangular.js +1 -1
  35. package/src/operations/extrusions/extrudeRectangular.test.js +5 -3
  36. package/src/operations/extrusions/extrudeRotate.js +14 -13
  37. package/src/operations/extrusions/extrudeRotate.test.js +7 -5
  38. package/src/operations/extrusions/slice/calculatePlane.test.js +3 -2
  39. package/src/operations/extrusions/slice/index.js +2 -0
  40. package/src/operations/modifiers/generalize.d.ts +12 -0
  41. package/src/operations/modifiers/generalize.test.js +3 -1
  42. package/src/operations/modifiers/index.d.ts +2 -0
  43. package/src/operations/modifiers/snap.d.ts +6 -0
  44. package/src/operations/modifiers/snap.test.js +5 -3
  45. package/src/operations/transforms/rotate.js +1 -1
  46. package/src/operations/transforms/rotate.test.js +13 -11
  47. package/src/operations/transforms/transform.js +1 -1
  48. package/src/primitives/arc.js +8 -8
  49. package/src/primitives/arc.test.js +9 -8
  50. package/src/primitives/circle.js +4 -2
  51. package/src/primitives/circle.test.js +6 -5
  52. package/src/primitives/cylinderElliptic.js +11 -11
  53. package/src/primitives/cylinderElliptic.test.js +3 -2
  54. package/src/primitives/ellipse.js +10 -10
  55. package/src/primitives/ellipse.test.js +6 -5
  56. package/src/primitives/ellipsoid.js +3 -2
  57. package/src/primitives/roundedCuboid.js +6 -6
  58. package/src/primitives/roundedCylinder.js +3 -3
  59. package/src/primitives/roundedRectangle.js +5 -5
  60. package/src/primitives/star.js +3 -2
  61. package/src/primitives/torus.js +4 -2
  62. package/src/primitives/torus.test.js +5 -3
  63. package/src/primitives/triangle.test.js +2 -1
  64. package/src/utils/degToRad.test.js +5 -5
  65. package/src/utils/radToDeg.test.js +6 -6
  66. package/src/utils/radiusToSegments.js +6 -4
  67. package/src/utils/radiusToSegments.test.js +5 -3
@@ -1,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  * Determine whether the given matrix is only translate and/or scale.
4
- * This code returns true for PI rotation as it can be interpreted as scale.
4
+ * This code returns true for TAU / 2 rotation as it can be interpreted as scale.
5
5
  *
6
6
  * @param {mat4} matrix - the matrix
7
7
  * @returns {Boolean} true if matrix is for translate and/or scale
@@ -1,9 +1,11 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../constants')
4
+
3
5
  const { isOnlyTransformScale, create, fromTranslation, fromTaitBryanRotation, fromScaling, invert, multiply } = require('./index')
4
6
 
5
7
  test('mat4: isOnlyTransformScale() should return true for right angles', (t) => {
6
- let someRotation = fromTaitBryanRotation(create(), Math.PI, 0, 0)
8
+ let someRotation = fromTaitBryanRotation(create(), TAU / 2, 0, 0)
7
9
  t.true(isOnlyTransformScale(someRotation))
8
10
  t.true(isOnlyTransformScale(invert(create(), someRotation)))
9
11
 
@@ -2,15 +2,18 @@ const test = require('ava')
2
2
 
3
3
  const { compareVectors } = require('../../test/helpers/index')
4
4
 
5
- const { mat4, vec2, vec3 } = require('./index')
5
+ const { constants, mat4, vec2, vec3 } = require('./index')
6
6
 
7
7
  // ALL POSITIVE ROTATIONS ARE CLOCKWISE
8
8
  // see https://webglfundamentals.org/webgl/lessons/webgl-3d-orthographic.html
9
9
  // IN A LEFT-HANDED COORDINATE SYSTEM
10
10
 
11
+ // JSCAD IS RIGHT-HANDED COORDINATE SYSTEM
12
+ // WHERE POSITIVE ROTATIONS ARE COUNTER-CLOCKWISE
13
+
11
14
  // identity matrices for comparisons
12
15
 
13
- const rad90 = Math.PI / 2
16
+ const rad90 = constants.TAU / 4
14
17
 
15
18
  // +90 degree rotation about X
16
19
  const cwX90Matrix = [
@@ -6,28 +6,28 @@ const { NEPS } = require('../constants')
6
6
  const rezero = (n) => Math.abs(n) < NEPS ? 0 : n
7
7
 
8
8
  /**
9
- * Return Math.sin but accurate for 90 degree rotations.
9
+ * Return Math.sin but accurate for TAU / 4 rotations.
10
10
  * Fixes rounding errors when sin should be 0.
11
11
  *
12
12
  * @param {Number} radians - angle in radians
13
13
  * @returns {Number} sine of the given angle
14
14
  * @alias module:modeling/utils.sin
15
15
  * @example
16
- * sin(Math.PI) == 0
17
- * sin(2 * Math.PI) == 0
16
+ * sin(TAU / 2) == 0
17
+ * sin(TAU) == 0
18
18
  */
19
19
  const sin = (radians) => rezero(Math.sin(radians))
20
20
 
21
21
  /**
22
- * Return Math.cos but accurate for 90 degree rotations.
22
+ * Return Math.cos but accurate for TAU / 4 rotations.
23
23
  * Fixes rounding errors when cos should be 0.
24
24
  *
25
25
  * @param {Number} radians - angle in radians
26
26
  * @returns {Number} cosine of the given angle
27
27
  * @alias module:modeling/utils.cos
28
28
  * @example
29
- * cos(0.5 * Math.PI) == 0
30
- * cos(1.5 * Math.PI) == 0
29
+ * cos(TAU * 0.25) == 0
30
+ * cos(TAU * 0.75) == 0
31
31
  */
32
32
  const cos = (radians) => rezero(Math.cos(radians))
33
33
 
@@ -1,14 +1,16 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../constants')
4
+
3
5
  const { cos, sin } = require('./trigonometry')
4
6
 
5
7
  test('utils: sin() should return rounded values', (t) => {
6
8
  t.is(sin(0), 0)
7
9
  t.is(sin(9), Math.sin(9))
8
- t.is(sin(0.5 * Math.PI), 1)
9
- t.is(sin(1.0 * Math.PI), 0)
10
- t.is(sin(1.5 * Math.PI), -1)
11
- t.is(sin(2.0 * Math.PI), 0)
10
+ t.is(sin(0.25 * TAU), 1)
11
+ t.is(sin(0.5 * TAU), 0)
12
+ t.is(sin(0.75 * TAU), -1)
13
+ t.is(sin(TAU), 0)
12
14
  t.is(sin(NaN), NaN)
13
15
  t.is(sin(Infinity), NaN)
14
16
  })
@@ -16,10 +18,10 @@ test('utils: sin() should return rounded values', (t) => {
16
18
  test('utils: cos() should return rounded values', (t) => {
17
19
  t.is(cos(0), 1)
18
20
  t.is(cos(9), Math.cos(9))
19
- t.is(cos(0.5 * Math.PI), 0)
20
- t.is(cos(1.0 * Math.PI), -1)
21
- t.is(cos(1.5 * Math.PI), 0)
22
- t.is(cos(2.0 * Math.PI), 1)
21
+ t.is(cos(0.25 * TAU), 0)
22
+ t.is(cos(0.5 * TAU), -1)
23
+ t.is(cos(0.75 * TAU), 0)
24
+ t.is(cos(TAU), 1)
23
25
  t.is(cos(NaN), NaN)
24
26
  t.is(cos(Infinity), NaN)
25
27
  })
@@ -8,6 +8,6 @@ const fromAngleRadians = require('./fromAngleRadians')
8
8
  * @returns {vec2} out
9
9
  * @alias module:modeling/maths/vec2.fromAngleDegrees
10
10
  */
11
- const fromAngleDegrees = (out, degrees) => fromAngleRadians(out, Math.PI * degrees / 180)
11
+ const fromAngleDegrees = (out, degrees) => fromAngleRadians(out, degrees * 0.017453292519943295)
12
12
 
13
13
  module.exports = fromAngleDegrees
@@ -1,4 +1,7 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+
2
5
  const { fromAngleRadians, create } = require('./index')
3
6
 
4
7
  const { compareVectors } = require('../../../test/helpers/index')
@@ -7,6 +10,6 @@ test('vec2: fromAngleRadians() should return a new vec2 with correct values', (t
7
10
  const obs1 = fromAngleRadians(create(), 0)
8
11
  t.true(compareVectors(obs1, [1.0, 0.0]))
9
12
 
10
- const obs2 = fromAngleRadians(obs1, Math.PI)
13
+ const obs2 = fromAngleRadians(obs1, TAU / 2)
11
14
  t.true(compareVectors(obs2, [-1, 1.2246468525851679e-16]))
12
15
  })
@@ -1,3 +1,5 @@
1
+ const { TAU } = require('../constants')
2
+
1
3
  const create = require('./create')
2
4
  const rotate = require('./rotate')
3
5
 
@@ -10,6 +12,6 @@ const rotate = require('./rotate')
10
12
  * @returns {vec2} out
11
13
  * @alias module:modeling/maths/vec2.normal
12
14
  */
13
- const normal = (out, vector) => rotate(out, vector, create(), (Math.PI / 2))
15
+ const normal = (out, vector) => rotate(out, vector, create(), (TAU / 4))
14
16
 
15
17
  module.exports = normal
@@ -1,10 +1,13 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+
2
5
  const { rotate, fromValues } = require('./index')
3
6
 
4
7
  const { compareVectors } = require('../../../test/helpers/index')
5
8
 
6
9
  test('vec2: rotate() called with three parameters should update a vec2 with correct values', (t) => {
7
- const radians = 90 * Math.PI / 180
10
+ const radians = TAU / 4
8
11
 
9
12
  const obs1 = fromValues(0, 0)
10
13
  const ret1 = rotate(obs1, [0, 0], [0, 0], 0)
@@ -1,10 +1,13 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+
2
5
  const { rotateX, fromValues } = require('./index')
3
6
 
4
7
  const { compareVectors } = require('../../../test/helpers/index')
5
8
 
6
9
  test('vec3: rotateX() called with four parameters should update a vec3 with correct values', (t) => {
7
- const radians = 90 * Math.PI / 180
10
+ const radians = TAU / 4
8
11
 
9
12
  const obs1 = fromValues(0, 0, 0)
10
13
  const ret1 = rotateX(obs1, [0, 0, 0], [0, 0, 0], 0)
@@ -1,10 +1,13 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+
2
5
  const { rotateY, fromValues } = require('./index')
3
6
 
4
7
  const { compareVectors } = require('../../../test/helpers/index')
5
8
 
6
9
  test('vec3: rotateY() called with three parameters should update a vec3 with correct values', (t) => {
7
- const radians = 90 * Math.PI / 180
10
+ const radians = TAU / 4
8
11
 
9
12
  const obs1 = fromValues(0, 0, 0)
10
13
  const ret1 = rotateY(obs1, [0, 0, 0], [0, 0, 0], 0)
@@ -1,10 +1,13 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+
2
5
  const { rotateZ, fromValues } = require('./index')
3
6
 
4
7
  const { compareVectors } = require('../../../test/helpers/index')
5
8
 
6
9
  test('vec3: rotateZ() called with four parameters should update a vec3 with correct values', (t) => {
7
- const radians = 90 * Math.PI / 180
10
+ const radians = TAU / 4
8
11
 
9
12
  const obs1 = fromValues(0, 0, 0)
10
13
  const ret1 = rotateZ(obs1, [0, 0, 0], [0, 0, 0], 0)
@@ -1,9 +1,11 @@
1
1
  const test = require('ava')
2
2
 
3
3
  const { comparePoints, nearlyEqual } = require('../../../test/helpers')
4
+
4
5
  const { geom2, geom3, path2 } = require('../../geometries')
5
6
  const measureBoundingBox = require('../../measurements/measureBoundingBox')
6
7
  const area = require('../../maths/utils/area')
8
+ const { TAU } = require('../../maths/constants')
7
9
  const sphere = require('../../primitives/sphere')
8
10
 
9
11
  const { expand } = require('./index')
@@ -51,7 +53,7 @@ test('expand: round-expanding a bent line produces expected geometry', (t) => {
51
53
  const expandedPoints = geom2.toPoints(expandedPathGeom2)
52
54
 
53
55
  t.notThrows(() => geom2.validate(expandedPathGeom2))
54
- const expectedArea = 56 + 2 * Math.PI * delta * 1.25 // shape will have 1 and 1/4 circles
56
+ const expectedArea = 56 + TAU * delta * 1.25 // shape will have 1 and 1/4 circles
55
57
  nearlyEqual(t, area(expandedPoints), expectedArea, 0.01, 'Measured area should be pretty close')
56
58
  const boundingBox = measureBoundingBox(expandedPathGeom2)
57
59
  t.true(comparePoints(boundingBox, [[-7, -2, 0], [2, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -1,4 +1,4 @@
1
- const { EPS } = require('../../maths/constants')
1
+ const { EPS, TAU } = require('../../maths/constants')
2
2
 
3
3
  const mat4 = require('../../maths/mat4')
4
4
  const vec3 = require('../../maths/vec3')
@@ -120,7 +120,7 @@ const expandShell = (options, geometry) => {
120
120
 
121
121
  // first of all equally spaced around the cylinder:
122
122
  for (let i = 0; i < segments; i++) {
123
- addUniqueAngle(angles, (i * Math.PI * 2 / segments))
123
+ addUniqueAngle(angles, (i * TAU / segments))
124
124
  }
125
125
 
126
126
  // and also at every normal of all touching planes:
@@ -130,10 +130,10 @@ const expandShell = (options, geometry) => {
130
130
  const co = vec3.dot(xbase, planenormal)
131
131
  let angle = Math.atan2(si, co)
132
132
 
133
- if (angle < 0) angle += Math.PI * 2
133
+ if (angle < 0) angle += TAU
134
134
  addUniqueAngle(angles, angle)
135
135
  angle = Math.atan2(-si, -co)
136
- if (angle < 0) angle += Math.PI * 2
136
+ if (angle < 0) angle += TAU
137
137
  addUniqueAngle(angles, angle)
138
138
  }
139
139
 
@@ -1,4 +1,4 @@
1
- const { EPS } = require('../../maths/constants')
1
+ const { EPS, TAU } = require('../../maths/constants')
2
2
 
3
3
  const intersect = require('../../maths/utils/intersect')
4
4
  const line2 = require('../../maths/line2')
@@ -139,7 +139,7 @@ const offsetFromPoints = (options, points) => {
139
139
 
140
140
  if (rotation !== 0.0) {
141
141
  // generate the segments
142
- cornersegments = Math.floor(segments * (Math.abs(rotation) / (2 * Math.PI)))
142
+ cornersegments = Math.floor(segments * (Math.abs(rotation) / TAU))
143
143
  const step = rotation / cornersegments
144
144
  const start = vec2.angle(vec2.subtract(v0, corner.s0[1], corner.c))
145
145
  const cornerpoints = []
@@ -2,6 +2,7 @@ const test = require('ava')
2
2
 
3
3
  const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
4
4
 
5
+ const { TAU } = require('../../maths/constants')
5
6
  const mat4 = require('../../maths/mat4')
6
7
 
7
8
  const { geom2, geom3, poly3 } = require('../../geometries')
@@ -56,10 +57,10 @@ test('extrudeFromSlices (torus)', (t) => {
56
57
  hex = poly3.transform(mat4.fromTranslation(mat4.create(), [0, 20, 0]), hex)
57
58
  hex = slice.fromPoints(poly3.toPoints(hex))
58
59
 
59
- const angle = Math.PI / 4
60
+ const angle = TAU / 8
60
61
  const geometry3 = extrudeFromSlices(
61
62
  {
62
- numberOfSlices: Math.PI * 2 / angle,
63
+ numberOfSlices: TAU / angle,
63
64
  capStart: false,
64
65
  capEnd: false,
65
66
  close: true,
@@ -2,6 +2,8 @@ const test = require('ava')
2
2
 
3
3
  const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
4
4
 
5
+ const { TAU } = require('../../maths/constants')
6
+
5
7
  const { geom2, geom3, path2 } = require('../../geometries')
6
8
 
7
9
  const { extrudeLinear } = require('./index')
@@ -77,7 +79,7 @@ test('extrudeLinear (no twist)', (t) => {
77
79
  test('extrudeLinear (twist)', (t) => {
78
80
  const geometry2 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
79
81
 
80
- let geometry3 = extrudeLinear({ height: 15, twistAngle: Math.PI / -4 }, geometry2)
82
+ let geometry3 = extrudeLinear({ height: 15, twistAngle: -TAU / 8 }, geometry2)
81
83
  let pts = geom3.toPoints(geometry3)
82
84
  let exp = [
83
85
  [[5, -5, 0], [5, 5, 0], [7.0710678118654755, 4.440892098500626e-16, 15]],
@@ -105,7 +107,7 @@ test('extrudeLinear (twist)', (t) => {
105
107
  t.is(pts.length, 12)
106
108
  t.true(comparePolygonsAsPoints(pts, exp))
107
109
 
108
- geometry3 = extrudeLinear({ height: 15, twistAngle: Math.PI / 2, twistSteps: 3 }, geometry2)
110
+ geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 4, twistSteps: 3 }, geometry2)
109
111
  pts = geom3.toPoints(geometry3)
110
112
  exp = [
111
113
  [[5, -5, 0], [5, 5, 0], [1.830127018922194, 6.830127018922193, 5]],
@@ -140,7 +142,7 @@ test('extrudeLinear (twist)', (t) => {
140
142
  t.is(pts.length, 28)
141
143
  t.true(comparePolygonsAsPoints(pts, exp))
142
144
 
143
- geometry3 = extrudeLinear({ height: 15, twistAngle: Math.PI / 2, twistSteps: 30 }, geometry2)
145
+ geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 2, twistSteps: 30 }, geometry2)
144
146
  pts = geom3.toPoints(geometry3)
145
147
  t.notThrows(() => geom3.validate(geometry3))
146
148
  t.is(pts.length, 244)
@@ -18,7 +18,7 @@ const extrudeRectangularGeom2 = require('./extrudeRectangularGeom2')
18
18
  *
19
19
  * @example
20
20
  * let mywalls = extrudeRectangular({size: 1, height: 3}, square({size: 20}))
21
- * let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle: Math.PI}, square({size: 20}))
21
+ * let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle: TAU / 2}, square({size: 20}))
22
22
  */
23
23
  const extrudeRectangular = (options, ...objects) => {
24
24
  const defaults = {
@@ -1,5 +1,7 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../../maths/constants')
4
+
3
5
  const { geom2, geom3 } = require('../../geometries')
4
6
 
5
7
  const { arc, rectangle } = require('../../primitives')
@@ -7,7 +9,7 @@ const { arc, rectangle } = require('../../primitives')
7
9
  const { extrudeRectangular } = require('./index')
8
10
 
9
11
  test('extrudeRectangular (defaults)', (t) => {
10
- const geometry1 = arc({ radius: 5, endAngle: Math.PI / 2, segments: 16 })
12
+ const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
11
13
  const geometry2 = rectangle({ size: [5, 5] })
12
14
 
13
15
  let obs = extrudeRectangular({ }, geometry1)
@@ -22,7 +24,7 @@ test('extrudeRectangular (defaults)', (t) => {
22
24
  })
23
25
 
24
26
  test('extrudeRectangular (chamfer)', (t) => {
25
- const geometry1 = arc({ radius: 5, endAngle: Math.PI / 2, segments: 16 })
27
+ const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
26
28
  const geometry2 = rectangle({ size: [5, 5] })
27
29
 
28
30
  let obs = extrudeRectangular({ corners: 'chamfer' }, geometry1)
@@ -37,7 +39,7 @@ test('extrudeRectangular (chamfer)', (t) => {
37
39
  })
38
40
 
39
41
  test('extrudeRectangular (segments = 8, round)', (t) => {
40
- const geometry1 = arc({ radius: 5, endAngle: Math.PI / 2, segments: 16 })
42
+ const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
41
43
  const geometry2 = rectangle({ size: [5, 5] })
42
44
 
43
45
  let obs = extrudeRectangular({ segments: 8, corners: 'round' }, geometry1)
@@ -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,7 +21,7 @@ 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
27
  [[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
@@ -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],
@@ -110,7 +112,7 @@ 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
118
  [[0, 0, 8], [7, 0, 8], [0, 7, 8]],
@@ -129,7 +131,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
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
137
  [[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
@@ -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 = {
@@ -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'
@@ -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')