@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
@@ -11,6 +11,7 @@ test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3'
11
11
 
12
12
  const geometry3 = extrudeRotate({ }, geometry2)
13
13
  const pts = geom3.toPoints(geometry3)
14
+ t.notThrows(() => geom3.validate(geometry3))
14
15
  t.is(pts.length, 96)
15
16
  })
16
17
 
@@ -34,15 +35,18 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
34
35
  [[26, 4.898587196589413e-16, 8], [10, 4.898587196589413e-16, 8], [10, -4.898587196589413e-16, -8]],
35
36
  [[10, -4.898587196589413e-16, -8], [26, -4.898587196589413e-16, -8], [26, 4.898587196589413e-16, 8]]
36
37
  ]
38
+ t.notThrows(() => geom3.validate(geometry3))
37
39
  t.is(pts.length, 12)
38
40
  t.true(comparePolygonsAsPoints(pts, exp))
39
41
 
40
42
  geometry3 = extrudeRotate({ segments: 4, angle: -250 * 0.017453292519943295 }, geometry2)
41
43
  pts = geom3.toPoints(geometry3)
44
+ t.notThrows(() => geom3.validate(geometry3))
42
45
  t.is(pts.length, 28)
43
46
 
44
47
  geometry3 = extrudeRotate({ segments: 4, angle: 250 * 0.017453292519943295 }, geometry2)
45
48
  pts = geom3.toPoints(geometry3)
49
+ t.notThrows(() => geom3.validate(geometry3))
46
50
  t.is(pts.length, 28)
47
51
  })
48
52
 
@@ -57,6 +61,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
57
61
  [18.38477631085024, 18.384776310850235, 8],
58
62
  [-11.803752993228215, 23.166169628897567, 8]
59
63
  ]
64
+ t.notThrows(() => geom3.validate(geometry3))
60
65
  t.is(pts.length, 40)
61
66
  t.true(comparePoints(pts[0], exp))
62
67
 
@@ -67,6 +72,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
67
72
  [18.38477631085024, -18.384776310850235, 8],
68
73
  [23.166169628897567, 11.803752993228215, 8]
69
74
  ]
75
+ t.notThrows(() => geom3.validate(geometry3))
70
76
  t.is(pts.length, 40)
71
77
  t.true(comparePoints(pts[0], exp))
72
78
  })
@@ -77,22 +83,26 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
77
83
  // test segments
78
84
  let geometry3 = extrudeRotate({ segments: 4 }, geometry2)
79
85
  let pts = geom3.toPoints(geometry3)
86
+ t.notThrows(() => geom3.validate(geometry3))
80
87
  t.is(pts.length, 32)
81
88
 
82
89
  geometry3 = extrudeRotate({ segments: 64 }, geometry2)
83
90
  pts = geom3.toPoints(geometry3)
91
+ t.notThrows(() => geom3.validate(geometry3))
84
92
  t.is(pts.length, 512)
85
93
 
86
94
  // test overlapping edges
87
95
  geometry2 = geom2.fromPoints([[0, 0], [2, 1], [1, 2], [1, 3], [3, 4], [0, 5]])
88
96
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
89
97
  pts = geom3.toPoints(geometry3)
98
+ t.notThrows.skip(() => geom3.validate(geometry3))
90
99
  t.is(pts.length, 64)
91
100
 
92
101
  // test overlapping edges that produce hollow shape
93
102
  geometry2 = geom2.fromPoints([[30, 0], [30, 60], [0, 60], [0, 50], [10, 40], [10, 30], [0, 20], [0, 10], [10, 0]])
94
103
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
95
104
  pts = geom3.toPoints(geometry3)
105
+ t.notThrows.skip(() => geom3.validate(geometry3))
96
106
  t.is(pts.length, 80)
97
107
  })
98
108
 
@@ -112,6 +122,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
112
122
  [[7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8], [0, -4.898587196589413e-16, -8]],
113
123
  [[0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8], [7, 4.898587196589413e-16, 8]]
114
124
  ]
125
+ t.notThrows.skip(() => geom3.validate(obs))
115
126
  t.is(pts.length, 8)
