@jscad/modeling 2.9.0 → 2.9.3

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 (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +4 -4
  3. package/dist/jscad-modeling.min.js +437 -428
  4. package/package.json +3 -2
  5. package/src/colors/colorize.test.js +1 -1
  6. package/src/geometries/geom2/index.d.ts +1 -0
  7. package/src/geometries/geom2/index.js +2 -1
  8. package/src/geometries/geom2/toOutlines.js +66 -52
  9. package/src/geometries/geom2/validate.d.ts +3 -0
  10. package/src/geometries/geom2/validate.js +36 -0
  11. package/src/geometries/geom3/create.js +1 -1
  12. package/src/geometries/geom3/create.test.js +1 -1
  13. package/src/geometries/geom3/fromPoints.js +1 -1
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +2 -1
  16. package/src/geometries/geom3/isA.js +1 -1
  17. package/src/geometries/geom3/validate.d.ts +3 -0
  18. package/src/geometries/geom3/validate.js +62 -0
  19. package/src/geometries/path2/index.d.ts +1 -1
  20. package/src/geometries/path2/index.js +2 -2
  21. package/src/geometries/path2/validate.d.ts +3 -0
  22. package/src/geometries/path2/validate.js +41 -0
  23. package/src/geometries/poly2/arePointsInside.js +0 -35
  24. package/src/geometries/poly3/create.js +1 -1
  25. package/src/geometries/poly3/index.d.ts +1 -0
  26. package/src/geometries/poly3/index.js +2 -1
  27. package/src/geometries/poly3/measureArea.test.js +16 -16
  28. package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
  29. package/src/geometries/poly3/validate.d.ts +4 -0
  30. package/src/geometries/poly3/validate.js +64 -0
  31. package/src/maths/constants.d.ts +1 -0
  32. package/src/maths/constants.js +11 -0
  33. package/src/maths/utils/aboutEqualNormals.js +1 -5
  34. package/src/measurements/measureCenterOfMass.test.js +2 -2
  35. package/src/operations/booleans/intersect.test.js +8 -0
  36. package/src/operations/booleans/intersectGeom3.js +2 -1
  37. package/src/operations/booleans/scission.test.js +4 -4
  38. package/src/operations/booleans/subtract.test.js +8 -0
  39. package/src/operations/booleans/subtractGeom3.js +2 -1
  40. package/src/operations/booleans/to3DWalls.js +1 -1
  41. package/src/operations/booleans/trees/Node.js +10 -16
  42. package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
  43. package/src/operations/booleans/trees/Tree.js +1 -2
  44. package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
  45. package/src/operations/booleans/union.test.js +27 -0
  46. package/src/operations/booleans/unionGeom3.js +2 -1
  47. package/src/operations/expansions/expand.test.js +30 -21
  48. package/src/operations/expansions/expandGeom3.test.js +14 -14
  49. package/src/operations/expansions/expandShell.js +5 -4
  50. package/src/operations/expansions/extrudePolygon.js +7 -7
  51. package/src/operations/expansions/offset.test.js +25 -0
  52. package/src/operations/extrusions/earcut/assignHoles.js +7 -3
  53. package/src/operations/extrusions/earcut/assignHoles.test.js +50 -4
  54. package/src/operations/extrusions/earcut/linkedList.js +1 -1
  55. package/src/operations/extrusions/extrudeFromSlices.test.js +16 -10
  56. package/src/operations/extrusions/extrudeLinear.test.js +15 -9
  57. package/src/operations/extrusions/extrudeRectangular.test.js +15 -8
  58. package/src/operations/extrusions/extrudeRotate.js +5 -1
  59. package/src/operations/extrusions/extrudeRotate.test.js +12 -0
  60. package/src/operations/extrusions/extrudeWalls.js +2 -2
  61. package/src/operations/extrusions/project.js +11 -14
  62. package/src/operations/extrusions/project.test.js +55 -55
  63. package/src/operations/hulls/hull.test.js +24 -1
  64. package/src/operations/hulls/hullChain.test.js +6 -4
  65. package/src/operations/hulls/hullGeom2.js +6 -18
  66. package/src/operations/hulls/hullGeom3.js +5 -18
  67. package/src/operations/hulls/hullPath2.js +4 -14
  68. package/src/operations/hulls/hullPath2.test.js +1 -1
  69. package/src/operations/hulls/hullPoints2.js +43 -92
  70. package/src/operations/hulls/toUniquePoints.js +34 -0
  71. package/src/operations/modifiers/generalize.js +2 -13
  72. package/src/operations/modifiers/generalize.test.js +5 -31
  73. package/src/operations/modifiers/insertTjunctions.js +1 -1
  74. package/src/operations/modifiers/insertTjunctions.test.js +21 -21
  75. package/src/operations/modifiers/mergePolygons.js +11 -14
  76. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +1 -1
  77. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
  78. package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
  79. package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
  80. package/src/operations/modifiers/snapPolygons.test.js +12 -12
  81. package/src/operations/modifiers/triangulatePolygons.js +3 -3
  82. package/src/operations/transforms/align.test.js +12 -0
  83. package/src/operations/transforms/center.js +1 -1
  84. package/src/operations/transforms/center.test.js +12 -0
  85. package/src/operations/transforms/mirror.test.js +16 -0
  86. package/src/operations/transforms/rotate.test.js +10 -0
  87. package/src/operations/transforms/scale.test.js +15 -0
  88. package/src/operations/transforms/transform.test.js +5 -0
  89. package/src/operations/transforms/translate.test.js +16 -0
  90. package/src/primitives/arc.test.js +11 -0
  91. package/src/primitives/circle.test.js +15 -9
  92. package/src/primitives/cube.test.js +3 -0
  93. package/src/primitives/cuboid.js +1 -1
  94. package/src/primitives/cuboid.test.js +9 -24
  95. package/src/primitives/cylinder.test.js +7 -4
  96. package/src/primitives/cylinderElliptic.js +14 -7
  97. package/src/primitives/cylinderElliptic.test.js +72 -50
  98. package/src/primitives/ellipse.js +3 -1
  99. package/src/primitives/ellipse.test.js +14 -8
  100. package/src/primitives/ellipsoid.js +8 -6
  101. package/src/primitives/ellipsoid.test.js +84 -80
  102. package/src/primitives/geodesicSphere.test.js +3 -0
  103. package/src/primitives/line.test.js +1 -0
  104. package/src/primitives/polygon.test.js +15 -10
  105. package/src/primitives/polyhedron.js +1 -1
  106. package/src/primitives/polyhedron.test.js +14 -42
  107. package/src/primitives/rectangle.test.js +3 -0
  108. package/src/primitives/roundedCuboid.js +6 -6
  109. package/src/primitives/roundedCuboid.test.js +5 -0
  110. package/src/primitives/roundedCylinder.js +7 -5
  111. package/src/primitives/roundedCylinder.test.js +40 -36
  112. package/src/primitives/roundedRectangle.test.js +5 -0
  113. package/src/primitives/sphere.test.js +52 -73
  114. package/src/primitives/square.test.js +3 -0
  115. package/src/primitives/star.test.js +6 -0
  116. package/src/primitives/torus.test.js +8 -1
  117. package/src/primitives/triangle.js +1 -2
  118. package/src/primitives/triangle.test.js +7 -0
  119. package/src/utils/areAllShapesTheSameType.js +2 -2
  120. package/src/utils/areAllShapesTheSameType.test.js +17 -0
  121. package/src/utils/index.d.ts +1 -0
  122. package/src/utils/index.js +3 -1
  123. package/src/utils/trigonometry.d.ts +2 -0
  124. package/src/utils/trigonometry.js +34 -0
  125. package/src/utils/trigonometry.test.js +25 -0
  126. package/test/helpers/nearlyEqual.js +4 -1
  127. package/src/geometries/path2/eachPoint.d.ts +0 -9
  128. package/src/geometries/path2/eachPoint.js +0 -17
  129. package/src/geometries/path2/eachPoint.test.js +0 -11
  130. package/src/operations/modifiers/edges.js +0 -195
  131. 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.0",
3
+ "version": "2.9.3",
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": "45fbcb7298fcacf3e45d67fdca32d0744ab7f2de"
64
+ "gitHead": "85fa1fcdfb2d516a201ecf31da242e64b5b3274e"
64
65
  }
