@jscad/modeling 2.11.1 → 2.12.1
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 +34 -0
- package/dist/jscad-modeling.min.js +338 -335
- package/package.json +2 -2
- package/src/curves/bezier/arcLengthToT.js +6 -6
- package/src/curves/bezier/arcLengthToT.test.js +25 -25
- package/src/curves/bezier/length.js +3 -5
- package/src/curves/bezier/length.test.js +2 -2
- package/src/curves/bezier/lengths.js +10 -10
- package/src/curves/bezier/lengths.test.js +3 -3
- package/src/geometries/geom2/transform.js +9 -1
- package/src/geometries/geom2/transform.test.js +58 -1
- package/src/geometries/poly2/arePointsInside.js +0 -7
- package/src/geometries/poly3/measureBoundingSphere.js +1 -2
- package/src/maths/plane/fromNoisyPoints.d.ts +6 -0
- package/src/maths/plane/fromNoisyPoints.js +106 -0
- package/src/maths/plane/fromNoisyPoints.test.js +24 -0
- package/src/maths/plane/index.d.ts +1 -0
- package/src/maths/plane/index.js +1 -0
- package/src/operations/booleans/trees/PolygonTreeNode.js +1 -1
- package/src/operations/expansions/expand.test.js +1 -1
- package/src/operations/extrusions/extrudeHelical.js +6 -7
- package/src/operations/extrusions/extrudeHelical.test.js +35 -37
- package/src/operations/extrusions/extrudeRotate.js +1 -1
- package/src/operations/extrusions/index.d.ts +1 -0
- package/src/operations/hulls/hullPoints2.js +3 -2
- package/src/operations/modifiers/index.js +1 -1
- package/src/operations/modifiers/retessellate.js +66 -27
- package/src/operations/transforms/mirror.test.js +9 -3
- package/src/primitives/circle.js +2 -2
- package/src/primitives/circle.test.js +7 -0
- package/src/primitives/cube.js +2 -2
- package/src/primitives/cube.test.js +7 -0
- package/src/primitives/cuboid.js +4 -1
- package/src/primitives/cuboid.test.js +7 -0
- package/src/primitives/cylinder.js +7 -2
- package/src/primitives/cylinder.test.js +14 -0
- package/src/primitives/ellipse.js +4 -1
- package/src/primitives/ellipse.test.js +7 -0
- package/src/primitives/ellipsoid.js +4 -1
- package/src/primitives/ellipsoid.test.js +7 -0
- package/src/primitives/geodesicSphere.js +5 -2
- package/src/primitives/geodesicSphere.test.js +7 -0
- package/src/primitives/polygon.d.ts +1 -0
- package/src/primitives/polygon.js +15 -4
- package/src/primitives/polygon.test.js +10 -0
- package/src/primitives/rectangle.js +4 -1
- package/src/primitives/rectangle.test.js +7 -0
- package/src/primitives/roundedCuboid.js +10 -3
- package/src/primitives/roundedCuboid.test.js +14 -0
- package/src/primitives/roundedCylinder.js +12 -5
- package/src/primitives/roundedCylinder.test.js +21 -0
- package/src/primitives/roundedRectangle.js +10 -3
- package/src/primitives/roundedRectangle.test.js +14 -0
- package/src/primitives/sphere.js +2 -2
- package/src/primitives/sphere.test.js +7 -0
- package/src/primitives/square.js +2 -2
- package/src/primitives/square.test.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.1",
|
|
4
4
|
"description": "Constructive Solid Geometry (CSG) Library for JSCAD",
|
|
5
5
|
"homepage": "https://openjscad.xyz/",
|
|
6
6
|
"repository": "https://github.com/jscad/OpenJSCAD.org",
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"nyc": "15.1.0",
|
|
62
62
|
"uglifyify": "5.0.2"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "e07bb27d61f638348c73cc1383dfb5339060a02a"
|
|
65
65
|
}
|
|
@@ -3,7 +3,7 @@ const lengths = require('./lengths')
|
|
|
3
3
|
/**
|
|
4
4
|
* Convert a given arc length along a bezier curve to a t value.
|
|
5
5
|
* Useful for generating equally spaced points along a bezier curve.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
8
|
* const points = [];
|
|
9
9
|
* const segments = 9; // this will generate 10 equally spaced points
|
|
@@ -14,7 +14,7 @@ const lengths = require('./lengths')
|
|
|
14
14
|
* points.push(point);
|
|
15
15
|
* }
|
|
16
16
|
* return points;
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
18
|
* @param {Object} [options] options for construction
|
|
19
19
|
* @param {Number} [options.distance=0] the distance along the bezier curve for which we want to find the corresponding t value.
|
|
20
20
|
* @param {Number} [options.segments=100] the number of segments to use when approximating the curve length.
|
|
@@ -27,8 +27,8 @@ const arcLengthToT = (options, bezier) => {
|
|
|
27
27
|
distance: 0,
|
|
28
28
|
segments: 100
|
|
29
29
|
}
|
|
30
|
-
const {distance, segments} = Object.assign({}, defaults, options)
|
|
31
|
-
|
|
30
|
+
const { distance, segments } = Object.assign({}, defaults, options)
|
|
31
|
+
|
|
32
32
|
const arcLengths = lengths(segments, bezier)
|
|
33
33
|
// binary search for the index with largest value smaller than target arcLength
|
|
34
34
|
let startIndex = 0
|
|
@@ -58,6 +58,6 @@ const arcLengthToT = (options, bezier) => {
|
|
|
58
58
|
const segmentFraction = (distance - lengthBefore) / segmentLength
|
|
59
59
|
// add that fractional amount and return
|
|
60
60
|
return (targetIndex + segmentFraction) / segments
|
|
61
|
-
}
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
module.exports = arcLengthToT
|
|
63
|
+
module.exports = arcLengthToT
|
|
@@ -9,71 +9,71 @@ const { nearlyEqual } = require('../../../test/helpers/index')
|
|
|
9
9
|
test('calculate arcLengthToT for an 1D linear bezier with numeric control points', (t) => {
|
|
10
10
|
const bezierCurve = bezier.create([0, 10])
|
|
11
11
|
const len = length(100, bezierCurve)
|
|
12
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
13
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.5, 0.0001)
|
|
14
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
12
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
13
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.5, 0.0001)
|
|
14
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
15
15
|
t.true(true)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test('calculate arcLengthToT for an 1D linear bezier with array control points', (t) => {
|
|
19
19
|
const bezierCurve = bezier.create([[0], [10]])
|
|
20
20
|
const len = length(100, bezierCurve)
|
|
21
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
22
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.5, 0.0001)
|
|
23
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
21
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
22
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.5, 0.0001)
|
|
23
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
24
24
|
t.true(true)
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
test('calculate arcLengthToT for a 2D linear bezier', (t) => {
|
|
28
28
|
const bezierCurve = bezier.create([[0, 0], [10, 10]])
|
|
29
29
|
const len = length(100, bezierCurve)
|
|
30
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
31
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.5, 0.0001)
|
|
32
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
30
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
31
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.5, 0.0001)
|
|
32
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
33
33
|
t.true(true)
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
test('calculate arcLengthToT for a 2D quadratic (3 control points) bezier', (t) => {
|
|
37
37
|
const bezierCurve = bezier.create([[0, 0], [0, 10], [10, 10]])
|
|
38
38
|
const len = length(100, bezierCurve)
|
|
39
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
40
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.50001, 0.0001)
|
|
41
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
39
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
40
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.50001, 0.0001)
|
|
41
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
42
42
|
t.true(true)
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
test('calculate arcLengthToT for a 2D cubic (4 control points) bezier', (t) => {
|
|
46
46
|
const bezierCurve = bezier.create([[0, 0], [0, 10], [10, 10], [10, 0]])
|
|
47
47
|
const len = length(100, bezierCurve)
|
|
48
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
49
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.49999, 0.0001)
|
|
50
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
48
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
49
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.49999, 0.0001)
|
|
50
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
51
51
|
t.true(true)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
test('calculate arcLengthToT for a 3D linear bezier', (t) => {
|
|
55
55
|
const bezierCurve = bezier.create([[0, 0, 0], [10, 10, 10]])
|
|
56
56
|
const len = length(100, bezierCurve)
|
|
57
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
58
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.49999, 0.0001)
|
|
59
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
57
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
58
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.49999, 0.0001)
|
|
59
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
60
60
|
t.true(true)
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
test('calculate arcLengthToT for a 3D quadratic (3 control points) bezier', (t) => {
|
|
64
64
|
const bezierCurve = bezier.create([[0, 0, 0], [5, 5, 5], [0, 0, 10]])
|
|
65
65
|
const len = length(100, bezierCurve)
|
|
66
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
67
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.49999, 0.0001)
|
|
68
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
66
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
67
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.49999, 0.0001)
|
|
68
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
69
69
|
t.true(true)
|
|
70
70
|
})
|
|
71
71
|
|
|
72
72
|
test('calculate arcLengthToT for a 3D cubic (4 control points) bezier', (t) => {
|
|
73
73
|
const bezierCurve = bezier.create([[0, 0, 0], [5, 5, 5], [0, 0, 10], [-5, -5, 5]])
|
|
74
74
|
const len = length(100, bezierCurve)
|
|
75
|
-
nearlyEqual(t, arcLengthToT({distance: 0}, bezierCurve), 0, 0.0001)
|
|
76
|
-
nearlyEqual(t, arcLengthToT({distance: len / 2}, bezierCurve), 0.5621, 0.0001)
|
|
77
|
-
nearlyEqual(t, arcLengthToT({distance: len}, bezierCurve), 1, 0.0001)
|
|
75
|
+
nearlyEqual(t, arcLengthToT({ distance: 0 }, bezierCurve), 0, 0.0001)
|
|
76
|
+
nearlyEqual(t, arcLengthToT({ distance: len / 2 }, bezierCurve), 0.5621, 0.0001)
|
|
77
|
+
nearlyEqual(t, arcLengthToT({ distance: len }, bezierCurve), 1, 0.0001)
|
|
78
78
|
t.true(true)
|
|
79
|
-
})
|
|
79
|
+
})
|
|
@@ -7,14 +7,12 @@ const lengths = require('./lengths')
|
|
|
7
7
|
* @example
|
|
8
8
|
* const b = bezier.create([[0, 0], [0, 10]]);
|
|
9
9
|
* console.log(length(100, b)) // output 10
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* @param {Number} segments the number of segments to use when approximating the curve length.
|
|
12
12
|
* @param {Object} bezier a bezier curve.
|
|
13
13
|
* @returns an approximation of the curve's length.
|
|
14
14
|
* @alias module:modeling/curves/bezier.length
|
|
15
15
|
*/
|
|
16
|
-
const length = (segments, bezier) =>
|
|
17
|
-
return lengths(segments, bezier)[segments]
|
|
18
|
-
};
|
|
16
|
+
const length = (segments, bezier) => lengths(segments, bezier)[segments]
|
|
19
17
|
|
|
20
|
-
module.exports = length
|
|
18
|
+
module.exports = length
|
|
@@ -7,7 +7,7 @@ const { nearlyEqual } = require('../../../test/helpers/index')
|
|
|
7
7
|
|
|
8
8
|
test('calculate the length of an 1D linear bezier with numeric control points', (t) => {
|
|
9
9
|
const bezierCurve = bezier.create([0, 10])
|
|
10
|
-
nearlyEqual(t, length(100, bezierCurve), 10, 0.0001)
|
|
10
|
+
nearlyEqual(t, length(100, bezierCurve), 10, 0.0001)
|
|
11
11
|
t.true(true)
|
|
12
12
|
})
|
|
13
13
|
|
|
@@ -51,4 +51,4 @@ test('calculate the length of a 3D cubic (4 control points) bezier', (t) => {
|
|
|
51
51
|
const bezierCurve = bezier.create([[0, 0, 0], [5, 5, 5], [0, 0, 10], [-5, -5, 5]])
|
|
52
52
|
nearlyEqual(t, length(100, bezierCurve), 17.2116, 0.0001)
|
|
53
53
|
t.true(true)
|
|
54
|
-
})
|
|
54
|
+
})
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
const valueAt = require(
|
|
1
|
+
const valueAt = require('./valueAt')
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Divides the bezier curve into line segments and returns the cumulative length of those segments as an array.
|
|
5
5
|
* Utility function used to calculate the curve's approximate length and determine the equivalence between arc length and time.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
8
|
* const b = bezier.create([[0, 0], [0, 10]]);
|
|
9
9
|
* const totalLength = lengths(100, b).pop(); // the last element of the array is the curve's approximate length
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* @param {Number} segments the number of segments to use when approximating the curve length.
|
|
12
12
|
* @param {Object} bezier a bezier curve.
|
|
13
13
|
* @returns an array containing the cumulative length of the segments.
|
|
14
14
|
*/
|
|
15
15
|
const lengths = (segments, bezier) => {
|
|
16
16
|
let sum = 0
|
|
17
|
-
|
|
17
|
+
const lengths = [0]
|
|
18
18
|
let previous = valueAt(0, bezier)
|
|
19
19
|
for (let index = 1; index <= segments; index++) {
|
|
20
20
|
const current = valueAt(index / segments, bezier)
|
|
@@ -23,7 +23,7 @@ const lengths = (segments, bezier) => {
|
|
|
23
23
|
previous = current
|
|
24
24
|
}
|
|
25
25
|
return lengths
|
|
26
|
-
}
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Calculates the Euclidean distance between two n-dimensional points.
|
|
@@ -31,7 +31,7 @@ const lengths = (segments, bezier) => {
|
|
|
31
31
|
* @example
|
|
32
32
|
* const distance = distanceBetween([0, 0], [0, 10]); // calculate distance between 2D points
|
|
33
33
|
* console.log(distance); // output 10
|
|
34
|
-
*
|
|
34
|
+
*
|
|
35
35
|
* @param {Array} a - first operand.
|
|
36
36
|
* @param {Array} b - second operand.
|
|
37
37
|
* @returns {Number} - distance.
|
|
@@ -41,7 +41,7 @@ const distanceBetween = (a, b) => {
|
|
|
41
41
|
return Math.abs(a - b)
|
|
42
42
|
} else if (Array.isArray(a) && Array.isArray(b)) {
|
|
43
43
|
if (a.length !== b.length) {
|
|
44
|
-
throw new Error(
|
|
44
|
+
throw new Error('The operands must have the same number of dimensions.')
|
|
45
45
|
}
|
|
46
46
|
let sum = 0
|
|
47
47
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -49,8 +49,8 @@ const distanceBetween = (a, b) => {
|
|
|
49
49
|
}
|
|
50
50
|
return Math.sqrt(sum)
|
|
51
51
|
} else {
|
|
52
|
-
throw new Error(
|
|
52
|
+
throw new Error('The operands must be of the same type, either number or array.')
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|
|
55
55
|
|
|
56
|
-
module.exports = lengths
|
|
56
|
+
module.exports = lengths
|
|
@@ -17,7 +17,7 @@ test('calculate lengths for a 1D linear bezier with numeric control points', (t)
|
|
|
17
17
|
test('calculate lengths for a 1D linear bezier with array control points', (t) => {
|
|
18
18
|
const bezierCurve = bezier.create([[0], [10]])
|
|
19
19
|
const result = lengths(100, bezierCurve)
|
|
20
|
-
t.is(result.length, 101)
|
|
20
|
+
t.is(result.length, 101)
|
|
21
21
|
nearlyEqual(t, result[0], 0, 0.0001)
|
|
22
22
|
nearlyEqual(t, result[50], 5, 0.0001)
|
|
23
23
|
nearlyEqual(t, result[100], 10, 0.0001)
|
|
@@ -26,7 +26,7 @@ test('calculate lengths for a 1D linear bezier with array control points', (t) =
|
|
|
26
26
|
test('calculate lengths for a 2D linear bezier', (t) => {
|
|
27
27
|
const bezierCurve = bezier.create([[0, 0], [10, 10]])
|
|
28
28
|
const result = lengths(100, bezierCurve)
|
|
29
|
-
t.is(result.length, 101)
|
|
29
|
+
t.is(result.length, 101)
|
|
30
30
|
nearlyEqual(t, result[0], 0, 0.0001)
|
|
31
31
|
nearlyEqual(t, result[50], 7.0710, 0.0001)
|
|
32
32
|
nearlyEqual(t, result[100], 14.1421, 0.0001)
|
|
@@ -53,7 +53,7 @@ test('calculate lengths for a 2D cubic (4 control points) bezier', (t) => {
|
|
|
53
53
|
test('calculate lengths for a 3D linear bezier', (t) => {
|
|
54
54
|
const bezierCurve = bezier.create([[0, 0, 0], [10, 10, 10]])
|
|
55
55
|
const result = lengths(100, bezierCurve)
|
|
56
|
-
t.is(result.length, 101)
|
|
56
|
+
t.is(result.length, 101)
|
|
57
57
|
nearlyEqual(t, result[0], 0, 0.0001)
|
|
58
58
|
nearlyEqual(t, result[50], 8.6602, 0.0001)
|
|
59
59
|
nearlyEqual(t, result[100], 17.3205, 0.0001)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const mat4 = require('../../maths/mat4')
|
|
2
2
|
|
|
3
|
+
const reverse = require('./reverse.js')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Transform the given geometry using the given matrix.
|
|
5
7
|
* This is a lazy transform of the sides, as this function only adjusts the transforms.
|
|
@@ -14,7 +16,13 @@ const mat4 = require('../../maths/mat4')
|
|
|
14
16
|
*/
|
|
15
17
|
const transform = (matrix, geometry) => {
|
|
16
18
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
17
|
-
|
|
19
|
+
const transformed = Object.assign({}, geometry, { transforms })
|
|
20
|
+
// determine if the transform is mirroring in 2D
|
|
21
|
+
if (matrix[0] * matrix[5] - matrix[4] * matrix[1] < 0) {
|
|
22
|
+
// reverse the order to preserve the orientation
|
|
23
|
+
return reverse(transformed)
|
|
24
|
+
}
|
|
25
|
+
return transformed
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
module.exports = transform
|
|
@@ -2,7 +2,13 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const mat4 = require('../../maths/mat4')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { measureArea } = require('../../measurements/index.js')
|
|
6
|
+
|
|
7
|
+
const { mirrorX, mirrorY, mirrorZ } = require('../../operations/transforms/index.js')
|
|
8
|
+
|
|
9
|
+
const { square } = require('../../primitives/index.js')
|
|
10
|
+
|
|
11
|
+
const { fromPoints, transform, toOutlines, toSides } = require('./index.js')
|
|
6
12
|
|
|
7
13
|
const { comparePoints, compareVectors } = require('../../../test/helpers/')
|
|
8
14
|
|
|
@@ -51,3 +57,54 @@ test('transform: adjusts the transforms of geom2', (t) => {
|
|
|
51
57
|
t.true(comparePoints(another.sides[2], expected.sides[2]))
|
|
52
58
|
t.true(compareVectors(another.transforms, expected.transforms))
|
|
53
59
|
})
|
|
60
|
+
|
|
61
|
+
test('transform: geom2 mirrorX', (t) => {
|
|
62
|
+
const geometry = square()
|
|
63
|
+
const transformed = mirrorX(geometry)
|
|
64
|
+
t.is(measureArea(geometry), 4)
|
|
65
|
+
// area will be negative unless we reversed the points
|
|
66
|
+
t.is(measureArea(transformed), 4)
|
|
67
|
+
const pts = toOutlines(transformed)[0]
|
|
68
|
+
const exp = [[-1, 1], [-1, -1], [1, -1], [1, 1]]
|
|
69
|
+
t.true(comparePoints(pts, exp))
|
|
70
|
+
t.deepEqual(toSides(transformed), [
|
|
71
|
+
[[1, 1], [-1, 1]],
|
|
72
|
+
[[-1, 1], [-1, -1]],
|
|
73
|
+
[[-1, -1], [1, -1]],
|
|
74
|
+
[[1, -1], [1, 1]]
|
|
75
|
+
])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('transform: geom2 mirrorY', (t) => {
|
|
79
|
+
const geometry = square()
|
|
80
|
+
const transformed = mirrorY(geometry)
|
|
81
|
+
t.is(measureArea(geometry), 4)
|
|
82
|
+
// area will be negative unless we reversed the points
|
|
83
|
+
t.is(measureArea(transformed), 4)
|
|
84
|
+
const pts = toOutlines(transformed)[0]
|
|
85
|
+
const exp = [[1, -1], [1, 1], [-1, 1], [-1, -1]]
|
|
86
|
+
t.true(comparePoints(pts, exp))
|
|
87
|
+
t.deepEqual(toSides(transformed), [
|
|
88
|
+
[[-1, -1], [1, -1]],
|
|
89
|
+
[[1, -1], [1, 1]],
|
|
90
|
+
[[1, 1], [-1, 1]],
|
|
91
|
+
[[-1, 1], [-1, -1]]
|
|
92
|
+
])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('transform: geom2 mirrorZ', (t) => {
|
|
96
|
+
const geometry = square()
|
|
97
|
+
const transformed = mirrorZ(geometry)
|
|
98
|
+
t.is(measureArea(geometry), 4)
|
|
99
|
+
// area will be negative unless we DIDN'T reverse the points
|
|
100
|
+
t.is(measureArea(transformed), 4)
|
|
101
|
+
const pts = toOutlines(transformed)[0]
|
|
102
|
+
const exp = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
|
|
103
|
+
t.true(comparePoints(pts, exp))
|
|
104
|
+
t.deepEqual(toSides(transformed), [
|
|
105
|
+
[[-1, 1], [-1, -1]],
|
|
106
|
+
[[-1, -1], [1, -1]],
|
|
107
|
+
[[1, -1], [1, 1]],
|
|
108
|
+
[[1, 1], [-1, 1]]
|
|
109
|
+
])
|
|
110
|
+
})
|
|
@@ -80,11 +80,4 @@ const isPointInside = (point, polygon) => {
|
|
|
80
80
|
return insideFlag
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/*
|
|
84
|
-
* > 0 : p2 is left of the line p0 -> p1
|
|
85
|
-
* = 0 : p2 is on the line p0 -> p1
|
|
86
|
-
* < 0 : p2 is right of the line p0 -> p1
|
|
87
|
-
*/
|
|
88
|
-
const isLeft = (p0, p1, p2) => (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1])
|
|
89
|
-
|
|
90
83
|
module.exports = arePointsInside
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const vec3 = require('../../maths/vec3')
|
|
2
1
|
const vec4 = require('../../maths/vec4')
|
|
3
2
|
|
|
4
3
|
const cache = new WeakMap()
|
|
@@ -10,7 +9,7 @@ const cache = new WeakMap()
|
|
|
10
9
|
* @alias module:modeling/geometries/poly3.measureBoundingSphere
|
|
11
10
|
*/
|
|
12
11
|
const measureBoundingSphere = (polygon) => {
|
|
13
|
-
|
|
12
|
+
const boundingSphere = cache.get(polygon)
|
|
14
13
|
if (boundingSphere) return boundingSphere
|
|
15
14
|
|
|
16
15
|
const vertices = polygon.vertices
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const vec3 = require('../vec3')
|
|
2
|
+
const fromNormalAndPoint = require('./fromNormalAndPoint')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a best-fit plane from the given noisy vertices.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: There are two possible orientations for every plane.
|
|
8
|
+
* This function always produces positive orientations.
|
|
9
|
+
*
|
|
10
|
+
* See http://www.ilikebigbits.com for the original discussion
|
|
11
|
+
*
|
|
12
|
+
* @param {Plane} out - receiving plane
|
|
13
|
+
* @param {Array} vertices - list of vertices in any order or position
|
|
14
|
+
* @returns {Plane} out
|
|
15
|
+
* @alias module:modeling/maths/plane.fromNoisyPoints
|
|
16
|
+
*/
|
|
17
|
+
const fromNoisyPoints = (out, ...vertices) => {
|
|
18
|
+
out[0] = 0.0
|
|
19
|
+
out[1] = 0.0
|
|
20
|
+
out[2] = 0.0
|
|
21
|
+
out[3] = 0.0
|
|
22
|
+
|
|
23
|
+
// calculate the centroid of the vertices
|
|
24
|
+
// NOTE: out is the centriod
|
|
25
|
+
const n = vertices.length
|
|
26
|
+
vertices.forEach((v) => {
|
|
27
|
+
vec3.add(out, out, v)
|
|
28
|
+
})
|
|
29
|
+
vec3.scale(out, out, 1.0 / n)
|
|
30
|
+
|
|
31
|
+
// Calculate full 3x3 covariance matrix, excluding symmetries
|
|
32
|
+
let xx = 0.0
|
|
33
|
+
let xy = 0.0
|
|
34
|
+
let xz = 0.0
|
|
35
|
+
let yy = 0.0
|
|
36
|
+
let yz = 0.0
|
|
37
|
+
let zz = 0.0
|
|
38
|
+
|
|
39
|
+
const vn = vec3.create()
|
|
40
|
+
vertices.forEach((v) => {
|
|
41
|
+
// NOTE: out is the centriod
|
|
42
|
+
vec3.subtract(vn, v, out)
|
|
43
|
+
xx += vn[0] * vn[0]
|
|
44
|
+
xy += vn[0] * vn[1]
|
|
45
|
+
xz += vn[0] * vn[2]
|
|
46
|
+
yy += vn[1] * vn[1]
|
|
47
|
+
yz += vn[1] * vn[2]
|
|
48
|
+
zz += vn[2] * vn[2]
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
xx /= n
|
|
52
|
+
xy /= n
|
|
53
|
+
xz /= n
|
|
54
|
+
yy /= n
|
|
55
|
+
yz /= n
|
|
56
|
+
zz /= n
|
|
57
|
+
|
|
58
|
+
// Calculate the smallest Eigenvector of the covariance matrix
|
|
59
|
+
// which becomes the plane normal
|
|
60
|
+
|
|
61
|
+
vn[0] = 0.0
|
|
62
|
+
vn[1] = 0.0
|
|
63
|
+
vn[2] = 0.0
|
|
64
|
+
|
|
65
|
+
// weighted directional vector
|
|
66
|
+
const wdv = vec3.create()
|
|
67
|
+
|
|
68
|
+
// X axis
|
|
69
|
+
let det = yy * zz - yz * yz
|
|
70
|
+
wdv[0] = det
|
|
71
|
+
wdv[1] = xz * yz - xy * zz
|
|
72
|
+
wdv[2] = xy * yz - xz * yy
|
|
73
|
+
|
|
74
|
+
let weight = det * det
|
|
75
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
76
|
+
|
|
77
|
+
// Y axis
|
|
78
|
+
det = xx * zz - xz * xz
|
|
79
|
+
wdv[0] = xz * yz - xy * zz
|
|
80
|
+
wdv[1] = det
|
|
81
|
+
wdv[2] = xy * xz - yz * xx
|
|
82
|
+
|
|
83
|
+
weight = det * det
|
|
84
|
+
if (vec3.dot(vn, wdv) < 0.0) {
|
|
85
|
+
weight = -weight
|
|
86
|
+
}
|
|
87
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
88
|
+
|
|
89
|
+
// Z axis
|
|
90
|
+
det = xx * yy - xy * xy
|
|
91
|
+
wdv[0] = xy * yz - xz * yy
|
|
92
|
+
wdv[1] = xy * xz - yz * xx
|
|
93
|
+
wdv[2] = det
|
|
94
|
+
|
|
95
|
+
weight = det * det
|
|
96
|
+
if (vec3.dot(vn, wdv) < 0.0) {
|
|
97
|
+
weight = -weight
|
|
98
|
+
}
|
|
99
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
100
|
+
|
|
101
|
+
// create the plane from normal and centriod
|
|
102
|
+
// NOTE: out is the centriod
|
|
103
|
+
return fromNormalAndPoint(out, vn, out)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = fromNoisyPoints
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
const { fromNoisyPoints, create } = require('./index')
|
|
3
|
+
|
|
4
|
+
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
|
+
|
|
6
|
+
test('plane: fromNoisyPoints() should return a new plane with correct values', (t) => {
|
|
7
|
+
const obs1 = fromNoisyPoints(create(), [0, 0, 0], [1, 0, 0], [1, 1, 0])
|
|
8
|
+
t.true(compareVectors(obs1, [0, 0, 1, 0]))
|
|
9
|
+
|
|
10
|
+
const obs2 = fromNoisyPoints(obs1, [0, 6, 0], [0, 2, 2], [0, 6, 6])
|
|
11
|
+
t.true(compareVectors(obs2, [1, 0, 0, 0]))
|
|
12
|
+
|
|
13
|
+
// same vertices results in an invalid plane
|
|
14
|
+
const obs3 = fromNoisyPoints(obs1, [0, 6, 0], [0, 6, 0], [0, 6, 0])
|
|
15
|
+
t.true(compareVectors(obs3, [0 / 0, 0 / 0, 0 / 0, 0 / 0]))
|
|
16
|
+
|
|
17
|
+
// co-linear vertices
|
|
18
|
+
const obs4 = fromNoisyPoints(obs1, [0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0])
|
|
19
|
+
t.true(compareVectors(obs4, [0, 0, 1, 0]))
|
|
20
|
+
|
|
21
|
+
// random vertices
|
|
22
|
+
const obs5 = fromNoisyPoints(obs1, [0, 0, 0], [5, 1, -2], [3, -2, 4], [1, 1, 0])
|
|
23
|
+
t.true(compareVectors(obs5, [0.08054818365229491, 0.8764542170444571, 0.47469990050062555, 0.4185833634679763]))
|
|
24
|
+
})
|
|
@@ -5,6 +5,7 @@ export { default as equals } from './equals'
|
|
|
5
5
|
export { default as flip } from './flip'
|
|
6
6
|
export { default as fromNormalAndPoint } from './fromNormalAndPoint'
|
|
7
7
|
export { default as fromValues } from './fromValues'
|
|
8
|
+
export { default as fromNoisyPoints } from './fromNoisyPoints'
|
|
8
9
|
export { default as fromPoints } from './fromPoints'
|
|
9
10
|
export { default as fromPointsRandom } from './fromPointsRandom'
|
|
10
11
|
export { default as signedDistanceToPoint } from './signedDistanceToPoint'
|
package/src/maths/plane/index.js
CHANGED
|
@@ -32,6 +32,7 @@ module.exports = {
|
|
|
32
32
|
* @function fromValues
|
|
33
33
|
*/
|
|
34
34
|
fromValues: require('../vec4/fromValues'),
|
|
35
|
+
fromNoisyPoints: require('./fromNoisyPoints'),
|
|
35
36
|
fromPoints: require('./fromPoints'),
|
|
36
37
|
fromPointsRandom: require('./fromPointsRandom'),
|
|
37
38
|
projectionOfPoint: require('./projectionOfPoint'),
|
|
@@ -25,7 +25,7 @@ class PolygonTreeNode {
|
|
|
25
25
|
this.parent = parent
|
|
26
26
|
this.children = []
|
|
27
27
|
this.polygon = polygon
|
|
28
|
-
this.removed = false
|
|
28
|
+
this.removed = false // state of branch or leaf
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// fill the tree with polygons. Should be called on the root node only; child nodes must
|
|
@@ -53,7 +53,7 @@ test('expand: round-expanding a bent line produces expected geometry', (t) => {
|
|
|
53
53
|
const expandedPoints = geom2.toPoints(expandedPathGeom2)
|
|
54
54
|
|
|
55
55
|
t.notThrows(() => geom2.validate(expandedPathGeom2))
|
|
56
|
-
const expectedArea = 56 + TAU
|
|
56
|
+
const expectedArea = 56 + TAU * delta * 1.25 // shape will have 1 and 1/4 circles
|
|
57
57
|
nearlyEqual(t, area(expandedPoints), expectedArea, 0.01, 'Measured area should be pretty close')
|
|
58
58
|
const boundingBox = measureBoundingBox(expandedPathGeom2)
|
|
59
59
|
t.true(comparePoints(boundingBox, [[-7, -2, 0], [2, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -40,7 +40,7 @@ const extrudeHelical = (options, geometry) => {
|
|
|
40
40
|
|
|
41
41
|
let pitch
|
|
42
42
|
// ignore height if pitch is set
|
|
43
|
-
if(!options.pitch && options.height) {
|
|
43
|
+
if (!options.pitch && options.height) {
|
|
44
44
|
pitch = options.height / (angle / TAU)
|
|
45
45
|
} else {
|
|
46
46
|
pitch = options.pitch ? options.pitch : defaults.pitch
|
|
@@ -49,18 +49,17 @@ const extrudeHelical = (options, geometry) => {
|
|
|
49
49
|
// needs at least 3 segments for each revolution
|
|
50
50
|
const minNumberOfSegments = 3
|
|
51
51
|
|
|
52
|
-
if (segmentsPerRotation < minNumberOfSegments)
|
|
53
|
-
throw new Error(`The number of segments per rotation needs to be at least 3.`)
|
|
52
|
+
if (segmentsPerRotation < minNumberOfSegments) { throw new Error('The number of segments per rotation needs to be at least 3.') }
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
const shapeSides = geom2.toSides(geometry)
|
|
56
55
|
if (shapeSides.length === 0) throw new Error('the given geometry cannot be empty')
|
|
57
56
|
|
|
58
57
|
// const pointsWithNegativeX = shapeSides.filter((s) => (s[0][0] < 0))
|
|
59
58
|
const pointsWithPositiveX = shapeSides.filter((s) => (s[0][0] >= 0))
|
|
60
|
-
|
|
59
|
+
|
|
61
60
|
let baseSlice = slice.fromSides(shapeSides)
|
|
62
|
-
|
|
63
|
-
if(pointsWithPositiveX.length === 0) {
|
|
61
|
+
|
|
62
|
+
if (pointsWithPositiveX.length === 0) {
|
|
64
63
|
// only points in negative x plane, reverse
|
|
65
64
|
baseSlice = slice.reverse(baseSlice)
|
|
66
65
|
}
|