@jscad/modeling 2.9.2 → 2.9.5

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 (114) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +4 -4
  3. package/dist/jscad-modeling.min.js +435 -444
  4. package/package.json +3 -2
  5. package/src/colors/colorize.d.ts +6 -5
  6. package/src/colors/colorize.test.js +1 -1
  7. package/src/geometries/geom2/toOutlines.js +66 -52
  8. package/src/geometries/geom2/type.d.ts +3 -2
  9. package/src/geometries/geom3/applyTransforms.js +1 -2
  10. package/src/geometries/geom3/create.js +1 -1
  11. package/src/geometries/geom3/create.test.js +1 -1
  12. package/src/geometries/geom3/fromPoints.js +1 -1
  13. package/src/geometries/geom3/type.d.ts +3 -2
  14. package/src/geometries/path2/index.d.ts +0 -1
  15. package/src/geometries/path2/index.js +0 -1
  16. package/src/geometries/path2/type.d.ts +3 -2
  17. package/src/geometries/poly3/create.js +1 -1
  18. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
  19. package/src/geometries/poly3/measureBoundingSphere.js +46 -8
  20. package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
  21. package/src/geometries/poly3/type.d.ts +3 -2
  22. package/src/geometries/poly3/validate.js +14 -0
  23. package/src/geometries/types.d.ts +4 -2
  24. package/src/maths/constants.d.ts +1 -0
  25. package/src/maths/constants.js +11 -0
  26. package/src/maths/mat4/fromRotation.js +9 -7
  27. package/src/maths/mat4/fromTaitBryanRotation.js +8 -6
  28. package/src/maths/mat4/fromXRotation.js +4 -2
  29. package/src/maths/mat4/fromYRotation.js +4 -2
  30. package/src/maths/mat4/fromZRotation.js +4 -2
  31. package/src/maths/mat4/isMirroring.js +11 -11
  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/utils/aboutEqualNormals.js +1 -5
  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 +1 -2
  42. package/src/{utils → maths/utils}/trigonometry.test.js +0 -0
  43. package/src/maths/vec2/distance.js +1 -1
  44. package/src/maths/vec2/fromAngleRadians.js +4 -2
  45. package/src/maths/vec2/length.js +1 -1
  46. package/src/maths/vec2/length.test.js +0 -10
  47. package/src/maths/vec3/angle.js +2 -2
  48. package/src/maths/vec3/angle.test.js +0 -12
  49. package/src/maths/vec3/distance.js +1 -1
  50. package/src/maths/vec3/length.js +1 -1
  51. package/src/maths/vec3/length.test.js +0 -10
  52. package/src/operations/booleans/intersectGeom2.test.js +69 -0
  53. package/src/operations/booleans/intersectGeom3.js +2 -1
  54. package/src/operations/booleans/{intersect.test.js → intersectGeom3.test.js} +3 -71
  55. package/src/operations/booleans/subtractGeom2.test.js +72 -0
  56. package/src/operations/booleans/subtractGeom3.js +2 -1
  57. package/src/operations/booleans/{subtract.test.js → subtractGeom3.test.js} +3 -74
  58. package/src/operations/booleans/to3DWalls.js +1 -1
  59. package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
  60. package/src/operations/booleans/unionGeom2.test.js +166 -0
  61. package/src/operations/booleans/unionGeom3.js +2 -1
  62. package/src/operations/booleans/{union.test.js → unionGeom3.test.js} +3 -168
  63. package/src/operations/expansions/expandGeom3.test.js +14 -14
  64. package/src/operations/expansions/expandShell.js +5 -4
  65. package/src/operations/expansions/extrudePolygon.js +7 -7
  66. package/src/operations/extrusions/extrudeFromSlices.js +3 -2
  67. package/src/operations/extrusions/extrudeFromSlices.test.js +2 -2
  68. package/src/operations/extrusions/extrudeRotate.js +5 -1
  69. package/src/operations/extrusions/extrudeRotate.test.js +47 -47
  70. package/src/operations/extrusions/extrudeWalls.js +2 -2
  71. package/src/operations/extrusions/project.js +11 -14
  72. package/src/operations/extrusions/project.test.js +49 -49
  73. package/src/operations/extrusions/slice/repair.js +62 -0
  74. package/src/operations/hulls/hullChain.test.js +4 -4
  75. package/src/operations/hulls/hullGeom2.js +6 -18
  76. package/src/operations/hulls/hullGeom3.js +5 -18
  77. package/src/operations/hulls/hullPath2.js +4 -14
  78. package/src/operations/hulls/hullPoints2.js +43 -92
  79. package/src/operations/hulls/toUniquePoints.js +34 -0
  80. package/src/operations/modifiers/generalize.js +2 -13
  81. package/src/operations/modifiers/generalize.test.js +0 -32
  82. package/src/operations/modifiers/insertTjunctions.js +1 -1
  83. package/src/operations/modifiers/insertTjunctions.test.js +21 -21
  84. package/src/operations/modifiers/mergePolygons.js +11 -14
  85. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +33 -32
  86. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
  87. package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
  88. package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
  89. package/src/operations/modifiers/snapPolygons.test.js +12 -12
  90. package/src/operations/modifiers/triangulatePolygons.js +3 -3
  91. package/src/operations/transforms/center.js +1 -1
  92. package/src/primitives/circle.test.js +7 -0
  93. package/src/primitives/cuboid.js +1 -1
  94. package/src/primitives/cylinderElliptic.js +5 -3
  95. package/src/primitives/cylinderElliptic.test.js +7 -1
  96. package/src/primitives/ellipse.js +1 -1
  97. package/src/primitives/ellipse.test.js +7 -0
  98. package/src/primitives/ellipsoid.js +3 -3
  99. package/src/primitives/geodesicSphere.js +3 -2
  100. package/src/primitives/polyhedron.js +1 -1
  101. package/src/primitives/roundedCuboid.js +10 -8
  102. package/src/primitives/roundedCylinder.js +2 -2
  103. package/src/primitives/torus.test.js +11 -7
  104. package/src/primitives/triangle.js +1 -2
  105. package/src/utils/index.d.ts +0 -1
  106. package/src/utils/index.js +1 -3
  107. package/src/geometries/path2/eachPoint.d.ts +0 -9
  108. package/src/geometries/path2/eachPoint.js +0 -17
  109. package/src/geometries/path2/eachPoint.test.js +0 -11
  110. package/src/maths/mat4/constants.d.ts +0 -1
  111. package/src/maths/mat4/constants.js +0 -5
  112. package/src/operations/extrusions/slice/repairSlice.js +0 -47
  113. package/src/operations/modifiers/edges.js +0 -195
  114. package/src/operations/modifiers/repairTjunctions.js +0 -44
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.9.2",
3
+ "version": "2.9.5",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
+ "homepage": "https://openjscad.xyz/",
5
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
6
7
  "main": "src/index.js",
