@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
@@ -23,14 +23,13 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
23
23
  const orthobasis = new OrthoNormalBasis(plane)
24
24
  const polygonvertices2d = [] // array of array of Vector2D
25
25
  const polygontopvertexindexes = [] // array of indexes of topmost vertex per polygon
26
- const topy2polygonindexes = {}
27
- const ycoordinatetopolygonindexes = {}
28
-
29
- const ycoordinatebins = {}
26
+ const topy2polygonindexes = new Map()
27
+ const ycoordinatetopolygonindexes = new Map()
30
28
 
31
29
  // convert all polygon vertices to 2D
32
30
  // Make a list of all encountered y coordinates
33
31
  // And build a map of all polygons that have a vertex at a certain y coordinate:
32
+ const ycoordinatebins = new Map()
34
33
  const ycoordinateBinningFactor = 10 / EPS
35
34
  for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
36
35
  const poly3d = sourcepolygons[polygonindex]
@@ -46,15 +45,15 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
46
45
  // close to each other, give them the same y coordinate:
47
46
  const ycoordinatebin = Math.floor(pos2d[1] * ycoordinateBinningFactor)
48
47
  let newy
49
- if (ycoordinatebin in ycoordinatebins) {
50
- newy = ycoordinatebins[ycoordinatebin]
51
- } else if (ycoordinatebin + 1 in ycoordinatebins) {
52
- newy = ycoordinatebins[ycoordinatebin + 1]
53
- } else if (ycoordinatebin - 1 in ycoordinatebins) {
54
- newy = ycoordinatebins[ycoordinatebin - 1]
48
+ if (ycoordinatebins.has(ycoordinatebin)) {
49
+ newy = ycoordinatebins.get(ycoordinatebin)
50
+ } else if (ycoordinatebins.has(ycoordinatebin + 1)) {
51
+ newy = ycoordinatebins.get(ycoordinatebin + 1)
52
+ } else if (ycoordinatebins.has(ycoordinatebin - 1)) {
53
+ newy = ycoordinatebins.get(ycoordinatebin - 1)
55
54
  } else {
56
55
  newy = pos2d[1]
57
- ycoordinatebins[ycoordinatebin] = pos2d[1]
56
+ ycoordinatebins.set(ycoordinatebin, pos2d[1])
58
57
  }
59
58
  pos2d = vec2.fromValues(pos2d[0], newy)
60
59
  vertices2d.push(pos2d)
@@ -66,10 +65,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
66
65
  if ((i === 0) || (y > maxy)) {
67
66
  maxy = y
68
67
  }
69
- if (!(y in ycoordinatetopolygonindexes)) {
70
- ycoordinatetopolygonindexes[y] = {}
68
+ let polygonindexes = ycoordinatetopolygonindexes.get(y)
69
+ if (!polygonindexes) {
70
+ polygonindexes = {} // PERF
71
+ ycoordinatetopolygonindexes.set(y, polygonindexes)
71
72
  }
72
- ycoordinatetopolygonindexes[y][polygonindex] = true
73
+ polygonindexes[polygonindex] = true
73
74
  }
74
75
  if (miny >= maxy) {
75
76
  // degenerate polygon, all vertices have same y coordinate. Just ignore it from now:
@@ -77,10 +78,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
77
78
  numvertices = 0
78
79
  minindex = -1
79
80
  } else {
80
- if (!(miny in topy2polygonindexes)) {
81
- topy2polygonindexes[miny] = []
81
+ let polygonindexes = topy2polygonindexes.get(miny)
82
+ if (!polygonindexes) {
83
+ polygonindexes = []
84
+ topy2polygonindexes.set(miny, polygonindexes)
82
85
  }
83
- topy2polygonindexes[miny].push(polygonindex)
86
+ polygonindexes.push(polygonindex)
84
87
  }
85
88
  } // if(numvertices > 0)
86
89
  // reverse the vertex order:
@@ -89,8 +92,9 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
89
92
  polygonvertices2d.push(vertices2d)
90
93
  polygontopvertexindexes.push(minindex)
91
94
  }
95
+
92
96
  const ycoordinates = []
