@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.
- package/CHANGELOG.md +14 -0
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/src/geometries/path2/appendBezier.js +1 -1
- package/src/geometries/poly3/index.js +1 -1
- package/src/geometries/poly3/measureBoundingBox.js +2 -0
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
- package/src/geometries/poly3/measureBoundingSphere.js +25 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
- package/src/index.js +41 -0
- package/src/measurements/measureBoundingSphere.js +2 -6
- package/src/operations/booleans/martinez/compareEvents.js +2 -7
- package/src/operations/booleans/martinez/connectEdges.js +30 -41
- package/src/operations/booleans/martinez/contour.js +1 -1
- package/src/operations/booleans/martinez/divideSegment.js +12 -11
- package/src/operations/booleans/martinez/fillQueue.js +24 -28
- package/src/operations/booleans/martinez/index.js +2 -1
- package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
- package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
- package/src/operations/booleans/martinez/splaytree.js +59 -457
- package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
- package/src/operations/booleans/martinez/sweepEvent.js +3 -17
- package/src/operations/booleans/trees/Node.js +25 -27
- package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
- package/src/operations/booleans/trees/Tree.js +9 -4
- package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
- package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
- package/src/operations/extrusions/extrudeWalls.js +3 -1
- package/src/operations/hulls/hullPoints2.js +20 -28
- package/src/operations/modifiers/mergePolygons.js +2 -3
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
//
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
86
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
38
|
+
sorted.forEach((point) => {
|
|
36
39
|
let cnt = stack.length
|
|
37
|
-
while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point
|
|
38
|
-
|
|
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
|
|
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)
|
|
85
|
-
return
|
|
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 =
|
|
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
|
}
|