@jscad/modeling 2.6.0 → 2.7.2
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 +56 -0
- package/dist/jscad-modeling.min.js +97 -94
- package/package.json +2 -2
- package/src/colors/colorize.js +35 -1
- package/src/colors/colorize.test.js +19 -4
- package/src/colors/hslToRgb.js +1 -1
- package/src/colors/hueToColorComponent.js +1 -0
- package/src/curves/bezier/create.js +3 -8
- package/src/curves/bezier/tangentAt.js +2 -2
- package/src/curves/index.js +1 -1
- package/src/geometries/geom2/clone.js +2 -12
- package/src/geometries/geom2/toOutlines.js +6 -11
- package/src/geometries/geom2/transform.js +0 -2
- package/src/geometries/geom3/clone.js +2 -14
- package/src/geometries/geom3/clone.test.js +0 -2
- package/src/geometries/geom3/create.js +1 -3
- package/src/geometries/geom3/create.test.js +0 -2
- package/src/geometries/geom3/fromCompactBinary.js +4 -6
- package/src/geometries/geom3/fromToCompactBinary.test.js +0 -6
- package/src/geometries/geom3/invert.js +2 -2
- package/src/geometries/geom3/invert.test.js +0 -2
- package/src/geometries/geom3/toCompactBinary.js +8 -10
- package/src/geometries/geom3/toPoints.js +1 -0
- package/src/geometries/geom3/transform.js +0 -2
- package/src/geometries/geom3/transform.test.js +0 -1
- package/src/geometries/geom3/type.d.ts +0 -1
- package/src/geometries/path2/clone.js +2 -13
- package/src/geometries/path2/transform.js +0 -2
- package/src/geometries/poly3/isConvex.js +1 -1
- package/src/geometries/poly3/measureArea.js +12 -13
- package/src/geometries/poly3/measureArea.test.js +15 -0
- package/src/geometries/poly3/plane.js +1 -2
- package/src/maths/line3/create.js +2 -1
- package/src/maths/mat4/fromRotation.js +1 -1
- package/src/maths/mat4/isIdentity.test.js +0 -2
- package/src/maths/mat4/isOnlyTransformScale.js +5 -4
- package/src/maths/mat4/rotate.js +1 -1
- package/src/maths/plane/fromPoints.js +32 -10
- package/src/maths/plane/fromPoints.test.js +4 -0
- package/src/maths/vec2/length.test.js +10 -0
- package/src/maths/vec3/angle.js +2 -2
- package/src/maths/vec3/angle.test.js +17 -0
- package/src/maths/vec3/length.test.js +10 -0
- package/src/measurements/measureBoundingBox.js +37 -121
- package/src/measurements/measureBoundingBox.test.js +8 -0
- package/src/measurements/measureCenterOfMass.js +0 -1
- package/src/measurements/measureEpsilon.js +3 -9
- package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
- package/src/operations/booleans/retessellate.js +1 -3
- package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
- package/src/operations/expansions/expand.js +2 -0
- package/src/operations/expansions/expand.test.js +1 -1
- package/src/operations/expansions/offset.js +1 -0
- package/src/operations/extrusions/extrudeLinear.js +1 -1
- package/src/operations/extrusions/extrudeRectangular.js +2 -1
- package/src/operations/extrusions/extrudeRotate.test.js +18 -10
- package/src/operations/hulls/quickhull/QuickHull.js +2 -2
- package/src/operations/modifiers/edges.js +1 -3
- package/src/operations/modifiers/generalize.js +3 -7
- package/src/operations/modifiers/snapPolygons.js +2 -2
- package/src/operations/modifiers/snapPolygons.test.js +13 -5
- package/src/operations/transforms/mirror.js +1 -1
- package/src/primitives/ellipse.js +1 -1
- package/src/primitives/geodesicSphere.js +2 -2
- package/src/primitives/index.d.ts +1 -0
- package/src/primitives/index.js +2 -1
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/triangle.d.ts +10 -0
- package/src/primitives/triangle.js +164 -0
- package/src/primitives/triangle.test.js +95 -0
- package/src/utils/insertSorted.js +1 -0
- package/test/helpers/comparePolygons.js +1 -3
- package/test/helpers/nearlyEqual.js +2 -6
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
* Determine whether the given matrix is translate
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* this method is primarily useful for measureBoundingBox
|
|
3
|
+
* Determine whether the given matrix is only translate and/or scale.
|
|
4
|
+
* This code returns true for PI rotation as it can be interpreted as scale.
|
|
7
5
|
*
|
|
6
|
+
* @param {mat4} matrix - the matrix
|
|
7
|
+
* @returns {Boolean} true if matrix is for translate and/or scale
|
|
8
|
+
* @alias module:modeling/maths/mat4.isOnlyTransformScale
|
|
8
9
|
*/
|
|
9
10
|
const isOnlyTransformScale = (matrix) => (
|
|
10
11
|
|
package/src/maths/mat4/rotate.js
CHANGED
|
@@ -10,17 +10,39 @@ const vec3 = require('../vec3')
|
|
|
10
10
|
* @returns {plane} out
|
|
11
11
|
* @alias module:modeling/maths/plane.fromPoints
|
|
12
12
|
*/
|
|
13
|
-
const fromPoints = (out,
|
|
14
|
-
const
|
|
15
|
-
const ca = vec3.subtract(vec3.create(), c, a)
|
|
16
|
-
vec3.cross(ba, ba, ca)
|
|
17
|
-
vec3.normalize(ba, ba) // normal part
|
|
18
|
-
const w = vec3.dot(ba, a)
|
|
13
|
+
const fromPoints = (out, ...vertices) => {
|
|
14
|
+
const len = vertices.length
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// Calculate normal vector for a single vertex
|
|
17
|
+
// Inline to avoid allocations
|
|
18
|
+
const ba = vec3.create()
|
|
19
|
+
const ca = vec3.create()
|
|
20
|
+
const vertexNormal = (index) => {
|
|
21
|
+
const a = vertices[index]
|
|
22
|
+
const b = vertices[(index + 1) % len]
|
|
23
|
+
const c = vertices[(index + 2) % len]
|
|
24
|
+
vec3.subtract(ba, b, a) // ba = b - a
|
|
25
|
+
vec3.subtract(ca, c, a) // ca = c - a
|
|
26
|
+
vec3.cross(ba, ba, ca) // ba = ba x ca
|
|
27
|
+
vec3.normalize(ba, ba)
|
|
28
|
+
return ba
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
out[0] = 0
|
|
32
|
+
out[1] = 0
|
|
33
|
+
out[2] = 0
|
|
34
|
+
if (len === 3) {
|
|
35
|
+
// optimization for triangles, which are always coplanar
|
|
36
|
+
vec3.copy(out, vertexNormal(0))
|
|
37
|
+
} else {
|
|
38
|
+
// sum of vertex normals
|
|
39
|
+
vertices.forEach((v, i) => {
|
|
40
|
+
vec3.add(out, out, vertexNormal(i))
|
|
41
|
+
})
|
|
42
|
+
// renormalize normal vector
|
|
43
|
+
vec3.normalize(out, out)
|
|
44
|
+
}
|
|
45
|
+
out[3] = vec3.dot(out, vertices[0])
|
|
24
46
|
return out
|
|
25
47
|
}
|
|
26
48
|
|
|
@@ -13,4 +13,8 @@ test('plane: fromPoints() should return a new plane with correct values', (t) =>
|
|
|
13
13
|
// planes created from the same points results in an invalid plane
|
|
14
14
|
const obs3 = fromPoints(obs1, [0, 6, 0], [0, 6, 0], [0, 6, 0])
|
|
15
15
|
t.true(compareVectors(obs3, [0 / 0, 0 / 0, 0 / 0, 0 / 0]))
|
|
16
|
+
|
|
17
|
+
// polygon with co-linear vertices
|
|
18
|
+
const obs4 = fromPoints(obs1, [0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0])
|
|
19
|
+
t.true(compareVectors(obs4, [0, 0, 1, 0]))
|
|
16
20
|
})
|
|
@@ -25,5 +25,15 @@ test('vec2: length() should return correct values', (t) => {
|
|
|
25
25
|
const length5 = length(vec5)
|
|
26
26
|
nearlyEqual(t, length5, 2.23606, EPS)
|
|
27
27
|
|
|
28
|
+
// huge vector
|
|
29
|
+
const vec6 = fromValues(1e200, 1e200)
|
|
30
|
+
const length6 = length(vec6)
|
|
31
|
+
nearlyEqual(t, length6, Math.SQRT2 * 1e200, EPS)
|
|
32
|
+
|
|
33
|
+
// tiny vector
|
|
34
|
+
const vec7 = fromValues(1e-200, 1e-200)
|
|
35
|
+
const length7 = length(vec7)
|
|
36
|
+
nearlyEqual(t, length7, Math.SQRT2 * 1e-200, EPS)
|
|
37
|
+
|
|
28
38
|
t.true(true)
|
|
29
39
|
})
|
package/src/maths/vec3/angle.js
CHANGED
|
@@ -15,8 +15,8 @@ const angle = (a, b) => {
|
|
|
15
15
|
const bx = b[0]
|
|
16
16
|
const by = b[1]
|
|
17
17
|
const bz = b[2]
|
|
18
|
-
const mag1 = Math.
|
|
19
|
-
const mag2 = Math.
|
|
18
|
+
const mag1 = Math.hypot(ax, ay, az)
|
|
19
|
+
const mag2 = Math.hypot(bx, by, bz)
|
|
20
20
|
const mag = mag1 * mag2
|
|
21
21
|
const cosine = mag && dot(a, b) / mag
|
|
22
22
|
return Math.acos(Math.min(Math.max(cosine, -1), 1))
|
|
@@ -25,5 +25,22 @@ test('vec3: angle() should return correct values', (t) => {
|
|
|
25
25
|
const angle4 = angle(veca4, vec4)
|
|
26
26
|
nearlyEqual(t, angle4, 3.14159, EPS)
|
|
27
27
|
|
|
28
|
+
const vec5a = fromValues(1, 0, 0)
|
|
29
|
+
const vec5b = fromValues(1, 1, 0)
|
|
30
|
+
const angle5 = angle(vec5a, vec5b)
|
|
31
|
+
nearlyEqual(t, angle5, 0.785398, EPS)
|
|
32
|
+
|
|
33
|
+
// tiny values
|
|
34
|
+
const vec6a = fromValues(1, 0, 0)
|
|
35
|
+
const vec6b = fromValues(1e-200, 1e-200, 0)
|
|
36
|
+
const angle6 = angle(vec6a, vec6b)
|
|
37
|
+
nearlyEqual(t, angle6, 0.785398, EPS)
|
|
38
|
+
|
|
39
|
+
// huge values
|
|
40
|
+
const vec7a = fromValues(1, 0, 0)
|
|
41
|
+
const vec7b = fromValues(1e200, 1e200, 0)
|
|
42
|
+
const angle7 = angle(vec7a, vec7b)
|
|
43
|
+
nearlyEqual(t, angle7, 0.785398, EPS)
|
|
44
|
+
|
|
28
45
|
t.true(true)
|
|
29
46
|
})
|
|
@@ -41,5 +41,15 @@ test('vec3: length() should return correct values', (t) => {
|
|
|
41
41
|
const length9 = length(vec9)
|
|
42
42
|
nearlyEqual(t, length9, 3.74165, EPS)
|
|
43
43
|
|
|
44
|
+
// huge vector
|
|
45
|
+
const vec10 = fromValues(1e200, 0, 1e200)
|
|
46
|
+
const length10 = length(vec10)
|
|
47
|
+
nearlyEqual(t, length10, Math.SQRT2 * 1e200, EPS)
|
|
48
|
+
|
|
49
|
+
// tiny vector
|
|
50
|
+
const vec11 = fromValues(1e-200, 0, 1e-200)
|
|
51
|
+
const length11 = length(vec11)
|
|
52
|
+
nearlyEqual(t, length11, Math.SQRT2 * 1e-200, EPS)
|
|
53
|
+
|
|
44
54
|
t.true(true)
|
|
45
55
|
})
|
|
@@ -2,7 +2,6 @@ const flatten = require('../utils/flatten')
|
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
const vec3 = require('../maths/vec3')
|
|
5
|
-
const mat4 = require('../maths/mat4')
|
|
6
5
|
|
|
7
6
|
const geom2 = require('../geometries/geom2')
|
|
8
7
|
const geom3 = require('../geometries/geom3')
|
|
@@ -12,133 +11,86 @@ const poly3 = require('../geometries/poly3')
|
|
|
12
11
|
const cache = new WeakMap()
|
|
13
12
|
|
|
14
13
|
/*
|
|
15
|
-
* Measure the min and max bounds of the given (path2) geometry.
|
|
16
|
-
* @return {Array[]} the min and max bounds for the geometry
|
|
14
|
+
* Measure the min and max bounds of the given (path2) geometry.
|
|
15
|
+
* @return {Array[]} the min and max bounds for the geometry
|
|
17
16
|
*/
|
|
18
|
-
const
|
|
19
|
-
let boundingBox = cache.get(
|
|
17
|
+
const measureBoundingBoxOfPath2 = (geometry) => {
|
|
18
|
+
let boundingBox = cache.get(geometry)
|
|
20
19
|
if (boundingBox) return boundingBox
|
|
21
20
|
|
|
21
|
+
const points = path2.toPoints(geometry)
|
|
22
|
+
|
|
22
23
|
let minpoint
|
|
23
24
|
if (points.length === 0) {
|
|
24
25
|
minpoint = vec2.create()
|
|
25
26
|
} else {
|
|
26
27
|
minpoint = vec2.clone(points[0])
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
let maxpoint = vec2.clone(minpoint)
|
|
29
30
|
|
|
30
31
|
points.forEach((point) => {
|
|
31
32
|
vec2.min(minpoint, minpoint, point)
|
|
32
33
|
vec2.max(maxpoint, maxpoint, point)
|
|
33
34
|
})
|
|
35
|
+
minpoint = [minpoint[0], minpoint[1], 0]
|
|
36
|
+
maxpoint = [maxpoint[0], maxpoint[1], 0]
|
|
34
37
|
|
|
35
|
-
boundingBox = [
|
|
36
|
-
|
|
38
|
+
boundingBox = [minpoint, maxpoint]
|
|
39
|
+
|
|
40
|
+
cache.set(geometry, boundingBox)
|
|
37
41
|
|
|
38
42
|
return boundingBox
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
/*
|
|
42
|
-
* Measure the min and max bounds of the given (
|
|
46
|
+
* Measure the min and max bounds of the given (geom2) geometry.
|
|
43
47
|
* @return {Array[]} the min and max bounds for the geometry
|
|
44
48
|
*/
|
|
45
|
-
const
|
|
49
|
+
const measureBoundingBoxOfGeom2 = (geometry) => {
|
|
46
50
|
let boundingBox = cache.get(geometry)
|
|
47
51
|
if (boundingBox) return boundingBox
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
const points = geom2.toPoints(geometry)
|
|
54
|
+
|
|
55
|
+
let minpoint
|
|
56
|
+
if (points.length === 0) {
|
|
57
|
+
minpoint = vec2.create()
|
|
52
58
|
} else {
|
|
53
|
-
|
|
54
|
-
boundingBox = measureBoundingBoxOfPath2Points(path2.toPoints(geometry))
|
|
59
|
+
minpoint = vec2.clone(points[0])
|
|
55
60
|
}
|
|
61
|
+
let maxpoint = vec2.clone(minpoint)
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/*
|
|
63
|
-
* Measure the min and max bounds of the given (geom2) geometry.points/sides.
|
|
64
|
-
* @return {Array[]} the min and max bounds for the geometr.points/sidesy
|
|
65
|
-
*/
|
|
66
|
-
const measureBoundingBoxOfGeom2Points = ({ points, sides }) => {
|
|
67
|
-
const cacheKey = points || sides
|
|
68
|
-
let boundingBox = cache.get(cacheKey)
|
|
69
|
-
if (boundingBox) return boundingBox
|
|
70
|
-
|
|
71
|
-
let minpoint, maxpoint
|
|
72
|
-
|
|
73
|
-
if (points) {
|
|
74
|
-
if (points.length === 0) {
|
|
75
|
-
minpoint = vec2.create()
|
|
76
|
-
} else {
|
|
77
|
-
minpoint = vec2.clone(points[0])
|
|
78
|
-
}
|
|
79
|
-
maxpoint = vec2.clone(minpoint)
|
|
63
|
+
points.forEach((point) => {
|
|
64
|
+
vec2.min(minpoint, minpoint, point)
|
|
65
|
+
vec2.max(maxpoint, maxpoint, point)
|
|
66
|
+
})
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
vec2.max(maxpoint, maxpoint, point)
|
|
84
|
-
})
|
|
85
|
-
} else { // sides
|
|
86
|
-
// to avoid calling costly toPoints, we take advantage of the knowlege how the toPoints works
|
|
87
|
-
if (sides.length === 0) {
|
|
88
|
-
minpoint = vec2.create()
|
|
89
|
-
} else {
|
|
90
|
-
minpoint = vec2.clone(sides[0][0])
|
|
91
|
-
}
|
|
92
|
-
maxpoint = vec2.clone(minpoint)
|
|
93
|
-
|
|
94
|
-
sides.forEach((side) => {
|
|
95
|
-
vec2.min(minpoint, minpoint, side[0])
|
|
96
|
-
vec2.max(maxpoint, maxpoint, side[0])
|
|
97
|
-
})
|
|
98
|
-
}
|
|
68
|
+
minpoint = [minpoint[0], minpoint[1], 0]
|
|
69
|
+
maxpoint = [maxpoint[0], maxpoint[1], 0]
|
|
99
70
|
|
|
100
|
-
boundingBox = [
|
|
71
|
+
boundingBox = [minpoint, maxpoint]
|
|
101
72
|
|
|
102
|
-
cache.set(
|
|
73
|
+
cache.set(geometry, boundingBox)
|
|
103
74
|
|
|
104
75
|
return boundingBox
|
|
105
76
|
}
|
|
106
77
|
|
|
107
78
|
/*
|
|
108
|
-
* Measure the min and max bounds of the given (
|
|
79
|
+
* Measure the min and max bounds of the given (geom3) geometry.
|
|
109
80
|
* @return {Array[]} the min and max bounds for the geometry
|
|
110
81
|
*/
|
|
111
|
-
const
|
|
82
|
+
const measureBoundingBoxOfGeom3 = (geometry) => {
|
|
112
83
|
let boundingBox = cache.get(geometry)
|
|
113
84
|
if (boundingBox) return boundingBox
|
|
114
85
|
|
|
115
|
-
|
|
116
|
-
// get boundingBox of original points and transform it
|
|
117
|
-
boundingBox = transformBoundingBox(measureBoundingBoxOfGeom2Points(geometry), geometry.transforms)
|
|
118
|
-
} else {
|
|
119
|
-
// transform the points and then caclulate the boundingBox
|
|
120
|
-
boundingBox = measureBoundingBoxOfGeom2Points({ points: geom2.toPoints(geometry) })
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
cache.set(geometry, boundingBox)
|
|
124
|
-
|
|
125
|
-
return boundingBox
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/*
|
|
129
|
-
* Measure the min and max bounds of the given (geom3) geometry.polygons.
|
|
130
|
-
* @return {Array[]} the min and max bounds for the geometry.polygons
|
|
131
|
-
*/
|
|
132
|
-
const measureBoundingBoxOfGeom3Polygons = (polygons) => {
|
|
133
|
-
let boundingBox = cache.get(polygons)
|
|
134
|
-
if (boundingBox) return boundingBox
|
|
86
|
+
const polygons = geom3.toPolygons(geometry)
|
|
135
87
|
|
|
136
|
-
|
|
88
|
+
let minpoint = vec3.create()
|
|
137
89
|
if (polygons.length > 0) {
|
|
138
90
|
const points = poly3.toPoints(polygons[0])
|
|
139
91
|
vec3.copy(minpoint, points[0])
|
|
140
92
|
}
|
|
141
|
-
|
|
93
|
+
let maxpoint = vec3.clone(minpoint)
|
|
142
94
|
|
|
143
95
|
polygons.forEach((polygon) => {
|
|
144
96
|
poly3.toPoints(polygon).forEach((point) => {
|
|
@@ -147,41 +99,16 @@ const measureBoundingBoxOfGeom3Polygons = (polygons) => {
|
|
|
147
99
|
})
|
|
148
100
|
})
|
|
149
101
|
|
|
150
|
-
|
|
102
|
+
minpoint = [minpoint[0], minpoint[1], minpoint[2]]
|
|
103
|
+
maxpoint = [maxpoint[0], maxpoint[1], maxpoint[2]]
|
|
151
104
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return boundingBox
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/*
|
|
158
|
-
* Measure the min and max bounds of the given (geom3) geometry.
|
|
159
|
-
* @return {Array[]} the min and max bounds for the geometry
|
|
160
|
-
*/
|
|
161
|
-
const measureBoundingBoxOfGeom3 = (geometry) => {
|
|
162
|
-
let boundingBox = cache.get(geometry)
|
|
163
|
-
if (boundingBox) return boundingBox
|
|
164
|
-
|
|
165
|
-
if (mat4.isOnlyTransformScale(geometry.transforms)) {
|
|
166
|
-
// get boundingBox of original points and transform it
|
|
167
|
-
boundingBox = transformBoundingBox(measureBoundingBoxOfGeom3Polygons(geometry.polygons), geometry.transforms)
|
|
168
|
-
} else {
|
|
169
|
-
// transform the points and then caclulate the boundingBox
|
|
170
|
-
boundingBox = measureBoundingBoxOfGeom3Polygons(geom3.toPolygons(geometry))
|
|
171
|
-
}
|
|
105
|
+
boundingBox = [minpoint, maxpoint]
|
|
172
106
|
|
|
173
107
|
cache.set(geometry, boundingBox)
|
|
174
108
|
|
|
175
109
|
return boundingBox
|
|
176
110
|
}
|
|
177
111
|
|
|
178
|
-
const transformBoundingBox = (boundingBox, transforms) => {
|
|
179
|
-
if (transforms && !mat4.isIdentity(transforms)) {
|
|
180
|
-
return [vec3.transform(vec3.create(), boundingBox[0], transforms), vec3.transform(vec3.create(), boundingBox[1], transforms)]
|
|
181
|
-
}
|
|
182
|
-
return boundingBox
|
|
183
|
-
}
|
|
184
|
-
|
|
185
112
|
/**
|
|
186
113
|
* Measure the min and max bounds of the given geometries.
|
|
187
114
|
* @param {...Object} geometries - the geometries to measure
|
|
@@ -204,15 +131,4 @@ const measureBoundingBox = (...geometries) => {
|
|
|
204
131
|
return results.length === 1 ? results[0] : results
|
|
205
132
|
}
|
|
206
133
|
|
|
207
|
-
/**
|
|
208
|
-
* Shortcut for geometries that are complex but have a fast way to calculate bounding box.
|
|
209
|
-
* Ellipsoid, or cylinder can provide boundingBox that pre-calculated based on parameters without traversing points.
|
|
210
|
-
*
|
|
211
|
-
* Another option is to calculate boundingBox durint toPoints (so the boundingBox could be calculated during transform)
|
|
212
|
-
*
|
|
213
|
-
* NOTE: It seems that measureBoundingBox is used all over the place, and it would be wise to allow
|
|
214
|
-
* shortcuts for calculating it, as default implementation goes through all points in geometry which is bound to be slow.
|
|
215
|
-
*/
|
|
216
|
-
measureBoundingBox.setCache = (geometry, boundingBox) => cache.set(geometry, boundingBox)
|
|
217
|
-
|
|
218
134
|
module.exports = measureBoundingBox
|
|
@@ -4,6 +4,8 @@ const { geom2, geom3, path2 } = require('../geometries')
|
|
|
4
4
|
|
|
5
5
|
const { line, rectangle, cuboid } = require('../primitives')
|
|
6
6
|
|
|
7
|
+
const { mirror } = require('../operations/transforms')
|
|
8
|
+
|
|
7
9
|
const { measureBoundingBox } = require('./index')
|
|
8
10
|
|
|
9
11
|
test('measureBoundingBox (single objects)', (t) => {
|
|
@@ -56,3 +58,9 @@ test('measureBoundingBox (multiple objects)', (t) => {
|
|
|
56
58
|
allbounds = measureBoundingBox(aline, arect, acube, o)
|
|
57
59
|
t.deepEqual(allbounds, [[[10, 10, 0], [15, 15, 0]], [[-5, -10, 0], [5, 10, 0]], [[-1, -1, -1], [1, 1, 1]], [[0, 0, 0], [0, 0, 0]]])
|
|
58
60
|
})
|
|
61
|
+
|
|
62
|
+
test('measureBoundingBox invert', (t) => {
|
|
63
|
+
const acube = mirror({}, cuboid())
|
|
64
|
+
const cbounds = measureBoundingBox(acube)
|
|
65
|
+
t.deepEqual(cbounds, [[-1, -1, -1], [1, 1, 1]])
|
|
66
|
+
})
|
|
@@ -8,25 +8,19 @@ const measureBoundingBox = require('./measureBoundingBox')
|
|
|
8
8
|
* Measure the epsilon of the given (path2) geometry.
|
|
9
9
|
* @return {Number} the epsilon (precision) of the geometry
|
|
10
10
|
*/
|
|
11
|
-
const measureEpsilonOfPath2 = (geometry) =>
|
|
12
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
13
|
-
}
|
|
11
|
+
const measureEpsilonOfPath2 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
14
12
|
|
|
15
13
|
/*
|
|
16
14
|
* Measure the epsilon of the given (geom2) geometry.
|
|
17
15
|
* @return {Number} the epsilon (precision) of the geometry
|
|
18
16
|
*/
|
|
19
|
-
const measureEpsilonOfGeom2 = (geometry) =>
|
|
20
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
21
|
-
}
|
|
17
|
+
const measureEpsilonOfGeom2 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
22
18
|
|
|
23
19
|
/*
|
|
24
20
|
* Measure the epsilon of the given (geom3) geometry.
|
|
25
21
|
* @return {Float} the epsilon (precision) of the geometry
|
|
26
22
|
*/
|
|
27
|
-
const measureEpsilonOfGeom3 = (geometry) =>
|
|
28
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 3)
|
|
29
|
-
}
|
|
23
|
+
const measureEpsilonOfGeom3 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 3)
|
|
30
24
|
|
|
31
25
|
/**
|
|
32
26
|
* Measure the epsilon of the given geometries.
|
|
@@ -309,7 +309,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
309
309
|
const polygon = poly3.fromPointsAndPlane(vertices3d, plane) // TODO support shared
|
|
310
310
|
|
|
311
311
|
// if we let empty polygon out, next retesselate will crash
|
|
312
|
-
if(polygon.vertices.length) destpolygons.push(polygon)
|
|
312
|
+
if (polygon.vertices.length) destpolygons.push(polygon)
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
} // if(yindex > 0)
|
|
@@ -10,9 +10,7 @@ const reTesselateCoplanarPolygons = require('./reTesselateCoplanarPolygons')
|
|
|
10
10
|
const NEPS = 1e-13
|
|
11
11
|
|
|
12
12
|
// Compare two normals (unit vectors) for equality.
|
|
13
|
-
const aboutEqualNormals = (a, b) =>
|
|
14
|
-
return (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
|
|
15
|
-
}
|
|
13
|
+
const aboutEqualNormals = (a, b) => (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
|
|
16
14
|
|
|
17
15
|
const coplanar = (plane1, plane2) => {
|
|
18
16
|
// expect the same distance from the origin, within tolerance
|
|
@@ -20,7 +20,6 @@ const splitPolygonByPlane = require('./splitPolygonByPlane')
|
|
|
20
20
|
// remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
|
|
21
21
|
// since they are no longer intact.
|
|
22
22
|
class PolygonTreeNode {
|
|
23
|
-
|
|
24
23
|
// constructor creates the root node
|
|
25
24
|
constructor () {
|
|
26
25
|
this.parent = null
|
|
@@ -10,6 +10,8 @@ const expandPath2 = require('./expandPath2')
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Expand the given geometry using the given options.
|
|
13
|
+
* Both interal and external space is expanded for 2D and 3D shapes.
|
|
14
|
+
*
|
|
13
15
|
* Note: Contract is expand using a negative delta.
|
|
14
16
|
* @param {Object} options - options for expand
|
|
15
17
|
* @param {Number} [options.delta=1] - delta (+/-) of expansion
|
|
@@ -120,7 +120,7 @@ test('expand: expanding of a geom3 produces expected changes to polygons', (t) =
|
|
|
120
120
|
const geometry2 = sphere({ radius: 5, segments: 8 })
|
|
121
121
|
const obs2 = expand({ delta: 5 }, geometry2)
|
|
122
122
|
const pts2 = geom3.toPoints(obs2)
|
|
123
|
-
t.is(pts2.length,
|
|
123
|
+
t.is(pts2.length, 2065)
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
test('expand (options): offsetting of a complex geom2 produces expected offset geom2', (t) => {
|
|
@@ -8,6 +8,7 @@ const offsetPath2 = require('./offsetPath2')
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Create offset geometry from the given geometry using the given options.
|
|
11
|
+
* Offsets from internal and external space are created.
|
|
11
12
|
* @param {Object} options - options for offset
|
|
12
13
|
* @param {Float} [options.delta=1] - delta of offset (+ to exterior, - from interior)
|
|
13
14
|
* @param {String} [options.corners='edge'] - type of corner to create after offseting; edge, chamfer, round
|
|
@@ -7,7 +7,7 @@ const extrudeLinearGeom2 = require('./extrudeLinearGeom2')
|
|
|
7
7
|
/**
|
|
8
8
|
* Extrude the given geometry in an upward linear direction using the given options.
|
|
9
9
|
* @param {Object} options - options for extrude
|
|
10
|
-
* @param {
|
|
10
|
+
* @param {Number} [options.height=1] the height of the extrusion
|
|
11
11
|
* @param {Number} [options.twistAngle=0] the final rotation (RADIANS) about the origin of the shape (if any)
|
|
12
12
|
* @param {Integer} [options.twistSteps=1] the resolution of the twist about the axis (if any)
|
|
13
13
|
* @param {...Object} geometry - the list of geometry to extrude
|
|
@@ -17,7 +17,8 @@ const extrudeRectangularGeom2 = require('./extrudeRectangularGeom2')
|
|
|
17
17
|
* @alias module:modeling/extrusions.extrudeRectangular
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
|
-
* let mywalls = extrudeRectangular({
|
|
20
|
+
* let mywalls = extrudeRectangular({size: 1, height: 3}, square({size: 20}))
|
|
21
|
+
* let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle: Math.PI}, square({size: 20}))
|
|
21
22
|
*/
|
|
22
23
|
const extrudeRectangular = (options, ...objects) => {
|
|
23
24
|
const defaults = {
|
|
@@ -107,10 +107,14 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
107
107
|
[[7, -4.898587196589413e-16, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [9.184850993605148e-16, 7, -8]],
|
|
108
108
|
[[7, 4.898587196589413e-16, 8], [7, -4.898587196589413e-16, -8], [9.184850993605148e-16, 7, -8]],
|
|
109
109
|
[[7, 4.898587196589413e-16, 8], [9.184850993605148e-16, 7, -8], [-6.123233995736767e-17, 7, 8]],
|
|
110
|
-
[
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
[
|
|
111
|
+
[-4.898587196589414e-16, 0, 8], [-6.123233995736777e-17, 6.999999999999999, 8],
|
|
112
|
+
[9.18485099360515e-16, 7.000000000000001, -8], [4.898587196589413e-16, 0, -8]
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
[7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8],
|
|
116
|
+
[0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8]
|
|
117
|
+
]
|
|
114
118
|
]
|
|
115
119
|
t.is(pts.length, 6)
|
|
116
120
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -133,12 +137,16 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
133
137
|
[[0.7071067811865472, 0.7071067811865478, 8], [1.414213562373095, 1.4142135623730951, 4], [-1.2246467991473532e-16, 2, 4]],
|
|
134
138
|
[[0.7071067811865472, 0.7071067811865478, 8], [-1.2246467991473532e-16, 2, 4], [-4.286263797015736e-16, 1, 8]],
|
|
135
139
|
[[-3.4638242249419727e-16, 3.4638242249419736e-16, 8], [0.7071067811865472, 0.7071067811865478, 8], [-4.286263797015736e-16, 1, 8]],
|
|
136
|
-
[
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
[
|
|
141
|
+
[-4.898587196589412e-16, 0, 8], [-4.2862637970157346e-16, 0.9999999999999998, 8],
|
|
142
|
+
[-1.2246467991473475e-16, 2.0000000000000004, 3.9999999999999964], [5.510910596163092e-16, 1.0000000000000004, -8],
|
|
143
|
+
[4.898587196589414e-16, 0, -8]
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
[0, -4.898587196589413e-16, -8.000000000000002], [1.0000000000000027, -4.898587196589413e-16, -8.000000000000002],
|
|
147
|
+
[2.000000000000001, 2.449293598294702e-16, 3.9999999999999964], [1.0000000000000004, 4.898587196589411e-16, 8],
|
|
148
|
+
[0, 4.898587196589411e-16, 8]
|
|
149
|
+
]
|
|
142
150
|
]
|
|
143
151
|
t.is(pts.length, 14)
|
|
144
152
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -722,7 +722,7 @@ class QuickHull {
|
|
|
722
722
|
for (let i = 0; i < this.newFaces.length; i += 1) {
|
|
723
723
|
const face = this.newFaces[i]
|
|
724
724
|
if (face.mark === VISIBLE) {
|
|
725
|
-
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {}
|
|
725
|
+
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {} // eslint-disable-line no-empty
|
|
726
726
|
}
|
|
727
727
|
}
|
|
728
728
|
|
|
@@ -733,7 +733,7 @@ class QuickHull {
|
|
|
733
733
|
const face = this.newFaces[i]
|
|
734
734
|
if (face.mark === NON_CONVEX) {
|
|
735
735
|
face.mark = VISIBLE
|
|
736
|
-
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {}
|
|
736
|
+
while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {} // eslint-disable-line no-empty
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
739
|
|
|
@@ -99,9 +99,7 @@ const splitPolygon = (openedge, polygon, eps) => {
|
|
|
99
99
|
/*
|
|
100
100
|
* TBD This should be part of vec3.
|
|
101
101
|
*/
|
|
102
|
-
const almostEquals = (eps, v1, v2) =>
|
|
103
|
-
return (Math.abs(v1[0] - v2[0]) <= eps && Math.abs(v1[1] - v2[1]) <= eps && Math.abs(v1[2] - v2[2]) <= eps)
|
|
104
|
-
}
|
|
102
|
+
const almostEquals = (eps, v1, v2) => (Math.abs(v1[0] - v2[0]) <= eps && Math.abs(v1[1] - v2[1]) <= eps && Math.abs(v1[2] - v2[2]) <= eps)
|
|
105
103
|
|
|
106
104
|
const enclosedEdge = (openedge, edge, eps) => {
|
|
107
105
|
if (openedge.distance < edge.distance) {
|
|
@@ -15,16 +15,11 @@ const repairTjunctions = require('./repairTjunctions')
|
|
|
15
15
|
|
|
16
16
|
/*
|
|
17
17
|
*/
|
|
18
|
-
const generalizePath2 = (options, geometry) =>
|
|
19
|
-
return geometry
|
|
20
|
-
}
|
|
21
|
-
|
|
18
|
+
const generalizePath2 = (options, geometry) => geometry
|
|
22
19
|
|
|
23
20
|
/*
|
|
24
21
|
*/
|
|
25
|
-
const generalizeGeom2 = (options, geometry) =>
|
|
26
|
-
return geometry
|
|
27
|
-
}
|
|
22
|
+
const generalizeGeom2 = (options, geometry) => geometry
|
|
28
23
|
|
|
29
24
|
/*
|
|
30
25
|
*/
|
|
@@ -78,6 +73,7 @@ const generalizeGeom3 = (options, geometry) => {
|
|
|
78
73
|
* @param {Boolean} [options.simplify=false] the geometries should be simplified
|
|
79
74
|
* @param {Boolean} [options.triangulate=false] the geometries should be triangulated
|
|
80
75
|
* @param {Boolean} [options.repair=false] the geometries should be repaired
|
|
76
|
+
* @param {...Object} geometries - the geometries to generalize
|
|
81
77
|
* @return {Object|Array} the modified geometry, or a list of modified geometries
|
|
82
78
|
* @alias module:modeling/modifiers.generalize
|
|
83
79
|
*/
|
|
@@ -3,7 +3,7 @@ const vec3 = require('../../maths/vec3')
|
|
|
3
3
|
const poly3 = require('../../geometries/poly3')
|
|
4
4
|
|
|
5
5
|
const isValidPoly3 = (epsilon, polygon) => {
|
|
6
|
-
const area = poly3.measureArea(polygon)
|
|
6
|
+
const area = Math.abs(poly3.measureArea(polygon))
|
|
7
7
|
return (Number.isFinite(area) && area > epsilon)
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ const snapPolygons = (epsilon, polygons) => {
|
|
|
17
17
|
const newvertices = []
|
|
18
18
|
for (let i = 0; i < snapvertices.length; i++) {
|
|
19
19
|
const j = (i + 1) % snapvertices.length
|
|
20
|
-
if (!
|
|
20
|
+
if (!vec3.equals(snapvertices[i], snapvertices[j])) newvertices.push(snapvertices[i])
|
|
21
21
|
}
|
|
22
22
|
const newpolygon = poly3.create(newvertices)
|
|
23
23
|
if (polygon.color) newpolygon.color = polygon.color
|