@jscad/modeling 3.0.1-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 (32) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/jscad-modeling.es.js +2 -2
  3. package/dist/jscad-modeling.min.js +2 -2
  4. package/package.json +2 -2
  5. package/src/geometries/path2/appendBezier.js +1 -1
  6. package/src/geometries/poly3/index.js +1 -1
  7. package/src/geometries/poly3/measureBoundingBox.js +2 -0
  8. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
  9. package/src/geometries/poly3/measureBoundingSphere.js +25 -8
  10. package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
  11. package/src/index.js +41 -0
  12. package/src/measurements/measureBoundingSphere.js +2 -6
  13. package/src/operations/booleans/martinez/compareEvents.js +2 -7
  14. package/src/operations/booleans/martinez/connectEdges.js +30 -41
  15. package/src/operations/booleans/martinez/contour.js +1 -1
  16. package/src/operations/booleans/martinez/divideSegment.js +12 -11
  17. package/src/operations/booleans/martinez/fillQueue.js +24 -28
  18. package/src/operations/booleans/martinez/index.js +2 -1
  19. package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
  20. package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
  21. package/src/operations/booleans/martinez/splaytree.js +59 -457
  22. package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
  23. package/src/operations/booleans/martinez/sweepEvent.js +3 -17
  24. package/src/operations/booleans/trees/Node.js +25 -27
  25. package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
  26. package/src/operations/booleans/trees/Tree.js +9 -4
  27. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
  28. package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
  29. package/src/operations/extrusions/extrudeWalls.js +3 -1
  30. package/src/operations/hulls/hullPoints2.js +20 -28
  31. package/src/operations/modifiers/mergePolygons.js +2 -3
  32. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
@@ -7,23 +7,27 @@ import * as poly3 from '../../../geometries/poly3/index.js'
7
7
 
8
8
  import { splitLineSegmentByPlane } from './splitLineSegmentByPlane.js'
9
9
 