7
8
  "types": "src/index.d.ts",
@@ -60,5 +61,5 @@
60
61
  "nyc": "15.1.0",
61
62
  "uglifyify": "5.0.2"
62
63
  },
63
- "gitHead": "0cebde0166c104e3c08cc05d2c03d9defc7eca26"
64
+ "gitHead": "225b034db0d94f748992da72b269833954a2e212"
64
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>
@@ -38,7 +38,7 @@ test('color (rgba on geometry)', (t) => {
38
38
  const obj0 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
39
39
  const obj1 = geom3.fromPoints([[[0, 0, 0], [1, 0, 0], [1, 0, 1]]])
40
40
  const obj2 = path2.fromPoints({ closed: true }, [[0, 0], [1, 0], [1, 1]])
41
- const obj3 = poly3.fromPoints([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
41
+ const obj3 = poly3.create([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
42
42
 
43
43
  const obs = colorize([1, 1, 0.5, 0.8], obj0, obj1, obj2, obj3)
44
44
  t.is(obs.length, 4)
@@ -6,22 +6,42 @@ const toSides = require('./toSides')
6
6
  * Create a list of edges which SHARE vertices.
7
7
  * This allows the edges to be traversed in order.
8
8
  */
9
- const toEdges = (sides) => {
10
- const vertices = {}
9
+ const toSharedVertices = (sides) => {
10
+ const unique = new Map() // {key: vertex}
11
11
  const getUniqueVertex = (vertex) => {
12
12
  const key = vertex.toString()
13
- if (!vertices[key]) {
14
- vertices[key] = vertex
13
+ if (unique.has(key)) {
14
+ return unique.get(key)
15
+ } else {
16
+ unique.set(key, vertex)
17
+ return vertex
15
18
  }
16
- return vertices[key]
17
19
  }
18
20
 
19
21
  return sides.map((side) => side.map(getUniqueVertex))
20
22
  }
21
23
 
24
+ /*
25
+ * Convert a list of sides into a map from vertex to edges.
26
+ */
27
+ const toVertexMap = (sides) => {
28
+ const vertexMap = new Map()
29
+ // first map to edges with shared vertices
30
+ const edges = toSharedVertices(sides)
31
+ // construct adjacent edges map
32
+ edges.forEach((edge) => {
33
+ if (vertexMap.has(edge[0])) {
34
+ vertexMap.get(edge[0]).push(edge)
35
+ } else {
36
+ vertexMap.set(edge[0], [edge])
37
+ }
38
+ })
39
+ return vertexMap
40
+ }
41
+
22
42
  /**
23
43
  * Create the outline(s) of the given geometry.
24
- * @param {geom2} geometry
44
+ * @param {geom2} geometry - geometry to create outlines from
25
45
  * @returns {Array} an array of outlines, where each outline is an array of ordered points
26
46
  * @alias module:modeling/geometries/geom2.toOutlines
27
47
  *
@@ -30,65 +50,35 @@ const toEdges = (sides) => {
30
50
  * let outlines = toOutlines(geometry) // returns two outlines
31
51
  */
32
52
  const toOutlines = (geometry) => {
33
- const vertexMap = new Map()
34
- const edges = toEdges(toSides(geometry))
35
- edges.forEach((edge) => {
36
- if (!(vertexMap.has(edge[0]))) {
37
- vertexMap.set(edge[0], [])
38
- }
39
- const sideslist = vertexMap.get(edge[0])
40
- sideslist.push(edge)
41
- })
42
-
53
+ const vertexMap = toVertexMap(toSides(geometry)) // {vertex: [edges]}
43
54
  const outlines = []
44
55
  while (true) {
45
- let startside
56
+ let startSide
46
57
  for (const [vertex, edges] of vertexMap) {
47
- startside = edges.shift()
48
- if (!startside) {
58
+ startSide = edges.shift()
59
+ if (!startSide) {
49
60
  vertexMap.delete(vertex)
50
61
  continue
51
62
  }
52
63
  break
53
64
  }
54
- if (startside === undefined) break // all starting sides have been visited
65
+ if (startSide === undefined) break // all starting sides have been visited
55
66
 
56
67
  const connectedVertexPoints = []
57
- const startvertex = startside[0]
58
- const v0 = vec2.create()
68
+ const startVertex = startSide[0]
59
69
  while (true) {
60
- connectedVertexPoints.push(startside[0])
61
- const nextvertex = startside[1]
62
- if (nextvertex === startvertex) break // the outline has been closed
63
- const nextpossiblesides = vertexMap.get(nextvertex)
64
- if (!nextpossiblesides) {
65
- throw new Error('the given geometry is not closed. verify proper construction')
70
+ connectedVertexPoints.push(startSide[0])
71
+ const nextVertex = startSide[1]
72
+ if (nextVertex === startVertex) break // the outline has been closed
73
+ const nextPossibleSides = vertexMap.get(nextVertex)
74
+ if (!nextPossibleSides) {
75
+ throw new Error(`geometry is not closed at vertex ${nextVertex}`)
66
76
  }
67
- let nextsideindex = -1
68
- if (nextpossiblesides.length === 1) {
69
- nextsideindex = 0
70
- } else {
71
- // more than one side starting at the same vertex
72
- let bestangle
73
- const startangle = vec2.angleDegrees(vec2.subtract(v0, startside[1], startside[0]))
74
- for (let sideindex = 0; sideindex < nextpossiblesides.length; sideindex++) {
75
- const nextpossibleside = nextpossiblesides[sideindex]
76
- const nextangle = vec2.angleDegrees(vec2.subtract(v0, nextpossibleside[1], nextpossibleside[0]))
77
- let angledif = nextangle - startangle
78
- if (angledif < -180) angledif += 360
79
- if (angledif >= 180) angledif -= 360
80
- if ((nextsideindex < 0) || (angledif > bestangle)) {
81
- nextsideindex = sideindex
82
- bestangle = angledif
83
- }
84
- }
77
+ const nextSide = popNextSide(startSide, nextPossibleSides)
78
+ if (nextPossibleSides.length === 0) {
79
+ vertexMap.delete(nextVertex)
85
80
  }
86
- const nextside = nextpossiblesides[nextsideindex]
87
- nextpossiblesides.splice(nextsideindex, 1) // remove side from list
88
- if (nextpossiblesides.length === 0) {
89
- vertexMap.delete(nextvertex)
90
- }
91
- startside = nextside
81
+ startSide = nextSide
92
82
  } // inner loop
93
83
 
94
84
  // due to the logic of fromPoints()
@@ -102,4 +92,28 @@ const toOutlines = (geometry) => {
102
92
  return outlines
103
93
  }
104
94
 
95
+ // find the first counter-clockwise edge from startSide and pop from nextSides
96
+ const popNextSide = (startSide, nextSides) => {
97
+ if (nextSides.length === 1) {
98
+ return nextSides.pop()
99
+ }
100
+ const v0 = vec2.create()
101
+ const startAngle = vec2.angleDegrees(vec2.subtract(v0, startSide[1], startSide[0]))
102
+ let bestAngle
103
+ let bestIndex
104
+ nextSides.forEach((nextSide, index) => {
105
+ const nextAngle = vec2.angleDegrees(vec2.subtract(v0, nextSide[1], nextSide[0]))
106
+ let angle = nextAngle - startAngle
107
+ if (angle < -180) angle += 360
108
+ if (angle >= 180) angle -= 360
109
+ if (bestIndex === undefined || angle > bestAngle) {
110
+ bestIndex = index
111
+ bestAngle = angle
112
+ }
113
+ })
114
+ const nextSide = nextSides[bestIndex]
115
+ nextSides.splice(bestIndex, 1) // remove side from list
116
+ return nextSide
117
+ }
118
+
105
119
  module.exports = toOutlines
@@ -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
  }
@@ -14,9 +14,8 @@ const applyTransforms = (geometry) => {
14
14
  if (mat4.isIdentity(geometry.transforms)) return geometry
15
15
 
16
16
  // apply transforms to each polygon
17
- // const isMirror = mat4.isMirroring(geometry.transforms)
18
- // TBD if (isMirror) newvertices.reverse()
19
17
  geometry.polygons = geometry.polygons.map((polygon) => poly3.transform(geometry.transforms, polygon))
18
+ // reset transforms
20
19
  geometry.transforms = mat4.create()
21
20
  return geometry
22
21
  }
@@ -18,7 +18,7 @@ const create = (polygons) => {
18
18
  polygons = [] // empty contents
19
19
  }
20
20
  return {
21
- polygons: polygons,
21
+ polygons,
22
22
  transforms: mat4.create()
23
23
  }
24
24
  }
@@ -14,7 +14,7 @@ test('create: Creates an empty geom3', (t) => {
14
14
 
15
15
  test('create: Creates a populated geom3', (t) => {
16
16
  const points = [[0, 0, 0], [0, 10, 0], [0, 10, 10]]
17
- const polygon = poly3.fromPoints(points)
17
+ const polygon = poly3.create(points)
18
18
 
19
19
  const polygons = [polygon]
20
20
  const expected = {
@@ -18,7 +18,7 @@ const fromPoints = (listofpoints) => {
18
18
 
19
19
  const polygons = listofpoints.map((points, index) => {
20
20
  // TODO catch the error, and rethrow with index
21
- const polygon = poly3.fromPoints(points)
21
+ const polygon = poly3.create(points)
22
22
  return polygon
23
23
  })
24
24
  const result = create(polygons)
@@ -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
  }
@@ -5,7 +5,6 @@ export { default as clone } from './clone'
5
5
  export { default as close } from './close'
6
6
  export { default as concat } from './concat'
7
7
  export { default as create } from './create'
8
- export { default as eachPoint, EachPointOptions } from './eachPoint'
9
8
  export { default as equals } from './equals'
10
9
  export { default as fromPoints, FromPointsOptions } from './fromPoints'
11
10
  export { default as fromCompactBinary } from './fromCompactBinary'
@@ -22,7 +22,6 @@ module.exports = {
22
22
  close: require('./close'),
23
23
  concat: require('./concat'),
24
24
  create: require('./create'),
25
- eachPoint: require('./eachPoint'),
26
25
  equals: require('./equals'),
27
26
  fromPoints: require('./fromPoints'),
28
27
  fromCompactBinary: require('./fromCompactBinary'),
@@ -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
  }
@@ -18,7 +18,7 @@ const create = (vertices) => {
18
18
  if (vertices === undefined || vertices.length < 3) {
19
19
  vertices = [] // empty contents
20
20
  }
21
- return { vertices: vertices }
21
+ return { vertices }
22
22
  }
23
23
 
24
24
  module.exports = create
@@ -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
  }
@@ -1,7 +1,10 @@
1
+ const signedDistanceToPoint = require('../../maths/plane/signedDistanceToPoint')
2
+ const { NEPS } = require('../../maths/constants')
1
3
  const vec3 = require('../../maths/vec3')
2
4
  const isA = require('./isA')
3
5
  const isConvex = require('./isConvex')
4
6
  const measureArea = require('./measureArea')
7
+ const plane = require('./plane')
5
8
 
6
9
  /**
7
10
  * Determine if the given object is a valid polygon.
@@ -45,6 +48,17 @@ const validate = (object) => {
45
48
  throw new Error(`poly3 invalid vertex ${vertex}`)
46
49
  }
47
50
  })
51
+
52
+ // check that points are co-planar
53
+ if (object.vertices.length > 3) {
54
+ const normal = plane(object)
55
+ object.vertices.forEach((vertex) => {
56
+ const dist = Math.abs(signedDistanceToPoint(normal, vertex))
57
+ if (dist > NEPS) {
58
+ throw new Error(`poly3 must be coplanar: vertex ${vertex} distance ${dist}`)
59
+ }
60
+ })
61
+ }
48
62
  }
49
63
 
50
64
  module.exports = validate
@@ -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'
@@ -1,2 +1,3 @@
1
1
  export const EPS: number
2
+ export const NEPS: number
2
3
  export const spatialResolution: number
@@ -14,7 +14,18 @@ const spatialResolution = 1e5
14
14
  */
15
15
  const EPS = 1e-5
16
16
 
17
+ /**
18
+ * Smaller epsilon used for measuring near zero distances.
19
+ * @default
20
+ * @alias module:modeling/maths.NEPS
21
+ */
22
+ const NEPS = 1e-13
23
+ // NEPS is derived from a series of tests to determine the optimal precision
24
+ // for comparing coplanar polygons, as provided by the sphere primitive at high
25
+ // segmentation. NEPS is for 64 bit Number values.
26
+
17
27
  module.exports = {
18
28
  EPS,
29
+ NEPS,
19
30
  spatialResolution
20
31
  }
@@ -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
@@ -19,20 +21,20 @@ const { EPSILON } = require('./constants')
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
  *
@@ -15,12 +17,12 @@
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):
@@ -13,8 +15,8 @@
13
15
  * let matrix = fromXRotation(create(), Math.PI / 2)
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):
@@ -13,8 +15,8 @@
13
15
  * let matrix = fromYRotation(create(), Math.PI / 2)
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