@jscad/modeling 2.9.2 → 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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -4
  3. package/dist/jscad-modeling.min.js +411 -417
  4. package/package.json +3 -2
  5. package/src/colors/colorize.test.js +1 -1
  6. package/src/geometries/geom2/toOutlines.js +66 -52
  7. package/src/geometries/geom3/create.js +1 -1
  8. package/src/geometries/geom3/create.test.js +1 -1
  9. package/src/geometries/geom3/fromPoints.js +1 -1
  10. package/src/geometries/path2/index.d.ts +0 -1
  11. package/src/geometries/path2/index.js +0 -1
  12. package/src/geometries/poly3/create.js +1 -1
  13. package/src/geometries/poly3/validate.js +14 -0
  14. package/src/maths/constants.d.ts +1 -0
  15. package/src/maths/constants.js +11 -0
  16. package/src/maths/utils/aboutEqualNormals.js +1 -5
  17. package/src/operations/booleans/intersectGeom3.js +2 -1
  18. package/src/operations/booleans/subtractGeom3.js +2 -1
  19. package/src/operations/booleans/to3DWalls.js +1 -1
  20. package/src/operations/booleans/unionGeom3.js +2 -1
  21. package/src/operations/expansions/expandGeom3.test.js +14 -14
  22. package/src/operations/expansions/expandShell.js +5 -4
  23. package/src/operations/expansions/extrudePolygon.js +7 -7
  24. package/src/operations/extrusions/extrudeFromSlices.test.js +2 -2
  25. package/src/operations/extrusions/extrudeRotate.js +5 -1
  26. package/src/operations/extrusions/extrudeRotate.test.js +5 -5
  27. package/src/operations/extrusions/extrudeWalls.js +2 -2
  28. package/src/operations/extrusions/project.js +11 -14
  29. package/src/operations/extrusions/project.test.js +50 -50
  30. package/src/operations/hulls/hullChain.test.js +4 -4
  31. package/src/operations/hulls/hullGeom2.js +6 -18
  32. package/src/operations/hulls/hullGeom3.js +5 -18
  33. package/src/operations/hulls/hullPath2.js +4 -14
  34. package/src/operations/hulls/hullPoints2.js +43 -92
  35. package/src/operations/hulls/toUniquePoints.js +34 -0
  36. package/src/operations/modifiers/generalize.js +2 -13
  37. package/src/operations/modifiers/generalize.test.js +0 -32
  38. package/src/operations/modifiers/insertTjunctions.js +1 -1
  39. package/src/operations/modifiers/insertTjunctions.test.js +21 -21
  40. package/src/operations/modifiers/mergePolygons.js +11 -14
  41. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +1 -1
  42. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
  43. package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
  44. package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
  45. package/src/operations/modifiers/snapPolygons.test.js +12 -12
  46. package/src/operations/modifiers/triangulatePolygons.js +3 -3
  47. package/src/operations/transforms/center.js +1 -1
  48. package/src/primitives/cuboid.js +1 -1
  49. package/src/primitives/cylinderElliptic.js +1 -1
  50. package/src/primitives/ellipsoid.js +2 -2
  51. package/src/primitives/polyhedron.js +1 -1
  52. package/src/primitives/roundedCuboid.js +6 -6
  53. package/src/primitives/roundedCylinder.js +1 -1
  54. package/src/primitives/torus.test.js +6 -6
  55. package/src/primitives/triangle.js +1 -2
  56. package/src/utils/trigonometry.js +1 -2
  57. package/src/geometries/path2/eachPoint.d.ts +0 -9
  58. package/src/geometries/path2/eachPoint.js +0 -17
  59. package/src/geometries/path2/eachPoint.test.js +0 -11
  60. package/src/operations/modifiers/edges.js +0 -195
  61. package/src/operations/modifiers/repairTjunctions.js +0 -44
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.9.2",
3
+ "version": "2.9.3",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
+ "homepage": "https://openjscad.xyz/",
5
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
6
7
  "main": "src/index.js",
7
8
  "types": "src/index.d.ts",
@@ -60,5 +61,5 @@
60
61
  "nyc": "15.1.0",
61
62
  "uglifyify": "5.0.2"
62
63
  },
63
- "gitHead": "0cebde0166c104e3c08cc05d2c03d9defc7eca26"
64
+ "gitHead": "85fa1fcdfb2d516a201ecf31da242e64b5b3274e"
64
65
  }