@@ -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)
@@ -10,6 +10,7 @@ export { default as toSides } from './toSides'
10
10
  export { default as toString } from './toString'
11
11
  export { default as toCompactBinary } from './toCompactBinary'
12
12
  export { default as transform } from './transform'
13
+ export { default as validate } from './validate'
13
14
 
14
15
  export { default as Geom2 } from './type'
15
16
  export as namespace geom2
@@ -25,5 +25,6 @@ module.exports = {
25
25
  toSides: require('./toSides'),
26
26
  toString: require('./toString'),
27
27
  toCompactBinary: require('./toCompactBinary'),
28
- transform: require('./transform')
28
+ transform: require('./transform'),
29
+ validate: require('./validate')
29
30
  }
@@ -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
@@ -0,0 +1,3 @@
1
+ export default validate
2
+
3
+ declare function validate(object: any): void
@@ -0,0 +1,36 @@
1
+ const vec2 = require('../../maths/vec2')
2
+ const isA = require('./isA')
3
+ const toOutlines = require('./toOutlines')
4
+
5
+ /**
6
+ * Determine if the given object is a valid geom2.
7
+ * Checks for closedness, self-edges, and valid data points.
8
+ *
9
+ * **If the geometry is not valid, an exception will be thrown with details of the geometry error.**
10
+ *
11
+ * @param {Object} object - the object to interrogate
12
+ * @throws {Error} error if the geometry is not valid
13
+ * @alias module:modeling/geometries/geom2.validate
14
+ */
15
+ const validate = (object) => {
16
+ if (!isA(object)) {
17
+ throw new Error('invalid geom2 structure')
18
+ }
19
+
20
+ // check for closedness
21
+ toOutlines(object)
22
+
23
+ // check for self-edges
24
+ object.sides.forEach((side) => {
25
+ if (vec2.equals(side[0], side[1])) {
26
+ throw new Error(`geom2 self-edge ${side[0]}`)
27
+ }
28
+ })
29
+
30
+ // check transforms
31
+ if (!object.transforms.every(Number.isFinite)) {
32
+ throw new Error(`geom2 invalid transforms ${object.transforms}`)
33
+ }
34
+ }
35
+
36
+ module.exports = validate
@@ -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)
@@ -9,6 +9,7 @@ export { default as toPolygons } from './toPolygons'
9
9
  export { default as toString } from './toString'
