@jscad/modeling 3.0.0-alpha.0 → 3.0.2-alpha.0

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 (161) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +1 -1
  3. package/dist/jscad-modeling.es.js +2 -2
  4. package/dist/jscad-modeling.min.js +2 -2
  5. package/package.json +2 -2
  6. package/rollup.config.js +1 -1
  7. package/src/colors/colorize.js +1 -5
  8. package/src/colors/colorize.test.js +8 -8
  9. package/src/geometries/geom2/transform.js +9 -1
  10. package/src/geometries/geom2/transform.test.js +57 -0
  11. package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
  12. package/src/geometries/geom3/fromPointsConvex.js +25 -0
  13. package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +1 -0
  16. package/src/geometries/index.js +3 -4
  17. package/src/geometries/path2/appendBezier.js +1 -1
  18. package/src/geometries/poly3/index.js +1 -1
  19. package/src/geometries/poly3/measureBoundingBox.js +2 -0
  20. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
  21. package/src/geometries/poly3/measureBoundingSphere.js +25 -8
  22. package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
  23. package/src/geometries/poly3/type.d.ts +1 -1
  24. package/src/geometries/slice/validate.js +1 -2
  25. package/src/index.js +41 -0
  26. package/src/maths/index.js +1 -0
  27. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  28. package/src/measurements/measureAggregateArea.js +0 -1
  29. package/src/measurements/measureAggregateBoundingBox.js +0 -1
  30. package/src/measurements/measureAggregateEpsilon.js +0 -1
  31. package/src/measurements/measureAggregateVolume.js +0 -1
  32. package/src/measurements/measureArea.js +0 -1
  33. package/src/measurements/measureBoundingBox.js +0 -1
  34. package/src/measurements/measureBoundingSphere.js +2 -6
  35. package/src/measurements/measureEpsilon.js +0 -1
  36. package/src/measurements/measureVolume.js +0 -1
  37. package/src/operations/booleans/index.d.ts +1 -0
  38. package/src/operations/booleans/intersect.js +5 -5
  39. package/src/operations/booleans/intersect.test.js +6 -7
  40. package/src/operations/booleans/intersectGeom2.js +2 -6
  41. package/src/operations/booleans/intersectGeom2.test.js +25 -1
  42. package/src/operations/booleans/intersectGeom3.js +2 -6
  43. package/src/operations/booleans/intersectGeom3.test.js +5 -1
  44. package/src/operations/booleans/martinez/compareEvents.js +2 -7
  45. package/src/operations/booleans/martinez/connectEdges.js +30 -41
  46. package/src/operations/booleans/martinez/contour.js +1 -1
  47. package/src/operations/booleans/martinez/divideSegment.js +12 -11
  48. package/src/operations/booleans/martinez/fillQueue.js +24 -28
  49. package/src/operations/booleans/martinez/index.js +2 -1
  50. package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
  51. package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
  52. package/src/operations/booleans/martinez/splaytree.js +59 -457
  53. package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
  54. package/src/operations/booleans/martinez/sweepEvent.js +3 -17
  55. package/src/operations/booleans/mayOverlap.js +0 -1
  56. package/src/operations/booleans/scission.d.ts +5 -0
  57. package/src/operations/booleans/scission.js +3 -5
  58. package/src/operations/booleans/scission.test.js +6 -0
  59. package/src/operations/booleans/subtract.js +5 -5
  60. package/src/operations/booleans/subtract.test.js +6 -7
  61. package/src/operations/booleans/subtractGeom2.js +2 -6
  62. package/src/operations/booleans/subtractGeom2.test.js +25 -1
  63. package/src/operations/booleans/subtractGeom3.js +2 -6
  64. package/src/operations/booleans/subtractGeom3.test.js +5 -1
  65. package/src/operations/booleans/trees/Node.js +25 -27
  66. package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
  67. package/src/operations/booleans/trees/Tree.js +9 -4
  68. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
  69. package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
  70. package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
  71. package/src/operations/booleans/union.js +5 -5
  72. package/src/operations/booleans/union.test.js +6 -7
  73. package/src/operations/booleans/unionGeom2.js +2 -6
  74. package/src/operations/booleans/unionGeom2.test.js +25 -1
  75. package/src/operations/booleans/unionGeom3.js +2 -6
  76. package/src/operations/booleans/unionGeom3.test.js +6 -1
  77. package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
  78. package/src/operations/extrusions/extrudeHelical.js +2 -8
  79. package/src/operations/extrusions/extrudeLinear.js +1 -5
  80. package/src/operations/extrusions/extrudeLinear.test.js +7 -1
  81. package/src/operations/extrusions/extrudeRotate.js +3 -2
  82. package/src/operations/extrusions/extrudeRotate.test.js +13 -1
  83. package/src/operations/extrusions/extrudeWalls.js +3 -1
  84. package/src/operations/extrusions/project.js +1 -5
  85. package/src/operations/hulls/hull.js +6 -5
  86. package/src/operations/hulls/hull.test.js +56 -3
  87. package/src/operations/hulls/hullChain.js +11 -6
  88. package/src/operations/hulls/hullChain.test.js +12 -2
  89. package/src/operations/hulls/hullGeom2.js +5 -6
  90. package/src/operations/hulls/hullGeom3.js +9 -18
  91. package/src/operations/hulls/hullPath2.js +6 -7
  92. package/src/operations/hulls/hullPath2.test.js +1 -1
  93. package/src/operations/hulls/hullPoints2.d.ts +3 -0
  94. package/src/operations/hulls/hullPoints2.js +24 -30
  95. package/src/operations/hulls/hullPoints3.d.ts +4 -0
  96. package/src/operations/hulls/hullPoints3.js +21 -0
  97. package/src/operations/hulls/index.d.ts +2 -0
  98. package/src/operations/hulls/index.js +3 -1
  99. package/src/operations/modifiers/generalize.js +2 -6
  100. package/src/operations/modifiers/index.js +1 -1
  101. package/src/operations/modifiers/mergePolygons.js +2 -3
  102. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
  103. package/src/operations/modifiers/snap.js +2 -6
  104. package/src/operations/offsets/offset.js +1 -5
  105. package/src/operations/offsets/offsetFromPoints.test.js +0 -1
  106. package/src/operations/offsets/offsetGeom2.test.js +1 -0
  107. package/src/operations/offsets/offsetGeom3.js +0 -2
  108. package/src/operations/offsets/offsetGeom3.test.js +9 -1
  109. package/src/operations/offsets/offsetPath2.js +3 -3
  110. package/src/operations/transforms/align.js +8 -7
  111. package/src/operations/transforms/align.test.js +2 -2
  112. package/src/operations/transforms/center.js +6 -9
  113. package/src/operations/transforms/center.test.js +19 -1
  114. package/src/operations/transforms/mirror.js +5 -8
  115. package/src/operations/transforms/mirror.test.js +7 -7
  116. package/src/operations/transforms/rotate.js +5 -8
  117. package/src/operations/transforms/scale.js +5 -8
  118. package/src/operations/transforms/transform.js +2 -5
  119. package/src/operations/transforms/translate.js +5 -8
  120. package/src/primitives/arc.js +2 -0
  121. package/src/primitives/arc.test.js +11 -11
  122. package/src/primitives/circle.test.js +18 -8
  123. package/src/primitives/cube.test.js +10 -0
  124. package/src/primitives/cuboid.test.js +10 -0
  125. package/src/primitives/cylinder.test.js +12 -0
  126. package/src/primitives/cylinderElliptic.test.js +21 -1
  127. package/src/primitives/ellipse.test.js +18 -8
  128. package/src/primitives/ellipsoid.test.js +12 -0
  129. package/src/primitives/geodesicSphere.test.js +8 -0
  130. package/src/primitives/line.test.js +1 -1
  131. package/src/primitives/polygon.d.ts +1 -0
  132. package/src/primitives/polygon.js +13 -4
  133. package/src/primitives/polygon.test.js +15 -0
  134. package/src/primitives/polyhedron.js +1 -0
  135. package/src/primitives/polyhedron.test.js +8 -2
  136. package/src/primitives/rectangle.test.js +9 -3
  137. package/src/primitives/roundedCuboid.js +1 -1
  138. package/src/primitives/roundedCuboid.test.js +20 -4
  139. package/src/primitives/roundedCylinder.js +1 -1
  140. package/src/primitives/roundedCylinder.test.js +20 -0
  141. package/src/primitives/roundedRectangle.js +1 -1
  142. package/src/primitives/roundedRectangle.test.js +15 -6
  143. package/src/primitives/sphere.test.js +12 -0
  144. package/src/primitives/square.test.js +10 -4
  145. package/src/primitives/star.test.js +14 -6
  146. package/src/primitives/torus.js +1 -1
  147. package/src/primitives/torus.test.js +11 -1
  148. package/src/primitives/triangle.test.js +17 -9
  149. package/src/utils/coalesce.d.ts +3 -0
  150. package/src/utils/coalesce.js +20 -0
  151. package/src/utils/index.js +2 -2
  152. package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
  153. package/src/maths/mat4/leftMultiplyVec2.js +0 -26
  154. package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
  155. package/src/maths/mat4/leftMultiplyVec3.js +0 -27
  156. package/src/maths/mat4/mirror.d.ts +0 -4
  157. package/src/maths/mat4/mirror.js +0 -32
  158. package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
  159. package/src/maths/mat4/rightMultiplyVec2.js +0 -27
  160. package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
  161. package/src/maths/mat4/rightMultiplyVec3.js +0 -28
