@jscad/modeling 3.0.5-alpha.0 → 3.0.6-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 +6 -0
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/src/colors/hsvToRgb.js +1 -0
- package/src/colors/rgbToHsl.js +3 -1
- package/src/colors/rgbToHsv.js +1 -0
- package/src/curves/bezier/create.js +1 -1
- package/src/geometries/geom2/transform.js +1 -0
- package/src/geometries/geom3/transform.js +1 -0
- package/src/geometries/geom3/validate.js +11 -1
- package/src/geometries/path2/appendArc.js +19 -25
- package/src/geometries/path2/appendBezier.js +8 -11
- package/src/geometries/path2/appendPoints.js +21 -3
- package/src/geometries/path2/appendPoints.test.js +16 -4
- package/src/geometries/path2/close.js +7 -10
- package/src/geometries/path2/concat.js +2 -2
- package/src/geometries/path2/fromPoints.js +7 -7
- package/src/geometries/path2/reverse.js +3 -3
- package/src/geometries/path2/transform.js +1 -0
- package/src/geometries/path2/validate.js +2 -2
- package/src/geometries/path3/close.js +7 -8
- package/src/geometries/path3/transform.js +1 -0
- package/src/geometries/slice/earcut/index.js +4 -2
- package/src/geometries/slice/earcut/linkedPolygon.js +4 -2
- package/src/geometries/slice/transform.js +2 -1
- package/src/maths/line2/intersectPointOfLines.js +1 -4
- package/src/operations/booleans/martinez/computeFields.js +6 -0
- package/src/operations/booleans/martinez/divideSegment.js +2 -1
- package/src/operations/booleans/martinez/fillQueue.js +3 -1
- package/src/operations/booleans/martinez/subdivideSegments.js +5 -4
- package/src/operations/booleans/subtractGeom3Sub.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +3 -0
- package/src/operations/extrusions/extrudeLinearGeom2.js +10 -7
- package/src/operations/hulls/quickhull/Face.js +8 -3
- package/src/operations/hulls/quickhull/QuickHull.js +10 -5
- package/src/operations/hulls/quickhull/VertexList.js +2 -1
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +2 -2
- package/src/operations/modifiers/reTesselateCoplanarPolygons.test.js +29 -3
- package/src/operations/modifiers/retessellate.js +1 -0
- package/src/operations/modifiers/snap.js +3 -3
- package/src/operations/modifiers/snap.test.js +12 -2
- package/src/operations/offsets/offsetGeom3.test.js +2 -2
- package/src/operations/transforms/translate.js +6 -2
- package/src/primitives/arc.js +3 -4
- package/src/primitives/ellipse.js +2 -3
- package/src/primitives/star.js +2 -4
- package/test/helpers/nearlyEqual.js +1 -0
|
@@ -4,6 +4,8 @@ import * as vec3 from '../../maths/vec3/index.js'
|
|
|
4
4
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
5
5
|
import * as slice from '../../geometries/slice/index.js'
|
|
6
6
|
|
|
7
|
+
import { isNumberArray } from '../../primitives/commonChecks.js'
|
|
8
|
+
|
|
7
9
|
import { extrudeFromSlices } from './extrudeFromSlices.js'
|
|
8
10
|
|
|
9
11
|
/*
|
|
@@ -26,6 +28,7 @@ export const extrudeLinearGeom2 = (options, geometry) => {
|
|
|
26
28
|
}
|
|
27
29
|
let { offset, twistAngle, twistSteps, repair } = Object.assign({ }, defaults, options)
|
|
28
30
|
|
|
31
|
+
if (!isNumberArray(offset, 3)) throw new Error('offset must be an array of three numbers')
|
|
29
32
|
if (twistSteps < 1) throw new Error('twistSteps must be 1 or more')
|
|
30
33
|
|
|
31
34
|
if (twistAngle === 0) {
|
|
@@ -33,18 +36,18 @@ export const extrudeLinearGeom2 = (options, geometry) => {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
// convert to vector in order to perform transforms
|
|
36
|
-
const offsetV = vec3.clone(offset)
|
|
37
|
-
|
|
38
39
|
let baseSlice = slice.fromOutlines(geom2.toOutlines(geometry))
|
|
39
|
-
if (
|
|
40
|
+
if (offset[2] < 0) baseSlice = slice.reverse(baseSlice)
|
|
40
41
|
|
|
41
|
-
const
|
|
42
|
+
const matrix1 = mat4.create()
|
|
43
|
+
const matrix2 = mat4.create()
|
|
44
|
+
const offset1 = vec3.create()
|
|
42
45
|
const createTwist = (progress, index, base) => {
|
|
43
46
|
const Zrotation = index / twistSteps * twistAngle
|
|
44
|
-
const Zoffset = vec3.scale(
|
|
45
|
-
mat4.multiply(
|
|
47
|
+
const Zoffset = vec3.scale(offset1, offset, index / twistSteps)
|
|
48
|
+
mat4.multiply(matrix1, mat4.fromZRotation(matrix1, Zrotation), mat4.fromTranslation(matrix2, Zoffset))
|
|
46
49
|
|
|
47
|
-
return slice.transform(
|
|
50
|
+
return slice.transform(matrix1, base)
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
options = {
|
|
@@ -308,9 +308,14 @@ export class Face {
|
|
|
308
308
|
const e2 = new HalfEdge(v2, face)
|
|
309
309
|
|
|
310
310
|
// join edges
|
|
311
|
-
e0.next =
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
e0.next = e1
|
|
312
|
+
e2.prev = e1
|
|
313
|
+
|
|
314
|
+
e1.next = e2
|
|
315
|
+
e0.prev = e2
|
|
316
|
+
|
|
317
|
+
e2.next = e0
|
|
318
|
+
e1.prev = e0
|
|
314
319
|
|
|
315
320
|
// main half edge reference
|
|
316
321
|
face.edge = e0
|
|
@@ -118,6 +118,7 @@ export class QuickHull {
|
|
|
118
118
|
end.next = null
|
|
119
119
|
return face.outside
|
|
120
120
|
}
|
|
121
|
+
return undefined
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
/**
|
|
@@ -215,11 +216,13 @@ export class QuickHull {
|
|
|
215
216
|
|
|
216
217
|
// initially assume that the first vertex is the min/max
|
|
217
218
|
for (i = 0; i < 3; i += 1) {
|
|
218
|
-
minVertices[i] =
|
|
219
|
+
minVertices[i] = this.vertices[0]
|
|
220
|
+
maxVertices[i] = this.vertices[0]
|
|
219
221
|
}
|
|
220
222
|
// copy the coordinates of the first vertex to min/max
|
|
221
223
|
for (i = 0; i < 3; i += 1) {
|
|
222
|
-
min[i] =
|
|
224
|
+
min[i] = this.vertices[0].point[i]
|
|
225
|
+
max[i] = this.vertices[0].point[i]
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
// compute the min/max vertex on all 6 directions
|
|
@@ -456,6 +459,7 @@ export class QuickHull {
|
|
|
456
459
|
}
|
|
457
460
|
return eyeVertex
|
|
458
461
|
}
|
|
462
|
+
return undefined
|
|
459
463
|
}
|
|
460
464
|
|
|
461
465
|
/**
|
|
@@ -477,7 +481,8 @@ export class QuickHull {
|
|
|
477
481
|
|
|
478
482
|
let edge
|
|
479
483
|
if (!crossEdge) {
|
|
480
|
-
|
|
484
|
+
crossEdge = face.getEdge(0)
|
|
485
|
+
edge = crossEdge
|
|
481
486
|
} else {
|
|
482
487
|
// start from the next edge since `crossEdge` was already analyzed
|
|
483
488
|
// (actually `crossEdge.opposite` was the face who called this method
|
|
@@ -722,7 +727,7 @@ export class QuickHull {
|
|
|
722
727
|
for (let i = 0; i < this.newFaces.length; i += 1) {
|
|
723
728
|
const face = this.newFaces[i]
|
|
724
729
|
if (face.mark === VISIBLE) {
|
|
725
|
-
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {}
|
|
730
|
+
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {}
|
|
726
731
|
}
|
|
727
732
|
}
|
|
728
733
|
|
|
@@ -733,7 +738,7 @@ export class QuickHull {
|
|
|
733
738
|
const face = this.newFaces[i]
|
|
734
739
|
if (face.mark === NON_CONVEX) {
|
|
735
740
|
face.mark = VISIBLE
|
|
736
|
-
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {}
|
|
741
|
+
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {}
|
|
737
742
|
}
|
|
738
743
|
}
|
|
739
744
|
|
|
@@ -317,8 +317,8 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
|
|
|
317
317
|
const vertices3d = points2d.map((point2d) => orthonormalFormula.to3D(point2d))
|
|
318
318
|
const polygon = poly3.fromVerticesAndPlane(vertices3d, plane) // TODO support shared
|
|
319
319
|
|
|
320
|
-
// if we let
|
|
321
|
-
if (polygon.vertices.length) destPolygons.push(polygon)
|
|
320
|
+
// if we let invalid polygons out, next retesselate will crash
|
|
321
|
+
if (polygon.vertices.length > 2) destPolygons.push(polygon)
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
} // if(yIndex > 0)
|
|
@@ -66,7 +66,9 @@ test('retessellateCoplanarPolygons: should merge coplanar polygons', (t) => {
|
|
|
66
66
|
polyL = translatePoly3([-15, -15, -15], polyE)
|
|
67
67
|
|
|
68
68
|
obs = reTesselateCoplanarPolygons([polyH, polyI, polyJ, polyK, polyL])
|
|
69
|
-
|
|
69
|
+
obs.forEach((polygon) => {
|
|
70
|
+
poly3.validate(polygon)
|
|
71
|
+
})
|
|
70
72
|
})
|
|
71
73
|
|
|
72
74
|
// Test for mark-and-filter optimization: multiple polygons that reach their
|
|
@@ -85,9 +87,8 @@ test('retessellateCoplanarPolygons: should correctly handle multiple polygon rem
|
|
|
85
87
|
// Each triangle should be preserved (they don't overlap)
|
|
86
88
|
t.is(obs.length, 3)
|
|
87
89
|
|
|
88
|
-
// Verify each polygon has 3 vertices (triangles)
|
|
89
90
|
obs.forEach((polygon) => {
|
|
90
|
-
|
|
91
|
+
poly3.validate(polygon)
|
|
91
92
|
})
|
|
92
93
|
})
|
|
93
94
|
|
|
@@ -103,3 +104,28 @@ test('retessellateCoplanarPolygons: should merge adjacent polygons with shared e
|
|
|
103
104
|
t.is(obs.length, 1)
|
|
104
105
|
t.is(obs[0].vertices.length, 4) // rectangle has 4 vertices
|
|
105
106
|
})
|
|
107
|
+
|
|
108
|
+
test('retessellateCoplanarPolygons: should not return invalid polygons', (t) => {
|
|
109
|
+
const polyA = poly3.create([
|
|
110
|
+
[ 3.72172376113686, -3.7661089598376325, 13 ],
|
|
111
|
+
[ 3.72172376113686, -3.7661089598376325, 13.515937566757202 ],
|
|
112
|
+
[ 3.721663581864801, -3.766195461144145, 13.515937566757202 ]
|
|
113
|
+
])
|
|
114
|
+
const polyB = poly3.create([
|
|
115
|
+
[ 3.721663581864801, -3.766195461144145, 13.0084828728813 ],
|
|
116
|
+
[ 3.721663581864801, -3.766195461144145, 13.00100040435791 ],
|
|
117
|
+
[ 3.721716664059388, -3.7661191611320772, 13.00100040435791 ]
|
|
118
|
+
])
|
|
119
|
+
const polyC = poly3.create([
|
|
120
|
+
[ 3.721663581864801, -3.766195461144145, 13.515937566757202 ],
|
|
121
|
+
[ 3.721663581864801, -3.766195461144145, 13.0084828728813 ],
|
|
122
|
+
[ 3.7217166640593886, -3.766119161132077, 13.00100040435791 ],
|
|
123
|
+
[ 3.7217236444490864, -3.766109127563903, 13.00100040435791 ]
|
|
124
|
+
])
|
|
125
|
+
const obs = reTesselateCoplanarPolygons([polyA, polyB, polyC])
|
|
126
|
+
t.is(obs.length, 1)
|
|
127
|
+
|
|
128
|
+
obs.forEach((polygon) => {
|
|
129
|
+
poly3.validate(polygon)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -18,6 +18,7 @@ export const retessellate = (geometry) => {
|
|
|
18
18
|
return geometry
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// FIXME this is creating new polygons
|
|
21
22
|
const polygons = geom3.toPolygons(geometry).map((polygon, index) => ({ vertices: polygon.vertices, plane: poly3.plane(polygon), index: index }))
|
|
22
23
|
const classified = classifyPolygons(polygons)
|
|
23
24
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as vec2 from '../../maths/vec2/index.js'
|
|
2
|
-
import * as vec3 from '../../maths/
|
|
2
|
+
import * as vec3 from '../../maths/vec3/index.js'
|
|
3
3
|
|
|
4
4
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
5
5
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
@@ -18,7 +18,7 @@ const snapPath2 = (geometry) => {
|
|
|
18
18
|
const points = path2.toPoints(geometry)
|
|
19
19
|
const newPoints = points.map((point) => vec2.snap(vec2.create(), point, epsilon))
|
|
20
20
|
// snap can produce duplicate points, remove those
|
|
21
|
-
return path2.fromPoints({}, newPoints)
|
|
21
|
+
return path2.fromPoints({ closed: geometry.isClosed }, newPoints)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/*
|
|
@@ -28,7 +28,7 @@ const snapPath3 = (geometry) => {
|
|
|
28
28
|
const vertices = path3.toVertices(geometry)
|
|
29
29
|
const newVertices = vertices.map((vertice) => vec3.snap(vec3.create(), vertice, epsilon))
|
|
30
30
|
// snap can produce duplicate points, remove those
|
|
31
|
-
return path3.fromVertices({}, newVertices)
|
|
31
|
+
return path3.fromVertices({ closed: geometry.isClosed }, newVertices)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/*
|
|
@@ -15,9 +15,10 @@ test('snap: snap of a path2 produces an expected path2', (t) => {
|
|
|
15
15
|
const geometry2 = arc({ radius: 1 / 2, segments: 8 })
|
|
16
16
|
const geometry3 = arc({ radius: 1.3333333333333333 / 2, segments: 8 })
|
|
17
17
|
const geometry4 = arc({ radius: TAU / 4 * 1000, segments: 8 })
|
|
18
|
+
const geometry5 = path2.fromPoints({ closed: true }, [[0, 0], [2, 2], [4, 0]])
|
|
18
19
|
|
|
19
|
-
const results = snap(geometry1, geometry2, geometry3, geometry4)
|
|
20
|
-
t.is(results.length,
|
|
20
|
+
const results = snap(geometry1, geometry2, geometry3, geometry4, geometry5)
|
|
21
|
+
t.is(results.length, 5)
|
|
21
22
|
|
|
22
23
|
let pts = path2.toPoints(results[0])
|
|
23
24
|
let exp = []
|
|
@@ -61,6 +62,15 @@ test('snap: snap of a path2 produces an expected path2', (t) => {
|
|
|
61
62
|
[1110.7100826766714, -1110.7100826766714]
|
|
62
63
|
]
|
|
63
64
|
t.true(comparePoints(pts, exp))
|
|
65
|
+
|
|
66
|
+
t.true(results[4].isClosed)
|
|
67
|
+
pts = path2.toPoints(results[4])
|
|
68
|
+
exp = [
|
|
69
|
+
[0, 0],
|
|
70
|
+
[2.00001, 2.00001],
|
|
71
|
+
[3.9999900000000004, 0]
|
|
72
|
+
]
|
|
73
|
+
t.true(comparePoints(pts, exp))
|
|
64
74
|
})
|
|
65
75
|
|
|
66
76
|
test('snap: snap of a geom2 produces an expected geom2', (t) => {
|
|
@@ -91,6 +91,6 @@ test('offsetGeom3: offset completes properly, issue 876', (t) => {
|
|
|
91
91
|
|
|
92
92
|
const obs = offset({ delta: 1.3, corners: 'round', segments: 12 }, sub)
|
|
93
93
|
t.notThrows.skip(() => geom3.validate(obs))
|
|
94
|
-
t.is(measureArea(obs), 524.
|
|
95
|
-
t.is(measureVolume(obs), 604.
|
|
94
|
+
t.is(measureArea(obs), 524.9674756919702)
|
|
95
|
+
t.is(measureVolume(obs), 604.0599468042326)
|
|
96
96
|
})
|
|
@@ -20,8 +20,12 @@ export const translate = (offset, ...objects) => {
|
|
|
20
20
|
if (!Array.isArray(offset)) throw new Error('offset must be an array')
|
|
21
21
|
|
|
22
22
|
// adjust the offset if necessary
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (offset.length < 3) {
|
|
24
|
+
offset = offset.slice() // don't modify the original
|
|
25
|
+
while (offset.length < 3) {
|
|
26
|
+
offset.push(0)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
25
29
|
|
|
26
30
|
const matrix = mat4.fromTranslation(mat4.create(), offset)
|
|
27
31
|
|
package/src/primitives/arc.js
CHANGED
|
@@ -51,14 +51,13 @@ export const arc = (options = {}) => {
|
|
|
51
51
|
|
|
52
52
|
const minAngle = Math.acos(((radius * radius) + (radius * radius) - (EPS * EPS)) / (2 * radius * radius))
|
|
53
53
|
|
|
54
|
-
const centerV = vec2.clone(center)
|
|
55
54
|
let point
|
|
56
55
|
const pointArray = []
|
|
57
56
|
if (rotation < minAngle) {
|
|
58
57
|
// there is no rotation, just a single point
|
|
59
58
|
point = vec2.fromAngleRadians(vec2.create(), startAngle)
|
|
60
59
|
vec2.scale(point, point, radius)
|
|
61
|
-
vec2.add(point, point,
|
|
60
|
+
vec2.add(point, point, center)
|
|
62
61
|
pointArray.push(point)
|
|
63
62
|
} else {
|
|
64
63
|
const numSteps = Math.floor(segments * (Math.abs(rotation) / TAU))
|
|
@@ -77,9 +76,9 @@ export const arc = (options = {}) => {
|
|
|
77
76
|
const angle = startAngle + (step * (rotation / numSteps))
|
|
78
77
|
point = vec2.fromAngleRadians(vec2.create(), angle)
|
|
79
78
|
vec2.scale(point, point, radius)
|
|
80
|
-
vec2.add(point, point,
|
|
79
|
+
vec2.add(point, point, center)
|
|
81
80
|
pointArray.push(point)
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
|
-
return path2.fromPoints({
|
|
83
|
+
return path2.fromPoints({}, pointArray)
|
|
85
84
|
}
|
|
@@ -61,7 +61,6 @@ export const ellipse = (options = {}) => {
|
|
|
61
61
|
|
|
62
62
|
segments = Math.floor(segments * (rotation / TAU))
|
|
63
63
|
|
|
64
|
-
const centerV = vec2.clone(center)
|
|
65
64
|
const step = rotation / segments // radians per segment
|
|
66
65
|
|
|
67
66
|
const points = []
|
|
@@ -69,9 +68,9 @@ export const ellipse = (options = {}) => {
|
|
|
69
68
|
for (let i = 0; i < segments; i++) {
|
|
70
69
|
const angle = (step * i) + startAngle
|
|
71
70
|
const point = vec2.fromValues(radius[0] * cos(angle), radius[1] * sin(angle))
|
|
72
|
-
vec2.add(point,
|
|
71
|
+
vec2.add(point, center, point)
|
|
73
72
|
points.push(point)
|
|
74
73
|
}
|
|
75
|
-
if (rotation < TAU) points.push(
|
|
74
|
+
if (rotation < TAU) points.push(vec2.clone(center)) // close with a new point
|
|
76
75
|
return geom2.create([points])
|
|
77
76
|
}
|
package/src/primitives/star.js
CHANGED
|
@@ -71,10 +71,8 @@ export const star = (options = {}) => {
|
|
|
71
71
|
innerRadius = outerRadius * getRadiusRatio(vertices, density)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
const outerPoints = getPoints(vertices, outerRadius, startAngle, centerV)
|
|
77
|
-
const innerPoints = getPoints(vertices, innerRadius, startAngle + Math.PI / vertices, centerV)
|
|
74
|
+
const outerPoints = getPoints(vertices, outerRadius, startAngle, center)
|
|
75
|
+
const innerPoints = getPoints(vertices, innerRadius, startAngle + Math.PI / vertices, center)
|
|
78
76
|
|
|
79
77
|
const allPoints = []
|
|
80
78
|
for (let i = 0; i < vertices; i++) {
|