@jscad/modeling 2.7.2 → 2.9.1

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 (238) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/jscad-modeling.min.js +443 -398
  3. package/package.json +2 -2
  4. package/src/curves/bezier/tangentAt.test.js +1 -1
  5. package/src/curves/bezier/valueAt.test.js +1 -1
  6. package/src/geometries/geom2/index.d.ts +1 -0
  7. package/src/geometries/geom2/index.js +12 -1
  8. package/src/geometries/geom2/isA.js +2 -2
  9. package/src/geometries/geom2/toCompactBinary.js +4 -4
  10. package/src/geometries/geom2/toString.js +1 -1
  11. package/src/geometries/geom2/transform.test.js +1 -1
  12. package/src/geometries/geom2/validate.d.ts +3 -0
  13. package/src/geometries/geom2/validate.js +36 -0
  14. package/src/geometries/geom3/fromCompactBinary.js +1 -1
  15. package/src/geometries/geom3/index.d.ts +1 -0
  16. package/src/geometries/geom3/index.js +19 -1
  17. package/src/geometries/geom3/isA.js +2 -2
  18. package/src/geometries/geom3/toCompactBinary.js +4 -4
  19. package/src/geometries/geom3/toString.js +1 -1
  20. package/src/geometries/geom3/transform.test.js +1 -1
  21. package/src/geometries/geom3/validate.d.ts +3 -0
  22. package/src/geometries/geom3/validate.js +62 -0
  23. package/src/geometries/index.js +8 -1
  24. package/src/geometries/path2/eachPoint.js +3 -3
  25. package/src/geometries/path2/index.d.ts +1 -0
  26. package/src/geometries/path2/index.js +13 -1
  27. package/src/geometries/path2/isA.js +2 -2
  28. package/src/geometries/path2/reverse.js +4 -4
  29. package/src/geometries/path2/toCompactBinary.js +6 -6
  30. package/src/geometries/path2/toString.js +1 -1
  31. package/src/geometries/path2/transform.test.js +1 -1
  32. package/src/geometries/path2/validate.d.ts +3 -0
  33. package/src/geometries/path2/validate.js +41 -0
  34. package/src/geometries/poly2/arePointsInside.js +0 -35
  35. package/src/geometries/poly2/arePointsInside.test.js +1 -1
  36. package/src/geometries/poly2/index.js +6 -0
  37. package/src/geometries/poly3/index.d.ts +1 -0
  38. package/src/geometries/poly3/index.js +9 -2
  39. package/src/geometries/poly3/invert.js +7 -1
  40. package/src/geometries/poly3/isA.js +2 -2
  41. package/src/geometries/poly3/isConvex.js +2 -2
  42. package/src/geometries/poly3/measureArea.js +4 -4
  43. package/src/geometries/poly3/measureArea.test.js +16 -16
  44. package/src/geometries/poly3/measureBoundingBox.js +2 -2
  45. package/src/geometries/poly3/measureBoundingSphere.js +2 -2
  46. package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
  47. package/src/geometries/poly3/measureSignedVolume.js +4 -4
  48. package/src/geometries/poly3/toPoints.js +2 -2
  49. package/src/geometries/poly3/toString.js +2 -2
  50. package/src/geometries/poly3/transform.js +2 -2
  51. package/src/geometries/poly3/validate.d.ts +4 -0
  52. package/src/geometries/poly3/validate.js +50 -0
  53. package/src/maths/index.js +1 -1
  54. package/src/maths/line2/equals.js +2 -2
  55. package/src/maths/line2/fromValues.js +2 -2
  56. package/src/maths/line2/intersectPointOfLines.js +1 -1
  57. package/src/maths/line2/intersectPointOfLines.test.js +1 -1
  58. package/src/maths/line2/reverse.test.js +1 -1
  59. package/src/maths/line2/transform.test.js +1 -1
  60. package/src/maths/line3/equals.js +2 -2
  61. package/src/maths/line3/reverse.test.js +1 -1
  62. package/src/maths/line3/transform.test.js +1 -1
  63. package/src/maths/mat4/fromVectorRotation.js +1 -1
  64. package/src/maths/mat4/fromVectorRotation.test.js +1 -1
  65. package/src/maths/mat4/identity.test.js +1 -1
  66. package/src/maths/mat4/invert.js +18 -18
  67. package/src/maths/mat4/isIdentity.js +1 -1
  68. package/src/maths/mat4/isMirroring.js +4 -4
  69. package/src/maths/mat4/isMirroring.test.js +1 -1
  70. package/src/maths/mat4/leftMultiplyVec3.js +2 -2
  71. package/src/maths/mat4/toString.js +2 -2
  72. package/src/maths/mat4/translate.test.js +1 -1
  73. package/src/maths/plane/flip.test.js +1 -1
  74. package/src/maths/plane/fromPoints.d.ts +1 -1
  75. package/src/maths/plane/fromPoints.js +1 -3
  76. package/src/maths/plane/signedDistanceToPoint.js +1 -1
  77. package/src/maths/plane/transform.test.js +1 -1
  78. package/src/maths/utils/aboutEqualNormals.js +2 -2
  79. package/src/maths/vec2/abs.d.ts +1 -1
  80. package/src/maths/vec2/add.test.js +1 -1
  81. package/src/maths/vec2/angleDegrees.d.ts +1 -1
  82. package/src/maths/vec2/angleRadians.d.ts +1 -1
  83. package/src/maths/vec2/create.js +1 -1
  84. package/src/maths/vec2/cross.test.js +1 -1
  85. package/src/maths/vec2/divide.test.js +1 -1
  86. package/src/maths/vec2/fromAngleDegrees.js +1 -1
  87. package/src/maths/vec2/fromScalar.js +1 -1
  88. package/src/maths/vec2/length.d.ts +1 -1
  89. package/src/maths/vec2/length.js +1 -1
  90. package/src/maths/vec2/lerp.test.js +1 -1
  91. package/src/maths/vec2/multiply.test.js +1 -1
  92. package/src/maths/vec2/negate.test.js +1 -1
  93. package/src/maths/vec2/normal.js +1 -1
  94. package/src/maths/vec2/normalize.d.ts +1 -1
  95. package/src/maths/vec2/normalize.test.js +1 -1
  96. package/src/maths/vec2/rotate.test.js +1 -1
  97. package/src/maths/vec2/squaredLength.d.ts +1 -1
  98. package/src/maths/vec2/squaredLength.js +3 -3
  99. package/src/maths/vec2/subtract.test.js +1 -1
  100. package/src/maths/vec2/toString.js +1 -1
  101. package/src/maths/vec2/transform.test.js +1 -1
  102. package/src/maths/vec3/abs.d.ts +1 -1
  103. package/src/maths/vec3/add.test.js +1 -1
  104. package/src/maths/vec3/cross.test.js +1 -1
  105. package/src/maths/vec3/divide.test.js +1 -1
  106. package/src/maths/vec3/fromScalar.js +1 -1
  107. package/src/maths/vec3/fromVec2.d.ts +1 -1
  108. package/src/maths/vec3/fromVec2.js +3 -3
  109. package/src/maths/vec3/length.d.ts +1 -1
  110. package/src/maths/vec3/length.js +4 -4
  111. package/src/maths/vec3/lerp.test.js +1 -1
  112. package/src/maths/vec3/multiply.test.js +1 -1
  113. package/src/maths/vec3/negate.d.ts +1 -1
  114. package/src/maths/vec3/negate.test.js +1 -1
  115. package/src/maths/vec3/normalize.d.ts +1 -1
  116. package/src/maths/vec3/normalize.test.js +1 -1
  117. package/src/maths/vec3/rotateX.test.js +1 -1
  118. package/src/maths/vec3/rotateY.test.js +1 -1
  119. package/src/maths/vec3/rotateZ.test.js +1 -1
  120. package/src/maths/vec3/scale.test.js +1 -1
  121. package/src/maths/vec3/squaredLength.d.ts +1 -1
  122. package/src/maths/vec3/squaredLength.js +4 -4
  123. package/src/maths/vec3/subtract.test.js +1 -1
  124. package/src/maths/vec3/toString.js +1 -1
  125. package/src/maths/vec3/transform.test.js +1 -1
  126. package/src/maths/vec4/toString.js +1 -1
  127. package/src/maths/vec4/transform.test.js +1 -1
  128. package/src/measurements/measureBoundingSphere.js +4 -4
  129. package/src/measurements/measureCenterOfMass.js +1 -1
  130. package/src/measurements/measureCenterOfMass.test.js +2 -2
  131. package/src/operations/booleans/intersect.test.js +8 -0
  132. package/src/operations/booleans/mayOverlap.js +3 -3
  133. package/src/operations/booleans/retessellate.js +2 -2
  134. package/src/operations/booleans/scission.js +1 -1
  135. package/src/operations/booleans/scission.test.js +4 -4
  136. package/src/operations/booleans/subtract.js +1 -1
  137. package/src/operations/booleans/subtract.test.js +8 -0
  138. package/src/operations/booleans/trees/Node.js +10 -16
  139. package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
  140. package/src/operations/booleans/trees/Tree.js +1 -2
  141. package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
  142. package/src/operations/booleans/union.test.js +28 -1
  143. package/src/operations/booleans/unionGeom3Sub.js +1 -1
  144. package/src/operations/expansions/expand.js +2 -2
  145. package/src/operations/expansions/expand.test.js +32 -55
  146. package/src/operations/expansions/expandShell.js +24 -18
  147. package/src/operations/expansions/offset.js +1 -1
  148. package/src/operations/expansions/offset.test.js +50 -89
  149. package/src/operations/expansions/offsetFromPoints.js +11 -6
  150. package/src/operations/extrusions/earcut/assignHoles.js +91 -0
  151. package/src/operations/extrusions/earcut/assignHoles.test.js +74 -0
  152. package/src/operations/extrusions/earcut/eliminateHoles.js +131 -0
  153. package/src/operations/extrusions/earcut/index.js +252 -0
  154. package/src/operations/extrusions/earcut/linkedList.js +58 -0
  155. package/src/operations/extrusions/earcut/linkedListSort.js +54 -0
  156. package/src/operations/extrusions/earcut/linkedPolygon.js +197 -0
  157. package/src/operations/extrusions/earcut/polygonHierarchy.js +64 -0
  158. package/src/operations/extrusions/earcut/triangle.js +16 -0
  159. package/src/operations/extrusions/extrudeFromSlices.js +10 -3
  160. package/src/operations/extrusions/extrudeFromSlices.test.js +47 -31
  161. package/src/operations/extrusions/extrudeLinear.js +10 -5
  162. package/src/operations/extrusions/extrudeLinear.test.js +91 -35
  163. package/src/operations/extrusions/extrudeLinearGeom2.js +5 -2
  164. package/src/operations/extrusions/extrudeLinearPath2.js +24 -0
  165. package/src/operations/extrusions/extrudeRectangular.js +1 -1
  166. package/src/operations/extrusions/extrudeRectangular.test.js +22 -15
  167. package/src/operations/extrusions/extrudeRotate.test.js +31 -27
  168. package/src/operations/extrusions/project.js +1 -1
  169. package/src/operations/extrusions/project.test.js +5 -5
  170. package/src/operations/extrusions/slice/calculatePlane.js +7 -4
  171. package/src/operations/extrusions/slice/isA.js +2 -2
  172. package/src/operations/extrusions/slice/repairSlice.js +47 -0
  173. package/src/operations/extrusions/slice/toPolygons.js +24 -60
  174. package/src/operations/hulls/hull.test.js +25 -2
  175. package/src/operations/hulls/hullChain.js +1 -1
  176. package/src/operations/hulls/hullChain.test.js +6 -4
  177. package/src/operations/hulls/hullGeom2.js +1 -1
  178. package/src/operations/hulls/hullPath2.js +6 -4
  179. package/src/operations/hulls/hullPath2.test.js +16 -0
  180. package/src/operations/hulls/hullPoints2.test.js +1 -1
  181. package/src/operations/modifiers/edges.js +1 -1
  182. package/src/operations/modifiers/generalize.js +1 -1
  183. package/src/operations/modifiers/generalize.test.js +6 -0
  184. package/src/operations/modifiers/snap.test.js +3 -3
  185. package/src/operations/transforms/align.d.ts +1 -1
  186. package/src/operations/transforms/align.test.js +12 -0
  187. package/src/operations/transforms/center.js +17 -17
  188. package/src/operations/transforms/center.test.js +12 -0
  189. package/src/operations/transforms/mirror.js +12 -12
  190. package/src/operations/transforms/mirror.test.js +16 -0
  191. package/src/operations/transforms/rotate.js +12 -12
  192. package/src/operations/transforms/rotate.test.js +10 -0
  193. package/src/operations/transforms/scale.js +19 -19
  194. package/src/operations/transforms/scale.test.js +15 -0
  195. package/src/operations/transforms/transform.js +3 -3
  196. package/src/operations/transforms/transform.test.js +5 -0
  197. package/src/operations/transforms/translate.js +14 -14
  198. package/src/operations/transforms/translate.test.js +16 -0
  199. package/src/primitives/arc.js +1 -1
  200. package/src/primitives/arc.test.js +11 -0
  201. package/src/primitives/circle.test.js +15 -9
  202. package/src/primitives/cube.test.js +3 -0
  203. package/src/primitives/cuboid.test.js +9 -24
  204. package/src/primitives/cylinder.test.js +7 -4
  205. package/src/primitives/cylinderElliptic.js +13 -6
  206. package/src/primitives/cylinderElliptic.test.js +72 -52
  207. package/src/primitives/ellipse.js +3 -1
  208. package/src/primitives/ellipse.test.js +14 -8
  209. package/src/primitives/ellipsoid.js +7 -5
  210. package/src/primitives/ellipsoid.test.js +84 -82
  211. package/src/primitives/geodesicSphere.d.ts +0 -1
  212. package/src/primitives/geodesicSphere.test.js +3 -0
  213. package/src/primitives/line.test.js +1 -0
  214. package/src/primitives/polygon.test.js +15 -10
  215. package/src/primitives/polyhedron.js +1 -1
  216. package/src/primitives/polyhedron.test.js +14 -42
  217. package/src/primitives/rectangle.test.js +3 -0
  218. package/src/primitives/roundedCuboid.test.js +5 -0
  219. package/src/primitives/roundedCylinder.js +6 -4
  220. package/src/primitives/roundedCylinder.test.js +40 -36
  221. package/src/primitives/roundedRectangle.test.js +5 -0
  222. package/src/primitives/sphere.test.js +52 -73
  223. package/src/primitives/square.test.js +3 -0
  224. package/src/primitives/star.test.js +6 -0
  225. package/src/primitives/torus.d.ts +0 -1
  226. package/src/primitives/torus.test.js +8 -1
  227. package/src/primitives/triangle.js +1 -1
  228. package/src/primitives/triangle.test.js +7 -0
  229. package/src/text/vectorText.js +2 -2
  230. package/src/utils/areAllShapesTheSameType.js +2 -2
  231. package/src/utils/areAllShapesTheSameType.test.js +17 -0
  232. package/src/utils/index.d.ts +1 -0
  233. package/src/utils/index.js +3 -1
  234. package/src/utils/padArrayToLength.js +1 -1
  235. package/src/utils/trigonometry.d.ts +2 -0
  236. package/src/utils/trigonometry.js +35 -0
  237. package/src/utils/trigonometry.test.js +25 -0
  238. package/test/helpers/nearlyEqual.js +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.7.2",
