@jscad/modeling 2.9.4 → 2.10.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 +50 -0
- package/README.md +12 -2
- package/dist/jscad-modeling.min.js +148 -151
- package/package.json +2 -2
- package/src/colors/colorize.d.ts +6 -5
- package/src/geometries/geom2/type.d.ts +3 -2
- package/src/geometries/geom3/type.d.ts +3 -2
- package/src/geometries/path2/appendArc.js +6 -5
- package/src/geometries/path2/appendArc.test.js +3 -1
- package/src/geometries/path2/appendBezier.js +2 -1
- package/src/geometries/path2/appendPoints.js +3 -12
- package/src/geometries/path2/appendPoints.test.js +16 -0
- package/src/geometries/path2/concat.js +9 -8
- package/src/geometries/path2/concat.test.js +13 -7
- package/src/geometries/path2/transform.js +1 -1
- package/src/geometries/path2/type.d.ts +3 -2
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
- package/src/geometries/poly3/measureBoundingSphere.js +46 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
- package/src/geometries/poly3/type.d.ts +3 -2
- package/src/geometries/types.d.ts +4 -2
- package/src/index.d.ts +1 -0
- package/src/maths/constants.js +10 -0
- package/src/maths/mat4/fromRotation.js +10 -8
- package/src/maths/mat4/fromTaitBryanRotation.js +9 -7
- package/src/maths/mat4/fromXRotation.js +5 -3
- package/src/maths/mat4/fromYRotation.js +5 -3
- package/src/maths/mat4/fromZRotation.js +5 -3
- package/src/maths/mat4/invert.test.js +5 -2
- package/src/maths/mat4/isOnlyTransformScale.js +1 -1
- package/src/maths/mat4/isOnlyTransformScale.test.js +3 -1
- package/src/maths/mat4/rotate.js +9 -5
- package/src/maths/mat4/rotateX.js +4 -2
- package/src/maths/mat4/rotateY.js +4 -2
- package/src/maths/mat4/rotateZ.js +4 -2
- package/src/maths/mat4/translate.test.js +2 -3
- package/src/maths/rotation.test.js +5 -2
- package/src/maths/utils/index.d.ts +1 -0
- package/src/maths/utils/index.js +2 -0
- package/src/{utils → maths/utils}/trigonometry.d.ts +0 -0
- package/src/{utils → maths/utils}/trigonometry.js +7 -7
- package/src/{utils → maths/utils}/trigonometry.test.js +10 -8
- package/src/maths/vec2/distance.js +1 -1
- package/src/maths/vec2/fromAngleDegrees.js +1 -1
- package/src/maths/vec2/fromAngleRadians.js +4 -2
- package/src/maths/vec2/fromAngleRadians.test.js +4 -1
- package/src/maths/vec2/length.js +1 -1
- package/src/maths/vec2/length.test.js +0 -10
- package/src/maths/vec2/normal.js +3 -1
- package/src/maths/vec2/rotate.test.js +4 -1
- package/src/maths/vec3/angle.js +2 -2
- package/src/maths/vec3/angle.test.js +0 -12
- package/src/maths/vec3/distance.js +1 -1
- package/src/maths/vec3/length.js +1 -1
- package/src/maths/vec3/length.test.js +0 -10
- package/src/maths/vec3/rotateX.test.js +4 -1
- package/src/maths/vec3/rotateY.test.js +4 -1
- package/src/maths/vec3/rotateZ.test.js +4 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
- package/src/operations/expansions/expand.test.js +3 -1
- package/src/operations/expansions/expandShell.js +4 -4
- package/src/operations/expansions/offsetFromPoints.js +2 -2
- package/src/operations/extrusions/extrudeFromSlices.test.js +3 -2
- package/src/operations/extrusions/extrudeLinear.test.js +5 -3
- package/src/operations/extrusions/extrudeRectangular.js +1 -1
- package/src/operations/extrusions/extrudeRectangular.test.js +5 -3
- package/src/operations/extrusions/extrudeRotate.js +14 -13
- package/src/operations/extrusions/extrudeRotate.test.js +49 -47
- package/src/operations/extrusions/project.test.js +2 -2
- package/src/operations/extrusions/slice/calculatePlane.test.js +3 -2
- package/src/operations/extrusions/slice/index.js +2 -0
- package/src/operations/extrusions/slice/repair.js +1 -1
- package/src/operations/modifiers/generalize.d.ts +12 -0
- package/src/operations/modifiers/generalize.test.js +3 -1
- package/src/operations/modifiers/index.d.ts +2 -0
- package/src/operations/modifiers/insertTjunctions.js +34 -35
- package/src/operations/modifiers/snap.d.ts +6 -0
- package/src/operations/modifiers/snap.test.js +5 -3
- package/src/operations/transforms/rotate.js +1 -1
- package/src/operations/transforms/rotate.test.js +13 -11
- package/src/operations/transforms/transform.js +1 -1
- package/src/primitives/arc.js +8 -8
- package/src/primitives/arc.test.js +9 -8
- package/src/primitives/circle.js +4 -2
- package/src/primitives/circle.test.js +12 -4
- package/src/primitives/cylinderElliptic.js +13 -11
- package/src/primitives/cylinderElliptic.test.js +9 -2
- package/src/primitives/ellipse.js +11 -11
- package/src/primitives/ellipse.test.js +12 -4
- package/src/primitives/ellipsoid.js +4 -3
- package/src/primitives/geodesicSphere.js +3 -2
- package/src/primitives/roundedCuboid.js +10 -8
- package/src/primitives/roundedCylinder.js +4 -4
- package/src/primitives/roundedRectangle.js +5 -5
- package/src/primitives/star.js +3 -2
- package/src/primitives/torus.js +4 -2
- package/src/primitives/torus.test.js +11 -5
- package/src/primitives/triangle.test.js +2 -1
- package/src/utils/degToRad.test.js +5 -5
- package/src/utils/index.d.ts +0 -1
- package/src/utils/index.js +1 -3
- package/src/utils/radToDeg.test.js +6 -6
- package/src/utils/radiusToSegments.js +6 -4
- package/src/utils/radiusToSegments.test.js +5 -3
- package/src/maths/mat4/constants.d.ts +0 -1
- package/src/maths/mat4/constants.js +0 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { TAU } = require('../../maths/constants')
|
|
1
2
|
const mat4 = require('../../maths/mat4')
|
|
2
3
|
|
|
3
4
|
const { mirrorX } = require('../transforms/mirror')
|
|
@@ -12,7 +13,7 @@ const extrudeFromSlices = require('./extrudeFromSlices')
|
|
|
12
13
|
* Rotate extrude the given geometry using the given options.
|
|
13
14
|
*
|
|
14
15
|
* @param {Object} options - options for extrusion
|
|
15
|
-
* @param {Number} [options.angle=
|
|
16
|
+
* @param {Number} [options.angle=TAU] - angle of the extrusion (RADIANS)
|
|
16
17
|
* @param {Number} [options.startAngle=0] - start angle of the extrusion (RADIANS)
|
|
17
18
|
* @param {String} [options.overflow='cap'] - what to do with points outside of bounds (+ / - x) :
|
|
18
19
|
* defaults to capping those points to 0 (only supported behaviour for now)
|
|
@@ -22,24 +23,24 @@ const extrudeFromSlices = require('./extrudeFromSlices')
|
|
|
22
23
|
* @alias module:modeling/extrusions.extrudeRotate
|
|
23
24
|
*
|
|
24
25
|
* @example
|
|
25
|
-
* const myshape = extrudeRotate({segments: 8, angle:
|
|
26
|
+
* const myshape = extrudeRotate({segments: 8, angle: TAU / 2}, circle({size: 3, center: [4, 0]}))
|
|
26
27
|
*/
|
|
27
28
|
const extrudeRotate = (options, geometry) => {
|
|
28
29
|
const defaults = {
|
|
29
30
|
segments: 12,
|
|
30
31
|
startAngle: 0,
|
|
31
|
-
angle:
|
|
32
|
+
angle: TAU,
|
|
32
33
|
overflow: 'cap'
|
|
33
34
|
}
|
|
34
35
|
let { segments, startAngle, angle, overflow } = Object.assign({}, defaults, options)
|
|
35
36
|
|
|
36
37
|
if (segments < 3) throw new Error('segments must be greater then 3')
|
|
37
38
|
|
|
38
|
-
startAngle = Math.abs(startAngle) >
|
|
39
|
-
angle = Math.abs(angle) >
|
|
39
|
+
startAngle = Math.abs(startAngle) > TAU ? startAngle % TAU : startAngle
|
|
40
|
+
angle = Math.abs(angle) > TAU ? angle % TAU : angle
|
|
40
41
|
|
|
41
42
|
let endAngle = startAngle + angle
|
|
42
|
-
endAngle = Math.abs(endAngle) >
|
|
43
|
+
endAngle = Math.abs(endAngle) > TAU ? endAngle % TAU : endAngle
|
|
43
44
|
|
|
44
45
|
if (endAngle < startAngle) {
|
|
45
46
|
const x = startAngle
|
|
@@ -47,11 +48,11 @@ const extrudeRotate = (options, geometry) => {
|
|
|
47
48
|
endAngle = x
|
|
48
49
|
}
|
|
49
50
|
let totalRotation = endAngle - startAngle
|
|
50
|
-
if (totalRotation <= 0.0) totalRotation =
|
|
51
|
+
if (totalRotation <= 0.0) totalRotation = TAU
|
|
51
52
|
|
|
52
|
-
if (Math.abs(totalRotation) <
|
|
53
|
+
if (Math.abs(totalRotation) < TAU) {
|
|
53
54
|
// adjust the segments to achieve the total rotation requested
|
|
54
|
-
const anglePerSegment =
|
|
55
|
+
const anglePerSegment = TAU / segments
|
|
55
56
|
segments = Math.floor(Math.abs(totalRotation) / anglePerSegment)
|
|
56
57
|
if (Math.abs(totalRotation) > (segments * anglePerSegment)) segments++
|
|
57
58
|
}
|
|
@@ -108,18 +109,18 @@ const extrudeRotate = (options, geometry) => {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
const rotationPerSlice = totalRotation / segments
|
|
111
|
-
const isCapped = Math.abs(totalRotation) <
|
|
112
|
+
const isCapped = Math.abs(totalRotation) < TAU
|
|
112
113
|
const baseSlice = slice.fromSides(geom2.toSides(geometry))
|
|
113
114
|
slice.reverse(baseSlice, baseSlice)
|
|
114
115
|
|
|
115
116
|
const matrix = mat4.create()
|
|
116
117
|
const createSlice = (progress, index, base) => {
|
|
117
118
|
let Zrotation = rotationPerSlice * index + startAngle
|
|
118
|
-
// fix rounding error when rotating
|
|
119
|
-
if (totalRotation ===
|
|
119
|
+
// fix rounding error when rotating TAU radians
|
|
120
|
+
if (totalRotation === TAU && index === segments) {
|
|
120
121
|
Zrotation = startAngle
|
|
121
122
|
}
|
|
122
|
-
mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(),
|
|
123
|
+
mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(), TAU / 4))
|
|
123
124
|
|
|
124
125
|
return slice.transform(matrix, base)
|
|
125
126
|
}
|
|
@@ -2,6 +2,8 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const { comparePoints, comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
+
const { TAU } = require('../../maths/constants')
|
|
6
|
+
|
|
5
7
|
const { geom2, geom3 } = require('../../geometries')
|
|
6
8
|
|
|
7
9
|
const { extrudeRotate } = require('./index')
|
|
@@ -19,21 +21,21 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
|
|
|
19
21
|
const geometry2 = geom2.fromPoints([[10, 8], [10, -8], [26, -8], [26, 8]])
|
|
20
22
|
|
|
21
23
|
// test angle
|
|
22
|
-
let geometry3 = extrudeRotate({ segments: 4, angle:
|
|
24
|
+
let geometry3 = extrudeRotate({ segments: 4, angle: TAU / 8 }, geometry2)
|
|
23
25
|
let pts = geom3.toPoints(geometry3)
|
|
24
26
|
const exp = [
|
|
25
|
-
[[10,
|
|
26
|
-
[[10,
|
|
27
|
-
[[10,
|
|
28
|
-
[[10,
|
|
29
|
-
[[26,
|
|
30
|
-
[[26,
|
|
31
|
-
[[26,
|
|
32
|
-
[[26,
|
|
33
|
-
[[7.
|
|
34
|
-
[[18.
|
|
35
|
-
[[26,
|
|
36
|
-
[[10,
|
|
27
|
+
[[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
|
|
28
|
+
[[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
|
|
29
|
+
[[10, 0, -8], [10, 0, 8], [7.0710678118654755, 7.071067811865475, 8]],
|
|
30
|
+
[[10, 0, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
31
|
+
[[26, 0, -8], [10, 0, -8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
32
|
+
[[26, 0, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
|
|
33
|
+
[[26, 0, 8], [26, 0, -8], [18.38477631085024, 18.384776310850235, -8]],
|
|
34
|
+
[[26, 0, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
|
|
35
|
+
[[7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8]],
|
|
36
|
+
[[18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
37
|
+
[[26, 0, 8], [10, 0, 8], [10, 0, -8]],
|
|
38
|
+
[[10, 0, -8], [26, 0, -8], [26, 0, 8]]
|
|
37
39
|
]
|
|
38
40
|
t.notThrows(() => geom3.validate(geometry3))
|
|
39
41
|
t.is(pts.length, 12)
|
|
@@ -54,7 +56,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
54
56
|
const geometry2 = geom2.fromPoints([[10, 8], [10, -8], [26, -8], [26, 8]])
|
|
55
57
|
|
|
56
58
|
// test startAngle
|
|
57
|
-
let geometry3 = extrudeRotate({ segments: 5, startAngle:
|
|
59
|
+
let geometry3 = extrudeRotate({ segments: 5, startAngle: TAU / 8 }, geometry2)
|
|
58
60
|
let pts = geom3.toPoints(geometry3)
|
|
59
61
|
let exp = [
|
|
60
62
|
[7.0710678118654755, 7.071067811865475, 8],
|
|
@@ -65,7 +67,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
65
67
|
t.is(pts.length, 40)
|
|
66
68
|
t.true(comparePoints(pts[0], exp))
|
|
67
69
|
|
|
68
|
-
geometry3 = extrudeRotate({ segments: 5, startAngle:
|
|
70
|
+
geometry3 = extrudeRotate({ segments: 5, startAngle: -TAU / 8 }, geometry2)
|
|
69
71
|
pts = geom3.toPoints(geometry3)
|
|
70
72
|
exp = [
|
|
71
73
|
[7.0710678118654755, -7.071067811865475, 8],
|
|
@@ -95,14 +97,14 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
|
|
|
95
97
|
geometry2 = geom2.fromPoints([[0, 0], [2, 1], [1, 2], [1, 3], [3, 4], [0, 5]])
|
|
96
98
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
97
99
|
pts = geom3.toPoints(geometry3)
|
|
98
|
-
t.notThrows
|
|
100
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
99
101
|
t.is(pts.length, 64)
|
|
100
102
|
|
|
101
103
|
// test overlapping edges that produce hollow shape
|
|
102
104
|
geometry2 = geom2.fromPoints([[30, 0], [30, 60], [0, 60], [0, 50], [10, 40], [10, 30], [0, 20], [0, 10], [10, 0]])
|
|
103
105
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
104
106
|
pts = geom3.toPoints(geometry3)
|
|
105
|
-
t.notThrows
|
|
107
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
106
108
|
t.is(pts.length, 80)
|
|
107
109
|
})
|
|
108
110
|
|
|
@@ -110,48 +112,48 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
110
112
|
// overlap of Y axis; even number of + and - points
|
|
111
113
|
let geometry = geom2.fromPoints([[-1, 8], [-1, -8], [7, -8], [7, 8]])
|
|
112
114
|
|
|
113
|
-
let obs = extrudeRotate({ segments: 4, angle:
|
|
115
|
+
let obs = extrudeRotate({ segments: 4, angle: TAU / 4 }, geometry)
|
|
114
116
|
let pts = geom3.toPoints(obs)
|
|
115
117
|
let exp = [
|
|
116
|
-
[[0,
|
|
117
|
-
[[7,
|
|
118
|
-
[[7,
|
|
119
|
-
[[7,
|
|
120
|
-
[[
|
|
121
|
-
[[
|
|
122
|
-
[[7,
|
|
123
|
-
[[0,
|
|
118
|
+
[[0, 0, 8], [7, 0, 8], [0, 7, 8]],
|
|
119
|
+
[[7, 0, -8], [0, 0, -8], [0, 7, -8]],
|
|
120
|
+
[[7, 0, 8], [7, 0, -8], [0, 7, -8]],
|
|
121
|
+
[[7, 0, 8], [0, 7, -8], [0, 7, 8]],
|
|
122
|
+
[[0, 0, -8], [0, 0, 8], [0, 7, 8]],
|
|
123
|
+
[[0, 7, 8], [0, 7, -8], [0, 0, -8]],
|
|
124
|
+
[[7, 0, 8], [0, 0, 8], [0, 0, -8]],
|
|
125
|
+
[[0, 0, -8], [7, 0, -8], [7, 0, 8]]
|
|
124
126
|
]
|
|
125
|
-
t.notThrows
|
|
127
|
+
t.notThrows(() => geom3.validate(obs))
|
|
126
128
|
t.is(pts.length, 8)
|
|
127
129
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
128
130
|
|
|
129
131
|
// overlap of Y axis; larger number of - points
|
|
130
132
|
geometry = geom2.fromPoints([[-1, 8], [-2, 4], [-1, -8], [7, -8], [7, 8]])
|
|
131
133
|
|
|
132
|
-
obs = extrudeRotate({ segments: 8, angle:
|
|
134
|
+
obs = extrudeRotate({ segments: 8, angle: TAU / 4 }, geometry)
|
|
133
135
|
pts = geom3.toPoints(obs)
|
|
134
136
|
exp = [
|
|
135
|
-
[[1,
|
|
136
|
-
[[2,
|
|
137
|
-
[[2,
|
|
138
|
-
[[1,
|
|
139
|
-
[[1,
|
|
140
|
-
[[0,
|
|
141
|
-
[[0.
|
|
142
|
-
[[1.
|
|
143
|
-
[[1.
|
|
144
|
-
[[0.
|
|
145
|
-
[[0.
|
|
146
|
-
[[
|
|
147
|
-
[[
|
|
148
|
-
[[
|
|
149
|
-
[[
|
|
150
|
-
[[0,
|
|
151
|
-
[[2,
|
|
152
|
-
[[0,
|
|
137
|
+
[[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
|
|
138
|
+
[[2, 0, 4], [1, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
|
|
139
|
+
[[2, 0, 4], [0.7071067811865476, 0.7071067811865475, -8], [1.4142135623730951, 1.414213562373095, 4]],
|
|
140
|
+
[[1, 0, 8], [2, 0, 4], [1.4142135623730951, 1.414213562373095, 4]],
|
|
141
|
+
[[1, 0, 8], [1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, 8]],
|
|
142
|
+
[[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]],
|
|
143
|
+
[[0.7071067811865476, 0.7071067811865475, -8], [0, 0, -8], [0, 1, -8]],
|
|
144
|
+
[[1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, -8], [0, 1, -8]],
|
|
145
|
+
[[1.4142135623730951, 1.414213562373095, 4], [0, 1, -8], [0, 2, 4]],
|
|
146
|
+
[[0.7071067811865476, 0.7071067811865475, 8], [1.4142135623730951, 1.414213562373095, 4], [0, 2, 4]],
|
|
147
|
+
[[0.7071067811865476, 0.7071067811865475, 8], [0, 2, 4], [0, 1, 8]],
|
|
148
|
+
[[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]],
|
|
149
|
+
[[0, 1, -8], [0, 0, -8], [0, 0, 8]],
|
|
150
|
+
[[0, 0, 8], [0, 1, 8], [0, 2, 4]],
|
|
151
|
+
[[0, 2, 4], [0, 1, -8], [0, 0, 8]],
|
|
152
|
+
[[0, 0, 8], [0, 0, -8], [1, 0, -8]],
|
|
153
|
+
[[2, 0, 4], [1, 0, 8], [0, 0, 8]],
|
|
154
|
+
[[0, 0, 8], [1, 0, -8], [2, 0, 4]]
|
|
153
155
|
]
|
|
154
|
-
t.notThrows
|
|
156
|
+
t.notThrows(() => geom3.validate(obs))
|
|
155
157
|
t.is(pts.length, 18)
|
|
156
158
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
157
159
|
})
|
|
@@ -29,10 +29,10 @@ test('project (defaults)', (t) => {
|
|
|
29
29
|
[0, -5.000013333333333],
|
|
30
30
|
[5.000013333333333, 0],
|
|
31
31
|
[-5.000013333333333, 0],
|
|
32
|
-
[-2.9999933333333333, 0],
|
|
33
|
-
[2.9999933333333333, 0],
|
|
34
32
|
[0, 2.9999933333333333],
|
|
33
|
+
[-2.9999933333333333, 0],
|
|
35
34
|
[0, -2.9999933333333333],
|
|
35
|
+
[2.9999933333333333, 0],
|
|
36
36
|
[0, 5.000013333333333]
|
|
37
37
|
]
|
|
38
38
|
t.true(comparePoints(pts, exp))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../../../maths/constants')
|
|
3
4
|
const { mat4 } = require('../../../maths')
|
|
4
5
|
|
|
5
6
|
const { calculatePlane, fromPoints, transform } = require('./index')
|
|
@@ -15,11 +16,11 @@ test('slice: calculatePlane() returns correct plans for various slices', (t) =>
|
|
|
15
16
|
const plane2 = calculatePlane(slice2)
|
|
16
17
|
t.true(compareVectors(plane2, [0, 0, 1, 0]))
|
|
17
18
|
|
|
18
|
-
const slice3 = transform(mat4.fromXRotation(mat4.create(),
|
|
19
|
+
const slice3 = transform(mat4.fromXRotation(mat4.create(), TAU / 4), slice2)
|
|
19
20
|
const plane3 = calculatePlane(slice3)
|
|
20
21
|
t.true(compareVectors(plane3, [0, -1, 0, 0]))
|
|
21
22
|
|
|
22
|
-
const slice4 = transform(mat4.fromZRotation(mat4.create(),
|
|
23
|
+
const slice4 = transform(mat4.fromZRotation(mat4.create(), TAU / 4), slice3)
|
|
23
24
|
const plane4 = calculatePlane(slice4)
|
|
24
25
|
t.true(compareVectors(plane4, [1, 0, 0, 0]))
|
|
25
26
|
|
|
@@ -40,7 +40,7 @@ const repair = (slice) => {
|
|
|
40
40
|
let bestReplacement
|
|
41
41
|
missingOut.forEach((key2) => {
|
|
42
42
|
const v2 = vertexMap.get(key2)
|
|
43
|
-
const distance =
|
|
43
|
+
const distance = vec3.distance(v1, v2)
|
|
44
44
|
if (distance < bestDistance) {
|
|
45
45
|
bestDistance = distance
|
|
46
46
|
bestReplacement = v2
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Geometry } from '../../geometries/types'
|
|
2
|
+
import RecursiveArray from '../../utils/recursiveArray'
|
|
3
|
+
|
|
4
|
+
export interface GeneralizeOptions {
|
|
5
|
+
snap?: boolean
|
|
6
|
+
simplify?: boolean
|
|
7
|
+
triangulate?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function generalize<T extends Geometry>(options: GeneralizeOptions, geometry: T): T
|
|
11
|
+
export function generalize<T extends Geometry>(options: GeneralizeOptions, ...geometries: RecursiveArray<T>): Array<T>
|
|
12
|
+
export function generalize(options: GeneralizeOptions, ...geometries: RecursiveArray<Geometry>): Array<Geometry>
|
|
@@ -2,6 +2,8 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const { comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
+
const { TAU } = require('../../maths/constants')
|
|
6
|
+
|
|
5
7
|
const { geom3 } = require('../../geometries')
|
|
6
8
|
|
|
7
9
|
const { cuboid } = require('../../primitives')
|
|
@@ -9,7 +11,7 @@ const { cuboid } = require('../../primitives')
|
|
|
9
11
|
const { generalize } = require('./index')
|
|
10
12
|
|
|
11
13
|
test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
|
|
12
|
-
const geometry1 = cuboid({ size: [
|
|
14
|
+
const geometry1 = cuboid({ size: [TAU / 2, TAU / 4, TAU] })
|
|
13
15
|
|
|
14
16
|
// apply no modifications
|
|
15
17
|
let result = generalize({}, geometry1)
|
|
@@ -28,15 +28,15 @@ const addSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, verte
|
|
|
28
28
|
} else {
|
|
29
29
|
sidemap.get(newsidetag).push(newsideobj)
|
|
30
30
|
}
|
|
31
|
-
if (starttag
|
|
32
|
-
vertextag2sidestart
|
|
31
|
+
if (vertextag2sidestart.has(starttag)) {
|
|
32
|
+
vertextag2sidestart.get(starttag).push(newsidetag)
|
|
33
33
|
} else {
|
|
34
|
-
vertextag2sidestart
|
|
34
|
+
vertextag2sidestart.set(starttag, [newsidetag])
|
|
35
35
|
}
|
|
36
|
-
if (endtag
|
|
37
|
-
vertextag2sideend
|
|
36
|
+
if (vertextag2sideend.has(endtag)) {
|
|
37
|
+
vertextag2sideend.get(endtag).push(newsidetag)
|
|
38
38
|
} else {
|
|
39
|
-
vertextag2sideend
|
|
39
|
+
vertextag2sideend.set(endtag, [newsidetag])
|
|
40
40
|
}
|
|
41
41
|
return newsidetag
|
|
42
42
|
}
|
|
@@ -67,18 +67,18 @@ const deleteSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, ve
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// adjust start and end lists
|
|
70
|
-
idx = vertextag2sidestart
|
|
70
|
+
idx = vertextag2sidestart.get(starttag).indexOf(sidetag)
|
|
71
71
|
if (assert && idx < 0) throw new Error('assert failed')
|
|
72
|
-
vertextag2sidestart
|
|
73
|
-
if (vertextag2sidestart
|
|
74
|
-
delete
|
|
72
|
+
vertextag2sidestart.get(starttag).splice(idx, 1)
|
|
73
|
+
if (vertextag2sidestart.get(starttag).length === 0) {
|
|
74
|
+
vertextag2sidestart.delete(starttag)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
idx = vertextag2sideend
|
|
77
|
+
idx = vertextag2sideend.get(endtag).indexOf(sidetag)
|
|
78
78
|
if (assert && idx < 0) throw new Error('assert failed')
|
|
79
|
-
vertextag2sideend
|
|
80
|
-
if (vertextag2sideend
|
|
81
|
-
delete
|
|
79
|
+
vertextag2sideend.get(endtag).splice(idx, 1)
|
|
80
|
+
if (vertextag2sideend.get(endtag).length === 0) {
|
|
81
|
+
vertextag2sideend.delete(endtag)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -158,25 +158,24 @@ const insertTjunctions = (polygons) => {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
if (sidemap.size > 0) {
|
|
161
|
-
// console.log('insertTjunctions',sidemap.size)
|
|
162
161
|
// STEP 2 : create a list of starting sides and ending sides
|
|
163
|
-
const vertextag2sidestart =
|
|
164
|
-
const vertextag2sideend =
|
|
165
|
-
const
|
|
162
|
+
const vertextag2sidestart = new Map()
|
|
163
|
+
const vertextag2sideend = new Map()
|
|
164
|
+
const sidesToCheck = new Map()
|
|
166
165
|
for (const [sidetag, sideobjs] of sidemap) {
|
|
167
|
-
|
|
166
|
+
sidesToCheck.set(sidetag, true)
|
|
168
167
|
sideobjs.forEach((sideobj) => {
|
|
169
168
|
const starttag = getTag(sideobj.vertex0)
|
|
170
169
|
const endtag = getTag(sideobj.vertex1)
|
|
171
|
-
if (starttag
|
|
172
|
-
vertextag2sidestart
|
|
170
|
+
if (vertextag2sidestart.has(starttag)) {
|
|
171
|
+
vertextag2sidestart.get(starttag).push(sidetag)
|
|
173
172
|
} else {
|
|
174
|
-
vertextag2sidestart
|
|
173
|
+
vertextag2sidestart.set(starttag, [sidetag])
|
|
175
174
|
}
|
|
176
|
-
if (endtag
|
|
177
|
-
vertextag2sideend
|
|
175
|
+
if (vertextag2sideend.has(endtag)) {
|
|
176
|
+
vertextag2sideend.get(endtag).push(sidetag)
|
|
178
177
|
} else {
|
|
179
|
-
vertextag2sideend
|
|
178
|
+
vertextag2sideend.set(endtag, [sidetag])
|
|
180
179
|
}
|
|
181
180
|
})
|
|
182
181
|
}
|
|
@@ -187,13 +186,13 @@ const insertTjunctions = (polygons) => {
|
|
|
187
186
|
if (sidemap.size === 0) break
|
|
188
187
|
|
|
189
188
|
for (const sidetag of sidemap.keys()) {
|
|
190
|
-
|
|
189
|
+
sidesToCheck.set(sidetag, true)
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
let donesomething = false
|
|
194
193
|
while (true) {
|
|
195
|
-
const sidetags =
|
|
196
|
-
if (sidetags.length === 0) break //
|
|
194
|
+
const sidetags = Array.from(sidesToCheck.keys())
|
|
195
|
+
if (sidetags.length === 0) break // sidesToCheck is empty, we're done!
|
|
197
196
|
const sidetagtocheck = sidetags[0]
|
|
198
197
|
let donewithside = true
|
|
199
198
|
if (sidemap.has(sidetagtocheck)) {
|
|
@@ -207,12 +206,12 @@ const insertTjunctions = (polygons) => {
|
|
|
207
206
|
const endvertextag = getTag(endvertex)
|
|
208
207
|
let matchingsides = []
|
|
209
208
|
if (directionindex === 0) {
|
|
210
|
-
if (startvertextag
|
|
211
|
-
matchingsides = vertextag2sideend
|
|
209
|
+
if (vertextag2sideend.has(startvertextag)) {
|
|
210
|
+
matchingsides = vertextag2sideend.get(startvertextag)
|
|
212
211
|
}
|
|
213
212
|
} else {
|
|
214
|
-
if (startvertextag
|
|
215
|
-
matchingsides = vertextag2sidestart
|
|
213
|
+
if (vertextag2sidestart.has(startvertextag)) {
|
|
214
|
+
matchingsides = vertextag2sidestart.get(startvertextag)
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
for (let matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
|
|
@@ -267,8 +266,8 @@ const insertTjunctions = (polygons) => {
|
|
|
267
266
|
deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, matchingside.vertex1, polygonindex)
|
|
268
267
|
const newsidetag1 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, endvertex, polygonindex)
|
|
269
268
|
const newsidetag2 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, matchingside.vertex1, polygonindex)
|
|
270
|
-
if (newsidetag1 !== null)
|
|
271
|
-
if (newsidetag2 !== null)
|
|
269
|
+
if (newsidetag1 !== null) sidesToCheck.set(newsidetag1, true)
|
|
270
|
+
if (newsidetag2 !== null) sidesToCheck.set(newsidetag2, true)
|
|
272
271
|
donewithside = false
|
|
273
272
|
directionindex = 2 // skip reverse direction check
|
|
274
273
|
donesomething = true
|
|
@@ -280,7 +279,7 @@ const insertTjunctions = (polygons) => {
|
|
|
280
279
|
} // for directionindex
|
|
281
280
|
} // if(sidetagtocheck in sidemap)
|
|
282
281
|
if (donewithside) {
|
|
283
|
-
delete
|
|
282
|
+
sidesToCheck.delete(sidetagtocheck)
|
|
284
283
|
}
|
|
285
284
|
}
|
|
286
285
|
if (!donesomething) break
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Geometry } from '../../geometries/types'
|
|
2
|
+
import RecursiveArray from '../../utils/recursiveArray'
|
|
3
|
+
|
|
4
|
+
export function snap<T extends Geometry>(geometry: T): T
|
|
5
|
+
export function snap<T extends Geometry>(...geometries: RecursiveArray<T>): Array<T>
|
|
6
|
+
export function snap(...geometries: RecursiveArray<Geometry>): Array<Geometry>
|
|
@@ -2,6 +2,8 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const { comparePoints, comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
+
const { TAU } = require('../../maths/constants')
|
|
6
|
+
|
|
5
7
|
const { geom2, geom3, path2 } = require('../../geometries')
|
|
6
8
|
|
|
7
9
|
const { arc, rectangle, cuboid } = require('../../primitives')
|
|
@@ -12,7 +14,7 @@ test('snap: snap of a path2 produces an expected path2', (t) => {
|
|
|
12
14
|
const geometry1 = path2.create()
|
|
13
15
|
const geometry2 = arc({ radius: 1 / 2, segments: 8 })
|
|
14
16
|
const geometry3 = arc({ radius: 1.3333333333333333 / 2, segments: 8 })
|
|
15
|
-
const geometry4 = arc({ radius:
|
|
17
|
+
const geometry4 = arc({ radius: TAU / 4 * 1000, segments: 8 })
|
|
16
18
|
|
|
17
19
|
const results = snap(geometry1, geometry2, geometry3, geometry4)
|
|
18
20
|
t.is(results.length, 4)
|
|
@@ -56,7 +58,7 @@ test('snap: snap of a geom2 produces an expected geom2', (t) => {
|
|
|
56
58
|
const geometry1 = geom2.create()
|
|
57
59
|
const geometry2 = rectangle({ size: [1, 1, 1] })
|
|
58
60
|
const geometry3 = rectangle({ size: [1.3333333333333333, 1.3333333333333333, 1.3333333333333333] })
|
|
59
|
-
const geometry4 = rectangle({ size: [
|
|
61
|
+
const geometry4 = rectangle({ size: [TAU / 2 * 1000, TAU / 2 * 1000, TAU / 2 * 1000] })
|
|
60
62
|
|
|
61
63
|
const results = snap(geometry1, geometry2, geometry3, geometry4)
|
|
62
64
|
t.is(results.length, 4)
|
|
@@ -88,7 +90,7 @@ test('snap: snap of a geom3 produces an expected geom3', (t) => {
|
|
|
88
90
|
const geometry1 = geom3.create()
|
|
89
91
|
const geometry2 = cuboid({ size: [1, 1, 1] })
|
|
90
92
|
const geometry3 = cuboid({ size: [1.3333333333333333, 1.3333333333333333, 1.3333333333333333] })
|
|
91
|
-
const geometry4 = cuboid({ size: [
|
|
93
|
+
const geometry4 = cuboid({ size: [TAU / 2 * 1000, TAU / 2 * 1000, TAU / 2 * 1000] })
|
|
92
94
|
|
|
93
95
|
const results = snap(geometry1, geometry2, geometry3, geometry4)
|
|
94
96
|
t.is(results.length, 4)
|
|
@@ -14,7 +14,7 @@ const path2 = require('../../geometries/path2')
|
|
|
14
14
|
* @alias module:modeling/transforms.rotate
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
|
-
* const newsphere = rotate([
|
|
17
|
+
* const newsphere = rotate([TAU / 8, 0, 0], sphere())
|
|
18
18
|
*/
|
|
19
19
|
const rotate = (angles, ...objects) => {
|
|
20
20
|
if (!Array.isArray(angles)) throw new Error('angles must be an array')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../../maths/constants')
|
|
4
|
+
|
|
3
5
|
const { geom2, geom3, path2 } = require('../../geometries')
|
|
4
6
|
|
|
5
7
|
const { rotate, rotateX, rotateY, rotateZ } = require('./index')
|
|
@@ -10,7 +12,7 @@ test('rotate: rotating of a path2 produces expected changes to points', (t) => {
|
|
|
10
12
|
const geometry = path2.fromPoints({}, [[1, 0], [0, 1], [-1, 0]])
|
|
11
13
|
|
|
12
14
|
// rotate about Z
|
|
13
|
-
let rotated = rotate([0, 0,
|
|
15
|
+
let rotated = rotate([0, 0, TAU / 4], geometry)
|
|
14
16
|
let obs = path2.toPoints(rotated)
|
|
15
17
|
const exp = [
|
|
16
18
|
new Float32Array([0, 1]),
|
|
@@ -19,7 +21,7 @@ test('rotate: rotating of a path2 produces expected changes to points', (t) => {
|
|
|
19
21
|
]
|
|
20
22
|
t.true(comparePoints(obs, exp))
|
|
21
23
|
|
|
22
|
-
rotated = rotateZ(
|
|
24
|
+
rotated = rotateZ(TAU / 4, geometry)
|
|
23
25
|
obs = path2.toPoints(rotated)
|
|
24
26
|
t.notThrows(() => path2.validate(rotated))
|
|
25
27
|
t.true(comparePoints(obs, exp))
|
|
@@ -29,7 +31,7 @@ test('rotate: rotating of a geom2 produces expected changes to points', (t) => {
|
|
|
29
31
|
const geometry = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
|
|
30
32
|
|
|
31
33
|
// rotate about Z
|
|
32
|
-
let rotated = rotate([0, 0, -
|
|
34
|
+
let rotated = rotate([0, 0, -TAU / 4], geometry)
|
|
33
35
|
let obs = geom2.toPoints(rotated)
|
|
34
36
|
const exp = [
|
|
35
37
|
new Float32Array([0, 0]),
|
|
@@ -39,7 +41,7 @@ test('rotate: rotating of a geom2 produces expected changes to points', (t) => {
|
|
|
39
41
|
t.notThrows(() => geom2.validate(rotated))
|
|
40
42
|
t.true(comparePoints(obs, exp))
|
|
41
43
|
|
|
42
|
-
rotated = rotateZ(-
|
|
44
|
+
rotated = rotateZ(-TAU / 4, geometry)
|
|
43
45
|
obs = geom2.toPoints(rotated)
|
|
44
46
|
t.notThrows(() => geom2.validate(rotated))
|
|
45
47
|
t.true(comparePoints(obs, exp))
|
|
@@ -57,7 +59,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
|
|
|
57
59
|
const geometry = geom3.fromPoints(points)
|
|
58
60
|
|
|
59
61
|
// rotate about X
|
|
60
|
-
let rotated = rotate([
|
|
62
|
+
let rotated = rotate([TAU / 4], geometry)
|
|
61
63
|
let obs = geom3.toPoints(rotated)
|
|
62
64
|
let exp = [
|
|
63
65
|
[[-2, 12, -7.000000000000001], [-2, -18, -6.999999999999999],
|
|
@@ -76,13 +78,13 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
|
|
|
76
78
|
t.notThrows(() => geom3.validate(rotated))
|
|
77
79
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
78
80
|
|
|
79
|
-
rotated = rotateX(
|
|
81
|
+
rotated = rotateX(TAU / 4, geometry)
|
|
80
82
|
obs = geom3.toPoints(rotated)
|
|
81
83
|
t.notThrows(() => geom3.validate(rotated))
|
|
82
84
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
83
85
|
|
|
84
86
|
// rotate about Y
|
|
85
|
-
rotated = rotate([0, -
|
|
87
|
+
rotated = rotate([0, -TAU / 4], geometry)
|
|
86
88
|
obs = geom3.toPoints(rotated)
|
|
87
89
|
exp = [
|
|
88
90
|
[[12, -7, -2.000000000000001], [-18, -7, -1.999999999999999],
|
|
@@ -101,12 +103,12 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
|
|
|
101
103
|
t.notThrows(() => geom3.validate(rotated))
|
|
102
104
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
103
105
|
|
|
104
|
-
rotated = rotateY(-
|
|
106
|
+
rotated = rotateY(-TAU / 4, geometry)
|
|
105
107
|
obs = geom3.toPoints(rotated)
|
|
106
108
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
107
109
|
|
|
108
110
|
// rotate about Z
|
|
109
|
-
rotated = rotate([0, 0,
|
|
111
|
+
rotated = rotate([0, 0, TAU / 2], geometry)
|
|
110
112
|
obs = geom3.toPoints(rotated)
|
|
111
113
|
exp = [
|
|
112
114
|
[[2.000000000000001, 7, -12], [2.000000000000001, 7, 18],
|
|
@@ -125,7 +127,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', (t) =>
|
|
|
125
127
|
t.notThrows(() => geom3.validate(rotated))
|
|
126
128
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
127
129
|
|
|
128
|
-
rotated = rotateZ(
|
|
130
|
+
rotated = rotateZ(TAU / 2, geometry)
|
|
129
131
|
obs = geom3.toPoints(rotated)
|
|
130
132
|
t.notThrows(() => geom3.validate(rotated))
|
|
131
133
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -136,7 +138,7 @@ test('rotate: rotating of multiple objects produces expected changes', (t) => {
|
|
|
136
138
|
const geometry1 = path2.fromPoints({}, [[-5, 5], [5, 5], [-5, -5], [10, -5]])
|
|
137
139
|
const geometry2 = geom2.fromPoints([[-5, -5], [0, 5], [10, -5]])
|
|
138
140
|
|
|
139
|
-
const rotated = rotate([0, 0,
|
|
141
|
+
const rotated = rotate([0, 0, TAU / 4], junk, geometry1, geometry2)
|
|
140
142
|
|
|
141
143
|
t.is(rotated[0], junk)
|
|
142
144
|
|
|
@@ -12,7 +12,7 @@ const path2 = require('../../geometries/path2')
|
|
|
12
12
|
* @alias module:modeling/transforms.transform
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
|
-
* const newsphere = transform(mat4.rotateX(
|
|
15
|
+
* const newsphere = transform(mat4.rotateX(TAU / 8), sphere())
|
|
16
16
|
*/
|
|
17
17
|
const transform = (matrix, ...objects) => {
|
|
18
18
|
// TODO how to check that the matrix is REAL?
|