@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.9.4",
3
+ "version": "2.10.0",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "homepage": "https://openjscad.xyz/",
6
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
@@ -61,5 +61,5 @@
61
61
  "nyc": "15.1.0",
62
62
  "uglifyify": "5.0.2"
63
63
  },
64
- "gitHead": "662965f40a1ce628aa97f30b814586e72a3acb36"
64
+ "gitHead": "13572067545460affd53b64b3ab834a39af8c7e5"
65
65
  }
@@ -5,9 +5,10 @@ import { RGB, RGBA } from './types'
5
5
 
6
6
  export default colorize
7
7
 
8
- declare function colorize<T extends Geometry>(color: RGB | RGBA, object: T): T
9
- declare function colorize<T>(color: RGB | RGBA, object: T): T & Colored
8
+ // Single Geom3 returns Colored Geom3
9
+ declare function colorize<T extends Geometry>(color: RGB | RGBA, object: T): T & Colored
10
10
 
11
- declare function colorize<T extends Geometry>(color: RGB | RGBA, ...objects: RecursiveArray<T>): Array<T>
12
- declare function colorize<T>(color: RGB | RGBA, ...objects: RecursiveArray<T>): Array<T & Colored>
13
- declare function colorize(color: RGB | RGBA, ...objects: RecursiveArray<any>): Array<any & Colored>
11
+ // List of Geom3 returns list of Colored Geom3
12
+ declare function colorize<T extends Geometry>(color: RGB | RGBA, ...objects: RecursiveArray<T>): Array<T & Colored>
13
+ // List of mixed geometries returns list of colored geometries
14
+ declare function colorize(color: RGB | RGBA, ...objects: RecursiveArray<Geometry>): Array<Geometry & Colored>
@@ -1,10 +1,11 @@
1
1
  import Vec2 from '../../maths/vec2/type'
2
2
  import Mat4 from '../../maths/mat4/type'
3
- import { Colored } from '../types'
3
+ import { Color } from '../types'
4
4
 
5
5
  export default Geom2
6
6
 
