@perplexdotgg/bounce 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/package.json +32 -0
  4. package/src/builders/ConvexHullBuilder.ts +437 -0
  5. package/src/builders/ConvexHullBuilder2d.ts +344 -0
  6. package/src/builders/ConvexHullBuilder3d.ts +1689 -0
  7. package/src/builders/HeightMapBuilder.ts +414 -0
  8. package/src/builders/TriangleMeshBuilder.ts +92 -0
  9. package/src/collision/CastShapesModule.ts +184 -0
  10. package/src/collision/CollideShapesModule.ts +152 -0
  11. package/src/collision/HeightMapCaster.ts +38 -0
  12. package/src/collision/HeightMapCollider.ts +33 -0
  13. package/src/collision/TriangleCaster.ts +249 -0
  14. package/src/collision/TriangleCollider.ts +308 -0
  15. package/src/collision/TriangleCollider2.ts +379 -0
  16. package/src/collision/activeEdge.ts +146 -0
  17. package/src/collision/cast/cast.ts +139 -0
  18. package/src/collision/cast/castCompoundVsCompound.ts +59 -0
  19. package/src/collision/cast/castCompoundVsConvex.ts +116 -0
  20. package/src/collision/cast/castConvexVsCompound.ts +123 -0
  21. package/src/collision/cast/castConvexVsConvex.ts +213 -0
  22. package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
  23. package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
  24. package/src/collision/cast/castRayVsCompound.ts +44 -0
  25. package/src/collision/cast/castRayVsConvex.ts +45 -0
  26. package/src/collision/cast/castRayVsHeightMap.ts +58 -0
  27. package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
  28. package/src/collision/closestPoints/closestPoints.ts +23 -0
  29. package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
  30. package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
  31. package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
  32. package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
  33. package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
  34. package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
  35. package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
  36. package/src/collision/collide/collide.ts +146 -0
  37. package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
  38. package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
  39. package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
  40. package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
  41. package/src/collision/collide/collideConvexVsCompound.ts +57 -0
  42. package/src/collision/collide/collideConvexVsConvex.ts +225 -0
  43. package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
  44. package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
  45. package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
  46. package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
  47. package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
  48. package/src/collision/collide/collideSphereVsSphere.ts +81 -0
  49. package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
  50. package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
  51. package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
  52. package/src/collision/epa/StaticArray.ts +154 -0
  53. package/src/collision/epa/TriangleFactory.ts +32 -0
  54. package/src/collision/epa/arrays.ts +99 -0
  55. package/src/collision/epa/binaryHeap.ts +82 -0
  56. package/src/collision/epa/structs.ts +227 -0
  57. package/src/collision/gjk/GjkModule.ts +864 -0
  58. package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
  59. package/src/collision/gjk/SupportPoints.ts +50 -0
  60. package/src/collision/imp/MinkowskiDifference.ts +36 -0
  61. package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
  62. package/src/collision/imp/finalizeImpResult.ts +69 -0
  63. package/src/collision/imp/findContactImp.ts +196 -0
  64. package/src/collision/imp/imp.ts +28 -0
  65. package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
  66. package/src/collision/mpr/findPortal.ts +152 -0
  67. package/src/collision/mpr/mpr.ts +29 -0
  68. package/src/collision/mpr/updatePortal.ts +52 -0
  69. package/src/constraints/BaseConstraint.ts +50 -0
  70. package/src/constraints/ConstraintOptions.ts +22 -0
  71. package/src/constraints/ConstraintSolver.ts +119 -0
  72. package/src/constraints/DistanceConstraint.ts +229 -0
  73. package/src/constraints/FixedConstraint.ts +203 -0
  74. package/src/constraints/HingeConstraint.ts +460 -0
  75. package/src/constraints/PointConstraint.ts +108 -0
  76. package/src/constraints/components/AngleComponent.ts +226 -0
  77. package/src/constraints/components/AxisComponent.ts +263 -0
  78. package/src/constraints/components/HingeComponent.ts +215 -0
  79. package/src/constraints/components/Motor.ts +36 -0
  80. package/src/constraints/components/PointConstraintComponent.ts +179 -0
  81. package/src/constraints/components/RotationEulerComponent.ts +139 -0
  82. package/src/constraints/components/Spring.ts +30 -0
  83. package/src/constraints/components/SpringComponent.ts +71 -0
  84. package/src/constraints/types.ts +6 -0
  85. package/src/helpers.ts +147 -0
  86. package/src/index.ts +50 -0
  87. package/src/math/BasicTransform.ts +19 -0
  88. package/src/math/NumberValue.ts +13 -0
  89. package/src/math/isometry.ts +64 -0
  90. package/src/math/mat3.ts +529 -0
  91. package/src/math/mat4.ts +588 -0
  92. package/src/math/quat.ts +193 -0
  93. package/src/math/scalar.ts +81 -0
  94. package/src/math/tensor.ts +17 -0
  95. package/src/math/vec3.ts +589 -0
  96. package/src/math/vec4.ts +10 -0
  97. package/src/physics/Body.ts +581 -0
  98. package/src/physics/CollisionFilter.ts +52 -0
  99. package/src/physics/SleepModule.ts +163 -0
  100. package/src/physics/broadphase/BodyPairsModule.ts +363 -0
  101. package/src/physics/broadphase/BvhModule.ts +237 -0
  102. package/src/physics/broadphase/BvhTree.ts +803 -0
  103. package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
  104. package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
  105. package/src/physics/manifold/ContactManifold.ts +227 -0
  106. package/src/physics/manifold/ContactManifoldModule.ts +623 -0
  107. package/src/physics/manifold/Face.ts +119 -0
  108. package/src/physics/manifold/ManifoldCache.ts +116 -0
  109. package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
  110. package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
  111. package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
  112. package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
  113. package/src/physics/solver/ContactConstraintModule.ts +659 -0
  114. package/src/physics/solver/ManifoldConstraint.ts +420 -0
  115. package/src/physics/solver/estimateCollisionResponse.ts +146 -0
  116. package/src/shape/Aabb.ts +400 -0
  117. package/src/shape/Box.ts +231 -0
  118. package/src/shape/Capsule.ts +332 -0
  119. package/src/shape/CompoundShape.ts +288 -0
  120. package/src/shape/Convex.ts +130 -0
  121. package/src/shape/ConvexHull.ts +423 -0
  122. package/src/shape/Cylinder.ts +313 -0
  123. package/src/shape/HeightMap.ts +511 -0
  124. package/src/shape/Line.ts +14 -0
  125. package/src/shape/Plane.ts +116 -0
  126. package/src/shape/Ray.ts +81 -0
  127. package/src/shape/Segment.ts +25 -0
  128. package/src/shape/Shape.ts +77 -0
  129. package/src/shape/Sphere.ts +181 -0
  130. package/src/shape/TransformedShape.ts +51 -0
  131. package/src/shape/Triangle.ts +122 -0
  132. package/src/shape/TriangleMesh.ts +186 -0
  133. package/src/types.ts +1 -0
  134. package/src/world.ts +1335 -0
  135. package/tests/BodyPairsModule.test.ts +71 -0
  136. package/tests/BvhTree.test.ts +406 -0
  137. package/tests/test.md +642 -0
  138. package/tests/vec3.test.ts +12 -0
  139. package/tsconfig.json +20 -0
  140. package/vite.config.js +40 -0
