@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
package/src/primitives/arc.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ const { isGT, isGTE, isNumberArray } = require('./commonChecks')
|
|
|
12
12
|
* @param {Array} [options.center=[0,0]] - center of arc
|
|
13
13
|
* @param {Number} [options.radius=1] - radius of arc
|
|
14
14
|
* @param {Number} [options.startAngle=0] - starting angle of the arc, in radians
|
|
15
|
-
* @param {Number} [options.endAngle=
|
|
15
|
+
* @param {Number} [options.endAngle=TAU] - ending angle of the arc, in radians
|
|
16
16
|
* @param {Number} [options.segments=32] - number of segments to create per full rotation
|
|
17
17
|
* @param {Boolean} [options.makeTangent=false] - adds line segments at both ends of the arc to ensure that the gradients at the edges are tangent
|
|
18
18
|
* @returns {path2} new 2D path
|
|
@@ -23,7 +23,7 @@ const arc = (options) => {
|
|
|
23
23
|
center: [0, 0],
|
|
24
24
|
radius: 1,
|
|
25
25
|
startAngle: 0,
|
|
26
|
-
endAngle:
|
|
26
|
+
endAngle: TAU,
|
|
27
27
|
makeTangent: false,
|
|
28
28
|
segments: 32
|
|
29
29
|
}
|
|
@@ -35,15 +35,15 @@ const arc = (options) => {
|
|
|
35
35
|
if (!isGTE(endAngle, 0)) throw new Error('endAngle must be positive')
|
|
36
36
|
if (!isGTE(segments, 4)) throw new Error('segments must be four or more')
|
|
37
37
|
|
|
38
|
-
startAngle = startAngle %
|
|
39
|
-
endAngle = endAngle %
|
|
38
|
+
startAngle = startAngle % TAU
|
|
39
|
+
endAngle = endAngle % TAU
|
|
40
40
|
|
|
41
|
-
let rotation =
|
|
41
|
+
let rotation = TAU
|
|
42
42
|
if (startAngle < endAngle) {
|
|
43
43
|
rotation = endAngle - startAngle
|
|
44
44
|
}
|
|
45
45
|
if (startAngle > endAngle) {
|
|
46
|
-
rotation = endAngle + (
|
|
46
|
+
rotation = endAngle + (TAU - startAngle)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const minangle = Math.acos(((radius * radius) + (radius * radius) - (EPS * EPS)) / (2 * radius * radius))
|
|
@@ -59,7 +59,7 @@ const arc = (options) => {
|
|
|
59
59
|
pointArray.push(point)
|
|
60
60
|
} else {
|
|
61
61
|
// note: add one additional step to acheive full rotation
|
|
62
|
-
const numsteps = Math.max(1, Math.floor(segments * (rotation /
|
|
62
|
+
const numsteps = Math.max(1, Math.floor(segments * (rotation / TAU))) + 1
|
|
63
63
|
let edgestepsize = numsteps * 0.5 / rotation // step size for half a degree
|
|
64
64
|
if (edgestepsize > 0.25) edgestepsize = 0.25
|
|
65
65
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { TAU } = require('../maths/constants')
|
|
5
4
|
const path2 = require('../geometries/path2')
|
|
6
5
|
|
|
7
6
|
const comparePoints = require('../../test/helpers/comparePoints')
|
|
8
7
|
|
|
8
|
+
const { arc } = require('./index')
|
|
9
|
+
|
|
9
10
|
test('arc (defaults)', (t) => {
|
|
10
11
|
const geometry = arc()
|
|
11
12
|
const obs = path2.toPoints(geometry)
|
|
@@ -86,7 +87,7 @@ test('arc (options)', (t) => {
|
|
|
86
87
|
[0.9350162426854147, -0.35460488704253595],
|
|
87
88
|
[1, -2.4492935982947064e-16]
|
|
88
89
|
]
|
|
89
|
-
geometry = arc({ startAngle:
|
|
90
|
+
geometry = arc({ startAngle: TAU / 4, segments: 16 })
|
|
90
91
|
obs = path2.toPoints(geometry)
|
|
91
92
|
|
|
92
93
|
t.notThrows(() => path2.validate(geometry))
|
|
@@ -102,7 +103,7 @@ test('arc (options)', (t) => {
|
|
|
102
103
|
[0.30901699437494745, 0.9510565162951535],
|
|
103
104
|
[6.123233995736766e-17, 1]
|
|
104
105
|
]
|
|
105
|
-
geometry = arc({ endAngle:
|
|
106
|
+
geometry = arc({ endAngle: TAU / 4, segments: 16 })
|
|
106
107
|
obs = path2.toPoints(geometry)
|
|
107
108
|
|
|
108
109
|
t.notThrows(() => path2.validate(geometry))
|
|
@@ -167,7 +168,7 @@ test('arc (rotations)', (t) => {
|
|
|
167
168
|
[-0.9510565162951535, 0.3090169943749475],
|
|
168
169
|
[-1, 1.2246467991473532e-16]
|
|
169
170
|
]
|
|
170
|
-
let geometry = arc({ startAngle:
|
|
171
|
+
let geometry = arc({ startAngle: TAU / 4, endAngle: TAU / 2, segments: 16 })
|
|
171
172
|
let obs = path2.toPoints(geometry)
|
|
172
173
|
|
|
173
174
|
t.notThrows(() => path2.validate(geometry))
|
|
@@ -186,7 +187,7 @@ test('arc (rotations)', (t) => {
|
|
|
186
187
|
[0.9396926207859084, -0.3420201433256686],
|
|
187
188
|
[1, -2.4492935982947064e-16]
|
|
188
189
|
]
|
|
189
|
-
geometry = arc({ startAngle:
|
|
190
|
+
geometry = arc({ startAngle: TAU / 2, endAngle: TAU, segments: 16 })
|
|
190
191
|
obs = path2.toPoints(geometry)
|
|
191
192
|
|
|
192
193
|
t.notThrows(() => path2.validate(geometry))
|
|
@@ -205,7 +206,7 @@ test('arc (rotations)', (t) => {
|
|
|
205
206
|
[0.34202014332566866, 0.9396926207859084],
|
|
206
207
|
[3.061616997868383e-16, 1]
|
|
207
208
|
]
|
|
208
|
-
geometry = arc({ startAngle:
|
|
209
|
+
geometry = arc({ startAngle: TAU * 0.75, endAngle: TAU / 4, segments: 16 })
|
|
209
210
|
obs = path2.toPoints(geometry)
|
|
210
211
|
|
|
211
212
|
t.notThrows(() => path2.validate(geometry))
|
|
@@ -213,7 +214,7 @@ test('arc (rotations)', (t) => {
|
|
|
213
214
|
t.true(comparePoints(obs, exp))
|
|
214
215
|
|
|
215
216
|
exp = [[-1.8369701987210297e-16, -1]]
|
|
216
|
-
geometry = arc({ startAngle:
|
|
217
|
+
geometry = arc({ startAngle: TAU * 0.75, endAngle: 270.000000005 * 0.017453292519943295, segments: 16 })
|
|
217
218
|
obs = path2.toPoints(geometry)
|
|
218
219
|
|
|
219
220
|
t.notThrows(() => path2.validate(geometry))
|
package/src/primitives/circle.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { TAU } = require('../maths/constants')
|
|
2
|
+
|
|
1
3
|
const ellipse = require('./ellipse')
|
|
2
4
|
|
|
3
5
|
const { isGT } = require('./commonChecks')
|
|
@@ -9,7 +11,7 @@ const { isGT } = require('./commonChecks')
|
|
|
9
11
|
* @param {Array} [options.center=[0,0]] - center of circle
|
|
10
12
|
* @param {Number} [options.radius=1] - radius of circle
|
|
11
13
|
* @param {Number} [options.startAngle=0] - start angle of circle, in radians
|
|
12
|
-
* @param {Number} [options.endAngle=
|
|
14
|
+
* @param {Number} [options.endAngle=TAU] - end angle of circle, in radians
|
|
13
15
|
* @param {Number} [options.segments=32] - number of segments to create per full rotation
|
|
14
16
|
* @returns {geom2} new 2D geometry
|
|
15
17
|
* @alias module:modeling/primitives.circle
|
|
@@ -21,7 +23,7 @@ const circle = (options) => {
|
|
|
21
23
|
center: [0, 0],
|
|
22
24
|
radius: 1,
|
|
23
25
|
startAngle: 0,
|
|
24
|
-
endAngle:
|
|
26
|
+
endAngle: TAU,
|
|
25
27
|
segments: 32
|
|
26
28
|
}
|
|
27
29
|
let { center, radius, startAngle, endAngle, segments } = Object.assign({}, defaults, options)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { TAU } = require('../maths/constants')
|
|
5
4
|
const geom2 = require('../geometries/geom2')
|
|
6
5
|
|
|
7
6
|
const comparePoints = require('../../test/helpers/comparePoints')
|
|
8
7
|
|
|
8
|
+
const { circle } = require('./index')
|
|
9
|
+
|
|
9
10
|
test('circle (defaults)', (t) => {
|
|
10
11
|
const geometry = circle()
|
|
11
12
|
const pts = geom2.toPoints(geometry)
|
|
@@ -84,7 +85,7 @@ test('circle (options)', (t) => {
|
|
|
84
85
|
t.true(comparePoints(pts, exp))
|
|
85
86
|
|
|
86
87
|
// test startAngle
|
|
87
|
-
geometry = circle({ radius: 3.5, startAngle:
|
|
88
|
+
geometry = circle({ radius: 3.5, startAngle: TAU / 4, segments: 16 })
|
|
88
89
|
pts = geom2.toPoints(geometry)
|
|
89
90
|
exp = [
|
|
90
91
|
[0, 3.5],
|
|
@@ -108,7 +109,7 @@ test('circle (options)', (t) => {
|
|
|
108
109
|
t.true(comparePoints(pts, exp))
|
|
109
110
|
|
|
110
111
|
// test endAngle
|
|
111
|
-
geometry = circle({ radius: 3.5, endAngle:
|
|
112
|
+
geometry = circle({ radius: 3.5, endAngle: TAU / 4, segments: 16 })
|
|
112
113
|
pts = geom2.toPoints(geometry)
|
|
113
114
|
exp = [
|
|
114
115
|
[3.5, 0],
|
|
@@ -123,6 +124,13 @@ test('circle (options)', (t) => {
|
|
|
123
124
|
t.deepEqual(pts.length, 6)
|
|
124
125
|
t.true(comparePoints(pts, exp))
|
|
125
126
|
|
|
127
|
+
// test full rotation with non-zero startAngle
|
|
128
|
+
geometry = circle({ startAngle: 1, endAngle: 1 + TAU })
|
|
129
|
+
pts = geom2.toPoints(geometry)
|
|
130
|
+
|
|
131
|
+
t.notThrows(() => geom2.validate(geometry))
|
|
132
|
+
t.deepEqual(pts.length, 32)
|
|
133
|
+
|
|
126
134
|
// test segments
|
|
127
135
|
geometry = circle({ radius: 3.5, segments: 5 })
|
|
128
136
|
pts = geom2.toPoints(geometry)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec3 = require('../maths/vec3')
|
|
4
4
|
|
|
5
5
|
const geom3 = require('../geometries/geom3')
|
|
6
6
|
const poly3 = require('../geometries/poly3')
|
|
7
7
|
|
|
8
|
-
const { sin, cos } = require('../utils/trigonometry')
|
|
8
|
+
const { sin, cos } = require('../maths/utils/trigonometry')
|
|
9
9
|
|
|
10
10
|
const { isGT, isGTE, isNumberArray } = require('./commonChecks')
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ const { isGT, isGTE, isNumberArray } = require('./commonChecks')
|
|
|
17
17
|
* @param {Array} [options.startRadius=[1,1]] - radius of rounded start, must be two dimensional array
|
|
18
18
|
* @param {Number} [options.startAngle=0] - start angle of cylinder, in radians
|
|
19
19
|
* @param {Array} [options.endRadius=[1,1]] - radius of rounded end, must be two dimensional array
|
|
20
|
-
* @param {Number} [options.endAngle=
|
|
20
|
+
* @param {Number} [options.endAngle=TAU] - end angle of cylinder, in radians
|
|
21
21
|
* @param {Number} [options.segments=32] - number of segments to create per full rotation
|
|
22
22
|
* @returns {geom3} new geometry
|
|
23
23
|
* @alias module:modeling/primitives.cylinderElliptic
|
|
@@ -32,7 +32,7 @@ const cylinderElliptic = (options) => {
|
|
|
32
32
|
startRadius: [1, 1],
|
|
33
33
|
startAngle: 0,
|
|
34
34
|
endRadius: [1, 1],
|
|
35
|
-
endAngle:
|
|
35
|
+
endAngle: TAU,
|
|
36
36
|
segments: 32
|
|
37
37
|
}
|
|
38
38
|
let { center, height, startRadius, startAngle, endRadius, endAngle, segments } = Object.assign({}, defaults, options)
|
|
@@ -48,15 +48,15 @@ const cylinderElliptic = (options) => {
|
|
|
48
48
|
if (!isGTE(endAngle, 0)) throw new Error('endAngle must be positive')
|
|
49
49
|
if (!isGTE(segments, 4)) throw new Error('segments must be four or more')
|
|
50
50
|
|
|
51
|
-
startAngle = startAngle %
|
|
52
|
-
endAngle = endAngle %
|
|
51
|
+
startAngle = startAngle % TAU
|
|
52
|
+
endAngle = endAngle % TAU
|
|
53
53
|
|
|
54
|
-
let rotation =
|
|
54
|
+
let rotation = TAU
|
|
55
55
|
if (startAngle < endAngle) {
|
|
56
56
|
rotation = endAngle - startAngle
|
|
57
57
|
}
|
|
58
58
|
if (startAngle > endAngle) {
|
|
59
|
-
rotation = endAngle + (
|
|
59
|
+
rotation = endAngle + (TAU - startAngle)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const minradius = Math.min(startRadius[0], startRadius[1], endRadius[0], endRadius[1])
|
|
@@ -64,7 +64,7 @@ const cylinderElliptic = (options) => {
|
|
|
64
64
|
(2 * minradius * minradius))
|
|
65
65
|
if (rotation < minangle) throw new Error('startAngle and endAngle do not define a significant rotation')
|
|
66
66
|
|
|
67
|
-
const slices = Math.floor(segments * (rotation /
|
|
67
|
+
const slices = Math.floor(segments * (rotation / TAU))
|
|
68
68
|
|
|
69
69
|
const start = vec3.fromValues(0, 0, -(height / 2))
|
|
70
70
|
const end = vec3.fromValues(0, 0, height / 2)
|
|
@@ -96,7 +96,9 @@ const cylinderElliptic = (options) => {
|
|
|
96
96
|
const polygons = []
|
|
97
97
|
for (let i = 0; i < slices; i++) {
|
|
98
98
|
const t0 = i / slices
|
|
99
|
-
|
|
99
|
+
let t1 = (i + 1) / slices
|
|
100
|
+
// fix rounding error when rotating TAU radians
|
|
101
|
+
if (rotation === TAU && i === slices - 1) t1 = 0
|
|
100
102
|
|
|
101
103
|
if (endRadius[0] === startRadius[0] && endRadius[1] === startRadius[1]) {
|
|
102
104
|
polygons.push(fromPoints(start, point(0, t1, endRadius), point(0, t0, endRadius)))
|
|
@@ -117,7 +119,7 @@ const cylinderElliptic = (options) => {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
|
-
if (rotation <
|
|
122
|
+
if (rotation < TAU) {
|
|
121
123
|
polygons.push(fromPoints(start, point(0, 0, startRadius), end))
|
|
122
124
|
polygons.push(fromPoints(point(0, 0, startRadius), point(1, 0, endRadius), end))
|
|
123
125
|
polygons.push(fromPoints(start, end, point(0, 1, startRadius)))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../maths/constants')
|
|
3
4
|
const geom3 = require('../geometries/geom3')
|
|
4
5
|
|
|
5
6
|
const { cylinderElliptic } = require('./index')
|
|
@@ -132,12 +133,18 @@ test('cylinderElliptic (options)', (t) => {
|
|
|
132
133
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
133
134
|
|
|
134
135
|
// test startAngle and endAngle
|
|
135
|
-
obs = cylinderElliptic({ startRadius: [1, 2], endRadius: [2, 1], startAngle:
|
|
136
|
+
obs = cylinderElliptic({ startRadius: [1, 2], endRadius: [2, 1], startAngle: TAU / 4, endAngle: TAU * 0.75, segments: 12 })
|
|
136
137
|
pts = geom3.toPoints(obs)
|
|
137
138
|
|
|
138
139
|
t.notThrows(() => geom3.validate(obs))
|
|
139
140
|
t.is(pts.length, 28)
|
|
140
|
-
|
|
141
|
+
|
|
142
|
+
// test startAngle and endAngle
|
|
143
|
+
obs = cylinderElliptic({ startAngle: 1, endAngle: 1 + TAU })
|
|
144
|
+
pts = geom3.toPoints(obs)
|
|
145
|
+
|
|
146
|
+
t.notThrows(() => geom3.validate(obs))
|
|
147
|
+
t.is(pts.length, 96)
|
|
141
148
|
|
|
142
149
|
// test segments
|
|
143
150
|
obs = cylinderElliptic({ segments: 8 })
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
|
|
5
5
|
const geom2 = require('../geometries/geom2')
|
|
6
6
|
|
|
7
|
-
const { sin, cos } = require('../utils/trigonometry')
|
|
7
|
+
const { sin, cos } = require('../maths/utils/trigonometry')
|
|
8
8
|
|
|
9
9
|
const { isGTE, isNumberArray } = require('./commonChecks')
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ const { isGTE, isNumberArray } = require('./commonChecks')
|
|
|
15
15
|
* @param {Array} [options.center=[0,0]] - center of ellipse
|
|
16
16
|
* @param {Array} [options.radius=[1,1]] - radius of ellipse, along X and Y
|
|
17
17
|
* @param {Number} [options.startAngle=0] - start angle of ellipse, in radians
|
|
18
|
-
* @param {Number} [options.endAngle=
|
|
18
|
+
* @param {Number} [options.endAngle=TAU] - end angle of ellipse, in radians
|
|
19
19
|
* @param {Number} [options.segments=32] - number of segments to create per full rotation
|
|
20
20
|
* @returns {geom2} new 2D geometry
|
|
21
21
|
* @alias module:modeling/primitives.ellipse
|
|
@@ -27,7 +27,7 @@ const ellipse = (options) => {
|
|
|
27
27
|
center: [0, 0],
|
|
28
28
|
radius: [1, 1],
|
|
29
29
|
startAngle: 0,
|
|
30
|
-
endAngle:
|
|
30
|
+
endAngle: TAU,
|
|
31
31
|
segments: 32
|
|
32
32
|
}
|
|
33
33
|
let { center, radius, startAngle, endAngle, segments } = Object.assign({}, defaults, options)
|
|
@@ -39,15 +39,15 @@ const ellipse = (options) => {
|
|
|
39
39
|
if (!isGTE(endAngle, 0)) throw new Error('endAngle must be positive')
|
|
40
40
|
if (!isGTE(segments, 3)) throw new Error('segments must be three or more')
|
|
41
41
|
|
|
42
|
-
startAngle = startAngle %
|
|
43
|
-
endAngle = endAngle %
|
|
42
|
+
startAngle = startAngle % TAU
|
|
43
|
+
endAngle = endAngle % TAU
|
|
44
44
|
|
|
45
|
-
let rotation =
|
|
45
|
+
let rotation = TAU
|
|
46
46
|
if (startAngle < endAngle) {
|
|
47
47
|
rotation = endAngle - startAngle
|
|
48
48
|
}
|
|
49
49
|
if (startAngle > endAngle) {
|
|
50
|
-
rotation = endAngle + (
|
|
50
|
+
rotation = endAngle + (TAU - startAngle)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const minradius = Math.min(radius[0], radius[1])
|
|
@@ -55,20 +55,20 @@ const ellipse = (options) => {
|
|
|
55
55
|
(2 * minradius * minradius))
|
|
56
56
|
if (rotation < minangle) throw new Error('startAngle and endAngle do not define a significant rotation')
|
|
57
57
|
|
|
58
|
-
segments = Math.floor(segments * (rotation /
|
|
58
|
+
segments = Math.floor(segments * (rotation / TAU))
|
|
59
59
|
|
|
60
60
|
const centerv = vec2.clone(center)
|
|
61
61
|
const step = rotation / segments // radians per segment
|
|
62
62
|
|
|
63
63
|
const points = []
|
|
64
|
-
segments = (rotation <
|
|
64
|
+
segments = (rotation < TAU) ? segments + 1 : segments
|
|
65
65
|
for (let i = 0; i < segments; i++) {
|
|
66
66
|
const angle = (step * i) + startAngle
|
|
67
67
|
const point = vec2.fromValues(radius[0] * cos(angle), radius[1] * sin(angle))
|
|
68
68
|
vec2.add(point, centerv, point)
|
|
69
69
|
points.push(point)
|
|
70
70
|
}
|
|
71
|
-
if (rotation <
|
|
71
|
+
if (rotation < TAU) points.push(centerv)
|
|
72
72
|
return geom2.fromPoints(points)
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { TAU } = require('../maths/constants')
|
|
5
4
|
const geom2 = require('../geometries/geom2')
|
|
6
5
|
|
|
7
6
|
const comparePoints = require('../../test/helpers/comparePoints')
|
|
8
7
|
|
|
8
|
+
const { ellipse } = require('./index')
|
|
9
|
+
|
|
9
10
|
test('ellipse (defaults)', (t) => {
|
|
10
11
|
const geometry = ellipse()
|
|
11
12
|
const obs = geom2.toPoints(geometry)
|
|
@@ -84,7 +85,7 @@ test('ellipse (options)', (t) => {
|
|
|
84
85
|
t.true(comparePoints(obs, exp))
|
|
85
86
|
|
|
86
87
|
// test startAngle
|
|
87
|
-
geometry = ellipse({ radius: [3, 5], startAngle:
|
|
88
|
+
geometry = ellipse({ radius: [3, 5], startAngle: TAU / 4, segments: 16 })
|
|
88
89
|
obs = geom2.toPoints(geometry)
|
|
89
90
|
exp = [
|
|
90
91
|
[0, 5],
|
|
@@ -108,7 +109,7 @@ test('ellipse (options)', (t) => {
|
|
|
108
109
|
t.true(comparePoints(obs, exp))
|
|
109
110
|
|
|
110
111
|
// test endAngle
|
|
111
|
-
geometry = ellipse({ radius: [3, 5], endAngle:
|
|
112
|
+
geometry = ellipse({ radius: [3, 5], endAngle: TAU / 4, segments: 16 })
|
|
112
113
|
obs = geom2.toPoints(geometry)
|
|
113
114
|
exp = [
|
|
114
115
|
[3, 0],
|
|
@@ -123,6 +124,13 @@ test('ellipse (options)', (t) => {
|
|
|
123
124
|
t.deepEqual(obs.length, 6)
|
|
124
125
|
t.true(comparePoints(obs, exp))
|
|
125
126
|
|
|
127
|
+
// test full rotation with non-zero startAngle
|
|
128
|
+
geometry = ellipse({ startAngle: 1, endAngle: 1 + TAU })
|
|
129
|
+
obs = geom2.toPoints(geometry)
|
|
130
|
+
|
|
131
|
+
t.notThrows(() => geom2.validate(geometry))
|
|
132
|
+
t.deepEqual(obs.length, 32)
|
|
133
|
+
|
|
126
134
|
// test segments
|
|
127
135
|
geometry = ellipse({ segments: 72 })
|
|
128
136
|
obs = geom2.toPoints(geometry)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
const { TAU } = require('../maths/constants')
|
|
1
2
|
const vec3 = require('../maths/vec3')
|
|
2
3
|
|
|
3
4
|
const geom3 = require('../geometries/geom3')
|
|
4
5
|
const poly3 = require('../geometries/poly3')
|
|
5
6
|
|
|
6
|
-
const { sin, cos } = require('../utils/trigonometry')
|
|
7
|
+
const { sin, cos } = require('../maths/utils/trigonometry')
|
|
7
8
|
|
|
8
9
|
const { isGTE, isNumberArray } = require('./commonChecks')
|
|
9
10
|
|
|
@@ -44,12 +45,12 @@ const ellipsoid = (options) => {
|
|
|
44
45
|
const p1 = vec3.create()
|
|
45
46
|
const p2 = vec3.create()
|
|
46
47
|
for (let slice1 = 0; slice1 <= segments; slice1++) {
|
|
47
|
-
const angle =
|
|
48
|
+
const angle = TAU * slice1 / segments
|
|
48
49
|
const cylinderpoint = vec3.add(vec3.create(), vec3.scale(p1, xvector, cos(angle)), vec3.scale(p2, yvector, sin(angle)))
|
|
49
50
|
if (slice1 > 0) {
|
|
50
51
|
let prevcospitch, prevsinpitch
|
|
51
52
|
for (let slice2 = 0; slice2 <= qsegments; slice2++) {
|
|
52
|
-
const pitch =
|
|
53
|
+
const pitch = TAU / 4 * slice2 / qsegments
|
|
53
54
|
const cospitch = cos(pitch)
|
|
54
55
|
const sinpitch = sin(pitch)
|
|
55
56
|
if (slice2 > 0) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const mat4 = require('../maths/mat4')
|
|
2
|
+
const vec3 = require('../maths/vec3')
|
|
2
3
|
|
|
3
4
|
const geom3 = require('../geometries/geom3')
|
|
4
5
|
|
|
@@ -79,7 +80,7 @@ const geodesicSphere = (options) => {
|
|
|
79
80
|
|
|
80
81
|
// -- normalize
|
|
81
82
|
for (let k = 0; k < 3; k++) {
|
|
82
|
-
const r =
|
|
83
|
+
const r = vec3.length(q[k])
|
|
83
84
|
for (let l = 0; l < 3; l++) {
|
|
84
85
|
q[k][l] /= r
|
|
85
86
|
}
|
|
@@ -95,7 +96,7 @@ const geodesicSphere = (options) => {
|
|
|
95
96
|
|
|
96
97
|
// -- normalize
|
|
97
98
|
for (let k = 0; k < 3; k++) {
|
|
98
|
-
const r =
|
|
99
|
+
const r = vec3.length(q[k])
|
|
99
100
|
for (let l = 0; l < 3; l++) {
|
|
100
101
|
q[k][l] /= r
|
|
101
102
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
const vec3 = require('../maths/vec3')
|
|
@@ -6,12 +6,14 @@ const vec3 = require('../maths/vec3')
|
|
|
6
6
|
const geom3 = require('../geometries/geom3')
|
|
7
7
|
const poly3 = require('../geometries/poly3')
|
|
8
8
|
|
|
9
|
+
const { sin, cos } = require('../maths/utils/trigonometry')
|
|
10
|
+
|
|
9
11
|
const { isGT, isGTE, isNumberArray } = require('./commonChecks')
|
|
10
12
|
|
|
11
13
|
const createCorners = (center, size, radius, segments, slice, positive) => {
|
|
12
|
-
const pitch = (
|
|
13
|
-
const cospitch =
|
|
14
|
-
const sinpitch =
|
|
14
|
+
const pitch = (TAU / 4) * slice / segments
|
|
15
|
+
const cospitch = cos(pitch)
|
|
16
|
+
const sinpitch = sin(pitch)
|
|
15
17
|
|
|
16
18
|
const layersegments = segments - slice
|
|
17
19
|
let layerradius = radius * cospitch
|
|
@@ -29,16 +31,16 @@ const createCorners = (center, size, radius, segments, slice, positive) => {
|
|
|
29
31
|
const corner2Points = []
|
|
30
32
|
const corner3Points = []
|
|
31
33
|
for (let i = 0; i <= layersegments; i++) {
|
|
32
|
-
const radians = layersegments > 0 ?
|
|
34
|
+
const radians = layersegments > 0 ? TAU / 4 * i / layersegments : 0
|
|
33
35
|
const point2d = vec2.fromAngleRadians(vec2.create(), radians)
|
|
34
36
|
vec2.scale(point2d, point2d, layerradius)
|
|
35
37
|
const point3d = vec3.fromVec2(vec3.create(), point2d)
|
|
36
38
|
corner0Points.push(vec3.add(vec3.create(), corner0, point3d))
|
|
37
|
-
vec3.rotateZ(point3d, point3d, [0, 0, 0],
|
|
39
|
+
vec3.rotateZ(point3d, point3d, [0, 0, 0], TAU / 4)
|
|
38
40
|
corner1Points.push(vec3.add(vec3.create(), corner1, point3d))
|
|
39
|
-
vec3.rotateZ(point3d, point3d, [0, 0, 0],
|
|
41
|
+
vec3.rotateZ(point3d, point3d, [0, 0, 0], TAU / 4)
|
|
40
42
|
corner2Points.push(vec3.add(vec3.create(), corner2, point3d))
|
|
41
|
-
vec3.rotateZ(point3d, point3d, [0, 0, 0],
|
|
43
|
+
vec3.rotateZ(point3d, point3d, [0, 0, 0], TAU / 4)
|
|
42
44
|
corner3Points.push(vec3.add(vec3.create(), corner3, point3d))
|
|
43
45
|
}
|
|
44
46
|
if (!positive) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec3 = require('../maths/vec3')
|
|
4
4
|
|
|
5
5
|
const geom3 = require('../geometries/geom3')
|
|
6
6
|
const poly3 = require('../geometries/poly3')
|
|
7
7
|
|
|
8
|
-
const { sin, cos } = require('../utils/trigonometry')
|
|
8
|
+
const { sin, cos } = require('../maths/utils/trigonometry')
|
|
9
9
|
|
|
10
10
|
const { isGT, isGTE, isNumberArray } = require('./commonChecks')
|
|
11
11
|
|
|
@@ -74,7 +74,7 @@ const roundedCylinder = (options) => {
|
|
|
74
74
|
const v2 = vec3.create()
|
|
75
75
|
let prevcylinderpoint
|
|
76
76
|
for (let slice1 = 0; slice1 <= segments; slice1++) {
|
|
77
|
-
const angle =
|
|
77
|
+
const angle = TAU * slice1 / segments
|
|
78
78
|
const cylinderpoint = vec3.add(vec3.create(), vec3.scale(v1, xvector, cos(angle)), vec3.scale(v2, yvector, sin(angle)))
|
|
79
79
|
if (slice1 > 0) {
|
|
80
80
|
// cylinder wall
|
|
@@ -87,7 +87,7 @@ const roundedCylinder = (options) => {
|
|
|
87
87
|
|
|
88
88
|
let prevcospitch, prevsinpitch
|
|
89
89
|
for (let slice2 = 0; slice2 <= qsegments; slice2++) {
|
|
90
|
-
const pitch =
|
|
90
|
+
const pitch = TAU / 4 * slice2 / qsegments
|
|
91
91
|
const cospitch = cos(pitch)
|
|
92
92
|
const sinpitch = sin(pitch)
|
|
93
93
|
if (slice2 > 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { EPS } = require('../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../maths/constants')
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
|
|
@@ -51,15 +51,15 @@ const roundedRectangle = (options) => {
|
|
|
51
51
|
const corner2Points = []
|
|
52
52
|
const corner3Points = []
|
|
53
53
|
for (let i = 0; i <= cornersegments; i++) {
|
|
54
|
-
const radians =
|
|
54
|
+
const radians = TAU / 4 * i / cornersegments
|
|
55
55
|
const point = vec2.fromAngleRadians(vec2.create(), radians)
|
|
56
56
|
vec2.scale(point, point, roundRadius)
|
|
57
57
|
corner0Points.push(vec2.add(vec2.create(), corner0, point))
|
|
58
|
-
vec2.rotate(point, point, vec2.create(),
|
|
58
|
+
vec2.rotate(point, point, vec2.create(), TAU / 4)
|
|
59
59
|
corner1Points.push(vec2.add(vec2.create(), corner1, point))
|
|
60
|
-
vec2.rotate(point, point, vec2.create(),
|
|
60
|
+
vec2.rotate(point, point, vec2.create(), TAU / 4)
|
|
61
61
|
corner2Points.push(vec2.add(vec2.create(), corner2, point))
|
|
62
|
-
vec2.rotate(point, point, vec2.create(),
|
|
62
|
+
vec2.rotate(point, point, vec2.create(), TAU / 4)
|
|
63
63
|
corner3Points.push(vec2.add(vec2.create(), corner3, point))
|
|
64
64
|
}
|
|
65
65
|
|
package/src/primitives/star.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { TAU } = require('../maths/constants')
|
|
1
2
|
const vec2 = require('../maths/vec2')
|
|
2
3
|
|
|
3
4
|
const geom2 = require('../geometries/geom2')
|
|
@@ -13,7 +14,7 @@ const getRadiusRatio = (vertices, density) => {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const getPoints = (vertices, radius, startAngle, center) => {
|
|
16
|
-
const a =
|
|
17
|
+
const a = TAU / vertices
|
|
17
18
|
|
|
18
19
|
const points = []
|
|
19
20
|
for (let i = 0; i < vertices; i++) {
|
|
@@ -63,7 +64,7 @@ const star = (options) => {
|
|
|
63
64
|
vertices = Math.floor(vertices)
|
|
64
65
|
density = Math.floor(density)
|
|
65
66
|
|
|
66
|
-
startAngle = startAngle %
|
|
67
|
+
startAngle = startAngle % TAU
|
|
67
68
|
|
|
68
69
|
if (innerRadius === 0) {
|
|
69
70
|
if (!isGTE(density, 2)) throw new Error('density must be two or more')
|
package/src/primitives/torus.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { TAU } = require('../maths/constants')
|
|
2
|
+
|
|
1
3
|
const extrudeRotate = require('../operations/extrusions/extrudeRotate')
|
|
2
4
|
const { rotate } = require('../operations/transforms/rotate')
|
|
3
5
|
const { translate } = require('../operations/transforms/translate')
|
|
@@ -14,7 +16,7 @@ const { isGT, isGTE } = require('./commonChecks')
|
|
|
14
16
|
* @param {Integer} [options.innerSegments=32] - number of segments to create per rotation
|
|
15
17
|
* @param {Integer} [options.outerSegments=32] - number of segments to create per rotation
|
|
16
18
|
* @param {Integer} [options.innerRotation=0] - rotation of small (inner) circle in radians
|
|
17
|
-
* @param {Number} [options.outerRotation=
|
|
19
|
+
* @param {Number} [options.outerRotation=TAU] - rotation (outer) of the torus (RADIANS)
|
|
18
20
|
* @param {Number} [options.startAngle=0] - start angle of the torus (RADIANS)
|
|
19
21
|
* @returns {geom3} new 3D geometry
|
|
20
22
|
* @alias module:modeling/primitives.torus
|
|
@@ -30,7 +32,7 @@ const torus = (options) => {
|
|
|
30
32
|
outerSegments: 32,
|
|
31
33
|
innerRotation: 0,
|
|
32
34
|
startAngle: 0,
|
|
33
|
-
outerRotation:
|
|
35
|
+
outerRotation: TAU
|
|
34
36
|
}
|
|
35
37
|
const { innerRadius, innerSegments, outerRadius, outerSegments, innerRotation, startAngle, outerRotation } = Object.assign({}, defaults, options)
|
|
36
38
|
|