@jscad/modeling 2.9.3 → 2.9.6

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 (70) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/jscad-modeling.min.js +133 -136
  3. package/package.json +2 -2
  4. package/src/colors/colorize.d.ts +6 -5
  5. package/src/geometries/geom2/type.d.ts +3 -2
  6. package/src/geometries/geom3/applyTransforms.js +1 -2
  7. package/src/geometries/geom3/type.d.ts +3 -2
  8. package/src/geometries/path2/appendPoints.js +3 -12
  9. package/src/geometries/path2/appendPoints.test.js +16 -0
  10. package/src/geometries/path2/concat.js +9 -8
  11. package/src/geometries/path2/concat.test.js +13 -7
  12. package/src/geometries/path2/type.d.ts +3 -2
  13. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
  14. package/src/geometries/poly3/measureBoundingSphere.js +46 -8
  15. package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
  16. package/src/geometries/poly3/type.d.ts +3 -2
  17. package/src/geometries/types.d.ts +4 -2
  18. package/src/maths/mat4/fromRotation.js +9 -7
  19. package/src/maths/mat4/fromTaitBryanRotation.js +8 -6
  20. package/src/maths/mat4/fromXRotation.js +4 -2
  21. package/src/maths/mat4/fromYRotation.js +4 -2
  22. package/src/maths/mat4/fromZRotation.js +4 -2
  23. package/src/maths/mat4/isMirroring.js +11 -11
  24. package/src/maths/mat4/rotate.js +9 -5
  25. package/src/maths/mat4/rotateX.js +4 -2
  26. package/src/maths/mat4/rotateY.js +4 -2
  27. package/src/maths/mat4/rotateZ.js +4 -2
  28. package/src/maths/mat4/translate.test.js +2 -3
  29. package/src/maths/utils/index.d.ts +1 -0
  30. package/src/maths/utils/index.js +2 -0
  31. package/src/{utils → maths/utils}/trigonometry.d.ts +0 -0
  32. package/src/{utils → maths/utils}/trigonometry.js +1 -1
  33. package/src/{utils → maths/utils}/trigonometry.test.js +0 -0
  34. package/src/maths/vec2/distance.js +1 -1
  35. package/src/maths/vec2/fromAngleRadians.js +4 -2
  36. package/src/maths/vec2/length.js +1 -1
  37. package/src/maths/vec2/length.test.js +0 -10
  38. package/src/maths/vec3/angle.js +2 -2
  39. package/src/maths/vec3/angle.test.js +0 -12
  40. package/src/maths/vec3/distance.js +1 -1
  41. package/src/maths/vec3/length.js +1 -1
  42. package/src/maths/vec3/length.test.js +0 -10
  43. package/src/operations/booleans/intersectGeom2.test.js +69 -0
  44. package/src/operations/booleans/{intersect.test.js → intersectGeom3.test.js} +3 -71
  45. package/src/operations/booleans/subtractGeom2.test.js +72 -0
  46. package/src/operations/booleans/{subtract.test.js → subtractGeom3.test.js} +3 -74
  47. package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
  48. package/src/operations/booleans/unionGeom2.test.js +166 -0
  49. package/src/operations/booleans/{union.test.js → unionGeom3.test.js} +3 -168
  50. package/src/operations/extrusions/extrudeFromSlices.js +3 -2
  51. package/src/operations/extrusions/extrudeRotate.test.js +42 -42
  52. package/src/operations/extrusions/project.test.js +2 -2
  53. package/src/operations/extrusions/slice/repair.js +62 -0
  54. package/src/operations/modifiers/insertTjunctions.js +34 -35
  55. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +32 -31
  56. package/src/primitives/circle.test.js +7 -0
  57. package/src/primitives/cylinderElliptic.js +4 -2
  58. package/src/primitives/cylinderElliptic.test.js +7 -1
  59. package/src/primitives/ellipse.js +1 -1
  60. package/src/primitives/ellipse.test.js +7 -0
  61. package/src/primitives/ellipsoid.js +1 -1
  62. package/src/primitives/geodesicSphere.js +3 -2
  63. package/src/primitives/roundedCuboid.js +4 -2
  64. package/src/primitives/roundedCylinder.js +1 -1
  65. package/src/primitives/torus.test.js +7 -3
  66. package/src/utils/index.d.ts +0 -1
  67. package/src/utils/index.js +1 -3
  68. package/src/maths/mat4/constants.d.ts +0 -1
  69. package/src/maths/mat4/constants.js +0 -5
  70. package/src/operations/extrusions/slice/repairSlice.js +0 -47