10
10
  export { default as toCompactBinary } from './toCompactBinary'
11
11
  export { default as transform } from './transform'
12
+ export { default as validate } from './validate'
12
13
 
13
14
  export { default as Geom3 } from './type'
14
15
  export as namespace geom3
@@ -31,5 +31,6 @@ module.exports = {
31
31
  toPolygons: require('./toPolygons'),
32
32
  toString: require('./toString'),
33
33
  toCompactBinary: require('./toCompactBinary'),
34
- transform: require('./transform')
34
+ transform: require('./transform'),
35
+ validate: require('./validate')
35
36
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Determine if the given object is a 3D geometry.
3
- * @param {object} object - the object to interrogate
3
+ * @param {Object} object - the object to interrogate
4
4
  * @returns {Boolean} true if the object matches a geom3
5
5
  * @alias module:modeling/geometries/geom3.isA
6
6
  */
@@ -0,0 +1,3 @@
1
+ export default validate
2
+
3
+ declare function validate(object: any): void
@@ -0,0 +1,62 @@
1
+ const poly3 = require('../poly3')
2
+ const isA = require('./isA')
3
+
4
+ /**
5
+ * Determine if the given object is a valid 3D geometry.
6
+ * Checks for valid data structure, convex polygon faces, and manifold edges.
7
+ *
8
+ * **If the geometry is not valid, an exception will be thrown with details of the geometry error.**
9
+ *
10
+ * @param {Object} object - the object to interrogate
11
+ * @throws {Error} error if the geometry is not valid
12
+ * @alias module:modeling/geometries/geom3.validate
13
+ */
14
+ const validate = (object) => {
15
+ if (!isA(object)) {
16
+ throw new Error('invalid geom3 structure')
17
+ }
18
+
19
+ // check polygons
20
+ object.polygons.forEach(poly3.validate)
21
+ validateManifold(object)
22
+
23
+ // check transforms
24
+ if (!object.transforms.every(Number.isFinite)) {
25
+ throw new Error(`geom3 invalid transforms ${object.transforms}`)
26
+ }
27
+
28
+ // TODO: check for self-intersecting
29
+ }
30
+
31
+ /*
32
+ * Check manifold edge condition: Every edge is in exactly 2 faces
33
+ */
34
+ const validateManifold = (object) => {
35
+ // count of each edge
36
+ const edgeCount = new Map()
37
+ object.polygons.forEach(({ vertices }) => {
38
+ vertices.forEach((v, i) => {
39
+ const v1 = `${v}`
40
+ const v2 = `${vertices[(i + 1) % vertices.length]}`
41
+ // sort for undirected edge
42
+ const edge = `${v1}/${v2}`
43
+ const count = edgeCount.has(edge) ? edgeCount.get(edge) : 0
44
+ edgeCount.set(edge, count + 1)
45
+ })
46
+ })
47
+
48
+ // check that edges are always matched
49
+ const nonManifold = []
50
+ edgeCount.forEach((count, edge) => {
51
+ const complementEdge = edge.split('/').reverse().join('/')
52
+ const complementCount = edgeCount.get(complementEdge)
53
+ if (count !== complementCount) {
54
+ nonManifold.push(edge.replace('/', ' -> '))
55
+ }
56
+ })
57
+ if (nonManifold.length > 0) {
58
+ throw new Error(`non-manifold edges ${nonManifold.length}\n${nonManifold.join('\n')}`)
59
+ }
60
+ }
61
+
62
+ module.exports = validate
@@ -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'
@@ -15,6 +14,7 @@ export { default as toPoints } from './toPoints'
15
14
  export { default as toString } from './toString'
16
15
  export { default as toCompactBinary } from './toCompactBinary'
17
16
  export { default as transform } from './transform'
17
+ export { default as validate } from './validate'
18
18
 
19
19
  export { default as Path2 } from './type'
20
20
  export as namespace path2
@@ -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'),
@@ -31,5 +30,6 @@ module.exports = {
31
30
  toPoints: require('./toPoints'),
32
31
  toString: require('./toString'),
33
32
  toCompactBinary: require('./toCompactBinary'),
34
- transform: require('./transform')
33
+ transform: require('./transform'),
34
+ validate: require('./validate')
35
35
  }
@@ -0,0 +1,3 @@
1
+ export default validate
2
+
3
+ declare function validate(object: any): void
@@ -0,0 +1,41 @@
1
+ const vec2 = require('../../maths/vec2')
2
+ const isA = require('./isA')
3
+
4
+ /**
5
+ * Determine if the given object is a valid path2.
6
+ * Checks for valid data points, and duplicate points.
7
+ *
8
+ * **If the geometry is not valid, an exception will be thrown with details of the geometry error.**
9
+ *
10
+ * @param {Object} object - the object to interrogate
11
+ * @throws {Error} error if the geometry is not valid
12
+ * @alias module:modeling/geometries/path2.validate
13
+ */
14
+ const validate = (object) => {
15
+ if (!isA(object)) {
16
+ throw new Error('invalid path2 structure')
17
+ }
18
+
19
+ // check for duplicate points
20
+ if (object.points.length > 1) {
21
+ for (let i = 0; i < object.points.length; i++) {
22
+ if (vec2.equals(object.points[i], object.points[(i + 1) % object.points.length])) {
23
+ throw new Error(`path2 duplicate points ${object.points[i]}`)
24
+ }
25
+ }
26
+ }
27
+
28
+ // check for infinity, nan
29
+ object.points.forEach((point) => {
30
+ if (!point.every(Number.isFinite)) {
31
+ throw new Error(`path2 invalid point ${point}`)
32
+ }
33
+ })
34
+
35
+ // check transforms
36
+ if (!object.transforms.every(Number.isFinite)) {
37
+ throw new Error(`path2 invalid transforms ${object.transforms}`)
38
+ }
39
+ }
40
+
41
+ module.exports = validate
@@ -23,41 +23,6 @@ const arePointsInside = (points, polygon) => {
23
23
  return sum === points.length ? 1 : 0
24
24
  }
25
25
 
26
- /*
27
- * Determine if the given point is inside the polygon.
28
- *
29
- * @see http://geomalgorithms.com/a03-_inclusion.html
30
- * @param {Array} point - an array with X and Y values
31
- * @param {Array} polygon - a list of points, where each point is an array with X and Y values
32
- * @return {Integer} 1 if the point is inside, 0 if outside
33
- */
34
- const isPointInsideOld = (point, polygon) => {
35
- let wn = 0
36
- const n = polygon.length
37
- const x = point[0]
38
- const y = point[1]
39
- for (let i = 0; i < polygon.length; i++) {
40
- const p1 = polygon[i]
41
- const p2 = polygon[(i + 1) % n]
42
- if (x !== p1[0] && y !== p1[1] && x !== p2[0] && y !== p2[1]) { // no overlap of points
43
- if (p1[1] <= y) {
44
- if (p2[1] > y) { // upward crossing
45
- if (isLeft(p1, p2, point) > 0) { // point left of edge
46
- wn++
47
- }
48
- }
49
- } else {
50
- if (p2[1] <= y) { // downward crossing
51
- if (isLeft(p1, p2, point) < 0) { // point right of edge
52
- wn--
53
- }
54
- }
55
- }
56
- }
57
- }
58
- return wn === 0 ? 1 : 0
59
- }
60
-
61
26
  /*
62
27
  * Determine if the given point is inside the polygon.
63
28
  *
@@ -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
@@ -13,6 +13,7 @@ export { default as plane } from './plane'
13
13
  export { default as toPoints } from './toPoints'
14
14
  export { default as toString } from './toString'
15
15
  export { default as transform } from './transform'
16
+ export { default as validate } from './validate'
16
17
 
17
18
  export { default as Poly3 } from './type'
18
19
  export as namespace poly3
@@ -24,5 +24,6 @@ module.exports = {
24
24
  plane: require('./plane'),
25
25
  toPoints: require('./toPoints'),
26
26
  toString: require('./toString'),
27
- transform: require('./transform')
27
+ transform: require('./transform'),
28
+ validate: require('./validate')
28
29
  }
@@ -62,10 +62,10 @@ test('poly3: measureArea() should return correct values', (t) => {
62
62
  ret2 = measureArea(ply2)
63
63
  ret3 = measureArea(ply3)
64
64
  ret4 = measureArea(ply4)
65
- nearlyEqual(ret1, 0.0, Number.EPSILON)
66
- nearlyEqual(ret2, 50.0, Number.EPSILON)
67
- nearlyEqual(ret3, 100.0, Number.EPSILON)
68
- nearlyEqual(ret4, 19.5, Number.EPSILON)
65
+ nearlyEqual(t, ret1, 0.0, Number.EPSILON)
66
+ nearlyEqual(t, ret2, 50.0, Number.EPSILON)
67
+ nearlyEqual(t, ret3, 100.0, Number.EPSILON)
68
+ nearlyEqual(t, ret4, 19.5, Number.EPSILON)
69
69
 
70
70
  rotation = mat4.fromYRotation(mat4.create(), (45 * 0.017453292519943295))
71
71
  ply1 = transform(rotation, ply1)
@@ -76,10 +76,10 @@ test('poly3: measureArea() should return correct values', (t) => {
76
76
  ret2 = measureArea(ply2)
77
77
  ret3 = measureArea(ply3)
78
78
  ret4 = measureArea(ply4)
79
- nearlyEqual(ret1, 0.0, Number.EPSILON)
80
- nearlyEqual(ret2, 50.0, Number.EPSILON)
81
- nearlyEqual(ret3, 100.0, Number.EPSILON)
82
- nearlyEqual(ret4, 19.5, Number.EPSILON)
79
+ nearlyEqual(t, ret1, 0.0, Number.EPSILON)
80
+ nearlyEqual(t, ret2, 50.0, Number.EPSILON)
81
+ nearlyEqual(t, ret3, 100.0, Number.EPSILON)
82
+ nearlyEqual(t, ret4, 19.5, Number.EPSILON)
83
83
 
84
84
  rotation = mat4.fromXRotation(mat4.create(), (45 * 0.017453292519943295))
85
85
  ply1 = transform(rotation, ply1)
@@ -90,10 +90,10 @@ test('poly3: measureArea() should return correct values', (t) => {
90
90
  ret2 = measureArea(ply2)
91
91
  ret3 = measureArea(ply3)
92
92
  ret4 = measureArea(ply4)
93
- nearlyEqual(ret1, 0.0, Number.EPSILON)
94
- nearlyEqual(ret2, 50.0, Number.EPSILON)
95
- nearlyEqual(ret3, 100.0, Number.EPSILON)
96
- nearlyEqual(ret4, 19.5, Number.EPSILON)
93
+ nearlyEqual(t, ret1, 0.0, Number.EPSILON)
94
+ nearlyEqual(t, ret2, 50.0, Number.EPSILON)
95
+ nearlyEqual(t, ret3, 100.0, Number.EPSILON)
96
+ nearlyEqual(t, ret4, 19.5, Number.EPSILON)
97
97
 
98
98
  // inverted
99
99
  ply1 = invert(ply1)
@@ -104,8 +104,8 @@ test('poly3: measureArea() should return correct values', (t) => {
104
104
  ret2 = measureArea(ply2)
105
105
  ret3 = measureArea(ply3)
106
106
  ret4 = measureArea(ply4)
107
- nearlyEqual(ret1, 0.0, Number.EPSILON)
108
- nearlyEqual(ret2, 50.0, Number.EPSILON)
109
- nearlyEqual(ret3, 100.0, Number.EPSILON)
110
- nearlyEqual(ret4, 19.5, Number.EPSILON)
107
+ nearlyEqual(t, ret1, 0.0, Number.EPSILON)
108
+ nearlyEqual(t, ret2, 50.0, Number.EPSILON)
109
+ nearlyEqual(t, ret3, 100.0, Number.EPSILON)
110
+ nearlyEqual(t, ret4, 19.5, Number.EPSILON * 2)
111
111
  })
@@ -10,21 +10,21 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
10
10
  let exp1 = [[0, 0, 0], 0]
11
11
  let ret1 = measureBoundingSphere(ply1)
12
12
  t.true(compareVectors(ret1[0], exp1[0]))
13
- nearlyEqual(ret1[1], exp1[1], Number.EPSILON)
13
+ nearlyEqual(t, ret1[1], exp1[1], Number.EPSILON)
14
14
 
15
15
  // simple triangle
16
16
  let ply2 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10]])
17
17
  let exp2 = [[0, 5, 5], 7.0710678118654755]
18
18
  let ret2 = measureBoundingSphere(ply2)
19
19
  t.true(compareVectors(ret2[0], exp2[0]))
20
- nearlyEqual(ret2[1], exp2[1], Number.EPSILON)
20
+ nearlyEqual(t, ret2[1], exp2[1], Number.EPSILON)
21
21
 
22
22
  // simple square
23
23
  let ply3 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]])
24
24
  let exp3 = [[0, 5, 5], 7.0710678118654755]
25
25
  let ret3 = measureBoundingSphere(ply3)
26
26
  t.true(compareVectors(ret3[0], exp3[0]))
27
- nearlyEqual(ret3[1], exp3[1], Number.EPSILON)
27
+ nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
28
28
 
29
29
  // V-shape
30
30
  const points = [
@@ -43,7 +43,7 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
43
43
  let exp4 = [[0, 4.5, 3], 4.6097722286464435]
44
44
  let ret4 = measureBoundingSphere(ply4)
45
45
  t.true(compareVectors(ret4[0], exp4[0]))
46
- nearlyEqual(ret4[1], exp4[1], Number.EPSILON)
46
+ nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
47
47
 
48
48
  // rotated to various angles
49
49
  const rotation = mat4.fromZRotation(mat4.create(), (45 * 0.017453292519943295))
@@ -57,14 +57,14 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
57
57
  ret4 = measureBoundingSphere(ply4)
58
58
  exp1 = [[0, 0, 0], 0]
59
59
  t.true(compareVectors(ret1[0], exp1[0]))
60
- nearlyEqual(ret1[1], exp1[1], Number.EPSILON)
60
+ nearlyEqual(t, ret1[1], exp1[1], Number.EPSILON)
61
61
  exp2 = [[-3.5355339059327373, 3.5355339059327378, 5], 7.0710678118654755]
62
62
  t.true(compareVectors(ret2[0], exp2[0]))
63
- nearlyEqual(ret2[1], exp2[1], Number.EPSILON)
63
+ nearlyEqual(t, ret2[1], exp2[1], Number.EPSILON)
64
64
  exp3 = [[-3.5355339059327373, 3.5355339059327378, 5], 7.0710678118654755]
65
65
  t.true(compareVectors(ret3[0], exp3[0]))
66
- nearlyEqual(ret3[1], exp3[1], Number.EPSILON)
66
+ nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
67
67
  exp4 = [[-3.181980515339464, 3.1819805153394642, 3], 4.6097722286464435]
68
68
  t.true(compareVectors(ret4[0], exp4[0]))
69
- nearlyEqual(ret4[1], exp4[1], Number.EPSILON)
69
+ nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
70
70
  })
@@ -0,0 +1,4 @@
1
+
2
+ export default validate
3
+
4
+ declare function validate(object: any): void