@jscad/modeling 2.9.0 → 2.9.3

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 (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +4 -4
  3. package/dist/jscad-modeling.min.js +437 -428
  4. package/package.json +3 -2
  5. package/src/colors/colorize.test.js +1 -1
  6. package/src/geometries/geom2/index.d.ts +1 -0
  7. package/src/geometries/geom2/index.js +2 -1
  8. package/src/geometries/geom2/toOutlines.js +66 -52
  9. package/src/geometries/geom2/validate.d.ts +3 -0
  10. package/src/geometries/geom2/validate.js +36 -0
  11. package/src/geometries/geom3/create.js +1 -1
  12. package/src/geometries/geom3/create.test.js +1 -1
  13. package/src/geometries/geom3/fromPoints.js +1 -1
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +2 -1
  16. package/src/geometries/geom3/isA.js +1 -1
  17. package/src/geometries/geom3/validate.d.ts +3 -0
  18. package/src/geometries/geom3/validate.js +62 -0
  19. package/src/geometries/path2/index.d.ts +1 -1
  20. package/src/geometries/path2/index.js +2 -2
  21. package/src/geometries/path2/validate.d.ts +3 -0
  22. package/src/geometries/path2/validate.js +41 -0
  23. package/src/geometries/poly2/arePointsInside.js +0 -35
  24. package/src/geometries/poly3/create.js +1 -1
  25. package/src/geometries/poly3/index.d.ts +1 -0
  26. package/src/geometries/poly3/index.js +2 -1
  27. package/src/geometries/poly3/measureArea.test.js +16 -16
  28. package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
  29. package/src/geometries/poly3/validate.d.ts +4 -0
  30. package/src/geometries/poly3/validate.js +64 -0
  31. package/src/maths/constants.d.ts +1 -0
  32. package/src/maths/constants.js +11 -0
  33. package/src/maths/utils/aboutEqualNormals.js +1 -5
  34. package/src/measurements/measureCenterOfMass.test.js +2 -2
  35. package/src/operations/booleans/intersect.test.js +8 -0
  36. package/src/operations/booleans/intersectGeom3.js +2 -1
  37. package/src/operations/booleans/scission.test.js +4 -4
  38. package/src/operations/booleans/subtract.test.js +8 -0
  39. package/src/operations/booleans/subtractGeom3.js +2 -1
  40. package/src/operations/booleans/to3DWalls.js +1 -1
  41. package/src/operations/booleans/trees/Node.js +10 -16
  42. package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
  43. package/src/operations/booleans/trees/Tree.js +1 -2
  44. package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
  45. package/src/operations/booleans/union.test.js +27 -0
  46. package/src/operations/booleans/unionGeom3.js +2 -1
  47. package/src/operations/expansions/expand.test.js +30 -21
  48. package/src/operations/expansions/expandGeom3.test.js +14 -14
  49. package/src/operations/expansions/expandShell.js +5 -4
  50. package/src/operations/expansions/extrudePolygon.js +7 -7
  51. package/src/operations/expansions/offset.test.js +25 -0
  52. package/src/operations/extrusions/earcut/assignHoles.js +7 -3
  53. package/src/operations/extrusions/earcut/assignHoles.test.js +50 -4
  54. package/src/operations/extrusions/earcut/linkedList.js +1 -1
  55. package/src/operations/extrusions/extrudeFromSlices.test.js +16 -10
  56. package/src/operations/extrusions/extrudeLinear.test.js +15 -9
  57. package/src/operations/extrusions/extrudeRectangular.test.js +15 -8
  58. package/src/operations/extrusions/extrudeRotate.js +5 -1
  59. package/src/operations/extrusions/extrudeRotate.test.js +12 -0
  60. package/src/operations/extrusions/extrudeWalls.js +2 -2
  61. package/src/operations/extrusions/project.js +11 -14
  62. package/src/operations/extrusions/project.test.js +55 -55
  63. package/src/operations/hulls/hull.test.js +24 -1
  64. package/src/operations/hulls/hullChain.test.js +6 -4
  65. package/src/operations/hulls/hullGeom2.js +6 -18
  66. package/src/operations/hulls/hullGeom3.js +5 -18
  67. package/src/operations/hulls/hullPath2.js +4 -14
  68. package/src/operations/hulls/hullPath2.test.js +1 -1
  69. package/src/operations/hulls/hullPoints2.js +43 -92
  70. package/src/operations/hulls/toUniquePoints.js +34 -0
  71. package/src/operations/modifiers/generalize.js +2 -13
  72. package/src/operations/modifiers/generalize.test.js +5 -31
  73. package/src/operations/modifiers/insertTjunctions.js +1 -1
  74. package/src/operations/modifiers/insertTjunctions.test.js +21 -21
  75. package/src/operations/modifiers/mergePolygons.js +11 -14
  76. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +1 -1
  77. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
  78. package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
  79. package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
  80. package/src/operations/modifiers/snapPolygons.test.js +12 -12
  81. package/src/operations/modifiers/triangulatePolygons.js +3 -3
  82. package/src/operations/transforms/align.test.js +12 -0
  83. package/src/operations/transforms/center.js +1 -1
  84. package/src/operations/transforms/center.test.js +12 -0
  85. package/src/operations/transforms/mirror.test.js +16 -0
  86. package/src/operations/transforms/rotate.test.js +10 -0
  87. package/src/operations/transforms/scale.test.js +15 -0
  88. package/src/operations/transforms/transform.test.js +5 -0
  89. package/src/operations/transforms/translate.test.js +16 -0
  90. package/src/primitives/arc.test.js +11 -0
  91. package/src/primitives/circle.test.js +15 -9
  92. package/src/primitives/cube.test.js +3 -0
  93. package/src/primitives/cuboid.js +1 -1
  94. package/src/primitives/cuboid.test.js +9 -24
  95. package/src/primitives/cylinder.test.js +7 -4
  96. package/src/primitives/cylinderElliptic.js +14 -7
  97. package/src/primitives/cylinderElliptic.test.js +72 -50
  98. package/src/primitives/ellipse.js +3 -1
  99. package/src/primitives/ellipse.test.js +14 -8
  100. package/src/primitives/ellipsoid.js +8 -6
  101. package/src/primitives/ellipsoid.test.js +84 -80
  102. package/src/primitives/geodesicSphere.test.js +3 -0
  103. package/src/primitives/line.test.js +1 -0
  104. package/src/primitives/polygon.test.js +15 -10
  105. package/src/primitives/polyhedron.js +1 -1
  106. package/src/primitives/polyhedron.test.js +14 -42
  107. package/src/primitives/rectangle.test.js +3 -0
  108. package/src/primitives/roundedCuboid.js +6 -6
  109. package/src/primitives/roundedCuboid.test.js +5 -0
  110. package/src/primitives/roundedCylinder.js +7 -5
  111. package/src/primitives/roundedCylinder.test.js +40 -36
  112. package/src/primitives/roundedRectangle.test.js +5 -0
  113. package/src/primitives/sphere.test.js +52 -73
  114. package/src/primitives/square.test.js +3 -0
  115. package/src/primitives/star.test.js +6 -0
  116. package/src/primitives/torus.test.js +8 -1
  117. package/src/primitives/triangle.js +1 -2
  118. package/src/primitives/triangle.test.js +7 -0
  119. package/src/utils/areAllShapesTheSameType.js +2 -2
  120. package/src/utils/areAllShapesTheSameType.test.js +17 -0
  121. package/src/utils/index.d.ts +1 -0
  122. package/src/utils/index.js +3 -1
  123. package/src/utils/trigonometry.d.ts +2 -0
  124. package/src/utils/trigonometry.js +34 -0
  125. package/src/utils/trigonometry.test.js +25 -0
  126. package/test/helpers/nearlyEqual.js +4 -1
  127. package/src/geometries/path2/eachPoint.d.ts +0 -9
  128. package/src/geometries/path2/eachPoint.js +0 -17
  129. package/src/geometries/path2/eachPoint.test.js +0 -11
  130. package/src/operations/modifiers/edges.js +0 -195
  131. package/src/operations/modifiers/repairTjunctions.js +0 -44
@@ -1,108 +1,59 @@
1
1
  const vec2 = require('../../maths/vec2')
2
2
 
3
- const angleBetweenPoints = (p0, p1) => Math.atan2((p1[1] - p0[1]), (p1[0] - p0[0]))
4
-
5
- const compareIndex = (index1, index2) => {
6
- if (index1.angle < index2.angle) {
7
- return -1
8
- } else if (index1.angle > index2.angle) {
9
- return 1
10
- } else {
11
- if (index1.distance < index2.distance) {
12
- return -1
13
- } else if (index1.distance > index2.distance) {
14
- return 1
15
- }
16
- }
17
- return 0
18
- }
19
-
20
- // Ported from https://github.com/bkiers/GrahamScan
21
- const compute = (points) => {
22
- if (points.length < 3) {
23
- return points
24
- }
25
-
26
- // Find the lowest point
27
- let min = 0
28
- points.forEach((point, i) => {
29
- const minpoint = points[min]
30
- if (point[1] === minpoint[1]) {
31
- if (point[0] < minpoint[0]) {
32
- min = i
33
- }
34
- } else if (point[1] < minpoint[1]) {
35
- min = i
3
+ /*
4
+ * Create a convex hull of the given set of points, where each point is an array of [x,y].
5
+ * Uses https://en.wikipedia.org/wiki/Graham_scan
6
+ * @param {Array} uniquePoints - list of UNIQUE points from which to create a hull
7
+ * @returns {Array} a list of points that form the hull
8
+ */
9
+ const hullPoints2 = (uniquePoints) => {
10
+ // find min point
11
+ let min = vec2.fromValues(Infinity, Infinity)
12
+ uniquePoints.forEach((point) => {
13
+ if (point[1] < min[1] || (point[1] === min[1] && point[0] < min[0])) {
14
+ min = point
36
15
  }
37
16
  })
38
17
 
39
- // Calculate angles and distances from the lowest point
40
- const al = []
41
- let angle = 0.0
42
- let dist = 0.0
43
- for (let i = 0; i < points.length; i++) {
44
- if (i === min) {
45
- continue
46
- }
47
- angle = angleBetweenPoints(points[min], points[i])
48
- if (angle < 0) {
49
- angle += Math.PI
50
- }
51
- dist = vec2.squaredDistance(points[min], points[i])
52
- al.push({ index: i, angle: angle, distance: dist })
53
- }
54
-
55
- al.sort((a, b) => compareIndex(a, b))
56
-
57
- // Wind around the points CCW, removing interior points
58
- const stack = new Array(points.length + 1)
59
- let j = 2
60
- for (let i = 0; i < points.length; i++) {
61
- if (i === min) {
62
- continue
63
- }
64
- stack[j] = al[j - 2].index
65
- j++
66
- }
67
- stack[0] = stack[points.length]
68
- stack[1] = min
18
+ // gather information for sorting by polar coordinates (point, angle, distSq)
19
+ const points = []
20
+ uniquePoints.forEach((point) => {
21
+ // use faster fakeAtan2 instead of Math.atan2
22
+ const angle = fakeAtan2(point[1] - min[1], point[0] - min[0])
23
+ const distSq = vec2.squaredDistance(point, min)
24
+ points.push({ point, angle, distSq })
25
+ })
69
26
 
70
- // clockwise < 0, colinear = 0, counter clockwise > 0
71
- const ccw = (i1, i2, i3) => (points[i2][0] - points[i1][0]) * (points[i3][1] - points[i1][1]) - (points[i2][1] - points[i1][1]) * (points[i3][0] - points[i1][0])
27
+ // sort by polar coordinates
28
+ points.sort((pt1, pt2) => pt1.angle < pt2.angle ? -1 : pt1.angle > pt2.angle ? 1 :
29
+ pt1.distSq < pt2.distSq ? -1 : pt1.distSq > pt2.distSq ? 1 : 0)
72
30
 
73
- let tmp
74
- let M = 2
75
- for (let i = 3; i <= points.length; i++) {
76
- while (ccw(stack[M - 1], stack[M], stack[i]) < Number.EPSILON) {
77
- M--
31
+ const stack = [] // start with empty stack
32
+ points.forEach((point) => {
33
+ let cnt = stack.length
34
+ while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point.point) <= Number.EPSILON) {
35
+ stack.pop() // get rid of colinear and interior (clockwise) points
36
+ cnt = stack.length
78
37
  }
79
- M++
80
- tmp = stack[i]
81
- stack[i] = stack[M]
82
- stack[M] = tmp
83
- }
38
+ stack.push(point.point)
39
+ })
84
40
 
85
- // Return the indices to the points
86
- const indices = new Array(M)
87
- for (let i = 0; i < M; i++) {
88
- indices[i] = stack[i + 1]
89
- }
90
- return indices
41
+ return stack
91
42
  }
92
43
 
93
- /*
94
- * Create a convex hull of the given set of points, where each point is an array of [x,y].
95
- * @param {Array} uniquepoints - list of UNIQUE points from which to create a hull
96
- * @returns {Array} a list of points that form the hull
97
- */
98
- const hullPoints2 = (uniquepoints) => {
99
- const indices = compute(uniquepoints)
44
+ // returns: < 0 clockwise, 0 colinear, > 0 counter-clockwise
45
+ const ccw = (v1, v2, v3) => (v2[0] - v1[0]) * (v3[1] - v1[1]) - (v2[1] - v1[1]) * (v3[0] - v1[0])
100
46
 
101
- let hullpoints = []
102
- if (Array.isArray(indices)) {
103
- hullpoints = indices.map((index) => uniquepoints[index])
47
+ // Returned "angle" is really 1/tan (inverse of slope) made negative to increase with angle.
48
+ // This function is strictly for sorting in this algorithm.
49
+ const fakeAtan2 = (y, x) => {
50
+ // The "if" is a special case for when the minimum vector found in loop above is present.
51
+ // We need to ensure that it sorts as the minimum point. Otherwise, this becomes NaN.
52
+ if (y === 0 && x === 0) {
53
+ return -Infinity
54
+ } else {
55
+ return -x / y
104
56
  }
105
- return hullpoints
106
57
  }
107
58
 
108
59
  module.exports = hullPoints2
@@ -0,0 +1,34 @@
1
+ const geom2 = require('../../geometries/geom2')
2
+ const geom3 = require('../../geometries/geom3')
3
+ const path2 = require('../../geometries/path2')
4
+
5
+ /*
6
+ * Return the unique vertices of a geometry
7
+ */
8
+ const toUniquePoints = (geometries) => {
9
+ const found = new Set()
10
+ const uniquePoints = []
11
+
12
+ const addPoint = (point) => {
13
+ const key = point.toString()
14
+ if (!found.has(key)) {
15
+ uniquePoints.push(point)
16
+ found.add(key)
17
+ }
18
+ }
19
+
20
+ geometries.forEach((geometry) => {
21
+ if (geom2.isA(geometry)) {
22
+ geom2.toPoints(geometry).forEach(addPoint)
23
+ } else if (geom3.isA(geometry)) {
24
+ // points are grouped by polygon
25
+ geom3.toPoints(geometry).forEach((points) => points.forEach(addPoint))
26
+ } else if (path2.isA(geometry)) {
27
+ path2.toPoints(geometry).forEach(addPoint)
28
+ }
29
+ })
30
+
31
+ return uniquePoints
32
+ }
33
+
34
+ module.exports = toUniquePoints
@@ -11,8 +11,6 @@ const mergePolygons = require('./mergePolygons')
11
11
  const insertTjunctions = require('./insertTjunctions')
12
12
  const triangulatePolygons = require('./triangulatePolygons')
13
13
 
14
- const repairTjunctions = require('./repairTjunctions')
15
-
16
14
  /*
17
15
  */
18
16
  const generalizePath2 = (options, geometry) => geometry
@@ -27,10 +25,9 @@ const generalizeGeom3 = (options, geometry) => {
27
25
  const defaults = {
28
26
  snap: false,
29
27
  simplify: false,
30
- triangulate: false,
31
- repair: false
28
+ triangulate: false
32
29
  }
33
- const { snap, simplify, triangulate, repair } = Object.assign({}, defaults, options)
30
+ const { snap, simplify, triangulate } = Object.assign({}, defaults, options)
34
31
 
35
32
  const epsilon = measureEpsilon(geometry)
36
33
  let polygons = geom3.toPolygons(geometry)
@@ -52,13 +49,6 @@ const generalizeGeom3 = (options, geometry) => {
52
49
  polygons = triangulatePolygons(epsilon, polygons)
53
50
  }
54
51
 
55
- // repair the polygons (possibly triangles) if requested
56
- if (repair) {
57
- // fix T junctions
58
- polygons = repairTjunctions(epsilon, polygons)
59
- // TODO fill holes
60
- }
61
-
62
52
  // FIXME replace with geom3.cloneShallow() when available
63
53
  const clone = Object.assign({}, geometry)
64
54
  clone.polygons = polygons
@@ -72,7 +62,6 @@ const generalizeGeom3 = (options, geometry) => {
72
62
  * @param {Boolean} [options.snap=false] the geometries should be snapped to epsilons
73
63
  * @param {Boolean} [options.simplify=false] the geometries should be simplified
74
64
  * @param {Boolean} [options.triangulate=false] the geometries should be triangulated
75
- * @param {Boolean} [options.repair=false] the geometries should be repaired
76
65
  * @param {...Object} geometries - the geometries to generalize
77
66
  * @return {Object|Array} the modified geometry, or a list of modified geometries
78
67
  * @alias module:modeling/modifiers.generalize
@@ -28,6 +28,7 @@ test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
28
28
  [[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, -0.7853981633974483, 3.141592653589793],
29
29
  [1.5707963267948966, 0.7853981633974483, 3.141592653589793], [-1.5707963267948966, 0.7853981633974483, 3.141592653589793]]
30
30
  ]
31
+ t.notThrows(() => geom3.validate(result))
31
32
  t.true(comparePolygonsAsPoints(pts, exp))
32
33
 
33
34
  // apply snap only
@@ -47,6 +48,7 @@ test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
47
48
  [[-1.5707910908071407, -0.7854138713607164, 3.1415821816142815], [1.5707910908071407, -0.7854138713607164, 3.1415821816142815],
48
49
  [1.5707910908071407, 0.7854138713607164, 3.1415821816142815], [-1.5707910908071407, 0.7854138713607164, 3.1415821816142815]]
49
50
  ]
51
+ t.notThrows(() => geom3.validate(result))
50
52
  t.true(comparePolygonsAsPoints(pts, exp))
51
53
 
52
54
  // apply triangulate only
@@ -78,6 +80,7 @@ test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
78
80
  [[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, 0.7853981633974483, 3.141592653589793],
79
81
  [-1.5707963267948966, 0.7853981633974483, 3.141592653589793]]
80
82
  ]
83
+ t.notThrows(() => geom3.validate(result))
81
84
  t.true(comparePolygonsAsPoints(pts, exp))
82
85
 
83
86
  const geometry2 = result // save the triangles for another test
@@ -99,37 +102,7 @@ test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
99
102
  [[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, -0.7853981633974483, 3.141592653589793],
100
103
  [1.5707963267948966, 0.7853981633974483, 3.141592653589793], [-1.5707963267948966, 0.7853981633974483, 3.141592653589793]]
101
104
  ]
102
- t.true(comparePolygonsAsPoints(pts, exp))
103
-
104
- // apply repairs only (triangles)
105
- result = generalize({ repair: true }, geometry2)
106
- pts = geom3.toPoints(result)
107
- exp = [
108
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [-1.5707963267948966, -0.7853981633974483, 3.141592653589793],
109
- [-1.5707963267948966, 0.7853981633974483, 3.141592653589793]],
110
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [-1.5707963267948966, 0.7853981633974483, 3.141592653589793],
111
- [-1.5707963267948966, 0.7853981633974483, -3.141592653589793]],
112
- [[1.5707963267948966, -0.7853981633974483, -3.141592653589793], [1.5707963267948966, 0.7853981633974483, -3.141592653589793],
113
- [1.5707963267948966, 0.7853981633974483, 3.141592653589793]],
114
- [[1.5707963267948966, -0.7853981633974483, -3.141592653589793], [1.5707963267948966, 0.7853981633974483, 3.141592653589793],
115
- [1.5707963267948966, -0.7853981633974483, 3.141592653589793]],
116
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [1.5707963267948966, -0.7853981633974483, -3.141592653589793],
117
- [1.5707963267948966, -0.7853981633974483, 3.141592653589793]],
118
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [1.5707963267948966, -0.7853981633974483, 3.141592653589793],
119
- [-1.5707963267948966, -0.7853981633974483, 3.141592653589793]],
120
- [[-1.5707963267948966, 0.7853981633974483, -3.141592653589793], [-1.5707963267948966, 0.7853981633974483, 3.141592653589793],
121
- [1.5707963267948966, 0.7853981633974483, 3.141592653589793]],
122
- [[-1.5707963267948966, 0.7853981633974483, -3.141592653589793], [1.5707963267948966, 0.7853981633974483, 3.141592653589793],
123
- [1.5707963267948966, 0.7853981633974483, -3.141592653589793]],
124
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [-1.5707963267948966, 0.7853981633974483, -3.141592653589793],
125
- [1.5707963267948966, 0.7853981633974483, -3.141592653589793]],
126
- [[-1.5707963267948966, -0.7853981633974483, -3.141592653589793], [1.5707963267948966, 0.7853981633974483, -3.141592653589793],
127
- [1.5707963267948966, -0.7853981633974483, -3.141592653589793]],
128
- [[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, -0.7853981633974483, 3.141592653589793],
129
- [1.5707963267948966, 0.7853981633974483, 3.141592653589793]],
130
- [[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, 0.7853981633974483, 3.141592653589793],
131
- [-1.5707963267948966, 0.7853981633974483, 3.141592653589793]]
132
- ]
105
+ t.notThrows(() => geom3.validate(result))
133
106
  t.true(comparePolygonsAsPoints(pts, exp))