@@ -4,11 +4,11 @@ import { geom2, geom3 } from '../../geometries/index.js'
4
4
 
5
5
  import { intersect } from './index.js'
6
6
 
7
- test('intersect error wrong number of arguments', (t) => {
8
- const message = 'intersect wrong number of arguments'
9
- t.throws(() => intersect(), { message })
10
- t.throws(() => intersect([]), { message })
11
- t.throws(() => intersect([[], []]), { message })
7
+ test('intersect empty arguments', (t) => {
8
+ t.is(intersect(), undefined)
9
+ t.is(intersect([]), undefined)
10
+ t.is(intersect([[], []]), undefined)
11
+ t.is(intersect(null, null), undefined)
12
12
  })
13
13
 
14
14
  test('intersect error different geometry types', (t) => {
@@ -20,6 +20,5 @@ test('intersect error non-geometries', (t) => {
20
20
  const message = 'intersect unsupported geometry type'
21
21
  t.throws(() => intersect([1, 2, 3], [4, 5, 6]), { message })
22
22
  t.throws(() => intersect([], [123]), { message })
23
- t.throws(() => intersect("one", "two"), { message })
24
- t.throws(() => intersect(null, null), { message })
23
+ t.throws(() => intersect('one', 'two'), { message })
25
24
  })
@@ -1,17 +1,13 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import { INTERSECTION } from './martinez/operation.js'
4
2
  import { boolean } from './martinez/index.js'
5
3
 
6
4
  /*
7
5
  * Return a new 2D geometry representing space in both the first geometry and
8
6
  * in the subsequent geometries. None of the given geometries are modified.
9
- * @param {...geom2} geometries - list of 2D geometries
7
+ * @param {Geom2[]} geometries - a flat list of 2D geometries
10
8
  * @returns {Geom2} new 2D geometry
11
9
  */
12
- export const intersectGeom2 = (...geometries) => {
13
- geometries = flatten(geometries)
14
-
10
+ export const intersectGeom2 = (geometries) => {
15
11
  let newGeometry = geometries.shift()
16
12
  geometries.forEach((geometry) => {
17
13
  newGeometry = boolean(newGeometry, geometry, INTERSECTION)
@@ -6,7 +6,7 @@ import { geom2 } from '../../geometries/index.js'
6
6
 
7
7
  import { measureArea } from '../../measurements/index.js'
8
8
 
9
- import { circle, rectangle } from '../../primitives/index.js'
9
+ import { circle, rectangle, square } from '../../primitives/index.js'
10
10
 
11
11
  import { intersect } from './index.js'
12
12
 
@@ -73,3 +73,27 @@ test('intersect: intersect of one or more geom2 objects produces expected geomet
73
73
  t.is(obs.length, 8)
74
74
  t.true(comparePoints(obs, exp))
75
75
  })
76
+
77
+ test('intersect with undefined/null values', (t) => {
78
+ const square1 = square({ size: 8 })
79
+ const square2 = square({ size: 6 })
80
+ const square3 = square({ size: 4 })
81
+ const geometries = [square1, undefined, square2, null, square3]
82
+
83
+ const obs = intersect(...geometries)
84
+ const pts = geom2.toPoints(obs)
85
+ t.notThrows(() => geom2.validate(obs))
86
+ t.is(pts.length, 4)
87
+ })
88
+
89
+ test('intersect of nested arrays', (t) => {
90
+ const square1 = square({ size: 8 })
91
+ const square2 = square({ size: 6 })
92
+ const square3 = square({ size: 4 })
93
+ const geometries = [square1, [square2, [square3]]]
94
+
95
+ const obs = intersect(...geometries)
96
+ const pts = geom2.toPoints(obs)
97
+ t.notThrows(() => geom2.validate(obs))
98
+ t.is(pts.length, 4)
99
+ })
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import { retessellate } from '../modifiers/retessellate.js'
4
2
 
5
3
  import { intersectGeom3Sub } from './intersectGeom3Sub.js'
@@ -7,12 +5,10 @@ import { intersectGeom3Sub } from './intersectGeom3Sub.js'
7
5
  /*
8
6
  * Return a new 3D geometry representing space in both the first geometry and
9
7
  * in the subsequent geometries. None of the given geometries are modified.
10
- * @param {...geom3} geometries - list of 3D geometries
8
+ * @param {Geom3[]} geometries - a flat list of 3D geometries
11
9
  * @returns {Geom3} new 3D geometry
12
10
  */
13
- export const intersectGeom3 = (...geometries) => {
14
- geometries = flatten(geometries)
15
-
11
+ export const intersectGeom3 = (geometries) => {
16
12
  let newGeometry = geometries.shift()
17
13
  geometries.forEach((geometry) => {
18
14
  newGeometry = intersectGeom3Sub(newGeometry, geometry)
@@ -4,7 +4,7 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
4
4
 
5
5
  import { geom3 } from '../../geometries/index.js'
6
6
 
7
- import { measureVolume } from '../../measurements/index.js'
7
+ import { measureArea, measureVolume } from '../../measurements/index.js'
8
8
 
9
9
  import { sphere, cuboid } from '../../primitives/index.js'
10
10
 
@@ -69,6 +69,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
69
69
  [[8.65956056235493e-17, 8.659560562354935e-17, 2], [1.4142135623730951, 3.4638242249419736e-16, 1.414213562373095], [0.9999999999999998, 1.0000000000000002, 1.414213562373095]]
70
70
  ]
71
71
  t.notThrows.skip(() => geom3.validate(result1))
72
+ t.is(measureArea(result1), 44.053756306589825)
72
73
  t.is(measureVolume(result1), 25.751611331979678)
73
74
  t.is(obs.length, 32)
74
75
  t.true(comparePolygonsAsPoints(obs, exp))
@@ -79,6 +80,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
79
80
  const result2 = intersect(geometry1, geometry2)
80
81
  obs = geom3.toPoints(result2)
81
82
  t.notThrows(() => geom3.validate(result2))
83
+ t.is(measureArea(result2), 0)
82
84
  t.is(measureVolume(result2), 0)
83
85
  t.is(obs.length, 0)
84
86
 
@@ -99,6 +101,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
99
101
  ]
100
102
 
101
103
  t.notThrows(() => geom3.validate(result3))
104
+ t.is(measureArea(result3), 6)
102
105
  t.is(measureVolume(result3), 1.0000000000000009)
103
106
  t.is(obs.length, 6)
104
107
  t.true(comparePolygonsAsPoints(obs, exp))
@@ -107,6 +110,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
107
110
  const result4 = intersect(geometry1, geometry3)
108
111
  obs = geom3.toPoints(result4)
109
112
  t.notThrows.skip(() => geom3.validate(result4))
113
+ t.is(measureArea(result4), 44.053756306589825)
110
114
  t.is(measureVolume(result4), 25.751611331979678)
111
115
  t.is(obs.length, 32)
112
116
  })
@@ -23,23 +23,18 @@ export const compareEvents = (e1, e2) => {
23
23
  // Event with lower y-coordinate is processed first
24
24
  if (p1[1] !== p2[1]) return p1[1] > p2[1] ? 1 : -1
25
25
 
26
- return specialCases(e1, e2, p1, p2)
27
- }
28
-
29
- const specialCases = (e1, e2, p1, p2) => {
30
26
  // Same coordinates, but one is a left endpoint and the other is
31
27
  // a right endpoint. The right endpoint is processed first
32
28
  if (e1.left !== e2.left) { return e1.left ? 1 : -1 }
33
29
 
34
- // const p2 = e1.otherEvent.point, p3 = e2.otherEvent.point
35
- // const sa = (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])
36
30
  // Same coordinates, both events
37
31
  // are left endpoints or right endpoints.
38
32
  // not collinear
39
33
  if (signedArea(p1, e1.otherEvent.point, e2.otherEvent.point) !== 0) {
40
- // the event associate to the bottom segment is processed first
34
+ // the event associate to the lowest segment is processed first
41
35
  return (!e1.isBelow(e2.otherEvent.point)) ? 1 : -1
42
36
  }
43
37
 
38
+ // Same coordinates, subject events are processed first
44
39
  return (!e1.isSubject && e2.isSubject) ? 1 : -1
45
40
  }
@@ -12,45 +12,39 @@ import { Contour } from './contour.js'
12
12
  * @return {SweepEvent[]}
13
13
  */
14
14
  const orderEvents = (sortedEvents) => {
15
- let event, i, len, tmp
16
15
  const resultEvents = []
17
- for (i = 0, len = sortedEvents.length; i < len; i++) {
18
- event = sortedEvents[i]
19
- if ((event.left && event.inResult) ||
20
- (!event.left && event.otherEvent.inResult)) {
21
- resultEvents.push(event)
16
+ sortedEvents.forEach((e) => {
17
+ if ((e.left && e.inResult) || (!e.left && e.otherEvent.inResult)) {
18
+ resultEvents.push(e)
22
19
  }
23
- }
20
+ })
24
21
  // Due to overlapping edges the resultEvents array can be not wholly sorted
25
22
  let sorted = false
26
23
  while (!sorted) {
27
24
  sorted = true
28
- for (i = 0, len = resultEvents.length; i < len; i++) {
25
+ const len = resultEvents.length
26
+ for (let i = 0; i < len; i++) {
29
27
  if ((i + 1) < len &&
30
28
  compareEvents(resultEvents[i], resultEvents[i + 1]) === 1) {
31
- tmp = resultEvents[i]
29
+ const tmp = resultEvents[i]
32
30
  resultEvents[i] = resultEvents[i + 1]
33
31
  resultEvents[i + 1] = tmp
34
32
  sorted = false
35
33
  }
36
34
  }
37
35
  }
38
-
39
- for (i = 0, len = resultEvents.length; i < len; i++) {
40
- event = resultEvents[i]
41
- event.otherPos = i
42
- }
36
+ // and index
37
+ resultEvents.forEach((e, i) => { e.otherPos = i })
43
38
 
44
39
  // imagine, the right event is found in the beginning of the queue,
45
40
  // when his left counterpart is not marked yet
46
- for (i = 0, len = resultEvents.length; i < len; i++) {
47
- event = resultEvents[i]
48
- if (!event.left) {
49
- tmp = event.otherPos
50
- event.otherPos = event.otherEvent.otherPos
51
- event.otherEvent.otherPos = tmp
41
+ resultEvents.forEach((e) => {
42
+ if (!e.left) {
43
+ const otherPos = e.otherPos
44
+ e.otherPos = e.otherEvent.otherPos
45
+ e.otherEvent.otherPos = otherPos
52
46
  }
53
- }
47
+ })
54
48
 
55
49
  return resultEvents
56
50
  }
@@ -62,14 +56,15 @@ const orderEvents = (sortedEvents) => {
62
56
  * @return {number}
63
57
  */
64
58
  const nextPos = (pos, resultEvents, processed, origPos) => {
65
- let newPos = pos + 1
66
- const p = resultEvents[pos].point
67
- let p1
68
59
  const length = resultEvents.length
69
60
 
61
+ const p0 = resultEvents[pos].point
62
+
63
+ let newPos = pos + 1
64
+ let p1
70
65
  if (newPos < length) { p1 = resultEvents[newPos].point }
71
66
 
72
- while (newPos < length && p1[0] === p[0] && p1[1] === p[1]) {
67
+ while (newPos < length && p1[0] === p0[0] && p1[1] === p0[1]) {
73
68
  if (!processed[newPos]) {
74
69
  return newPos
75
70
  } else {
@@ -81,7 +76,6 @@ const nextPos = (pos, resultEvents, processed, origPos) => {
81
76
  }
82
77
 
83
78
  newPos = pos - 1
84
-
85
79
  while (processed[newPos] && newPos > origPos) {
86
80
  newPos--
87
81
  }
@@ -90,7 +84,8 @@ const nextPos = (pos, resultEvents, processed, origPos) => {
90
84
  }
91
85
 
92
86
  const initializeContourFromContext = (event, contours, contourId) => {
93
- const contour = new Contour()
87
+ const contour = new Contour() // default is exterior contour of depth 0
88
+
94
89
  if (event.prevInResult != null) {
95
90
  const prevInResult = event.prevInResult
96
91
  // Note that it is valid to query the "previous in result" for its output contour id,
@@ -99,6 +94,7 @@ const initializeContourFromContext = (event, contours, contourId) => {
99
94
  // result".
100
95
  const lowerContourId = prevInResult.outputContourId
101
96
  const lowerResultTransition = prevInResult.resultTransition
97
+
102
98
  if (lowerContourId < 0) {
103
99
  contour.holeOf = null
104
100
  contour.depth = 0
@@ -122,30 +118,24 @@ const initializeContourFromContext = (event, contours, contourId) => {
122
118
  }
123
119
  } else {
124
120
  // We are outside => this contour is an exterior contour of same depth.
125
- contour.holeOf = null
126
121
  contour.depth = contours[lowerContourId].depth
127
122
  }
128
- } else {
129
- // There is no lower/previous contour => this contour is an exterior contour of depth 0.
130
- contour.holeOf = null
131
- contour.depth = 0
132
123
  }
133
124
  return contour
134
125
  }
135
126
 
136
127
  /**
137
128
  * @param {Array.<SweepEvent>} sortedEvents
138
- * @return {Array.<*>} polygons
129
+ * @return array of Contour
139
130
  */
140
131
  export const connectEdges = (sortedEvents) => {
141
132
  const resultEvents = orderEvents(sortedEvents)
142
- const len = resultEvents.length
133
+ const evlen = resultEvents.length
143
134
 
144
- // "false"-filled array
145
- const processed = {}
135
+ const processed = []
146
136
  const contours = []
147
137
 
148
- for (let i = 0; i < len; i++) {
138
+ for (let i = 0; i < evlen; i++) {
149
139
  if (processed[i]) {
150
140
  continue
151
141
  }
@@ -156,7 +146,7 @@ export const connectEdges = (sortedEvents) => {
156
146
  // Helper function that combines marking an event as processed with assigning its output contour ID
157
147
  const markAsProcessed = (pos) => {
158
148
  processed[pos] = true
159
- if (pos < resultEvents.length && resultEvents[pos]) {
149
+ if (pos < evlen) {
160
150
  resultEvents[pos].outputContourId = contourId
161
151
  }
162
152
  }
@@ -164,8 +154,7 @@ export const connectEdges = (sortedEvents) => {
164
154
  let pos = i
165
155
  const origPos = i
166
156
 
167
- const initial = resultEvents[i].point
168
- contour.points.push(initial)
157
+ contour.points.push(resultEvents[pos].point)
169
158
 
170
159
  while (true) {
171
160
  markAsProcessed(pos)
@@ -177,7 +166,7 @@ export const connectEdges = (sortedEvents) => {
177
166
 
178
167
  pos = nextPos(pos, resultEvents, processed, origPos)
179
168
 
180
- if (pos === origPos || pos >= resultEvents.length || !resultEvents[pos]) {
169
+ if (pos === origPos || pos >= evlen) {
181
170
  break
182
171
  }
183
172
  }
@@ -3,7 +3,7 @@ export class Contour {
3
3
  this.points = []
4
4
  this.holeIds = []
5
5
  this.holeOf = null
6
- this.depth = null
6
+ this.depth = 0
7
7
  }
8
8
 
9
9
  isExterior () {
@@ -2,28 +2,29 @@ import { SweepEvent } from './sweepEvent.js'
2
2
  import { compareEvents } from './compareEvents.js'
3
3
 
4
4
  /**
5
- * @param {SweepEvent} se
6
- * @param {Array.<Number>} p
5
+ * Divide the given segment at the given point, push the parts on the given queue.
6
+ * @param {SweepEvent} segment
7
+ * @param {Array.<Number>} point
7
8
  * @param {Queue} queue
8
- * @return {Queue}
9
+ * @return {Queue} given queue
9
10
  */
10
- export const divideSegment = (se, p, queue) => {
11
- const r = new SweepEvent(p, false, se, se.isSubject)
12
- const l = new SweepEvent(p, true, se.otherEvent, se.isSubject)
11
+ export const divideSegment = (segment, point, queue) => {
12
+ const r = new SweepEvent(point, false, segment, segment.isSubject)
13
+ const l = new SweepEvent(point, true, segment.otherEvent, segment.isSubject)
13
14
 
14
- r.contourId = l.contourId = se.contourId
15
+ r.contourId = l.contourId = segment.contourId
15
16
 
16
17
  // avoid a rounding error. The left event would be processed after the right event
17
- if (compareEvents(l, se.otherEvent) > 0) {
18
- se.otherEvent.left = true
18
+ if (compareEvents(l, segment.otherEvent) > 0) {
19
+ segment.otherEvent.left = true
19
20
  l.left = false
20
21
  }
21
22
 
22
23
  // avoid a rounding error. The left event would be processed after the right event
23
24
  // if (compareEvents(se, r) > 0) {}
24
25
 
25
- se.otherEvent.otherEvent = l
26
- se.otherEvent = r
26
+ segment.otherEvent.otherEvent = l
27
+ segment.otherEvent = r
27
28
 
28
29
  queue.push(l)
29
30
  queue.push(r)
@@ -9,26 +9,23 @@ import { DIFFERENCE } from './operation.js'
9
9
  import { SweepEvent } from './sweepEvent.js'
10
10
  import { Queue } from './tinyqueue.js'
11
11
 
12
- const max = Math.max
13
- const min = Math.min
12
+ let externalRingId = 0
14
13
 
15
- let contourId = 0
16
-
17
- const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExteriorRing) => {
14
+ const processPolygon = (contourOrHole, isSubject, ringId, queue, bbox, isExteriorRing) => {
18
15
  const len = contourOrHole.length - 1
19
- let s1, s2, e1, e2
20
16
  for (let i = 0; i < len; i++) {
21
- s1 = contourOrHole[i]
22
- s2 = contourOrHole[i + 1]
23
- e1 = new SweepEvent(s1, false, undefined, isSubject)
24
- e2 = new SweepEvent(s2, false, e1, isSubject)
17
+ const s1 = contourOrHole[i]
18
+ const s2 = contourOrHole[i + 1]
19
+ const e1 = new SweepEvent(s1, false, undefined, isSubject)
20
+ const e2 = new SweepEvent(s2, false, e1, isSubject)
21
+
25
22
  e1.otherEvent = e2
26
23
 
27
24
  if (s1[0] === s2[0] && s1[1] === s2[1]) {
28
25
  continue // skip collapsed edges, or it breaks
29
26
  }
30
27
 
31
- e1.contourId = e2.contourId = depth
28
+ e1.contourId = e2.contourId = ringId
32
29
  if (!isExteriorRing) {
33
30
  e1.isExteriorRing = false
34
31
  e2.isExteriorRing = false
@@ -41,10 +38,10 @@ const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExterior
41
38
 
42
39
  const x = s1[0]
43
40
  const y = s1[1]
44
- bbox[0] = min(bbox[0], x)
45
- bbox[1] = min(bbox[1], y)
46
- bbox[2] = max(bbox[2], x)
47
- bbox[3] = max(bbox[3], y)
41
+ bbox[0] = Math.min(bbox[0], x)
42
+ bbox[1] = Math.min(bbox[1], y)
43
+ bbox[2] = Math.max(bbox[2], x)
44
+ bbox[3] = Math.max(bbox[3], y)
48
45
 
49
46
  // Pushing it so the queue is sorted from left to right,
50
47
  // with object on the left having the highest priority.
@@ -55,24 +52,23 @@ const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExterior
55
52
 
56
53
  export const fillQueue = (subject, clipping, sbbox, cbbox, operation) => {
57
54
  const eventQueue = new Queue([], compareEvents)
58
- let polygonSet, isExteriorRing, i, ii, j, jj //, k, kk
59
55
 
60
- for (i = 0, ii = subject.length; i < ii; i++) {
61
- polygonSet = subject[i]
62
- for (j = 0, jj = polygonSet.length; j < jj; j++) {
63
- isExteriorRing = j === 0
64
- if (isExteriorRing) contourId++
65
- processPolygon(polygonSet[j], true, contourId, eventQueue, sbbox, isExteriorRing)
56
+ for (let i = 0; i < subject.length; i++) {
57
+ const polygonSet = subject[i]
58
+ for (let j = 0; j < polygonSet.length; j++) {
59
+ const isExteriorRing = j === 0
60
+ if (isExteriorRing) externalRingId++
61
+ processPolygon(polygonSet[j], true, externalRingId, eventQueue, sbbox, isExteriorRing)
66
62
  }
67
63
  }
68
64
 
69
- for (i = 0, ii = clipping.length; i < ii; i++) {
70
- polygonSet = clipping[i]
71
- for (j = 0, jj = polygonSet.length; j < jj; j++) {
72
- isExteriorRing = j === 0
65
+ for (let i = 0; i < clipping.length; i++) {
66
+ const polygonSet = clipping[i]
67
+ for (let j = 0; j < polygonSet.length; j++) {
68
+ let isExteriorRing = j === 0
73
69
  if (operation === DIFFERENCE) isExteriorRing = false
74
- if (isExteriorRing) contourId++
75
- processPolygon(polygonSet[j], false, contourId, eventQueue, cbbox, isExteriorRing)
70
+ if (isExteriorRing) externalRingId++
71
+ processPolygon(polygonSet[j], false, externalRingId, eventQueue, cbbox, isExteriorRing)
76
72
  }
77
73
  }
78
74
 
@@ -135,6 +135,7 @@ export const boolean = (subjectGeom, clippingGeom, operation) => {
135
135
  // Followed by holes if any
136
136
  for (let j = 0; j < contour.holeIds.length; j++) {
137
137
  const holeId = contour.holeIds[j]
138
+ // Reverse the order of points for holes
138
139
  const holePoints = contours[holeId].points
139
140
  const hole = []
140
141
  for (let k = holePoints.length - 2; k >= 0; k--) {
@@ -146,7 +147,7 @@ export const boolean = (subjectGeom, clippingGeom, operation) => {
146
147
  }
147
148
  }
148
149
 
149
- if (polygons) {
150
+ if (polygons.length) {
150
151
  return fromOutlines(polygons.flat())
151
152
  } else {
152
153
  return geom2.create()
@@ -15,36 +15,40 @@ import {
15
15
  } from './edgeType.js'
16
16
 
17
17
  /**
18
+ * and split the segments if an intersection is detected.
18
19
  * @param {SweepEvent} se1
19
20
  * @param {SweepEvent} se2
20
21
  * @param {Queue} queue
21
- * @return {number}
22
+ * @return
23
+ * 0=no intersection
24
+ * 1=intersect point
25
+ * 2=overlap, left points cooinciding
26
+ * 3=overlap, right points cooinciding
27
+ * 4=segments overlap
28
+ * 5=segment within segment
22
29
  */
23
30
  export const possibleIntersection = (se1, se2, queue) => {
24
- // that disallows self-intersecting polygons,
25
- // did cost us half a day, so I'll leave it
26
- // out of respect
27
- // if (se1.isSubject === se2.isSubject) return
31
+ // null = no intersection
32
+ // array[1] = point of intersection
33
+ // array[2] = line overlaps, segment of overlap, i.e. two points
28
34
  const inter = segmentIntersection(
29
35
  se1.point, se1.otherEvent.point,
30
36
  se2.point, se2.otherEvent.point
31
37
  )
32
38
 
33
39
  const nIntersections = inter ? inter.length : 0
34
- if (nIntersections === 0) return 0 // no intersection
35
40
 
36
- // the line segments intersect at an endpoint of both line segments
41
+ // no intersection of segments
42
+ if (nIntersections === 0) return 0
43
+
44
+ // single point of intersection, check the end points
37
45
  if ((nIntersections === 1) &&
38
46
  (equals(se1.point, se2.point) ||
39
47
  equals(se1.otherEvent.point, se2.otherEvent.point))) {
40
48
  return 0
41
49
  }
42
50
 
43
- if (nIntersections === 2 && se1.isSubject === se2.isSubject) {
44
- return 0
45
- }
46
-
47
- // The line segments associated to se1 and se2 intersect
51
+ // single point of intersection, divide the segments
48
52
  if (nIntersections === 1) {
49
53
  // if the intersection point is not an endpoint of se1
50
54
  if (!equals(se1.point, inter[0]) && !equals(se1.otherEvent.point, inter[0])) {
@@ -58,56 +62,63 @@ export const possibleIntersection = (se1, se2, queue) => {
58
62
  return 1
59
63
  }
60
64
 
61
- // The line segments associated to se1 and se2 overlap
62
- const events = []
65
+ // segments overlap, check for same subject/clipping
66
+ if (nIntersections === 2 && se1.isSubject === se2.isSubject) {
67
+ return 0
68
+ }
69
+
70
+ // segments overlap, determine the overlap, and divide segments
71
+ // FIXME eliminate this stack
72
+ const segmentEvents = []
63
73
  let leftCoincide = false
64
74
  let rightCoincide = false
65
75
 
66
76
  if (equals(se1.point, se2.point)) {
67
77
  leftCoincide = true // linked
68
78
  } else if (compareEvents(se1, se2) === 1) {
69
- events.push(se2, se1)
79
+ segmentEvents.push(se2, se1)
70
80
  } else {
71
- events.push(se1, se2)
81
+ segmentEvents.push(se1, se2)
72
82
  }
73
83
 
74
84
  if (equals(se1.otherEvent.point, se2.otherEvent.point)) {
75
85
  rightCoincide = true
76
86
  } else if (compareEvents(se1.otherEvent, se2.otherEvent) === 1) {
77
- events.push(se2.otherEvent, se1.otherEvent)
87
+ segmentEvents.push(se2.otherEvent, se1.otherEvent)
78
88
  } else {
79
- events.push(se1.otherEvent, se2.otherEvent)
89
+ segmentEvents.push(se1.otherEvent, se2.otherEvent)
80
90
  }
81
91
 
82
- if ((leftCoincide && rightCoincide) || leftCoincide) {
92
+ if (leftCoincide) {
83
93
  // both line segments are equal or share the left endpoint
94
+ // FIXME: this setting of type should be done outside this function
84
95
  se2.type = NON_CONTRIBUTING
85
96
  se1.type = (se2.inOut === se1.inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION
86
97
 
87
- if (leftCoincide && !rightCoincide) {
88
- // honestly no idea, but changing events selection from [2, 1]
98
+ if (!rightCoincide) {
99
+ // honestly no idea, but changing segmentEvents selection from [2, 1]
89
100
  // to [0, 1] fixes the overlapping self-intersecting polygons issue
90
- divideSegment(events[1].otherEvent, events[0].point, queue)
101
+ divideSegment(segmentEvents[1].otherEvent, segmentEvents[0].point, queue)
91
102
  }
92
103
  return 2
93
104
  }
94
105
 
95
106
  // the line segments share the right endpoint
96
107
  if (rightCoincide) {
97
- divideSegment(events[0], events[1].point, queue)
108
+ divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
98
109
  return 3
99
110
  }
100
111
 
101
112
  // no line segment includes totally the other one
102
- if (events[0] !== events[3].otherEvent) {
103
- divideSegment(events[0], events[1].point, queue)
104
- divideSegment(events[1], events[2].point, queue)
105
- return 3
113
+ if (segmentEvents[0] !== segmentEvents[3].otherEvent) {
114
+ divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
115
+ divideSegment(segmentEvents[1], segmentEvents[2].point, queue)
116
+ return 4
106
117
  }
107
118
 
108
119
  // one line segment includes the other one
109
- divideSegment(events[0], events[1].point, queue)
110
- divideSegment(events[3].otherEvent, events[2].point, queue)
120
+ divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
121
+ divideSegment(segmentEvents[3].otherEvent, segmentEvents[2].point, queue)
111
122
 
112
- return 3
123
+ return 5
113
124
  }