@@ -1,113 +1,14 @@
1
1
  const test = require('ava')
2
2
 
3
- const { comparePolygonsAsPoints, comparePoints } = require('../../../test/helpers')
3
+ const { comparePolygonsAsPoints } = require('../../../test/helpers')
4
4
 
5
- const { geom2, geom3 } = require('../../geometries')
5
+ const { geom3 } = require('../../geometries')
6
6
 
7
- const { circle, rectangle, sphere, cuboid } = require('../../primitives')
7
+ const { sphere, cuboid } = require('../../primitives')
8
8
 
9
9
  const { union } = require('./index')
10
10
 
11
11
  const { center } = require('../transforms/center')
12
- const { translate } = require('../transforms/translate')
13
-
14
- // test('union: union of a path produces expected changes to points', (t) => {
15
- // let geometry = path.fromPoints({}, [[0, 1, 0], [1, 0, 0]])
16
- //
17
- // geometry = union({normal: [1, 0, 0]}, geometry)
18
- // let obs = path.toPoints(geometry)
19
- // let exp = []
20
- //
21
- // t.deepEqual(obs, exp)
22
- // })
23
-
24
- test('union of one or more geom2 objects produces expected geometry', (t) => {
25
- const geometry1 = circle({ radius: 2, segments: 8 })
26
-
27
- // union of one object
28
- const result1 = union(geometry1)
29
- let obs = geom2.toPoints(result1)
30
- let exp = [
31
- [2, 0],
32
- [1.4142000000000001, 1.4142000000000001],
33
- [0, 2],
34
- [-1.4142000000000001, 1.4142000000000001],
35
- [-2, 0],
36
- [-1.4142000000000001, -1.4142000000000001],
37
- [0, -2],
38
- [1.4142000000000001, -1.4142000000000001]
39
- ]
40
- t.notThrows(() => geom2.validate(result1))
41
- t.true(comparePoints(obs, exp))
42
-
43
- // union of two non-overlapping objects
44
- const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
45
-
46
- const result2 = union(geometry1, geometry2)
47
- obs = geom2.toPoints(result2)
48
- exp = [
49
- [2, 0],
50
- [1.4142000000000001, 1.4142000000000001],
51
- [0, 2],
52
- [-1.4142000000000001, 1.4142000000000001],
53
- [-2, 0],
54
- [-1.4142000000000001, -1.4142000000000001],
55
- [0, -2],
56
- [8, 12],
57
- [8, 8],
58
- [12, 8],
59
- [12, 12],
60
- [1.4142000000000001, -1.4142000000000001]
61
- ]
62
- t.notThrows(() => geom2.validate(result2))
63
- t.true(comparePoints(obs, exp))
64
-
65
- // union of two partially overlapping objects
66
- const geometry3 = rectangle({ size: [18, 18] })
67
-
68
- const result3 = union(geometry2, geometry3)
69
- obs = geom2.toPoints(result3)
70
- exp = [
71
- [11.999973333333333, 11.999973333333333],
72
- [7.999933333333333, 11.999973333333333],
73
- [9.000053333333334, 7.999933333333333],
74
- [-9.000053333333334, 9.000053333333334],
75
- [-9.000053333333334, -9.000053333333334],
76
- [9.000053333333334, -9.000053333333334],
77
- [7.999933333333333, 9.000053333333334],
78
- [11.999973333333333, 7.999933333333333]
79
- ]
80
- t.notThrows(() => geom2.validate(result3))
81
- t.true(comparePoints(obs, exp))
82
-
83
- // union of two completely overlapping objects
84
- const result4 = union(geometry1, geometry3)
85
- obs = geom2.toPoints(result4)
86
- exp = [
87
- [-9.000046666666666, -9.000046666666666],
88
- [9.000046666666666, -9.000046666666666],
89
- [9.000046666666666, 9.000046666666666],
90
- [-9.000046666666666, 9.000046666666666]
91
- ]
92
- t.notThrows(() => geom2.validate(result4))
93
- t.true(comparePoints(obs, exp))
94
-
95
- // union of unions of non-overlapping objects (BSP gap from #907)
96
- const circ = circle({ radius: 1, segments: 32 })
97
- const result5 = union(
98
- union(
99
- translate([17, 21], circ),
100
- translate([7, 0], circ),
101
- ),
102
- union(
103
- translate([3, 21], circ),
104
- translate([17, 21], circ),
105
- )
106
- )
107
- obs = geom2.toPoints(result5)
108
- t.notThrows.skip(() => geom2.validate(result5))
109
- t.is(obs.length, 112)
110
- })
111
12
 