3
+ "version": "2.9.1",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "repository": "https://github.com/jscad/OpenJSCAD.org",
6
6
  "main": "src/index.js",
@@ -60,5 +60,5 @@
60
60
  "nyc": "15.1.0",
61
61
  "uglifyify": "5.0.2"
62
62
  },
63
- "gitHead": "b6c5675d2d9a292e0ba24896bf22d0e9dc5d4270"
63
+ "gitHead": "a90b9bad95a417661c619dc733e62c587dc71a4a"
64
64
  }
@@ -20,7 +20,7 @@ test('quadratic bezier (3 control points)', (t) => {
20
20
  t.is(bezier.tangentAt(1, OneDCurve), 20)
21
21
  })
22
22
 
23
- test('quadratic bezier (3 control points, non symetric)', (t) => {
23
+ test('quadratic bezier (3 control points, non symmetric)', (t) => {
24
24
  const OneDCurve = bezier.create([0, 0, 20])
25
25
  t.is(bezier.tangentAt(0, OneDCurve), 0)
26
26
  t.is(bezier.tangentAt(0.5, OneDCurve), 20)
@@ -20,7 +20,7 @@ test('quadratic bezier (3 control points)', (t) => {
20
20
  t.is(bezier.valueAt(1, OneDCurve), 20)
21
21
  })
22
22
 
23
- test('quadratic bezier (3 control points, non symetric)', (t) => {
23
+ test('quadratic bezier (3 control points, non symmetric)', (t) => {
24
24
  const OneDCurve = bezier.create([0, 0, 20])
25
25
  t.is(bezier.valueAt(0, OneDCurve), 0)
26
26
  t.is(bezier.valueAt(0.5, OneDCurve), 5)
@@ -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
@@ -2,6 +2,16 @@
2
2
  * Represents a 2D geometry consisting of a list of sides.
3
3
  * @see {@link geom2} for data structure information.
4
4
  * @module modeling/geometries/geom2
5
+ *
6
+ * @example
7
+ * colorize([0.5,0,1,1], square()) // purple square
8
+ *
9
+ * @example
10
+ * {
11
+ * "sides": [[[-1,1],[-1,-1]],[[-1,-1],[1,-1]],[[1,-1],[1,1]],[[1,1],[-1,1]]],
12
+ * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
13
+ * "color": [0.5,0,1,1]
14
+ * }
5
15
  */
6
16
  module.exports = {
7
17
  clone: require('./clone'),
@@ -15,5 +25,6 @@ module.exports = {
15
25
  toSides: require('./toSides'),
16
26
  toString: require('./toString'),
17
27
  toCompactBinary: require('./toCompactBinary'),
18
- transform: require('./transform')
28
+ transform: require('./transform'),
29
+ validate: require('./validate')
19
30
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Determin if the given object is a 2D geometry.
3
- * @param {Object} object - the object to interogate
2
+ * Determine if the given object is a 2D geometry.
3
+ * @param {Object} object - the object to interrogate
4
4
  * @returns {Boolean} true, if the object matches a geom2 based object
5
5
  * @alias module:modeling/geometries/geom2.isA
6
6
  */
@@ -4,11 +4,11 @@
4
4
  * @returns {TypedArray} compact binary representation
5
5
  * @alias module:modeling/geometries/geom2.toCompactBinary
6
6
  */
7
- const toCompactBinary = (geom) => {
8
- const sides = geom.sides
9
- const transforms = geom.transforms
7
+ const toCompactBinary = (geometry) => {
8
+ const sides = geometry.sides
9
+ const transforms = geometry.transforms
10
10
  let color = [-1, -1, -1, -1]
11
- if (geom.color) color = geom.color
11
+ if (geometry.color) color = geometry.color
12
12
 
13
13
  // FIXME why Float32Array?
14
14
  const compacted = new Float32Array(1 + 16 + 4 + (sides.length * 4)) // type + transforms + color + sides data
@@ -5,7 +5,7 @@ const toSides = require('./toSides')
5
5
  /**
6
6
  * Create a string representing the contents of the given geometry.
7
7
  * @param {geom2} geometry - the geometry
8
- * @returns {String} a representive string
8
+ * @returns {String} a representative string
9
9
  * @alias module:modeling/geometries/geom2.toString
10
10
  *
11
11
  * @example
@@ -11,7 +11,7 @@ test('transform: adjusts the transforms of geom2', (t) => {
11
11
  const rotation = 90 * 0.017453292519943295
12
12
  const rotate90 = mat4.fromZRotation(mat4.create(), rotation)
13
13
 
14
- // continue with typical user scenario, several itterations of transforms and access
14
+ // continue with typical user scenario, several iterations of transforms and access
15
15
 
16
16
  // expect lazy transform, i.e. only the transforms change
17
17
  const expected = {
@@ -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
@@ -33,7 +33,7 @@ const fromCompactBinary = (data) => {
33
33
  created.polygons.push(poly3.create(vertices))
34
34
  }
35
35
 
36
- // transfer known properities, i.e. color
36
+ // transfer known properties, i.e. color
37
37
  if (data[17] >= 0) {
38
38
  created.color = [data[17], data[18], data[19], data[20]]
39
39
  }
@@ -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
@@ -2,6 +2,23 @@
2
2
  * Represents a 3D geometry consisting of a list of polygons.
3
3
  * @see {@link geom3} for data structure information.
4
4
  * @module modeling/geometries/geom3
5
+ *
6
+ * @example
7
+ * colorize([0,0.5,1,0.6], cube()) // transparent ice cube
8
+ *
9
+ * @example
10
+ * {
11
+ * "polygons": [
12
+ * {"vertices": [[-1,-1,-1], [-1,-1,1], [-1,1,1], [-1,1,-1]]},
13
+ * {"vertices": [[1,-1,-1], [1,1,-1], [1,1,1], [1,-1,1]]},
14
+ * {"vertices": [[-1,-1,-1], [1,-1,-1], [1,-1,1], [-1,-1,1]]},
15
+ * {"vertices": [[-1,1,-1], [-1,1,1], [1,1,1], [1,1,-1]]},
16
+ * {"vertices": [[-1,-1,-1], [-1,1,-1], [1,1,-1], [1,-1,-1]]},
17
+ * {"vertices": [[-1,-1,1], [1,-1,1], [1,1,1], [-1,1,1]]}
18
+ * ],
19
+ * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
20
+ * "color": [0,0.5,1,0.6]
21
+ * }
5
22
  */
6
23
  module.exports = {
7
24
  clone: require('./clone'),
@@ -14,5 +31,6 @@ module.exports = {
14
31
  toPolygons: require('./toPolygons'),
15
32
  toString: require('./toString'),
16
33
  toCompactBinary: require('./toCompactBinary'),
17
- transform: require('./transform')
34
+ transform: require('./transform'),
35
+ validate: require('./validate')
18
36
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Determin if the given object is a 3D geometry.
3
- * @param {object} object - the object to interogate
2
+ * Determine if the given object is a 3D geometry.
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
  */
@@ -6,14 +6,14 @@ const poly3 = require('../poly3')
6
6
  * @return {TypedArray} compact binary representation
7
7
  * @alias module:modeling/geometries/geom3.toCompactBinary
8
8
  */
9
- const toCompactBinary = (geom) => {
10
- const polygons = geom.polygons
11
- const transforms = geom.transforms
9
+ const toCompactBinary = (geometry) => {
10
+ const polygons = geometry.polygons
11
+ const transforms = geometry.transforms
12
12
 
13
13
  const numberOfPolygons = polygons.length
14
14
  const numberOfVertices = polygons.reduce((count, polygon) => count + polygon.vertices.length, 0)
15
15
  let color = [-1, -1, -1, -1]
16
- if (geom.color) color = geom.color
16
+ if (geometry.color) color = geometry.color
17
17
 
18
18
  // FIXME why Float32Array?
19
19
  const compacted = new Float32Array(1 + 16 + 4 + 1 + numberOfPolygons + (numberOfVertices * 3))
@@ -5,7 +5,7 @@ const toPolygons = require('./toPolygons')
5
5
  /**
6
6
  * Create a string representing the contents of the given geometry.
7
7
  * @param {geom3} geometry - the geometry
8
- * @returns {String} a representive string
8
+ * @returns {String} a representative string
9
9
  * @alias module:modeling/geometries/geom3.toString
10
10
  *
11
11
  * @example
@@ -11,7 +11,7 @@ test('transform: Adjusts the transforms of a populated geom3', (t) => {
11
11
  const rotation = 90 * 0.017453292519943295
12
12
  const rotate90 = mat4.fromZRotation(mat4.create(), rotation)
13
13
 
14
- // continue with typical user scenario, several itterations of transforms and access
14
+ // continue with typical user scenario, several iterations of transforms and access
15
15
 
16
16
  // expect lazy transform, i.e. only the transforms change
17
17
  const expected = {
@@ -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
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * Geometries are objects that represent the contents of primitives or the results of operations.
3
- * Note: Geometries are consider immutable, so never change the contents directly.
3
+ * Note: Geometries are considered immutable, so never change the contents directly.
4
+ *
5
+ * @see {@link geom2} - 2D geometry consisting of sides
6
+ * @see {@link geom3} - 3D geometry consisting of polygons
7
+ * @see {@link path2} - 2D geometry consisting of ordered points
8
+ * @see {@link poly2} - 2D polygon consisting of ordered vertices
9
+ * @see {@link poly3} - 3D polygon consisting of ordered vertices
10
+ *
4
11
  * @module modeling/geometries
5
12
  * @example
6
13
  * const { geom2, geom3, path2, poly2, poly3 } = require('@jscad/modeling').geometries
@@ -1,14 +1,14 @@
1
1
  const toPoints = require('./toPoints')
2
2
 
3
3
  /**
4
- * Calls a function for each point in the geometry.
4
+ * Calls a function for each point in the path.
5
5
  * @param {Object} options - options
6
6
  * @param {Function} thunk - the function to call
7
- * @param {path2} geometry - the geometry to traverse
7
+ * @param {path2} path - the path to traverse
8
8
  * @alias module:modeling/geometries/path2.eachPoint
9
9
  *
10
10
  * @example
11
- * eachPoint({}, accumulate, geometry)
11
+ * eachPoint({}, accumulate, path)
12
12
  */
13
13
  const eachPoint = (options, thunk, path) => {
14
14
  toPoints(path).forEach(thunk)
@@ -15,6 +15,7 @@ export { default as toPoints } from './toPoints'
15
15
  export { default as toString } from './toString'
16
16
  export { default as toCompactBinary } from './toCompactBinary'
17
17
  export { default as transform } from './transform'
18
+ export { default as validate } from './validate'
18
19
 
19
20
  export { default as Path2 } from './type'
20
21
  export as namespace path2
@@ -2,6 +2,17 @@
2
2
  * Represents a 2D geometry consisting of a list of ordered points.
3
3
  * @see {@link path2} for data structure information.
4
4
  * @module modeling/geometries/path2
5
+ *
6
+ * @example
7
+ * colorize([0,0,0,1], path2.fromPoints({ closed: true }, [[0,0], [4,0], [4,3]]))
8
+ *
9
+ * @example
10
+ * {
11
+ * "points": [[0,0], [4,0], [4,3]],
12
+ * "isClosed": true,
13
+ * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
14
+ * "color": [0,0,0,1]
15
+ * }
5
16
  */
6
17
  module.exports = {
7
18
  appendArc: require('./appendArc'),
@@ -20,5 +31,6 @@ module.exports = {
20
31
  toPoints: require('./toPoints'),
21
32
  toString: require('./toString'),
22
33
  toCompactBinary: require('./toCompactBinary'),
23
- transform: require('./transform')
34
+ transform: require('./transform'),
35
+ validate: require('./validate')
24
36
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Determin if the given object is a path2 geometry.
3
- * @param {Object} object - the object to interogate
2
+ * Determine if the given object is a path2 geometry.
3
+ * @param {Object} object - the object to interrogate
4
4
  * @returns {Boolean} true if the object matches a path2
5
5
  * @alias module:modeling/geometries/path2.isA
6
6
  */
@@ -3,17 +3,17 @@ const clone = require('./clone')
3
3
  /**
4
4
  * Reverses the path so that the points are in the opposite order.
5
5
  * This swaps the left (interior) and right (exterior) edges.
6
- * @param {path2} geometry - the geometry to reverse
6
+ * @param {path2} geometry - the path to reverse
7
7
  * @returns {path2} a new path
8
8
  * @alias module:modeling/geometries/path2.reverse
9
9
  *
10
10
  * @example
11
11
  * let newpath = reverse(mypath)
12
12
  */
13
- const reverse = (path) => {
13
+ const reverse = (geometry) => {
14
14
  // NOTE: this only updates the order of the points
15
- const cloned = clone(path)
16
- cloned.points = path.points.slice().reverse()
15
+ const cloned = clone(geometry)
16
+ cloned.points = geometry.points.slice().reverse()
17
17
  return cloned
18
18
  }
19
19
 
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Produce a compact binary representation from the given path.
3
- * @param {path2} geometry - the path
3
+ * @param {path2} geometry - the path geometry
4
4
  * @returns {TypedArray} compact binary representation
5
5
  * @alias module:modeling/geometries/path2.toCompactBinary
6
6
  */
7
- const toCompactBinary = (geom) => {
8
- const points = geom.points
9
- const transforms = geom.transforms
7
+ const toCompactBinary = (geometry) => {
8
+ const points = geometry.points
9
+ const transforms = geometry.transforms
10
10
  let color = [-1, -1, -1, -1]
11
- if (geom.color) color = geom.color
11
+ if (geometry.color) color = geometry.color
12
12
 
13
13
  // FIXME why Float32Array?
14
14
  const compacted = new Float32Array(1 + 16 + 1 + 4 + (points.length * 2)) // type + transforms + isClosed + color + points data
@@ -32,7 +32,7 @@ const toCompactBinary = (geom) => {
32
32
  compacted[15] = transforms[14]
33
33
  compacted[16] = transforms[15]
34
34
 
35
- compacted[17] = geom.isClosed ? 1 : 0
35
+ compacted[17] = geometry.isClosed ? 1 : 0
36
36
 
37
37
  compacted[18] = color[0]
38
38
  compacted[19] = color[1]
@@ -5,7 +5,7 @@ const toPoints = require('./toPoints')
5
5
  /**
6
6
  * Create a string representing the contents of the given path.
7
7
  * @param {path2} geometry - the path
8
- * @returns {String} a representive string
8
+ * @returns {String} a representative string
9
9
  * @alias module:modeling/geometries/path2.toString
10
10
  *
11
11
  * @example
@@ -11,7 +11,7 @@ test('transform: adjusts the transforms of path', (t) => {
11
11
  const rotation = 90 * 0.017453292519943295
12
12
  const rotate90 = mat4.fromZRotation(mat4.create(), rotation)
13
13
 
14
- // continue with typical user scenario, several itterations of transforms and access
14
+ // continue with typical user scenario, several iterations of transforms and access
15
15
 
16
16
  // expect lazy transform, i.e. only the transforms change
17
17
  const expected = {
@@ -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
  *
@@ -12,7 +12,7 @@ test('poly2: arePointsInside() should return proper values', (t) => {
12
12
  t.is(obs, 0)
13
13
 
14
14
  polygon = create([[0, 0], [5, 0], [5, 5], [0, 5]])
15
- // points overlaping points
15
+ // points overlapping points
16
16
  obs = arePointsInside([[0, 0]], polygon)
17
17
  t.is(obs, 0)
18
18
  obs = arePointsInside([[5, 0]], polygon)
@@ -2,6 +2,12 @@
2
2
  * Represents a 2D polygon consisting of a list of ordered vertices.
3
3
  * @see {@link poly2} for data structure information.
4
4
  * @module modeling/geometries/poly2
5
+ *
6
+ * @example
7
+ * poly2.create([[0,0], [4,0], [4,3]])
8
+ *
9
+ * @example
10
+ * {"vertices": [[0,0], [4,0], [4,3]]}
5
11
  */
6
12
  module.exports = {
7
13
  arePointsInside: require('./arePointsInside'),
@@ -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
@@ -1,7 +1,13 @@
1
1
  /**
2
- * Represents a convex 3D polygon consisting of a list of vertices.
2
+ * Represents a convex 3D polygon consisting of a list of ordered vertices.
3
3
  * @see {@link poly3} for data structure information.
4
4
  * @module modeling/geometries/poly3
5
+ *
6
+ * @example
7
+ * poly3.create([[0,0,0], [4,0,0], [4,3,12]])
8
+ *
9
+ * @example
10
+ * {"vertices": [[0,0,0], [4,0,0], [4,3,12]]}
5
11
  */
6
12
  module.exports = {
7
13
  clone: require('./clone'),
@@ -18,5 +24,6 @@ module.exports = {
18
24
  plane: require('./plane'),
19
25
  toPoints: require('./toPoints'),
20
26
  toString: require('./toString'),
21
- transform: require('./transform')
27
+ transform: require('./transform'),
28
+ validate: require('./validate')
22
29
  }
@@ -1,3 +1,4 @@
1
+ const plane = require('../../maths/plane')
1
2
  const create = require('./create')
2
3
 
3
4
  /**
@@ -9,7 +10,12 @@ const create = require('./create')
9
10
  */
10
11
  const invert = (polygon) => {
11
12
  const vertices = polygon.vertices.slice().reverse()
12
- return create(vertices)
13
+ const inverted = create(vertices)
14
+ if (polygon.plane) {
15
+ // Flip existing plane to save recompute
16
+ inverted.plane = plane.flip(plane.create(), polygon.plane)
17
+ }
18
+ return inverted
13
19
  }
14
20
 
15
21
  module.exports = invert