@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.
- package/CHANGELOG.md +20 -0
- package/README.md +4 -4
- package/dist/jscad-modeling.min.js +411 -417
- package/package.json +3 -2
- package/src/colors/colorize.test.js +1 -1
- package/src/geometries/geom2/toOutlines.js +66 -52
- package/src/geometries/geom3/create.js +1 -1
- package/src/geometries/geom3/create.test.js +1 -1
- package/src/geometries/geom3/fromPoints.js +1 -1
- package/src/geometries/path2/index.d.ts +0 -1
- package/src/geometries/path2/index.js +0 -1
- package/src/geometries/poly3/create.js +1 -1
- package/src/geometries/poly3/validate.js +14 -0
- package/src/maths/constants.d.ts +1 -0
- package/src/maths/constants.js +11 -0
- package/src/maths/utils/aboutEqualNormals.js +1 -5
- package/src/operations/booleans/intersectGeom3.js +2 -1
- package/src/operations/booleans/subtractGeom3.js +2 -1
- package/src/operations/booleans/to3DWalls.js +1 -1
- package/src/operations/booleans/unionGeom3.js +2 -1
- package/src/operations/expansions/expandGeom3.test.js +14 -14
- package/src/operations/expansions/expandShell.js +5 -4
- package/src/operations/expansions/extrudePolygon.js +7 -7
- package/src/operations/extrusions/extrudeFromSlices.test.js +2 -2
- package/src/operations/extrusions/extrudeRotate.js +5 -1
- package/src/operations/extrusions/extrudeRotate.test.js +5 -5
- package/src/operations/extrusions/extrudeWalls.js +2 -2
- package/src/operations/extrusions/project.js +11 -14
- package/src/operations/extrusions/project.test.js +50 -50
- package/src/operations/hulls/hullChain.test.js +4 -4
- package/src/operations/hulls/hullGeom2.js +6 -18
- package/src/operations/hulls/hullGeom3.js +5 -18
- package/src/operations/hulls/hullPath2.js +4 -14
- package/src/operations/hulls/hullPoints2.js +43 -92
- package/src/operations/hulls/toUniquePoints.js +34 -0
- package/src/operations/modifiers/generalize.js +2 -13
- package/src/operations/modifiers/generalize.test.js +0 -32
- package/src/operations/modifiers/insertTjunctions.js +1 -1
- package/src/operations/modifiers/insertTjunctions.test.js +21 -21
- package/src/operations/modifiers/mergePolygons.js +11 -14
- package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +1 -1
- package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
- package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
- package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
- package/src/operations/modifiers/snapPolygons.test.js +12 -12
- package/src/operations/modifiers/triangulatePolygons.js +3 -3
- package/src/operations/transforms/center.js +1 -1
- package/src/primitives/cuboid.js +1 -1
- package/src/primitives/cylinderElliptic.js +1 -1
- package/src/primitives/ellipsoid.js +2 -2
- package/src/primitives/polyhedron.js +1 -1
- package/src/primitives/roundedCuboid.js +6 -6
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/torus.test.js +6 -6
- package/src/primitives/triangle.js +1 -2
- package/src/utils/trigonometry.js +1 -2
- package/src/geometries/path2/eachPoint.d.ts +0 -9
- package/src/geometries/path2/eachPoint.js +0 -17
- package/src/geometries/path2/eachPoint.test.js +0 -11
- package/src/operations/modifiers/edges.js +0 -195
- 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.
|
|
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": "
|
|
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.
|
|
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
|
|
10
|
-
const
|
|
9
|
+
const toSharedVertices = (sides) => {
|
|
10
|
+
const unique = new Map() // {key: vertex}
|
|
11
11
|
const getUniqueVertex = (vertex) => {
|
|
12
12
|
const key = vertex.toString()
|
|
13
|
-
if (
|
|
14
|
-
|
|
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
|
|
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 =
|
|
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
|
|
56
|
+
let startSide
|
|
46
57
|
for (const [vertex, edges] of vertexMap) {
|
|
47
|
-
|
|
48
|
-
if (!
|
|
58
|
+
startSide = edges.shift()
|
|
59
|
+
if (!startSide) {
|
|
49
60
|
vertexMap.delete(vertex)
|
|
50
61
|
continue
|
|
51
62
|
}
|
|
52
63
|
break
|
|
53
64
|
}
|
|
54
|
-
if (
|
|
65
|
+
if (startSide === undefined) break // all starting sides have been visited
|
|
55
66
|
|
|
56
67
|
const connectedVertexPoints = []
|
|
57
|
-
const
|
|
58
|
-
const v0 = vec2.create()
|
|
68
|
+
const startVertex = startSide[0]
|
|
59
69
|
while (true) {
|
|
60
|
-
connectedVertexPoints.push(
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
65
|
-
throw new Error(
|
|
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
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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'),
|
|
@@ -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
|
package/src/maths/constants.d.ts
CHANGED
package/src/maths/constants.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
@@ -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.
|
|
10
|
-
poly3.
|
|
11
|
-
poly3.
|
|
12
|
-
poly3.
|
|
13
|
-
poly3.
|
|
14
|
-
poly3.
|
|
15
|
-
poly3.
|
|
16
|
-
poly3.
|
|
17
|
-
poly3.
|
|
18
|
-
poly3.
|
|
19
|
-
poly3.
|
|
20
|
-
poly3.
|
|
21
|
-
poly3.
|
|
22
|
-
poly3.
|
|
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('../
|
|
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.
|
|
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.
|
|
178
|
-
polygons.push(poly3.
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
44
|
+
projpolys = projpolys.map((p) => poly3.transform(rotation, p))
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
//
|
|
51
|
-
|
|
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
|
-
|
|
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
|
/**
|