@jscad/modeling 2.9.6 → 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 +17 -0
- package/README.md +12 -2
- package/dist/jscad-modeling.min.js +28 -28
- package/package.json +2 -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/transform.js +1 -1
- package/src/index.d.ts +1 -0
- package/src/maths/constants.js +10 -0
- package/src/maths/mat4/fromRotation.js +1 -1
- package/src/maths/mat4/fromTaitBryanRotation.js +1 -1
- package/src/maths/mat4/fromXRotation.js +1 -1
- package/src/maths/mat4/fromYRotation.js +1 -1
- package/src/maths/mat4/fromZRotation.js +1 -1
- 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/rotation.test.js +5 -2
- package/src/maths/utils/trigonometry.js +6 -6
- package/src/maths/utils/trigonometry.test.js +10 -8
- package/src/maths/vec2/fromAngleDegrees.js +1 -1
- package/src/maths/vec2/fromAngleRadians.test.js +4 -1
- package/src/maths/vec2/normal.js +3 -1
- package/src/maths/vec2/rotate.test.js +4 -1
- 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/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 +7 -5
- package/src/operations/extrusions/slice/calculatePlane.test.js +3 -2
- package/src/operations/extrusions/slice/index.js +2 -0
- 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/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 +6 -5
- package/src/primitives/cylinderElliptic.js +11 -11
- package/src/primitives/cylinderElliptic.test.js +3 -2
- package/src/primitives/ellipse.js +10 -10
- package/src/primitives/ellipse.test.js +6 -5
- package/src/primitives/ellipsoid.js +3 -2
- package/src/primitives/roundedCuboid.js +6 -6
- package/src/primitives/roundedCylinder.js +3 -3
- 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 +5 -3
- package/src/primitives/triangle.test.js +2 -1
- package/src/utils/degToRad.test.js +5 -5
- package/src/utils/radToDeg.test.js +6 -6
- package/src/utils/radiusToSegments.js +6 -4
- package/src/utils/radiusToSegments.test.js +5 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
3
|
* Determine whether the given matrix is only translate and/or scale.
|
|
4
|
-
* This code returns true for
|
|
4
|
+
* This code returns true for TAU / 2 rotation as it can be interpreted as scale.
|
|
5
5
|
*
|
|
6
6
|
* @param {mat4} matrix - the matrix
|
|
7
7
|
* @returns {Boolean} true if matrix is for translate and/or scale
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
3
5
|
const { isOnlyTransformScale, create, fromTranslation, fromTaitBryanRotation, fromScaling, invert, multiply } = require('./index')
|
|
4
6
|
|
|
5
7
|
test('mat4: isOnlyTransformScale() should return true for right angles', (t) => {
|
|
6
|
-
let someRotation = fromTaitBryanRotation(create(),
|
|
8
|
+
let someRotation = fromTaitBryanRotation(create(), TAU / 2, 0, 0)
|
|
7
9
|
t.true(isOnlyTransformScale(someRotation))
|
|
8
10
|
t.true(isOnlyTransformScale(invert(create(), someRotation)))
|
|
9
11
|
|
|
@@ -2,15 +2,18 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const { compareVectors } = require('../../test/helpers/index')
|
|
4
4
|
|
|
5
|
-
const { mat4, vec2, vec3 } = require('./index')
|
|
5
|
+
const { constants, mat4, vec2, vec3 } = require('./index')
|
|
6
6
|
|
|
7
7
|
// ALL POSITIVE ROTATIONS ARE CLOCKWISE
|
|
8
8
|
// see https://webglfundamentals.org/webgl/lessons/webgl-3d-orthographic.html
|
|
9
9
|
// IN A LEFT-HANDED COORDINATE SYSTEM
|
|
10
10
|
|
|
11
|
+
// JSCAD IS RIGHT-HANDED COORDINATE SYSTEM
|
|
12
|
+
// WHERE POSITIVE ROTATIONS ARE COUNTER-CLOCKWISE
|
|
13
|
+
|
|
11
14
|
// identity matrices for comparisons
|
|
12
15
|
|
|
13
|
-
const rad90 =
|
|
16
|
+
const rad90 = constants.TAU / 4
|
|
14
17
|
|
|
15
18
|
// +90 degree rotation about X
|
|
16
19
|
const cwX90Matrix = [
|
|
@@ -6,28 +6,28 @@ const { NEPS } = require('../constants')
|
|
|
6
6
|
const rezero = (n) => Math.abs(n) < NEPS ? 0 : n
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Return Math.sin but accurate for
|
|
9
|
+
* Return Math.sin but accurate for TAU / 4 rotations.
|
|
10
10
|
* Fixes rounding errors when sin should be 0.
|
|
11
11
|
*
|
|
12
12
|
* @param {Number} radians - angle in radians
|
|
13
13
|
* @returns {Number} sine of the given angle
|
|
14
14
|
* @alias module:modeling/utils.sin
|
|
15
15
|
* @example
|
|
16
|
-
* sin(
|
|
17
|
-
* sin(
|
|
16
|
+
* sin(TAU / 2) == 0
|
|
17
|
+
* sin(TAU) == 0
|
|
18
18
|
*/
|
|
19
19
|
const sin = (radians) => rezero(Math.sin(radians))
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Return Math.cos but accurate for
|
|
22
|
+
* Return Math.cos but accurate for TAU / 4 rotations.
|
|
23
23
|
* Fixes rounding errors when cos should be 0.
|
|
24
24
|
*
|
|
25
25
|
* @param {Number} radians - angle in radians
|
|
26
26
|
* @returns {Number} cosine of the given angle
|
|
27
27
|
* @alias module:modeling/utils.cos
|
|
28
28
|
* @example
|
|
29
|
-
* cos(
|
|
30
|
-
* cos(
|
|
29
|
+
* cos(TAU * 0.25) == 0
|
|
30
|
+
* cos(TAU * 0.75) == 0
|
|
31
31
|
*/
|
|
32
32
|
const cos = (radians) => rezero(Math.cos(radians))
|
|
33
33
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
3
5
|
const { cos, sin } = require('./trigonometry')
|
|
4
6
|
|
|
5
7
|
test('utils: sin() should return rounded values', (t) => {
|
|
6
8
|
t.is(sin(0), 0)
|
|
7
9
|
t.is(sin(9), Math.sin(9))
|
|
8
|
-
t.is(sin(0.
|
|
9
|
-
t.is(sin(
|
|
10
|
-
t.is(sin(
|
|
11
|
-
t.is(sin(
|
|
10
|
+
t.is(sin(0.25 * TAU), 1)
|
|
11
|
+
t.is(sin(0.5 * TAU), 0)
|
|
12
|
+
t.is(sin(0.75 * TAU), -1)
|
|
13
|
+
t.is(sin(TAU), 0)
|
|
12
14
|
t.is(sin(NaN), NaN)
|
|
13
15
|
t.is(sin(Infinity), NaN)
|
|
14
16
|
})
|
|
@@ -16,10 +18,10 @@ test('utils: sin() should return rounded values', (t) => {
|
|
|
16
18
|
test('utils: cos() should return rounded values', (t) => {
|
|
17
19
|
t.is(cos(0), 1)
|
|
18
20
|
t.is(cos(9), Math.cos(9))
|
|
19
|
-
t.is(cos(0.
|
|
20
|
-
t.is(cos(
|
|
21
|
-
t.is(cos(
|
|
22
|
-
t.is(cos(
|
|
21
|
+
t.is(cos(0.25 * TAU), 0)
|
|
22
|
+
t.is(cos(0.5 * TAU), -1)
|
|
23
|
+
t.is(cos(0.75 * TAU), 0)
|
|
24
|
+
t.is(cos(TAU), 1)
|
|
23
25
|
t.is(cos(NaN), NaN)
|
|
24
26
|
t.is(cos(Infinity), NaN)
|
|
25
27
|
})
|
|
@@ -8,6 +8,6 @@ const fromAngleRadians = require('./fromAngleRadians')
|
|
|
8
8
|
* @returns {vec2} out
|
|
9
9
|
* @alias module:modeling/maths/vec2.fromAngleDegrees
|
|
10
10
|
*/
|
|
11
|
-
const fromAngleDegrees = (out, degrees) => fromAngleRadians(out,
|
|
11
|
+
const fromAngleDegrees = (out, degrees) => fromAngleRadians(out, degrees * 0.017453292519943295)
|
|
12
12
|
|
|
13
13
|
module.exports = fromAngleDegrees
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
2
5
|
const { fromAngleRadians, create } = require('./index')
|
|
3
6
|
|
|
4
7
|
const { compareVectors } = require('../../../test/helpers/index')
|
|
@@ -7,6 +10,6 @@ test('vec2: fromAngleRadians() should return a new vec2 with correct values', (t
|
|
|
7
10
|
const obs1 = fromAngleRadians(create(), 0)
|
|
8
11
|
t.true(compareVectors(obs1, [1.0, 0.0]))
|
|
9
12
|
|
|
10
|
-
const obs2 = fromAngleRadians(obs1,
|
|
13
|
+
const obs2 = fromAngleRadians(obs1, TAU / 2)
|
|
11
14
|
t.true(compareVectors(obs2, [-1, 1.2246468525851679e-16]))
|
|
12
15
|
})
|
package/src/maths/vec2/normal.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { TAU } = require('../constants')
|
|
2
|
+
|
|
1
3
|
const create = require('./create')
|
|
2
4
|
const rotate = require('./rotate')
|
|
3
5
|
|
|
@@ -10,6 +12,6 @@ const rotate = require('./rotate')
|
|
|
10
12
|
* @returns {vec2} out
|
|
11
13
|
* @alias module:modeling/maths/vec2.normal
|
|
12
14
|
*/
|
|
13
|
-
const normal = (out, vector) => rotate(out, vector, create(), (
|
|
15
|
+
const normal = (out, vector) => rotate(out, vector, create(), (TAU / 4))
|
|
14
16
|
|
|
15
17
|
module.exports = normal
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
2
5
|
const { rotate, fromValues } = require('./index')
|
|
3
6
|
|
|
4
7
|
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
8
|
|
|
6
9
|
test('vec2: rotate() called with three parameters should update a vec2 with correct values', (t) => {
|
|
7
|
-
const radians =
|
|
10
|
+
const radians = TAU / 4
|
|
8
11
|
|
|
9
12
|
const obs1 = fromValues(0, 0)
|
|
10
13
|
const ret1 = rotate(obs1, [0, 0], [0, 0], 0)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
2
5
|
const { rotateX, fromValues } = require('./index')
|
|
3
6
|
|
|
4
7
|
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
8
|
|
|
6
9
|
test('vec3: rotateX() called with four parameters should update a vec3 with correct values', (t) => {
|
|
7
|
-
const radians =
|
|
10
|
+
const radians = TAU / 4
|
|
8
11
|
|
|
9
12
|
const obs1 = fromValues(0, 0, 0)
|
|
10
13
|
const ret1 = rotateX(obs1, [0, 0, 0], [0, 0, 0], 0)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
2
5
|
const { rotateY, fromValues } = require('./index')
|
|
3
6
|
|
|
4
7
|
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
8
|
|
|
6
9
|
test('vec3: rotateY() called with three parameters should update a vec3 with correct values', (t) => {
|
|
7
|
-
const radians =
|
|
10
|
+
const radians = TAU / 4
|
|
8
11
|
|
|
9
12
|
const obs1 = fromValues(0, 0, 0)
|
|
10
13
|
const ret1 = rotateY(obs1, [0, 0, 0], [0, 0, 0], 0)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { TAU } = require('../constants')
|
|
4
|
+
|
|
2
5
|
const { rotateZ, fromValues } = require('./index')
|
|
3
6
|
|
|
4
7
|
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
8
|
|
|
6
9
|
test('vec3: rotateZ() called with four parameters should update a vec3 with correct values', (t) => {
|
|
7
|
-
const radians =
|
|
10
|
+
const radians = TAU / 4
|
|
8
11
|
|
|
9
12
|
const obs1 = fromValues(0, 0, 0)
|
|
10
13
|
const ret1 = rotateZ(obs1, [0, 0, 0], [0, 0, 0], 0)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
3
|
const { comparePoints, nearlyEqual } = require('../../../test/helpers')
|
|
4
|
+
|
|
4
5
|
const { geom2, geom3, path2 } = require('../../geometries')
|
|
5
6
|
const measureBoundingBox = require('../../measurements/measureBoundingBox')
|
|
6
7
|
const area = require('../../maths/utils/area')
|
|
8
|
+
const { TAU } = require('../../maths/constants')
|
|
7
9
|
const sphere = require('../../primitives/sphere')
|
|
8
10
|
|
|
9
11
|
const { expand } = require('./index')
|
|
@@ -51,7 +53,7 @@ test('expand: round-expanding a bent line produces expected geometry', (t) => {
|
|
|
51
53
|
const expandedPoints = geom2.toPoints(expandedPathGeom2)
|
|
52
54
|
|
|
53
55
|
t.notThrows(() => geom2.validate(expandedPathGeom2))
|
|
54
|
-
const expectedArea = 56 +
|
|
56
|
+
const expectedArea = 56 + TAU * delta * 1.25 // shape will have 1 and 1/4 circles
|
|
55
57
|
nearlyEqual(t, area(expandedPoints), expectedArea, 0.01, 'Measured area should be pretty close')
|
|
56
58
|
const boundingBox = measureBoundingBox(expandedPathGeom2)
|
|
57
59
|
t.true(comparePoints(boundingBox, [[-7, -2, 0], [2, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { EPS } = require('../../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../../maths/constants')
|
|
2
2
|
|
|
3
3
|
const mat4 = require('../../maths/mat4')
|
|
4
4
|
const vec3 = require('../../maths/vec3')
|
|
@@ -120,7 +120,7 @@ const expandShell = (options, geometry) => {
|
|
|
120
120
|
|
|
121
121
|
// first of all equally spaced around the cylinder:
|
|
122
122
|
for (let i = 0; i < segments; i++) {
|
|
123
|
-
addUniqueAngle(angles, (i *
|
|
123
|
+
addUniqueAngle(angles, (i * TAU / segments))
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// and also at every normal of all touching planes:
|
|
@@ -130,10 +130,10 @@ const expandShell = (options, geometry) => {
|
|
|
130
130
|
const co = vec3.dot(xbase, planenormal)
|
|
131
131
|
let angle = Math.atan2(si, co)
|
|
132
132
|
|
|
133
|
-
if (angle < 0) angle +=
|
|
133
|
+
if (angle < 0) angle += TAU
|
|
134
134
|
addUniqueAngle(angles, angle)
|
|
135
135
|
angle = Math.atan2(-si, -co)
|
|
136
|
-
if (angle < 0) angle +=
|
|
136
|
+
if (angle < 0) angle += TAU
|
|
137
137
|
addUniqueAngle(angles, angle)
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { EPS } = require('../../maths/constants')
|
|
1
|
+
const { EPS, TAU } = require('../../maths/constants')
|
|
2
2
|
|
|
3
3
|
const intersect = require('../../maths/utils/intersect')
|
|
4
4
|
const line2 = require('../../maths/line2')
|
|
@@ -139,7 +139,7 @@ const offsetFromPoints = (options, points) => {
|
|
|
139
139
|
|
|
140
140
|
if (rotation !== 0.0) {
|
|
141
141
|
// generate the segments
|
|
142
|
-
cornersegments = Math.floor(segments * (Math.abs(rotation) /
|
|
142
|
+
cornersegments = Math.floor(segments * (Math.abs(rotation) / TAU))
|
|
143
143
|
const step = rotation / cornersegments
|
|
144
144
|
const start = vec2.angle(vec2.subtract(v0, corner.s0[1], corner.c))
|
|
145
145
|
const cornerpoints = []
|
|
@@ -2,6 +2,7 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
|
|
4
4
|
|
|
5
|
+
const { TAU } = require('../../maths/constants')
|
|
5
6
|
const mat4 = require('../../maths/mat4')
|
|
6
7
|
|
|
7
8
|
const { geom2, geom3, poly3 } = require('../../geometries')
|
|
@@ -56,10 +57,10 @@ test('extrudeFromSlices (torus)', (t) => {
|
|
|
56
57
|
hex = poly3.transform(mat4.fromTranslation(mat4.create(), [0, 20, 0]), hex)
|
|
57
58
|
hex = slice.fromPoints(poly3.toPoints(hex))
|
|
58
59
|
|
|
59
|
-
const angle =
|
|
60
|
+
const angle = TAU / 8
|
|
60
61
|
const geometry3 = extrudeFromSlices(
|
|
61
62
|
{
|
|
62
|
-
numberOfSlices:
|
|
63
|
+
numberOfSlices: TAU / angle,
|
|
63
64
|
capStart: false,
|
|
64
65
|
capEnd: false,
|
|
65
66
|
close: true,
|
|
@@ -2,6 +2,8 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
|
|
4
4
|
|
|
5
|
+
const { TAU } = require('../../maths/constants')
|
|
6
|
+
|
|
5
7
|
const { geom2, geom3, path2 } = require('../../geometries')
|
|
6
8
|
|
|
7
9
|
const { extrudeLinear } = require('./index')
|
|
@@ -77,7 +79,7 @@ test('extrudeLinear (no twist)', (t) => {
|
|
|
77
79
|
test('extrudeLinear (twist)', (t) => {
|
|
78
80
|
const geometry2 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
|
|
79
81
|
|
|
80
|
-
let geometry3 = extrudeLinear({ height: 15, twistAngle:
|
|
82
|
+
let geometry3 = extrudeLinear({ height: 15, twistAngle: -TAU / 8 }, geometry2)
|
|
81
83
|
let pts = geom3.toPoints(geometry3)
|
|
82
84
|
let exp = [
|
|
83
85
|
[[5, -5, 0], [5, 5, 0], [7.0710678118654755, 4.440892098500626e-16, 15]],
|
|
@@ -105,7 +107,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
105
107
|
t.is(pts.length, 12)
|
|
106
108
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
107
109
|
|
|
108
|
-
geometry3 = extrudeLinear({ height: 15, twistAngle:
|
|
110
|
+
geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 4, twistSteps: 3 }, geometry2)
|
|
109
111
|
pts = geom3.toPoints(geometry3)
|
|
110
112
|
exp = [
|
|
111
113
|
[[5, -5, 0], [5, 5, 0], [1.830127018922194, 6.830127018922193, 5]],
|
|
@@ -140,7 +142,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
140
142
|
t.is(pts.length, 28)
|
|
141
143
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
142
144
|
|
|
143
|
-
geometry3 = extrudeLinear({ height: 15, twistAngle:
|
|
145
|
+
geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 2, twistSteps: 30 }, geometry2)
|
|
144
146
|
pts = geom3.toPoints(geometry3)
|
|
145
147
|
t.notThrows(() => geom3.validate(geometry3))
|
|
146
148
|
t.is(pts.length, 244)
|
|
@@ -18,7 +18,7 @@ const extrudeRectangularGeom2 = require('./extrudeRectangularGeom2')
|
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* let mywalls = extrudeRectangular({size: 1, height: 3}, square({size: 20}))
|
|
21
|
-
* let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle:
|
|
21
|
+
* let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle: TAU / 2}, square({size: 20}))
|
|
22
22
|
*/
|
|
23
23
|
const extrudeRectangular = (options, ...objects) => {
|
|
24
24
|
const defaults = {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
+
const { TAU } = require('../../maths/constants')
|
|
4
|
+
|
|
3
5
|
const { geom2, geom3 } = require('../../geometries')
|
|
4
6
|
|
|
5
7
|
const { arc, rectangle } = require('../../primitives')
|
|
@@ -7,7 +9,7 @@ const { arc, rectangle } = require('../../primitives')
|
|
|
7
9
|
const { extrudeRectangular } = require('./index')
|
|
8
10
|
|
|
9
11
|
test('extrudeRectangular (defaults)', (t) => {
|
|
10
|
-
const geometry1 = arc({ radius: 5, endAngle:
|
|
12
|
+
const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
|
|
11
13
|
const geometry2 = rectangle({ size: [5, 5] })
|
|
12
14
|
|
|
13
15
|
let obs = extrudeRectangular({ }, geometry1)
|
|
@@ -22,7 +24,7 @@ test('extrudeRectangular (defaults)', (t) => {
|
|
|
22
24
|
})
|
|
23
25
|
|
|
24
26
|
test('extrudeRectangular (chamfer)', (t) => {
|
|
25
|
-
const geometry1 = arc({ radius: 5, endAngle:
|
|
27
|
+
const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
|
|
26
28
|
const geometry2 = rectangle({ size: [5, 5] })
|
|
27
29
|
|
|
28
30
|
let obs = extrudeRectangular({ corners: 'chamfer' }, geometry1)
|
|
@@ -37,7 +39,7 @@ test('extrudeRectangular (chamfer)', (t) => {
|
|
|
37
39
|
})
|
|
38
40
|
|
|
39
41
|
test('extrudeRectangular (segments = 8, round)', (t) => {
|
|
40
|
-
const geometry1 = arc({ radius: 5, endAngle:
|
|
42
|
+
const geometry1 = arc({ radius: 5, endAngle: TAU / 4, segments: 16 })
|
|
41
43
|
const geometry2 = rectangle({ size: [5, 5] })
|
|
42
44
|
|
|
43
45
|
let obs = extrudeRectangular({ segments: 8, corners: 'round' }, geometry1)
|
|
@@ -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,7 +21,7 @@ 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
27
|
[[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
|
|
@@ -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],
|
|
@@ -110,7 +112,7 @@ 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
118
|
[[0, 0, 8], [7, 0, 8], [0, 7, 8]],
|
|
@@ -129,7 +131,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
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
137
|
[[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
|
|
@@ -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
|
|
|
@@ -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)
|
|
@@ -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')
|