93
- for (const ycoordinate in ycoordinatetopolygonindexes) ycoordinates.push(ycoordinate)
97
+ ycoordinatetopolygonindexes.forEach((polylist, y) => ycoordinates.push(y))
94
98
  ycoordinates.sort(fnNumberSort)
95
99
 
96
100
  // Now we will iterate over all y coordinates, from lowest to highest y coordinate
@@ -108,15 +112,14 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
108
112
  let prevoutpolygonrow = []
109
113
  for (let yindex = 0; yindex < ycoordinates.length; yindex++) {
110
114
  const newoutpolygonrow = []
111
- const ycoordinateasstring = ycoordinates[yindex]
112
- const ycoordinate = Number(ycoordinateasstring)
115
+ const ycoordinate = ycoordinates[yindex]
113
116
 
114
117
  // update activepolygons for this y coordinate:
115
118
  // - Remove any polygons that end at this y coordinate
116
119
  // - update leftvertexindex and rightvertexindex (which point to the current vertex index
117
120
  // at the the left and right side of the polygon
118
121
  // Iterate over all polygons that have a corner at this y coordinate:
119
- const polygonindexeswithcorner = ycoordinatetopolygonindexes[ycoordinateasstring]
122
+ const polygonindexeswithcorner = ycoordinatetopolygonindexes.get(ycoordinate)
120
123
  for (let activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) {
121
124
  const activepolygon = activepolygons[activepolygonindex]
122
125
  const polygonindex = activepolygon.polygonindex
@@ -166,7 +169,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
166
169
  nextycoordinate = Number(ycoordinates[yindex + 1])
167
170
  const middleycoordinate = 0.5 * (ycoordinate + nextycoordinate)
168
171
  // update activepolygons by adding any polygons that start here:
169
- const startingpolygonindexes = topy2polygonindexes[ycoordinateasstring]
172
+ const startingpolygonindexes = topy2polygonindexes.get(ycoordinate)
170
173
  for (const polygonindexKey in startingpolygonindexes) {
171
174
  const polygonindex = startingpolygonindexes[polygonindexKey]
172
175
  const vertices2d = polygonvertices2d[polygonindex]
@@ -212,8 +215,6 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
212
215
  })
213
216
  } // for(let polygonindex in startingpolygonindexes)
214
217
  } // yindex < ycoordinates.length-1
215
- // if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
216
- // FIXME : what ???
217
218
 
218
219
  // Now activepolygons is up to date
219
220
  // Build the output polygons for the next row in newoutpolygonrow:
@@ -252,19 +253,19 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
252
253
  } // for(activepolygon in activepolygons)
253
254
  if (yindex > 0) {
254
255
  // try to match the new polygons against the previous row:
255
- const prevcontinuedindexes = {}
256
- const matchedindexes = {}
256
+ const prevcontinuedindexes = new Set()
257
+ const matchedindexes = new Set()
257
258
  for (let i = 0; i < newoutpolygonrow.length; i++) {
258
259
  const thispolygon = newoutpolygonrow[i]
259
260
  for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
260
- if (!matchedindexes[ii]) { // not already processed?
261
+ if (!matchedindexes.has(ii)) { // not already processed?
261
262
  // We have a match if the sidelines are equal or if the top coordinates
262
263
  // are on the sidelines of the previous polygon
263
264
  const prevpolygon = prevoutpolygonrow[ii]
264
265
  if (vec2.distance(prevpolygon.bottomleft, thispolygon.topleft) < EPS) {
265
266
  if (vec2.distance(prevpolygon.bottomright, thispolygon.topright) < EPS) {
266
267
  // Yes, the top of this polygon matches the bottom of the previous:
267
- matchedindexes[ii] = true
268
+ matchedindexes.add(ii)
268
269
  // Now check if the joined polygon would remain convex:
269
270
  const v1 = line2.direction(thispolygon.leftline)
270
271
  const v2 = line2.direction(prevpolygon.leftline)
@@ -284,16 +285,16 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
284
285
  thispolygon.outpolygon = prevpolygon.outpolygon
285
286
  thispolygon.leftlinecontinues = leftlinecontinues
286
287
  thispolygon.rightlinecontinues = rightlinecontinues
287
- prevcontinuedindexes[ii] = true
288
+ prevcontinuedindexes.add(ii)
288
289
  }
289
290
  break
290
291
  }
291
292
  }
292
- } // if(!prevcontinuedindexes[ii])
293
+ } // if(!prevcontinuedindexes.has(ii))
293
294
  } // for ii