@@ -38,7 +38,7 @@ test('color (rgba on geometry)', (t) => {
38
38
  const obj0 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
39
39
  const obj1 = geom3.fromPoints([[[0, 0, 0], [1, 0, 0], [1, 0, 1]]])
40
40
  const obj2 = path2.fromPoints({ closed: true }, [[0, 0], [1, 0], [1, 1]])
41
- const obj3 = poly3.fromPoints([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
41
+ const obj3 = poly3.create([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
42
42
 
43
43
  const obs = colorize([1, 1, 0.5, 0.8], obj0, obj1, obj2, obj3)
44
44
  t.is(obs.length, 4)
@@ -6,22 +6,42 @@ const toSides = require('./toSides')
6
6
  * Create a list of edges which SHARE vertices.
7
7
  * This allows the edges to be traversed in order.
8
8
  */
9
- const toEdges = (sides) => {
10
- const vertices = {}
9
+ const toSharedVertices = (sides) => {
10
+ const unique = new Map() // {key: vertex}
11
11
  const getUniqueVertex = (vertex) => {
12
12
  const key = vertex.toString()
13
- if (!vertices[key]) {
14
- vertices[key] = vertex
13
+ if (unique.has(key)) {
14
+ return unique.get(key)
15
+ } else {
16
+ unique.set(key, vertex)
17
+ return vertex
15
18
  }
16
- return vertices[key]
17
19
  }
18
20
 
19
21
  return sides.map((side) => side.map(getUniqueVertex))
20
22
  }
21
23
 
24
+ /*
25
+ * Convert a list of sides into a map from vertex to edges.
26
+ */
27
+ const toVertexMap = (sides) => {
28
+ const vertexMap = new Map()
29
+ // first map to edges with shared vertices
30
+ const edges = toSharedVertices(sides)
31
+ // construct adjacent edges map
32
+ edges.forEach((edge) => {
33
+ if (vertexMap.has(edge[0])) {
34
+ vertexMap.get(edge[0]).push(edge)
35
+ } else {
36
+ vertexMap.set(edge[0], [edge])
37
+ }
38
+ })
39
+ return vertexMap
40
+ }
41
+
22
42
  /**
23
43
  * Create the outline(s) of the given geometry.
24
- * @param {geom2} geometry
44
+ * @param {geom2} geometry - geometry to create outlines from
25
45
  * @returns {Array} an array of outlines, where each outline is an array of ordered points
26
46
  * @alias module:modeling/geometries/geom2.toOutlines
27
47
  *
@@ -30,65 +50,35 @@ const toEdges = (sides) => {
30
50
  * let outlines = toOutlines(geometry) // returns two outlines
31
51
  */
32
52
  const toOutlines = (geometry) => {
33
- const vertexMap = new Map()
34
- const edges = toEdges(toSides(geometry))
35
- edges.forEach((edge) => {
36
- if (!(vertexMap.has(edge[0]))) {
37
- vertexMap.set(edge[0], [])
38
- }
39
- const sideslist = vertexMap.get(edge[0])
40
- sideslist.push(edge)
41
- })
42
-
53
+ const vertexMap = toVertexMap(toSides(geometry)) // {vertex: [edges]}
43
54
  const outlines = []
44
55
  while (true) {
45
- let startside
56
+ let startSide
46
57
  for (const [vertex, edges] of vertexMap) {
47
- startside = edges.shift()
48
- if (!startside) {
58
+ startSide = edges.shift()
59
+ if (!startSide) {
49
60
  vertexMap.delete(vertex)
50
61
  continue
51
62
  }
52
63
  break
53
64
  }
54
- if (startside === undefined) break // all starting sides have been visited
65
+ if (startSide === undefined) break // all starting sides have been visited
55
66
 
56
67
  const connectedVertexPoints = []
57
- const startvertex = startside[0]
58
- const v0 = vec2.create()
68
+ const startVertex = startSide[0]
59
69
  while (true) {
60
- connectedVertexPoints.push(startside[0])
61
- const nextvertex = startside[1]
62
- if (nextvertex === startvertex) break // the outline has been closed
63
- const nextpossiblesides = vertexMap.get(nextvertex)
64
- if (!nextpossiblesides) {
65
- throw new Error('the given geometry is not closed. verify proper construction')
70
+ connectedVertexPoints.push(startSide[0])
71
+ const nextVertex = startSide[1]
72
+ if (nextVertex === startVertex) break // the outline has been closed
73
+ const nextPossibleSides = vertexMap.get(nextVertex)
74
+ if (!nextPossibleSides) {
75
+ throw new Error(`geometry is not closed at vertex ${nextVertex}`)
66
76
  }
67
- let nextsideindex = -1
68
- if (nextpossiblesides.length === 1) {
69
- nextsideindex = 0
70
- } else {
71
- // more than one side starting at the same vertex
72
- let bestangle
73
- const startangle = vec2.angleDegrees(vec2.subtract(v0, startside[1], startside[0]))
74
- for (let sideindex = 0; sideindex < nextpossiblesides.length; sideindex++) {
75
- const nextpossibleside = nextpossiblesides[sideindex]
76
- const nextangle = vec2.angleDegrees(vec2.subtract(v0, nextpossibleside[1], nextpossibleside[0]))
77
- let angledif = nextangle - startangle
78
- if (angledif < -180) angledif += 360
79
- if (angledif >= 180) angledif -= 360
80
- if ((nextsideindex < 0) || (angledif > bestangle)) {
81
- nextsideindex = sideindex
82
- bestangle = angledif
83
- }
84
- }
77
+ const nextSide = popNextSide(startSide, nextPossibleSides)
78
+ if (nextPossibleSides.length === 0) {
79
+ vertexMap.delete(nextVertex)
85
80
  }
86
- const nextside = nextpossiblesides[nextsideindex]
87
- nextpossiblesides.splice(nextsideindex, 1) // remove side from list
88
- if (nextpossiblesides.length === 0) {
89
- vertexMap.delete(nextvertex)
90
- }
91
- startside = nextside
81
+ startSide = nextSide
92
82
  } // inner loop
93
83
 
94
84
  // due to the logic of fromPoints()
@@ -102,4 +92,28 @@ const toOutlines = (geometry) => {
102
92
  return outlines
103
93
  }
104
94
 
95
+ // find the first counter-clockwise edge from startSide and pop from nextSides
96
+ const popNextSide = (startSide, nextSides) => {
97
+ if (nextSides.length === 1) {
98
+ return nextSides.pop()
99
+ }
100
+ const v0 = vec2.create()
101
+ const startAngle = vec2.angleDegrees(vec2.subtract(v0, startSide[1], startSide[0]))
102
+ let bestAngle
103
+ let bestIndex
104
+ nextSides.forEach((nextSide, index) => {
105
+ const nextAngle = vec2.angleDegrees(vec2.subtract(v0, nextSide[1], nextSide[0]))
106
+ let angle = nextAngle - startAngle
107
+ if (angle < -180) angle += 360
108
+ if (angle >= 180) angle -= 360
109
+ if (bestIndex === undefined || angle > bestAngle) {
110
+ bestIndex = index
111
+ bestAngle = angle
112
+ }
113
+ })
114
+ const nextSide = nextSides[bestIndex]
115
+ nextSides.splice(bestIndex, 1) // remove side from list
116
+ return nextSide
117
+ }
118
+
105
119
  module.exports = toOutlines
@@ -18,7 +18,7 @@ const create = (polygons) => {
18
18
  polygons = [] // empty contents
19
19
  }
20
20
  return {
21
- polygons: polygons,
21
+ polygons,
22
22
  transforms: mat4.create()
23
23
  }
24
24
  }
@@ -14,7 +14,7 @@ test('create: Creates an empty geom3', (t) => {
14
14
 
15
15
  test('create: Creates a populated geom3', (t) => {
16
16
  const points = [[0, 0, 0], [0, 10, 0], [0, 10, 10]]
17
- const polygon = poly3.fromPoints(points)
17
+ const polygon = poly3.create(points)
18
18
 
19
19
  const polygons = [polygon]
20
20
  const expected = {
@@ -18,7 +18,7 @@ const fromPoints = (listofpoints) => {
18
18
 
19
19
  const polygons = listofpoints.map((points, index) => {
20
20
  // TODO catch the error, and rethrow with index
21
- const polygon = poly3.fromPoints(points)
21
+ const polygon = poly3.create(points)
22
22
  return polygon
23
23
  })
24
24
  const result = create(polygons)
@@ -5,7 +5,6 @@ export { default as clone } from './clone'
5
5
  export { default as close } from './close'
6
6
  export { default as concat } from './concat'
7
7
  export { default as create } from './create'
8
- export { default as eachPoint, EachPointOptions } from './eachPoint'
9
8
  export { default as equals } from './equals'
10
9
  export { default as fromPoints, FromPointsOptions } from './fromPoints'
11
10
  export { default as fromCompactBinary } from './fromCompactBinary'
@@ -22,7 +22,6 @@ module.exports = {
22
22
  close: require('./close'),
23
23
  concat: require('./concat'),
24
24
  create: require('./create'),
25
- eachPoint: require('./eachPoint'),
26
25
  equals: require('./equals'),
27
26
  fromPoints: require('./fromPoints'),
28
27
  fromCompactBinary: require('./fromCompactBinary'),
@@ -18,7 +18,7 @@ const create = (vertices) => {
18
18
  if (vertices === undefined || vertices.length < 3) {
19
19
  vertices = [] // empty contents
20
20
  }
21
- return { vertices: vertices }
21
+ return { vertices }
22
22
  }
23
23
 
24
24
  module.exports = create
@@ -1,7 +1,10 @@
1
+ const signedDistanceToPoint = require('../../maths/plane/signedDistanceToPoint')
2
+ const { NEPS } = require('../../maths/constants')
1
3
  const vec3 = require('../../maths/vec3')
2
4
  const isA = require('./isA')
3
5
  const isConvex = require('./isConvex')
4
6
  const measureArea = require('./measureArea')
7
+ const plane = require('./plane')
5
8
 
6
9
  /**
7
10
  * Determine if the given object is a valid polygon.
@@ -45,6 +48,17 @@ const validate = (object) => {
45
48
  throw new Error(`poly3 invalid vertex ${vertex}`)
46
49
  }
47
50
  })
51
+
52
+ // check that points are co-planar
53
+ if (object.vertices.length > 3) {
54
+ const normal = plane(object)
55
+ object.vertices.forEach((vertex) => {
56
+ const dist = Math.abs(signedDistanceToPoint(normal, vertex))
57
+ if (dist > NEPS) {
58
+ throw new Error(`poly3 must be coplanar: vertex ${vertex} distance ${dist}`)
59
+ }
60
+ })
61
+ }
48
62
  }
49
63
 
50
64
  module.exports = validate
@@ -1,2 +1,3 @@
1
1
  export const EPS: number
2
+ export const NEPS: number
2
3
  export const spatialResolution: number
@@ -14,7 +14,18 @@ const spatialResolution = 1e5
14
14
  */
15
15
  const EPS = 1e-5
16
16
 
17
+ /**
18
+ * Smaller epsilon used for measuring near zero distances.
19
+ * @default
20
+ * @alias module:modeling/maths.NEPS
21
+ */
22
+ const NEPS = 1e-13
23
+ // NEPS is derived from a series of tests to determine the optimal precision
24
+ // for comparing coplanar polygons, as provided by the sphere primitive at high
25
+ // segmentation. NEPS is for 64 bit Number values.
26
+
17
27
  module.exports = {
18
28
  EPS,
29
+ NEPS,
19
30
  spatialResolution
20
31
  }
@@ -1,8 +1,4 @@
1
- // Normals are directional vectors with component values from 0 to 1.0, requiring specialized comparison
2
- // This EPS is derived from a series of tests to determine the optimal precision for comparing coplanar polygons,
3
- // as provided by the sphere primitive at high segmentation
4
- // This EPS is for 64 bit Number values
5
- const NEPS = 1e-13
1
+ const { NEPS } = require('../constants')
6
2
 
7
3
  /**
8
4
  * Compare two normals (unit vectors) for near equality.
@@ -1,6 +1,7 @@
1
1
  const flatten = require('../../utils/flatten')
2
2
 
3
- const retessellate = require('./retessellate')
3
+ const retessellate = require('../modifiers/retessellate')
4
+
4
5
  const intersectSub = require('./intersectGeom3Sub')
5
6
 
6
7
  /*
@@ -1,6 +1,7 @@
1
1
  const flatten = require('../../utils/flatten')
2
2
 
3
- const retessellate = require('./retessellate')
3
+ const retessellate = require('../modifiers/retessellate')
4
+
4
5
  const subtractSub = require('./subtractGeom3Sub')
5
6
 
6
7
  /*
@@ -14,7 +14,7 @@ const to3DWall = (z0, z1, side) => {
14
14
  vec3.fromVec2(vec3.create(), side[1], z1),
15
15
  vec3.fromVec2(vec3.create(), side[0], z1)
16
16
  ]
17
- return poly3.fromPoints(points)
17
+ return poly3.create(points)
18
18
  }
19
19
 
20
20
  /*
@@ -1,6 +1,7 @@
1
1
  const flatten = require('../../utils/flatten')
2
2
 
3
- const retessellate = require('./retessellate')
3
+ const retessellate = require('../modifiers/retessellate')
4
+
4
5
  const unionSub = require('./unionGeom3Sub')
5
6
 
6
7
  /*
@@ -6,20 +6,20 @@ const expandGeom3 = require('./expandGeom3')
6
6
  test('expandGeom3: expand completes properly, issue 876', async (t) => {
7
7
  setTimeout(() => t.fail(), 1000)
8
8
  const polies = [
9
- poly3.fromPoints([[-19.61, -0.7999999999999986, 11.855], [-19.61, -0.8000000000000015, -11.855], [-19.61, -2.7500000000000018, -11.855], [-19.61, -2.7499999999999982, 11.855]]),
10
- poly3.fromPoints([[-17.32, -2.75, 10], [-17.32, -2.7500000000000013, -10], [-17.32, -0.8000000000000014, -10], [-17.32, -0.7999999999999987, 10]]),
11
- poly3.fromPoints([[-16.863040644206997, -0.8000000000000015, -10.28], [-16.863040644206997, -2.7500000000000018, -10.28], [-14.292644267871385, -2.7500000000000018, -11.855000000000016], [-14.292644267871383, -0.8000000000000015, -11.855000000000018]]),
12
- poly3.fromPoints([[-17.319999999999993, -0.8000000000000015, -9.999999999999996], [-17.319999999999993, -2.7500000000000018, -9.999999999999996], [-16.87560702649131, -2.7500000000000018, -10.272299999999998], [-16.866696319053347, -0.8000000000000015, -10.277759999999997]]),
13
- poly3.fromPoints([[-16.863040644207004, -2.7500000000000013, -10.280000000000001], [-16.863040644207004, -0.8000000000000014, -10.280000000000001], [-16.86669631905335, -0.8000000000000012, -10.27776], [-16.875607026491313, -2.75, -10.272300000000001]]),
14
- poly3.fromPoints([[-14.107140000000015, -0.7999999999999987, 11.85500000000003], [-14.107140000000015, -2.7499999999999982, 11.85500000000003], [-17.319999999999975, -2.7499999999999982, 9.999999999999956], [-17.319999999999975, -0.7999999999999987, 9.999999999999956]]),
15
- poly3.fromPoints([[-17.32, -0.7999999999999988, 9.999999999999993], [-17.32, -0.7999999999999986, 11.855], [-14.107139999999994, -0.7999999999999986, 11.855]]),
16
- poly3.fromPoints([[-17.32, -0.800000000000001, -11.855], [-17.32, -0.8000000000000008, -10.000000000000078], [-14.292644267871482, -0.800000000000001, -11.855]]),
17
- poly3.fromPoints([[-17.32, -0.800000000000001, -11.855], [-19.61, -0.800000000000001, -11.855], [-19.61, -0.7999999999999986, 11.855], [-17.32, -0.7999999999999986, 11.855]]),
18
- poly3.fromPoints([[-17.32, -2.7500000000000013, -10.000000000000076], [-17.32, -2.7500000000000018, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855]]),
19
- poly3.fromPoints([[-17.32, -2.7499999999999982, 11.855], [-17.32, -2.7499999999999987, 9.999999999999996], [-14.107139999999996, -2.7499999999999982, 11.855]]),
20
- poly3.fromPoints([[-17.32, -2.7499999999999982, 11.855], [-19.61, -2.7499999999999982, 11.855], [-19.61, -2.7500000000000018, -11.855], [-17.32, -2.7500000000000018, -11.855]]),
21
- poly3.fromPoints([[-14.107139999999996, -0.7999999999999986, 11.855], [-19.61, -0.7999999999999986, 11.855], [-19.61, -2.7499999999999982, 11.855], [-14.107139999999994, -2.7499999999999982, 11.855]]),
22
- poly3.fromPoints([[-19.61, -0.8000000000000015, -11.855], [-14.292644267871486, -0.8000000000000015, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855], [-19.61, -2.7500000000000018, -11.855]])
9
+ poly3.create([[-19.61, -0.7999999999999986, 11.855], [-19.61, -0.8000000000000015, -11.855], [-19.61, -2.7500000000000018, -11.855], [-19.61, -2.7499999999999982, 11.855]]),
10
+ poly3.create([[-17.32, -2.75, 10], [-17.32, -2.7500000000000013, -10], [-17.32, -0.8000000000000014, -10], [-17.32, -0.7999999999999987, 10]]),
11
+ poly3.create([[-16.863040644206997, -0.8000000000000015, -10.28], [-16.863040644206997, -2.7500000000000018, -10.28], [-14.292644267871385, -2.7500000000000018, -11.855000000000016], [-14.292644267871383, -0.8000000000000015, -11.855000000000018]]),
12
+ poly3.create([[-17.319999999999993, -0.8000000000000015, -9.999999999999996], [-17.319999999999993, -2.7500000000000018, -9.999999999999996], [-16.87560702649131, -2.7500000000000018, -10.272299999999998], [-16.866696319053347, -0.8000000000000015, -10.277759999999997]]),
13
+ poly3.create([[-16.863040644207004, -2.7500000000000013, -10.280000000000001], [-16.863040644207004, -0.8000000000000014, -10.280000000000001], [-16.86669631905335, -0.8000000000000012, -10.27776], [-16.875607026491313, -2.75, -10.272300000000001]]),
14
+ poly3.create([[-14.107140000000015, -0.7999999999999987, 11.85500000000003], [-14.107140000000015, -2.7499999999999982, 11.85500000000003], [-17.319999999999975, -2.7499999999999982, 9.999999999999956], [-17.319999999999975, -0.7999999999999987, 9.999999999999956]]),
15
+ poly3.create([[-17.32, -0.7999999999999988, 9.999999999999993], [-17.32, -0.7999999999999986, 11.855], [-14.107139999999994, -0.7999999999999986, 11.855]]),
16
+ poly3.create([[-17.32, -0.800000000000001, -11.855], [-17.32, -0.8000000000000008, -10.000000000000078], [-14.292644267871482, -0.800000000000001, -11.855]]),
17
+ poly3.create([[-17.32, -0.800000000000001, -11.855], [-19.61, -0.800000000000001, -11.855], [-19.61, -0.7999999999999986, 11.855], [-17.32, -0.7999999999999986, 11.855]]),
18
+ poly3.create([[-17.32, -2.7500000000000013, -10.000000000000076], [-17.32, -2.7500000000000018, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855]]),
19
+ poly3.create([[-17.32, -2.7499999999999982, 11.855], [-17.32, -2.7499999999999987, 9.999999999999996], [-14.107139999999996, -2.7499999999999982, 11.855]]),
20
+ poly3.create([[-17.32, -2.7499999999999982, 11.855], [-19.61, -2.7499999999999982, 11.855], [-19.61, -2.7500000000000018, -11.855], [-17.32, -2.7500000000000018, -11.855]]),
21
+ poly3.create([[-14.107139999999996, -0.7999999999999986, 11.855], [-19.61, -0.7999999999999986, 11.855], [-19.61, -2.7499999999999982, 11.855], [-14.107139999999994, -2.7499999999999982, 11.855]]),
22
+ poly3.create([[-19.61, -0.8000000000000015, -11.855], [-14.292644267871486, -0.8000000000000015, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855], [-19.61, -2.7500000000000018, -11.855]])
23
23
  ]
24
24
 
25
25
  const sub = geom3.create(polies)
@@ -10,7 +10,8 @@ const poly3 = require('../../geometries/poly3')
10
10
 
11
11
  const sphere = require('../../primitives/sphere')
12
12
 
13
- const retessellate = require('../booleans/retessellate')
13
+ const retessellate = require('../modifiers/retessellate')
14
+
14
15
  const unionGeom3Sub = require('../booleans/unionGeom3Sub')
15
16
 
16
17
  const extrudePolygon = require('./extrudePolygon')
@@ -166,7 +167,7 @@ const expandShell = (options, geometry) => {
166
167
  startfacevertices.push(p1)
167
168
  endfacevertices.push(p2)
168
169
  const points = [prevp2, p2, p1, prevp1]
169
- const polygon = poly3.fromPoints(points)
170
+ const polygon = poly3.create(points)
170
171
  polygons.push(polygon)
171
172
  }
172
173
  prevp1 = p1
@@ -174,8 +175,8 @@ const expandShell = (options, geometry) => {
174
175
  }
175
176
  }
176
177
  endfacevertices.reverse()
177
- polygons.push(poly3.fromPoints(startfacevertices))
178
- polygons.push(poly3.fromPoints(endfacevertices))
178
+ polygons.push(poly3.create(startfacevertices))
179
+ polygons.push(poly3.create(endfacevertices))
179
180
 
180
181
  const cylinder = geom3.create(polygons)
181
182
  result = unionGeom3Sub(result, cylinder)
@@ -17,14 +17,14 @@ const extrudePolygon = (offsetvector, polygon1) => {
17
17
  const polygon2 = poly3.transform(mat4.fromTranslation(mat4.create(), offsetvector), polygon1)
18
18
  const numvertices = polygon1.vertices.length
19
19
  for (let i = 0; i < numvertices; i++) {
20
- const sidefacepoints = []
21
20
  const nexti = (i < (numvertices - 1)) ? i + 1 : 0
22
- sidefacepoints.push(polygon1.vertices[i])
23
- sidefacepoints.push(polygon2.vertices[i])
24
- sidefacepoints.push(polygon2.vertices[nexti])
25
- sidefacepoints.push(polygon1.vertices[nexti])
26
- const sidefacepolygon = poly3.fromPoints(sidefacepoints)
27
- newpolygons.push(sidefacepolygon)
21
+ const sideFacePolygon = poly3.create([
22
+ polygon1.vertices[i],
23
+ polygon2.vertices[i],
24
+ polygon2.vertices[nexti],
25
+ polygon1.vertices[nexti]
26
+ ])
27
+ newpolygons.push(sideFacePolygon)
28
28
  }
29
29
  newpolygons.push(poly3.invert(polygon2))
30
30
 
@@ -32,7 +32,7 @@ test('extrudeFromSlices (defaults)', (t) => {
32
32
  t.is(pts.length, 12)
33
33
  t.true(comparePolygonsAsPoints(pts, exp))
34
34
 
35
- const poly2 = poly3.fromPoints([[10, 10, 0], [-10, 10, 0], [-10, -10, 0], [10, -10, 0]])
35
+ const poly2 = poly3.create([[10, 10, 0], [-10, 10, 0], [-10, -10, 0], [10, -10, 0]])
36
36
  geometry3 = extrudeFromSlices({ }, poly2)
37
37
  pts = geom3.toPoints(geometry3)
38
38
 
@@ -45,7 +45,7 @@ test('extrudeFromSlices (torus)', (t) => {
45
45
  const sqrt3 = Math.sqrt(3) / 2
46
46
  const radius = 10
47
47
 
48
- let hex = poly3.fromPoints([
48
+ let hex = poly3.create([
49
49
  [radius, 0, 0],
50
50
  [radius / 2, radius * sqrt3, 0],
51
51
  [-radius / 2, radius * sqrt3, 0],
@@ -114,7 +114,11 @@ const extrudeRotate = (options, geometry) => {
114
114
 
115
115
  const matrix = mat4.create()
116
116
  const createSlice = (progress, index, base) => {
117
- const Zrotation = rotationPerSlice * index + startAngle
117
+ let Zrotation = rotationPerSlice * index + startAngle
118
+ // fix rounding error when rotating 2 * PI radians
119
+ if (totalRotation === Math.PI * 2 && index === segments) {
120
+ Zrotation = startAngle
121
+ }
118
122
  mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(), Math.PI / 2))
119
123
 
120
124
  return slice.transform(matrix, base)
@@ -11,7 +11,7 @@ test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3'
11
11
 
12
12
  const geometry3 = extrudeRotate({ }, geometry2)
13
13
  const pts = geom3.toPoints(geometry3)
14
- t.notThrows.skip(() => geom3.validate(geometry3))
14
+ t.notThrows(() => geom3.validate(geometry3))
15
15
  t.is(pts.length, 96)
16
16
  })
17
17
 
@@ -61,7 +61,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
61
61
  [18.38477631085024, 18.384776310850235, 8],
62
62
  [-11.803752993228215, 23.166169628897567, 8]
63
63
  ]
64
- t.notThrows.skip(() => geom3.validate(geometry3))
64
+ t.notThrows(() => geom3.validate(geometry3))
65
65
  t.is(pts.length, 40)
66
66
  t.true(comparePoints(pts[0], exp))
67
67
 
@@ -72,7 +72,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
72
72
  [18.38477631085024, -18.384776310850235, 8],
73
73
  [23.166169628897567, 11.803752993228215, 8]
74
74
  ]
75
- t.notThrows.skip(() => geom3.validate(geometry3))
75
+ t.notThrows(() => geom3.validate(geometry3))
76
76
  t.is(pts.length, 40)
77
77
  t.true(comparePoints(pts[0], exp))
78
78
  })
@@ -83,12 +83,12 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
83
83
  // test segments
84
84
  let geometry3 = extrudeRotate({ segments: 4 }, geometry2)
85
85
  let pts = geom3.toPoints(geometry3)
86
- t.notThrows.skip(() => geom3.validate(geometry3))
86
+ t.notThrows(() => geom3.validate(geometry3))
87
87
  t.is(pts.length, 32)
88
88
 
89
89
  geometry3 = extrudeRotate({ segments: 64 }, geometry2)
90
90
  pts = geom3.toPoints(geometry3)
91
- t.notThrows.skip(() => geom3.validate(geometry3))
91
+ t.notThrows(() => geom3.validate(geometry3))
92
92
  t.is(pts.length, 512)
93
93
 
94
94
  // test overlapping edges
@@ -64,11 +64,11 @@ const extrudeWalls = (slice0, slice1) => {
64
64
  edges0.forEach((edge0, i) => {
65
65
  const edge1 = edges1[i]
66
66
 
67
- const poly0 = poly3.fromPoints([edge0[0], edge0[1], edge1[1]])
67
+ const poly0 = poly3.create([edge0[0], edge0[1], edge1[1]])
68
68
  const poly0area = poly3.measureArea(poly0)
69
69
  if (Number.isFinite(poly0area) && poly0area > EPSAREA) walls.push(poly0)
70
70
 
71
- const poly1 = poly3.fromPoints([edge0[0], edge1[1], edge1[0]])
71
+ const poly1 = poly3.create([edge0[0], edge1[1], edge1[0]])
72
72
  const poly1area = poly3.measureArea(poly1)
73
73
  if (Number.isFinite(poly1area) && poly1area > EPSAREA) walls.push(poly1)
74
74
  })
@@ -11,7 +11,6 @@ const poly3 = require('../../geometries/poly3')
11
11
  const measureEpsilon = require('../../measurements/measureEpsilon')
12
12
 
13
13
  const unionGeom2 = require('../booleans/unionGeom2')
14
- const unionGeom3 = require('../booleans/unionGeom3')
15
14
 
16
15
  const projectGeom3 = (options, geometry) => {
17
16
  // create a plane from the options, and verify
@@ -27,32 +26,30 @@ const projectGeom3 = (options, geometry) => {
27
26
 
28
27
  // project the polygons to the plane
29
28
  const polygons = geom3.toPolygons(geometry)
30
- const projpolys = []
29
+ let projpolys = []
31
30
  for (let i = 0; i < polygons.length; i++) {
32
31
  const newpoints = polygons[i].vertices.map((v) => plane.projectionOfPoint(projplane, v))
33
32
  const newpoly = poly3.create(newpoints)
34
- // only keep projections that have a measurable area
35
- if (poly3.measureArea(newpoly) < epsilonArea) continue
36
33
  // only keep projections that face the same direction as the plane
37
34
  const newplane = poly3.plane(newpoly)
38
35
  if (!aboutEqualNormals(projplane, newplane)) continue
36
+ // only keep projections that have a measurable area
37
+ if (poly3.measureArea(newpoly) < epsilonArea) continue
39
38
  projpolys.push(newpoly)
40
39
  }
41
- // union the projected polygons to eliminate overlaying polygons
42
- let projection = geom3.create(projpolys)
43
- projection = unionGeom3(projection, projection)
44
- // rotate the projection to lay on X/Y axes if necessary
40
+
41
+ // rotate the polygons to lay on X/Y axes if necessary
45
42
  if (!aboutEqualNormals(projplane, [0, 0, 1])) {
46
43
  const rotation = mat4.fromVectorRotation(mat4.create(), projplane, [0, 0, 1])
47
- projection = geom3.transform(rotation, projection)
44
+ projpolys = projpolys.map((p) => poly3.transform(rotation, p))
48
45
  }
49
46
 
50
- // convert the projection (polygons) into a series of 2D geometry
51
- const projections2D = geom3.toPolygons(projection).map((p) => geom2.fromPoints(poly3.toPoints(p)))
52
- // union the 2D geometries to obtain the outline of the projection
53
- projection = unionGeom2(projections2D)
47
+ // sort the polygons to allow the union to ignore small pieces efficiently
48
+ projpolys = projpolys.sort((a, b) => poly3.measureArea(b) - poly3.measureArea(a))
54
49
 
55
- return projection
50
+ // convert polygons to geometry, and union all pieces into a single geometry
51
+ const projgeoms = projpolys.map((p) => geom2.fromPoints(p.vertices))
52
+ return unionGeom2(projgeoms)
56
53
  }
57
54
 
58
55
  /**