112
13
  test('union of one or more geom3 objects produces expected geometry', (t) => {
113
14
  const geometry1 = sphere({ radius: 2, segments: 8 })
@@ -230,69 +131,3 @@ test('union of geom3 with rounding issues #137', (t) => {
230
131
  t.notThrows(() => geom3.validate(obs))
231
132
  t.is(pts.length, 6) // number of polygons in union
232
133
  })
233
-
234
- test('union of geom2 with closing issues #15', (t) => {
235
- const c = geom2.create([
236
- [[-45.82118740347841168159, -16.85726810555620147625], [-49.30331715865012398581, -14.68093629710870118288]],
237
- [[-49.10586702080816223770, -15.27604177352110781385], [-48.16645938811709015681, -15.86317173589183227023]],
238
- [[-49.60419521731581937729, -14.89550781504266296906], [-49.42407001323204696064, -15.67605088949303393520]],
239
- [[-49.05727291218684626983, -15.48661638542171203881], [-49.10586702080816223770, -15.27604177352110781385]],
240
- [[-49.30706235399220815907, -15.81529674600091794900], [-46.00505780290426827150, -17.21108547999804727624]],
241
- [[-46.00505780290426827150, -17.21108547999804727624], [-45.85939703723252591772, -17.21502856394236857795]],
242
- [[-45.85939703723252591772, -17.21502856394236857795], [-45.74972032664388166268, -17.11909303495791334626]],
243
- [[-45.74972032664388166268, -17.11909303495791334626], [-45.73424573227583067592, -16.97420292661295349035]],
244
- [[-45.73424573227583067592, -16.97420292661295349035], [-45.82118740347841168159, -16.85726810555620147625]],
245
- [[-49.30331715865012398581, -14.68093629710870118288], [-49.45428884427643367871, -14.65565769658912387285]],
246
- [[-49.45428884427643367871, -14.65565769658912387285], [-49.57891661679624917269, -14.74453612941635327616]],
247
- [[-49.57891661679624917269, -14.74453612941635327616], [-49.60419521731581937729, -14.89550781504266296906]],
248
- [[-49.42407001323204696064, -15.67605088949303393520], [-49.30706235399220815907, -15.81529674600091794900]],
249
- [[-48.16645938811709015681, -15.86317173589183227023], [-49.05727291218684626983, -15.48661638542171203881]]
250
- ])
251
- const d = geom2.create([
252
- [[-49.03431352173912216585, -15.58610714407888764299], [-49.21443872582289458251, -14.80556406962851667686]],
253
- [[-68.31614651314507113966, -3.10790373951434872879], [-49.34036769611472550423, -15.79733157434056778357]],
254
- [[-49.58572929483430868913, -14.97552686612213790340], [-49.53755741140093959984, -15.18427183431472826669]],
255
- [[-49.53755741140093959984, -15.18427183431472826669], [-54.61235529924312714911, -11.79066769321313756791]],
256
- [[-49.30227466841120076424, -14.68159232649114187552], [-68.09792828135776687759, -2.77270756611528668145]],
257
- [[-49.21443872582289458251, -14.80556406962851667686], [-49.30227466841120076424, -14.68159232649114187552]],
258
- [[-49.34036769611472550423, -15.79733157434056778357], [-49.18823337756091262918, -15.82684012194931710837]],
259
- [[-49.18823337756091262918, -15.82684012194931710837], [-49.06069007212390431505, -15.73881563386780157998]],
260
- [[-49.06069007212390431505, -15.73881563386780157998], [-49.03431352173912216585, -15.58610714407888764299]],
261
- [[-68.09792828135776687759, -2.77270756611528668145], [-68.24753735887460948106, -2.74623350179570024920]],
262
- [[-68.24753735887460948106, -2.74623350179570024920], [-68.37258141465594007968, -2.83253376987636329432]],
263
- [[-68.37258141465594007968, -2.83253376987636329432], [-68.40089829889257089235, -2.98180502037078554167]],
264
- [[-68.40089829889257089235, -2.98180502037078554167], [-68.31614651314507113966, -3.10790373951434872879]],
265
- [[-54.61235529924312714911, -11.79066769321313756791], [-49.58572929483430868913, -14.97552686612213790340]]
266
- ])
267
- // geom2.toOutlines(c)
268
- // geom2.toOutlines(d)
269
-
270
- const obs = union(c, d)
271
- // const outlines = geom2.toOutlines(obs)
272
- const pts = geom2.toPoints(obs)
273
- const exp = [
274
- [-49.10585516965137, -15.276000175919414],
275
- [-49.0573272145917, -15.486679335654257],
276
- [-49.307011370463215, -15.815286644243773],
277
- [-46.00502320253235, -17.211117609669667],
278
- [-45.85943933735334, -17.215031154432545],
279
- [-45.74972963250071, -17.119149307742074],
280
- [-45.734205904941305, -16.974217700023555],
281
- [-48.166473975068946, -15.86316234184296],
282
- [-49.318621553259746, -15.801589237573706],
283
- [-49.585786209072104, -14.975570389622606],
284
- [-68.31614189569036, -3.1078763476921982],
285
- [-49.53751915699663, -15.184292776976012],
286
- [-68.09789654941396, -2.7727464644978874],
287
- [-68.24752441084793, -2.7462648116024244],
288
- [-68.37262739176788, -2.8324932478777995],
289
- [-68.40093536555268, -2.98186020632758],
290
- [-54.61234310251047, -11.79072766159384],
291
- [-49.30335872868453, -14.680880468978017],
292
- [-49.34040695243976, -15.797284338334542],
293
- [-45.82121705016925, -16.857333163105647]
294
- ]
295
- t.notThrows(() => geom2.validate(obs))
296
- t.is(pts.length, 20) // number of sides in union
297
- t.true(comparePoints(pts, exp))
298
- })
@@ -5,7 +5,7 @@ const geom3 = require('../../geometries/geom3')
5
5
  const poly3 = require('../../geometries/poly3')
6
6
 
7
7
  const slice = require('./slice')
8
- const repairSlice = require('./slice/repairSlice')
8
+ const repairSlice = require('./slice/repair')
9
9
 
10
10
  const extrudeWalls = require('./extrudeWalls')
11
11
 
@@ -59,7 +59,8 @@ const extrudeFromSlices = (options, base) => {
59
59
 
60
60
  // Repair gaps in the base slice
61
61
  if (repair) {
62
- repairSlice(base)
62
+ // note: base must be a slice, if base is geom2 this doesn't repair
63
+ base = repairSlice(base)
63
64
  }
64
65
 
65
66
  const sMax = numberOfSlices - 1
@@ -22,18 +22,18 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
22
22
  let geometry3 = extrudeRotate({ segments: 4, angle: Math.PI / 4 }, geometry2)
23
23
  let pts = geom3.toPoints(geometry3)
24
24
  const exp = [
25
- [[10, 4.898587196589413e-16, 8], [26, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, 8]],
26
- [[10, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
27
- [[10, -4.898587196589413e-16, -8], [10, 4.898587196589413e-16, 8], [7.0710678118654755, 7.071067811865475, 8]],
28
- [[10, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
29
- [[26, -4.898587196589413e-16, -8], [10, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, -8]],
30
- [[26, -4.898587196589413e-16, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
31
- [[26, 4.898587196589413e-16, 8], [26, -4.898587196589413e-16, -8], [18.38477631085024, 18.384776310850235, -8]],
32
- [[26, 4.898587196589413e-16, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
33
- [[7.071067811865476, 7.0710678118654755, -8], [7.071067811865476, 7.0710678118654755, 8], [18.384776310850242, 18.384776310850235, 8]],
34
- [[18.384776310850242, 18.384776310850235, 8], [18.384776310850242, 18.384776310850235, -8], [7.071067811865476, 7.0710678118654755, -8]],
35
- [[26, 4.898587196589413e-16, 8], [10, 4.898587196589413e-16, 8], [10, -4.898587196589413e-16, -8]],
36
- [[10, -4.898587196589413e-16, -8], [26, -4.898587196589413e-16, -8], [26, 4.898587196589413e-16, 8]]
25
+ [[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
26
+ [[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
27
+ [[10, 0, -8], [10, 0, 8], [7.0710678118654755, 7.071067811865475, 8]],
28
+ [[10, 0, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
29
+ [[26, 0, -8], [10, 0, -8], [7.0710678118654755, 7.071067811865475, -8]],
30
+ [[26, 0, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
31
+ [[26, 0, 8], [26, 0, -8], [18.38477631085024, 18.384776310850235, -8]],
32
+ [[26, 0, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
33
+ [[7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8]],
34
+ [[18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8]],
35
+ [[26, 0, 8], [10, 0, 8], [10, 0, -8]],
36
+ [[10, 0, -8], [26, 0, -8], [26, 0, 8]]
37
37
  ]
38
38
  t.notThrows(() => geom3.validate(geometry3))
39
39
  t.is(pts.length, 12)
@@ -95,14 +95,14 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
95
95
  geometry2 = geom2.fromPoints([[0, 0], [2, 1], [1, 2], [1, 3], [3, 4], [0, 5]])
96
96
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
97
97
  pts = geom3.toPoints(geometry3)
98
- t.notThrows.skip(() => geom3.validate(geometry3))
98
+ t.notThrows(() => geom3.validate(geometry3))
99
99
  t.is(pts.length, 64)
100
100
 
101
101
  // test overlapping edges that produce hollow shape
102
102
  geometry2 = geom2.fromPoints([[30, 0], [30, 60], [0, 60], [0, 50], [10, 40], [10, 30], [0, 20], [0, 10], [10, 0]])
103
103
  geometry3 = extrudeRotate({ segments: 8 }, geometry2)
104
104
  pts = geom3.toPoints(geometry3)
105
- t.notThrows.skip(() => geom3.validate(geometry3))
105
+ t.notThrows(() => geom3.validate(geometry3))
106
106
  t.is(pts.length, 80)
107
107
  })
108
108
 
@@ -113,16 +113,16 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
113
113
  let obs = extrudeRotate({ segments: 4, angle: Math.PI / 2 }, geometry)
114
114
  let pts = geom3.toPoints(obs)
115
115
  let exp = [
116
- [[0, 4.898587196589413e-16, 8], [7, 4.898587196589413e-16, 8], [-6.123233995736767e-17, 7, 8]],
117
- [[7, -4.898587196589413e-16, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [9.184850993605148e-16, 7, -8]],
118
- [[7, 4.898587196589413e-16, 8], [7, -4.898587196589413e-16, -8], [9.184850993605148e-16, 7, -8]],
119
- [[7, 4.898587196589413e-16, 8], [9.184850993605148e-16, 7, -8], [-6.123233995736767e-17, 7, 8]],
120
- [[4.898587196589413e-16, -2.999519565323715e-32, -8], [-4.898587196589413e-16, 2.999519565323715e-32, 8], [-6.123233995736767e-17, 7, 8]],
121
- [[-6.123233995736767e-17, 7, 8], [9.184850993605148e-16, 7, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8]],
122
- [[7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8], [0, -4.898587196589413e-16, -8]],
123
- [[0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8], [7, 4.898587196589413e-16, 8]]
116
+ [[0, 0, 8], [7, 0, 8], [0, 7, 8]],
117
+ [[7, 0, -8], [0, 0, -8], [0, 7, -8]],
118
+ [[7, 0, 8], [7, 0, -8], [0, 7, -8]],
119
+ [[7, 0, 8], [0, 7, -8], [0, 7, 8]],
120
+ [[0, 0, -8], [0, 0, 8], [0, 7, 8]],
121
+ [[0, 7, 8], [0, 7, -8], [0, 0, -8]],
122
+ [[7, 0, 8], [0, 0, 8], [0, 0, -8]],
123
+ [[0, 0, -8], [7, 0, -8], [7, 0, 8]]
124
124
  ]
125
- t.notThrows.skip(() => geom3.validate(obs))
125
+ t.notThrows(() => geom3.validate(obs))
126
126
  t.is(pts.length, 8)
127
127
  t.true(comparePolygonsAsPoints(pts, exp))
128
128
 
@@ -132,26 +132,26 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
132
132
  obs = extrudeRotate({ segments: 8, angle: Math.PI / 2 }, geometry)
133
133
  pts = geom3.toPoints(obs)
134
134
  exp = [
135
- [[1, -4.898587196589413e-16, -8], [3.4638242249419727e-16, -3.4638242249419736e-16, -8], [0.7071067811865479, 0.7071067811865471, -8]],
136
- [[2, 2.4492935982947064e-16, 4], [1, -4.898587196589413e-16, -8], [0.7071067811865479, 0.7071067811865471, -8]],
137
- [[2, 2.4492935982947064e-16, 4], [0.7071067811865479, 0.7071067811865471, -8], [1.414213562373095, 1.4142135623730951, 4]],
138
- [[1, 4.898587196589413e-16, 8], [2, 2.4492935982947064e-16, 4], [1.414213562373095, 1.4142135623730951, 4]],
139
- [[1, 4.898587196589413e-16, 8], [1.414213562373095, 1.4142135623730951, 4], [0.7071067811865472, 0.7071067811865478, 8]],
140
- [[0, 4.898587196589413e-16, 8], [1, 4.898587196589413e-16, 8], [0.7071067811865472, 0.7071067811865478, 8]],
141
- [[0.7071067811865479, 0.7071067811865471, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [5.51091059616309e-16, 1, -8]],
142
- [[1.414213562373095, 1.4142135623730951, 4], [0.7071067811865479, 0.7071067811865471, -8], [5.51091059616309e-16, 1, -8]],
143
- [[1.414213562373095, 1.4142135623730951, 4], [5.51091059616309e-16, 1, -8], [-1.2246467991473532e-16, 2, 4]],
144
- [[0.7071067811865472, 0.7071067811865478, 8], [1.414213562373095, 1.4142135623730951, 4], [-1.2246467991473532e-16, 2, 4]],
145
- [[0.7071067811865472, 0.7071067811865478, 8], [-1.2246467991473532e-16, 2, 4], [-4.286263797015736e-16, 1, 8]],
146
- [[-3.4638242249419727e-16, 3.4638242249419736e-16, 8], [0.7071067811865472, 0.7071067811865478, 8], [-4.286263797015736e-16, 1, 8]],
147
- [[5.51091059616309e-16, 1, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [-4.898587196589415e-16, 2.9995195653237163e-32, 8]],
148
- [[-4.898587196589415e-16, 2.9995195653237163e-32, 8], [-4.286263797015738e-16, 1, 8], [-1.2246467991473544e-16, 2, 4]],
149
- [[-1.2246467991473544e-16, 2, 4], [5.51091059616309e-16, 1, -8], [-4.898587196589415e-16, 2.9995195653237163e-32, 8]],
150
- [[0, 4.898587196589413e-16, 8], [0, -4.898587196589413e-16, -8], [1, -4.898587196589413e-16, -8]],
151
- [[2, 2.4492935982947064e-16, 4], [1, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8]],
152
- [[0, 4.898587196589413e-16, 8], [1, -4.898587196589413e-16, -8], [2, 2.4492935982947064e-16, 4]]
135
+ [[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
136
+ [[2, 0, 4], [1, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
137
+ [[2, 0, 4], [0.7071067811865476, 0.7071067811865475, -8], [1.4142135623730951, 1.414213562373095, 4]],
138
+ [[1, 0, 8], [2, 0, 4], [1.4142135623730951, 1.414213562373095, 4]],
139
+ [[1, 0, 8], [1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, 8]],
140
+ [[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]],
141
+ [[0.7071067811865476, 0.7071067811865475, -8], [0, 0, -8], [0, 1, -8]],
142
+ [[1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, -8], [0, 1, -8]],
143
+ [[1.4142135623730951, 1.414213562373095, 4], [0, 1, -8], [0, 2, 4]],
144
+ [[0.7071067811865476, 0.7071067811865475, 8], [1.4142135623730951, 1.414213562373095, 4], [0, 2, 4]],
145
+ [[0.7071067811865476, 0.7071067811865475, 8], [0, 2, 4], [0, 1, 8]],
146
+ [[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]],
147
+ [[0, 1, -8], [0, 0, -8], [0, 0, 8]],
148
+ [[0, 0, 8], [0, 1, 8], [0, 2, 4]],
149
+ [[0, 2, 4], [0, 1, -8], [0, 0, 8]],
150
+ [[0, 0, 8], [0, 0, -8], [1, 0, -8]],
151
+ [[2, 0, 4], [1, 0, 8], [0, 0, 8]],
152
+ [[0, 0, 8], [1, 0, -8], [2, 0, 4]]
153
153
  ]
154
- t.notThrows.skip(() => geom3.validate(obs))
154
+ t.notThrows(() => geom3.validate(obs))
155
155
  t.is(pts.length, 18)
156
156
  t.true(comparePolygonsAsPoints(pts, exp))
157
157
  })
@@ -29,10 +29,10 @@ test('project (defaults)', (t) => {
29
29
  [0, -5.000013333333333],
30
30
  [5.000013333333333, 0],
31
31
  [-5.000013333333333, 0],
32
- [-2.9999933333333333, 0],
33
- [2.9999933333333333, 0],
34
32
  [0, 2.9999933333333333],
33
+ [-2.9999933333333333, 0],
35
34
  [0, -2.9999933333333333],
35
+ [2.9999933333333333, 0],
36
36
  [0, 5.000013333333333]
37
37
  ]
38
38
  t.true(comparePoints(pts, exp))
@@ -0,0 +1,62 @@
1
+ const vec3 = require('../../../maths/vec3')
2
+ const create = require('./create')
3
+
4
+ /*
5
+ * Mend gaps in a 2D slice to make it a closed polygon
6
+ */
7
+ const repair = (slice) => {
8
+ if (!slice.edges) return slice
9
+ let edges = slice.edges
10
+ const vertexMap = new Map() // string key to vertex map
11
+ const edgeCount = new Map() // count of (in - out) edges
12
+
13
+ // Remove self-edges
14
+ edges = edges.filter((e) => !vec3.equals(e[0], e[1]))
15
+
16
+ // build vertex and edge count maps
17
+ edges.forEach((edge) => {
18
+ const inKey = edge[0].toString()
19
+ const outKey = edge[1].toString()
20
+ vertexMap.set(inKey, edge[0])
21
+ vertexMap.set(outKey, edge[1])
22
+ edgeCount.set(inKey, (edgeCount.get(inKey) || 0) + 1) // in
23
+ edgeCount.set(outKey, (edgeCount.get(outKey) || 0) - 1) // out
24
+ })
25
+
26
+ // find vertices which are missing in or out edges
27
+ const missingIn = []
28
+ const missingOut = []
29
+ edgeCount.forEach((count, vertex) => {
30
+ if (count < 0) missingIn.push(vertex)
31
+ if (count > 0) missingOut.push(vertex)
32
+ })
33
+
34
+ // pairwise distance of bad vertices
35
+ missingIn.forEach((key1) => {
36
+ const v1 = vertexMap.get(key1)
37
+
38
+ // find the closest vertex that is missing an out edge
39
+ let bestDistance = Infinity
40
+ let bestReplacement
41
+ missingOut.forEach((key2) => {
42
+ const v2 = vertexMap.get(key2)
43
+ const distance = vec3.distance(v1, v2)
44
+ if (distance < bestDistance) {
45
+ bestDistance = distance
46
+ bestReplacement = v2
47
+ }
48
+ })
49
+ console.warn(`slice.repair: repairing vertex gap ${v1} to ${bestReplacement} distance ${bestDistance}`)
50
+
51
+ // merge broken vertices
52
+ edges = edges.map((edge) => {
53
+ if (edge[0].toString() === key1) return [bestReplacement, edge[1]]
54
+ if (edge[1].toString() === key1) return [edge[0], bestReplacement]
55
+ return edge
56
+ })
57
+ })
58
+
59
+ return create(edges)
60
+ }
61
+
62
+ module.exports = repair
@@ -28,15 +28,15 @@ const addSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, verte
28
28
  } else {
29
29
  sidemap.get(newsidetag).push(newsideobj)
30
30
  }
31
- if (starttag in vertextag2sidestart) {
32
- vertextag2sidestart[starttag].push(newsidetag)
31
+ if (vertextag2sidestart.has(starttag)) {
32
+ vertextag2sidestart.get(starttag).push(newsidetag)
33
33
  } else {
34
- vertextag2sidestart[starttag] = [newsidetag]
34
+ vertextag2sidestart.set(starttag, [newsidetag])
35
35
  }
36
- if (endtag in vertextag2sideend) {
37
- vertextag2sideend[endtag].push(newsidetag)
36
+ if (vertextag2sideend.has(endtag)) {
37
+ vertextag2sideend.get(endtag).push(newsidetag)
38
38
  } else {
39
- vertextag2sideend[endtag] = [newsidetag]
39
+ vertextag2sideend.set(endtag, [newsidetag])
40
40
  }
41
41
  return newsidetag
42
42
  }
@@ -67,18 +67,18 @@ const deleteSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, ve
67
67
  }
68
68
 
69
69
  // adjust start and end lists
70
- idx = vertextag2sidestart[starttag].indexOf(sidetag)
70
+ idx = vertextag2sidestart.get(starttag).indexOf(sidetag)
71
71
  if (assert && idx < 0) throw new Error('assert failed')
72
- vertextag2sidestart[starttag].splice(idx, 1)
73
- if (vertextag2sidestart[starttag].length === 0) {
74
- delete vertextag2sidestart[starttag]
72
+ vertextag2sidestart.get(starttag).splice(idx, 1)
73
+ if (vertextag2sidestart.get(starttag).length === 0) {
74
+ vertextag2sidestart.delete(starttag)
75
75
  }
76
76
 
77
- idx = vertextag2sideend[endtag].indexOf(sidetag)
77
+ idx = vertextag2sideend.get(endtag).indexOf(sidetag)
78
78
  if (assert && idx < 0) throw new Error('assert failed')
79
- vertextag2sideend[endtag].splice(idx, 1)
80
- if (vertextag2sideend[endtag].length === 0) {
81
- delete vertextag2sideend[endtag]
79
+ vertextag2sideend.get(endtag).splice(idx, 1)
80
+ if (vertextag2sideend.get(endtag).length === 0) {
81
+ vertextag2sideend.delete(endtag)
82
82
  }
83
83
  }
84
84
 
@@ -158,25 +158,24 @@ const insertTjunctions = (polygons) => {
158
158
  }
159
159
 
160
160
  if (sidemap.size > 0) {
161
- // console.log('insertTjunctions',sidemap.size)
162
161
  // STEP 2 : create a list of starting sides and ending sides
163
- const vertextag2sidestart = {}
164
- const vertextag2sideend = {}
165
- const sidestocheck = {}
162
+ const vertextag2sidestart = new Map()
163
+ const vertextag2sideend = new Map()
164
+ const sidesToCheck = new Map()
166
165
  for (const [sidetag, sideobjs] of sidemap) {
167
- sidestocheck[sidetag] = true
166
+ sidesToCheck.set(sidetag, true)
168
167
  sideobjs.forEach((sideobj) => {
169
168
  const starttag = getTag(sideobj.vertex0)
170
169
  const endtag = getTag(sideobj.vertex1)
171
- if (starttag in vertextag2sidestart) {
172
- vertextag2sidestart[starttag].push(sidetag)
170
+ if (vertextag2sidestart.has(starttag)) {
171
+ vertextag2sidestart.get(starttag).push(sidetag)
173
172
  } else {
174
- vertextag2sidestart[starttag] = [sidetag]
173
+ vertextag2sidestart.set(starttag, [sidetag])
175
174
  }
176
- if (endtag in vertextag2sideend) {
177
- vertextag2sideend[endtag].push(sidetag)
175
+ if (vertextag2sideend.has(endtag)) {
176
+ vertextag2sideend.get(endtag).push(sidetag)
178
177
  } else {
179
- vertextag2sideend[endtag] = [sidetag]
178
+ vertextag2sideend.set(endtag, [sidetag])
180
179
  }
181
180
  })
182
181
  }
@@ -187,13 +186,13 @@ const insertTjunctions = (polygons) => {
187
186
  if (sidemap.size === 0) break
188
187
 
189
188
  for (const sidetag of sidemap.keys()) {
190
- sidestocheck[sidetag] = true
189
+ sidesToCheck.set(sidetag, true)
191
190
  }
192
191
 
193
192
  let donesomething = false
194
193
  while (true) {
195
- const sidetags = Object.keys(sidestocheck)
196
- if (sidetags.length === 0) break // sidestocheck is empty, we're done!
194
+ const sidetags = Array.from(sidesToCheck.keys())
195
+ if (sidetags.length === 0) break // sidesToCheck is empty, we're done!
197
196
  const sidetagtocheck = sidetags[0]
198
197
  let donewithside = true
199
198
  if (sidemap.has(sidetagtocheck)) {
@@ -207,12 +206,12 @@ const insertTjunctions = (polygons) => {
207
206
  const endvertextag = getTag(endvertex)
208
207
  let matchingsides = []
209
208
  if (directionindex === 0) {
210
- if (startvertextag in vertextag2sideend) {
211
- matchingsides = vertextag2sideend[startvertextag]
209
+ if (vertextag2sideend.has(startvertextag)) {
210
+ matchingsides = vertextag2sideend.get(startvertextag)
212
211
  }
213
212
  } else {
214
- if (startvertextag in vertextag2sidestart) {
215
- matchingsides = vertextag2sidestart[startvertextag]
213
+ if (vertextag2sidestart.has(startvertextag)) {
214
+ matchingsides = vertextag2sidestart.get(startvertextag)
216
215
  }
217
216
  }
218
217
  for (let matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
@@ -267,8 +266,8 @@ const insertTjunctions = (polygons) => {
267
266
  deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, matchingside.vertex1, polygonindex)
268
267
  const newsidetag1 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, endvertex, polygonindex)
269
268
  const newsidetag2 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, matchingside.vertex1, polygonindex)
270
- if (newsidetag1 !== null) sidestocheck[newsidetag1] = true
271
- if (newsidetag2 !== null) sidestocheck[newsidetag2] = true
269
+ if (newsidetag1 !== null) sidesToCheck.set(newsidetag1, true)
270
+ if (newsidetag2 !== null) sidesToCheck.set(newsidetag2, true)
272
271
  donewithside = false
273
272
  directionindex = 2 // skip reverse direction check
274
273
  donesomething = true
@@ -280,7 +279,7 @@ const insertTjunctions = (polygons) => {
280
279
  } // for directionindex
281
280
  } // if(sidetagtocheck in sidemap)
282
281
  if (donewithside) {
283
- delete sidestocheck[sidetagtocheck]
282
+ sidesToCheck.delete(sidetagtocheck)
284
283
  }
285
284
  }
286
285
  if (!donesomething) break