@perplexdotgg/bounce 1.0.0 → 1.0.2
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/build/bounce.d.ts +39501 -0
- package/build/bounce.js +17166 -0
- package/package.json +1 -1
- package/src/builders/ConvexHullBuilder.ts +0 -437
- package/src/builders/ConvexHullBuilder2d.ts +0 -344
- package/src/builders/ConvexHullBuilder3d.ts +0 -1689
- package/src/builders/HeightMapBuilder.ts +0 -414
- package/src/builders/TriangleMeshBuilder.ts +0 -92
- package/src/collision/CastShapesModule.ts +0 -184
- package/src/collision/CollideShapesModule.ts +0 -152
- package/src/collision/HeightMapCaster.ts +0 -38
- package/src/collision/HeightMapCollider.ts +0 -33
- package/src/collision/TriangleCaster.ts +0 -249
- package/src/collision/TriangleCollider.ts +0 -308
- package/src/collision/TriangleCollider2.ts +0 -379
- package/src/collision/activeEdge.ts +0 -146
- package/src/collision/cast/cast.ts +0 -139
- package/src/collision/cast/castCompoundVsCompound.ts +0 -59
- package/src/collision/cast/castCompoundVsConvex.ts +0 -116
- package/src/collision/cast/castConvexVsCompound.ts +0 -123
- package/src/collision/cast/castConvexVsConvex.ts +0 -213
- package/src/collision/cast/castConvexVsHeightMap.ts +0 -73
- package/src/collision/cast/castConvexVsTriangleMesh.ts +0 -56
- package/src/collision/cast/castRayVsCompound.ts +0 -44
- package/src/collision/cast/castRayVsConvex.ts +0 -45
- package/src/collision/cast/castRayVsHeightMap.ts +0 -58
- package/src/collision/cast/castRayVsTriangleMesh.ts +0 -58
- package/src/collision/closestPoints/closestPoints.ts +0 -23
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +0 -32
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +0 -81
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +0 -30
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +0 -96
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +0 -195
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +0 -25
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +0 -72
- package/src/collision/collide/collide.ts +0 -146
- package/src/collision/collide/collideCompoundVsCompound.ts +0 -60
- package/src/collision/collide/collideCompoundVsConvex.ts +0 -59
- package/src/collision/collide/collideCompoundVsHeightMap.ts +0 -73
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +0 -56
- package/src/collision/collide/collideConvexVsCompound.ts +0 -57
- package/src/collision/collide/collideConvexVsConvex.ts +0 -225
- package/src/collision/collide/collideConvexVsConvexImp.ts +0 -236
- package/src/collision/collide/collideConvexVsHeightMap.ts +0 -53
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +0 -58
- package/src/collision/collide/collideHeightMapVsCompound.ts +0 -69
- package/src/collision/collide/collideHeightMapVsConvex.ts +0 -53
- package/src/collision/collide/collideSphereVsSphere.ts +0 -81
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +0 -58
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +0 -58
- package/src/collision/epa/EpaConvexHullBuilder.ts +0 -397
- package/src/collision/epa/StaticArray.ts +0 -154
- package/src/collision/epa/TriangleFactory.ts +0 -32
- package/src/collision/epa/arrays.ts +0 -99
- package/src/collision/epa/binaryHeap.ts +0 -82
- package/src/collision/epa/structs.ts +0 -227
- package/src/collision/gjk/GjkModule.ts +0 -864
- package/src/collision/gjk/PenetrationDepthModule.ts +0 -493
- package/src/collision/gjk/SupportPoints.ts +0 -50
- package/src/collision/imp/MinkowskiDifference.ts +0 -36
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +0 -40
- package/src/collision/imp/finalizeImpResult.ts +0 -69
- package/src/collision/imp/findContactImp.ts +0 -196
- package/src/collision/imp/imp.ts +0 -28
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +0 -207
- package/src/collision/mpr/findPortal.ts +0 -152
- package/src/collision/mpr/mpr.ts +0 -29
- package/src/collision/mpr/updatePortal.ts +0 -52
- package/src/constraints/BaseConstraint.ts +0 -50
- package/src/constraints/ConstraintOptions.ts +0 -22
- package/src/constraints/ConstraintSolver.ts +0 -119
- package/src/constraints/DistanceConstraint.ts +0 -229
- package/src/constraints/FixedConstraint.ts +0 -203
- package/src/constraints/HingeConstraint.ts +0 -460
- package/src/constraints/PointConstraint.ts +0 -108
- package/src/constraints/components/AngleComponent.ts +0 -226
- package/src/constraints/components/AxisComponent.ts +0 -263
- package/src/constraints/components/HingeComponent.ts +0 -215
- package/src/constraints/components/Motor.ts +0 -36
- package/src/constraints/components/PointConstraintComponent.ts +0 -179
- package/src/constraints/components/RotationEulerComponent.ts +0 -139
- package/src/constraints/components/Spring.ts +0 -30
- package/src/constraints/components/SpringComponent.ts +0 -71
- package/src/constraints/types.ts +0 -6
- package/src/helpers.ts +0 -147
- package/src/index.ts +0 -50
- package/src/math/BasicTransform.ts +0 -19
- package/src/math/NumberValue.ts +0 -13
- package/src/math/isometry.ts +0 -64
- package/src/math/mat3.ts +0 -529
- package/src/math/mat4.ts +0 -588
- package/src/math/quat.ts +0 -193
- package/src/math/scalar.ts +0 -81
- package/src/math/tensor.ts +0 -17
- package/src/math/vec3.ts +0 -589
- package/src/math/vec4.ts +0 -10
- package/src/physics/Body.ts +0 -581
- package/src/physics/CollisionFilter.ts +0 -52
- package/src/physics/SleepModule.ts +0 -163
- package/src/physics/broadphase/BodyPairsModule.ts +0 -363
- package/src/physics/broadphase/BvhModule.ts +0 -237
- package/src/physics/broadphase/BvhTree.ts +0 -803
- package/src/physics/broadphase/ConstraintPairsModule.ts +0 -385
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +0 -379
- package/src/physics/manifold/ContactManifold.ts +0 -227
- package/src/physics/manifold/ContactManifoldModule.ts +0 -623
- package/src/physics/manifold/Face.ts +0 -119
- package/src/physics/manifold/ManifoldCache.ts +0 -116
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +0 -131
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +0 -73
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +0 -72
- package/src/physics/narrowphase/CollideBodiesModule.ts +0 -755
- package/src/physics/solver/ContactConstraintModule.ts +0 -659
- package/src/physics/solver/ManifoldConstraint.ts +0 -420
- package/src/physics/solver/estimateCollisionResponse.ts +0 -146
- package/src/shape/Aabb.ts +0 -400
- package/src/shape/Box.ts +0 -231
- package/src/shape/Capsule.ts +0 -332
- package/src/shape/CompoundShape.ts +0 -288
- package/src/shape/Convex.ts +0 -130
- package/src/shape/ConvexHull.ts +0 -423
- package/src/shape/Cylinder.ts +0 -313
- package/src/shape/HeightMap.ts +0 -511
- package/src/shape/Line.ts +0 -14
- package/src/shape/Plane.ts +0 -116
- package/src/shape/Ray.ts +0 -81
- package/src/shape/Segment.ts +0 -25
- package/src/shape/Shape.ts +0 -77
- package/src/shape/Sphere.ts +0 -181
- package/src/shape/TransformedShape.ts +0 -51
- package/src/shape/Triangle.ts +0 -122
- package/src/shape/TriangleMesh.ts +0 -186
- package/src/types.ts +0 -1
- package/src/world.ts +0 -1335
- package/tests/BodyPairsModule.test.ts +0 -71
- package/tests/BvhTree.test.ts +0 -406
- package/tests/test.md +0 -642
- package/tests/vec3.test.ts +0 -12
|
@@ -1,1689 +0,0 @@
|
|
|
1
|
-
import { PoolClass } from "monomorph";
|
|
2
|
-
import { ClosestPointResult } from "../collision/closestPoints/closestPoints";
|
|
3
|
-
import { Vec3 } from "../math/vec3";
|
|
4
|
-
import { degreesToRadians, squared } from "../math/scalar";
|
|
5
|
-
import { ConvexHullBuilder2d, ConvexHullBuilder2dResult } from "./ConvexHullBuilder2d";
|
|
6
|
-
import { computeClosestPointOnLine } from "../collision/closestPoints/computeClosestPointOnLine";
|
|
7
|
-
|
|
8
|
-
// TODO: this may need to change, not sure if value is too large
|
|
9
|
-
const floatEpsilon = 1e-5;
|
|
10
|
-
|
|
11
|
-
const minTriangleAreaSquared = 1e-12;
|
|
12
|
-
|
|
13
|
-
type Tuple3 = [number, number, number];
|
|
14
|
-
|
|
15
|
-
const calculateNormalAndCentroidy0 = /*@__PURE__*/ Vec3.create();
|
|
16
|
-
const calculateNormalAndCentroidy1 = /*@__PURE__*/ Vec3.create();
|
|
17
|
-
const calculateNormalAndCentroidy2 = /*@__PURE__*/ Vec3.create();
|
|
18
|
-
const calculateNormalAndCentroide0 = /*@__PURE__*/ Vec3.create();
|
|
19
|
-
const calculateNormalAndCentroide1 = /*@__PURE__*/ Vec3.create();
|
|
20
|
-
const calculateNormalAndCentroide2 = /*@__PURE__*/ Vec3.create();
|
|
21
|
-
const calculateNormalAndCentroidshortestEdgeNormal = /*@__PURE__*/ Vec3.create();
|
|
22
|
-
const calculateNormalAndCentroidcentroid = /*@__PURE__*/ Vec3.create();
|
|
23
|
-
const calculateNormalAndCentroidnormal = /*@__PURE__*/ Vec3.create();
|
|
24
|
-
|
|
25
|
-
const isFacingcentroid = /*@__PURE__*/ Vec3.create();
|
|
26
|
-
const isFacingnormal = /*@__PURE__*/ Vec3.create();
|
|
27
|
-
const isFacingvectorFromCentroid = /*@__PURE__*/ Vec3.create();
|
|
28
|
-
|
|
29
|
-
const getFaceForPointcentroid = /*@__PURE__*/ Vec3.create();
|
|
30
|
-
const getFaceForPointnormal = /*@__PURE__*/ Vec3.create();
|
|
31
|
-
const getFaceForPointtempVector = /*@__PURE__*/ Vec3.create();
|
|
32
|
-
|
|
33
|
-
const getDistanceToEdgeSquaredp1 = /*@__PURE__*/ Vec3.create();
|
|
34
|
-
const getDistanceToEdgeSquaredp2 = /*@__PURE__*/ Vec3.create();
|
|
35
|
-
const getDistanceToEdgeSquaredvector12 = /*@__PURE__*/ Vec3.create();
|
|
36
|
-
const getDistanceToEdgeSquaredtempVector = /*@__PURE__*/ Vec3.create();
|
|
37
|
-
const getDistanceToEdgeSquarednormal = /*@__PURE__*/ Vec3.create();
|
|
38
|
-
const getDistanceToEdgeSquaredp1ToPoint = /*@__PURE__*/ Vec3.create();
|
|
39
|
-
const getDistanceToEdgeSquaredclosest = /*@__PURE__*/ ClosestPointResult.create();
|
|
40
|
-
const getDistanceToEdgeSquaredpointToP1 = /*@__PURE__*/ Vec3.create();
|
|
41
|
-
const getDistanceToEdgeSquaredpointToP2 = /*@__PURE__*/ Vec3.create();
|
|
42
|
-
|
|
43
|
-
const assignPointToFacepoint = /*@__PURE__*/ Vec3.create();
|
|
44
|
-
|
|
45
|
-
const determineCoplanarDistancecurrentVertex = /*@__PURE__*/ Vec3.create();
|
|
46
|
-
const determineCoplanarDistancemaxComponents = /*@__PURE__*/ Vec3.create();
|
|
47
|
-
|
|
48
|
-
const buildConvexHullvertexA = /*@__PURE__*/ Vec3.create();
|
|
49
|
-
const buildConvexHullvertexB = /*@__PURE__*/ Vec3.create();
|
|
50
|
-
const buildConvexHullvertexC = /*@__PURE__*/ Vec3.create();
|
|
51
|
-
const buildConvexHullvertexD = /*@__PURE__*/ Vec3.create();
|
|
52
|
-
const buildConvexHullvectorCA = /*@__PURE__*/ Vec3.create();
|
|
53
|
-
const buildConvexHullvectorCB = /*@__PURE__*/ Vec3.create();
|
|
54
|
-
const buildConvexHullvectorAB = /*@__PURE__*/ Vec3.create();
|
|
55
|
-
const buildConvexHullvectorAC = /*@__PURE__*/ Vec3.create();
|
|
56
|
-
const buildConvexHullcrossCA_CB = /*@__PURE__*/ Vec3.create();
|
|
57
|
-
const buildConvexHullinitialPlaneNormal = /*@__PURE__*/ Vec3.create();
|
|
58
|
-
const buildConvexHullinitialPlaneCentroid = /*@__PURE__*/ Vec3.create();
|
|
59
|
-
const buildConvexHulltempVector = /*@__PURE__*/ Vec3.create();
|
|
60
|
-
const buildConvexHullbase1 = /*@__PURE__*/ Vec3.create();
|
|
61
|
-
const buildConvexHullbase2 = /*@__PURE__*/ Vec3.create();
|
|
62
|
-
|
|
63
|
-
const addPointposition = /*@__PURE__*/ Vec3.create();
|
|
64
|
-
|
|
65
|
-
const mergeDegenerateFacenormal = /*@__PURE__*/ Vec3.create();
|
|
66
|
-
const mergeDegenerateFacep1 = /*@__PURE__*/ Vec3.create();
|
|
67
|
-
const mergeDegenerateFacep2 = /*@__PURE__*/ Vec3.create();
|
|
68
|
-
|
|
69
|
-
const mergeCoplanarOrConcaveFacesdeltaCentroid = /*@__PURE__*/ Vec3.create();
|
|
70
|
-
const mergeCoplanarOrConcaveFacesinFaceNormal = /*@__PURE__*/ Vec3.create();
|
|
71
|
-
const mergeCoplanarOrConcaveFacesinFaceCentroid = /*@__PURE__*/ Vec3.create();
|
|
72
|
-
const mergeCoplanarOrConcaveFacesotherFaceNormal = /*@__PURE__*/ Vec3.create();
|
|
73
|
-
const mergeCoplanarOrConcaveFacesotherFaceCentroid = /*@__PURE__*/ Vec3.create();
|
|
74
|
-
|
|
75
|
-
const getCenterOfMassAndVolumevertexA = /*@__PURE__*/ Vec3.create();
|
|
76
|
-
const getCenterOfMassAndVolumevertexB = /*@__PURE__*/ Vec3.create();
|
|
77
|
-
const getCenterOfMassAndVolumevertexC = /*@__PURE__*/ Vec3.create();
|
|
78
|
-
const getCenterOfMassAndVolumevertexD = /*@__PURE__*/ Vec3.create();
|
|
79
|
-
const getCenterOfMassAndVolumevectorDA = /*@__PURE__*/ Vec3.create();
|
|
80
|
-
const getCenterOfMassAndVolumevectorDB = /*@__PURE__*/ Vec3.create();
|
|
81
|
-
const getCenterOfMassAndVolumevectorDC = /*@__PURE__*/ Vec3.create();
|
|
82
|
-
const getCenterOfMassAndVolumecrossDB_DC = /*@__PURE__*/ Vec3.create();
|
|
83
|
-
const getCenterOfMassAndVolumecentroid = /*@__PURE__*/ Vec3.create();
|
|
84
|
-
const getCenterOfMassAndVolumecenterOfMassTetrahedron = /*@__PURE__*/ Vec3.create();
|
|
85
|
-
|
|
86
|
-
const determineMaxError = /*@__PURE__*/ Vec3.create();
|
|
87
|
-
const determineMaxErrornormal = /*@__PURE__*/ Vec3.create();
|
|
88
|
-
const determineMaxErrorcentroid = /*@__PURE__*/ Vec3.create();
|
|
89
|
-
const determineMaxErrortempVector = /*@__PURE__*/ Vec3.create();
|
|
90
|
-
|
|
91
|
-
const faceNormal = /*@__PURE__*/ Vec3.create();
|
|
92
|
-
const neighborNormal = /*@__PURE__*/ Vec3.create();
|
|
93
|
-
|
|
94
|
-
export const enum ConvexHullBuilderResult {
|
|
95
|
-
Success, ///< Hull building finished successfully
|
|
96
|
-
MaxVerticesReached, ///< Hull building finished successfully, but the desired accuracy was not reached because the max vertices limit was reached
|
|
97
|
-
TooFewPoints, ///< Too few points to create a hull
|
|
98
|
-
TooFewFaces, ///< Too few faces in the created hull (signifies precision errors during building)
|
|
99
|
-
Degenerate, ///< Degenerate hull detected
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
class ConvexHullBuilderCoplanar {
|
|
103
|
-
positionIndex: number;
|
|
104
|
-
distanceSquared: number;
|
|
105
|
-
|
|
106
|
-
constructor(positionIndex: number, distanceSquared: number) {
|
|
107
|
-
this.positionIndex = positionIndex;
|
|
108
|
-
this.distanceSquared = distanceSquared;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
class ConvexHullBuilderEdge {
|
|
113
|
-
face: ConvexHullBuilderFace;
|
|
114
|
-
nextEdge?: ConvexHullBuilderEdge;
|
|
115
|
-
neighbourEdge?: ConvexHullBuilderEdge;
|
|
116
|
-
startIndex: number;
|
|
117
|
-
// isFirst: boolean = false;
|
|
118
|
-
|
|
119
|
-
constructor(face: ConvexHullBuilderFace, startIndex: number) {
|
|
120
|
-
this.face = face;
|
|
121
|
-
this.startIndex = startIndex;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
getPreviousEdge() {
|
|
125
|
-
let prevEdge: ConvexHullBuilderEdge | undefined = this;
|
|
126
|
-
while (prevEdge?.nextEdge && prevEdge.nextEdge !== this) {
|
|
127
|
-
prevEdge = prevEdge.nextEdge;
|
|
128
|
-
}
|
|
129
|
-
return prevEdge;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
class ConvexHullBuilderFullEdge {
|
|
134
|
-
startIndex: number;
|
|
135
|
-
endIndex: number;
|
|
136
|
-
neighbourEdge: ConvexHullBuilderEdge;
|
|
137
|
-
|
|
138
|
-
constructor(startIndex: number, endIndex: number, neighbourEdge: ConvexHullBuilderEdge) {
|
|
139
|
-
this.startIndex = startIndex;
|
|
140
|
-
this.endIndex = endIndex;
|
|
141
|
-
this.neighbourEdge = neighbourEdge;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export class ConvexHullBuilderFace {
|
|
146
|
-
normal: Tuple3;
|
|
147
|
-
centroid: Tuple3;
|
|
148
|
-
conflictList: number[];
|
|
149
|
-
firstEdge?: ConvexHullBuilderEdge;
|
|
150
|
-
furthestPointDistanceSquared: number = 0;
|
|
151
|
-
removed: boolean = false;
|
|
152
|
-
|
|
153
|
-
indices: Tuple3;
|
|
154
|
-
|
|
155
|
-
constructor() {
|
|
156
|
-
this.normal = [0, 0, 0];
|
|
157
|
-
this.centroid = [0, 0, 0];
|
|
158
|
-
this.conflictList = [];
|
|
159
|
-
this.indices = [0, 0, 0];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
copy(face: ConvexHullBuilderFace) {
|
|
163
|
-
this.normal[0] = face.normal[0];
|
|
164
|
-
this.normal[1] = face.normal[1];
|
|
165
|
-
this.normal[2] = face.normal[2];
|
|
166
|
-
this.centroid[0] = face.centroid[0];
|
|
167
|
-
this.centroid[1] = face.centroid[1];
|
|
168
|
-
this.centroid[2] = face.centroid[2];
|
|
169
|
-
this.conflictList = face.conflictList.slice();
|
|
170
|
-
this.firstEdge = face.firstEdge;
|
|
171
|
-
this.furthestPointDistanceSquared = face.furthestPointDistanceSquared;
|
|
172
|
-
this.removed = face.removed;
|
|
173
|
-
this.indices[0] = face.indices[0];
|
|
174
|
-
this.indices[1] = face.indices[1];
|
|
175
|
-
this.indices[2] = face.indices[2];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
initialize(index0: number, index1: number, index2: number, polyhedron: PoolClass<Vec3>) {
|
|
179
|
-
if (index0 === index1 || index0 === index2 || index1 === index2) {
|
|
180
|
-
throw new Error("Indices must be unique");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Create 3 edges
|
|
184
|
-
const edge0 = new ConvexHullBuilderEdge(this, index0);
|
|
185
|
-
const edge1 = new ConvexHullBuilderEdge(this, index1);
|
|
186
|
-
const edge2 = new ConvexHullBuilderEdge(this, index2);
|
|
187
|
-
|
|
188
|
-
// Link edges
|
|
189
|
-
edge0.nextEdge = edge1;
|
|
190
|
-
edge1.nextEdge = edge2;
|
|
191
|
-
edge2.nextEdge = edge0;
|
|
192
|
-
this.firstEdge = edge0;
|
|
193
|
-
|
|
194
|
-
this.calculateNormalAndCentroid(polyhedron);
|
|
195
|
-
|
|
196
|
-
this.indices[0] = index0;
|
|
197
|
-
this.indices[1] = index1;
|
|
198
|
-
this.indices[2] = index2;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
calculateNormalAndCentroid(polyhedron: PoolClass<Vec3>) {
|
|
202
|
-
// Get point that we use to construct a triangle fan
|
|
203
|
-
let edge = this.firstEdge!;
|
|
204
|
-
calculateNormalAndCentroidy0.copy(polyhedron.array[edge.startIndex]);
|
|
205
|
-
|
|
206
|
-
// Get the 2nd point
|
|
207
|
-
edge = edge.nextEdge!;
|
|
208
|
-
calculateNormalAndCentroidy1.copy(polyhedron.array[edge.startIndex]);
|
|
209
|
-
|
|
210
|
-
// Start accumulating the centroid
|
|
211
|
-
// centroid.zero();
|
|
212
|
-
calculateNormalAndCentroidcentroid.addVectors(calculateNormalAndCentroidy0, calculateNormalAndCentroidy1);
|
|
213
|
-
let n = 2;
|
|
214
|
-
|
|
215
|
-
// Start accumulating the normal
|
|
216
|
-
calculateNormalAndCentroidnormal.zero();
|
|
217
|
-
|
|
218
|
-
// Loop over remaining edges accumulating normals in a triangle fan fashion
|
|
219
|
-
for (edge = edge.nextEdge!; edge !== this.firstEdge; edge = edge.nextEdge!) {
|
|
220
|
-
// Get the 3rd point
|
|
221
|
-
calculateNormalAndCentroidy2.copy(polyhedron.array[edge.startIndex]);
|
|
222
|
-
|
|
223
|
-
// Calculate edges (counter clockwise)
|
|
224
|
-
calculateNormalAndCentroide0.subtractVectors(calculateNormalAndCentroidy1, calculateNormalAndCentroidy0);
|
|
225
|
-
calculateNormalAndCentroide1.subtractVectors(calculateNormalAndCentroidy2, calculateNormalAndCentroidy1);
|
|
226
|
-
calculateNormalAndCentroide2.subtractVectors(calculateNormalAndCentroidy0, calculateNormalAndCentroidy2);
|
|
227
|
-
|
|
228
|
-
// The best normal is calculated by using the two shortest edges
|
|
229
|
-
// See: https://box2d.org/posts/2014/01/troublesome-triangle/
|
|
230
|
-
// The difference in normals is most pronounced when one edge is much smaller than the others (in which case the others must have roughly the same length).
|
|
231
|
-
// Therefore we can suffice by just picking the shortest from 2 edges and use that with the 3rd edge to calculate the normal.
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
calculateNormalAndCentroide1.dot(calculateNormalAndCentroide1) <
|
|
235
|
-
calculateNormalAndCentroide2.dot(calculateNormalAndCentroide2)
|
|
236
|
-
) {
|
|
237
|
-
calculateNormalAndCentroidshortestEdgeNormal.crossVectors(
|
|
238
|
-
calculateNormalAndCentroide0,
|
|
239
|
-
calculateNormalAndCentroide1
|
|
240
|
-
);
|
|
241
|
-
} else {
|
|
242
|
-
calculateNormalAndCentroidshortestEdgeNormal.crossVectors(
|
|
243
|
-
calculateNormalAndCentroide2,
|
|
244
|
-
calculateNormalAndCentroide0
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Accumulate normal
|
|
249
|
-
calculateNormalAndCentroidnormal.addVectors(
|
|
250
|
-
calculateNormalAndCentroidnormal,
|
|
251
|
-
calculateNormalAndCentroidshortestEdgeNormal
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Accumulate centroid
|
|
255
|
-
calculateNormalAndCentroidcentroid.addVectors(calculateNormalAndCentroidcentroid, calculateNormalAndCentroidy2);
|
|
256
|
-
n++;
|
|
257
|
-
|
|
258
|
-
// Update y1 for next triangle
|
|
259
|
-
calculateNormalAndCentroidy1.copy(calculateNormalAndCentroidy2);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Finalize normal, TODO: this is not done in the reference implementation
|
|
263
|
-
calculateNormalAndCentroidnormal.normalizeVector(calculateNormalAndCentroidnormal);
|
|
264
|
-
|
|
265
|
-
// Finalize centroid
|
|
266
|
-
calculateNormalAndCentroidcentroid.scaleVector(calculateNormalAndCentroidcentroid, 1 / n);
|
|
267
|
-
|
|
268
|
-
// update centroid and normal data
|
|
269
|
-
this.centroid[0] = calculateNormalAndCentroidcentroid.x;
|
|
270
|
-
this.centroid[1] = calculateNormalAndCentroidcentroid.y;
|
|
271
|
-
this.centroid[2] = calculateNormalAndCentroidcentroid.z;
|
|
272
|
-
this.normal[0] = calculateNormalAndCentroidnormal.x;
|
|
273
|
-
this.normal[1] = calculateNormalAndCentroidnormal.y;
|
|
274
|
-
this.normal[2] = calculateNormalAndCentroidnormal.z;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
isFacing(point: Vec3) {
|
|
278
|
-
isFacingcentroid.x = this.centroid[0];
|
|
279
|
-
isFacingcentroid.y = this.centroid[1];
|
|
280
|
-
isFacingcentroid.z = this.centroid[2];
|
|
281
|
-
isFacingnormal.x = this.normal[0];
|
|
282
|
-
isFacingnormal.y = this.normal[1];
|
|
283
|
-
isFacingnormal.z = this.normal[2];
|
|
284
|
-
|
|
285
|
-
isFacingvectorFromCentroid.subtractVectors(point, isFacingcentroid);
|
|
286
|
-
return isFacingnormal.dot(isFacingvectorFromCentroid) > 0;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export class ConvexHullBuilder3d {
|
|
291
|
-
polyhedron?: PoolClass<Vec3>;
|
|
292
|
-
coplanarList: ConvexHullBuilderCoplanar[];
|
|
293
|
-
faces: ConvexHullBuilderFace[];
|
|
294
|
-
|
|
295
|
-
constructor() {
|
|
296
|
-
this.coplanarList = [];
|
|
297
|
-
this.faces = [];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
freeFaces() {
|
|
301
|
-
this.faces.length = 0;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
getFaceForPoint(point: Vec3, faces: ConvexHullBuilderFace[]): [ConvexHullBuilderFace | undefined, number] {
|
|
305
|
-
let outFace = undefined;
|
|
306
|
-
let outDistanceSquared = 0.0;
|
|
307
|
-
|
|
308
|
-
for (const face of faces) {
|
|
309
|
-
if (!face.removed) {
|
|
310
|
-
// Determine distance to face
|
|
311
|
-
getFaceForPointcentroid.x = face.centroid[0];
|
|
312
|
-
getFaceForPointcentroid.y = face.centroid[1];
|
|
313
|
-
getFaceForPointcentroid.z = face.centroid[2];
|
|
314
|
-
getFaceForPointnormal.x = face.normal[0];
|
|
315
|
-
getFaceForPointnormal.y = face.normal[1];
|
|
316
|
-
getFaceForPointnormal.z = face.normal[2];
|
|
317
|
-
getFaceForPointtempVector.subtractVectors(point, getFaceForPointcentroid);
|
|
318
|
-
const dot = getFaceForPointnormal.dot(getFaceForPointtempVector);
|
|
319
|
-
if (dot > 0.0) {
|
|
320
|
-
const distanceSquared = (dot * dot) / getFaceForPointnormal.squaredLength();
|
|
321
|
-
if (distanceSquared > outDistanceSquared) {
|
|
322
|
-
outFace = face;
|
|
323
|
-
outDistanceSquared = distanceSquared;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return [outFace, outDistanceSquared];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
getDistanceToEdgeSquared(point: Vec3, face: ConvexHullBuilderFace): number {
|
|
333
|
-
let allInside = true;
|
|
334
|
-
let edgeDistanceSquared = Infinity;
|
|
335
|
-
|
|
336
|
-
// Test if it is inside the edges of the polygon
|
|
337
|
-
let edge = face.firstEdge!;
|
|
338
|
-
|
|
339
|
-
getDistanceToEdgeSquaredp1.copy(this.polyhedron!.array[edge.getPreviousEdge().startIndex]);
|
|
340
|
-
|
|
341
|
-
do {
|
|
342
|
-
getDistanceToEdgeSquaredp2.copy(this.polyhedron!.array[edge.startIndex]);
|
|
343
|
-
|
|
344
|
-
getDistanceToEdgeSquarednormal.x = face.normal[0];
|
|
345
|
-
getDistanceToEdgeSquarednormal.y = face.normal[1];
|
|
346
|
-
getDistanceToEdgeSquarednormal.z = face.normal[2];
|
|
347
|
-
|
|
348
|
-
getDistanceToEdgeSquaredvector12.subtractVectors(getDistanceToEdgeSquaredp2, getDistanceToEdgeSquaredp1);
|
|
349
|
-
getDistanceToEdgeSquaredp1ToPoint.subtractVectors(point, getDistanceToEdgeSquaredp1);
|
|
350
|
-
getDistanceToEdgeSquaredtempVector.crossVectors(
|
|
351
|
-
getDistanceToEdgeSquaredvector12,
|
|
352
|
-
getDistanceToEdgeSquaredp1ToPoint
|
|
353
|
-
);
|
|
354
|
-
if (getDistanceToEdgeSquaredtempVector.dot(getDistanceToEdgeSquarednormal) < 0.0) {
|
|
355
|
-
// It is outside
|
|
356
|
-
allInside = false;
|
|
357
|
-
|
|
358
|
-
// Measure distance to this edge
|
|
359
|
-
getDistanceToEdgeSquaredpointToP1.subtractVectors(getDistanceToEdgeSquaredp1, point);
|
|
360
|
-
getDistanceToEdgeSquaredpointToP2.subtractVectors(getDistanceToEdgeSquaredp2, point);
|
|
361
|
-
computeClosestPointOnLine(
|
|
362
|
-
getDistanceToEdgeSquaredclosest,
|
|
363
|
-
getDistanceToEdgeSquaredpointToP1,
|
|
364
|
-
getDistanceToEdgeSquaredpointToP2
|
|
365
|
-
);
|
|
366
|
-
edgeDistanceSquared = Math.min(edgeDistanceSquared, getDistanceToEdgeSquaredclosest.point.squaredLength());
|
|
367
|
-
}
|
|
368
|
-
getDistanceToEdgeSquaredp1.copy(getDistanceToEdgeSquaredp2);
|
|
369
|
-
edge = edge.nextEdge!;
|
|
370
|
-
} while (edge !== face.firstEdge);
|
|
371
|
-
|
|
372
|
-
return allInside ? 0.0 : edgeDistanceSquared;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
assignPointToFace(
|
|
376
|
-
polyhedron: PoolClass<Vec3>,
|
|
377
|
-
index: number,
|
|
378
|
-
faces: ConvexHullBuilderFace[],
|
|
379
|
-
toleranceSquared: number
|
|
380
|
-
) {
|
|
381
|
-
assignPointToFacepoint.copy(polyhedron.array[index]);
|
|
382
|
-
|
|
383
|
-
// Find the face for which the point is furthest away
|
|
384
|
-
const [bestFace, bestDistanceSquared] = this.getFaceForPoint(assignPointToFacepoint, faces);
|
|
385
|
-
|
|
386
|
-
if (bestFace) {
|
|
387
|
-
// Check if this point is within the tolerance margin to the plane
|
|
388
|
-
if (bestDistanceSquared <= toleranceSquared) {
|
|
389
|
-
// Check distance to edges
|
|
390
|
-
const distanceToEdgeSquared = this.getDistanceToEdgeSquared(assignPointToFacepoint, bestFace);
|
|
391
|
-
if (distanceToEdgeSquared > toleranceSquared) {
|
|
392
|
-
// Point is outside of the face and too far away to discard
|
|
393
|
-
this.coplanarList.push(new ConvexHullBuilderCoplanar(index, distanceToEdgeSquared));
|
|
394
|
-
}
|
|
395
|
-
} else {
|
|
396
|
-
// This point is in front of the face, add it to the conflict list
|
|
397
|
-
if (bestDistanceSquared > bestFace.furthestPointDistanceSquared) {
|
|
398
|
-
// This point is futher away than any others, update the distance and add point as last point
|
|
399
|
-
bestFace.furthestPointDistanceSquared = bestDistanceSquared;
|
|
400
|
-
bestFace.conflictList.push(index);
|
|
401
|
-
} else {
|
|
402
|
-
// Not the furthest point, add it as the before last point
|
|
403
|
-
bestFace.conflictList.push(bestFace.conflictList[bestFace.conflictList.length - 1]);
|
|
404
|
-
bestFace.conflictList[bestFace.conflictList.length - 2] = index;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return true;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
determineCoplanarDistance() {
|
|
415
|
-
// Formula as per: Implementing Quickhull - Dirk Gregorius.
|
|
416
|
-
|
|
417
|
-
determineCoplanarDistancemaxComponents.zero();
|
|
418
|
-
|
|
419
|
-
for (let i = 0; i < this.polyhedron!.length; ++i) {
|
|
420
|
-
determineCoplanarDistancecurrentVertex.copy(this.polyhedron!.array[i]);
|
|
421
|
-
determineCoplanarDistancemaxComponents.x = Math.max(
|
|
422
|
-
determineCoplanarDistancemaxComponents.x,
|
|
423
|
-
Math.abs(determineCoplanarDistancecurrentVertex.x)
|
|
424
|
-
);
|
|
425
|
-
determineCoplanarDistancemaxComponents.y = Math.max(
|
|
426
|
-
determineCoplanarDistancemaxComponents.y,
|
|
427
|
-
Math.abs(determineCoplanarDistancecurrentVertex.y)
|
|
428
|
-
);
|
|
429
|
-
determineCoplanarDistancemaxComponents.z = Math.max(
|
|
430
|
-
determineCoplanarDistancemaxComponents.z,
|
|
431
|
-
Math.abs(determineCoplanarDistancecurrentVertex.z)
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return (
|
|
436
|
-
3.0 *
|
|
437
|
-
floatEpsilon *
|
|
438
|
-
(determineCoplanarDistancemaxComponents.x +
|
|
439
|
-
determineCoplanarDistancemaxComponents.y +
|
|
440
|
-
determineCoplanarDistancemaxComponents.z)
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
getNumberOfVerticesUsed() {
|
|
445
|
-
const usedVertices = new Set<number>();
|
|
446
|
-
for (const face of this.faces) {
|
|
447
|
-
let edge = face.firstEdge!;
|
|
448
|
-
do {
|
|
449
|
-
usedVertices.add(edge.startIndex);
|
|
450
|
-
edge = edge.nextEdge!;
|
|
451
|
-
} while (edge !== face.firstEdge);
|
|
452
|
-
}
|
|
453
|
-
return usedVertices.size;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// TODO this is unused/untested, i may have broken it - Hamza
|
|
457
|
-
containsFace(inIndices: number[]): boolean {
|
|
458
|
-
for (const face of this.faces) {
|
|
459
|
-
let edge = face.firstEdge;
|
|
460
|
-
let index = inIndices.indexOf(edge!.startIndex);
|
|
461
|
-
// let startIndex = inIndices.indexOf(edge!.startIndex);
|
|
462
|
-
if (index !== inIndices[inIndices.length - 1]) {
|
|
463
|
-
let matches = 0;
|
|
464
|
-
// let currentIndex = startIndex;
|
|
465
|
-
|
|
466
|
-
do {
|
|
467
|
-
// Check if index matches
|
|
468
|
-
if (index !== edge!.startIndex) {
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Increment number of matches
|
|
473
|
-
matches++;
|
|
474
|
-
|
|
475
|
-
index++;
|
|
476
|
-
|
|
477
|
-
if (index === inIndices[inIndices.length - 1]) {
|
|
478
|
-
index = inIndices[0];
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Next index in list of inIndices
|
|
482
|
-
// currentIndex = (currentIndex + 1) % inIndices.length;
|
|
483
|
-
|
|
484
|
-
// Next edge
|
|
485
|
-
edge = edge!.nextEdge;
|
|
486
|
-
} while (edge !== face.firstEdge);
|
|
487
|
-
|
|
488
|
-
if (matches === inIndices.length) {
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
buildConvexHull(
|
|
498
|
-
polyhedron: PoolClass<Vec3>,
|
|
499
|
-
positions2d: PoolClass<Vec3>,
|
|
500
|
-
tolerance: number,
|
|
501
|
-
maxVertexCount: number,
|
|
502
|
-
maxAngleBetweenAdjacentFacesDegrees: number = 1
|
|
503
|
-
): ConvexHullBuilderResult {
|
|
504
|
-
this.polyhedron = polyhedron;
|
|
505
|
-
|
|
506
|
-
// TODO: cleanup if needed
|
|
507
|
-
// Free the faces possibly left over from an earlier hull
|
|
508
|
-
this.freeFaces();
|
|
509
|
-
|
|
510
|
-
// Test that we have at least 3 points
|
|
511
|
-
if (polyhedron.length < 3) {
|
|
512
|
-
throw new Error("Need at least 3 points to make a hull");
|
|
513
|
-
return ConvexHullBuilderResult.TooFewPoints;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Determine a suitable tolerance for detecting that points are coplanar
|
|
517
|
-
const coplanarToleranceSquared = squared(this.determineCoplanarDistance());
|
|
518
|
-
|
|
519
|
-
// Increase desired tolerance if accuracy doesn't allow it
|
|
520
|
-
const toleranceSquared = Math.max(coplanarToleranceSquared, squared(tolerance));
|
|
521
|
-
|
|
522
|
-
// Find point furthest from the origin
|
|
523
|
-
let index1 = -1;
|
|
524
|
-
let maxDistanceSquared = -1.0;
|
|
525
|
-
for (let i = 0; i < polyhedron.length; ++i) {
|
|
526
|
-
buildConvexHullvertexA.copy(polyhedron.array[i]);
|
|
527
|
-
let distanceSquared = buildConvexHullvertexA.squaredLength();
|
|
528
|
-
if (distanceSquared > maxDistanceSquared) {
|
|
529
|
-
maxDistanceSquared = distanceSquared;
|
|
530
|
-
index1 = i;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (index1 < 0) {
|
|
535
|
-
throw new Error("No vertex found");
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
buildConvexHullvertexA.copy(polyhedron.array[index1]);
|
|
539
|
-
|
|
540
|
-
// Find point that is furthest away from this point
|
|
541
|
-
let index2 = -1;
|
|
542
|
-
maxDistanceSquared = -1.0;
|
|
543
|
-
for (let i = 0; i < polyhedron.length; ++i) {
|
|
544
|
-
if (i === index1) {
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
buildConvexHullvertexB.copy(polyhedron.array[i]);
|
|
549
|
-
let distanceSquared = buildConvexHullvertexA.squaredDistance(buildConvexHullvertexB);
|
|
550
|
-
if (distanceSquared > maxDistanceSquared) {
|
|
551
|
-
maxDistanceSquared = distanceSquared;
|
|
552
|
-
index2 = i;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (index2 < 0) {
|
|
557
|
-
throw new Error("No vertex found");
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
buildConvexHullvertexB.copy(polyhedron.array[index2]);
|
|
561
|
-
|
|
562
|
-
// Find point that forms the biggest triangle
|
|
563
|
-
let index3 = -1;
|
|
564
|
-
let bestTriangleAreaSquared = -1.0;
|
|
565
|
-
for (let i = 0; i < polyhedron.length; ++i) {
|
|
566
|
-
if (i === index1 || i === index2) {
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
buildConvexHullvertexC.copy(polyhedron.array[i]);
|
|
571
|
-
buildConvexHullvectorCA.subtractVectors(buildConvexHullvertexA, buildConvexHullvertexC);
|
|
572
|
-
buildConvexHullvectorCB.subtractVectors(buildConvexHullvertexB, buildConvexHullvertexC);
|
|
573
|
-
buildConvexHullcrossCA_CB.crossVectors(buildConvexHullvectorCA, buildConvexHullvectorCB);
|
|
574
|
-
const triangleAreaSquared = buildConvexHullcrossCA_CB.squaredLength();
|
|
575
|
-
|
|
576
|
-
if (triangleAreaSquared > bestTriangleAreaSquared) {
|
|
577
|
-
bestTriangleAreaSquared = triangleAreaSquared;
|
|
578
|
-
index3 = i;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (index3 < 0) {
|
|
583
|
-
throw new Error("No vertex found");
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
buildConvexHullvertexC.copy(polyhedron.array[index3]);
|
|
587
|
-
|
|
588
|
-
if (bestTriangleAreaSquared < minTriangleAreaSquared) {
|
|
589
|
-
throw new Error("Could not find a suitable initial triangle because its area was too small");
|
|
590
|
-
return ConvexHullBuilderResult.Degenerate;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Check if we have only 3 vertices
|
|
594
|
-
if (polyhedron.length === 3) {
|
|
595
|
-
// Create two triangles (back to back)
|
|
596
|
-
const triangle1 = this.createTriangle(index1, index2, index3);
|
|
597
|
-
const triangle2 = this.createTriangle(index1, index3, index2);
|
|
598
|
-
|
|
599
|
-
// Link faces edges
|
|
600
|
-
this.linkFace(triangle1.firstEdge!, triangle2.firstEdge!.nextEdge!.nextEdge!);
|
|
601
|
-
this.linkFace(triangle1.firstEdge!.nextEdge!, triangle2.firstEdge!.nextEdge!);
|
|
602
|
-
this.linkFace(triangle1.firstEdge!.nextEdge!.nextEdge!, triangle2.firstEdge!);
|
|
603
|
-
|
|
604
|
-
return ConvexHullBuilderResult.Success;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Find point that forms the biggest tetrahedron
|
|
608
|
-
buildConvexHullvectorAB.subtractVectors(buildConvexHullvertexB, buildConvexHullvertexA);
|
|
609
|
-
buildConvexHullvectorAC.subtractVectors(buildConvexHullvertexC, buildConvexHullvertexA);
|
|
610
|
-
|
|
611
|
-
buildConvexHullinitialPlaneNormal.crossVectors(buildConvexHullvectorAB, buildConvexHullvectorAC);
|
|
612
|
-
buildConvexHullinitialPlaneNormal.normalizeVector(buildConvexHullinitialPlaneNormal);
|
|
613
|
-
|
|
614
|
-
buildConvexHullinitialPlaneCentroid.addVectors(buildConvexHullvertexA, buildConvexHullvertexB);
|
|
615
|
-
buildConvexHullinitialPlaneCentroid.addVectors(buildConvexHullinitialPlaneCentroid, buildConvexHullvertexC);
|
|
616
|
-
buildConvexHullinitialPlaneCentroid.scaleVector(buildConvexHullinitialPlaneCentroid, 1 / 3);
|
|
617
|
-
|
|
618
|
-
let index4 = -1;
|
|
619
|
-
let maxDistance = 0.0;
|
|
620
|
-
for (let i = 0; i < polyhedron.length; ++i) {
|
|
621
|
-
if (i === index1 || i === index2 || i === index3) {
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
buildConvexHullvertexD.copy(polyhedron.array[i]);
|
|
626
|
-
buildConvexHulltempVector.subtractVectors(buildConvexHullvertexD, buildConvexHullinitialPlaneCentroid);
|
|
627
|
-
const distance = buildConvexHulltempVector.dot(buildConvexHullinitialPlaneNormal);
|
|
628
|
-
if (Math.abs(distance) > Math.abs(maxDistance)) {
|
|
629
|
-
maxDistance = distance;
|
|
630
|
-
index4 = i;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (index4 < 0) {
|
|
635
|
-
throw new Error("No vertex found");
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
buildConvexHullvertexD.copy(polyhedron.array[index4]);
|
|
639
|
-
|
|
640
|
-
// Check if the hull is coplanar
|
|
641
|
-
if (squared(maxDistance) <= 25.0 * coplanarToleranceSquared) {
|
|
642
|
-
// First project all points in 2D space
|
|
643
|
-
buildConvexHullbase1.computeNormalizedPerpendicular(buildConvexHullinitialPlaneNormal);
|
|
644
|
-
buildConvexHullbase2.crossVectors(buildConvexHullinitialPlaneNormal, buildConvexHullbase1);
|
|
645
|
-
|
|
646
|
-
// const positions2d = this.world.allocate(Polyhedron, {
|
|
647
|
-
// maxCount: 1,
|
|
648
|
-
// extraSize: 3 * polyhedron.vertices.length,
|
|
649
|
-
// });
|
|
650
|
-
// positions2d.add();
|
|
651
|
-
// const offset2d = positions2d.fullOffset;
|
|
652
|
-
|
|
653
|
-
for (let i = 0; i < polyhedron.length; ++i) {
|
|
654
|
-
buildConvexHulltempVector.copy(polyhedron.array[i]);
|
|
655
|
-
positions2d.array[i].x = buildConvexHullbase1.dot(buildConvexHulltempVector); // x
|
|
656
|
-
positions2d.array[i].y = buildConvexHullbase2.dot(buildConvexHulltempVector); // y
|
|
657
|
-
positions2d.array[i].z = 0.0; // z
|
|
658
|
-
// positions2d.structsBufferFloat64[offset2d + i * 3 + 0] = base1.dot(tempVector);
|
|
659
|
-
// positions2d.structsBufferFloat64[offset2d + i * 3 + 1] = base2.dot(tempVector);
|
|
660
|
-
// positions2d.structsBufferFloat64[offset2d + i * 3 + 2] = 0.0;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Build hull
|
|
664
|
-
const edges2d: number[] = [];
|
|
665
|
-
const builder2d = new ConvexHullBuilder2d();
|
|
666
|
-
const result = builder2d.buildConvexHull(edges2d, index1, index2, index3, positions2d, tolerance, maxVertexCount);
|
|
667
|
-
|
|
668
|
-
// Create faces (back to back)
|
|
669
|
-
const face1 = this.createFace();
|
|
670
|
-
const face2 = this.createFace();
|
|
671
|
-
|
|
672
|
-
// Create edges for face 1
|
|
673
|
-
const edgesF1: ConvexHullBuilderEdge[] = [];
|
|
674
|
-
for (const startIndex of edges2d) {
|
|
675
|
-
const edge = new ConvexHullBuilderEdge(face1, startIndex);
|
|
676
|
-
if (edgesF1.length === 0) {
|
|
677
|
-
face1.firstEdge = edge;
|
|
678
|
-
} else {
|
|
679
|
-
edgesF1[edgesF1.length - 1].nextEdge = edge;
|
|
680
|
-
}
|
|
681
|
-
edgesF1.push(edge);
|
|
682
|
-
}
|
|
683
|
-
edgesF1[edgesF1.length - 1].nextEdge = face1.firstEdge;
|
|
684
|
-
|
|
685
|
-
// Create edges for face 2
|
|
686
|
-
const edgesF2: ConvexHullBuilderEdge[] = [];
|
|
687
|
-
for (let i = edges2d.length - 1; i >= 0; --i) {
|
|
688
|
-
const edge = new ConvexHullBuilderEdge(face2, edges2d[i]);
|
|
689
|
-
if (edgesF2.length === 0) {
|
|
690
|
-
face2.firstEdge = edge;
|
|
691
|
-
} else {
|
|
692
|
-
edgesF2[edgesF2.length - 1].nextEdge = edge;
|
|
693
|
-
}
|
|
694
|
-
edgesF2.push(edge);
|
|
695
|
-
}
|
|
696
|
-
edgesF2[edgesF2.length - 1].nextEdge = face2.firstEdge;
|
|
697
|
-
|
|
698
|
-
// Link edges
|
|
699
|
-
for (let i = 0; i < edges2d.length; ++i) {
|
|
700
|
-
this.linkFace(edgesF1[i], edgesF2[(2 * edges2d.length - 2 - i) % edges2d.length]);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Calculate the plane for both faces
|
|
704
|
-
face1.calculateNormalAndCentroid(polyhedron);
|
|
705
|
-
face2.normal[0] = -face1.normal[0];
|
|
706
|
-
face2.normal[1] = -face1.normal[1];
|
|
707
|
-
face2.normal[2] = -face1.normal[2];
|
|
708
|
-
face2.centroid[0] = face1.centroid[0];
|
|
709
|
-
face2.centroid[1] = face1.centroid[1];
|
|
710
|
-
face2.centroid[2] = face1.centroid[2];
|
|
711
|
-
|
|
712
|
-
return result === ConvexHullBuilder2dResult.MaxVerticesReached
|
|
713
|
-
? ConvexHullBuilderResult.MaxVerticesReached
|
|
714
|
-
: ConvexHullBuilderResult.Success;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Ensure the planes are facing outwards
|
|
718
|
-
if (maxDistance < 0.0) {
|
|
719
|
-
let temp = index2;
|
|
720
|
-
index2 = index3;
|
|
721
|
-
index3 = temp;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Create tetrahedron
|
|
725
|
-
const face1 = this.createTriangle(index1, index2, index4);
|
|
726
|
-
const face2 = this.createTriangle(index2, index3, index4);
|
|
727
|
-
const face3 = this.createTriangle(index3, index1, index4);
|
|
728
|
-
const face4 = this.createTriangle(index1, index3, index2);
|
|
729
|
-
|
|
730
|
-
// Link face edges
|
|
731
|
-
this.linkFace(face1.firstEdge!, face4.firstEdge!.nextEdge!.nextEdge!);
|
|
732
|
-
this.linkFace(face1.firstEdge!.nextEdge!, face2.firstEdge!.nextEdge!.nextEdge!);
|
|
733
|
-
this.linkFace(face1.firstEdge!.nextEdge!.nextEdge!, face3.firstEdge!.nextEdge!);
|
|
734
|
-
this.linkFace(face2.firstEdge!, face4.firstEdge!.nextEdge!);
|
|
735
|
-
this.linkFace(face2.firstEdge!.nextEdge!, face3.firstEdge!.nextEdge!.nextEdge!);
|
|
736
|
-
this.linkFace(face3.firstEdge!, face4.firstEdge!);
|
|
737
|
-
|
|
738
|
-
// Build the initial conflict lists
|
|
739
|
-
const faces: ConvexHullBuilderFace[] = [face1, face2, face3, face4];
|
|
740
|
-
for (let index = 0; index < polyhedron.length; ++index) {
|
|
741
|
-
if (index !== index1 && index !== index2 && index !== index3 && index !== index4) {
|
|
742
|
-
this.assignPointToFace(polyhedron, index, faces, toleranceSquared);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Overestimate of the actual amount of vertices we use, for limiting the amount of vertices in the hull
|
|
747
|
-
let numVerticesUsed = 4;
|
|
748
|
-
|
|
749
|
-
// Loop through the remainder of the points and add them
|
|
750
|
-
while (true) {
|
|
751
|
-
// Find the face with the furthest point on it
|
|
752
|
-
let faceWithFurthestPoint: ConvexHullBuilderFace | undefined = undefined;
|
|
753
|
-
let furthestDistanceSquared = 0.0;
|
|
754
|
-
for (const face of this.faces) {
|
|
755
|
-
if (face.furthestPointDistanceSquared > furthestDistanceSquared) {
|
|
756
|
-
furthestDistanceSquared = face.furthestPointDistanceSquared;
|
|
757
|
-
faceWithFurthestPoint = face;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
let furthestPointIndex: number | undefined = undefined;
|
|
762
|
-
if (faceWithFurthestPoint) {
|
|
763
|
-
// Take the furthest point
|
|
764
|
-
furthestPointIndex = faceWithFurthestPoint.conflictList.pop();
|
|
765
|
-
} else if (this.coplanarList.length !== 0) {
|
|
766
|
-
// Try to assign points to faces (this also recalculates the distance to the hull for the coplanar vertices)
|
|
767
|
-
let coplanarList: ConvexHullBuilderCoplanar[] = [];
|
|
768
|
-
// TODO: is this equivalent to the swap?
|
|
769
|
-
[this.coplanarList, coplanarList] = [coplanarList, this.coplanarList];
|
|
770
|
-
// this does a deep copy (not fully recursive, but enough for our purposes)... i believe this is equivalent to the swap here
|
|
771
|
-
// coplanarList = this.coplanarList.map(coplanar => ({ ...coplanar }));
|
|
772
|
-
|
|
773
|
-
let added = false;
|
|
774
|
-
for (const coplanarObject of coplanarList) {
|
|
775
|
-
added =
|
|
776
|
-
this.assignPointToFace(polyhedron, coplanarObject.positionIndex, this.faces, toleranceSquared) || added;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// If we were able to assign a point, loop again to pick it up
|
|
780
|
-
if (added) {
|
|
781
|
-
continue;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// If the coplanar list is empty, there are no points left and we're done
|
|
785
|
-
if (this.coplanarList.length === 0) {
|
|
786
|
-
break;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
do {
|
|
790
|
-
// Find the vertex that is furthest from the hull
|
|
791
|
-
let bestIndex = 0;
|
|
792
|
-
let bestDistanceSquared = this.coplanarList[0].distanceSquared;
|
|
793
|
-
for (let i = 1; i < this.coplanarList.length; ++i) {
|
|
794
|
-
const coplanarObject = this.coplanarList[i];
|
|
795
|
-
if (coplanarObject.distanceSquared > bestDistanceSquared) {
|
|
796
|
-
bestIndex = i;
|
|
797
|
-
bestDistanceSquared = coplanarObject.distanceSquared;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Swap it to the end
|
|
802
|
-
const temp = this.coplanarList[bestIndex];
|
|
803
|
-
this.coplanarList[bestIndex] = this.coplanarList[this.coplanarList.length - 1];
|
|
804
|
-
this.coplanarList[this.coplanarList.length - 1] = temp;
|
|
805
|
-
|
|
806
|
-
// Remove it
|
|
807
|
-
furthestPointIndex = this.coplanarList.pop()?.positionIndex;
|
|
808
|
-
|
|
809
|
-
// Find the face for which the point is furthest away
|
|
810
|
-
if (furthestPointIndex) {
|
|
811
|
-
buildConvexHulltempVector.copy(polyhedron.array[furthestPointIndex]);
|
|
812
|
-
[faceWithFurthestPoint, bestDistanceSquared] = this.getFaceForPoint(buildConvexHulltempVector, this.faces);
|
|
813
|
-
}
|
|
814
|
-
} while (this.coplanarList.length !== 0 && !faceWithFurthestPoint);
|
|
815
|
-
|
|
816
|
-
if (!faceWithFurthestPoint) {
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
} else {
|
|
820
|
-
// If there are no more vertices, we're done
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Check if we have a limit on the max vertices that we should produce
|
|
825
|
-
if (numVerticesUsed >= maxVertexCount) {
|
|
826
|
-
// Count the actual amount of used vertices (we did not take the removal of any vertices into account)
|
|
827
|
-
numVerticesUsed = this.getNumberOfVerticesUsed();
|
|
828
|
-
|
|
829
|
-
// Check if there are too many
|
|
830
|
-
if (numVerticesUsed >= maxVertexCount) {
|
|
831
|
-
return ConvexHullBuilderResult.MaxVerticesReached;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// We're about to add another vertex
|
|
836
|
-
++numVerticesUsed;
|
|
837
|
-
|
|
838
|
-
// Add the point to the hull
|
|
839
|
-
const newFaces: ConvexHullBuilderFace[] = [];
|
|
840
|
-
this.addPoint(faceWithFurthestPoint, furthestPointIndex!, coplanarToleranceSquared, newFaces);
|
|
841
|
-
|
|
842
|
-
// Redistribute points on conflict lists belonging to removed faces
|
|
843
|
-
for (const face of this.faces) {
|
|
844
|
-
if (face.removed) {
|
|
845
|
-
for (const index of face.conflictList) {
|
|
846
|
-
this.assignPointToFace(polyhedron, index, newFaces, toleranceSquared);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// Permanently delete faces that we removed in AddPoint()
|
|
852
|
-
this.garbageCollectFaces();
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// this.mergeSimilarFaces(maxAngleBetweenAdjacentFacesDegrees);
|
|
856
|
-
|
|
857
|
-
// Check if we are left with a hull. It is possible that hull building fails if the points are nearly coplanar.
|
|
858
|
-
if (this.faces.length < 2) {
|
|
859
|
-
throw new Error("Too few faces in hull");
|
|
860
|
-
return ConvexHullBuilderResult.TooFewFaces;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
return ConvexHullBuilderResult.Success;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
mergeSimilarFaces(maxAngleBetweenAdjacentFaces: number) {
|
|
867
|
-
const threshold = Math.cos(degreesToRadians(maxAngleBetweenAdjacentFaces));
|
|
868
|
-
|
|
869
|
-
for (const face of this.faces) {
|
|
870
|
-
if (face.removed) continue;
|
|
871
|
-
let edge = face.firstEdge!;
|
|
872
|
-
do {
|
|
873
|
-
const neighbor = edge.neighbourEdge?.face;
|
|
874
|
-
if (!neighbor || neighbor.removed) {
|
|
875
|
-
edge = edge.nextEdge!;
|
|
876
|
-
continue;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
faceNormal.fromArray(face.normal);
|
|
880
|
-
neighborNormal.fromArray(neighbor.normal);
|
|
881
|
-
|
|
882
|
-
if (faceNormal.dot(neighborNormal) >= threshold) {
|
|
883
|
-
this.mergeFaces(edge);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
edge = edge.nextEdge!;
|
|
887
|
-
} while (edge !== face.firstEdge);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
this.garbageCollectFaces();
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
addPoint(
|
|
894
|
-
inFacingFace: ConvexHullBuilderFace,
|
|
895
|
-
inIndex: number,
|
|
896
|
-
inCoplanarToleranceSquared: number,
|
|
897
|
-
outNewFaces: ConvexHullBuilderFace[]
|
|
898
|
-
): void {
|
|
899
|
-
// Get position
|
|
900
|
-
addPointposition.copy(this.polyhedron!.array[inIndex]);
|
|
901
|
-
|
|
902
|
-
this.validateFaces();
|
|
903
|
-
|
|
904
|
-
// Find edge of convex hull of faces that are not facing the new vertex
|
|
905
|
-
const edges: ConvexHullBuilderFullEdge[] = [];
|
|
906
|
-
this.findEdge(inFacingFace, addPointposition, edges);
|
|
907
|
-
if (edges.length < 3) {
|
|
908
|
-
// debugger;
|
|
909
|
-
throw new Error("Not enough edges found");
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// Create new faces
|
|
913
|
-
outNewFaces.length = 0;
|
|
914
|
-
for (const edge of edges) {
|
|
915
|
-
if (edge.startIndex === edge.endIndex) {
|
|
916
|
-
throw new Error("Start and end index are the same");
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
const face = this.createTriangle(edge.startIndex, edge.endIndex, inIndex);
|
|
920
|
-
outNewFaces.push(face);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// Link edges
|
|
924
|
-
for (let i = 0; i < outNewFaces.length; ++i) {
|
|
925
|
-
// debugger;
|
|
926
|
-
this.linkFace(outNewFaces[i].firstEdge!, edges[i].neighbourEdge!);
|
|
927
|
-
this.linkFace(
|
|
928
|
-
outNewFaces[i].firstEdge!.nextEdge!,
|
|
929
|
-
outNewFaces[(i + 1) % outNewFaces.length].firstEdge!.nextEdge!.nextEdge!
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Loop on faces that were modified until nothing needs to be checked anymore
|
|
934
|
-
const affectedFaces: ConvexHullBuilderFace[] = [...outNewFaces];
|
|
935
|
-
while (affectedFaces.length !== 0) {
|
|
936
|
-
// Take the next face
|
|
937
|
-
const face = affectedFaces.pop()!;
|
|
938
|
-
|
|
939
|
-
if (face.removed) {
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Merge with neighbour if this is a degenerate face
|
|
944
|
-
this.mergeDegenerateFace(face, affectedFaces);
|
|
945
|
-
|
|
946
|
-
// Merge with coplanar neighbours (or when the neighbour forms a concave edge)
|
|
947
|
-
if (face.removed) {
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
this.mergeCoplanarOrConcaveFaces(face, inCoplanarToleranceSquared, affectedFaces);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
this.validateFaces();
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
garbageCollectFaces(): void {
|
|
957
|
-
for (let i = this.faces.length - 1; i >= 0; --i) {
|
|
958
|
-
const face = this.faces[i];
|
|
959
|
-
if (face.removed) {
|
|
960
|
-
this.freeFace(face);
|
|
961
|
-
this.faces.splice(i, 1);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
createFace(): ConvexHullBuilderFace {
|
|
967
|
-
// Call provider to create face
|
|
968
|
-
const face = new ConvexHullBuilderFace();
|
|
969
|
-
|
|
970
|
-
// Add to list
|
|
971
|
-
this.faces.push(face);
|
|
972
|
-
return face;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
createTriangle(index0: number, index1: number, index2: number) {
|
|
976
|
-
const face = this.createFace();
|
|
977
|
-
face.initialize(index0, index1, index2, this.polyhedron!);
|
|
978
|
-
return face;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
freeFace(inFace: ConvexHullBuilderFace): void {
|
|
982
|
-
// #v-ifdef DEV
|
|
983
|
-
if (!inFace.removed) {
|
|
984
|
-
throw new Error("Face should be removed");
|
|
985
|
-
}
|
|
986
|
-
// #v-endif
|
|
987
|
-
|
|
988
|
-
// Make sure that this face is not connected
|
|
989
|
-
let e = inFace.firstEdge;
|
|
990
|
-
if (e !== undefined) {
|
|
991
|
-
do {
|
|
992
|
-
// #v-ifdef DEV
|
|
993
|
-
if (e!.neighbourEdge !== undefined) {
|
|
994
|
-
throw new Error("Face should not have neighbours");
|
|
995
|
-
}
|
|
996
|
-
// #v-endif
|
|
997
|
-
e = e!.nextEdge;
|
|
998
|
-
} while (e !== inFace.firstEdge);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Free the face
|
|
1002
|
-
// delete inFace; TODO: may not need to delete anything
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
linkFace(edge1: ConvexHullBuilderEdge, edge2: ConvexHullBuilderEdge) {
|
|
1006
|
-
// Check not connected yet
|
|
1007
|
-
// #v-ifdef DEV
|
|
1008
|
-
if (edge1.neighbourEdge !== undefined) {
|
|
1009
|
-
throw new Error("Edge 1 already connected");
|
|
1010
|
-
}
|
|
1011
|
-
// #v-endif
|
|
1012
|
-
|
|
1013
|
-
// #v-ifdef DEV
|
|
1014
|
-
if (edge2.neighbourEdge !== undefined) {
|
|
1015
|
-
throw new Error("Edge 2 already connected");
|
|
1016
|
-
}
|
|
1017
|
-
// #v-endif
|
|
1018
|
-
|
|
1019
|
-
// #v-ifdef DEV
|
|
1020
|
-
if (edge1.face === edge2.face) {
|
|
1021
|
-
throw new Error("Faces are the same");
|
|
1022
|
-
}
|
|
1023
|
-
// #v-endif
|
|
1024
|
-
|
|
1025
|
-
// // Check not connected yet
|
|
1026
|
-
|
|
1027
|
-
// #v-ifdef DEV
|
|
1028
|
-
// Check faces are different
|
|
1029
|
-
if (edge1.face === edge2.face) {
|
|
1030
|
-
throw new Error("Faces are the same");
|
|
1031
|
-
}
|
|
1032
|
-
// #v-endif
|
|
1033
|
-
|
|
1034
|
-
// #v-ifdef DEV
|
|
1035
|
-
// Check vertices match
|
|
1036
|
-
if (edge1.startIndex !== edge2.nextEdge!.startIndex) {
|
|
1037
|
-
throw new Error("Vertices do not match");
|
|
1038
|
-
}
|
|
1039
|
-
// #v-endif
|
|
1040
|
-
|
|
1041
|
-
// #v-ifdef DEV
|
|
1042
|
-
if (edge2.startIndex !== edge1.nextEdge!.startIndex) {
|
|
1043
|
-
throw new Error("Vertices do not match");
|
|
1044
|
-
}
|
|
1045
|
-
// #v-endif
|
|
1046
|
-
|
|
1047
|
-
// Link up
|
|
1048
|
-
edge1.neighbourEdge = edge2;
|
|
1049
|
-
edge2.neighbourEdge = edge1;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
unlinkFace(inFace: ConvexHullBuilderFace): void {
|
|
1053
|
-
// Unlink from neighbours
|
|
1054
|
-
let edge = inFace.firstEdge;
|
|
1055
|
-
do {
|
|
1056
|
-
if (edge!.neighbourEdge) {
|
|
1057
|
-
// #v-ifdef DEV
|
|
1058
|
-
// Validate that neighbour points to us
|
|
1059
|
-
if (edge!.neighbourEdge.neighbourEdge !== edge) {
|
|
1060
|
-
throw new Error("Neighbour does not point to us");
|
|
1061
|
-
}
|
|
1062
|
-
// #v-endif
|
|
1063
|
-
|
|
1064
|
-
// Unlink
|
|
1065
|
-
edge!.neighbourEdge.neighbourEdge = undefined;
|
|
1066
|
-
edge!.neighbourEdge = undefined;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
edge = edge!.nextEdge;
|
|
1070
|
-
} while (edge !== inFace.firstEdge);
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
findEdge(inFacingFace: ConvexHullBuilderFace, inVertex: Vec3, outEdges: ConvexHullBuilderFullEdge[]): void {
|
|
1074
|
-
// #v-ifdef DEV
|
|
1075
|
-
// Assert that we were given an empty array
|
|
1076
|
-
if (outEdges.length !== 0) {
|
|
1077
|
-
throw new Error("outEdges must be empty");
|
|
1078
|
-
}
|
|
1079
|
-
// #v-endif
|
|
1080
|
-
|
|
1081
|
-
// #v-ifdef DEV
|
|
1082
|
-
// Should start with a facing face
|
|
1083
|
-
if (!inFacingFace.isFacing(inVertex)) {
|
|
1084
|
-
throw new Error("inFacingFace must be facing inVertex");
|
|
1085
|
-
}
|
|
1086
|
-
// #v-endif
|
|
1087
|
-
|
|
1088
|
-
// Flag as removed
|
|
1089
|
-
inFacingFace.removed = true;
|
|
1090
|
-
|
|
1091
|
-
// Instead of recursing, we build our own stack with the information we need
|
|
1092
|
-
interface StackEntry {
|
|
1093
|
-
firstEdge: ConvexHullBuilderEdge;
|
|
1094
|
-
currentEdge: ConvexHullBuilderEdge;
|
|
1095
|
-
visited: boolean;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
const stack: StackEntry[] = [];
|
|
1099
|
-
let currentStackPosition = 0;
|
|
1100
|
-
|
|
1101
|
-
// Start with the face / edge provided
|
|
1102
|
-
stack[currentStackPosition] = {
|
|
1103
|
-
firstEdge: inFacingFace.firstEdge!,
|
|
1104
|
-
currentEdge: inFacingFace.firstEdge!,
|
|
1105
|
-
visited: false,
|
|
1106
|
-
};
|
|
1107
|
-
|
|
1108
|
-
let iteration = -1;
|
|
1109
|
-
|
|
1110
|
-
while (true) {
|
|
1111
|
-
iteration++;
|
|
1112
|
-
const currentEntry = stack[currentStackPosition];
|
|
1113
|
-
|
|
1114
|
-
// Next edge
|
|
1115
|
-
const edge = currentEntry.currentEdge;
|
|
1116
|
-
currentEntry.currentEdge = edge.nextEdge!;
|
|
1117
|
-
|
|
1118
|
-
// If we're back at the first edge we've completed the face and we're done
|
|
1119
|
-
if (edge === currentEntry.firstEdge && currentEntry.visited) {
|
|
1120
|
-
// This face needs to be removed, unlink it now, caller will free
|
|
1121
|
-
this.unlinkFace(edge.face);
|
|
1122
|
-
|
|
1123
|
-
// Pop from stack
|
|
1124
|
-
if (--currentStackPosition < 0) {
|
|
1125
|
-
break;
|
|
1126
|
-
}
|
|
1127
|
-
} else {
|
|
1128
|
-
currentEntry.visited = true;
|
|
1129
|
-
// Visit neighbour face
|
|
1130
|
-
const neighbourEdge = edge.neighbourEdge;
|
|
1131
|
-
if (neighbourEdge) {
|
|
1132
|
-
const neighbourFace = neighbourEdge.face;
|
|
1133
|
-
if (!neighbourFace.removed) {
|
|
1134
|
-
// Check if vertex is on the front side of this face
|
|
1135
|
-
if (neighbourFace.isFacing(inVertex)) {
|
|
1136
|
-
// Vertex on front, this face needs to be removed
|
|
1137
|
-
neighbourFace.removed = true;
|
|
1138
|
-
|
|
1139
|
-
// Add element to the stack of elements to visit
|
|
1140
|
-
currentStackPosition++;
|
|
1141
|
-
stack[currentStackPosition] = {
|
|
1142
|
-
firstEdge: neighbourEdge,
|
|
1143
|
-
currentEdge: neighbourEdge.nextEdge!, // We don't need to test this edge again since we came from it
|
|
1144
|
-
visited: false,
|
|
1145
|
-
};
|
|
1146
|
-
} else {
|
|
1147
|
-
// Vertex behind, keep edge
|
|
1148
|
-
const full = new ConvexHullBuilderFullEdge(edge.startIndex, neighbourEdge.startIndex, neighbourEdge);
|
|
1149
|
-
outEdges.push(full);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
mergeFaces(inEdge: ConvexHullBuilderEdge): void {
|
|
1158
|
-
// Get the face
|
|
1159
|
-
const face = inEdge.face;
|
|
1160
|
-
|
|
1161
|
-
// Find the previous and next edge
|
|
1162
|
-
const nextEdge = inEdge.nextEdge;
|
|
1163
|
-
const prevEdge = inEdge.getPreviousEdge();
|
|
1164
|
-
|
|
1165
|
-
// Get the other face
|
|
1166
|
-
const otherEdge = inEdge.neighbourEdge;
|
|
1167
|
-
const otherFace = otherEdge!.face;
|
|
1168
|
-
|
|
1169
|
-
// #v-ifdef DEV
|
|
1170
|
-
// Check if attempting to merge with self
|
|
1171
|
-
if (face === otherFace) {
|
|
1172
|
-
throw new Error("Attempting to merge with self");
|
|
1173
|
-
}
|
|
1174
|
-
// #v-endif
|
|
1175
|
-
|
|
1176
|
-
// Loop over the edges of the other face and make them belong to inFace
|
|
1177
|
-
let edge = otherEdge!.nextEdge;
|
|
1178
|
-
prevEdge!.nextEdge = edge;
|
|
1179
|
-
while (true) {
|
|
1180
|
-
edge!.face = face;
|
|
1181
|
-
if (edge!.nextEdge === otherEdge) {
|
|
1182
|
-
// Terminate when we are back at otherEdge
|
|
1183
|
-
edge!.nextEdge = nextEdge;
|
|
1184
|
-
break;
|
|
1185
|
-
}
|
|
1186
|
-
edge = edge!.nextEdge;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
// If the first edge happens to be inEdge we need to fix it because this edge is no longer part of the face.
|
|
1190
|
-
// Note that we replace it with the first edge of the merged face so that if the MergeFace function is called
|
|
1191
|
-
// from a loop that loops around the face that it will still terminate after visiting all edges once.
|
|
1192
|
-
if (face.firstEdge === inEdge) {
|
|
1193
|
-
face.firstEdge = prevEdge!.nextEdge;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// Free the edges
|
|
1197
|
-
// delete inEdge; TODO: may not need to delete anything
|
|
1198
|
-
// delete otherEdge; TODO: may not need to delete anything
|
|
1199
|
-
|
|
1200
|
-
// Mark the other face as removed
|
|
1201
|
-
otherFace.firstEdge = undefined;
|
|
1202
|
-
otherFace.removed = true;
|
|
1203
|
-
|
|
1204
|
-
// Recalculate plane
|
|
1205
|
-
face.calculateNormalAndCentroid(this.polyhedron!);
|
|
1206
|
-
|
|
1207
|
-
// Merge conflict lists
|
|
1208
|
-
if (face.furthestPointDistanceSquared > otherFace.furthestPointDistanceSquared) {
|
|
1209
|
-
// This face has a point that's further away, make sure it remains the last one as we add the other points to this faces list
|
|
1210
|
-
face.conflictList.splice(face.conflictList.length - 1, 0, ...otherFace.conflictList);
|
|
1211
|
-
} else {
|
|
1212
|
-
// The other face has a point that's furthest away, add that list at the end.
|
|
1213
|
-
face.conflictList.splice(face.conflictList.length, 0, ...otherFace.conflictList);
|
|
1214
|
-
face.furthestPointDistanceSquared = otherFace.furthestPointDistanceSquared;
|
|
1215
|
-
}
|
|
1216
|
-
otherFace.conflictList.length = 0;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
mergeDegenerateFace(inFace: ConvexHullBuilderFace, ioAffectedFaces: ConvexHullBuilderFace[]): void {
|
|
1220
|
-
mergeDegenerateFacenormal.x = inFace.normal[0];
|
|
1221
|
-
mergeDegenerateFacenormal.y = inFace.normal[1];
|
|
1222
|
-
mergeDegenerateFacenormal.z = inFace.normal[2];
|
|
1223
|
-
|
|
1224
|
-
// Check area of face
|
|
1225
|
-
if (mergeDegenerateFacenormal.squaredLength() < minTriangleAreaSquared) {
|
|
1226
|
-
// Find longest edge, since this face is a sliver this should keep the face convex
|
|
1227
|
-
let maxLengthSquared = 0.0;
|
|
1228
|
-
let longestEdge: ConvexHullBuilderEdge | undefined = undefined;
|
|
1229
|
-
let edge = inFace.firstEdge;
|
|
1230
|
-
mergeDegenerateFacep1.copy(this.polyhedron!.array[edge!.startIndex]);
|
|
1231
|
-
do {
|
|
1232
|
-
const nextEdge = edge!.nextEdge;
|
|
1233
|
-
mergeDegenerateFacep2.copy(this.polyhedron!.array[nextEdge!.startIndex]);
|
|
1234
|
-
const lengthSquared = mergeDegenerateFacep1.squaredDistance(mergeDegenerateFacep2);
|
|
1235
|
-
if (lengthSquared >= maxLengthSquared) {
|
|
1236
|
-
maxLengthSquared = lengthSquared;
|
|
1237
|
-
longestEdge = edge;
|
|
1238
|
-
}
|
|
1239
|
-
mergeDegenerateFacep1.copy(mergeDegenerateFacep2);
|
|
1240
|
-
edge = nextEdge;
|
|
1241
|
-
} while (edge !== inFace.firstEdge);
|
|
1242
|
-
|
|
1243
|
-
// Merge with face on longest edge
|
|
1244
|
-
this.mergeFaces(longestEdge!);
|
|
1245
|
-
|
|
1246
|
-
// Remove any invalid edges
|
|
1247
|
-
this.removeInvalidEdges(inFace, ioAffectedFaces);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
mergeCoplanarOrConcaveFaces(
|
|
1252
|
-
inFace: ConvexHullBuilderFace,
|
|
1253
|
-
inCoplanarToleranceSquared: number,
|
|
1254
|
-
ioAffectedFaces: ConvexHullBuilderFace[]
|
|
1255
|
-
): void {
|
|
1256
|
-
mergeCoplanarOrConcaveFacesinFaceNormal.x = inFace.normal[0];
|
|
1257
|
-
mergeCoplanarOrConcaveFacesinFaceNormal.y = inFace.normal[1];
|
|
1258
|
-
mergeCoplanarOrConcaveFacesinFaceNormal.z = inFace.normal[2];
|
|
1259
|
-
mergeCoplanarOrConcaveFacesinFaceCentroid.x = inFace.centroid[0];
|
|
1260
|
-
mergeCoplanarOrConcaveFacesinFaceCentroid.y = inFace.centroid[1];
|
|
1261
|
-
mergeCoplanarOrConcaveFacesinFaceCentroid.z = inFace.centroid[2];
|
|
1262
|
-
|
|
1263
|
-
let merged = false;
|
|
1264
|
-
|
|
1265
|
-
let edge = inFace.firstEdge;
|
|
1266
|
-
do {
|
|
1267
|
-
// Store next edge since this edge can be removed
|
|
1268
|
-
const nextEdge = edge!.nextEdge;
|
|
1269
|
-
|
|
1270
|
-
// Test if centroid of one face is above plane of the other face by inCoplanarToleranceSq.
|
|
1271
|
-
// If so we need to merge other face into inFace.
|
|
1272
|
-
const otherFace = edge!.neighbourEdge!.face;
|
|
1273
|
-
mergeCoplanarOrConcaveFacesotherFaceNormal.x = otherFace.normal[0];
|
|
1274
|
-
mergeCoplanarOrConcaveFacesotherFaceNormal.y = otherFace.normal[1];
|
|
1275
|
-
mergeCoplanarOrConcaveFacesotherFaceNormal.z = otherFace.normal[2];
|
|
1276
|
-
mergeCoplanarOrConcaveFacesotherFaceCentroid.x = otherFace.centroid[0];
|
|
1277
|
-
mergeCoplanarOrConcaveFacesotherFaceCentroid.y = otherFace.centroid[1];
|
|
1278
|
-
mergeCoplanarOrConcaveFacesotherFaceCentroid.z = otherFace.centroid[2];
|
|
1279
|
-
|
|
1280
|
-
mergeCoplanarOrConcaveFacesdeltaCentroid.subtractVectors(
|
|
1281
|
-
mergeCoplanarOrConcaveFacesotherFaceCentroid,
|
|
1282
|
-
mergeCoplanarOrConcaveFacesinFaceCentroid
|
|
1283
|
-
);
|
|
1284
|
-
const distanceOtherFaceCentroid = mergeCoplanarOrConcaveFacesinFaceNormal.dot(
|
|
1285
|
-
mergeCoplanarOrConcaveFacesdeltaCentroid
|
|
1286
|
-
);
|
|
1287
|
-
const signedDistanceOtherFaceCentroidSquared = Math.abs(distanceOtherFaceCentroid) * distanceOtherFaceCentroid;
|
|
1288
|
-
const distanceFaceCentroid = -mergeCoplanarOrConcaveFacesotherFaceNormal.dot(
|
|
1289
|
-
mergeCoplanarOrConcaveFacesdeltaCentroid
|
|
1290
|
-
);
|
|
1291
|
-
const signedDistanceFaceCentroidSquared = Math.abs(distanceFaceCentroid) * distanceFaceCentroid;
|
|
1292
|
-
const inFaceNormalLengthSquared = mergeCoplanarOrConcaveFacesinFaceNormal.squaredLength();
|
|
1293
|
-
const otherFaceNormalLengthSquared = mergeCoplanarOrConcaveFacesotherFaceNormal.squaredLength();
|
|
1294
|
-
if (
|
|
1295
|
-
(signedDistanceOtherFaceCentroidSquared > -inCoplanarToleranceSquared * inFaceNormalLengthSquared ||
|
|
1296
|
-
signedDistanceFaceCentroidSquared > -inCoplanarToleranceSquared * otherFaceNormalLengthSquared) &&
|
|
1297
|
-
mergeCoplanarOrConcaveFacesinFaceNormal.dot(mergeCoplanarOrConcaveFacesotherFaceNormal) > 0.0
|
|
1298
|
-
) {
|
|
1299
|
-
// Never merge faces that are back to back
|
|
1300
|
-
this.mergeFaces(edge!);
|
|
1301
|
-
merged = true;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
edge = nextEdge;
|
|
1305
|
-
} while (edge !== inFace.firstEdge);
|
|
1306
|
-
|
|
1307
|
-
if (merged) {
|
|
1308
|
-
this.removeInvalidEdges(inFace, ioAffectedFaces);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
markAffected(inFace: ConvexHullBuilderFace, ioAffectedFaces: ConvexHullBuilderFace[]): void {
|
|
1313
|
-
if (!ioAffectedFaces.includes(inFace)) {
|
|
1314
|
-
ioAffectedFaces.push(inFace);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
removeInvalidEdges(inFace: ConvexHullBuilderFace, ioAffectedFaces: ConvexHullBuilderFace[]): void {
|
|
1319
|
-
// This marks that the plane needs to be recalculated (we delay this until the end of the function since we don't use the plane and we want to avoid calculating it multiple times)
|
|
1320
|
-
let recalculatePlane = false;
|
|
1321
|
-
|
|
1322
|
-
// We keep going through this loop until no more edges were removed
|
|
1323
|
-
let removed: boolean;
|
|
1324
|
-
|
|
1325
|
-
do {
|
|
1326
|
-
removed = false;
|
|
1327
|
-
|
|
1328
|
-
// Loop over all edges in this face
|
|
1329
|
-
let edge = inFace.firstEdge;
|
|
1330
|
-
let neighbourFace = edge!.neighbourEdge!.face;
|
|
1331
|
-
|
|
1332
|
-
do {
|
|
1333
|
-
let nextEdge = edge!.nextEdge;
|
|
1334
|
-
let nextNeighbourFace = nextEdge!.neighbourEdge!.face;
|
|
1335
|
-
|
|
1336
|
-
if (neighbourFace === inFace) {
|
|
1337
|
-
// We only remove 1 edge at a time, check if this edge's next edge is our neighbour.
|
|
1338
|
-
// If this check fails, we will continue to scan along the edge until we find an edge where this is the case.
|
|
1339
|
-
if (edge?.neighbourEdge === nextEdge) {
|
|
1340
|
-
// This edge leads back to the starting point, this means the edge is interior and needs to be removed
|
|
1341
|
-
|
|
1342
|
-
// Remove edge
|
|
1343
|
-
const prevEdge = edge!.getPreviousEdge();
|
|
1344
|
-
prevEdge!.nextEdge = nextEdge!.nextEdge;
|
|
1345
|
-
if (inFace.firstEdge === edge || inFace.firstEdge === nextEdge) {
|
|
1346
|
-
inFace.firstEdge = prevEdge;
|
|
1347
|
-
}
|
|
1348
|
-
// delete edge; TODO: may not need to delete anything
|
|
1349
|
-
// delete nextEdge; TODO: may not need to delete anything
|
|
1350
|
-
|
|
1351
|
-
// Check if inFace now has only 2 edges left
|
|
1352
|
-
if (this.removeTwoEdgeFace(inFace, ioAffectedFaces)) {
|
|
1353
|
-
return; // Bail if face no longer exists
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// Restart the loop
|
|
1357
|
-
recalculatePlane = true;
|
|
1358
|
-
removed = true;
|
|
1359
|
-
break;
|
|
1360
|
-
}
|
|
1361
|
-
} else if (neighbourFace === nextNeighbourFace) {
|
|
1362
|
-
// There are two edges that connect to the same face, we will remove the second one
|
|
1363
|
-
|
|
1364
|
-
// First merge the neighbours edges
|
|
1365
|
-
const neighbourEdge = nextEdge!.neighbourEdge;
|
|
1366
|
-
const nextNeighbourEdge = neighbourEdge!.nextEdge;
|
|
1367
|
-
if (neighbourFace.firstEdge === nextNeighbourEdge) {
|
|
1368
|
-
neighbourFace.firstEdge = neighbourEdge;
|
|
1369
|
-
}
|
|
1370
|
-
neighbourEdge!.nextEdge = nextNeighbourEdge!.nextEdge;
|
|
1371
|
-
neighbourEdge!.neighbourEdge = edge;
|
|
1372
|
-
// delete nextNeighbourEdge; TODO: may not need to delete anything
|
|
1373
|
-
|
|
1374
|
-
// Then merge my own edges
|
|
1375
|
-
if (inFace.firstEdge === nextEdge) {
|
|
1376
|
-
inFace.firstEdge = edge;
|
|
1377
|
-
}
|
|
1378
|
-
edge!.nextEdge = nextEdge!.nextEdge;
|
|
1379
|
-
edge!.neighbourEdge = neighbourEdge;
|
|
1380
|
-
// delete nextEdge; TODO: may not need to delete anything
|
|
1381
|
-
|
|
1382
|
-
// Check if neighbour has only 2 edges left
|
|
1383
|
-
if (!this.removeTwoEdgeFace(neighbourFace, ioAffectedFaces)) {
|
|
1384
|
-
// No, we need to recalculate its plane
|
|
1385
|
-
neighbourFace.calculateNormalAndCentroid(this.polyhedron!);
|
|
1386
|
-
|
|
1387
|
-
// Mark neighbour face as affected
|
|
1388
|
-
this.markAffected(neighbourFace, ioAffectedFaces);
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// Check if inFace now has only 2 edges left
|
|
1392
|
-
if (this.removeTwoEdgeFace(inFace, ioAffectedFaces)) {
|
|
1393
|
-
return; // Bail if face no longer exists
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// Restart loop
|
|
1397
|
-
recalculatePlane = true;
|
|
1398
|
-
removed = true;
|
|
1399
|
-
break;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
// This edge is ok, go to the next edge
|
|
1403
|
-
edge = nextEdge;
|
|
1404
|
-
neighbourFace = nextNeighbourFace;
|
|
1405
|
-
} while (edge !== inFace.firstEdge);
|
|
1406
|
-
} while (removed);
|
|
1407
|
-
|
|
1408
|
-
// Recalculate plane?
|
|
1409
|
-
if (recalculatePlane) {
|
|
1410
|
-
inFace.calculateNormalAndCentroid(this.polyhedron!);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
removeTwoEdgeFace(inFace: ConvexHullBuilderFace, ioAffectedFaces: ConvexHullBuilderFace[]): boolean {
|
|
1415
|
-
// Check if this face contains only 2 edges
|
|
1416
|
-
let edge = inFace.firstEdge;
|
|
1417
|
-
let nextEdge = edge!.nextEdge;
|
|
1418
|
-
// #v-ifdef DEV
|
|
1419
|
-
if (edge === nextEdge) {
|
|
1420
|
-
throw new Error("1 edge faces should not exist");
|
|
1421
|
-
}
|
|
1422
|
-
// #v-endif
|
|
1423
|
-
|
|
1424
|
-
if (nextEdge!.nextEdge === edge) {
|
|
1425
|
-
// Schedule both neighbours for re-checking
|
|
1426
|
-
const neighbourEdge = edge!.neighbourEdge;
|
|
1427
|
-
const neighbourFace = neighbourEdge!.face;
|
|
1428
|
-
const nextNeighbourEdge = nextEdge!.neighbourEdge;
|
|
1429
|
-
const nextNeighbourFace = nextNeighbourEdge!.face;
|
|
1430
|
-
this.markAffected(neighbourFace, ioAffectedFaces);
|
|
1431
|
-
this.markAffected(nextNeighbourFace, ioAffectedFaces);
|
|
1432
|
-
|
|
1433
|
-
// Link my neighbours to each other
|
|
1434
|
-
neighbourEdge!.neighbourEdge = nextNeighbourEdge;
|
|
1435
|
-
nextNeighbourEdge!.neighbourEdge = neighbourEdge;
|
|
1436
|
-
|
|
1437
|
-
// Unlink my edges
|
|
1438
|
-
edge!.neighbourEdge = undefined;
|
|
1439
|
-
nextEdge!.neighbourEdge = undefined;
|
|
1440
|
-
|
|
1441
|
-
// Mark this face as removed
|
|
1442
|
-
inFace.removed = true;
|
|
1443
|
-
|
|
1444
|
-
return true;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
return false;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
validateFace(inFace: ConvexHullBuilderFace): void {
|
|
1451
|
-
if (inFace.removed) {
|
|
1452
|
-
let edge = inFace.firstEdge;
|
|
1453
|
-
if (edge !== undefined) {
|
|
1454
|
-
do {
|
|
1455
|
-
// #v-ifdef DEV
|
|
1456
|
-
if (edge!.neighbourEdge !== undefined) {
|
|
1457
|
-
throw new Error("Removed face should not have neighbours");
|
|
1458
|
-
}
|
|
1459
|
-
// #v-endif
|
|
1460
|
-
edge = edge!.nextEdge;
|
|
1461
|
-
} while (edge !== inFace.firstEdge);
|
|
1462
|
-
}
|
|
1463
|
-
} else {
|
|
1464
|
-
let edgeCount = 0;
|
|
1465
|
-
|
|
1466
|
-
let edge = inFace.firstEdge;
|
|
1467
|
-
do {
|
|
1468
|
-
// Count edge
|
|
1469
|
-
++edgeCount;
|
|
1470
|
-
|
|
1471
|
-
// Validate that adjacent faces are all different
|
|
1472
|
-
if (this.faces.length > 2) {
|
|
1473
|
-
for (let otherEdge = edge!.nextEdge; otherEdge !== inFace.firstEdge; otherEdge = otherEdge!.nextEdge) {
|
|
1474
|
-
// #v-ifdef DEV
|
|
1475
|
-
if (edge!.neighbourEdge!.face === otherEdge!.neighbourEdge!.face) {
|
|
1476
|
-
throw new Error("Adjacent faces should be different");
|
|
1477
|
-
}
|
|
1478
|
-
// #v-endif
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// #v-ifdef DEV
|
|
1482
|
-
// Assert that the face is correct
|
|
1483
|
-
if (edge!.face !== inFace) {
|
|
1484
|
-
throw new Error("Face is not correct");
|
|
1485
|
-
}
|
|
1486
|
-
// #v-endif
|
|
1487
|
-
|
|
1488
|
-
const nbEdge = edge!.neighbourEdge;
|
|
1489
|
-
// #v-ifdef DEV
|
|
1490
|
-
// Assert that we have a neighbour
|
|
1491
|
-
if (nbEdge === undefined) {
|
|
1492
|
-
throw new Error("Edge should have a neighbour");
|
|
1493
|
-
}
|
|
1494
|
-
// #v-endif
|
|
1495
|
-
|
|
1496
|
-
// #v-ifdef DEV
|
|
1497
|
-
// Assert that our neighbours edge points to us
|
|
1498
|
-
if (nbEdge.neighbourEdge !== edge) {
|
|
1499
|
-
throw new Error("Neighbour should point to us");
|
|
1500
|
-
}
|
|
1501
|
-
// #v-endif
|
|
1502
|
-
|
|
1503
|
-
// #v-ifdef DEV
|
|
1504
|
-
// Assert that it belongs to a different face
|
|
1505
|
-
if (nbEdge.face === inFace) {
|
|
1506
|
-
throw new Error("Neighbour should belong to a different face");
|
|
1507
|
-
}
|
|
1508
|
-
// #v-endif
|
|
1509
|
-
|
|
1510
|
-
// #v-ifdef DEV
|
|
1511
|
-
// Assert that the next edge of the neighbour points to the same vertex as this edge's vertex
|
|
1512
|
-
if (nbEdge.nextEdge!.startIndex !== edge!.startIndex) {
|
|
1513
|
-
throw new Error("Neighbour's next edge should point to the same vertex");
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// #v-endif
|
|
1517
|
-
// #v-ifdef DEV
|
|
1518
|
-
// Assert that my next edge points to the same vertex as my neighbours vertex
|
|
1519
|
-
if (edge!.nextEdge!.startIndex !== nbEdge.startIndex) {
|
|
1520
|
-
throw new Error("My next edge should point to the same vertex as my neighbours vertex");
|
|
1521
|
-
}
|
|
1522
|
-
// #v-endif
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
edge = edge!.nextEdge;
|
|
1526
|
-
} while (edge !== inFace.firstEdge);
|
|
1527
|
-
|
|
1528
|
-
// #v-ifdef DEV
|
|
1529
|
-
// Assert that we have 3 or more edges
|
|
1530
|
-
if (edgeCount < 3) {
|
|
1531
|
-
throw new Error("Face should have 3 or more edges");
|
|
1532
|
-
}
|
|
1533
|
-
// #v-endif
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
validateFaces(): void {
|
|
1538
|
-
for (const face of this.faces) {
|
|
1539
|
-
this.validateFace(face);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
getCenterOfMassAndVolume(outCenterOfMass: Vec3): number {
|
|
1544
|
-
// Fourth point is the average of all face centroids
|
|
1545
|
-
getCenterOfMassAndVolumevertexD.zero();
|
|
1546
|
-
for (const face of this.faces) {
|
|
1547
|
-
getCenterOfMassAndVolumevertexD.x += face.centroid[0];
|
|
1548
|
-
getCenterOfMassAndVolumevertexD.y += face.centroid[1];
|
|
1549
|
-
getCenterOfMassAndVolumevertexD.z += face.centroid[2];
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
getCenterOfMassAndVolumevertexD.scaleVector(getCenterOfMassAndVolumevertexD, 1.0 / this.faces.length);
|
|
1553
|
-
|
|
1554
|
-
// Calculate mass and center of mass of this convex hull by summing all tetrahedrons
|
|
1555
|
-
let outVolume = 0.0;
|
|
1556
|
-
outCenterOfMass.zero();
|
|
1557
|
-
for (const face of this.faces) {
|
|
1558
|
-
// Get the first vertex that we'll use to create a triangle fan
|
|
1559
|
-
let edge = face.firstEdge;
|
|
1560
|
-
getCenterOfMassAndVolumevertexA.copy(this.polyhedron!.array[edge!.startIndex]);
|
|
1561
|
-
|
|
1562
|
-
// Get the second vertex
|
|
1563
|
-
edge = edge!.nextEdge;
|
|
1564
|
-
getCenterOfMassAndVolumevertexB.copy(this.polyhedron!.array[edge!.startIndex]);
|
|
1565
|
-
|
|
1566
|
-
for (edge = edge!.nextEdge; edge !== face.firstEdge; edge = edge!.nextEdge) {
|
|
1567
|
-
// Fetch the last point of the triangle
|
|
1568
|
-
getCenterOfMassAndVolumevertexC.copy(this.polyhedron!.array[edge!.startIndex]);
|
|
1569
|
-
|
|
1570
|
-
// Calculate center of mass and mass of this tetrahedron,
|
|
1571
|
-
// see: https://en.wikipedia.org/wiki/Tetrahedron#Volume
|
|
1572
|
-
getCenterOfMassAndVolumevectorDA.subtractVectors(
|
|
1573
|
-
getCenterOfMassAndVolumevertexA,
|
|
1574
|
-
getCenterOfMassAndVolumevertexD
|
|
1575
|
-
);
|
|
1576
|
-
getCenterOfMassAndVolumevectorDB.subtractVectors(
|
|
1577
|
-
getCenterOfMassAndVolumevertexB,
|
|
1578
|
-
getCenterOfMassAndVolumevertexD
|
|
1579
|
-
);
|
|
1580
|
-
getCenterOfMassAndVolumevectorDC.subtractVectors(
|
|
1581
|
-
getCenterOfMassAndVolumevertexC,
|
|
1582
|
-
getCenterOfMassAndVolumevertexD
|
|
1583
|
-
);
|
|
1584
|
-
getCenterOfMassAndVolumecrossDB_DC.crossVectors(
|
|
1585
|
-
getCenterOfMassAndVolumevectorDB,
|
|
1586
|
-
getCenterOfMassAndVolumevectorDC
|
|
1587
|
-
);
|
|
1588
|
-
const volume_tetrahedron = getCenterOfMassAndVolumevectorDA.dot(getCenterOfMassAndVolumecrossDB_DC); // Needs to be divided by 6, postpone this until the end of the loop
|
|
1589
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron.addVectors(
|
|
1590
|
-
getCenterOfMassAndVolumevertexA,
|
|
1591
|
-
getCenterOfMassAndVolumevertexB
|
|
1592
|
-
);
|
|
1593
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron.addVectors(
|
|
1594
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron,
|
|
1595
|
-
getCenterOfMassAndVolumevertexC
|
|
1596
|
-
);
|
|
1597
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron.addVectors(
|
|
1598
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron,
|
|
1599
|
-
getCenterOfMassAndVolumevertexD
|
|
1600
|
-
); // Needs to be divided by 4, postpone this until the end of the loop
|
|
1601
|
-
|
|
1602
|
-
// Accumulate results
|
|
1603
|
-
outVolume += volume_tetrahedron;
|
|
1604
|
-
outCenterOfMass.addScaledToVector(
|
|
1605
|
-
outCenterOfMass,
|
|
1606
|
-
getCenterOfMassAndVolumecenterOfMassTetrahedron,
|
|
1607
|
-
volume_tetrahedron
|
|
1608
|
-
);
|
|
1609
|
-
|
|
1610
|
-
// Update v2 for next triangle
|
|
1611
|
-
getCenterOfMassAndVolumevertexB.copy(getCenterOfMassAndVolumevertexC);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
// Calculate center of mass, fall back to average point in case there is no volume (everything is on a plane in this case)
|
|
1616
|
-
if (outVolume > floatEpsilon) {
|
|
1617
|
-
outCenterOfMass.scaleVector(outCenterOfMass, 1.0 / (4.0 * outVolume));
|
|
1618
|
-
} else {
|
|
1619
|
-
outCenterOfMass.copy(getCenterOfMassAndVolumevertexD);
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
outVolume /= 6.0;
|
|
1623
|
-
|
|
1624
|
-
return outVolume;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
determineMaxError(): [ConvexHullBuilderFace | undefined, number, number, number] {
|
|
1628
|
-
const outCoplanarDistance = this.determineCoplanarDistance();
|
|
1629
|
-
|
|
1630
|
-
// This measures the distance from a polygon to the furthest point outside of the hull
|
|
1631
|
-
let maxError = 0.0;
|
|
1632
|
-
let maxErrorFace: ConvexHullBuilderFace | undefined = undefined;
|
|
1633
|
-
let maxErrorPoint = -1;
|
|
1634
|
-
|
|
1635
|
-
for (let i = 0; i < this.polyhedron!.length; ++i) {
|
|
1636
|
-
determineMaxError.copy(this.polyhedron!.array[i]);
|
|
1637
|
-
|
|
1638
|
-
// This measures the closest edge from all faces to point v
|
|
1639
|
-
// Note that we take the min of all faces since there may be multiple near coplanar faces so if we were to test this per face
|
|
1640
|
-
// we may find that a point is outside of a polygon and mark it as an error, while it is actually inside a nearly coplanar
|
|
1641
|
-
// polygon.
|
|
1642
|
-
let minEdgeDistanceSquared = Infinity;
|
|
1643
|
-
let minEdgeDistanceFace: ConvexHullBuilderFace | undefined = undefined;
|
|
1644
|
-
|
|
1645
|
-
for (const face of this.faces) {
|
|
1646
|
-
// Check if point is on or in front of plane
|
|
1647
|
-
determineMaxErrornormal.x = face.normal[0];
|
|
1648
|
-
determineMaxErrornormal.y = face.normal[1];
|
|
1649
|
-
determineMaxErrornormal.z = face.normal[2];
|
|
1650
|
-
determineMaxErrorcentroid.x = face.centroid[0];
|
|
1651
|
-
determineMaxErrorcentroid.y = face.centroid[1];
|
|
1652
|
-
determineMaxErrorcentroid.z = face.centroid[2];
|
|
1653
|
-
const normalLength = determineMaxErrornormal.length();
|
|
1654
|
-
// #v-ifdef DEV
|
|
1655
|
-
if (normalLength <= 0.0) {
|
|
1656
|
-
throw new Error(`Normal length should be greater than 0, got ${normalLength}`);
|
|
1657
|
-
}
|
|
1658
|
-
// #v-endif
|
|
1659
|
-
determineMaxErrortempVector.subtractVectors(determineMaxError, determineMaxErrorcentroid);
|
|
1660
|
-
const planeDistance = determineMaxError.dot(determineMaxErrornormal) / normalLength;
|
|
1661
|
-
if (planeDistance > -outCoplanarDistance) {
|
|
1662
|
-
// Check distance to the edges of this face
|
|
1663
|
-
const edgeDistanceSquared = this.getDistanceToEdgeSquared(determineMaxError, face);
|
|
1664
|
-
if (edgeDistanceSquared < minEdgeDistanceSquared) {
|
|
1665
|
-
minEdgeDistanceSquared = edgeDistanceSquared;
|
|
1666
|
-
minEdgeDistanceFace = face;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
// If the point is inside the polygon and the point is in front of the plane, measure the distance
|
|
1670
|
-
if (edgeDistanceSquared === 0.0 && planeDistance > maxError) {
|
|
1671
|
-
maxError = planeDistance;
|
|
1672
|
-
maxErrorFace = face;
|
|
1673
|
-
maxErrorPoint = i;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
// If the minimum distance to an edge is further than our current max error, we use that as max error
|
|
1679
|
-
const minEdgeDistance = Math.sqrt(minEdgeDistanceSquared);
|
|
1680
|
-
if (minEdgeDistanceFace !== undefined && minEdgeDistance > maxError) {
|
|
1681
|
-
maxError = minEdgeDistance;
|
|
1682
|
-
maxErrorFace = minEdgeDistanceFace;
|
|
1683
|
-
maxErrorPoint = i;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
return [maxErrorFace, maxError, maxErrorPoint, outCoplanarDistance];
|
|
1688
|
-
}
|
|
1689
|
-
}
|