@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/jscad-modeling.min.js +338 -335
  3. package/package.json +2 -2
  4. package/src/curves/bezier/arcLengthToT.js +6 -6
  5. package/src/curves/bezier/arcLengthToT.test.js +25 -25
  6. package/src/curves/bezier/length.js +3 -5
  7. package/src/curves/bezier/length.test.js +2 -2
  8. package/src/curves/bezier/lengths.js +10 -10
  9. package/src/curves/bezier/lengths.test.js +3 -3
  10. package/src/geometries/geom2/transform.js +9 -1
  11. package/src/geometries/geom2/transform.test.js +58 -1
  12. package/src/geometries/poly2/arePointsInside.js +0 -7
  13. package/src/geometries/poly3/measureBoundingSphere.js +1 -2
  14. package/src/maths/plane/fromNoisyPoints.d.ts +6 -0
  15. package/src/maths/plane/fromNoisyPoints.js +106 -0
  16. package/src/maths/plane/fromNoisyPoints.test.js +24 -0
  17. package/src/maths/plane/index.d.ts +1 -0
  18. package/src/maths/plane/index.js +1 -0
  19. package/src/operations/booleans/trees/PolygonTreeNode.js +1 -1
  20. package/src/operations/expansions/expand.test.js +1 -1
  21. package/src/operations/extrusions/extrudeHelical.js +6 -7
  22. package/src/operations/extrusions/extrudeHelical.test.js +35 -37
  23. package/src/operations/extrusions/extrudeRotate.js +1 -1
  24. package/src/operations/extrusions/index.d.ts +1 -0
  25. package/src/operations/hulls/hullPoints2.js +3 -2
  26. package/src/operations/modifiers/index.js +1 -1
  27. package/src/operations/modifiers/retessellate.js +66 -27
  28. package/src/operations/transforms/mirror.test.js +9 -3
  29. package/src/primitives/circle.js +2 -2
  30. package/src/primitives/circle.test.js +7 -0
  31. package/src/primitives/cube.js +2 -2
  32. package/src/primitives/cube.test.js +7 -0
  33. package/src/primitives/cuboid.js +4 -1
  34. package/src/primitives/cuboid.test.js +7 -0
  35. package/src/primitives/cylinder.js +7 -2
  36. package/src/primitives/cylinder.test.js +14 -0
  37. package/src/primitives/ellipse.js +4 -1
  38. package/src/primitives/ellipse.test.js +7 -0
  39. package/src/primitives/ellipsoid.js +4 -1
  40. package/src/primitives/ellipsoid.test.js +7 -0
  41. package/src/primitives/geodesicSphere.js +5 -2
  42. package/src/primitives/geodesicSphere.test.js +7 -0
  43. package/src/primitives/polygon.d.ts +1 -0
  44. package/src/primitives/polygon.js +15 -4
  45. package/src/primitives/polygon.test.js +10 -0
  46. package/src/primitives/rectangle.js +4 -1
  47. package/src/primitives/rectangle.test.js +7 -0
  48. package/src/primitives/roundedCuboid.js +10 -3
  49. package/src/primitives/roundedCuboid.test.js +14 -0
  50. package/src/primitives/roundedCylinder.js +12 -5
  51. package/src/primitives/roundedCylinder.test.js +21 -0
  52. package/src/primitives/roundedRectangle.js +10 -3
  53. package/src/primitives/roundedRectangle.test.js +14 -0
  54. package/src/primitives/sphere.js +2 -2
  55. package/src/primitives/sphere.test.js +7 -0
  56. package/src/primitives/square.js +2 -2
  57. 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.11.1",
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": "4313974b50957018d2edd010e3a251f59bea46a4"
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("./valueAt")
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
- let lengths = [0]
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("The operands must have the same number of dimensions.")
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("The operands must be of the same type, either number or array.")
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
- return Object.assign({}, geometry, { transforms })
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 { transform, fromPoints, toSides } = require('./index')
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
- let boundingSphere = cache.get(polygon)
12
+ const boundingSphere = cache.get(polygon)
14
13
  if (boundingSphere) return boundingSphere
15
14
 
16
15
  const vertices = polygon.vertices
@@ -0,0 +1,6 @@
1
+ import Plane from './type'
2
+ import Vec3 from '../vec3/type'
3
+
4
+ export default fromNoisyPoints
5
+
6
+ declare function fromNoisyPoints(out: Plane, ...vertices: Array<Vec3>): Plane
@@ -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'
@@ -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 // state of branch or leaf
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 * delta * 1.25 // shape will have 1 and 1/4 circles
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
- let shapeSides = geom2.toSides(geometry)
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
  }