116
127
  t.true(comparePolygonsAsPoints(pts, exp))
117
128
 
@@ -140,6 +151,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
140
151
  [[2, 2.4492935982947064e-16, 4], [1, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8]],
141
152
  [[0, 4.898587196589413e-16, 8], [1, -4.898587196589413e-16, -8], [2, 2.4492935982947064e-16, 4]]
142
153
  ]
154
+ t.notThrows.skip(() => geom3.validate(obs))
143
155
  t.is(pts.length, 18)
144
156
  t.true(comparePolygonsAsPoints(pts, exp))
145
157
  })
@@ -64,11 +64,11 @@ const extrudeWalls = (slice0, slice1) => {
64
64
  edges0.forEach((edge0, i) => {
65
65
  const edge1 = edges1[i]
66
66
 
67
- const poly0 = poly3.fromPoints([edge0[0], edge0[1], edge1[1]])
67
+ const poly0 = poly3.create([edge0[0], edge0[1], edge1[1]])
68
68
  const poly0area = poly3.measureArea(poly0)
69
69
  if (Number.isFinite(poly0area) && poly0area > EPSAREA) walls.push(poly0)
70
70
 
71
- const poly1 = poly3.fromPoints([edge0[0], edge1[1], edge1[0]])
71
+ const poly1 = poly3.create([edge0[0], edge1[1], edge1[0]])
72
72
  const poly1area = poly3.measureArea(poly1)
73
73
  if (Number.isFinite(poly1area) && poly1area > EPSAREA) walls.push(poly1)
74
74
  })
@@ -11,7 +11,6 @@ const poly3 = require('../../geometries/poly3')
11
11
  const measureEpsilon = require('../../measurements/measureEpsilon')
12
12
 
13
13
  const unionGeom2 = require('../booleans/unionGeom2')
14
- const unionGeom3 = require('../booleans/unionGeom3')
15
14
 
16
15
  const projectGeom3 = (options, geometry) => {
17
16
  // create a plane from the options, and verify
@@ -27,32 +26,30 @@ const projectGeom3 = (options, geometry) => {
27
26
 
28
27
  // project the polygons to the plane
29
28
  const polygons = geom3.toPolygons(geometry)
30
- const projpolys = []
29
+ let projpolys = []
31
30
  for (let i = 0; i < polygons.length; i++) {
32
31
  const newpoints = polygons[i].vertices.map((v) => plane.projectionOfPoint(projplane, v))
33
32
  const newpoly = poly3.create(newpoints)
34
- // only keep projections that have a measurable area
35
- if (poly3.measureArea(newpoly) < epsilonArea) continue
36
33
  // only keep projections that face the same direction as the plane
37
34
  const newplane = poly3.plane(newpoly)
38
35
  if (!aboutEqualNormals(projplane, newplane)) continue
36
+ // only keep projections that have a measurable area
37
+ if (poly3.measureArea(newpoly) < epsilonArea) continue
39
38
  projpolys.push(newpoly)
40
39
  }
41
- // union the projected polygons to eliminate overlaying polygons
42
- let projection = geom3.create(projpolys)
43
- projection = unionGeom3(projection, projection)
44
- // rotate the projection to lay on X/Y axes if necessary
40
+
41
+ // rotate the polygons to lay on X/Y axes if necessary
45
42
  if (!aboutEqualNormals(projplane, [0, 0, 1])) {
46
43
  const rotation = mat4.fromVectorRotation(mat4.create(), projplane, [0, 0, 1])
47
- projection = geom3.transform(rotation, projection)
44
+ projpolys = projpolys.map((p) => poly3.transform(rotation, p))
48
45
  }
49
46
 
50
- // convert the projection (polygons) into a series of 2D geometry
51
- const projections2D = geom3.toPolygons(projection).map((p) => geom2.fromPoints(poly3.toPoints(p)))
52
- // union the 2D geometries to obtain the outline of the projection
53
- projection = unionGeom2(projections2D)
47
+ // sort the polygons to allow the union to ignore small pieces efficiently
48
+ projpolys = projpolys.sort((a, b) => poly3.measureArea(b) - poly3.measureArea(a))
54
49
 
55
- return projection
50
+ // convert polygons to geometry, and union all pieces into a single geometry
51
+ const projgeoms = projpolys.map((p) => geom2.fromPoints(p.vertices))
52
+ return unionGeom2(projgeoms)
56
53
  }
57
54
 
58
55
  /**
@@ -16,108 +16,108 @@ test('project (defaults)', (t) => {
16
16
 
17
17
  const results = project({ }, geometry0, geometry1, geometry2, geometry3, geometry4)
18
18
  t.is(results.length, 5)
19
- t.true(geom2.isA(results[0]))
20
- t.true(geom2.isA(results[1]))
19
+ t.notThrows(() => geom2.validate(results[0]))
20
+ t.notThrows(() => geom2.validate(results[1]))
21
21
  t.is(results[2], geometry2)
22
22
  t.is(results[3], geometry3)
23
23
  t.is(results[4], geometry4)
24
24
 
25
25
  const result = project({ }, torus({ outerSegments: 4 }))
26
- t.true(geom2.isA(result))
26
+ t.notThrows(() => geom2.validate(result))
27
27
  const pts = geom2.toPoints(result)
28
28
  const exp = [
29
- [0, -2.9999933333333333],
30
29
  [0, -5.000013333333333],
31
- [0, 5.000013333333333],
32
30
  [5.000013333333333, 0],
33
- [0, 2.9999933333333333],
31
+ [-5.000013333333333, 0],
34
32
  [-2.9999933333333333, 0],
35
33
  [2.9999933333333333, 0],
36
- [-5.000013333333333, 0]
34
+ [0, 2.9999933333333333],
35
+ [0, -2.9999933333333333],
36
+ [0, 5.000013333333333]
37
37
  ]
38
38
  t.true(comparePoints(pts, exp))
39
39
  })
40
40
 
41
41
  test('project (X and Y axis)', (t) => {
42
42
  let result = project({ axis: [1, 0, 0], origin: [1, 0, 0] }, torus({ outerSegments: 4 }))
43
- t.true(geom2.isA(result))
43
+ t.notThrows(() => geom2.validate(result))
44
44
  let pts = geom2.toPoints(result)
45
45
  let exp = [
46
- [-1.0000200000000001, -3.999986666666667],
47
- [-0.8314600000000001, 4.555553333333334],
48
- [-0.9807933333333334, -4.1951],
49
- [-0.7070933333333335, 4.707126666666667],
50
- [-0.9238600000000001, -4.382700000000001],
51
- [-0.5555666666666668, 4.831446666666667],
52
- [-0.8314600000000001, -4.555553333333334],
53
- [-0.3826666666666667, 4.923893333333334],
54
- [-0.7070933333333335, -4.707126666666667],
55
- [-0.19511333333333336, 4.98078],
56
- [-0.5555666666666668, -4.831446666666667],
57
- [0, 5.000006666666668],
58
- [-0.3826666666666667, -4.923893333333334],
59
- [0.19511333333333336, 4.98078],
60
46
  [-0.19511333333333336, -4.98078],
61
- [0.3826666666666667, 4.923893333333334],
62
47
  [0, -5.000006666666668],
63
- [0.5555666666666668, 4.831446666666667],
48
+ [0, 5.000006666666668],
49
+ [0.3826666666666667, 4.923893333333334],
50
+ [-0.3826666666666667, -4.923893333333334],
64
51
  [0.19511333333333336, -4.98078],
65
- [0.7070933333333335, 4.707126666666667],
52
+ [-0.19511333333333336, 4.98078],
53
+ [-0.5555666666666668, -4.831446666666667],
54
+ [0.5555666666666668, 4.831446666666667],
55
+ [-0.3826666666666667, 4.923893333333334],
66
56
  [0.3826666666666667, -4.923893333333334],
67
- [0.8314600000000001, 4.555553333333334],
57
+ [0.7070933333333335, 4.707126666666667],
58
+ [-0.7070933333333335, -4.707126666666667],
68
59
  [0.5555666666666668, -4.831446666666667],
69
- [0.9238600000000001, 4.382700000000001],
60
+ [-0.5555666666666668, 4.831446666666667],
61
+ [0.8314600000000001, 4.555553333333334],
62
+ [-0.8314600000000001, -4.555553333333334],
70
63
  [0.7070933333333335, -4.707126666666667],
71
- [0.9807933333333334, 4.1951],
64
+ [-0.7070933333333335, 4.707126666666667],
65
+ [-0.9238600000000001, -4.382700000000001],
66
+ [0.9238600000000001, 4.382700000000001],
67
+ [-0.8314600000000001, 4.555553333333334],
72
68
  [0.8314600000000001, -4.555553333333334],
69
+ [0.9807933333333334, 4.1951],
70
+ [-0.9807933333333334, -4.1951],
71
+ [0.9238600000000001, -4.382700000000001],
72
+ [-0.9238600000000001, 4.382700000000001],
73
73
  [1.0000200000000001, 3.999986666666667],
74
+ [-1.0000200000000001, -3.999986666666667],
74
75
  [1.0000200000000001, -3.999986666666667],
75
- [0.9238600000000001, -4.382700000000001],
76
- [0.9807933333333334, -4.1951],
77
76
  [-1.0000200000000001, 3.999986666666667],
78
77
  [-0.9807933333333334, 4.1951],
79
- [-0.9238600000000001, 4.382700000000001]
78
+ [0.9807933333333334, -4.1951],
79
+ [0.19511333333333336, 4.98078]
80
80
  ]
81
81
  t.true(comparePoints(pts, exp))
82
82
 
83
83
  result = project({ axis: [0, 1, 0], origin: [0, -1, 0] }, torus({ outerSegments: 4 }))
84
- t.true(geom2.isA(result))
84
+ t.notThrows(() => geom2.validate(result))
85
85
  pts = geom2.toPoints(result)
86
86
  exp = [
87
- [3.999986666666667, -1.0000200000000001],
88
- [-4.555553333333334, -0.8314600000000001],
89
- [4.1951, -0.9807933333333334],
90
- [-4.707126666666667, -0.7070933333333335],
91
- [4.382700000000001, -0.9238600000000001],
92
- [-4.831446666666667, -0.5555666666666668],
93
- [4.555553333333334, -0.8314600000000001],
94
- [-4.923893333333334, -0.3826666666666667],
95
- [4.707126666666667, -0.7070933333333335],
96
- [-4.98078, -0.19511333333333336],
97
- [4.831446666666667, -0.5555666666666668],
98
- [-5.000006666666668, 0],
99
- [4.923893333333334, -0.3826666666666667],
100
- [-4.98078, 0.19511333333333336],
101
87
  [4.98078, -0.19511333333333336],
102
- [-4.923893333333334, 0.3826666666666667],
103
88
  [5.000006666666668, 0],
104
- [-4.831446666666667, 0.5555666666666668],
89
+ [-5.000006666666668, 0],
90
+ [-4.923893333333334, 0.3826666666666667],
91
+ [4.923893333333334, -0.3826666666666667],
105
92
  [4.98078, 0.19511333333333336],
106
- [-4.707126666666667, 0.7070933333333335],
93
+ [-4.98078, -0.19511333333333336],
94
+ [4.831446666666667, -0.5555666666666668],
95
+ [-4.831446666666667, 0.5555666666666668],
96
+ [-4.923893333333334, -0.3826666666666667],
107
97
  [4.923893333333334, 0.3826666666666667],
108
- [-4.555553333333334, 0.8314600000000001],
98
+ [-4.707126666666667, 0.7070933333333335],
99
+ [4.707126666666667, -0.7070933333333335],
109
100
  [4.831446666666667, 0.5555666666666668],
110
- [-4.382700000000001, 0.9238600000000001],
101
+ [-4.831446666666667, -0.5555666666666668],
102
+ [4.555553333333334, -0.8314600000000001],
103
+ [-4.555553333333334, 0.8314600000000001],
111
104
  [4.707126666666667, 0.7070933333333335],
112
- [-4.1951, 0.9807933333333334],
105
+ [-4.707126666666667, -0.7070933333333335],
106
+ [4.382700000000001, -0.9238600000000001],
107
+ [-4.382700000000001, 0.9238600000000001],
108
+ [-4.555553333333334, -0.8314600000000001],
113
109
  [4.555553333333334, 0.8314600000000001],
110
+ [-4.1951, 0.9807933333333334],
111
+ [4.1951, -0.9807933333333334],
112
+ [4.382700000000001, 0.9238600000000001],
113
+ [-4.382700000000001, -0.9238600000000001],
114
+ [3.999986666666667, -1.0000200000000001],
114
115
  [-3.999986666666667, 1.0000200000000001],
115
116
  [3.999986666666667, 1.0000200000000001],
116
- [4.382700000000001, 0.9238600000000001],
117
- [4.1951, 0.9807933333333334],
118
117
  [-3.999986666666667, -1.0000200000000001],
118
+ [4.1951, 0.9807933333333334],
119
119
  [-4.1951, -0.9807933333333334],
120
- [-4.382700000000001, -0.9238600000000001]
120
+ [-4.98078, 0.19511333333333336]
121
121
  ]
122
122
  t.true(comparePoints(pts, exp))
123
123
  })
@@ -16,12 +16,14 @@ test('hull (single, geom2)', (t) => {
16
16
  let obs = hull(geometry)
17
17
  let pts = geom2.toPoints(obs)
18
18
 
19
+ t.notThrows(() => geom2.validate(geometry))
19
20
  t.is(pts.length, 0)
20
21
 
21
22
  geometry = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
22
23
  obs = hull(geometry)
23
24
  pts = geom2.toPoints(obs)
24
25
 
26
+ t.notThrows(() => geom2.validate(geometry))
25
27
  t.is(pts.length, 4)
26
28
 
27
29
  // convex C shape
@@ -40,6 +42,7 @@ test('hull (single, geom2)', (t) => {
40
42
  obs = hull(geometry)
41
43
  pts = geom2.toPoints(obs)
42
44
 
45
+ t.notThrows(() => geom2.validate(geometry))
43
46
  t.is(pts.length, 7)
44
47
  })
45
48
 
@@ -66,23 +69,27 @@ test('hull (multiple, overlapping, geom2)', (t) => {
66
69
  let obs = hull(geometry1, geometry1)
67
70
  let pts = geom2.toPoints(obs)
68
71
 
72
+ t.notThrows(() => geom2.validate(obs))
69
73
  t.is(pts.length, 4)
70
74
 
71
75
  // one inside another
72
76
  obs = hull(geometry1, geometry2)
73
77
  pts = geom2.toPoints(obs)
74
78
 
79
+ t.notThrows(() => geom2.validate(obs))
75
80
  t.is(pts.length, 4)
76
81
 
77
82
  // one overlapping another
78
83
  obs = hull(geometry1, geometry3)
79
84
  pts = geom2.toPoints(obs)
80
85
 
86
+ t.notThrows(() => geom2.validate(obs))
81
87
  t.is(pts.length, 8)
82
88
 
83
89
  obs = hull(geometry2, geometry4)
84
90
  pts = geom2.toPoints(obs)
85
91
 
92
+ t.notThrows(() => geom2.validate(obs))
86
93
  t.is(pts.length, 7)
87
94
  })
88
95
 
@@ -108,22 +115,27 @@ test('hull (multiple, various, geom2)', (t) => {
108
115
 
109
116
  let obs = hull(geometry1, geometry2)
110
117
  let pts = geom2.toPoints(obs)
118
+ t.notThrows(() => geom2.validate(obs))
111
119
  t.is(pts.length, 5)
112
120
 
113
121
  obs = hull(geometry1, geometry3)
114
122
  pts = geom2.toPoints(obs)
123
+ t.notThrows(() => geom2.validate(obs))
115
124
  t.is(pts.length, 5)
116
125
 
117
126
  obs = hull(geometry2, geometry3)
118
127
  pts = geom2.toPoints(obs)
128
+ t.notThrows(() => geom2.validate(obs))
119
129
  t.is(pts.length, 5)
120
130
 
121
131
  obs = hull(geometry1, geometry2, geometry3)
122
132
  pts = geom2.toPoints(obs)
133
+ t.notThrows(() => geom2.validate(obs))
123
134
  t.is(pts.length, 6)
124
135
 
125
136
  obs = hull(geometry5, geometry4)
126
137
  pts = geom2.toPoints(obs)
138
+ t.notThrows(() => geom2.validate(obs))
127
139
  t.is(pts.length, 8)
128
140
  })
129
141
 
@@ -133,6 +145,7 @@ test('hull (single, path2)', (t) => {
133
145
  let obs = hull(geometry)
134
146
  let pts = path2.toPoints(obs)
135
147
 
148
+ t.notThrows(() => path2.validate(obs))
136
149
  t.is(pts.length, 0)
137
150
 
138
151
  geometry = path2.fromPoints({}, [[0, 0], [5, 0], [5, 10], [4, 1]])
@@ -140,6 +153,7 @@ test('hull (single, path2)', (t) => {
140
153
  obs = hull(geometry)
141
154
  pts = path2.toPoints(obs)
142
155
 
156
+ t.notThrows(() => path2.validate(obs))
143
157
  t.is(pts.length, 3)
144
158
  })
145
159
 
@@ -165,22 +179,27 @@ test('hull (multiple, various, path2)', (t) => {
165
179
 
166
180
  let obs = hull(geometry1, geometry2)
167
181
  let pts = path2.toPoints(obs)
182
+ t.notThrows(() => path2.validate(obs))
168
183
  t.is(pts.length, 5)
169
184
 
170
185
  obs = hull(geometry1, geometry3)
171
186
  pts = path2.toPoints(obs)
187
+ t.notThrows(() => path2.validate(obs))
172
188
  t.is(pts.length, 5)
173
189
 
174
190
  obs = hull(geometry2, geometry3)
175
191
  pts = path2.toPoints(obs)
192
+ t.notThrows(() => path2.validate(obs))
176
193
  t.is(pts.length, 5)
177
194
 
178
195
  obs = hull(geometry1, geometry2, geometry3)
179
196
  pts = path2.toPoints(obs)
197
+ t.notThrows(() => path2.validate(obs))
180
198
  t.is(pts.length, 6)
181
199
 
182
200
  obs = hull(geometry5, geometry4)
183
201
  pts = path2.toPoints(obs)
202
+ t.notThrows(() => path2.validate(obs))
184
203
  t.is(pts.length, 8)
185
204
  })
186
205
 
@@ -190,6 +209,7 @@ test('hull (single, geom3)', (t) => {
190
209
  let obs = hull(geometry)
191
210
  let pts = geom3.toPoints(obs)
192
211
 
212
+ t.notThrows(() => geom3.validate(obs))
193
213
  t.is(pts.length, 0)
194
214
 
195
215
  geometry = sphere({ radius: 2, segments: 8 })
@@ -197,6 +217,7 @@ test('hull (single, geom3)', (t) => {
197
217
  obs = hull(geometry)
198
218
  pts = geom3.toPoints(obs)
199
219
 
220
+ t.notThrows.skip(() => geom3.validate(obs))
200
221
  t.is(pts.length, 32)
201
222
  })
202
223
 
@@ -214,6 +235,7 @@ test('hull (multiple, geom3)', (t) => {
214
235
  [[1, -1, 1], [-1, -1, 1], [-1, -1, -1], [1, -1, -1]]
215
236
  ]
216
237
 
238
+ t.notThrows(() => geom3.validate(obs))
217
239
  t.is(pts.length, 6)
218
240
  t.true(comparePolygonsAsPoints(pts, exp))
219
241
 
@@ -236,6 +258,7 @@ test('hull (multiple, geom3)', (t) => {
236
258
  [[1, 1, -1], [6.5, 6.5, 3.5], [6.5, 3.5, 3.5], [1, -1, -1]]
237
259
  ]
238
260
 
261
+ t.notThrows(() => geom3.validate(obs))
239
262
  t.is(pts.length, 12)
240
263
  t.true(comparePolygonsAsPoints(pts, exp))
241
264
  })
@@ -248,6 +271,6 @@ test('hull (multiple, overlapping, geom3)', (t) => {
248
271
  const obs = hull(geometry1, geometry2, geometry3)
249
272
  const pts = geom3.toPoints(obs)
250
273
 
251
- // t.is(pts.length, 160)
274
+ t.notThrows(() => geom3.validate(obs))
252
275
  t.is(pts.length, 92)
253
276
  })
@@ -12,12 +12,14 @@ test('hullChain (two, geom2)', (t) => {
12
12
  let obs = hullChain(geometry1, geometry1)
13
13
  let pts = geom2.toPoints(obs)
14
14
 
15
+ t.notThrows(() => geom2.validate(obs))
15
16
  t.is(pts.length, 4)
16
17
 
17
18
  // different
18
19
  obs = hullChain(geometry1, geometry2)
19
20
  pts = geom2.toPoints(obs)
20
21
 
22
+ t.notThrows(() => geom2.validate(obs))
21
23
  t.is(pts.length, 6)
22
24
  })
23
25
 
@@ -31,16 +33,16 @@ test('hullChain (three, geom2)', (t) => {
31
33
  let pts = geom2.toPoints(obs)
32
34
 
33
35
  // the sides change based on the bestplane chosen in trees/Node.js
36
+ t.notThrows(() => geom2.validate(obs))
34
37
  t.is(pts.length, 10)
35
- // t.is(pts.length, 11)
36
38
 
37
39
  // closed
38
40
  obs = hullChain(geometry1, geometry2, geometry3, geometry1)
39
41
  pts = geom2.toPoints(obs)
40
42
 
41
43
  // the sides change based on the bestplane chosen in trees/Node.js
44
+ t.notThrows(() => geom2.validate(obs))
42
45
  t.is(pts.length, 10)
43
- // t.is(pts.length, 13)
44
46
  })
45
47
 
46
48
  test('hullChain (three, geom3)', (t) => {
@@ -73,13 +75,13 @@ test('hullChain (three, geom3)', (t) => {
73
75
  let obs = hullChain(geometry1, geometry2, geometry3)
74
76
  let pts = geom3.toPoints(obs)
75
77
 
76
- // t.is(pts.length, 27)
78
+ t.notThrows.skip(() => geom3.validate(obs))
77
79
  t.is(pts.length, 23)
78
80
 
79
81
  // closed
80
82
  obs = hullChain(geometry1, geometry2, geometry3, geometry1)
81
83
  pts = geom3.toPoints(obs)
82
84
 
83
- // t.is(pts.length, 45)
85
+ t.notThrows.skip(() => geom3.validate(obs))
84
86
  t.is(pts.length, 28)
85
87
  })
@@ -3,6 +3,7 @@ const flatten = require('../../utils/flatten')
3
3
  const geom2 = require('../../geometries/geom2')
4
4
 
5
5
  const hullPoints2 = require('./hullPoints2')
6
+ const toUniquePoints = require('./toUniquePoints')
6
7
 
7
8
  /*
8
9
  * Create a convex hull of the given geom2 geometries.
@@ -13,28 +14,15 @@ const hullGeom2 = (...geometries) => {
13
14
  geometries = flatten(geometries)
14
15
 
15
16
  // extract the unique points from the geometries
16
- const uniquepoints = []
17
- const found = new Map()
18
- for (let g = 0; g < geometries.length; g++) {
19
- const sides = geom2.toSides(geometries[g])
20
- for (let s = 0; s < sides.length; s++) {
21
- const side = sides[s]
22
- const point = side[0]
23
- const id = `${point[0]},${point[1]}`
24
- if (found.has(id)) continue
25
- uniquepoints.push(point)
26
- found.set(id, true)
27
- }
28
- }
29
- found.clear()
30
-
31
- const hullpoints = hullPoints2(uniquepoints)
17
+ const unique = toUniquePoints(geometries)
18
+
19
+ const hullPoints = hullPoints2(unique)
32
20
 
33
21
  // NOTE: more than three points are required to create a new geometry
34
- if (hullpoints.length < 3) return geom2.create()
22
+ if (hullPoints.length < 3) return geom2.create()
35
23
 
36
24
  // assemble a new geometry from the list of points
37
- return geom2.fromPoints(hullpoints)
25
+ return geom2.fromPoints(hullPoints)
38
26
  }
39
27
 
40
28
  module.exports = hullGeom2
@@ -4,6 +4,7 @@ const geom3 = require('../../geometries/geom3')
4
4
  const poly3 = require('../../geometries/poly3')
5
5
 
6
6
  const quickhull = require('./quickhull')
7
+ const toUniquePoints = require('./toUniquePoints')
7
8
 
8
9
  /*
9
10
  * Create a convex hull of the given geometries (geom3).
@@ -16,26 +17,12 @@ const hullGeom3 = (...geometries) => {
16
17
  if (geometries.length === 1) return geometries[0]
17
18
 
18
19
  // extract the unique vertices from the geometries
19
- const uniquevertices = []
20
- const found = new Map()
21
- for (let g = 0; g < geometries.length; ++g) {
22
- const polygons = geom3.toPolygons(geometries[g])
23
- for (let p = 0; p < polygons.length; ++p) {
24
- const vertices = polygons[p].vertices
25
- for (let v = 0; v < vertices.length; ++v) {
26
- const id = `${vertices[v]}`
27
- if (found.has(id)) continue
28
- uniquevertices.push(vertices[v])
29
- found.set(id, true)
30
- }
31
- }
32
- }
33
- found.clear()
34
-
35
- const faces = quickhull(uniquevertices, { skipTriangulation: true })
20
+ const unique = toUniquePoints(geometries)
21
+
22
+ const faces = quickhull(unique, { skipTriangulation: true })
36
23
 
37
24
  const polygons = faces.map((face) => {
38
- const vertices = face.map((index) => uniquevertices[index])
25
+ const vertices = face.map((index) => unique[index])
39
26
  return poly3.create(vertices)
40
27
  })
41
28
 
@@ -3,6 +3,7 @@ const flatten = require('../../utils/flatten')
3
3
  const path2 = require('../../geometries/path2')
4
4
 
5
5
  const hullPoints2 = require('./hullPoints2')
6
+ const toUniquePoints = require('./toUniquePoints')
6
7
 
7
8
  /*
8
9
  * Create a convex hull of the given geometries (path2).
@@ -13,23 +14,12 @@ const hullPath2 = (...geometries) => {
13
14
  geometries = flatten(geometries)
14
15
 
15
16
  // extract the unique points from the geometries
16
- const uniquepoints = []
17
- const found = new Set()
18
- geometries.forEach((geometry) => {
19
- const points = path2.toPoints(geometry)
20
- points.forEach((point) => {
21
- const key = point.toString()
22
- if (!found.has(key)) {
23
- uniquepoints.push(point)
24
- found.add(key)
25
- }
26
- })
27
- })
17
+ const unique = toUniquePoints(geometries)
28
18
 
29
- const hullpoints = hullPoints2(uniquepoints)
19
+ const hullPoints = hullPoints2(unique)
30
20
 
31
21
  // assemble a new geometry from the list of points
32
- return path2.fromPoints({ closed: true }, hullpoints)
22
+ return path2.fromPoints({ closed: true }, hullPoints)
33
23
  }
34
24
 
35
25
  module.exports = hullPath2
@@ -10,7 +10,7 @@ test('hullPath2', (t) => {
10
10
  const geometry2 = path2.fromPoints({ closed }, [[0, 0], [4, -4], [4, 4]])
11
11
 
12
12
  const obs = hullPath2(geometry1, geometry2)
13
- t.true(path2.isA(obs))
13
+ t.notThrows(() => path2.validate(obs))
14
14
  const pts = path2.toPoints(obs)
15
15
  t.is(pts.length, 4)
16
16
  })