@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
|
@@ -1,108 +1,59 @@
|
|
|
1
1
|
const vec2 = require('../../maths/vec2')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
//
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
while (ccw(stack[
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
stack[i] = stack[M]
|
|
82
|
-
stack[M] = tmp
|
|
83
|
-
}
|
|
38
|
+
stack.push(point.point)
|
|
39
|
+
})
|
|
84
40
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
|
@@ -104,38 +104,6 @@ test('generalize: generalize of a geom3 produces an expected geom3', (t) => {
|
|
|
104
104
|
]
|
|
105
105
|
t.notThrows(() => geom3.validate(result))
|
|
106
106
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
107
|
-
|
|
108
|
-
// apply repairs only (triangles)
|
|
109
|
-
result = generalize({ repair: true }, geometry2)
|
|
110
|
-
pts = geom3.toPoints(result)
|
|
111
|
-
exp = [
|
|
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
|
-
[[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, -0.7853981633974483, 3.141592653589793],
|
|
133
|
-
[1.5707963267948966, 0.7853981633974483, 3.141592653589793]],
|
|
134
|
-
[[-1.5707963267948966, -0.7853981633974483, 3.141592653589793], [1.5707963267948966, 0.7853981633974483, 3.141592653589793],
|
|
135
|
-
[-1.5707963267948966, 0.7853981633974483, 3.141592653589793]]
|
|
136
|
-
]
|
|
137
|
-
t.notThrows(() => geom3.validate(result))
|
|
138
|
-
t.true(comparePolygonsAsPoints(pts, exp))
|
|
139
107
|
})
|
|
140
108
|
|
|
141
109
|
test('generalize: generalize of a geom3 with T junctions produces an expected geom3', (t) => {
|
|
@@ -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.
|
|
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.
|
|
60
|
-
poly3.
|
|
61
|
-
poly3.
|
|
62
|
-
poly3.
|
|
63
|
-
poly3.
|
|
64
|
-
poly3.
|
|
65
|
-
poly3.
|
|
66
|
-
poly3.
|
|
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.
|
|
74
|
-
poly3.
|
|
75
|
-
poly3.
|
|
76
|
-
poly3.
|
|
77
|
-
poly3.
|
|
78
|
-
poly3.
|
|
79
|
-
poly3.
|
|
80
|
-
poly3.
|
|
81
|
-
poly3.
|
|
82
|
-
poly3.
|
|
83
|
-
poly3.
|
|
84
|
-
poly3.
|
|
85
|
-
poly3.
|
|
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(
|
|
58
|
-
const d1 = vec3.subtract(
|
|
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.
|
|
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 = (
|
|
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(
|
|
202
|
+
const retesselayedpolygons = mergeCoplanarPolygons(sourcepolygons)
|
|
206
203
|
destpolygons = destpolygons.concat(retesselayedpolygons)
|
|
207
204
|
})
|
|
208
205
|
return destpolygons
|
|
@@ -23,15 +23,14 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
23
23
|
const orthobasis = new OrthoNormalBasis(plane)
|
|
24
24
|
const polygonvertices2d = [] // array of array of Vector2D
|
|
25
25
|
const polygontopvertexindexes = [] // array of indexes of topmost vertex per polygon
|
|
26
|
-
const topy2polygonindexes =
|
|
27
|
-
const ycoordinatetopolygonindexes =
|
|
28
|
-
|
|
29
|
-
const ycoordinatebins = {}
|
|
26
|
+
const topy2polygonindexes = new Map()
|
|
27
|
+
const ycoordinatetopolygonindexes = new Map()
|
|
30
28
|
|
|
31
29
|
// convert all polygon vertices to 2D
|
|
32
30
|
// Make a list of all encountered y coordinates
|
|
33
31
|
// And build a map of all polygons that have a vertex at a certain y coordinate:
|
|
34
|
-
const
|
|
32
|
+
const ycoordinatebins = new Map()
|
|
33
|
+
const ycoordinateBinningFactor = 10 / EPS
|
|
35
34
|
for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
|
|
36
35
|
const poly3d = sourcepolygons[polygonindex]
|
|
37
36
|
let vertices2d = []
|
|
@@ -46,15 +45,15 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
46
45
|
// close to each other, give them the same y coordinate:
|
|
47
46
|
const ycoordinatebin = Math.floor(pos2d[1] * ycoordinateBinningFactor)
|
|
48
47
|
let newy
|
|
49
|
-
if (ycoordinatebin
|
|
50
|
-
newy = ycoordinatebins
|
|
51
|
-
} else if (ycoordinatebin + 1
|
|
52
|
-
newy = ycoordinatebins
|
|
53
|
-
} else if (ycoordinatebin - 1
|
|
54
|
-
newy = ycoordinatebins
|
|
48
|
+
if (ycoordinatebins.has(ycoordinatebin)) {
|
|
49
|
+
newy = ycoordinatebins.get(ycoordinatebin)
|
|
50
|
+
} else if (ycoordinatebins.has(ycoordinatebin + 1)) {
|
|
51
|
+
newy = ycoordinatebins.get(ycoordinatebin + 1)
|
|
52
|
+
} else if (ycoordinatebins.has(ycoordinatebin - 1)) {
|
|
53
|
+
newy = ycoordinatebins.get(ycoordinatebin - 1)
|
|
55
54
|
} else {
|
|
56
55
|
newy = pos2d[1]
|
|
57
|
-
ycoordinatebins
|
|
56
|
+
ycoordinatebins.set(ycoordinatebin, pos2d[1])
|
|
58
57
|
}
|
|
59
58
|
pos2d = vec2.fromValues(pos2d[0], newy)
|
|
60
59
|
vertices2d.push(pos2d)
|
|
@@ -66,10 +65,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
66
65
|
if ((i === 0) || (y > maxy)) {
|
|
67
66
|
maxy = y
|
|
68
67
|
}
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let polygonindexes = ycoordinatetopolygonindexes.get(y)
|
|
69
|
+
if (!polygonindexes) {
|
|
70
|
+
polygonindexes = {} // PERF
|
|
71
|
+
ycoordinatetopolygonindexes.set(y, polygonindexes)
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
polygonindexes[polygonindex] = true
|
|
73
74
|
}
|
|
74
75
|
if (miny >= maxy) {
|
|
75
76
|
// degenerate polygon, all vertices have same y coordinate. Just ignore it from now:
|
|
@@ -77,10 +78,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
77
78
|
numvertices = 0
|
|
78
79
|
minindex = -1
|
|
79
80
|
} else {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
let polygonindexes = topy2polygonindexes.get(miny)
|
|
82
|
+
if (!polygonindexes) {
|
|
83
|
+
polygonindexes = []
|
|
84
|
+
topy2polygonindexes.set(miny, polygonindexes)
|
|
82
85
|
}
|
|
83
|
-
|
|
86
|
+
polygonindexes.push(polygonindex)
|
|
84
87
|
}
|
|
85
88
|
} // if(numvertices > 0)
|
|
86
89
|
// reverse the vertex order:
|
|
@@ -89,8 +92,9 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
89
92
|
polygonvertices2d.push(vertices2d)
|
|
90
93
|
polygontopvertexindexes.push(minindex)
|
|
91
94
|
}
|
|
95
|
+
|
|
92
96
|
const ycoordinates = []
|
|
93
|
-
|
|
97
|
+
ycoordinatetopolygonindexes.forEach((polylist, y) => ycoordinates.push(y))
|
|
94
98
|
ycoordinates.sort(fnNumberSort)
|
|
95
99
|
|
|
96
100
|
// Now we will iterate over all y coordinates, from lowest to highest y coordinate
|
|
@@ -108,15 +112,14 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
108
112
|
let prevoutpolygonrow = []
|
|
109
113
|
for (let yindex = 0; yindex < ycoordinates.length; yindex++) {
|
|
110
114
|
const newoutpolygonrow = []
|
|
111
|
-
const
|
|
112
|
-
const ycoordinate = Number(ycoordinateasstring)
|
|
115
|
+
const ycoordinate = ycoordinates[yindex]
|
|
113
116
|
|
|
114
117
|
// update activepolygons for this y coordinate:
|
|
115
118
|
// - Remove any polygons that end at this y coordinate
|
|
116
119
|
// - update leftvertexindex and rightvertexindex (which point to the current vertex index
|
|
117
120
|
// at the the left and right side of the polygon
|
|
118
121
|
// Iterate over all polygons that have a corner at this y coordinate:
|
|
119
|
-
const polygonindexeswithcorner = ycoordinatetopolygonindexes
|
|
122
|
+
const polygonindexeswithcorner = ycoordinatetopolygonindexes.get(ycoordinate)
|
|
120
123
|
for (let activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) {
|
|
121
124
|
const activepolygon = activepolygons[activepolygonindex]
|
|
122
125
|
const polygonindex = activepolygon.polygonindex
|
|
@@ -166,7 +169,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
166
169
|
nextycoordinate = Number(ycoordinates[yindex + 1])
|
|
167
170
|
const middleycoordinate = 0.5 * (ycoordinate + nextycoordinate)
|
|
168
171
|
// update activepolygons by adding any polygons that start here:
|
|
169
|
-
const startingpolygonindexes = topy2polygonindexes
|
|
172
|
+
const startingpolygonindexes = topy2polygonindexes.get(ycoordinate)
|
|
170
173
|
for (const polygonindexKey in startingpolygonindexes) {
|
|
171
174
|
const polygonindex = startingpolygonindexes[polygonindexKey]
|
|
172
175
|
const vertices2d = polygonvertices2d[polygonindex]
|
|
@@ -212,8 +215,6 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
212
215
|
})
|
|
213
216
|
} // for(let polygonindex in startingpolygonindexes)
|
|
214
217
|
} // yindex < ycoordinates.length-1
|
|
215
|
-
// if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
|
|
216
|
-
// FIXME : what ???
|
|
217
218
|
|
|
218
219
|
// Now activepolygons is up to date
|
|
219
220
|
// Build the output polygons for the next row in newoutpolygonrow:
|
|
@@ -252,19 +253,19 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
252
253
|
} // for(activepolygon in activepolygons)
|
|
253
254
|
if (yindex > 0) {
|
|
254
255
|
// try to match the new polygons against the previous row:
|
|
255
|
-
const prevcontinuedindexes =
|
|
256
|
-
const matchedindexes =
|
|
256
|
+
const prevcontinuedindexes = new Set()
|
|
257
|
+
const matchedindexes = new Set()
|
|
257
258
|
for (let i = 0; i < newoutpolygonrow.length; i++) {
|
|
258
259
|
const thispolygon = newoutpolygonrow[i]
|
|
259
260
|
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
260
|
-
if (!matchedindexes
|
|
261
|
+
if (!matchedindexes.has(ii)) { // not already processed?
|
|
261
262
|
// We have a match if the sidelines are equal or if the top coordinates
|
|
262
263
|
// are on the sidelines of the previous polygon
|
|
263
264
|
const prevpolygon = prevoutpolygonrow[ii]
|
|
264
265
|
if (vec2.distance(prevpolygon.bottomleft, thispolygon.topleft) < EPS) {
|
|
265
266
|
if (vec2.distance(prevpolygon.bottomright, thispolygon.topright) < EPS) {
|
|
266
267
|
// Yes, the top of this polygon matches the bottom of the previous:
|
|
267
|
-
matchedindexes
|
|
268
|
+
matchedindexes.add(ii)
|
|
268
269
|
// Now check if the joined polygon would remain convex:
|
|
269
270
|
const v1 = line2.direction(thispolygon.leftline)
|
|
270
271
|
const v2 = line2.direction(prevpolygon.leftline)
|
|
@@ -284,16 +285,16 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
284
285
|
thispolygon.outpolygon = prevpolygon.outpolygon
|
|
285
286
|
thispolygon.leftlinecontinues = leftlinecontinues
|
|
286
287
|
thispolygon.rightlinecontinues = rightlinecontinues
|
|
287
|
-
prevcontinuedindexes
|
|
288
|
+
prevcontinuedindexes.add(ii)
|
|
288
289
|
}
|
|
289
290
|
break
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
} // if(!prevcontinuedindexes
|
|
293
|
+
} // if(!prevcontinuedindexes.has(ii))
|
|
293
294
|
} // for ii
|
|
294
295
|
} // for i
|
|
295
296
|
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
296
|
-
if (!prevcontinuedindexes
|
|
297
|
+
if (!prevcontinuedindexes.has(ii)) {
|
|
297
298
|
// polygon ends here
|
|
298
299
|
// Finish the polygon with the last point(s):
|
|
299
300
|
const prevpolygon = prevoutpolygonrow[ii]
|
|
@@ -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.
|
|
21
|
-
const polyB = poly3.
|
|
22
|
-
const polyC = poly3.
|
|
23
|
-
const polyD = poly3.
|
|
24
|
-
const polyE = poly3.
|
|
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
|
|
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
|
-
|
|
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
|
|
File without changes
|