@@ -0,0 +1,414 @@
1
+ import { InputType } from "monomorph";
2
+ import { computeClosestPowersOfTwo, degreesToRadians, isPowerOf2 } from "../math/scalar";
3
+ import { Vec3 } from "../math/vec3";
4
+ import {
5
+ computeCornerIndicesInGrid,
6
+ computeNumberOfCells,
7
+ computeNumberOfLevels,
8
+ HeightMap,
9
+ updateShape,
10
+ } from "../shape/HeightMap";
11
+ import { Triangle } from "../shape/Triangle";
12
+ import { Tuple4 } from "../types";
13
+ import { isEdgeActive } from "../collision/activeEdge";
14
+
15
+ const cos5Degrees = Math.cos(degreesToRadians(5));
16
+
17
+ const a1 = /*@__PURE__*/ Vec3.create();
18
+ const b1 = /*@__PURE__*/ Vec3.create();
19
+ const c1 = /*@__PURE__*/ Vec3.create();
20
+ const a2 = /*@__PURE__*/ Vec3.create();
21
+ const b2 = /*@__PURE__*/ Vec3.create();
22
+ const c2 = /*@__PURE__*/ Vec3.create();
23
+ const triangleLeftOfEdge = /*@__PURE__*/ Triangle.create();
24
+ const triangleRightOfEdge = /*@__PURE__*/ Triangle.create();
25
+ const normalLeftOfEdge = /*@__PURE__*/ Vec3.create();
26
+ const normalRightOfEdge = /*@__PURE__*/ Vec3.create();
27
+ const edge = /*@__PURE__*/ Vec3.create();
28
+
29
+ export class HeightMapBuilder {
30
+ buildHeightMapQuadtree(heightMap: HeightMap): void {
31
+ const numberOfSubdivisions = heightMap.subdivisionsCount;
32
+
33
+ // #v-ifdef DEV
34
+ // assert that number of subdivisions is a power of 2
35
+ if (isPowerOf2(numberOfSubdivisions) === false) {
36
+ throw new Error(`expected number of subdivisions to be a power of 2, got ${numberOfSubdivisions} instead`);
37
+ }
38
+ // #v-endif
39
+
40
+ const index: Tuple4<number> = [0, 0, 0, 0];
41
+
42
+ // walk through each layer starting from the bottom, aggregating min and max heights for each cell
43
+ let currentNumberOfSubdivisions = heightMap.subdivisionsCount;
44
+ while (currentNumberOfSubdivisions >= 1) {
45
+ // get the level of the current layer
46
+ const level = Math.log2(currentNumberOfSubdivisions);
47
+
48
+ // get offset for the current layer
49
+ const offset = computeNumberOfCells(currentNumberOfSubdivisions / 2);
50
+
51
+ // case: we're in the bottom layer, the 4 "children" heights are queried from the heightMap data itself
52
+ if (currentNumberOfSubdivisions === heightMap.subdivisionsCount) {
53
+ // for each cell in the current layer
54
+ for (let x = 0; x < currentNumberOfSubdivisions; x++) {
55
+ for (let z = 0; z < currentNumberOfSubdivisions; z++) {
56
+ // get the current level corner vertex indices
57
+ computeCornerIndicesInGrid(index, x, z, currentNumberOfSubdivisions + 1);
58
+
59
+ const blHeight = heightMap.heights[index[0]];
60
+ const brHeight = heightMap.heights[index[1]];
61
+ const tlHeight = heightMap.heights[index[2]];
62
+ const trHeight = heightMap.heights[index[3]];
63
+
64
+ const minHeight = Math.min(blHeight, brHeight, tlHeight, trHeight);
65
+ const maxHeight = Math.max(blHeight, brHeight, tlHeight, trHeight);
66
+
67
+ const flatIndex = z + x * currentNumberOfSubdivisions;
68
+ heightMap.minHeights[offset + flatIndex] = minHeight;
69
+ heightMap.maxHeights[offset + flatIndex] = maxHeight;
70
+ heightMap.levels[offset + flatIndex] = level;
71
+ }
72
+ }
73
+ }
74
+ // case: we're in a higher layer, the 4 "children" heights are queried from the lower layer
75
+ else {
76
+ // get the lower level offset
77
+ const lowerOffset = computeNumberOfCells(currentNumberOfSubdivisions);
78
+
79
+ for (let x = 0; x < currentNumberOfSubdivisions; x++) {
80
+ for (let z = 0; z < currentNumberOfSubdivisions; z++) {
81
+ // get the lower level corner block indices
82
+ computeCornerIndicesInGrid(index, x * 2, z * 2, currentNumberOfSubdivisions * 2);
83
+
84
+ const blMinHeight = heightMap.minHeights[lowerOffset + index[0]];
85
+ const brMinHeight = heightMap.minHeights[lowerOffset + index[1]];
86
+ const tlMinHeight = heightMap.minHeights[lowerOffset + index[2]];
87
+ const trMinHeight = heightMap.minHeights[lowerOffset + index[3]];
88
+
89
+ const blMaxHeight = heightMap.maxHeights[lowerOffset + index[0]];
90
+ const brMaxHeight = heightMap.maxHeights[lowerOffset + index[1]];
91
+ const tlMaxHeight = heightMap.maxHeights[lowerOffset + index[2]];
92
+ const trMaxHeight = heightMap.maxHeights[lowerOffset + index[3]];
93
+
94
+ const minHeight = Math.min(blMinHeight, brMinHeight, tlMinHeight, trMinHeight);
95
+ const maxHeight = Math.max(blMaxHeight, brMaxHeight, tlMaxHeight, trMaxHeight);
96
+
97
+ const flatIndex = z + x * currentNumberOfSubdivisions;
98
+ heightMap.minHeights[offset + flatIndex] = minHeight;
99
+ heightMap.maxHeights[offset + flatIndex] = maxHeight;
100
+ heightMap.levels[offset + flatIndex] = level;
101
+ }
102
+ }
103
+ }
104
+
105
+ currentNumberOfSubdivisions /= 2;
106
+ }
107
+ }
108
+
109
+ buildActiveEdges(heightMap: HeightMap): void {
110
+ // TODO: this can be cleaned up to avoid repetition of logic
111
+
112
+ // we store 1 uint32 per triangle, using the first 3 bits to represent the active edges
113
+
114
+ // for each quad
115
+
116
+ let activeEdge = 0b000;
117
+ let result = 0b000;
118
+
119
+ for (let row = 0; row < heightMap.subdivisionsCount; row++) {
120
+ for (let col = 0; col < heightMap.subdivisionsCount; col++) {
121
+ // compute flat index
122
+ const index = col + row * heightMap.subdivisionsCount;
123
+
124
+ // outer edge flags
125
+ const isFirstRow = row === 0;
126
+ const isLastRow = row === heightMap.subdivisionsCount - 1;
127
+ const isFirstColumn = col === 0;
128
+ const isLastColumn = col === heightMap.subdivisionsCount - 1;
129
+
130
+ // triangle indices
131
+ const firstTriangleIndex = index * 2 + 0;
132
+ const secondTriangleIndex = index * 2 + 1;
133
+
134
+ // TRIANGLE ABC
135
+ activeEdge = 0b000;
136
+ result = 0b000;
137
+
138
+ // check if edge 0 is active (AB)
139
+
140
+ // if edge is an outer edge, it is active
141
+ if (isFirstRow) {
142
+ activeEdge |= 0b001;
143
+ }
144
+ // if edge is an interior edge, get both triangles on either side of the edge
145
+ else {
146
+ // get triangles
147
+ heightMap.computeVertex(a1, col + 0, row + 0);
148
+ heightMap.computeVertex(b1, col + 1, row + 0);
149
+ heightMap.computeVertex(c1, col + 0, row + 1);
150
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
151
+
152
+ heightMap.computeVertex(a2, col + 1, row + 0);
153
+ heightMap.computeVertex(b2, col + 0, row + 0);
154
+ heightMap.computeVertex(c2, col + 1, row - 1);
155
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
156
+
157
+ // get normals
158
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
159
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
160
+
161
+ // get edge direction
162
+ edge.subtractVectors(b1, a1);
163
+ edge.normalize();
164
+
165
+ // check if edge is active
166
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b001 : 0b000;
167
+ activeEdge |= result;
168
+ }
169
+
170
+ // check if edge 1 is active (BC)
171
+
172
+ // this edge is always interior, get both triangles on either side of the edge
173
+
174
+ // get triangles, left is ABC, right is BDC
175
+ heightMap.computeVertex(a1, col + 1, row + 0);
176
+ heightMap.computeVertex(b1, col + 0, row + 1);
177
+ heightMap.computeVertex(c1, col + 0, row + 0);
178
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
179
+
180
+ heightMap.computeVertex(a2, col + 1, row + 0);
181
+ heightMap.computeVertex(b2, col + 1, row + 1);
182
+ heightMap.computeVertex(c2, col + 0, row + 1);
183
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
184
+
185
+ // get normals
186
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
187
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
188
+
189
+ // get edge direction
190
+ edge.subtractVectors(b1, a1);
191
+ edge.normalize();
192
+
193
+ // check if edge is active
194
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b010 : 0b000;
195
+ activeEdge |= result;
196
+
197
+ // check if edge 2 is active (CA)
198
+
199
+ // if edge is an outer edge, it is active
200
+ if (isFirstColumn) {
201
+ activeEdge |= 0b100;
202
+ }
203
+
204
+ // if edge is an interior edge, get both triangles on either side of the edge
205
+ else {
206
+ // get triangles
207
+ heightMap.computeVertex(a1, col + 0, row + 1);
208
+ heightMap.computeVertex(b1, col + 0, row + 0);
209
+ heightMap.computeVertex(c1, col + 1, row + 0);
210
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
211
+
212
+ heightMap.computeVertex(a2, col + 0, row + 1);
213
+ heightMap.computeVertex(b2, col - 1, row + 1);
214
+ heightMap.computeVertex(c2, col + 0, row + 0);
215
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
216
+
217
+ // get normals
218
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
219
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
220
+
221
+ // get edge direction
222
+ edge.subtractVectors(b1, a1);
223
+ edge.normalize();
224
+
225
+ // check if edge is active
226
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b100 : 0b000;
227
+ activeEdge |= result;
228
+ }
229
+
230
+ // set active edges for triangle ABC
231
+ heightMap.activeEdges[firstTriangleIndex] = activeEdge;
232
+
233
+ // TRIANGLE DCB
234
+
235
+ activeEdge = 0b000;
236
+ result = 0b000;
237
+
238
+ // check if edge 0 is active (DC)
239
+
240
+ // if edge is an outer edge, it is active
241
+ if (isLastRow) {
242
+ activeEdge |= 0b001;
243
+ }
244
+
245
+ // if edge is an interior edge, get both triangles on either side of the edge
246
+ else {
247
+ // get triangles
248
+ heightMap.computeVertex(a1, col + 1, row + 1);
249
+ heightMap.computeVertex(b1, col + 0, row + 1);
250
+ heightMap.computeVertex(c1, col + 1, row + 0);
251
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
252
+
253
+ heightMap.computeVertex(a2, col + 1, row + 1);
254
+ heightMap.computeVertex(b2, col + 0, row + 2);
255
+ heightMap.computeVertex(c2, col + 0, row + 1);
256
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
257
+
258
+ // get normals
259
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
260
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
261
+
262
+ // get edge direction
263
+ edge.subtractVectors(b1, a1);
264
+ edge.normalize();
265
+
266
+ // check if edge is active
267
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b001 : 0b000;
268
+ activeEdge |= result;
269
+ }
270
+
271
+ // check if edge 1 is active (CB)
272
+
273
+ // this edge is always interior, get both triangles on either side of the edge
274
+
275
+ // get triangles
276
+ heightMap.computeVertex(a1, col + 0, row + 1);
277
+ heightMap.computeVertex(b1, col + 1, row + 0);
278
+ heightMap.computeVertex(c1, col + 1, row + 1);
279
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
280
+
281
+ heightMap.computeVertex(a2, col + 0, row + 1);
282
+ heightMap.computeVertex(b2, col + 0, row + 0);
283
+ heightMap.computeVertex(c2, col + 1, row + 0);
284
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
285
+
286
+ // get normals
287
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
288
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
289
+
290
+ // get edge direction
291
+ edge.subtractVectors(b1, a1);
292
+ edge.normalize();
293
+
294
+ // check if edge is active
295
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b010 : 0b000;
296
+ activeEdge |= result;
297
+
298
+ // check if edge 2 is active (BD)
299
+
300
+ // if edge is an outer edge, it is active
301
+ if (isLastColumn) {
302
+ activeEdge |= 0b100;
303
+ }
304
+
305
+ // if edge is an interior edge, get both triangles on either side of the edge
306
+ else {
307
+ // get triangles
308
+ heightMap.computeVertex(a1, col + 1, row + 0);
309
+ heightMap.computeVertex(b1, col + 1, row + 1);
310
+ heightMap.computeVertex(c1, col + 0, row + 1);
311
+ triangleLeftOfEdge.set({ a: a1, b: b1, c: c1 });
312
+
313
+ heightMap.computeVertex(a2, col + 1, row + 0);
314
+ heightMap.computeVertex(b2, col + 2, row + 0);
315
+ heightMap.computeVertex(c2, col + 1, row + 1);
316
+ triangleRightOfEdge.set({ a: a2, b: b2, c: c2 });
317
+
318
+ // get normals
319
+ triangleLeftOfEdge.computeNormal(normalLeftOfEdge);
320
+ triangleRightOfEdge.computeNormal(normalRightOfEdge);
321
+
322
+ // get edge direction
323
+ edge.subtractVectors(b1, a1);
324
+ edge.normalize();
325
+
326
+ // check if edge is active
327
+ result = isEdgeActive(normalLeftOfEdge, normalRightOfEdge, edge, cos5Degrees) ? 0b100 : 0b000;
328
+ activeEdge |= result;
329
+ }
330
+
331
+ // set active edges for triangle DCB
332
+ heightMap.activeEdges[secondTriangleIndex] = activeEdge;
333
+ }
334
+ }
335
+ }
336
+
337
+ buildHeightMap(
338
+ vertexCount: { width: number; depth: number },
339
+ extents: InputType<Vec3>,
340
+ heights: Float32Array | Float64Array | number[],
341
+ heightMapPool?: typeof HeightMap.Pool
342
+ ): HeightMap {
343
+ if (vertexCount.width < 2 || vertexCount.depth < 2) {
344
+ throw new Error(`vertexCount must be at least 2x2: ${vertexCount.width}x${vertexCount.depth}`);
345
+ }
346
+
347
+ if (vertexCount.width !== vertexCount.depth) {
348
+ throw new Error(`non-square heightmaps are not yet supported: ${vertexCount.width}x${vertexCount.depth}`);
349
+ }
350
+
351
+ const subdivisionsCount = vertexCount.width - 1; // number of subdivisions is one less than the number of vertices
352
+ if (!isPowerOf2(subdivisionsCount)) {
353
+ const { previous, next } = computeClosestPowersOfTwo(subdivisionsCount);
354
+ throw new Error(
355
+ `non-power-of-two sub-divisions are not yet supported. the closest valid vertex counts are: [${previous + 1}, ${
356
+ next + 1
357
+ }]`
358
+ );
359
+ }
360
+
361
+ if (heights.length !== vertexCount.width * vertexCount.depth) {
362
+ throw new Error(
363
+ `heights length must match vertex count: ${vertexCount.width * vertexCount.depth}, got ${
364
+ heights.length
365
+ } instead`
366
+ );
367
+ }
368
+
369
+ for (let i = 0; i < heights.length; i++) {
370
+ const height = heights[i];
371
+ if (height < 0.0 || height > 1.0) {
372
+ throw new Error(`heights must be normalized to the range [0, 1], got ${height} at index ${i} instead`);
373
+ }
374
+ }
375
+
376
+ const numberOfQuads = subdivisionsCount ** 2;
377
+ const numberOfTriangles = numberOfQuads * 2;
378
+ const numberOfCells = computeNumberOfCells(subdivisionsCount);
379
+
380
+ actualExtents.fromArray([1, 1, 1]);
381
+ actualExtents.set(extents);
382
+
383
+ scale.x = actualExtents.x / subdivisionsCount;
384
+ scale.y = actualExtents.y;
385
+ scale.z = actualExtents.z / subdivisionsCount;
386
+
387
+ positionOffset.x = -actualExtents.x * 0.5;
388
+ positionOffset.y = 0.0;
389
+ positionOffset.z = -actualExtents.z * 0.5;
390
+
391
+ // 2. allocate the heightMap
392
+ const heightMap = HeightMap.create({
393
+ subdivisionsCount: subdivisionsCount,
394
+ scale: scale,
395
+ positionOffset: positionOffset,
396
+ }, heightMapPool);
397
+ heightMap.heights = Array.from(heights);
398
+ heightMap.activeEdges = new Array(numberOfTriangles).fill(0);
399
+ heightMap.minHeights = new Array(numberOfCells).fill(Number.POSITIVE_INFINITY);
400
+ heightMap.maxHeights = new Array(numberOfCells).fill(Number.NEGATIVE_INFINITY);
401
+ heightMap.levels = new Array(numberOfCells).fill(0);
402
+
403
+ // 3. build the quadtree and active edges
404
+ this.buildActiveEdges(heightMap);
405
+ this.buildHeightMapQuadtree(heightMap);
406
+
407
+ updateShape(heightMap);
408
+ return heightMap;
409
+ }
410
+ }
411
+
412
+ const scale = /*@__PURE__*/ Vec3.create();
413
+ const positionOffset = /*@__PURE__*/ Vec3.create();
414
+ const actualExtents = /*@__PURE__*/ Vec3.create();
@@ -0,0 +1,92 @@
1
+ import { Vec3 } from "../math/vec3";
2
+ import { TriangleMeshBvhTreeOptions, TriangleMeshBvhTree, TriangleMeshBvhNode } from "../physics/broadphase/TriangleMeshBvhTree";
3
+ import { Triangle } from "../shape/Triangle";
4
+ import { TriangleMesh, updateShape } from "../shape/TriangleMesh";
5
+ import { ConvexHullBuilder } from "./ConvexHullBuilder";
6
+
7
+ export interface BuildTriangleMeshParams {
8
+ vertexPositions: Float32Array;
9
+ faceIndices: Uint32Array;
10
+ bvhOptions: TriangleMeshBvhTreeOptions;
11
+ }
12
+
13
+ export class TriangleMeshBuilder {
14
+ convexHullBuilder: ConvexHullBuilder;
15
+
16
+ constructor() {
17
+ this.convexHullBuilder = new ConvexHullBuilder();
18
+ }
19
+
20
+ build(
21
+ params: BuildTriangleMeshParams,
22
+ triangleMeshPool?: typeof TriangleMesh.Pool,
23
+ bvhNodePool?: typeof TriangleMeshBvhNode.Pool,
24
+ bvhTreePool?: typeof TriangleMeshBvhTree.Pool,
25
+ vertexPositionPool?: typeof Vec3.Pool,
26
+ faceIndexPool?: typeof Vec3.Pool,
27
+ trianglePool?: typeof Triangle.Pool
28
+ ) {
29
+ const vertexPositions = vertexPositionPool || new Vec3.Pool(params.vertexPositions.length / 3);
30
+ const faceIndices = faceIndexPool || new Vec3.Pool(params.faceIndices.length / 3);
31
+ const triangles = trianglePool || new Triangle.Pool(params.faceIndices.length / 3);
32
+ const mesh = TriangleMesh.create({
33
+ vertexPositions: { pool: vertexPositions },
34
+ faceIndices: { pool: faceIndices },
35
+ triangles: { pool: triangles },
36
+ }, triangleMeshPool);
37
+
38
+ for (let i = 0; i < params.vertexPositions.length; i += 3) {
39
+ mesh.vertexPositions.create(
40
+ {
41
+ x: params.vertexPositions[i + 0],
42
+ y: params.vertexPositions[i + 1],
43
+ z: params.vertexPositions[i + 2],
44
+ },
45
+ );
46
+ }
47
+
48
+ for (let i = 0; i < params.faceIndices.length; i += 3) {
49
+ mesh.faceIndices.create(
50
+ {
51
+ x: params.faceIndices[i + 0],
52
+ y: params.faceIndices[i + 1],
53
+ z: params.faceIndices[i + 2],
54
+ },
55
+ );
56
+ }
57
+
58
+ let subShapeId = 0;
59
+ for (const face of mesh.faceIndices!) {
60
+ const triangle = mesh.triangles.create(
61
+ {
62
+ a: mesh.vertexPositions.getAtIndex(face.x)!,
63
+ b: mesh.vertexPositions.getAtIndex(face.y)!,
64
+ c: mesh.vertexPositions.getAtIndex(face.z)!,
65
+ },
66
+ );
67
+ triangle.computeLocalBounds(triangle.computedBounds);
68
+ triangle.computeNormal(triangle.normal);
69
+ triangle.subShapeId = subShapeId++;
70
+ triangle.activeEdges = 0b111;
71
+ }
72
+
73
+ updateShape(mesh);
74
+
75
+ const nodes = bvhNodePool || new TriangleMeshBvhNode.Pool();
76
+ bvhTreePool = bvhTreePool || new TriangleMeshBvhTree.Pool();
77
+ mesh.bvh = TriangleMeshBvhTree.create({
78
+ nodes: { pool: nodes },
79
+ }, bvhTreePool);
80
+
81
+ mesh.bvh.build({
82
+ mesh: mesh,
83
+ bvhTreeOptions: params.bvhOptions,
84
+ }, triangles);
85
+
86
+ const hull = this.convexHullBuilder.buildFromPoints(params.vertexPositions, 0.0, 1e-3);
87
+ mesh.computedVolume = hull.computedVolume;
88
+ mesh.inertia.copy(hull.inertia);
89
+
90
+ return mesh;
91
+ }
92
+ }
@@ -0,0 +1,184 @@
1
+ import { Isometry } from "../math/isometry";
2
+ import { Shape, ShapeType } from "../shape/Shape";
3
+ import { Vec3 } from "../math/vec3";
4
+ import { castConvexVsConvex } from "./cast/castConvexVsConvex";
5
+ import { PenetrationDepthModule } from "./gjk/PenetrationDepthModule";
6
+ import { castCompoundVsConvex } from "./cast/castCompoundVsConvex";
7
+ import { castConvexVsCompound } from "./cast/castConvexVsCompound";
8
+ import { CastCollector, CastSettings, RayCastSettings } from "./cast/cast";
9
+ import { Body } from "../physics/Body";
10
+ import { castConvexVsHeightMap } from "./cast/castConvexVsHeightMap";
11
+ import { castConvexVsTriangleMesh } from "./cast/castConvexVsTriangleMesh";
12
+ import { Ray } from "../shape/Ray";
13
+ import { castRayVsConvex } from "./cast/castRayVsConvex";
14
+ import { castRayVsCompound } from "./cast/castRayVsCompound";
15
+ import { castRayVsTriangleMesh } from "./cast/castRayVsTriangleMesh";
16
+ import { castRayVsHeightMap } from "./cast/castRayVsHeightMap";
17
+
18
+ const castShapesFns = {
19
+ [ShapeType.sphere]: {
20
+ [ShapeType.box]: castConvexVsConvex,
21
+ [ShapeType.capsule]: castConvexVsConvex,
22
+ [ShapeType.compoundShape]: castConvexVsCompound,
23
+ [ShapeType.convexHull]: castConvexVsConvex,
24
+ [ShapeType.cylinder]: castConvexVsConvex,
25
+ [ShapeType.heightMap]: castConvexVsHeightMap,
26
+ [ShapeType.sphere]: castConvexVsConvex,
27
+ [ShapeType.triangleMesh]: castConvexVsTriangleMesh,
28
+ },
29
+ [ShapeType.box]: {
30
+ [ShapeType.box]: castConvexVsConvex,
31
+ [ShapeType.capsule]: castConvexVsConvex,
32
+ [ShapeType.compoundShape]: castConvexVsCompound,
33
+ [ShapeType.convexHull]: castConvexVsConvex,
34
+ [ShapeType.cylinder]: castConvexVsConvex,
35
+ [ShapeType.heightMap]: castConvexVsHeightMap,
36
+ [ShapeType.sphere]: castConvexVsConvex,
37
+ [ShapeType.triangleMesh]: castConvexVsTriangleMesh,
38
+ },
39
+ [ShapeType.capsule]: {
40
+ [ShapeType.box]: castConvexVsConvex,
41
+ [ShapeType.capsule]: castConvexVsConvex,
42
+ [ShapeType.compoundShape]: castConvexVsCompound,
43
+ [ShapeType.convexHull]: castConvexVsConvex,
44
+ [ShapeType.cylinder]: castConvexVsConvex,
45
+ [ShapeType.heightMap]: castConvexVsHeightMap,
46
+ [ShapeType.sphere]: castConvexVsConvex,
47
+ [ShapeType.triangleMesh]: castConvexVsTriangleMesh,
48
+ },
49
+ [ShapeType.cylinder]: {
50
+ [ShapeType.box]: castConvexVsConvex,
51
+ [ShapeType.capsule]: castConvexVsConvex,
52
+ [ShapeType.compoundShape]: castConvexVsCompound,
53
+ [ShapeType.convexHull]: castConvexVsConvex,
54
+ [ShapeType.cylinder]: castConvexVsConvex,
55
+ [ShapeType.heightMap]: castConvexVsHeightMap,
56
+ [ShapeType.sphere]: castConvexVsConvex,
57
+ [ShapeType.triangleMesh]: castConvexVsTriangleMesh,
58
+ },
59
+ [ShapeType.compoundShape]: {
60
+ [ShapeType.box]: castCompoundVsConvex,
61
+ [ShapeType.capsule]: castCompoundVsConvex,
62
+ // [ShapeType.compoundShape]: castCompoundVsCompound,
63
+ [ShapeType.convexHull]: castCompoundVsConvex,
64
+ [ShapeType.cylinder]: castCompoundVsConvex,
65
+ // [ShapeType.heightMap]: castCompoundVsHeightMap,
66
+ [ShapeType.sphere]: castCompoundVsConvex,
67
+ // [ShapeType.triangleMesh]: castCompoundVsTriangleMesh,
68
+ },
69
+ // [ShapeType.heightMap]: {
70
+ // [ShapeType.box]: castHeightMapVsConvex,
71
+ // [ShapeType.capsule]: castHeightMapVsConvex,
72
+ // [ShapeType.compoundShape]: castHeightMapVsCompound,
73
+ // [ShapeType.convexHull]: castHeightMapVsConvex,
74
+ // [ShapeType.cylinder]: castHeightMapVsConvex,
75
+ // [ShapeType.heightMap]: castHeightMapVsHeightMap,
76
+ // [ShapeType.sphere]: castHeightMapVsConvex,
77
+ // [ShapeType.triangleMesh]: castHeightMapVsTriangleMesh,
78
+ // },
79
+ // [ShapeType.triangleMesh]: {
80
+ // [ShapeType.box]: castTriangleMeshVsConvex,
81
+ // [ShapeType.capsule]: castTriangleMeshVsConvex,
82
+ // [ShapeType.compoundShape]: castTriangleMeshVsCompound,
83
+ // [ShapeType.convexHull]: castTriangleMeshVsConvex,
84
+ // [ShapeType.cylinder]: castTriangleMeshVsConvex,
85
+ // [ShapeType.heightMap]: castTriangleMeshVsHeightMap,
86
+ // [ShapeType.sphere]: castTriangleMeshVsConvex,
87
+ // [ShapeType.triangleMesh]: castTriangleMeshVsTriangleMesh,
88
+ // },
89
+ // [ShapeType.triangle]: {
90
+ // [ShapeType.box]: castTriangleVsConvex,
91
+ // [ShapeType.sphere]: castTriangleVsConvex,
92
+ // },
93
+ } as const;
94
+
95
+ const raycastFns = {
96
+ [ShapeType.sphere]: castRayVsConvex,
97
+ [ShapeType.box]: castRayVsConvex,
98
+ [ShapeType.capsule]: castRayVsConvex,
99
+ [ShapeType.cylinder]: castRayVsConvex,
100
+ [ShapeType.compoundShape]: castRayVsCompound, // castRayVsCompound,
101
+ [ShapeType.convexHull]: castRayVsConvex,
102
+ [ShapeType.heightMap]: castRayVsHeightMap, // castRayVsHeightMap,
103
+ [ShapeType.triangleMesh]: castRayVsTriangleMesh, // castRayVsTriangleMesh,
104
+ };
105
+
106
+ export class CastShapesModule {
107
+ penetrationDepthModule: PenetrationDepthModule;
108
+
109
+ constructor(penetrationDepthModule: PenetrationDepthModule) {
110
+ this.penetrationDepthModule = penetrationDepthModule;
111
+ }
112
+
113
+ castRay(
114
+ collector: CastCollector,
115
+ ray: Ray,
116
+ shapeB: Shape,
117
+ isometryB: Isometry,
118
+ scaleB: number,
119
+ offset: Vec3,
120
+ settings: RayCastSettings,
121
+ bodyB: Body | null = null
122
+ ) {
123
+ const fn = raycastFns[shapeB.type as keyof typeof raycastFns];
124
+ if (!fn) {
125
+ return;
126
+ }
127
+ return fn(
128
+ this.penetrationDepthModule,
129
+ collector,
130
+ ray,
131
+ // @ts-ignore
132
+ shapeB,
133
+ isometryB,
134
+ scaleB,
135
+ offset,
136
+ settings,
137
+ bodyB
138
+ );
139
+ }
140
+
141
+ // casts two shapes that implement the ShapeWithType interface, using the query map
142
+ // the shapes must be in local space
143
+ // the isometries must transform the shapes into world space
144
+ // the results are in world space
145
+ castShapes(
146
+ collector: CastCollector,
147
+ shapeA: Shape,
148
+ isometryA: Isometry,
149
+ scaleA: number,
150
+ shapeB: Shape,
151
+ isometryB: Isometry,
152
+ scaleB: number,
153
+ displacement: Vec3,
154
+ offset: Vec3,
155
+ settings: CastSettings,
156
+ bodyA: Body | null = null,
157
+ bodyB: Body | null = null
158
+ ): void {
159
+ const keyA = shapeA.type as keyof typeof castShapesFns;
160
+ const keyB = shapeB.type as keyof (typeof castShapesFns)[keyof typeof castShapesFns];
161
+ const castFunction = castShapesFns[keyA][keyB];
162
+ if (!castFunction) {
163
+ throw new Error(`no cast function found for shapes with type ${keyA} and ${keyB}`);
164
+ }
165
+
166
+ return castFunction(
167
+ this.penetrationDepthModule,
168
+ collector,
169
+ // @ts-ignore
170
+ shapeA,
171
+ shapeB,
172
+ isometryA,
173
+ isometryB,
174
+ scaleA,
175
+ scaleB,
176
+ displacement,
177
+ offset,
178
+ settings,
179
+ undefined,
180
+ bodyA,
181
+ bodyB
182
+ );
183
+ }
184
+ }