10
- // Returns object:
11
- // .type:
12
- // 0: coplanar-front
13
- // 1: coplanar-back
14
- // 2: front
15
- // 3: back
16
- // 4: spanning
17
- // In case the polygon is spanning, returns:
18
- // .front: a Polygon3 of the front part
19
- // .back: a Polygon3 of the back part
20
- export const splitPolygonByPlane = (splane, polygon) => {
21
- const result = {
22
- type: null,
23
- front: null,
24
- back: null
25
- }
26
- // cache in local lets (speedup):
10
+ /*
11
+ * Split the given polygon by the given plane.
12
+ *
13
+ * @@param (Object} result - object of which to update with the result
14
+ * @param {Plane} splane - plane to split across
15
+ * @param {Poly3} ploygon - polygon of which to split
16
+ * @returns none
17
+ *
18
+ * The result is updated in place to improve performance (no allocation)
19
+ * result.type:
20
+ * 0: coplanar-front
21
+ * 1: coplanar-back
22
+ * 2: front
23
+ * 3: back
24
+ * 4: spanning
25
+ *
26
+ * In case the polygon is spanning (4)
27
+ * result.front contains null or a ploygon (front part)
28
+ * result.back contains null or a polygon (back part)
29
+ */
30
+ export const splitPolygonByPlane = (result, splane, polygon) => {
27
31
  const vertices = polygon.vertices
28
32
  const numVertices = vertices.length
29
33
  const pplane = poly3.plane(polygon)
@@ -41,17 +45,20 @@ export const splitPolygonByPlane = (splane, polygon) => {
41
45
  if (t > EPS) hasFront = true
42
46
  if (t < MINEPS) hasBack = true
43
47
  }
48
+
44
49
  if ((!hasFront) && (!hasBack)) {
45
50
  // all points coplanar
46
51
  const t = vec3.dot(splane, pplane)
47
52
  result.type = (t >= 0) ? 0 : 1
48
53
  } else if (!hasBack) {
54
+ // points only front of the plane
49
55
  result.type = 2
50
56
  } else if (!hasFront) {
57
+ // points only back of the plane
51
58
  result.type = 3
52
59
  } else {
53
- // spanning
54
- result.type = 4
60
+ // points span the plane
61
+ // split the line segments by the plane
55
62
  const frontVertices = []
56
63
  const backVertices = []
57
64
  let isback = vertexIsBack[0]
@@ -61,14 +68,10 @@ export const splitPolygonByPlane = (splane, polygon) => {
61
68
  if (nextVertexIndex >= numVertices) nextVertexIndex = 0
62
69
  const nextIsBack = vertexIsBack[nextVertexIndex]
63
70
  if (isback === nextIsBack) {
64
- // line segment is on one side of the plane:
65
- if (isback) {
66
- backVertices.push(vertex)
67
- } else {
68
- frontVertices.push(vertex)
69
- }
71
+ // line segment is on one side of the plane
72
+ isback ? backVertices.push(vertex) : frontVertices.push(vertex)
70
73
  } else {
71
- // line segment intersects plane:
74
+ // line segment spans the plane
72
75
  const nextPoint = vertices[nextVertexIndex]
73
76
  const intersectionPoint = splitLineSegmentByPlane(splane, vertex, nextPoint)
74
77
  if (isback) {
@@ -82,8 +85,9 @@ export const splitPolygonByPlane = (splane, polygon) => {
82
85
  }
83
86
  }
84
87
  isback = nextIsBack
85
- } // for vertexIndex
86
- // remove duplicate vertices:
88
+ }
89
+
90
+ // remove duplicate vertices
87
91
  const EPS_SQUARED = EPS * EPS
88
92
  if (backVertices.length >= 3) {
89
93
  let prevVertex = backVertices[backVertices.length - 1]
@@ -107,12 +111,13 @@ export const splitPolygonByPlane = (splane, polygon) => {
107
111
  prevVertex = vertex
108
112
  }
109
113
  }
110
- if (frontVertices.length >= 3) {
111
- result.front = poly3.fromVerticesAndPlane(frontVertices, pplane)
112
- }
113
- if (backVertices.length >= 3) {
114
- result.back = poly3.fromVerticesAndPlane(backVertices, pplane)
115
- }
114
+
115
+ // assemble the result
116
+ result.type = 4
117
+
118
+ result.front = frontVertices.length >= 3 ? poly3.fromVerticesAndPlane(frontVertices, pplane) : null
119
+
120
+ result.back = backVertices.length >= 3 ? poly3.fromVerticesAndPlane(backVertices, pplane) : null
116
121
  }
117
122
  }
118
123
  return result
@@ -25,10 +25,11 @@ const repartitionEdges = (newLength, edges) => {
25
25
  }
26
26
 
27
27
  const divisor = vec3.fromValues(multiple, multiple, multiple)
28
+ const increment = vec3.create()
28
29
 
29
30
  const newEdges = []
30
31
  edges.forEach((edge) => {
31
- const increment = vec3.subtract(vec3.create(), edge[1], edge[0])
32
+ vec3.subtract(increment, edge[1], edge[0])
32
33
  vec3.divide(increment, increment, divisor)
33
34
 
34
35
  // repartition the edge
@@ -48,6 +49,7 @@ const EPSAREA = (EPS * EPS / 2) * Math.sin(Math.PI / 3)
48
49
  * Extrude (build) walls between the given slices.
49
50
  * Each wall consists of two triangles, which may be invalid if slices are overlapping.
50
51
  */
52
+ // FIXME this function should take an eps parameter
51
53
  export const extrudeWalls = (slice0, slice1) => {
52
54
  let edges0 = slice.toEdges(slice0)
53
55
  let edges1 = slice.toEdges(slice1)
@@ -17,28 +17,32 @@ export const hullPoints2 = (uniquePoints) => {
17
17
  }
18
18
  })
19
19
 
20
- // gather information for sorting by polar coordinates (point, angle, distSq)
21
- const points = []
22
- uniquePoints.forEach((point) => {
23
- // use faster fakeAtan2 instead of Math.atan2
24
- const angle = fakeAtan2(point[1] - min[1], point[0] - min[0])
25
- const distSq = vec2.squaredDistance(point, min)
26
- points.push({ point, angle, distSq })
27
- })
20
+ // calculations relative to min point
21
+ const squaredDistance = (point) => vec2.squaredDistance(point, min)
22
+ const polarAngle = (point) => (point[0] === min[0] && point[1] === min[1]) ? -Infinity : -(point[0] - min[0]) / (point[1] - min[1])
28
23
 
29
- // sort by polar coordinates
30
- points.sort((pt1, pt2) => pt1.angle !== pt2.angle
31
- ? pt1.angle - pt2.angle
32
- : pt1.distSq - pt2.distSq)
24
+ // points are sorted by polar angle in clockwise order
25
+ const sorted = uniquePoints
26
+ sorted.sort((pt1, pt2) => {
27
+ const pa1 = polarAngle(pt1)
28
+ const pa2 = polarAngle(pt2)
29
+ if (pa1 === pa2) {
30
+ // sort by the relative distances to min point
31
+ return squaredDistance(pt1) - squaredDistance(pt2)
32
+ }
33
+ // sort by polar angles to min point
34
+ return pa1 - pa2
35
+ })
33
36
 
34
37
  const stack = [] // start with empty stack
35
- points.forEach((point) => {
38
+ sorted.forEach((point) => {
36
39
  let cnt = stack.length
37
- while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point.point) <= Number.EPSILON) {
38
- stack.pop() // get rid of colinear and interior (clockwise) points
40
+ while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point) <= Number.EPSILON) {
41
+ // get rid of colinear and interior (clockwise) points
42
+ stack.pop()
39
43
  cnt = stack.length
40
44
  }
41
- stack.push(point.point)
45
+ stack.push(point)
42
46
  })
43
47
 
44
48
  return stack
@@ -46,15 +50,3 @@ export const hullPoints2 = (uniquePoints) => {
46
50
 
47
51
  // returns: < 0 clockwise, 0 colinear, > 0 counter-clockwise
48
52
  const ccw = (v1, v2, v3) => (v2[0] - v1[0]) * (v3[1] - v1[1]) - (v2[1] - v1[1]) * (v3[0] - v1[0])
49
-
50
- // Returned "angle" is really 1/tan (inverse of slope) made negative to increase with angle.
51
- // This function is strictly for sorting in this algorithm.
52
- const fakeAtan2 = (y, x) => {
53
- // The "if" is a special case for when the minimum vector found in loop above is present.
54
- // We need to ensure that it sorts as the minimum point. Otherwise, this becomes NaN.
55
- if (y === 0 && x === 0) {
56
- return -Infinity
57
- } else {
58
- return -x / y
59
- }
60
- }
@@ -67,7 +67,6 @@ const calculateAngle = (prevVertex, midVertex, nextVertex, normal) => {
67
67
 
68
68
  // create a polygon starting from the given edge (if possible)
69
69
  const createPolygonAnd = (edge) => {
70
- let polygon
71
70
  const vertices = []
72
71
  while (edge.next) {
73
72
  const next = edge.next
@@ -81,8 +80,8 @@ const createPolygonAnd = (edge) => {
81
80
 
82
81
  edge = next
83
82
  }
84
- if (vertices.length > 0) polygon = poly3.create(vertices)
85
- return polygon
83
+ if (vertices.length > 0) return poly3.create(vertices)
84
+ return null
86
85
  }
87
86
 
88
87
  /*
@@ -31,7 +31,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
31
31
  // Make a list of all encountered y coordinates
32
32
  // And build a map of all polygons that have a vertex at a certain y coordinate:
33
33
  const yCoordinateBins = new Map()
34
- const yCoordinateBinningFactor = 10 / EPS
34
+ const yCoordinateBinningFactor = 10 / EPS // FIXME
35
35
  for (let polygonIndex = 0; polygonIndex < numPolygons; polygonIndex++) {
36
36
  const poly3d = sourcePolygons[polygonIndex]
37
37
  let vertices2d = []
@@ -68,7 +68,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
68
68
  }
69
69
  let polygonIndexes = yCoordinateToPolygonIndexes.get(y)
70
70
  if (!polygonIndexes) {
71
- polygonIndexes = {} // PERF
71
+ polygonIndexes = []
72
72
  yCoordinateToPolygonIndexes.set(y, polygonIndexes)
73
73
  }
74
74
  polygonIndexes[polygonIndex] = true
@@ -242,7 +242,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
242
242
  const prevOutPolygon = newOutPolygonRow[newOutPolygonRow.length - 1]
243
243
  const d1 = vec2.distance(outPolygon.topLeft, prevOutPolygon.topRight)
244
244
  const d2 = vec2.distance(outPolygon.bottomLeft, prevOutPolygon.bottomRight)
245
- if ((d1 < EPS) && (d2 < EPS)) {
245
+ if ((d1 < EPS) && (d2 < EPS)) { // FIXME
246
246
  // we can join this polygon with the one to the left:
247
247
  outPolygon.topLeft = prevOutPolygon.topLeft
248
248
  outPolygon.leftLine = prevOutPolygon.leftLine
@@ -263,8 +263,8 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
263
263
  // We have a match if the sidelines are equal or if the top coordinates
264
264
  // are on the sidelines of the previous polygon
265
265
  const prevPolygon = prevOutPolygonRow[ii]
266
- if (vec2.distance(prevPolygon.bottomLeft, thisPolygon.topLeft) < EPS) {
267
- if (vec2.distance(prevPolygon.bottomRight, thisPolygon.topRight) < EPS) {
266
+ if (vec2.distance(prevPolygon.bottomLeft, thisPolygon.topLeft) < EPS) { // FIXME
267
+ if (vec2.distance(prevPolygon.bottomRight, thisPolygon.topRight) < EPS) { // FIXME
268
268
  // Yes, the top of this polygon matches the bottom of the previous:
269
269
  matchedIndexes.add(ii)
270
270
  // Now check if the joined polygon would remain convex:
@@ -300,7 +300,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
300
300
  // Finish the polygon with the last point(s):
301
301
  const prevPolygon = prevOutPolygonRow[ii]
302
302
  prevPolygon.outPolygon.rightPoints.push(prevPolygon.bottomRight)
303
- if (vec2.distance(prevPolygon.bottomRight, prevPolygon.bottomLeft) > EPS) {
303
+ if (vec2.distance(prevPolygon.bottomRight, prevPolygon.bottomLeft) > EPS) { // FIXME
304
304
  // polygon ends with a horizontal line:
305
305
  prevPolygon.outPolygon.leftPoints.push(prevPolygon.bottomLeft)
306
306
  }
@@ -324,7 +324,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
324
324
  rightPoints: []
325
325
  }
326
326
  thisPolygon.outPolygon.leftPoints.push(thisPolygon.topLeft)
327
- if (vec2.distance(thisPolygon.topLeft, thisPolygon.topRight) > EPS) {
327
+ if (vec2.distance(thisPolygon.topLeft, thisPolygon.topRight) > EPS) { // FIXME
328
328
  // we have a horizontal line at the top:
329
329
  thisPolygon.outPolygon.rightPoints.push(thisPolygon.topRight)
330
330
  }