@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,196 @@
1
+ import { assert } from "../../helpers";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { SupportShape } from "../../shape/Convex";
4
+ import { MprResult, MprStatus } from "../mpr/mpr";
5
+ import { MinkowskiDifference } from "./MinkowskiDifference";
6
+ import { finalizeImpResult } from "./finalizeImpResult";
7
+ import { ImpResult, ImpStatus } from "./imp";
8
+ import { incrementalMinimumDistanceExploreDirection } from "./incrementalMinimumDistanceExploreDirection";
9
+
10
+ const support = /*@__PURE__*/ MinkowskiDifference.create();
11
+ const d = /*@__PURE__*/ Vec3.create();
12
+ const v1 = /*@__PURE__*/ Vec3.create();
13
+ const v2 = /*@__PURE__*/ Vec3.create();
14
+ const v3 = /*@__PURE__*/ Vec3.create();
15
+ const v1_dir_in_support = /*@__PURE__*/ Vec3.create();
16
+ const v2_dir_in_support = /*@__PURE__*/ Vec3.create();
17
+ const v3_dir_in_support = /*@__PURE__*/ Vec3.create();
18
+ const new_d = /*@__PURE__*/ Vec3.create();
19
+ const prev_direction = /*@__PURE__*/ Vec3.create();
20
+ const direction_difference = /*@__PURE__*/ Vec3.create();
21
+ const prev_bounds: [number, number] = [0, 0];
22
+ const bounds: [number, number] = [0, 0];
23
+ const mprResult = /*@__PURE__*/ new MprResult();
24
+
25
+ export function findContactImp(
26
+ refine_data: ImpResult,
27
+ shapeA: SupportShape,
28
+ shapeB: SupportShape,
29
+ init_direction: Vec3,
30
+ max_iteration: number,
31
+ tolerance: number,
32
+ return_on_subroutine_converge: boolean
33
+ ): void {
34
+ // resetMprResult(mprResult);
35
+ d.copy(init_direction);
36
+ // #v-ifdef DEV
37
+ assert(d.isUnitVector(), "d must be a unit vector");
38
+ // #v-endif
39
+
40
+ // The main loop
41
+ prev_direction.copy(init_direction);
42
+ prev_bounds[0] = -1;
43
+ prev_bounds[1] = -1;
44
+ for (let outer_iteration = 0; outer_iteration < max_iteration; outer_iteration++) {
45
+ // Invoke subroutine
46
+ bounds[0] = 0;
47
+ bounds[1] = 0;
48
+ const portal_valid = outer_iteration >= 1;
49
+ incrementalMinimumDistanceExploreDirection(
50
+ mprResult,
51
+ shapeA,
52
+ shapeB,
53
+ d,
54
+ v1,
55
+ v2,
56
+ v3,
57
+ v1_dir_in_support,
58
+ v2_dir_in_support,
59
+ v3_dir_in_support,
60
+ bounds,
61
+ new_d,
62
+ portal_valid,
63
+ max_iteration,
64
+ tolerance
65
+ );
66
+
67
+ // Update the output
68
+ switch (mprResult.status) {
69
+ case MprStatus.NewDirection: {
70
+ d.copy(new_d);
71
+ break;
72
+ }
73
+
74
+ case MprStatus.SubroutineConverge: {
75
+ // directly return without further checking
76
+ if (return_on_subroutine_converge) {
77
+ finalizeImpResult(
78
+ refine_data,
79
+ shapeA,
80
+ shapeB,
81
+ d,
82
+ v1,
83
+ v2,
84
+ v3,
85
+ v1_dir_in_support,
86
+ v2_dir_in_support,
87
+ v3_dir_in_support,
88
+ new_d,
89
+ bounds
90
+ );
91
+ refine_data.status = ImpStatus.OK;
92
+ return;
93
+ }
94
+
95
+ // Prev-data is valid, use that to check the convergence
96
+ if (outer_iteration >= 1) {
97
+ // #v-ifdef DEV
98
+ assert(prev_bounds[0] >= 0, "prev_bounds[0] must be valid");
99
+ assert(prev_bounds[1] >= 0, "prev_bounds[1] must be valid");
100
+ // #v-endif
101
+ if (Math.abs(bounds[1] - prev_bounds[1]) <= tolerance) {
102
+ // Done as not much improvement
103
+ finalizeImpResult(
104
+ refine_data,
105
+ shapeA,
106
+ shapeB,
107
+ d,
108
+ v1,
109
+ v2,
110
+ v3,
111
+ v1_dir_in_support,
112
+ v2_dir_in_support,
113
+ v3_dir_in_support,
114
+ new_d,
115
+ bounds
116
+ );
117
+ refine_data.status = ImpStatus.OK;
118
+ return;
119
+ }
120
+ }
121
+
122
+ // Check direction
123
+ const check_direction_termination = true;
124
+ const direction_tolerance = 1e-3;
125
+ direction_difference.subtractVectors(new_d, prev_direction);
126
+ if (check_direction_termination && direction_difference.length() < direction_tolerance) {
127
+ finalizeImpResult(
128
+ refine_data,
129
+ shapeA,
130
+ shapeB,
131
+ d,
132
+ v1,
133
+ v2,
134
+ v3,
135
+ v1_dir_in_support,
136
+ v2_dir_in_support,
137
+ v3_dir_in_support,
138
+ new_d,
139
+ bounds
140
+ );
141
+ refine_data.status = ImpStatus.OK;
142
+ return;
143
+ }
144
+
145
+ // Move to next, as the subroutine converge
146
+ // The portal can be rather small here
147
+ // portal_valid = false;
148
+ d.copy(new_d);
149
+ break;
150
+ }
151
+
152
+ case MprStatus.Degenerated: {
153
+ // Done on degenerate
154
+ refine_data.minimum_penetration = bounds[1];
155
+ refine_data.penetration_direction.copy(new_d);
156
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, new_d);
157
+ refine_data.worldContactPointA.copy(support.pointA);
158
+ refine_data.worldContactPointB.copy(support.pointB);
159
+ refine_data.status = ImpStatus.OK;
160
+ return;
161
+ }
162
+
163
+ case MprStatus.FailedNoIntersect: {
164
+ refine_data.status = ImpStatus.FailedNoIntersect;
165
+ return;
166
+ }
167
+
168
+ default:
169
+ refine_data.status = ImpStatus.Failed;
170
+ return;
171
+ }
172
+
173
+ // Move to next iteration, book keeping
174
+ prev_direction.copy(new_d);
175
+ prev_bounds[0] = bounds[0];
176
+ prev_bounds[1] = bounds[1];
177
+ }
178
+
179
+ // Too many iterations
180
+ finalizeImpResult(
181
+ refine_data,
182
+ shapeA,
183
+ shapeB,
184
+ d,
185
+ v1,
186
+ v2,
187
+ v3,
188
+ v1_dir_in_support,
189
+ v2_dir_in_support,
190
+ v3_dir_in_support,
191
+ prev_direction,
192
+ prev_bounds
193
+ );
194
+ refine_data.status = ImpStatus.IterationLimit;
195
+ return;
196
+ }
@@ -0,0 +1,28 @@
1
+ import { createClass, MonomorphType, NumberType, PropertyDefinition, PropertyDefinitionMap } from "monomorph";
2
+ import { Vec3 } from "../../math/vec3";
3
+
4
+ export const enum ImpStatus {
5
+ OK,
6
+ Failed,
7
+ FailedNoIntersect,
8
+ DirectedPenetrationFailed,
9
+ IterationLimit,
10
+ }
11
+
12
+ const impResultProps = {
13
+ status: NumberType(ImpStatus.Failed) as PropertyDefinition<ImpStatus, true>,
14
+ minimum_penetration: NumberType(0.0),
15
+ penetration_direction: MonomorphType(Vec3),
16
+ worldContactPointA: MonomorphType(Vec3),
17
+ worldContactPointB: MonomorphType(Vec3),
18
+ } as const satisfies PropertyDefinitionMap;
19
+
20
+ export class ImpResult extends createClass<ImpResult, typeof impResultProps>(impResultProps) {
21
+ reset() {
22
+ this.status = ImpStatus.Failed;
23
+ this.minimum_penetration = 0;
24
+ this.penetration_direction.zero();
25
+ this.worldContactPointA.zero();
26
+ this.worldContactPointB.zero();
27
+ }
28
+ }
@@ -0,0 +1,207 @@
1
+ import { assert } from "../../helpers";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { SupportShape } from "../../shape/Convex";
4
+ import { findPortal } from "../mpr/findPortal";
5
+ import { FindPortalResult, FindPortalStatus, MprResult, MprStatus } from "../mpr/mpr";
6
+ import { updatePortal } from "../mpr/updatePortal";
7
+ import { MinkowskiDifference } from "./MinkowskiDifference";
8
+ import { computeExploredDistanceLowerUpperBound } from "./computeExploredDistanceLowerUpperBound";
9
+
10
+ const support = /*@__PURE__*/ MinkowskiDifference.create();
11
+ const d = /*@__PURE__*/ Vec3.create();
12
+ const v0 = /*@__PURE__*/ Vec3.create();
13
+ const v0_scaled = /*@__PURE__*/ Vec3.create();
14
+ const init_support_dir = /*@__PURE__*/ Vec3.create();
15
+ const init_support = /*@__PURE__*/ Vec3.create();
16
+ const v123_normal = /*@__PURE__*/ Vec3.create();
17
+ const v4 = /*@__PURE__*/ Vec3.create();
18
+ const v12 = /*@__PURE__*/ Vec3.create();
19
+ const v13 = /*@__PURE__*/ Vec3.create();
20
+
21
+ const findPortalResult = /*@__PURE__*/ new FindPortalResult();
22
+
23
+ export function incrementalMinimumDistanceExploreDirection(
24
+ result: MprResult,
25
+ shapeA: SupportShape,
26
+ shapeB: SupportShape,
27
+ direction_to_explore: Vec3,
28
+ v1: Vec3,
29
+ v2: Vec3,
30
+ v3: Vec3,
31
+ v1_dir_in_support: Vec3,
32
+ v2_dir_in_support: Vec3,
33
+ v3_dir_in_support: Vec3,
34
+ bounds: [number, number],
35
+ new_direction: Vec3,
36
+ v123_valid: boolean,
37
+ max_iterations: number,
38
+ tolerance: number
39
+ ): void {
40
+ // resetFindPortalResult(findPortalResult);
41
+
42
+ d.normalizeVector(direction_to_explore);
43
+ v0.negateVector(d);
44
+
45
+ // Compute the upper bound
46
+ init_support_dir.copy(d);
47
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, init_support_dir);
48
+ init_support.copy(support.point);
49
+
50
+ // No intersect case
51
+ if (support.isOriginBeyondSupportPoint) {
52
+ result.status = MprStatus.FailedNoIntersect;
53
+ return;
54
+ }
55
+
56
+ // Do we need to re-init v123
57
+ const reinit_v123 = !v123_valid;
58
+
59
+ // Init v1, v2 and v3
60
+ if (reinit_v123) {
61
+ v1_dir_in_support.copy(d);
62
+ v1.copy(init_support);
63
+
64
+ v2_dir_in_support.crossVectors(v0, v1);
65
+ v2_dir_in_support.normalizeVector(v2_dir_in_support);
66
+ // o_to_v0 and o_to_v1 can be co-linear, check it
67
+ // Note that v0 MUST have norm one
68
+ if (v2_dir_in_support.absNorm() <= v1.absNorm() * tolerance) {
69
+ // Refer to the note in RunIntersect
70
+ bounds[0] = v1.dot(d);
71
+ bounds[1] = bounds[0];
72
+ new_direction.copy(d);
73
+ result.status = MprStatus.Degenerated;
74
+ return;
75
+ }
76
+
77
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v2_dir_in_support);
78
+ v2.copy(support.point);
79
+ if (support.isOriginBeyondSupportPoint) {
80
+ result.status = MprStatus.FailedNoIntersect;
81
+ return;
82
+ }
83
+
84
+ // it is better to form portal faces to be oriented "outside" origin
85
+ // Here O must be an interior point in penetration query
86
+ v3_dir_in_support.crossVectors(v1, v2);
87
+ v3_dir_in_support.normalizeVector(v3_dir_in_support);
88
+ if (v3_dir_in_support.dot(v0) < 0) {
89
+ // swap v1/v2
90
+ v1.swap(v2);
91
+ v1_dir_in_support.swap(v2_dir_in_support);
92
+ v3_dir_in_support.negateVector(v3_dir_in_support);
93
+ }
94
+
95
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v3_dir_in_support);
96
+ v3.copy(support.point);
97
+ if (support.isOriginBeyondSupportPoint) {
98
+ result.status = MprStatus.FailedNoIntersect;
99
+ return;
100
+ }
101
+ }
102
+
103
+ // Scale the v0 to the max of v1/v2/v3
104
+ // #v-ifdef DEV
105
+ assert(v0.isUnitVector(), "v0 must be a unit vector");
106
+ // #v-endif
107
+ v0_scaled.copy(v0);
108
+ const v1_squared_norm = v1.squaredLength();
109
+ const v2_squared_norm = v2.squaredLength();
110
+ const v3_squared_norm = v3.squaredLength();
111
+ const max_squared_norm = Math.max(v1_squared_norm, v2_squared_norm, v3_squared_norm);
112
+ v0_scaled.scaleVector(v0_scaled, Math.sqrt(max_squared_norm));
113
+
114
+ // the loop to find the portal
115
+ findPortal(
116
+ findPortalResult,
117
+ shapeA,
118
+ shapeB,
119
+ v0_scaled,
120
+ v1,
121
+ v2,
122
+ v3,
123
+ v1_dir_in_support,
124
+ v2_dir_in_support,
125
+ v3_dir_in_support,
126
+ max_iterations
127
+ );
128
+
129
+ switch (findPortalResult.status) {
130
+ case FindPortalStatus.IterationLimit: {
131
+ result.status = MprStatus.Failed;
132
+ return;
133
+ }
134
+
135
+ case FindPortalStatus.DetectSeperated: {
136
+ result.status = MprStatus.FailedNoIntersect;
137
+ return;
138
+ }
139
+
140
+ default: {
141
+ // #v-ifdef DEV
142
+ assert(findPortalResult.status === FindPortalStatus.PortalFound, "Expecting portal found");
143
+ // #v-endif
144
+ }
145
+ }
146
+
147
+ // portal refinement
148
+ let portal_refinement_iteration = 0;
149
+ while (portal_refinement_iteration < max_iterations) {
150
+ // Update iteration data
151
+ portal_refinement_iteration++;
152
+
153
+ // Compute the normal
154
+ // The v123_normal must be oriented in the same side with O
155
+ v12.subtractVectors(v2, v1);
156
+ v13.subtractVectors(v3, v1);
157
+ v123_normal.crossVectors(v12, v13);
158
+ v123_normal.normalizeVector(v123_normal);
159
+ if (v123_normal.dot(d) < 0) {
160
+ v2.swap(v3);
161
+ v2_dir_in_support.swap(v3_dir_in_support);
162
+ v123_normal.negateVector(v123_normal);
163
+ }
164
+
165
+ // A new point v4 on that direction
166
+ // v123_normal would be normalized in computeShapeSupport
167
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v123_normal);
168
+ v4.copy(support.point);
169
+ const distance_ub_on_new_supporting_plane = v4.dot(v123_normal);
170
+ // #v-ifdef DEV
171
+ assert(v123_normal.isUnitVector(), "Expecting v123_normal to be unit vector");
172
+ // #v-endif
173
+ if (support.isOriginBeyondSupportPoint) {
174
+ result.status = MprStatus.FailedNoIntersect;
175
+ return;
176
+ }
177
+
178
+ // Compute some distance
179
+ const v1_dot_v123_normal = v1.dot(v123_normal);
180
+ const v4_dot_v123_normal = v4.dot(v123_normal);
181
+ const v1_v4_distance_on_n123 = v4_dot_v123_normal - v1_dot_v123_normal;
182
+ computeExploredDistanceLowerUpperBound(bounds, d, v1, v2, v3, v123_normal, v1_dot_v123_normal, v4_dot_v123_normal);
183
+
184
+ // compute the shortcut
185
+ if (bounds[0] > distance_ub_on_new_supporting_plane + tolerance) {
186
+ new_direction.copy(v123_normal);
187
+ result.status = MprStatus.NewDirection;
188
+ return;
189
+ }
190
+
191
+ // Separation plane very close to the new (candidate) portal
192
+ // Note that v123_normal is normalized
193
+ // We must should stop the subroutine
194
+ if (Math.abs(v1_v4_distance_on_n123) <= tolerance) {
195
+ new_direction.copy(v123_normal);
196
+ result.status = MprStatus.SubroutineConverge;
197
+ return;
198
+ }
199
+
200
+ // update the portal
201
+ updatePortal(v0_scaled, v4, v123_normal, v1, v2, v3, v1_dir_in_support, v2_dir_in_support, v3_dir_in_support);
202
+ }
203
+
204
+ // Failure
205
+ result.status = MprStatus.Failed;
206
+ return;
207
+ }
@@ -0,0 +1,152 @@
1
+ import { assert } from "../../helpers";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { SupportShape } from "../../shape/Convex";
4
+ import { MinkowskiDifference } from "../imp/MinkowskiDifference";
5
+ import { FindPortalResult, FindPortalStatus } from "./mpr";
6
+
7
+ const dot_eps_ratio = Number.EPSILON; // TODO: this may not be a 1-to-1 substitute for the c++ code: `static constexpr T dot_eps_ratio = std::numeric_limits<T>::epsilon();`
8
+
9
+ const support = /*@__PURE__*/ MinkowskiDifference.create();
10
+ const v0_to_v1 = /*@__PURE__*/ Vec3.create();
11
+ const v0_to_v2 = /*@__PURE__*/ Vec3.create();
12
+ const v0_to_v3 = /*@__PURE__*/ Vec3.create();
13
+ const v031_normal = /*@__PURE__*/ Vec3.create();
14
+ const v012_normal = /*@__PURE__*/ Vec3.create();
15
+ const v023_normal = /*@__PURE__*/ Vec3.create();
16
+ const search_v2_dir = /*@__PURE__*/ Vec3.create();
17
+ const search_v3_dir = /*@__PURE__*/ Vec3.create();
18
+ const search_v1_dir = /*@__PURE__*/ Vec3.create();
19
+
20
+ export function findPortal(
21
+ result: FindPortalResult,
22
+ shapeA: SupportShape,
23
+ shapeB: SupportShape,
24
+ v0: Vec3,
25
+ v1: Vec3,
26
+ v2: Vec3,
27
+ v3: Vec3,
28
+ v1_dir_in_support: Vec3,
29
+ v2_dir_in_support: Vec3,
30
+ v3_dir_in_support: Vec3,
31
+ max_iterations: number
32
+ ): void {
33
+ const v0_abs_norm = v0.absNorm();
34
+ let find_candidate_portal_iteration = 0;
35
+
36
+ // The actual loop
37
+ while (true) {
38
+ if (find_candidate_portal_iteration >= max_iterations) {
39
+ result.status = FindPortalStatus.IterationLimit;
40
+ return;
41
+ }
42
+
43
+ // update iteration data
44
+ find_candidate_portal_iteration++;
45
+ v0_to_v1.subtractVectors(v1, v0);
46
+ v0_to_v2.subtractVectors(v2, v0);
47
+ v0_to_v3.subtractVectors(v3, v0);
48
+
49
+ // update the corresponded vertex
50
+ // these normal are not oriented
51
+ v031_normal.crossVectors(v0_to_v3, v0_to_v1);
52
+ v012_normal.crossVectors(v0_to_v1, v0_to_v2);
53
+ const signed_volume = v0_to_v2.dot(v031_normal);
54
+
55
+ // orient it
56
+ if (signed_volume < 0) {
57
+ v2.swap(v3);
58
+ v2_dir_in_support.swap(v3_dir_in_support);
59
+ v0_to_v2.swap(v0_to_v3);
60
+
61
+ // something tricky here, note the changing of vectors
62
+ v012_normal.swap(v031_normal);
63
+ v031_normal.negateVector(v031_normal);
64
+ v012_normal.negateVector(v012_normal);
65
+ }
66
+
67
+ // case 031
68
+ // #v-ifdef DEV
69
+ assert(v0_to_v2.dot(v031_normal) >= 0, "v0_to_v2 and v031_normal should be in the same direction");
70
+ // #v-endif
71
+ const v031_seperated_v2_and_o = v0.dot(v031_normal) > dot_eps_ratio * v0_abs_norm * v031_normal.absNorm();
72
+
73
+ if (v031_seperated_v2_and_o) {
74
+ // Orient the normal towards O
75
+ // #v-ifdef DEV
76
+ assert(v0.dot(v031_normal) > 0, "o_to_v0 and v031_normal should be in the same direction");
77
+ // #v-endif
78
+ search_v2_dir.negateVector(v031_normal);
79
+
80
+ // Find a new v2 in that direction
81
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v2_dir);
82
+ v2.copy(support.point);
83
+ v2_dir_in_support.copy(search_v2_dir);
84
+
85
+ // Miss detection
86
+ if (support.isOriginBeyondSupportPoint) {
87
+ result.status = FindPortalStatus.DetectSeperated;
88
+ return;
89
+ }
90
+
91
+ continue;
92
+ }
93
+
94
+ // case 012
95
+ // #v-ifdef DEV
96
+ assert(v0_to_v3.dot(v012_normal) >= 0, "v0_to_v3 and v012_normal should be in the same direction");
97
+ // #v-endif
98
+ const v012_seperated_v3_and_o = v0.dot(v012_normal) > dot_eps_ratio * v0_abs_norm * v012_normal.absNorm();
99
+
100
+ if (v012_seperated_v3_and_o) {
101
+ // Orient the normal towards O
102
+ // #v-ifdef DEV
103
+ assert(v0.dot(v012_normal) > 0, "o_to_v0 and v012_normal should be in the same direction");
104
+ // #v-endif
105
+ search_v3_dir.negateVector(v012_normal);
106
+
107
+ // Find a new v3 in that direction
108
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v3_dir);
109
+ v3.copy(support.point);
110
+ v3_dir_in_support.copy(search_v3_dir);
111
+
112
+ // Miss detection
113
+ if (support.isOriginBeyondSupportPoint) {
114
+ result.status = FindPortalStatus.DetectSeperated;
115
+ return;
116
+ }
117
+
118
+ continue;
119
+ }
120
+
121
+ // case 023
122
+ v023_normal.crossVectors(v0_to_v2, v0_to_v3); // this must be done after orienting
123
+ // #v-ifdef DEV
124
+ assert(v0_to_v1.dot(v023_normal) >= 0, "v0_to_v1 and v023_normal should be in the same direction");
125
+ // #v-endif
126
+ const v023_seperated_v1_and_o = v0.dot(v023_normal) > dot_eps_ratio * v0_abs_norm * v023_normal.absNorm();
127
+ if (v023_seperated_v1_and_o) {
128
+ // Orient the normal towards O
129
+ // #v-ifdef DEV
130
+ assert(v0.dot(v023_normal) > 0, "o_to_v0 and v023_normal should be in the same direction");
131
+ // #v-endif
132
+ search_v1_dir.negateVector(v023_normal);
133
+
134
+ // Find a new v1 in that direction
135
+ support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v1_dir);
136
+ v1.copy(support.point);
137
+ v1_dir_in_support.copy(search_v1_dir);
138
+
139
+ // Miss detection
140
+ if (support.isOriginBeyondSupportPoint) {
141
+ result.status = FindPortalStatus.DetectSeperated;
142
+ return;
143
+ }
144
+
145
+ continue;
146
+ }
147
+
148
+ // no seperation found, we're done
149
+ result.status = FindPortalStatus.PortalFound;
150
+ return;
151
+ }
152
+ }
@@ -0,0 +1,29 @@
1
+ export const enum FindPortalStatus {
2
+ IterationLimit,
3
+ DetectSeperated,
4
+ PortalFound,
5
+ }
6
+
7
+ export const enum MprStatus {
8
+ NewDirection,
9
+ SubroutineConverge,
10
+ Degenerated,
11
+ Failed,
12
+ FailedNoIntersect,
13
+ }
14
+
15
+ export class MprResult {
16
+ status: MprStatus;
17
+
18
+ constructor() {
19
+ this.status = MprStatus.NewDirection;
20
+ }
21
+ }
22
+
23
+ export class FindPortalResult {
24
+ status: FindPortalStatus;
25
+
26
+ constructor() {
27
+ this.status = FindPortalStatus.IterationLimit;
28
+ }
29
+ }
@@ -0,0 +1,52 @@
1
+ import { Vec3 } from "../../math/vec3";
2
+
3
+ const v0_v4_o_normal = /*@__PURE__*/ Vec3.create();
4
+
5
+ export function updatePortal(
6
+ v0: Vec3,
7
+ v4: Vec3,
8
+ v123_normal: Vec3,
9
+ v1: Vec3,
10
+ v2: Vec3,
11
+ v3: Vec3,
12
+ v1_dir_in_support: Vec3,
13
+ v2_dir_in_support: Vec3,
14
+ v3_dir_in_support: Vec3
15
+ ): void {
16
+ // v4 must appear in the next portal
17
+ // select two in v1, v2 and v3
18
+ // First do a separation in with plane v0_v4_o
19
+ v0_v4_o_normal.crossVectors(v4, v0);
20
+ let dot = v1.dot(v0_v4_o_normal);
21
+ if (dot > 0) {
22
+ dot = v2.dot(v0_v4_o_normal);
23
+ if (dot > 0) {
24
+ // Discard v1
25
+ v1.copy(v4);
26
+ if (v1_dir_in_support !== undefined) {
27
+ v1_dir_in_support.copy(v123_normal);
28
+ }
29
+ } else {
30
+ // Discard v3
31
+ v3.copy(v4);
32
+ if (v3_dir_in_support !== undefined) {
33
+ v3_dir_in_support.copy(v123_normal);
34
+ }
35
+ }
36
+ } else {
37
+ dot = v3.dot(v0_v4_o_normal);
38
+ if (dot > 0) {
39
+ // Discard v2
40
+ v2.copy(v4);
41
+ if (v2_dir_in_support !== undefined) {
42
+ v2_dir_in_support.copy(v123_normal);
43
+ }
44
+ } else {
45
+ // Discard v1
46
+ v1.copy(v4);
47
+ if (v1_dir_in_support !== undefined) {
48
+ v1_dir_in_support.copy(v123_normal);
49
+ }
50
+ }
51
+ }
52
+ }