@jscad/modeling 2.9.2 → 2.9.5
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 +50 -0
- package/README.md +4 -4
- package/dist/jscad-modeling.min.js +435 -444
- package/package.json +3 -2
- package/src/colors/colorize.d.ts +6 -5
- package/src/colors/colorize.test.js +1 -1
- package/src/geometries/geom2/toOutlines.js +66 -52
- package/src/geometries/geom2/type.d.ts +3 -2
- package/src/geometries/geom3/applyTransforms.js +1 -2
- 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/geom3/type.d.ts +3 -2
- package/src/geometries/path2/index.d.ts +0 -1
- package/src/geometries/path2/index.js +0 -1
- package/src/geometries/path2/type.d.ts +3 -2
- package/src/geometries/poly3/create.js +1 -1
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
- package/src/geometries/poly3/measureBoundingSphere.js +46 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
- package/src/geometries/poly3/type.d.ts +3 -2
- package/src/geometries/poly3/validate.js +14 -0
- package/src/geometries/types.d.ts +4 -2
- package/src/maths/constants.d.ts +1 -0
- package/src/maths/constants.js +11 -0
- package/src/maths/mat4/fromRotation.js +9 -7
- package/src/maths/mat4/fromTaitBryanRotation.js +8 -6
- package/src/maths/mat4/fromXRotation.js +4 -2
- package/src/maths/mat4/fromYRotation.js +4 -2
- package/src/maths/mat4/fromZRotation.js +4 -2
- package/src/maths/mat4/isMirroring.js +11 -11
- package/src/maths/mat4/rotate.js +9 -5
- package/src/maths/mat4/rotateX.js +4 -2
- package/src/maths/mat4/rotateY.js +4 -2
- package/src/maths/mat4/rotateZ.js +4 -2
- package/src/maths/mat4/translate.test.js +2 -3
- package/src/maths/utils/aboutEqualNormals.js +1 -5
- package/src/maths/utils/index.d.ts +1 -0
- package/src/maths/utils/index.js +2 -0
- package/src/{utils → maths/utils}/trigonometry.d.ts +0 -0
- package/src/{utils → maths/utils}/trigonometry.js +1 -2
- package/src/{utils → maths/utils}/trigonometry.test.js +0 -0
- package/src/maths/vec2/distance.js +1 -1
- package/src/maths/vec2/fromAngleRadians.js +4 -2
- package/src/maths/vec2/length.js +1 -1
- package/src/maths/vec2/length.test.js +0 -10
- package/src/maths/vec3/angle.js +2 -2
- package/src/maths/vec3/angle.test.js +0 -12
- package/src/maths/vec3/distance.js +1 -1
- package/src/maths/vec3/length.js +1 -1
- package/src/maths/vec3/length.test.js +0 -10
- package/src/operations/booleans/intersectGeom2.test.js +69 -0
- package/src/operations/booleans/intersectGeom3.js +2 -1
- package/src/operations/booleans/{intersect.test.js → intersectGeom3.test.js} +3 -71
- package/src/operations/booleans/subtractGeom2.test.js +72 -0
- package/src/operations/booleans/subtractGeom3.js +2 -1
- package/src/operations/booleans/{subtract.test.js → subtractGeom3.test.js} +3 -74
- package/src/operations/booleans/to3DWalls.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
- package/src/operations/booleans/unionGeom2.test.js +166 -0
- package/src/operations/booleans/unionGeom3.js +2 -1
- package/src/operations/booleans/{union.test.js → unionGeom3.test.js} +3 -168
- 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.js +3 -2
- package/src/operations/extrusions/extrudeFromSlices.test.js +2 -2
- package/src/operations/extrusions/extrudeRotate.js +5 -1
- package/src/operations/extrusions/extrudeRotate.test.js +47 -47
- package/src/operations/extrusions/extrudeWalls.js +2 -2
- package/src/operations/extrusions/project.js +11 -14
- package/src/operations/extrusions/project.test.js +49 -49
- package/src/operations/extrusions/slice/repair.js +62 -0
- 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 +33 -32
- 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/circle.test.js +7 -0
- package/src/primitives/cuboid.js +1 -1
- package/src/primitives/cylinderElliptic.js +5 -3
- package/src/primitives/cylinderElliptic.test.js +7 -1
- package/src/primitives/ellipse.js +1 -1
- package/src/primitives/ellipse.test.js +7 -0
- package/src/primitives/ellipsoid.js +3 -3
- package/src/primitives/geodesicSphere.js +3 -2
- package/src/primitives/polyhedron.js +1 -1
- package/src/primitives/roundedCuboid.js +10 -8
- package/src/primitives/roundedCylinder.js +2 -2
- package/src/primitives/torus.test.js +11 -7
- package/src/primitives/triangle.js +1 -2
- package/src/utils/index.d.ts +0 -1
- package/src/utils/index.js +1 -3
- 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/maths/mat4/constants.d.ts +0 -1
- package/src/maths/mat4/constants.js +0 -5
- package/src/operations/extrusions/slice/repairSlice.js +0 -47
- 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.5",
|
|
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": "225b034db0d94f748992da72b269833954a2e212"
|
|
64
65
|
}
|
package/src/colors/colorize.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { RGB, RGBA } from './types'
|
|
|
5
5
|
|
|
6
6
|
export default colorize
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
declare function colorize<T>(color: RGB | RGBA, object: T): T & Colored
|
|
8
|
+
// Single Geom3 returns Colored Geom3
|
|
9
|
+
declare function colorize<T extends Geometry>(color: RGB | RGBA, object: T): T & Colored
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
declare function colorize<T>(color: RGB | RGBA, ...objects: RecursiveArray<T>): Array<T & Colored>
|
|
13
|
-
|
|
11
|
+
// List of Geom3 returns list of Colored Geom3
|
|
12
|
+
declare function colorize<T extends Geometry>(color: RGB | RGBA, ...objects: RecursiveArray<T>): Array<T & Colored>
|
|
13
|
+
// List of mixed geometries returns list of colored geometries
|
|
14
|
+
declare function colorize(color: RGB | RGBA, ...objects: RecursiveArray<Geometry>): Array<Geometry & Colored>
|
|
@@ -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
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import Vec2 from '../../maths/vec2/type'
|
|
2
2
|
import Mat4 from '../../maths/mat4/type'
|
|
3
|
-
import {
|
|
3
|
+
import { Color } from '../types'
|
|
4
4
|
|
|
5
5
|
export default Geom2
|
|
6
6
|
|
|
7
|
-
declare interface Geom2
|
|
7
|
+
declare interface Geom2 {
|
|
8
8
|
sides: Array<[Vec2, Vec2]>
|
|
9
9
|
transforms: Mat4
|
|
10
|
+
color?: Color
|
|
10
11
|
}
|
|
@@ -14,9 +14,8 @@ const applyTransforms = (geometry) => {
|
|
|
14
14
|
if (mat4.isIdentity(geometry.transforms)) return geometry
|
|
15
15
|
|
|
16
16
|
// apply transforms to each polygon
|
|
17
|
-
// const isMirror = mat4.isMirroring(geometry.transforms)
|
|
18
|
-
// TBD if (isMirror) newvertices.reverse()
|
|
19
17
|
geometry.polygons = geometry.polygons.map((polygon) => poly3.transform(geometry.transforms, polygon))
|
|
18
|
+
// reset transforms
|
|
20
19
|
geometry.transforms = mat4.create()
|
|
21
20
|
return geometry
|
|
22
21
|
}
|
|
@@ -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)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import Poly3 from '../poly3/type'
|
|
2
2
|
import Mat4 from '../../maths/mat4/type'
|
|
3
|
-
import {
|
|
3
|
+
import { Color } from '../types'
|
|
4
4
|
|
|
5
5
|
export default Geom3
|
|
6
6
|
|
|
7
|
-
declare interface Geom3
|
|
7
|
+
declare interface Geom3 {
|
|
8
8
|
polygons: Array<Poly3>
|
|
9
9
|
transforms: Mat4
|
|
10
|
+
color?: Color
|
|
10
11
|
}
|
|
@@ -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,11 +1,12 @@
|
|
|
1
1
|
import Vec2 from '../../maths/vec2/type'
|
|
2
2
|
import Mat4 from '../../maths/mat4/type'
|
|
3
|
-
import {
|
|
3
|
+
import { Color } from '../types'
|
|
4
4
|
|
|
5
5
|
export default Path2
|
|
6
6
|
|
|
7
|
-
declare interface Path2
|
|
7
|
+
declare interface Path2 {
|
|
8
8
|
points: Array<Vec2>
|
|
9
9
|
isClosed: boolean
|
|
10
10
|
transforms: Mat4
|
|
11
|
+
color?: Color
|
|
11
12
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Poly3 from './type'
|
|
2
|
-
import
|
|
2
|
+
import Vec4 from '../../maths/vec4/type'
|
|
3
3
|
|
|
4
4
|
export default measureBoundingSphere
|
|
5
5
|
|
|
6
|
-
declare function measureBoundingSphere(polygon: Poly3):
|
|
6
|
+
declare function measureBoundingSphere(polygon: Poly3): Vec4
|
|
@@ -1,19 +1,57 @@
|
|
|
1
1
|
const vec3 = require('../../maths/vec3')
|
|
2
|
-
const
|
|
2
|
+
const vec4 = require('../../maths/vec4')
|
|
3
|
+
|
|
4
|
+
const cache = new WeakMap()
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Measure the bounding sphere of the given polygon.
|
|
6
8
|
* @param {poly3} polygon - the polygon to measure
|
|
7
|
-
* @returns {
|
|
9
|
+
* @returns {vec4} the computed bounding sphere; center point (3D) and radius
|
|
8
10
|
* @alias module:modeling/geometries/poly3.measureBoundingSphere
|
|
9
11
|
*/
|
|
10
12
|
const measureBoundingSphere = (polygon) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
13
|
+
let boundingSphere = cache.get(polygon)
|
|
14
|
+
if (boundingSphere) return boundingSphere
|
|
15
|
+
|
|
16
|
+
const vertices = polygon.vertices
|
|
17
|
+
const out = vec4.create()
|
|
18
|
+
|
|
19
|
+
if (vertices.length === 0) {
|
|
20
|
+
out[0] = 0
|
|
21
|
+
out[1] = 0
|
|
22
|
+
out[2] = 0
|
|
23
|
+
out[3] = 0
|
|
24
|
+
return out
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// keep a list of min/max vertices by axis
|
|
28
|
+
let minx = vertices[0]
|
|
29
|
+
let miny = minx
|
|
30
|
+
let minz = minx
|
|
31
|
+
let maxx = minx
|
|
32
|
+
let maxy = minx
|
|
33
|
+
let maxz = minx
|
|
34
|
+
|
|
35
|
+
vertices.forEach((v) => {
|
|
36
|
+
if (minx[0] > v[0]) minx = v
|
|
37
|
+
if (miny[1] > v[1]) miny = v
|
|
38
|
+
if (minz[2] > v[2]) minz = v
|
|
39
|
+
if (maxx[0] < v[0]) maxx = v
|
|
40
|
+
if (maxy[1] < v[1]) maxy = v
|
|
41
|
+
if (maxz[2] < v[2]) maxz = v
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
out[0] = (minx[0] + maxx[0]) * 0.5 // center of sphere
|
|
45
|
+
out[1] = (miny[1] + maxy[1]) * 0.5
|
|
46
|
+
out[2] = (minz[2] + maxz[2]) * 0.5
|
|
47
|
+
const x = out[0] - maxx[0]
|
|
48
|
+
const y = out[1] - maxy[1]
|
|
49
|
+
const z = out[2] - maxz[2]
|
|
50
|
+
out[3] = Math.sqrt(x * x + y * y + z * z) // radius of sphere
|
|
51
|
+
|
|
52
|
+
cache.set(polygon, out)
|
|
53
|
+
|
|
54
|
+
return out
|
|
17
55
|
}
|
|
18
56
|
|
|
19
57
|
module.exports = measureBoundingSphere
|
|
@@ -3,28 +3,23 @@ const { measureBoundingSphere, create, fromPoints, transform } = require('./inde
|
|
|
3
3
|
|
|
4
4
|
const mat4 = require('../../maths/mat4')
|
|
5
5
|
|
|
6
|
-
const { compareVectors, nearlyEqual } = require('../../../test/helpers/index')
|
|
7
|
-
|
|
8
6
|
test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
9
7
|
let ply1 = create()
|
|
10
|
-
let exp1 = [
|
|
8
|
+
let exp1 = [0, 0, 0, 0]
|
|
11
9
|
let ret1 = measureBoundingSphere(ply1)
|
|
12
|
-
t.
|
|
13
|
-
nearlyEqual(t, ret1[1], exp1[1], Number.EPSILON)
|
|
10
|
+
t.deepEqual(ret1, exp1)
|
|
14
11
|
|
|
15
12
|
// simple triangle
|
|
16
13
|
let ply2 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10]])
|
|
17
|
-
let exp2 = [
|
|
14
|
+
let exp2 = [0, 5, 5, 7.0710678118654755]
|
|
18
15
|
let ret2 = measureBoundingSphere(ply2)
|
|
19
|
-
t.
|
|
20
|
-
nearlyEqual(t, ret2[1], exp2[1], Number.EPSILON)
|
|
16
|
+
t.deepEqual(ret2, exp2)
|
|
21
17
|
|
|
22
18
|
// simple square
|
|
23
19
|
let ply3 = fromPoints([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]])
|
|
24
|
-
let exp3 = [
|
|
20
|
+
let exp3 = [0, 5, 5, 7.0710678118654755]
|
|
25
21
|
let ret3 = measureBoundingSphere(ply3)
|
|
26
|
-
t.
|
|
27
|
-
nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
|
|
22
|
+
t.deepEqual(ret3, exp3)
|
|
28
23
|
|
|
29
24
|
// V-shape
|
|
30
25
|
const points = [
|
|
@@ -40,10 +35,9 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
|
40
35
|
[0, 3, 3]
|
|
41
36
|
]
|
|
42
37
|
let ply4 = fromPoints(points)
|
|
43
|
-
let exp4 = [
|
|
38
|
+
let exp4 = [0, 4.5, 3, 4.6097722286464435]
|
|
44
39
|
let ret4 = measureBoundingSphere(ply4)
|
|
45
|
-
t.
|
|
46
|
-
nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
|
|
40
|
+
t.deepEqual(ret4, exp4)
|
|
47
41
|
|
|
48
42
|
// rotated to various angles
|
|
49
43
|
const rotation = mat4.fromZRotation(mat4.create(), (45 * 0.017453292519943295))
|
|
@@ -55,16 +49,12 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
|
55
49
|
ret2 = measureBoundingSphere(ply2)
|
|
56
50
|
ret3 = measureBoundingSphere(ply3)
|
|
57
51
|
ret4 = measureBoundingSphere(ply4)
|
|
58
|
-
exp1 = [
|
|
59
|
-
t.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
t.
|
|
66
|
-
nearlyEqual(t, ret3[1], exp3[1], Number.EPSILON)
|
|
67
|
-
exp4 = [[-3.181980515339464, 3.1819805153394642, 3], 4.6097722286464435]
|
|
68
|
-
t.true(compareVectors(ret4[0], exp4[0]))
|
|
69
|
-
nearlyEqual(t, ret4[1], exp4[1], Number.EPSILON)
|
|
52
|
+
exp1 = [0, 0, 0, 0]
|
|
53
|
+
t.deepEqual(ret1, exp1)
|
|
54
|
+
exp2 = [-3.5355339059327373, 3.5355339059327378, 5, 7.0710678118654755]
|
|
55
|
+
t.deepEqual(ret2, exp2)
|
|
56
|
+
exp3 = [-3.5355339059327373, 3.5355339059327378, 5, 7.0710678118654755]
|
|
57
|
+
t.deepEqual(ret3, exp3)
|
|
58
|
+
exp4 = [-3.181980515339464, 3.1819805153394642, 3, 4.6097722286464435]
|
|
59
|
+
t.deepEqual(ret4, exp4)
|
|
70
60
|
})
|
|
@@ -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
|
|
@@ -8,8 +8,10 @@ import { RGB, RGBA } from '../colors'
|
|
|
8
8
|
// see https://github.com/jscad/OpenJSCAD.org/pull/726#issuecomment-724575265
|
|
9
9
|
export type Geometry = Geom2 | Geom3 | Poly3 | Path2
|
|
10
10
|
|
|
11
|
-
export type
|
|
12
|
-
|
|
11
|
+
export type Color = RGB | RGBA
|
|
12
|
+
|
|
13
|
+
export interface Colored {
|
|
14
|
+
color: Color
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export { default as Geom2 } from './geom2/type'
|
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,6 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { EPS } = require('../constants')
|
|
2
|
+
|
|
3
|
+
const { sin, cos } = require('../utils/trigonometry')
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const identity = require('./identity')
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Creates a matrix from a given angle around a given axis
|
|
@@ -19,20 +21,20 @@ const { EPSILON } = require('./constants')
|
|
|
19
21
|
*/
|
|
20
22
|
const fromRotation = (out, rad, axis) => {
|
|
21
23
|
let [x, y, z] = axis
|
|
22
|
-
|
|
24
|
+
const lengthSquared = x * x + y * y + z * z
|
|
23
25
|
|
|
24
|
-
if (Math.abs(
|
|
26
|
+
if (Math.abs(lengthSquared) < EPS) {
|
|
25
27
|
// axis is 0,0,0 or almost
|
|
26
28
|
return identity(out)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
len = 1 /
|
|
31
|
+
const len = 1 / Math.sqrt(lengthSquared)
|
|
30
32
|
x *= len
|
|
31
33
|
y *= len
|
|
32
34
|
z *= len
|
|
33
35
|
|
|
34
|
-
const s =
|
|
35
|
-
const c =
|
|
36
|
+
const s = sin(rad)
|
|
37
|
+
const c = cos(rad)
|
|
36
38
|
const t = 1 - c
|
|
37
39
|
|
|
38
40
|
// Perform rotation-specific matrix multiplication
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { sin, cos } = require('../utils/trigonometry')
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Creates a matrix from the given Tait–Bryan angles.
|
|
3
5
|
*
|
|
@@ -15,12 +17,12 @@
|
|
|
15
17
|
*/
|
|
16
18
|
const fromTaitBryanRotation = (out, yaw, pitch, roll) => {
|
|
17
19
|
// precompute sines and cosines of Euler angles
|
|
18
|
-
const sy =
|
|
19
|
-
const cy =
|
|
20
|
-
const sp =
|
|
21
|
-
const cp =
|
|
22
|
-
const sr =
|
|
23
|
-
const cr =
|
|
20
|
+
const sy = sin(yaw)
|
|
21
|
+
const cy = cos(yaw)
|
|
22
|
+
const sp = sin(pitch)
|
|
23
|
+
const cp = cos(pitch)
|
|
24
|
+
const sr = sin(roll)
|
|
25
|
+
const cr = cos(roll)
|
|
24
26
|
|
|
25
27
|
// create and populate rotation matrix
|
|
26
28
|
// left-hand-rule rotation
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { sin, cos } = require('../utils/trigonometry')
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Creates a matrix from the given angle around the X axis.
|
|
3
5
|
* This is equivalent to (but much faster than):
|
|
@@ -13,8 +15,8 @@
|
|
|
13
15
|
* let matrix = fromXRotation(create(), Math.PI / 2)
|
|
14
16
|
*/
|
|
15
17
|
const fromXRotation = (out, radians) => {
|
|
16
|
-
const s =
|
|
17
|
-
const c =
|
|
18
|
+
const s = sin(radians)
|
|
19
|
+
const c = cos(radians)
|
|
18
20
|
|
|
19
21
|
// Perform axis-specific matrix multiplication
|
|
20
22
|
out[0] = 1
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { sin, cos } = require('../utils/trigonometry')
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Creates a matrix from the given angle around the Y axis.
|
|
3
5
|
* This is equivalent to (but much faster than):
|
|
@@ -13,8 +15,8 @@
|
|
|
13
15
|
* let matrix = fromYRotation(create(), Math.PI / 2)
|
|
14
16
|
*/
|
|
15
17
|
const fromYRotation = (out, radians) => {
|
|
16
|
-
const s =
|
|
17
|
-
const c =
|
|
18
|
+
const s = sin(radians)
|
|
19
|
+
const c = cos(radians)
|
|
18
20
|
|
|
19
21
|
// Perform axis-specific matrix multiplication
|
|
20
22
|
out[0] = c
|