7
- declare interface Geom2 extends Colored {
7
+ declare interface Geom2 {
8
8
  sides: Array<[Vec2, Vec2]>
9
9
  transforms: Mat4
10
+ color?: Color
10
11
  }
@@ -1,10 +1,11 @@
1
1
  import Poly3 from '../poly3/type'
2
2
  import Mat4 from '../../maths/mat4/type'
3
- import { Colored } from '../types'
3
+ import { Color } from '../types'
4
4
 
5
5
  export default Geom3
6
6
 
7
- declare interface Geom3 extends Colored {
7
+ declare interface Geom3 {
8
8
  polygons: Array<Poly3>
9
9
  transforms: Mat4
10
+ color?: Color
10
11
  }
@@ -1,3 +1,4 @@
1
+ const { TAU } = require('../../maths/constants')
1
2
  const vec2 = require('../../maths/vec2')
2
3
 
3
4
  const fromPoints = require('./fromPoints')
@@ -12,7 +13,7 @@ const toPoints = require('./toPoints')
12
13
  * @param {vec2} [options.radius=[0,0]] - radius of arc (X and Y)
13
14
  * @param {Number} [options.xaxisrotation=0] - rotation (RADIANS) of the X axis of the arc with respect to the X axis of the coordinate system
14
15
  * @param {Boolean} [options.clockwise=false] - draw an arc clockwise with respect to the center point
15
- * @param {Boolean} [options.large=false] - draw an arc longer than PI radians
16
+ * @param {Boolean} [options.large=false] - draw an arc longer than TAU / 2 radians
16
17
  * @param {Number} [options.segments=16] - number of segments per full rotation
17
18
  * @param {path2} geometry - the path of which to append the arc
18
19
  * @returns {path2} a new path with the appended points
@@ -111,15 +112,15 @@ const appendArc = (options, geometry) => {
111
112
  const theta1 = vec2.angleRadians(vector1)
112
113
  const theta2 = vec2.angleRadians(vector2)
113
114
  let deltatheta = theta2 - theta1
114
- deltatheta = deltatheta % (2 * Math.PI)
115
+ deltatheta = deltatheta % TAU
115
116
  if ((!sweepFlag) && (deltatheta > 0)) {
116
- deltatheta -= 2 * Math.PI
117
+ deltatheta -= TAU
117
118
  } else if ((sweepFlag) && (deltatheta < 0)) {
118
- deltatheta += 2 * Math.PI
119
+ deltatheta += TAU
119
120
  }
120
121
 
121
122
  // Ok, we have the center point and angle range (from theta1, deltatheta radians) so we can create the ellipse
122
- let numsteps = Math.ceil(Math.abs(deltatheta) / (2 * Math.PI) * segments) + 1
123
+ let numsteps = Math.ceil(Math.abs(deltatheta) / TAU * segments) + 1
123
124
  if (numsteps < 1) numsteps = 1
124
125
  for (let step = 1; step < numsteps; step++) {
125
126
  const theta = theta1 + step / numsteps * deltatheta
@@ -1,5 +1,7 @@
1
1
  const test = require('ava')
2
2
 
3
+ const { TAU } = require('../../maths/constants')
4
+
3
5
  const { appendArc, fromPoints, toPoints } = require('./index')
4
6
 
5
7
  const { comparePoints } = require('../../../test/helpers/')
@@ -48,7 +50,7 @@ test('appendArc: appending to a path produces a new path', (t) => {
48
50
  t.is(pts.length, 16)
49
51
 
50
52
  // test xaxisrotation
51
- obs = appendArc({ endpoint: [12, -22], radius: [15, -20], xaxisrotation: Math.PI / 2 }, p2)
53
+ obs = appendArc({ endpoint: [12, -22], radius: [15, -20], xaxisrotation: TAU / 4 }, p2)
52
54
  pts = toPoints(obs)
53
55
  exp = [
54
56
  [27, -22],
@@ -1,3 +1,4 @@
1
+ const { TAU } = require('../../maths/constants')
1
2
  const vec2 = require('../../maths/vec2')
2
3
  const vec3 = require('../../maths/vec2')
3
4
 
@@ -117,7 +118,7 @@ const appendBezier = (options, geometry) => {
117
118
 
118
119
  // subdivide each segment until the angle at each vertex becomes small enough:
119
120
  let subdivideBase = 1
120
- const maxangle = Math.PI * 2 / segments
121
+ const maxangle = TAU / segments
121
122
  const maxsinangle = Math.sin(maxangle)
122
123
  while (subdivideBase < newpoints.length - 1) {
123
124
  const dir1 = vec2.subtract(v0, newpoints[subdivideBase], newpoints[subdivideBase - 1])
@@ -1,5 +1,5 @@
1
- const fromPoints = require('./fromPoints')
2
- const toPoints = require('./toPoints')
1
+ const concat = require('./concat')
2
+ const create = require('./create')
3
3
 
4
4
  /**
5
5
  * Append the given list of points to the end of the given geometry.
@@ -10,15 +10,6 @@ const toPoints = require('./toPoints')
10
10
  * @example
11
11
  * let newpath = appendPoints([[3, 4], [4, 5]], oldpath)
12
12
  */
13
- const appendPoints = (points, geometry) => {
14
- if (geometry.isClosed) {
15
- throw new Error('cannot append points to a closed path')
16
- }
17
-
18
- let newpoints = toPoints(geometry)
19
- newpoints = newpoints.concat(points)
20
-
21
- return fromPoints({}, newpoints)
22
- }
13
+ const appendPoints = (points, geometry) => concat(geometry, create(points))
23
14
 
24
15
  module.exports = appendPoints
@@ -17,3 +17,19 @@ test('appendPoints: appending to a path produces a new path with expected points
17
17
  t.not(p1, obs)
18
18
  t.is(pts.length, 4)
19
19
  })
20
+
21
+ test('appendPoints: appending empty points to a path produces a new path with expected points', (t) => {
22
+ const p1 = fromPoints({}, [[1, 1], [2, 2]])
23
+ const obs = appendPoints([], p1)
24
+ const pts = toPoints(obs)
25
+ t.not(p1, obs)
26
+ t.is(pts.length, 2)
27
+ })
28
+
29
+ test('appendPoints: appending same points to a path produces a new path with expected points', (t) => {
30
+ const p1 = fromPoints({}, [[1, 1], [2, 2]])
31
+ const obs = appendPoints([[2, 2], [3, 3]], p1)
32
+ const pts = toPoints(obs)
33
+ t.not(p1, obs)
34
+ t.is(pts.length, 3)
35
+ })
@@ -1,8 +1,11 @@
1
1
  const fromPoints = require('./fromPoints')
2
2
  const toPoints = require('./toPoints')
3
+
3
4
  const { equals } = require('../../maths/vec2')
5
+
4
6
  /**
5
7
  * Concatenate the given paths.
8
+ *
6
9
  * If both contain the same point at the junction, merge it into one.
7
10
  * A concatenation of zero paths is an empty, open path.
8
11
  * A concatenation of one closed path to a series of open paths produces a closed path.
@@ -17,16 +20,14 @@ const { equals } = require('../../maths/vec2')
17
20
  const concat = (...paths) => {
18
21
  // Only the last path can be closed, producing a closed path.
19
22
  let isClosed = false
20
- for (const path of paths) {
21
- if (isClosed) {
22
- throw new Error('Cannot concatenate to a closed path')
23
- }
24
- isClosed = path.isClosed
25
- }
26
23
  let newpoints = []
27
- paths.forEach((path) => {
28
- const tmp = toPoints(path)
24
+ paths.forEach((path, i) => {
25
+ const tmp = toPoints(path).slice()
29
26
  if (newpoints.length > 0 && tmp.length > 0 && equals(tmp[0], newpoints[newpoints.length - 1])) tmp.shift()
27
+ if (tmp.length > 0 && isClosed) {
28
+ throw new Error(`Cannot concatenate to a closed path; check the ${i}th path`)
29
+ }
30
+ isClosed = path.isClosed
30
31
  newpoints = newpoints.concat(tmp)
31
32
  })
32
33
  return fromPoints({ closed: isClosed }, newpoints)
@@ -10,10 +10,16 @@ test('concat: empty paths produces an empty open path', (t) => {
10
10
  t.true(equals(concat(fromPoints({}, []), fromPoints({}, [])), fromPoints({ closed: false }, [])))
11
11
  })
12
12
 
13
- test('concat: Two open paths produces a open path', (t) => {
14
- t.true(equals(concat(fromPoints({ closed: false }, [[0, 0]]),
15
- fromPoints({ closed: false }, [[1, 1]])),
16
- fromPoints({ closed: false }, [[0, 0], [1, 1]])))
13
+ test('concat: many open paths produces a open path', (t) => {
14
+ const p1 = fromPoints({ closed: false }, [[0, 0]])
15
+ const p2 = fromPoints({ closed: false }, [[1, 1]])
16
+ const p3 = fromPoints({ closed: false }, [[1, 1], [3, 3]])
17
+
18
+ const result = concat(p1, p2, p3)
19
+ t.true(equals(result, fromPoints({}, [[0, 0], [1, 1], [3, 3]])))
20
+ t.is(p1.points.length, 1)
21
+ t.is(p2.points.length, 1)
22
+ t.is(p3.points.length, 2)
17
23
  })
18
24
 
19
25
  test('concat: An open path and a closed path produces a closed path', (t) => {
@@ -23,7 +29,7 @@ test('concat: An open path and a closed path produces a closed path', (t) => {
23
29
  })
24
30
 
25
31
  test('concat: A closed path and an open path throws an error', (t) => {
26
- t.throws(() => concat(fromPoints({ closed: true }, [[0, 0]]),
27
- fromPoints({ closed: false }, [[1, 1]])),
28
- { message: 'Cannot concatenate to a closed path' })
32
+ const p1 = fromPoints({ closed: true }, [[0, 0]])
33
+ const p2 = fromPoints({ closed: false }, [[1, 1]])
34
+ t.throws(() => concat(p1, p2), { message: 'Cannot concatenate to a closed path; check the 1th path' })
29
35
  })
@@ -10,7 +10,7 @@ const mat4 = require('../../maths/mat4')
10
10
  * @alias module:modeling/geometries/path2.transform
11
11
  *
12
12
  * @example
13
- * let newpath = transform(fromZRotation(Math.PI / 4), path)
13
+ * let newpath = transform(fromZRotation(TAU / 8), path)
14
14
  */
15
15
  const transform = (matrix, geometry) => {
16
16
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
@@ -1,11 +1,12 @@
1
1
  import Vec2 from '../../maths/vec2/type'
2
2
  import Mat4 from '../../maths/mat4/type'
3
- import { Colored } from '../types'
3
+ import { Color } from '../types'
4
4
 
5
5
  export default Path2
6
6
 
7
- declare interface Path2 extends Colored {
7
+ declare interface Path2 {
8
8
  points: Array<Vec2>
9
9
  isClosed: boolean
10
10
  transforms: Mat4
11
+ color?: Color
11
12
  }
@@ -1,6 +1,6 @@
1
1
  import Poly3 from './type'
2
- import Vec3 from '../../maths/vec3/type'
2
+ import Vec4 from '../../maths/vec4/type'
3
3
 
4
4
  export default measureBoundingSphere
5
5
 
6
- declare function measureBoundingSphere(polygon: Poly3): [Vec3, Vec3]
6
+ declare function measureBoundingSphere(polygon: Poly3): Vec4
@@ -1,19 +1,57 @@
1
1
  const vec3 = require('../../maths/vec3')
2
- const measureBoundingBox = require('./measureBoundingBox')
2
+ const vec4 = require('../../maths/vec4')
3
+
4
+ const cache = new WeakMap()
3
5
 
4
6
  /**
5
7
  * Measure the bounding sphere of the given polygon.
6
8
  * @param {poly3} polygon - the polygon to measure
7
- * @returns {Array} the computed bounding sphere; center point (3D) and radius
9
+ * @returns {vec4} the computed bounding sphere; center point (3D) and radius
8
10
  * @alias module:modeling/geometries/poly3.measureBoundingSphere
9
11
  */
10
12
  const measureBoundingSphere = (polygon) => {
11
- const box = measureBoundingBox(polygon)
12
- const center = box[0]
13
- vec3.add(center, box[0], box[1])
14
- vec3.scale(center, center, 0.5)
15
- const radius = vec3.distance(center, box[1])
16
- return [center, radius]
13
+ let boundingSphere = cache.get(polygon)
14
+ if (boundingSphere) return boundingSphere
15
+
16
+ const vertices = polygon.vertices
17
+ const out = vec4.create()
18
+
19
+ if (vertices.length === 0) {
20
+ out[0] = 0
21
+ out[1] = 0
22
+ out[2] = 0
23
+ out[3] = 0
24
+ return out
25
+ }
26
+
27
+ // keep a list of min/max vertices by axis
28
+ let minx = vertices[0]
29
+ let miny = minx
30
+ let minz = minx
31
+ let maxx = minx
32
+ let maxy = minx
33
+ let maxz = minx
34
+
35
+ vertices.forEach((v) => {
36
+ if (minx[0] > v[0]) minx = v
37
+ if (miny[1] > v[1]) miny = v
38
+ if (minz[2] > v[2]) minz = v
39
+ if (maxx[0] < v[0]) maxx = v
40
+ if (maxy[1] < v[1]) maxy = v
41
+ if (maxz[2] < v[2]) maxz = v
42
+ })
43
+
44
+ out[0] = (minx[0] + maxx[0]) * 0.5 // center of sphere
45
+ out[1] = (miny[1] + maxy[1]) * 0.5
46
+ out[2] = (minz[2] + maxz[2]) * 0.5
47
+ const x = out[0] - maxx[0]
48
+ const y = out[1] - maxy[1]
49
+ const z = out[2] - maxz[2]
50
+ out[3] = Math.sqrt(x * x + y * y + z * z) // radius of sphere
51
+
52
+ cache.set(polygon, out)
53
+
54
+ return out
17
55
  }
18
56
 
19
57
  module.exports = measureBoundingSphere
@@ -3,28 +3,23 @@ const { measureBoundingSphere, create, fromPoints, transform } = require('./inde
3
3
 
4
4
  const mat4 = require('../../maths/mat4')
5
5
 
6
- const { compareVectors, nearlyEqual } = require('../../../test/helpers/index')
7
-
8
6
  test('poly3: measureBoundingSphere() should return correct values', (t) => {
9
7
  let ply1 = create()
10
- let exp1 = [[0, 0, 0], 0]
8
+ let exp1 = [0, 0, 0, 0]
11
9
  let ret1 = measureBoundingSphere(ply1)
12
- t.true(compareVectors(ret1[0], exp1[0]))
13
- nearlyEqual(t, ret1[1], exp1[1], Number.EPSILON)
10
+ t.deepEqual(ret1, exp1)
14
11
 
15
12
  // simple triangle
16
13
  let ply2 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10]])
17
- let exp2 = [[0, 5, 5], 7.0710678118654755]
14
+ let exp2 = [0, 5, 5, 7.0710678118654755]
18
15
  let ret2 = measureBoundingSphere(ply2)
19
- t.true(compareVectors(ret2[0], exp2[0]))
20
- nearlyEqual(t, ret2[1], exp2[1], Number.EPSILON)
16
+ t.deepEqual(ret2, exp2)
21
17
 
22
18
  // simple square
23
19
  let ply3 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]])
24
- let exp3 = [[0, 5, 5], 7.0710678118654755]
20
+ let exp3 = [0, 5, 5, 7.0710678118654755]
25
21
  let ret3 = measureBoundingSphere(ply3)
26
- t.true(compareVectors(ret3[0], exp3[0]))
27
- nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
22
+ t.deepEqual(ret3, exp3)
28
23
 
29
24
  // V-shape
30
25
  const points = [
@@ -40,10 +35,9 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
40
35
  [0, 3, 3]
41
36
  ]
42
37
  let ply4 = fromPoints(points)
43
- let exp4 = [[0, 4.5, 3], 4.6097722286464435]
38
+ let exp4 = [0, 4.5, 3, 4.6097722286464435]
44
39
  let ret4 = measureBoundingSphere(ply4)
45
- t.true(compareVectors(ret4[0], exp4[0]))
46
- nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
40
+ t.deepEqual(ret4, exp4)
47
41
 
48
42
  // rotated to various angles
49
43
  const rotation = mat4.fromZRotation(mat4.create(), (45 * 0.017453292519943295))
@@ -55,16 +49,12 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
55
49
  ret2 = measureBoundingSphere(ply2)
56
50
  ret3 = measureBoundingSphere(ply3)
57
51
  ret4 = measureBoundingSphere(ply4)
58
- exp1 = [[0, 0, 0], 0]
59
- t.true(compareVectors(ret1[0], exp1[0]))
60
- nearlyEqual(t, ret1[1], exp1[1], Number.EPSILON)
61
- exp2 = [[-3.5355339059327373, 3.5355339059327378, 5], 7.0710678118654755]
62
- t.true(compareVectors(ret2[0], exp2[0]))
63
- nearlyEqual(t, ret2[1], exp2[1], Number.EPSILON)
64
- exp3 = [[-3.5355339059327373, 3.5355339059327378, 5], 7.0710678118654755]
65
- t.true(compareVectors(ret3[0], exp3[0]))
66
- nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
67
- exp4 = [[-3.181980515339464, 3.1819805153394642, 3], 4.6097722286464435]
68
- t.true(compareVectors(ret4[0], exp4[0]))
69
- nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
52
+ exp1 = [0, 0, 0, 0]
53
+ t.deepEqual(ret1, exp1)
54
+ exp2 = [-3.5355339059327373, 3.5355339059327378, 5, 7.0710678118654755]
55
+ t.deepEqual(ret2, exp2)
56
+ exp3 = [-3.5355339059327373, 3.5355339059327378, 5, 7.0710678118654755]
57
+ t.deepEqual(ret3, exp3)
58
+ exp4 = [-3.181980515339464, 3.1819805153394642, 3, 4.6097722286464435]
59
+ t.deepEqual(ret4, exp4)
70
60
  })
@@ -1,8 +1,9 @@
1
1
  import Vec3 from '../../maths/vec3/type'
2
- import { Colored } from '../types'
2
+ import { Color } from '../types'
3
3
 
4
4
  export default Poly3
5
5
 
6
- declare interface Poly3 extends Colored {
6
+ declare interface Poly3 {
7
7
  vertices: Array<Vec3>
8
+ color?: Color
8
9
  }
@@ -8,8 +8,10 @@ import { RGB, RGBA } from '../colors'
8
8
  // see https://github.com/jscad/OpenJSCAD.org/pull/726#issuecomment-724575265
9
9
  export type Geometry = Geom2 | Geom3 | Poly3 | Path2
10
10
 
11
- export type Colored = {
12
- color?: RGB | RGBA
11
+ export type Color = RGB | RGBA
12
+
13
+ export interface Colored {
14
+ color: Color
13
15
  }
14
16
 
15
17
  export { default as Geom2 } from './geom2/type'
package/src/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export * as booleans from './operations/booleans'
11
11
  export * as expansions from './operations/expansions'
12
12
  export * as extrusions from './operations/extrusions'
13
13
  export * as hulls from './operations/hulls'
14
+ export * as modifiers from './operations/modifiers'
14
15
  export * as transforms from './operations/transforms'
15
16
 
16
17
  export as namespace modeling
@@ -24,8 +24,18 @@ const NEPS = 1e-13
24
24
  // for comparing coplanar polygons, as provided by the sphere primitive at high
25
25
  // segmentation. NEPS is for 64 bit Number values.
26
26
 
27
+ /**
28
+ * The TAU property represents the ratio of the circumference of a circle to its radius.
29
+ * Approximately 6.28318530717958647692
30
+ * @default
31
+ * @example
32
+ * const { TAU } = require('@jscad/modeling').maths.constants
33
+ */
34
+ const TAU = Math.PI * 2
35
+
27
36
  module.exports = {
28
37
  EPS,
29
38
  NEPS,
39
+ TAU,
30
40
  spatialResolution
31
41
  }
@@ -1,6 +1,8 @@
1
- const identity = require('./identity')
1
+ const { EPS } = require('../constants')
2
+
3
+ const { sin, cos } = require('../utils/trigonometry')
2
4
 
3
- const { EPSILON } = require('./constants')
5
+ const identity = require('./identity')
4
6
 
5
7
  /**
6
8
  * Creates a matrix from a given angle around a given axis
@@ -15,24 +17,24 @@ const { EPSILON } = require('./constants')
15
17
  * @returns {mat4} out
16
18
  * @alias module:modeling/maths/mat4.fromRotation
17
19
  * @example
18
- * let matrix = fromRotation(create(), Math.PI / 2, [0, 0, 3])
20
+ * let matrix = fromRotation(create(), TAU / 4, [0, 0, 3])
19
21
  */
20
22
  const fromRotation = (out, rad, axis) => {
21
23
  let [x, y, z] = axis
22
- let len = Math.hypot(x, y, z)
24
+ const lengthSquared = x * x + y * y + z * z
23
25
 
24
- if (Math.abs(len) < EPSILON) {
26
+ if (Math.abs(lengthSquared) < EPS) {
25
27
  // axis is 0,0,0 or almost
26
28
  return identity(out)
27
29
  }
28
30
 
29
- len = 1 / len
31
+ const len = 1 / Math.sqrt(lengthSquared)
30
32
  x *= len
31
33
  y *= len
32
34
  z *= len
33
35
 
34
- const s = Math.sin(rad)
35
- const c = Math.cos(rad)
36
+ const s = sin(rad)
37
+ const c = cos(rad)
36
38
  const t = 1 - c
37
39
 
38
40
  // Perform rotation-specific matrix multiplication
@@ -1,3 +1,5 @@
1
+ const { sin, cos } = require('../utils/trigonometry')
2
+
1
3
  /**
2
4
  * Creates a matrix from the given Tait–Bryan angles.
3
5
  *
@@ -11,16 +13,16 @@
11
13
  * @returns {mat4} out
12
14
  * @alias module:modeling/maths/mat4.fromTaitBryanRotation
13
15
  * @example
14
- * let matrix = fromTaitBryanRotation(create(), Math.PI / 2, 0, Math.PI)
16
+ * let matrix = fromTaitBryanRotation(create(), TAU / 4, 0, TAU / 2)
15
17
  */
16
18
  const fromTaitBryanRotation = (out, yaw, pitch, roll) => {
17
19
  // precompute sines and cosines of Euler angles
18
- const sy = Math.sin(yaw)
19
- const cy = Math.cos(yaw)
20
- const sp = Math.sin(pitch)
21
- const cp = Math.cos(pitch)
22
- const sr = Math.sin(roll)
23
- const cr = Math.cos(roll)
20
+ const sy = sin(yaw)
21
+ const cy = cos(yaw)
22
+ const sp = sin(pitch)
23
+ const cp = cos(pitch)
24
+ const sr = sin(roll)
25
+ const cr = cos(roll)
24
26
 
25
27
  // create and populate rotation matrix
26
28
  // left-hand-rule rotation
@@ -1,3 +1,5 @@
1
+ const { sin, cos } = require('../utils/trigonometry')
2
+
1
3
  /**
2
4
  * Creates a matrix from the given angle around the X axis.
3
5
  * This is equivalent to (but much faster than):
@@ -10,11 +12,11 @@
10
12
  * @returns {mat4} out
11
13
  * @alias module:modeling/maths/mat4.fromXRotation
12
14
  * @example
13
- * let matrix = fromXRotation(create(), Math.PI / 2)
15
+ * let matrix = fromXRotation(create(), TAU / 4)
14
16
  */
15
17
  const fromXRotation = (out, radians) => {
16
- const s = Math.sin(radians)
17
- const c = Math.cos(radians)
18
+ const s = sin(radians)
19
+ const c = cos(radians)
18
20
 
19
21
  // Perform axis-specific matrix multiplication
20
22
  out[0] = 1
@@ -1,3 +1,5 @@
1
+ const { sin, cos } = require('../utils/trigonometry')
2
+
1
3
  /**
2
4
  * Creates a matrix from the given angle around the Y axis.
3
5
  * This is equivalent to (but much faster than):
@@ -10,11 +12,11 @@
10
12
  * @returns {mat4} out
11
13
  * @alias module:modeling/maths/mat4.fromYRotation
12
14
  * @example
13
- * let matrix = fromYRotation(create(), Math.PI / 2)
15
+ * let matrix = fromYRotation(create(), TAU / 4)
14
16
  */
15
17
  const fromYRotation = (out, radians) => {
16
- const s = Math.sin(radians)
17
- const c = Math.cos(radians)
18
+ const s = sin(radians)
19
+ const c = cos(radians)
18
20
 
19
21
  // Perform axis-specific matrix multiplication
20
22
  out[0] = c
@@ -1,3 +1,5 @@
1
+ const { sin, cos } = require('../utils/trigonometry')
2
+
1
3
  /**
2
4
  * Creates a matrix from the given angle around the Z axis.
3
5
  * This is equivalent to (but much faster than):
@@ -10,11 +12,11 @@
10
12
  * @returns {mat4} out
11
13
  * @alias module:modeling/maths/mat4.fromZRotation
12
14
  * @example
13
- * let matrix = fromZRotation(create(), Math.PI / 2)
15
+ * let matrix = fromZRotation(create(), TAU / 4)
14
16
  */
15
17
  const fromZRotation = (out, radians) => {
16
- const s = Math.sin(radians)
17
- const c = Math.cos(radians)
18
+ const s = sin(radians)
19
+ const c = cos(radians)
18
20
 
19
21
  // Perform axis-specific matrix multiplication
20
22
  out[0] = c
@@ -1,8 +1,11 @@
1
1
  const test = require('ava')
2
+
3
+ const { TAU } = require('../constants')
4
+ const vec3 = require('../vec3/index')
5
+
2
6
  const { create, invert, fromTranslation, fromXRotation } = require('./index')
3
7
 
4
8
  const { compareVectors } = require('../../../test/helpers/index')
5
- const vec3 = require('../vec3/index')
6
9
 
7
10
  test('mat4: invert() translate ', (t) => {
8
11
  const matrix = fromTranslation(create(), [10, 10, 0])
@@ -17,7 +20,7 @@ test('mat4: invert() translate ', (t) => {
17
20
  })
18
21
 
19
22
  test('mat4: invert() rotate ', (t) => {
20
- const matrix = fromXRotation(create(), Math.PI / 2)
23
+ const matrix = fromXRotation(create(), TAU / 4)
21
24
  const matrixInv = invert(create(), matrix)
22
25
 
23
26
  const vec1 = [10, 10, 10]
@@ -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