134
107
  })
135
108
 
@@ -190,5 +163,6 @@ test('generalize: generalize of a geom3 with T junctions produces an expected ge
190
163
  [[0, 1, 1], [-1, 1, 1], [0, 0, 1]],
191
164
  [[-1, 1, 1], [-1, 0, 1], [0, 0, 1]]
192
165
  ]
166
+ t.notThrows(() => geom3.validate(result))
193
167
  t.true(comparePolygonsAsPoints(pts, exp))
194
168
  })
@@ -259,7 +259,7 @@ const insertTjunctions = (polygons) => {
259
259
  // split the side by inserting the vertex:
260
260
  const newvertices = polygon.vertices.slice(0)
261
261
  newvertices.splice(insertionvertextagindex, 0, endvertex)
262
- const newpolygon = poly3.fromPoints(newvertices)
262
+ const newpolygon = poly3.create(newvertices)
263
263
 
264
264
  newpolygons[polygonindex] = newpolygon
265
265
 
@@ -56,33 +56,33 @@ test('insertTjunctions: insertTjunctions produces expected polygons', (t) => {
56
56
 
57
57
  const result3 = insertTjunctions(geom3.toPolygons(geometry3))
58
58
  let exp = [
59
- poly3.fromPoints([[-1, -1, -1], [-1, -1, 1], [-1, 1, 1], [-1, 1, -1]]),
60
- poly3.fromPoints([[1, -1, -1], [1, 1, -1], [1, 1, 1], [1, -1, 1]]),
61
- poly3.fromPoints([[-1, -1, -1], [1, -1, -1], [1, -1, 1], [-1, -1, 1]]),
62
- poly3.fromPoints([[-1, 1, -1], [-1, 1, 1], [1, 1, 1], [1, 1, -1]]),
63
- poly3.fromPoints([[-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]]),
64
- poly3.fromPoints([[0, 0, 1], [-1, -1, 1], [1, -1, 1], [1, 1, 1]]),
65
- poly3.fromPoints([[1, 1, 1], [-1, 1, 1], [0, 0, 1]]),
66
- poly3.fromPoints([[-1, 1, 1], [-1, -1, 1], [0, 0, 1]])
59
+ poly3.create([[-1, -1, -1], [-1, -1, 1], [-1, 1, 1], [-1, 1, -1]]),
60
+ poly3.create([[1, -1, -1], [1, 1, -1], [1, 1, 1], [1, -1, 1]]),
61
+ poly3.create([[-1, -1, -1], [1, -1, -1], [1, -1, 1], [-1, -1, 1]]),
62
+ poly3.create([[-1, 1, -1], [-1, 1, 1], [1, 1, 1], [1, 1, -1]]),
63
+ poly3.create([[-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]]),
64
+ poly3.create([[0, 0, 1], [-1, -1, 1], [1, -1, 1], [1, 1, 1]]),
65
+ poly3.create([[1, 1, 1], [-1, 1, 1], [0, 0, 1]]),
66
+ poly3.create([[-1, 1, 1], [-1, -1, 1], [0, 0, 1]])
67
67
  ]
68
68
  t.not(result3, geom3.toPolygons(geometry3))
69
69
  t.true(comparePolygonLists(result3, exp))
70
70
 
71
71
  const result4 = insertTjunctions(geom3.toPolygons(geometry4))
72
72
  exp = [
73
- poly3.fromPoints([[-1, -1, -1], [-1, -1, 1], [-1, 0, 1], [-1, 1, 1], [-1, 1, -1]]),
74
- poly3.fromPoints([[1, -1, -1], [1, 1, -1], [1, 1, 1], [1, 0, 1], [1, -1, 1]]),
75
- poly3.fromPoints([[-1, -1, -1], [1, -1, -1], [1, -1, 1], [0, -1, 1], [-1, -1, 1]]),
76
- poly3.fromPoints([[-1, 1, -1], [-1, 1, 1], [0, 1, 1], [1, 1, 1], [1, 1, -1]]),
77
- poly3.fromPoints([[-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]]),
78
- poly3.fromPoints([[-1, -1, 1], [0, -1, 1], [0, 0, 1]]),
79
- poly3.fromPoints([[-1, 0, 1], [-1, -1, 1], [0, 0, 1]]),
80
- poly3.fromPoints([[0, -1, 1], [1, -1, 1], [0, 0, 1]]),
81
- poly3.fromPoints([[1, -1, 1], [1, 0, 1], [0, 0, 1]]),
82
- poly3.fromPoints([[1, 0, 1], [1, 1, 1], [0, 0, 1]]),
83
- poly3.fromPoints([[1, 1, 1], [0, 1, 1], [0, 0, 1]]),
84
- poly3.fromPoints([[0, 1, 1], [-1, 1, 1], [0, 0, 1]]),
85
- poly3.fromPoints([[-1, 1, 1], [-1, 0, 1], [0, 0, 1]])
73
+ poly3.create([[-1, -1, -1], [-1, -1, 1], [-1, 0, 1], [-1, 1, 1], [-1, 1, -1]]),
74
+ poly3.create([[1, -1, -1], [1, 1, -1], [1, 1, 1], [1, 0, 1], [1, -1, 1]]),
75
+ poly3.create([[-1, -1, -1], [1, -1, -1], [1, -1, 1], [0, -1, 1], [-1, -1, 1]]),
76
+ poly3.create([[-1, 1, -1], [-1, 1, 1], [0, 1, 1], [1, 1, 1], [1, 1, -1]]),
77
+ poly3.create([[-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]]),
78
+ poly3.create([[-1, -1, 1], [0, -1, 1], [0, 0, 1]]),
79
+ poly3.create([[-1, 0, 1], [-1, -1, 1], [0, 0, 1]]),
80
+ poly3.create([[0, -1, 1], [1, -1, 1], [0, 0, 1]]),
81
+ poly3.create([[1, -1, 1], [1, 0, 1], [0, 0, 1]]),
82
+ poly3.create([[1, 0, 1], [1, 1, 1], [0, 0, 1]]),
83
+ poly3.create([[1, 1, 1], [0, 1, 1], [0, 0, 1]]),
84
+ poly3.create([[0, 1, 1], [-1, 1, 1], [0, 0, 1]]),
85
+ poly3.create([[-1, 1, 1], [-1, 0, 1], [0, 0, 1]])
86
86
  ]
87
87
  t.not(result4, geometry4)
88
88
  t.true(comparePolygonLists(result4, exp))
@@ -1,3 +1,4 @@
1
+ const aboutEqualNormals = require('../../maths/utils/aboutEqualNormals')
1
2
  const vec3 = require('../../maths/vec3')
2
3
 
3
4
  const poly3 = require('../../geometries/poly3')
@@ -53,9 +54,12 @@ const calculateAnglesBetween = (current, opposite, normal) => {
53
54
  return [angle1, angle2]
54
55
  }
55
56
 
57
+ const v1 = vec3.create()
58
+ const v2 = vec3.create()
59
+
56
60
  const calculateAngle = (prevpoint, point, nextpoint, normal) => {
57
- const d0 = vec3.subtract(vec3.create(), point, prevpoint)
58
- const d1 = vec3.subtract(vec3.create(), nextpoint, point)
61
+ const d0 = vec3.subtract(v1, point, prevpoint)
62
+ const d1 = vec3.subtract(v2, nextpoint, point)
59
63
  vec3.cross(d0, d0, d1)
60
64
  return vec3.dot(d0, normal)
61
65
  }
@@ -76,7 +80,7 @@ const createPolygonAnd = (edge) => {
76
80
 
77
81
  edge = next
78
82
  }
79
- if (points.length > 0) polygon = poly3.fromPoints(points)
83
+ if (points.length > 0) polygon = poly3.create(points)
80
84
  return polygon
81
85
  }
82
86
 
@@ -85,7 +89,7 @@ const createPolygonAnd = (edge) => {
85
89
  * @param {poly3[]} sourcepolygons - list of polygons
86
90
  * @returns {poly3[]} new set of polygons
87
91
  */
88
- const mergeCoplanarPolygons = (epsilon, sourcepolygons) => {
92
+ const mergeCoplanarPolygons = (sourcepolygons) => {
89
93
  if (sourcepolygons.length < 2) return sourcepolygons
90
94
 
91
95
  const normal = sourcepolygons[0].plane
@@ -167,18 +171,11 @@ const mergeCoplanarPolygons = (epsilon, sourcepolygons) => {
167
171
  if (polygon) destpolygons.push(polygon)
168
172
  })
169
173
 
174
+ edgeList.clear()
175
+
170
176
  return destpolygons
171
177
  }
172
178
 
173
- // Normals are directional vectors with component values from 0 to 1.0, requiring specialized comparision
174
- // This EPS is derived from a serieas of tests to determine the optimal precision for comparing coplanar polygons,
175
- // as provided by the sphere primitive at high segmentation
176
- // This EPS is for 64 bit Number values
177
- const NEPS = 1e-13
178
-
179
- // Compare two normals (unit vectors) for equality.
180
- const aboutEqualNormals = (a, b) => (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
181
-
182
179
  const coplanar = (plane1, plane2) => {
183
180
  // expect the same distance from the origin, within tolerance
184
181
  if (Math.abs(plane1[3] - plane2[3]) < 0.00000015) {
@@ -202,7 +199,7 @@ const mergePolygons = (epsilon, polygons) => {
202
199
  let destpolygons = []
203
200
  polygonsPerPlane.forEach((mapping) => {
204
201
  const sourcepolygons = mapping[1]
205
- const retesselayedpolygons = mergeCoplanarPolygons(epsilon, sourcepolygons)
202
+ const retesselayedpolygons = mergeCoplanarPolygons(sourcepolygons)
206
203
  destpolygons = destpolygons.concat(retesselayedpolygons)
207
204
  })
208
205
  return destpolygons
@@ -31,7 +31,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
31
31
  // convert all polygon vertices to 2D
32
32
  // Make a list of all encountered y coordinates
33
33
  // And build a map of all polygons that have a vertex at a certain y coordinate:
34
- const ycoordinateBinningFactor = 1.0 / EPS * 10
34
+ const ycoordinateBinningFactor = 10 / EPS
35
35
  for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
36
36
  const poly3d = sourcepolygons[polygonindex]
37
37
  let vertices2d = []
@@ -17,11 +17,11 @@ const rotatePoly3 = (angles, polygon) => {
17
17
  }
18
18
 
19
19
  test.only('retessellateCoplanarPolygons: should merge coplanar polygons', (t) => {
20
- const polyA = poly3.fromPoints([[-5, -5, 0], [5, -5, 0], [5, 5, 0], [-5, 5, 0]])
21
- const polyB = poly3.fromPoints([[5, -5, 0], [8, 0, 0], [5, 5, 0]])
22
- const polyC = poly3.fromPoints([[-5, 5, 0], [-8, 0, 0], [-5, -5, 0]])
23
- const polyD = poly3.fromPoints([[-5, 5, 0], [5, 5, 0], [0, 8, 0]])
24
- const polyE = poly3.fromPoints([[5, -5, 0], [-5, -5, 0], [0, -8, 0]])
20
+ const polyA = poly3.create([[-5, -5, 0], [5, -5, 0], [5, 5, 0], [-5, 5, 0]])
21
+ const polyB = poly3.create([[5, -5, 0], [8, 0, 0], [5, 5, 0]])
22
+ const polyC = poly3.create([[-5, 5, 0], [-8, 0, 0], [-5, -5, 0]])
23
+ const polyD = poly3.create([[-5, 5, 0], [5, 5, 0], [0, 8, 0]])
24
+ const polyE = poly3.create([[5, -5, 0], [-5, -5, 0], [0, -8, 0]])
25
25
 
26
26
  // combine polygons in each direction
27
27
  let obs = reTesselateCoplanarPolygons([polyA, polyB])
@@ -1,16 +1,9 @@
1
1
  const geom3 = require('../../geometries/geom3')
2
2
  const poly3 = require('../../geometries/poly3')
3
3
 
4
- const reTesselateCoplanarPolygons = require('./reTesselateCoplanarPolygons')
5
-
6
- // Normals are directional vectors with component values from 0 to 1.0, requiring specialized comparison
7
- // This EPS is derived from a series of tests to determine the optimal precision for comparing coplanar polygons,
8
- // as provided by the sphere primitive at high segmentation
9
- // This EPS is for 64 bit Number values
10
- const NEPS = 1e-13
4
+ const aboutEqualNormals = require('../../maths/utils/aboutEqualNormals')
11
5
 
12
- // Compare two normals (unit vectors) for equality.
13
- const aboutEqualNormals = (a, b) => (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
6
+ const reTesselateCoplanarPolygons = require('./reTesselateCoplanarPolygons')
14
7
 
15
8
  const coplanar = (plane1, plane2) => {
16
9
  // expect the same distance from the origin, within tolerance
@@ -7,49 +7,49 @@ const snapPolygons = require('./snapPolygons')
7
7
  test('snapPolygons: snap of polygons produces expected results', (t) => {
8
8
  const polygons = [
9
9
  // valid polygons
10
- poly3.fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10]]), // OK
11
- poly3.fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]]), // OK
10
+ poly3.create([[0, 0, 0], [0, 10, 0], [0, 10, 10]]), // OK
11
+ poly3.create([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]]), // OK
12
12
  // invalid polygons
13
13
  poly3.create(),
14
- poly3.fromPoints([[0, 0, 0]]),
15
- poly3.fromPoints([[0, 0, 0], [0, 10, 0]]),
14
+ poly3.create([[0, 0, 0]]),
15
+ poly3.create([[0, 0, 0], [0, 10, 0]]),
16
16
  // duplicated vertices
17
- poly3.fromPoints([
17
+ poly3.create([
18
18
  [-24.445112000000115, 19.346837333333426, 46.47572533333356],
19
19
  [-24.44446933333345, 19.346837333333426, 46.47508266666689],
20
20
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
21
21
  [-23.70540266666678, 18.79864266666676, 39.56448800000019]
22
22
  ]), // OK
23
- poly3.fromPoints([
23
+ poly3.create([
24
24
  [-24.445112000000115, 19.346837333333426, 46.47572533333356],
25
25
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
26
26
  [-23.70540266666678, 18.79864266666676, 39.56448800000019]
27
27
  ]),
28
- poly3.fromPoints([
28
+ poly3.create([
29
29
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
30
30
  [-23.70540266666678, 18.79864266666676, 39.56448800000019]
31
31
  ]),
32
32
  // duplicate vertices after snap
33
- poly3.fromPoints([
33
+ poly3.create([
34
34
  [-24.445112000000115, 19.346837333333426, 46.47572533333356],
35
35
  [-24.44446933333345, 19.346837333333426, 46.47508266666689],
36
36
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
37
37
  [-23.70540266666678 - 0.00001234, 18.79864266666676 + 0.000001234, 39.56448800000019 + 0.00001234]
38
38
  ]), // OK
39
- poly3.fromPoints([
39
+ poly3.create([
40
40
  [-24.445112000000115, 19.346837333333426, 46.47572533333356],
41
41
  [-23.70540266666678 - 0.00001234, 18.79864266666676 + 0.000001234, 39.56448800000019 + 0.00001234],
42
42
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
43
43
  [-23.70540266666678 - 0.00001234, 18.79864266666676 + 0.000001234, 39.56448800000019 + 0.00001234]
44
44
  ]),
45
- poly3.fromPoints([
45
+ poly3.create([
46
46
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
47
47
  [-23.70540266666678 - 0.00001234, 18.79864266666676 + 0.000001234, 39.56448800000019 + 0.00001234],
48
48
  [-23.70540266666678, 18.79864266666676, 39.56448800000019],
49
49
  [-23.70540266666678 - 0.00001234, 18.79864266666676 + 0.000001234, 39.56448800000019 + 0.00001234]
50
50
  ]),
51
51
  // inverted polygon
52
- poly3.fromPoints([
52
+ poly3.create([
53
53
  [20.109133333333336, -4.894033333333335, -1.0001266666666668],
54
54
  [20.021120000000003, -5.1802133333333344, -1.0001266666666668],
55
55
  [20.020300000000002, -5.182946666666668, -1.0001266666666668],
@@ -61,7 +61,7 @@ test('snapPolygons: snap of polygons produces expected results', (t) => {
61
61
  const results = snapPolygons(0.0001, polygons)
62
62
  t.is(results.length, 5)
63
63
 
64
- const exp3 = poly3.fromPoints([
64
+ const exp3 = poly3.create([
65
65
  [-24.4451, 19.3468, 46.4757],
66
66
  [-24.4445, 19.3468, 46.475100000000005],
67
67
  [-23.7054, 18.7986, 39.5645]
@@ -10,15 +10,15 @@ const triangulatePolygon = (epsilon, polygon, triangles) => {
10
10
  polygon.vertices.forEach((vertice) => vec3.add(midpoint, midpoint, vertice))
11
11
  vec3.snap(midpoint, vec3.divide(midpoint, midpoint, [nv, nv, nv]), epsilon)
12
12
  for (let i = 0; i < nv; i++) {
13
- const poly = poly3.fromPoints([midpoint, polygon.vertices[i], polygon.vertices[(i + 1) % nv]])
13
+ const poly = poly3.create([midpoint, polygon.vertices[i], polygon.vertices[(i + 1) % nv]])
14
14
  if (polygon.color) poly.color = polygon.color
15
15
  triangles.push(poly)
16
16
  }
17
17
  return
18
18
  }
19
19
  // exactly 4 vertices, use simple triangulation
20
- const poly0 = poly3.fromPoints([polygon.vertices[0], polygon.vertices[1], polygon.vertices[2]])
21
- const poly1 = poly3.fromPoints([polygon.vertices[0], polygon.vertices[2], polygon.vertices[3]])
20
+ const poly0 = poly3.create([polygon.vertices[0], polygon.vertices[1], polygon.vertices[2]])
21
+ const poly1 = poly3.create([polygon.vertices[0], polygon.vertices[2], polygon.vertices[3]])
22
22
  if (polygon.color) {
23
23
  poly0.color = polygon.color
24
24
  poly1.color = polygon.color
@@ -1,6 +1,7 @@
1
1
  const test = require('ava')
2
2
 
3
3
  const { comparePoints } = require('../../../test/helpers')
4
+ const { geom3 } = require('../../geometries')
4
5
  const { measureBoundingBox, measureAggregateBoundingBox } = require('../../measurements')
5
6
  const { cube } = require('../../primitives')
6
7
 
@@ -11,6 +12,7 @@ test('align: single object returns geometry unchanged if all axes are none', (t)
11
12
  const aligned = align({ modes: ['none', 'none', 'none'] }, original)
12
13
  const bounds = measureBoundingBox(aligned)
13
14
  const expectedBounds = [[8, 8, 8], [12, 12, 12]]
15
+ t.notThrows(() => geom3.validate(aligned))
14
16
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
15
17
  })
16
18
 
@@ -19,6 +21,7 @@ test('align: single objects returns geometry aligned, different modes on each ax
19
21
  const aligned = align({ modes: ['center', 'min', 'max'] }, original)
20
22
  const bounds = measureBoundingBox(aligned)
21
23
  const expectedBounds = [[-2, 0, -4], [2, 4, 0]]
24
+ t.notThrows(() => geom3.validate(aligned))
22
25
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
23
26
  })
24
27
 
@@ -27,6 +30,7 @@ test('align: unfilled modes and relativeTo arrays return results with expected v
27
30
  const aligned = align({ modes: ['center'], relativeTo: [0] }, original)
28
31
  const bounds = measureBoundingBox(aligned)
29
32
  const expectedBounds = [[-2, 8, 8], [2, 12, 12]]
33
+ t.notThrows(() => geom3.validate(aligned))
30
34
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
31
35
  })
32
36
 
@@ -38,6 +42,8 @@ test('align: multiple objects grouped returns geometry aligned, different modes
38
42
  const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [6, -10, 0], grouped: true }, original)
39
43
  const bounds = measureAggregateBoundingBox(aligned)
40
44
  const expectedBounds = [[1.5, -10, -9], [10.5, -1, 0]]
45
+ t.notThrows(() => geom3.validate(aligned[0]))
46
+ t.notThrows(() => geom3.validate(aligned[1]))
41
47
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
42
48
  })
43
49
 
@@ -49,6 +55,8 @@ test('align: multiple objects ungrouped returns geometry aligned, different mode
49
55
  const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [30, 30, 30] }, original)
50
56
  const bounds = measureAggregateBoundingBox(aligned)
51
57
  const expectedBounds = [[28, 30, 26], [32, 34, 30]]
58
+ t.notThrows(() => geom3.validate(aligned[0]))
59
+ t.notThrows(() => geom3.validate(aligned[1]))
52
60
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
53
61
  })
54
62
 
@@ -60,6 +68,8 @@ test('align: multiple objects grouped, relativeTo is nulls, returns geometry unc
60
68
  const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [null, null, null], grouped: true }, original)
61
69
  const bounds = measureAggregateBoundingBox(aligned)
62
70
  const expectedBounds = [[3, 3, 3], [12, 12, 12]]
71
+ t.notThrows(() => geom3.validate(aligned[0]))
72
+ t.notThrows(() => geom3.validate(aligned[1]))
63
73
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
64
74
  })
65
75
 
@@ -71,6 +81,8 @@ test('align: multiple objects ungrouped, relativeTo is nulls, returns geometry a
71
81
  const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [null, null, null], grouped: false }, original)
72
82
  const bounds = measureAggregateBoundingBox(aligned)
73
83
  const expectedBounds = [[5.5, 3, 8], [9.5, 7, 12]]
84
+ t.notThrows(() => geom3.validate(aligned[0]))
85
+ t.notThrows(() => geom3.validate(aligned[1]))
74
86
  t.true(comparePoints(bounds, expectedBounds), 'Bounding box was not as expected. Result: ' + JSON.stringify(bounds))
75
87
  })
76
88
 
@@ -39,7 +39,7 @@ const center = (options, ...objects) => {
39
39
  const defaults = {
40
40
  axes: [true, true, true],
41
41
  relativeTo: [0, 0, 0]
42
- // TODO : Add addition 'methods' of centering; midpoint, centeriod
42
+ // TODO: Add additional 'methods' of centering: midpoint, centroid
43
43
  }
44
44
  const { axes, relativeTo } = Object.assign({}, defaults, options)
45
45