294
295
  } // for i
295
296
  for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
296
- if (!prevcontinuedindexes[ii]) {
297
+ if (!prevcontinuedindexes.has(ii)) {
297
298
  // polygon ends here
298
299
  // Finish the polygon with the last point(s):
299
300
  const prevpolygon = prevoutpolygonrow[ii]
@@ -123,6 +123,13 @@ test('circle (options)', (t) => {
123
123
  t.deepEqual(pts.length, 6)
124
124
  t.true(comparePoints(pts, exp))
125
125
 
126
+ // test full rotation with non-zero startAngle
127
+ geometry = circle({ startAngle: 1, endAngle: 1 + 2 * Math.PI })
128
+ pts = geom2.toPoints(geometry)
129
+
130
+ t.notThrows(() => geom2.validate(geometry))
131
+ t.deepEqual(pts.length, 32)
132
+
126
133
  // test segments
127
134
  geometry = circle({ radius: 3.5, segments: 5 })
128
135
  pts = geom2.toPoints(geometry)
@@ -5,7 +5,7 @@ const vec3 = require('../maths/vec3')
5
5
  const geom3 = require('../geometries/geom3')
6
6
  const poly3 = require('../geometries/poly3')
7
7
 
8
- const { sin, cos } = require('../utils/trigonometry')
8
+ const { sin, cos } = require('../maths/utils/trigonometry')
9
9
 
10
10
  const { isGT, isGTE, isNumberArray } = require('./commonChecks')
11
11
 
@@ -96,7 +96,9 @@ const cylinderElliptic = (options) => {
96
96
  const polygons = []
97
97
  for (let i = 0; i < slices; i++) {
98
98
  const t0 = i / slices
99
- const t1 = (i + 1) / slices
99
+ let t1 = (i + 1) / slices
100
+ // fix rounding error when rotating 2 * PI radians
101
+ if (rotation === 2 * Math.PI && i === slices - 1) t1 = 0
100
102
 
101
103
  if (endRadius[0] === startRadius[0] && endRadius[1] === startRadius[1]) {
102
104
  polygons.push(fromPoints(start, point(0, t1, endRadius), point(0, t0, endRadius)))
@@ -137,7 +137,13 @@ test('cylinderElliptic (options)', (t) => {
137
137
 
138
138
  t.notThrows(() => geom3.validate(obs))
139
139
  t.is(pts.length, 28)
140
- // t.true(comparePolygonsAsPoints(pts, exp))
140
+
141
+ // test startAngle and endAngle
142
+ obs = cylinderElliptic({ startAngle: 1, endAngle: 1 + 2 * Math.PI })
143
+ pts = geom3.toPoints(obs)
144
+
145
+ t.notThrows(() => geom3.validate(obs))
146
+ t.is(pts.length, 96)
141
147
 
142
148
  // test segments
143
149
  obs = cylinderElliptic({ segments: 8 })
@@ -4,7 +4,7 @@ const vec2 = require('../maths/vec2')
4
4
 
5
5
  const geom2 = require('../geometries/geom2')
6
6
 
7
- const { sin, cos } = require('../utils/trigonometry')
7
+ const { sin, cos } = require('../maths/utils/trigonometry')
8
8
 
9
9
  const { isGTE, isNumberArray } = require('./commonChecks')
10
10
 
@@ -123,6 +123,13 @@ test('ellipse (options)', (t) => {
123
123
  t.deepEqual(obs.length, 6)
124
124
  t.true(comparePoints(obs, exp))
125
125
 
126
+ // test full rotation with non-zero startAngle
127
+ geometry = ellipse({ startAngle: 1, endAngle: 1 + 2 * Math.PI })
128
+ obs = geom2.toPoints(geometry)
129
+
130
+ t.notThrows(() => geom2.validate(geometry))
131
+ t.deepEqual(obs.length, 32)
132
+
126
133
  // test segments
127
134
  geometry = ellipse({ segments: 72 })
128
135
  obs = geom2.toPoints(geometry)
@@ -3,7 +3,7 @@ const vec3 = require('../maths/vec3')
3
3
  const geom3 = require('../geometries/geom3')
4
4
  const poly3 = require('../geometries/poly3')
5
5
 
6
- const { sin, cos } = require('../utils/trigonometry')
6
+ const { sin, cos } = require('../maths/utils/trigonometry')
7
7
 
8
8
  const { isGTE, isNumberArray } = require('./commonChecks')
9
9
 
@@ -1,4 +1,5 @@
1
1
  const mat4 = require('../maths/mat4')
2
+ const vec3 = require('../maths/vec3')
2
3
 
3
4
  const geom3 = require('../geometries/geom3')
4
5
 
@@ -79,7 +80,7 @@ const geodesicSphere = (options) => {
79
80
 
80
81
  // -- normalize
81
82
  for (let k = 0; k < 3; k++) {
82
- const r = Math.hypot(q[k][0], q[k][1], q[k][2])
83
+ const r = vec3.length(q[k])
83
84
  for (let l = 0; l < 3; l++) {
84
85
  q[k][l] /= r
85
86
  }
@@ -95,7 +96,7 @@ const geodesicSphere = (options) => {
95
96
 
96
97
  // -- normalize
97
98
  for (let k = 0; k < 3; k++) {
98
- const r = Math.hypot(q[k][0], q[k][1], q[k][2])
99
+ const r = vec3.length(q[k])
99
100
  for (let l = 0; l < 3; l++) {
100
101
  q[k][l] /= r
101
102
  }
@@ -6,12 +6,14 @@ const vec3 = require('../maths/vec3')
6
6
  const geom3 = require('../geometries/geom3')
7
7
  const poly3 = require('../geometries/poly3')
8
8
 
9
+ const { sin, cos } = require('../maths/utils/trigonometry')
10
+
9
11
  const { isGT, isGTE, isNumberArray } = require('./commonChecks')
10
12
 
11
13
  const createCorners = (center, size, radius, segments, slice, positive) => {
12
14
  const pitch = (Math.PI / 2) * slice / segments
13
- const cospitch = Math.cos(pitch)
14
- const sinpitch = Math.sin(pitch)
15
+ const cospitch = cos(pitch)
16
+ const sinpitch = sin(pitch)
15
17
 
16
18
  const layersegments = segments - slice
17
19
  let layerradius = radius * cospitch
@@ -5,7 +5,7 @@ const vec3 = require('../maths/vec3')
5
5
  const geom3 = require('../geometries/geom3')
6
6
  const poly3 = require('../geometries/poly3')
7
7
 
8
- const { sin, cos } = require('../utils/trigonometry')
8
+ const { sin, cos } = require('../maths/utils/trigonometry')
9
9
 
10
10
  const { isGT, isGTE, isNumberArray } = require('./commonChecks')
11
11
 
@@ -15,7 +15,6 @@ test('torus (defaults)', (t) => {
15
15
 
16
16
  const bounds = measureBoundingBox(obs)
17
17
  const expectedBounds = [[-5, -5, -1], [5, 5, 1]]
18
- t.notThrows(() => geom3.validate(obs))
19
18
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected: ' + JSON.stringify(bounds))
20
19
  })
21
20
 
@@ -27,7 +26,6 @@ test('torus (simple options)', (t) => {
27
26
 
28
27
  const bounds = measureBoundingBox(obs)
29
28
  const expectedBounds = [[-5.5, -5.5, -0.5], [5.5, 5.5, 0.5]]
30
- t.notThrows(() => geom3.validate(obs))
31
29
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected: ' + JSON.stringify(bounds))
32
30
  })
33
31
 
@@ -39,10 +37,16 @@ test('torus (complex options)', (t) => {
39
37
 
40
38
  const bounds = measureBoundingBox(obs)
41
39
  const expectedBounds = [[-6, 0, -1], [0, 6, 1]]
42
- t.notThrows(() => geom3.validate(obs))
43
40
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected: ' + JSON.stringify(bounds))
44
41
  })
45
42
 
43
+ test('torus (startAngle)', (t) => {
44
+ const obs = torus({ startAngle: 1, endAngle: 1 + 2 * Math.PI })
45
+ const pts = geom3.toPoints(obs)
46
+ t.notThrows(() => geom3.validate(obs))
47
+ t.is(pts.length, 2048)
48
+ })
49
+
46
50
  test('torus (square by square)', (t) => {
47
51
  const obs = torus({ innerSegments: 4, outerSegments: 4, innerRotation: Math.PI / 2 })
48
52
 
@@ -5,6 +5,5 @@ export { default as fnNumberSort } from './fnNumberSort'
5
5
  export { default as insertSorted } from './insertSorted'
6
6
  export { default as radiusToSegments } from './radiusToSegments'
7
7
  export { default as radToDeg } from './radToDeg'
8
- export * from './trigonometry'
9
8
 
10
9
  export as namespace utils
@@ -6,12 +6,10 @@
6
6
  */
7
7
  module.exports = {
8
8
  areAllShapesTheSameType: require('./areAllShapesTheSameType'),
9
- cos: require('./trigonometry').cos,
10
9
  degToRad: require('./degToRad'),
11
10
  flatten: require('./flatten'),
12
11
  fnNumberSort: require('./fnNumberSort'),
13
12
  insertSorted: require('./insertSorted'),
14
13
  radiusToSegments: require('./radiusToSegments'),
15
- radToDeg: require('./radToDeg'),
16
- sin: require('./trigonometry').sin
14
+ radToDeg: require('./radToDeg')
17
15
  }
@@ -1 +0,0 @@
1
- export const EPSILON: number
@@ -1,5 +0,0 @@
1
- const EPSILON = 0.000001
2
-
3
- module.exports = {
4
- EPSILON
5
- }
@@ -1,47 +0,0 @@
1
- const vec3 = require('../../../maths/vec3')
2
-
3
- /*
4
- * Mend gaps in a 2D slice to make it a closed polygon
5
- */
6
- const repairSlice = (slice) => {
7
- if (!slice.edges) return slice
8
- const vertexMap = {} // string key to vertex map
9
- const edgeCount = {} // count of (in - out) edges
10
- slice.edges.forEach((edge) => {
11
- const inKey = edge[0].toString()
12
- const outKey = edge[1].toString()
13
- vertexMap[inKey] = edge[0]
14
- vertexMap[outKey] = edge[1]
15
- edgeCount[inKey] = (edgeCount[inKey] || 0) + 1 // in
16
- edgeCount[outKey] = (edgeCount[outKey] || 0) - 1 // out
17
- })
18
- // find vertices which are missing in or out edges
19
- const missingIn = Object.keys(edgeCount).filter((e) => edgeCount[e] < 0)
20
- const missingOut = Object.keys(edgeCount).filter((e) => edgeCount[e] > 0)
21
- // pairwise distance of bad vertices
22
- missingIn.forEach((key1) => {
23
- const v1 = vertexMap[key1]
24
- // find the closest vertex that is missing an out edge
25
- let bestDistance = Infinity
26
- let bestReplacement
27
- missingOut.forEach((key2) => {
28
- const v2 = vertexMap[key2]
29
- const distance = Math.hypot(v1[0] - v2[0], v1[1] - v2[1])
30
- if (distance < bestDistance) {
31
- bestDistance = distance
32
- bestReplacement = v2
33
- }
34
- })
35
- console.warn(`repairSlice: repairing vertex gap ${v1} to ${bestReplacement} distance ${bestDistance}`)
36
- // merge broken vertices
37
- slice.edges.forEach((edge) => {
38
- if (edge[0].toString() === key1) edge[0] = bestReplacement
39
- if (edge[1].toString() === key1) edge[1] = bestReplacement
40
- })
41
- })
42
- // Remove self-edges
43
- slice.edges = slice.edges.filter((e) => !vec3.equals(e[0], e[1]))
44
- return slice
45
- }
46
-
47
- module.exports = repairSlice