@aura3d/physics 1.0.6

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 (147) hide show
  1. package/README.md +26 -0
  2. package/dist/CharacterController.d.ts +52 -0
  3. package/dist/CharacterController.d.ts.map +1 -0
  4. package/dist/CharacterController.js +146 -0
  5. package/dist/CharacterController.js.map +1 -0
  6. package/dist/ClothFixtures.d.ts +87 -0
  7. package/dist/ClothFixtures.d.ts.map +1 -0
  8. package/dist/ClothFixtures.js +264 -0
  9. package/dist/ClothFixtures.js.map +1 -0
  10. package/dist/Collider.d.ts +27 -0
  11. package/dist/Collider.d.ts.map +1 -0
  12. package/dist/Collider.js +45 -0
  13. package/dist/Collider.js.map +1 -0
  14. package/dist/CollisionEvents.d.ts +26 -0
  15. package/dist/CollisionEvents.d.ts.map +1 -0
  16. package/dist/CollisionEvents.js +46 -0
  17. package/dist/CollisionEvents.js.map +1 -0
  18. package/dist/CollisionVolumes.d.ts +57 -0
  19. package/dist/CollisionVolumes.d.ts.map +1 -0
  20. package/dist/CollisionVolumes.js +159 -0
  21. package/dist/CollisionVolumes.js.map +1 -0
  22. package/dist/Constraint.d.ts +31 -0
  23. package/dist/Constraint.d.ts.map +1 -0
  24. package/dist/Constraint.js +103 -0
  25. package/dist/Constraint.js.map +1 -0
  26. package/dist/Constraints.d.ts +2 -0
  27. package/dist/Constraints.d.ts.map +1 -0
  28. package/dist/Constraints.js +2 -0
  29. package/dist/Constraints.js.map +1 -0
  30. package/dist/Crowd.d.ts +64 -0
  31. package/dist/Crowd.d.ts.map +1 -0
  32. package/dist/Crowd.js +209 -0
  33. package/dist/Crowd.js.map +1 -0
  34. package/dist/ECSPhysicsBridge.d.ts +17 -0
  35. package/dist/ECSPhysicsBridge.d.ts.map +1 -0
  36. package/dist/ECSPhysicsBridge.js +37 -0
  37. package/dist/ECSPhysicsBridge.js.map +1 -0
  38. package/dist/FireSmokeFixtures.d.ts +103 -0
  39. package/dist/FireSmokeFixtures.d.ts.map +1 -0
  40. package/dist/FireSmokeFixtures.js +271 -0
  41. package/dist/FireSmokeFixtures.js.map +1 -0
  42. package/dist/FluidFixtures.d.ts +75 -0
  43. package/dist/FluidFixtures.d.ts.map +1 -0
  44. package/dist/FluidFixtures.js +219 -0
  45. package/dist/FluidFixtures.js.map +1 -0
  46. package/dist/FractureFixtures.d.ts +68 -0
  47. package/dist/FractureFixtures.d.ts.map +1 -0
  48. package/dist/FractureFixtures.js +199 -0
  49. package/dist/FractureFixtures.js.map +1 -0
  50. package/dist/HitboxWorld.d.ts +250 -0
  51. package/dist/HitboxWorld.d.ts.map +1 -0
  52. package/dist/HitboxWorld.js +771 -0
  53. package/dist/HitboxWorld.js.map +1 -0
  54. package/dist/KinematicBody.d.ts +157 -0
  55. package/dist/KinematicBody.d.ts.map +1 -0
  56. package/dist/KinematicBody.js +405 -0
  57. package/dist/KinematicBody.js.map +1 -0
  58. package/dist/KinematicWorld.d.ts +58 -0
  59. package/dist/KinematicWorld.d.ts.map +1 -0
  60. package/dist/KinematicWorld.js +246 -0
  61. package/dist/KinematicWorld.js.map +1 -0
  62. package/dist/Navigation.d.ts +67 -0
  63. package/dist/Navigation.d.ts.map +1 -0
  64. package/dist/Navigation.js +261 -0
  65. package/dist/Navigation.js.map +1 -0
  66. package/dist/PhysicsDebugDraw.d.ts +11 -0
  67. package/dist/PhysicsDebugDraw.d.ts.map +1 -0
  68. package/dist/PhysicsDebugDraw.js +64 -0
  69. package/dist/PhysicsDebugDraw.js.map +1 -0
  70. package/dist/PhysicsSandboxFixtures.d.ts +53 -0
  71. package/dist/PhysicsSandboxFixtures.d.ts.map +1 -0
  72. package/dist/PhysicsSandboxFixtures.js +280 -0
  73. package/dist/PhysicsSandboxFixtures.js.map +1 -0
  74. package/dist/PhysicsStepper.d.ts +15 -0
  75. package/dist/PhysicsStepper.d.ts.map +1 -0
  76. package/dist/PhysicsStepper.js +37 -0
  77. package/dist/PhysicsStepper.js.map +1 -0
  78. package/dist/PhysicsWorld.d.ts +101 -0
  79. package/dist/PhysicsWorld.d.ts.map +1 -0
  80. package/dist/PhysicsWorld.js +868 -0
  81. package/dist/PhysicsWorld.js.map +1 -0
  82. package/dist/PlatformerFixtures.d.ts +70 -0
  83. package/dist/PlatformerFixtures.d.ts.map +1 -0
  84. package/dist/PlatformerFixtures.js +120 -0
  85. package/dist/PlatformerFixtures.js.map +1 -0
  86. package/dist/Raycast.d.ts +23 -0
  87. package/dist/Raycast.d.ts.map +1 -0
  88. package/dist/Raycast.js +271 -0
  89. package/dist/Raycast.js.map +1 -0
  90. package/dist/RigidBody.d.ts +69 -0
  91. package/dist/RigidBody.d.ts.map +1 -0
  92. package/dist/RigidBody.js +255 -0
  93. package/dist/RigidBody.js.map +1 -0
  94. package/dist/ScenePhysicsBridge.d.ts +42 -0
  95. package/dist/ScenePhysicsBridge.d.ts.map +1 -0
  96. package/dist/ScenePhysicsBridge.js +168 -0
  97. package/dist/ScenePhysicsBridge.js.map +1 -0
  98. package/dist/Shape.d.ts +50 -0
  99. package/dist/Shape.d.ts.map +1 -0
  100. package/dist/Shape.js +128 -0
  101. package/dist/Shape.js.map +1 -0
  102. package/dist/SoftBodyFixtures.d.ts +70 -0
  103. package/dist/SoftBodyFixtures.d.ts.map +1 -0
  104. package/dist/SoftBodyFixtures.js +272 -0
  105. package/dist/SoftBodyFixtures.js.map +1 -0
  106. package/dist/Steering.d.ts +161 -0
  107. package/dist/Steering.d.ts.map +1 -0
  108. package/dist/Steering.js +347 -0
  109. package/dist/Steering.js.map +1 -0
  110. package/dist/VehicleDynamics.d.ts +152 -0
  111. package/dist/VehicleDynamics.d.ts.map +1 -0
  112. package/dist/VehicleDynamics.js +335 -0
  113. package/dist/VehicleDynamics.js.map +1 -0
  114. package/dist/index.d.ts +29 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +29 -0
  117. package/dist/index.js.map +1 -0
  118. package/package.json +17 -0
  119. package/src/CharacterController.ts +184 -0
  120. package/src/ClothFixtures.ts +359 -0
  121. package/src/Collider.ts +67 -0
  122. package/src/CollisionEvents.ts +70 -0
  123. package/src/CollisionVolumes.ts +218 -0
  124. package/src/Constraint.ts +120 -0
  125. package/src/Constraints.ts +1 -0
  126. package/src/Crowd.ts +283 -0
  127. package/src/ECSPhysicsBridge.ts +53 -0
  128. package/src/FireSmokeFixtures.ts +382 -0
  129. package/src/FluidFixtures.ts +301 -0
  130. package/src/FractureFixtures.ts +277 -0
  131. package/src/HitboxWorld.ts +1179 -0
  132. package/src/KinematicBody.ts +553 -0
  133. package/src/KinematicWorld.ts +310 -0
  134. package/src/Navigation.ts +321 -0
  135. package/src/PhysicsDebugDraw.ts +71 -0
  136. package/src/PhysicsSandboxFixtures.ts +394 -0
  137. package/src/PhysicsStepper.ts +47 -0
  138. package/src/PhysicsWorld.ts +986 -0
  139. package/src/PlatformerFixtures.ts +205 -0
  140. package/src/Raycast.ts +315 -0
  141. package/src/RigidBody.ts +311 -0
  142. package/src/ScenePhysicsBridge.ts +207 -0
  143. package/src/Shape.ts +182 -0
  144. package/src/SoftBodyFixtures.ts +363 -0
  145. package/src/Steering.ts +531 -0
  146. package/src/VehicleDynamics.ts +524 -0
  147. package/src/index.ts +28 -0
@@ -0,0 +1,868 @@
1
+ import { Body as CannonBody, Box as CannonBox, Cylinder as CannonCylinder, Plane as CannonPlane, Quaternion as CannonQuaternion, Sphere as CannonSphere, Vec3 as CannonVec3, World as CannonWorld } from "cannon-es";
2
+ import { Collider } from "./Collider.js";
3
+ import { CollisionEventQueue } from "./CollisionEvents.js";
4
+ import { Constraint } from "./Constraint.js";
5
+ import { raycastCollider, sphereCastCollider } from "./Raycast.js";
6
+ import { RigidBody } from "./RigidBody.js";
7
+ import { cloneVec3, dotVec3, normalizeVec3, scaleVec3, subVec3 } from "./Shape.js";
8
+ export class PhysicsWorld {
9
+ gravity;
10
+ fixedDelta;
11
+ solverIterations;
12
+ enableSleeping;
13
+ sleepVelocityThreshold;
14
+ sleepDelay;
15
+ bodiesById = new Map();
16
+ collidersById = new Map();
17
+ constraintsList = [];
18
+ bodyColliders = new Map();
19
+ eventQueue = new CollisionEventQueue();
20
+ requestedBackend;
21
+ backendSelection;
22
+ cannonWorld;
23
+ cannonBodiesByAuraId = new Map();
24
+ nextBodyId = 1;
25
+ nextColliderId = 1;
26
+ lastEvents = [];
27
+ lastBroadphasePairs = 0;
28
+ lastBroadphaseProfile = emptyBroadphaseProfile();
29
+ steps = 0;
30
+ constructor(descriptor = {}) {
31
+ this.requestedBackend = descriptor.backend ?? "auto";
32
+ this.gravity = cloneVec3(descriptor.gravity ?? [0, -9.81, 0]);
33
+ this.fixedDelta = descriptor.fixedDelta ?? 1 / 60;
34
+ this.solverIterations = descriptor.solverIterations ?? 1;
35
+ this.enableSleeping = descriptor.enableSleeping ?? true;
36
+ this.sleepVelocityThreshold = descriptor.sleepVelocityThreshold ?? 0.02;
37
+ this.sleepDelay = descriptor.sleepDelay ?? 0.5;
38
+ if (!Number.isFinite(this.fixedDelta) || this.fixedDelta <= 0) {
39
+ throw new Error("fixedDelta must be a finite positive number.");
40
+ }
41
+ if (!Number.isInteger(this.solverIterations) || this.solverIterations <= 0) {
42
+ throw new Error("solverIterations must be a positive integer.");
43
+ }
44
+ if (!Number.isFinite(this.sleepVelocityThreshold) || this.sleepVelocityThreshold < 0) {
45
+ throw new Error("sleepVelocityThreshold must be finite and non-negative.");
46
+ }
47
+ if (!Number.isFinite(this.sleepDelay) || this.sleepDelay < 0) {
48
+ throw new Error("sleepDelay must be finite and non-negative.");
49
+ }
50
+ this.backendSelection = {
51
+ requested: this.requestedBackend,
52
+ active: this.requestedBackend === "aura-js" ? "aura-js" : "cannon-es",
53
+ deterministic: true,
54
+ jsFallbackAvailable: true
55
+ };
56
+ if (this.backendSelection.active === "cannon-es") {
57
+ this.cannonWorld = new CannonWorld({
58
+ gravity: toCannonVec3(this.gravity),
59
+ allowSleep: this.enableSleeping
60
+ });
61
+ this.cannonWorld.solver.iterations = this.solverIterations;
62
+ this.cannonWorld.defaultContactMaterial.friction = 0.5;
63
+ this.cannonWorld.defaultContactMaterial.restitution = 0;
64
+ }
65
+ }
66
+ createRigidBody(descriptor = {}) {
67
+ const body = new RigidBody(this.nextBodyId, descriptor);
68
+ this.nextBodyId += 1;
69
+ this.bodiesById.set(body.id, body);
70
+ this.bodyColliders.set(body.id, new Set());
71
+ const cannonBody = this.createCannonBody(body);
72
+ if (cannonBody) {
73
+ this.cannonBodiesByAuraId.set(body.id, cannonBody);
74
+ this.cannonWorld?.addBody(cannonBody);
75
+ }
76
+ return body;
77
+ }
78
+ createCollider(body, descriptor) {
79
+ const bodyId = typeof body === "number" ? body : body.id;
80
+ if (!this.bodiesById.has(bodyId)) {
81
+ throw new Error(`Cannot create collider for missing body ${bodyId}.`);
82
+ }
83
+ const collider = new Collider(this.nextColliderId, bodyId, descriptor);
84
+ this.nextColliderId += 1;
85
+ this.collidersById.set(collider.id, collider);
86
+ this.bodyColliders.get(bodyId)?.add(collider.id);
87
+ this.addCannonCollider(collider);
88
+ return collider;
89
+ }
90
+ createConstraint(descriptor) {
91
+ if (!this.bodiesById.has(descriptor.bodyA.id) || !this.bodiesById.has(descriptor.bodyB.id)) {
92
+ throw new Error("Cannot create a constraint for bodies outside this PhysicsWorld.");
93
+ }
94
+ const constraint = new Constraint(descriptor);
95
+ this.constraintsList.push(constraint);
96
+ return constraint;
97
+ }
98
+ getBody(id) {
99
+ return this.bodiesById.get(id);
100
+ }
101
+ getCollider(id) {
102
+ return this.collidersById.get(id);
103
+ }
104
+ bodies() {
105
+ return Array.from(this.bodiesById.values()).sort((a, b) => a.id - b.id);
106
+ }
107
+ colliders() {
108
+ return Array.from(this.collidersById.values()).sort((a, b) => a.id - b.id);
109
+ }
110
+ bodyValues() {
111
+ return this.bodiesById.values();
112
+ }
113
+ colliderValues() {
114
+ return this.collidersById.values();
115
+ }
116
+ constraints() {
117
+ return [...this.constraintsList];
118
+ }
119
+ removeRigidBody(id) {
120
+ const colliderIds = Array.from(this.bodyColliders.get(id) ?? []);
121
+ for (const colliderId of colliderIds) {
122
+ this.removeCollider(colliderId);
123
+ }
124
+ this.bodyColliders.delete(id);
125
+ this.bodiesById.delete(id);
126
+ const cannonBody = this.cannonBodiesByAuraId.get(id);
127
+ if (cannonBody) {
128
+ this.cannonWorld?.removeBody(cannonBody);
129
+ this.cannonBodiesByAuraId.delete(id);
130
+ }
131
+ for (let index = this.constraintsList.length - 1; index >= 0; index -= 1) {
132
+ const constraint = this.constraintsList[index];
133
+ if (constraint.bodyA.id === id || constraint.bodyB.id === id) {
134
+ this.constraintsList.splice(index, 1);
135
+ }
136
+ }
137
+ }
138
+ removeCollider(id) {
139
+ const collider = this.collidersById.get(id);
140
+ if (!collider) {
141
+ return;
142
+ }
143
+ this.bodyColliders.get(collider.bodyId)?.delete(id);
144
+ this.collidersById.delete(id);
145
+ const removalEvents = this.eventQueue.removeCollider(id);
146
+ if (removalEvents.length > 0) {
147
+ this.lastEvents = [...this.lastEvents, ...removalEvents].sort((a, b) => a.pairKey.localeCompare(b.pairKey) || a.type.localeCompare(b.type));
148
+ }
149
+ }
150
+ step(dt = this.fixedDelta) {
151
+ if (!Number.isFinite(dt) || dt <= 0) {
152
+ throw new Error("PhysicsWorld.step dt must be finite and positive.");
153
+ }
154
+ if (this.cannonWorld) {
155
+ return this.stepCannon(dt);
156
+ }
157
+ for (const body of this.bodyValues()) {
158
+ body.integrate(dt, this.gravity);
159
+ }
160
+ for (const constraint of this.constraintsList) {
161
+ constraint.solve();
162
+ }
163
+ let contacts = [];
164
+ for (let i = 0; i < this.solverIterations; i += 1) {
165
+ contacts = this.detectContacts();
166
+ for (const contact of contacts) {
167
+ this.resolveContact(contact);
168
+ }
169
+ for (const constraint of this.constraintsList) {
170
+ constraint.solve();
171
+ }
172
+ }
173
+ this.lastEvents = this.eventQueue.update(contacts);
174
+ this.updateSleeping(dt, contacts);
175
+ this.steps += 1;
176
+ return this.lastEvents;
177
+ }
178
+ drainEvents() {
179
+ const events = this.lastEvents;
180
+ this.lastEvents = [];
181
+ return events;
182
+ }
183
+ raycast(origin, direction, options = {}) {
184
+ const normalized = normalizeVec3(direction);
185
+ return this.raycastAll(origin, normalized, options)[0];
186
+ }
187
+ raycastAll(origin, direction, options = {}) {
188
+ const normalized = normalizeVec3(direction);
189
+ const hits = [];
190
+ for (const collider of this.colliderValues()) {
191
+ const body = this.bodiesById.get(collider.bodyId);
192
+ if (!body) {
193
+ continue;
194
+ }
195
+ const hit = raycastCollider(origin, normalized, collider, body, options);
196
+ if (hit) {
197
+ hits.push(hit);
198
+ }
199
+ }
200
+ hits.sort((a, b) => a.distance - b.distance || a.colliderId - b.colliderId);
201
+ return hits;
202
+ }
203
+ sphereCast(origin, radius, direction, options = {}) {
204
+ return this.sphereCastAll(origin, radius, direction, options)[0];
205
+ }
206
+ sphereCastAll(origin, radius, direction, options = {}) {
207
+ const normalized = normalizeVec3(direction);
208
+ const hits = [];
209
+ for (const collider of this.colliderValues()) {
210
+ const body = this.bodiesById.get(collider.bodyId);
211
+ if (!body) {
212
+ continue;
213
+ }
214
+ const hit = sphereCastCollider(origin, radius, normalized, collider, body, options);
215
+ if (hit) {
216
+ hits.push(hit);
217
+ }
218
+ }
219
+ hits.sort((a, b) => a.distance - b.distance || a.colliderId - b.colliderId);
220
+ return hits;
221
+ }
222
+ snapshot() {
223
+ const contacts = this.eventQueue.snapshotContacts();
224
+ const bodies = this.bodies();
225
+ return {
226
+ backend: this.backendSelection,
227
+ bodies: bodies.map((body) => body.snapshot()),
228
+ contacts,
229
+ stats: {
230
+ steps: this.steps,
231
+ bodies: this.bodiesById.size,
232
+ colliders: this.collidersById.size,
233
+ constraints: this.constraintsList.length,
234
+ broadphasePairs: this.lastBroadphasePairs,
235
+ broadphaseFiniteColliders: this.lastBroadphaseProfile.finiteColliders,
236
+ broadphaseInfiniteColliders: this.lastBroadphaseProfile.infiniteColliders,
237
+ broadphaseCandidateTests: this.lastBroadphaseProfile.candidateTests,
238
+ broadphaseActiveMax: this.lastBroadphaseProfile.activeMax,
239
+ broadphaseRejectedByBounds: this.lastBroadphaseProfile.rejectedByBounds,
240
+ contacts: contacts.length,
241
+ events: this.lastEvents.length,
242
+ sleepingBodies: bodies.filter((body) => body.sleeping).length,
243
+ kineticEnergy: totalKineticEnergy(bodies),
244
+ maxContactPenetration: contacts.reduce((max, contact) => Math.max(max, contact.penetration), 0)
245
+ }
246
+ };
247
+ }
248
+ detectContacts() {
249
+ const potentialPairs = this.collectPotentialPairs();
250
+ this.lastBroadphasePairs = potentialPairs.length;
251
+ const contacts = [];
252
+ for (const [a, bodyA, b, bodyB] of potentialPairs) {
253
+ const contact = buildContact(a, bodyA, b, bodyB);
254
+ if (contact) {
255
+ contacts.push(contact);
256
+ }
257
+ }
258
+ contacts.sort((a, b) => a.colliderA - b.colliderA || a.colliderB - b.colliderB);
259
+ return contacts;
260
+ }
261
+ collectPotentialPairs() {
262
+ const finiteEntries = [];
263
+ const infiniteEntries = [];
264
+ const profile = emptyBroadphaseProfile();
265
+ for (const collider of this.colliderValues()) {
266
+ const body = this.bodiesById.get(collider.bodyId);
267
+ if (!body) {
268
+ continue;
269
+ }
270
+ const bounds = collider.bounds(body.position);
271
+ const entry = { collider, body, bounds };
272
+ if (isFiniteBounds(bounds)) {
273
+ finiteEntries.push(entry);
274
+ profile.finiteColliders += 1;
275
+ }
276
+ else {
277
+ infiniteEntries.push(entry);
278
+ profile.infiniteColliders += 1;
279
+ }
280
+ }
281
+ finiteEntries.sort((a, b) => a.bounds.min[0] - b.bounds.min[0] || a.collider.id - b.collider.id);
282
+ const pairs = [];
283
+ const seen = new Set();
284
+ const active = [];
285
+ for (const entry of finiteEntries) {
286
+ for (let index = active.length - 1; index >= 0; index -= 1) {
287
+ if (active[index].bounds.max[0] < entry.bounds.min[0]) {
288
+ active.splice(index, 1);
289
+ }
290
+ }
291
+ for (const other of active) {
292
+ profile.candidateTests += 1;
293
+ if (boundsOverlap(other.bounds, entry.bounds)) {
294
+ pushPotentialPair(pairs, seen, other, entry);
295
+ }
296
+ else {
297
+ profile.rejectedByBounds += 1;
298
+ }
299
+ }
300
+ for (const infinite of infiniteEntries) {
301
+ profile.candidateTests += 1;
302
+ pushPotentialPair(pairs, seen, infinite, entry);
303
+ }
304
+ active.push(entry);
305
+ profile.activeMax = Math.max(profile.activeMax, active.length);
306
+ }
307
+ for (let i = 0; i < infiniteEntries.length; i += 1) {
308
+ for (let j = i + 1; j < infiniteEntries.length; j += 1) {
309
+ profile.candidateTests += 1;
310
+ pushPotentialPair(pairs, seen, infiniteEntries[i], infiniteEntries[j]);
311
+ }
312
+ }
313
+ pairs.sort((a, b) => a[0].id - b[0].id || a[2].id - b[2].id);
314
+ this.lastBroadphaseProfile = profile;
315
+ return pairs;
316
+ }
317
+ resolveContact(contact) {
318
+ if (contact.sensor) {
319
+ return;
320
+ }
321
+ const bodyA = this.bodiesById.get(contact.bodyA);
322
+ const bodyB = this.bodiesById.get(contact.bodyB);
323
+ if (!bodyA || !bodyB) {
324
+ return;
325
+ }
326
+ const invMassSum = bodyA.inverseMass + bodyB.inverseMass;
327
+ if (invMassSum <= 0) {
328
+ return;
329
+ }
330
+ const correction = scaleVec3(contact.normal, contact.penetration / invMassSum);
331
+ if (bodyA.inverseMass > 0) {
332
+ bodyA.position = [bodyA.position[0] - correction[0] * bodyA.inverseMass, bodyA.position[1] - correction[1] * bodyA.inverseMass, bodyA.position[2] - correction[2] * bodyA.inverseMass];
333
+ }
334
+ if (bodyB.inverseMass > 0) {
335
+ bodyB.position = [bodyB.position[0] + correction[0] * bodyB.inverseMass, bodyB.position[1] + correction[1] * bodyB.inverseMass, bodyB.position[2] + correction[2] * bodyB.inverseMass];
336
+ }
337
+ const relativeVelocity = subVec3(bodyB.velocity, bodyA.velocity);
338
+ const velocityAlongNormal = dotVec3(relativeVelocity, contact.normal);
339
+ const materialA = this.effectiveMaterial(bodyA, contact.colliderA);
340
+ const materialB = this.effectiveMaterial(bodyB, contact.colliderB);
341
+ const restitution = Math.max(materialA.restitution, materialB.restitution);
342
+ let normalImpulseMagnitude = 0;
343
+ if (velocityAlongNormal <= 0) {
344
+ normalImpulseMagnitude = -(1 + restitution) * velocityAlongNormal / invMassSum;
345
+ this.applyImpulsePair(bodyA, bodyB, scaleVec3(contact.normal, normalImpulseMagnitude));
346
+ }
347
+ const updatedRelativeVelocity = subVec3(bodyB.velocity, bodyA.velocity);
348
+ const updatedNormalVelocity = dotVec3(updatedRelativeVelocity, contact.normal);
349
+ const tangentVelocity = subVec3(updatedRelativeVelocity, scaleVec3(contact.normal, updatedNormalVelocity));
350
+ const tangentSpeed = Math.hypot(tangentVelocity[0], tangentVelocity[1], tangentVelocity[2]);
351
+ if (tangentSpeed > 1e-9) {
352
+ const tangent = scaleVec3(tangentVelocity, 1 / tangentSpeed);
353
+ const friction = Math.sqrt(Math.max(0, materialA.friction) * Math.max(0, materialB.friction));
354
+ const targetMagnitude = -dotVec3(updatedRelativeVelocity, tangent) / invMassSum;
355
+ const maxFriction = friction * (Math.abs(normalImpulseMagnitude) + contact.penetration);
356
+ const frictionMagnitude = Math.max(-maxFriction, Math.min(maxFriction, targetMagnitude));
357
+ if (frictionMagnitude !== 0) {
358
+ this.applyImpulsePair(bodyA, bodyB, scaleVec3(tangent, frictionMagnitude));
359
+ }
360
+ }
361
+ }
362
+ effectiveMaterial(body, colliderId) {
363
+ const collider = this.collidersById.get(colliderId);
364
+ return {
365
+ restitution: Math.max(body.restitution, collider?.material.restitution ?? 0),
366
+ friction: Math.max(body.friction, collider?.material.friction ?? 0)
367
+ };
368
+ }
369
+ applyImpulsePair(bodyA, bodyB, impulse) {
370
+ if (bodyA.inverseMass > 0) {
371
+ bodyA.velocity = [bodyA.velocity[0] - impulse[0] * bodyA.inverseMass, bodyA.velocity[1] - impulse[1] * bodyA.inverseMass, bodyA.velocity[2] - impulse[2] * bodyA.inverseMass];
372
+ if (bodyA.sleeping && bodyA.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
373
+ bodyA.wake();
374
+ }
375
+ }
376
+ if (bodyB.inverseMass > 0) {
377
+ bodyB.velocity = [bodyB.velocity[0] + impulse[0] * bodyB.inverseMass, bodyB.velocity[1] + impulse[1] * bodyB.inverseMass, bodyB.velocity[2] + impulse[2] * bodyB.inverseMass];
378
+ if (bodyB.sleeping && bodyB.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
379
+ bodyB.wake();
380
+ }
381
+ }
382
+ }
383
+ updateSleeping(dt, contacts) {
384
+ if (!this.enableSleeping) {
385
+ return;
386
+ }
387
+ const contactBodyIds = new Set();
388
+ for (const contact of contacts) {
389
+ if (!contact.sensor) {
390
+ contactBodyIds.add(contact.bodyA);
391
+ contactBodyIds.add(contact.bodyB);
392
+ }
393
+ }
394
+ const gravityMagnitudeSquared = dotVec3(this.gravity, this.gravity);
395
+ const thresholdSquared = this.sleepVelocityThreshold * this.sleepVelocityThreshold;
396
+ for (const body of this.bodyValues()) {
397
+ if (body.type !== "dynamic") {
398
+ continue;
399
+ }
400
+ if (body.speedSquared() > thresholdSquared) {
401
+ body.resetSleepTimer();
402
+ continue;
403
+ }
404
+ const supportedOrUnaccelerated = contactBodyIds.has(body.id) || gravityMagnitudeSquared <= thresholdSquared;
405
+ if (!supportedOrUnaccelerated) {
406
+ body.resetSleepTimer();
407
+ continue;
408
+ }
409
+ if (body.accumulateSleepTime(dt) >= this.sleepDelay) {
410
+ body.sleep();
411
+ }
412
+ }
413
+ }
414
+ createCannonBody(body) {
415
+ if (!this.cannonWorld)
416
+ return undefined;
417
+ const cannonBody = new CannonBody({
418
+ type: body.type === "static" ? CannonBody.STATIC : body.type === "kinematic" ? CannonBody.KINEMATIC : CannonBody.DYNAMIC,
419
+ mass: body.type === "dynamic" ? body.mass : 0,
420
+ position: toCannonVec3(body.position),
421
+ velocity: toCannonVec3(body.velocity),
422
+ quaternion: new CannonQuaternion(body.rotation[0], body.rotation[1], body.rotation[2], body.rotation[3]),
423
+ angularVelocity: toCannonVec3(body.angularVelocity),
424
+ linearDamping: body.linearDamping,
425
+ angularDamping: body.angularDamping,
426
+ allowSleep: this.enableSleeping,
427
+ sleepSpeedLimit: this.sleepVelocityThreshold,
428
+ sleepTimeLimit: this.sleepDelay
429
+ });
430
+ if (body.sleeping)
431
+ cannonBody.sleep();
432
+ return cannonBody;
433
+ }
434
+ addCannonCollider(collider) {
435
+ if (!this.cannonWorld)
436
+ return;
437
+ const body = this.bodiesById.get(collider.bodyId);
438
+ const cannonBody = this.cannonBodiesByAuraId.get(collider.bodyId);
439
+ if (!body || !cannonBody)
440
+ return;
441
+ const resolved = toCannonShape(collider.shape);
442
+ if (!resolved) {
443
+ this.disableCannonBackend(`unsupported shape '${collider.shape.kind}'`);
444
+ return;
445
+ }
446
+ cannonBody.collisionFilterGroup = collider.filter.layer;
447
+ cannonBody.collisionFilterMask = collider.filter.mask;
448
+ cannonBody.isTrigger = cannonBody.isTrigger || collider.sensor;
449
+ cannonBody.addShape(resolved.shape, resolved.offset, resolved.orientation);
450
+ cannonBody.updateMassProperties();
451
+ syncCannonFromAura(body, cannonBody);
452
+ }
453
+ disableCannonBackend(reason) {
454
+ if (!this.cannonWorld)
455
+ return;
456
+ this.cannonWorld = undefined;
457
+ this.cannonBodiesByAuraId.clear();
458
+ this.backendSelection = {
459
+ requested: this.requestedBackend,
460
+ active: "aura-js",
461
+ fallback: reason,
462
+ deterministic: true,
463
+ jsFallbackAvailable: true
464
+ };
465
+ }
466
+ stepCannon(dt) {
467
+ if (!this.cannonWorld)
468
+ return [];
469
+ this.cannonWorld.gravity.copy(toCannonVec3(this.gravity));
470
+ for (const body of this.bodyValues()) {
471
+ const cannonBody = this.cannonBodiesByAuraId.get(body.id);
472
+ if (cannonBody)
473
+ syncCannonFromAura(body, cannonBody);
474
+ }
475
+ this.cannonWorld.step(dt);
476
+ for (const body of this.bodyValues()) {
477
+ const cannonBody = this.cannonBodiesByAuraId.get(body.id);
478
+ if (cannonBody)
479
+ syncAuraFromCannon(cannonBody, body);
480
+ }
481
+ const contacts = this.detectContacts();
482
+ this.lastEvents = this.eventQueue.update(contacts);
483
+ this.updateSleeping(dt, contacts);
484
+ this.steps += 1;
485
+ return this.lastEvents;
486
+ }
487
+ }
488
+ function toCannonVec3(value) {
489
+ return new CannonVec3(value[0], value[1], value[2]);
490
+ }
491
+ function fromCannonVec3(value) {
492
+ return [value.x, value.y, value.z];
493
+ }
494
+ function syncCannonFromAura(body, cannonBody) {
495
+ cannonBody.position.set(body.position[0], body.position[1], body.position[2]);
496
+ cannonBody.velocity.set(body.velocity[0], body.velocity[1], body.velocity[2]);
497
+ cannonBody.quaternion.set(body.rotation[0], body.rotation[1], body.rotation[2], body.rotation[3]);
498
+ cannonBody.angularVelocity.set(body.angularVelocity[0], body.angularVelocity[1], body.angularVelocity[2]);
499
+ if (body.sleeping)
500
+ cannonBody.sleep();
501
+ else
502
+ cannonBody.wakeUp();
503
+ }
504
+ function syncAuraFromCannon(cannonBody, body) {
505
+ body.previousPosition = cloneVec3(body.position);
506
+ body.previousRotation = [body.rotation[0], body.rotation[1], body.rotation[2], body.rotation[3]];
507
+ body.position = fromCannonVec3(cannonBody.position);
508
+ body.velocity = fromCannonVec3(cannonBody.velocity);
509
+ body.angularVelocity = fromCannonVec3(cannonBody.angularVelocity);
510
+ body.rotation = [cannonBody.quaternion.x, cannonBody.quaternion.y, cannonBody.quaternion.z, cannonBody.quaternion.w];
511
+ body.sleeping = cannonBody.sleepState === CannonBody.SLEEPING;
512
+ }
513
+ function toCannonShape(shape) {
514
+ if (shape.kind === "box")
515
+ return { shape: new CannonBox(toCannonVec3(shape.halfExtents)) };
516
+ if (shape.kind === "sphere")
517
+ return { shape: new CannonSphere(shape.radius) };
518
+ if (shape.kind === "capsule")
519
+ return { shape: new CannonCylinder(shape.radius, shape.radius, shape.halfHeight * 2 + shape.radius * 2, 12) };
520
+ if (shape.kind === "plane") {
521
+ const orientation = new CannonQuaternion();
522
+ orientation.setFromVectors(new CannonVec3(0, 0, 1), toCannonVec3(shape.normal));
523
+ return {
524
+ shape: new CannonPlane(),
525
+ offset: toCannonVec3(scaleVec3(shape.normal, shape.constant)),
526
+ orientation
527
+ };
528
+ }
529
+ return undefined;
530
+ }
531
+ function totalKineticEnergy(bodies) {
532
+ let total = 0;
533
+ for (const body of bodies) {
534
+ if (body.type !== "dynamic") {
535
+ continue;
536
+ }
537
+ const linearSpeedSquared = dotVec3(body.velocity, body.velocity);
538
+ const angularEnergy = body.angularVelocity.reduce((sum, velocity, index) => {
539
+ const inverseInertia = body.inverseInertia[index] ?? 0;
540
+ return inverseInertia > 0 ? sum + 0.5 * velocity * velocity / inverseInertia : sum;
541
+ }, 0);
542
+ total += 0.5 * body.mass * linearSpeedSquared + angularEnergy;
543
+ }
544
+ return total;
545
+ }
546
+ function emptyBroadphaseProfile() {
547
+ return {
548
+ finiteColliders: 0,
549
+ infiniteColliders: 0,
550
+ candidateTests: 0,
551
+ activeMax: 0,
552
+ rejectedByBounds: 0
553
+ };
554
+ }
555
+ function isFiniteBounds(bounds) {
556
+ return isFiniteBoundValue(bounds.min[0]) && isFiniteBoundValue(bounds.min[1]) && isFiniteBoundValue(bounds.min[2]) &&
557
+ isFiniteBoundValue(bounds.max[0]) && isFiniteBoundValue(bounds.max[1]) && isFiniteBoundValue(bounds.max[2]);
558
+ }
559
+ function isFiniteBoundValue(value) {
560
+ return Number.isFinite(value) && Math.abs(value) < Number.MAX_SAFE_INTEGER;
561
+ }
562
+ function boundsOverlap(a, b) {
563
+ return a.min[0] <= b.max[0] && a.max[0] >= b.min[0] &&
564
+ a.min[1] <= b.max[1] && a.max[1] >= b.min[1] &&
565
+ a.min[2] <= b.max[2] && a.max[2] >= b.min[2];
566
+ }
567
+ function pushPotentialPair(pairs, seen, a, b) {
568
+ if (a.collider.bodyId === b.collider.bodyId || !a.collider.canCollideWith(b.collider)) {
569
+ return;
570
+ }
571
+ if (a.body.type !== "dynamic" && b.body.type !== "dynamic") {
572
+ return;
573
+ }
574
+ const first = a.collider.id < b.collider.id ? a : b;
575
+ const second = a.collider.id < b.collider.id ? b : a;
576
+ const key = `${first.collider.id}:${second.collider.id}`;
577
+ if (seen.has(key)) {
578
+ return;
579
+ }
580
+ seen.add(key);
581
+ pairs.push([first.collider, first.body, second.collider, second.body]);
582
+ }
583
+ function buildContact(a, bodyA, b, bodyB) {
584
+ const ab = subVec3(bodyB.position, bodyA.position);
585
+ if (a.shape.kind === "plane" || b.shape.kind === "plane") {
586
+ const planeCollider = a.shape.kind === "plane" ? a : b;
587
+ const planeBody = a.shape.kind === "plane" ? bodyA : bodyB;
588
+ const otherCollider = a.shape.kind === "plane" ? b : a;
589
+ const otherBody = a.shape.kind === "plane" ? bodyB : bodyA;
590
+ if (planeCollider.shape.kind !== "plane") {
591
+ return undefined;
592
+ }
593
+ const radius = supportRadius(otherCollider);
594
+ const signedDistance = dotVec3(planeCollider.shape.normal, otherBody.position) + planeCollider.shape.constant + dotVec3(planeCollider.shape.normal, planeBody.position);
595
+ if (signedDistance > radius) {
596
+ return undefined;
597
+ }
598
+ const normal = a.shape.kind === "plane" ? planeCollider.shape.normal : scaleVec3(planeCollider.shape.normal, -1);
599
+ return {
600
+ colliderA: a.id,
601
+ colliderB: b.id,
602
+ bodyA: bodyA.id,
603
+ bodyB: bodyB.id,
604
+ normal,
605
+ penetration: radius - signedDistance,
606
+ sensor: a.sensor || b.sensor
607
+ };
608
+ }
609
+ if (a.shape.kind === "sphere" && b.shape.kind === "sphere") {
610
+ const distance = Math.hypot(ab[0], ab[1], ab[2]);
611
+ const radiusSum = a.shape.radius + b.shape.radius;
612
+ if (distance >= radiusSum) {
613
+ return undefined;
614
+ }
615
+ return {
616
+ colliderA: a.id,
617
+ colliderB: b.id,
618
+ bodyA: bodyA.id,
619
+ bodyB: bodyB.id,
620
+ normal: distance > 1e-9 ? [ab[0] / distance, ab[1] / distance, ab[2] / distance] : [1, 0, 0],
621
+ penetration: radiusSum - distance,
622
+ sensor: a.sensor || b.sensor
623
+ };
624
+ }
625
+ if ((a.shape.kind === "sphere" && b.shape.kind === "box") || (a.shape.kind === "box" && b.shape.kind === "sphere")) {
626
+ return buildSphereBoxContact(a, bodyA, b, bodyB);
627
+ }
628
+ if ((a.shape.kind === "capsule" && b.shape.kind === "sphere") || (a.shape.kind === "sphere" && b.shape.kind === "capsule")) {
629
+ return buildCapsuleSphereContact(a, bodyA, b, bodyB);
630
+ }
631
+ if ((a.shape.kind === "capsule" && b.shape.kind === "box") || (a.shape.kind === "box" && b.shape.kind === "capsule")) {
632
+ return buildCapsuleBoxContact(a, bodyA, b, bodyB);
633
+ }
634
+ if (a.shape.kind === "capsule" && b.shape.kind === "capsule") {
635
+ return buildCapsuleCapsuleContact(a, bodyA, b, bodyB);
636
+ }
637
+ const boundsA = a.bounds(bodyA.position);
638
+ const boundsB = b.bounds(bodyB.position);
639
+ const overlapX = Math.min(boundsA.max[0] - boundsB.min[0], boundsB.max[0] - boundsA.min[0]);
640
+ const overlapY = Math.min(boundsA.max[1] - boundsB.min[1], boundsB.max[1] - boundsA.min[1]);
641
+ const overlapZ = Math.min(boundsA.max[2] - boundsB.min[2], boundsB.max[2] - boundsA.min[2]);
642
+ if (overlapX <= 0 || overlapY <= 0 || overlapZ <= 0) {
643
+ return undefined;
644
+ }
645
+ let normal = [Math.sign(ab[0]) || 1, 0, 0];
646
+ let penetration = overlapX;
647
+ if (overlapY < penetration) {
648
+ normal = [0, Math.sign(ab[1]) || 1, 0];
649
+ penetration = overlapY;
650
+ }
651
+ if (overlapZ < penetration) {
652
+ normal = [0, 0, Math.sign(ab[2]) || 1];
653
+ penetration = overlapZ;
654
+ }
655
+ return {
656
+ colliderA: a.id,
657
+ colliderB: b.id,
658
+ bodyA: bodyA.id,
659
+ bodyB: bodyB.id,
660
+ normal,
661
+ penetration,
662
+ sensor: a.sensor || b.sensor
663
+ };
664
+ }
665
+ function buildSphereBoxContact(a, bodyA, b, bodyB) {
666
+ const sphereCollider = a.shape.kind === "sphere" ? a : b;
667
+ const sphereBody = a.shape.kind === "sphere" ? bodyA : bodyB;
668
+ const boxCollider = a.shape.kind === "box" ? a : b;
669
+ const boxBody = a.shape.kind === "box" ? bodyA : bodyB;
670
+ if (sphereCollider.shape.kind !== "sphere" || boxCollider.shape.kind !== "box") {
671
+ return undefined;
672
+ }
673
+ const boxBounds = boxCollider.bounds(boxBody.position);
674
+ const closest = [
675
+ clamp(sphereBody.position[0], boxBounds.min[0], boxBounds.max[0]),
676
+ clamp(sphereBody.position[1], boxBounds.min[1], boxBounds.max[1]),
677
+ clamp(sphereBody.position[2], boxBounds.min[2], boxBounds.max[2])
678
+ ];
679
+ const fromBoxToSphere = subVec3(sphereBody.position, closest);
680
+ const distance = Math.hypot(fromBoxToSphere[0], fromBoxToSphere[1], fromBoxToSphere[2]);
681
+ if (distance >= sphereCollider.shape.radius) {
682
+ return undefined;
683
+ }
684
+ const normalFromBoxToSphere = distance > 1e-9 ? scaleVec3(fromBoxToSphere, 1 / distance) : axisFromBoxCenter(boxBody.position, sphereBody.position, boxCollider.shape.halfExtents);
685
+ const normal = a.shape.kind === "box" ? normalFromBoxToSphere : scaleVec3(normalFromBoxToSphere, -1);
686
+ return {
687
+ colliderA: a.id,
688
+ colliderB: b.id,
689
+ bodyA: bodyA.id,
690
+ bodyB: bodyB.id,
691
+ normal,
692
+ penetration: sphereCollider.shape.radius - distance,
693
+ sensor: a.sensor || b.sensor
694
+ };
695
+ }
696
+ function buildCapsuleSphereContact(a, bodyA, b, bodyB) {
697
+ const capsuleCollider = a.shape.kind === "capsule" ? a : b;
698
+ const capsuleBody = a.shape.kind === "capsule" ? bodyA : bodyB;
699
+ const sphereCollider = a.shape.kind === "sphere" ? a : b;
700
+ const sphereBody = a.shape.kind === "sphere" ? bodyA : bodyB;
701
+ if (capsuleCollider.shape.kind !== "capsule" || sphereCollider.shape.kind !== "sphere") {
702
+ return undefined;
703
+ }
704
+ const closest = closestPointOnSegment(sphereBody.position, capsuleSegment(capsuleBody.position, capsuleCollider.shape.halfHeight));
705
+ const delta = subVec3(sphereBody.position, closest);
706
+ const distance = Math.hypot(delta[0], delta[1], delta[2]);
707
+ const radiusSum = capsuleCollider.shape.radius + sphereCollider.shape.radius;
708
+ if (distance >= radiusSum) {
709
+ return undefined;
710
+ }
711
+ const normalFromCapsuleToSphere = distance > 1e-9 ? scaleVec3(delta, 1 / distance) : [1, 0, 0];
712
+ const normal = a.shape.kind === "capsule" ? normalFromCapsuleToSphere : scaleVec3(normalFromCapsuleToSphere, -1);
713
+ return {
714
+ colliderA: a.id,
715
+ colliderB: b.id,
716
+ bodyA: bodyA.id,
717
+ bodyB: bodyB.id,
718
+ normal,
719
+ penetration: radiusSum - distance,
720
+ sensor: a.sensor || b.sensor
721
+ };
722
+ }
723
+ function buildCapsuleCapsuleContact(a, bodyA, b, bodyB) {
724
+ if (a.shape.kind !== "capsule" || b.shape.kind !== "capsule") {
725
+ return undefined;
726
+ }
727
+ const closest = closestPointsBetweenSegments(capsuleSegment(bodyA.position, a.shape.halfHeight), capsuleSegment(bodyB.position, b.shape.halfHeight));
728
+ const delta = subVec3(closest.b, closest.a);
729
+ const distance = Math.hypot(delta[0], delta[1], delta[2]);
730
+ const radiusSum = a.shape.radius + b.shape.radius;
731
+ if (distance >= radiusSum) {
732
+ return undefined;
733
+ }
734
+ return {
735
+ colliderA: a.id,
736
+ colliderB: b.id,
737
+ bodyA: bodyA.id,
738
+ bodyB: bodyB.id,
739
+ normal: distance > 1e-9 ? scaleVec3(delta, 1 / distance) : [1, 0, 0],
740
+ penetration: radiusSum - distance,
741
+ sensor: a.sensor || b.sensor
742
+ };
743
+ }
744
+ function buildCapsuleBoxContact(a, bodyA, b, bodyB) {
745
+ const capsuleCollider = a.shape.kind === "capsule" ? a : b;
746
+ const capsuleBody = a.shape.kind === "capsule" ? bodyA : bodyB;
747
+ const boxCollider = a.shape.kind === "box" ? a : b;
748
+ const boxBody = a.shape.kind === "box" ? bodyA : bodyB;
749
+ if (capsuleCollider.shape.kind !== "capsule" || boxCollider.shape.kind !== "box") {
750
+ return undefined;
751
+ }
752
+ const bounds = boxCollider.bounds(boxBody.position);
753
+ const closest = closestVerticalSegmentPointToAabb(capsuleBody.position, capsuleCollider.shape.halfHeight, bounds);
754
+ const fromBoxToCapsule = subVec3(closest.segmentPoint, closest.boxPoint);
755
+ const distance = Math.hypot(fromBoxToCapsule[0], fromBoxToCapsule[1], fromBoxToCapsule[2]);
756
+ if (distance >= capsuleCollider.shape.radius) {
757
+ return undefined;
758
+ }
759
+ const normalFromBoxToCapsule = distance > 1e-9 ? scaleVec3(fromBoxToCapsule, 1 / distance) : axisFromBoxCenter(boxBody.position, closest.segmentPoint, boxCollider.shape.halfExtents);
760
+ const normal = a.shape.kind === "box" ? normalFromBoxToCapsule : scaleVec3(normalFromBoxToCapsule, -1);
761
+ return {
762
+ colliderA: a.id,
763
+ colliderB: b.id,
764
+ bodyA: bodyA.id,
765
+ bodyB: bodyB.id,
766
+ normal,
767
+ penetration: capsuleCollider.shape.radius - distance,
768
+ sensor: a.sensor || b.sensor
769
+ };
770
+ }
771
+ function capsuleSegment(position, halfHeight) {
772
+ return {
773
+ start: [position[0], position[1] - halfHeight, position[2]],
774
+ end: [position[0], position[1] + halfHeight, position[2]]
775
+ };
776
+ }
777
+ function closestPointOnSegment(point, segment) {
778
+ const ab = subVec3(segment.end, segment.start);
779
+ const lengthSquared = dotVec3(ab, ab);
780
+ if (lengthSquared <= 1e-9) {
781
+ return segment.start;
782
+ }
783
+ const t = clamp(dotVec3(subVec3(point, segment.start), ab) / lengthSquared, 0, 1);
784
+ return [
785
+ segment.start[0] + ab[0] * t,
786
+ segment.start[1] + ab[1] * t,
787
+ segment.start[2] + ab[2] * t
788
+ ];
789
+ }
790
+ function closestPointsBetweenSegments(a, b) {
791
+ const d1 = subVec3(a.end, a.start);
792
+ const d2 = subVec3(b.end, b.start);
793
+ const r = subVec3(a.start, b.start);
794
+ const aLength = dotVec3(d1, d1);
795
+ const bLength = dotVec3(d2, d2);
796
+ const d12 = dotVec3(d1, d2);
797
+ const d1r = dotVec3(d1, r);
798
+ const d2r = dotVec3(d2, r);
799
+ const denominator = aLength * bLength - d12 * d12;
800
+ let s = denominator <= 1e-9 ? 0 : clamp((d12 * d2r - bLength * d1r) / denominator, 0, 1);
801
+ let t = bLength <= 1e-9 ? 0 : (d12 * s + d2r) / bLength;
802
+ if (t < 0) {
803
+ t = 0;
804
+ s = aLength <= 1e-9 ? 0 : clamp(-d1r / aLength, 0, 1);
805
+ }
806
+ else if (t > 1) {
807
+ t = 1;
808
+ s = aLength <= 1e-9 ? 0 : clamp((d12 - d1r) / aLength, 0, 1);
809
+ }
810
+ return {
811
+ a: [a.start[0] + d1[0] * s, a.start[1] + d1[1] * s, a.start[2] + d1[2] * s],
812
+ b: [b.start[0] + d2[0] * t, b.start[1] + d2[1] * t, b.start[2] + d2[2] * t]
813
+ };
814
+ }
815
+ function closestVerticalSegmentPointToAabb(position, halfHeight, bounds) {
816
+ const segmentMinY = position[1] - halfHeight;
817
+ const segmentMaxY = position[1] + halfHeight;
818
+ let segmentY;
819
+ let boxY;
820
+ if (segmentMaxY < bounds.min[1]) {
821
+ segmentY = segmentMaxY;
822
+ boxY = bounds.min[1];
823
+ }
824
+ else if (segmentMinY > bounds.max[1]) {
825
+ segmentY = segmentMinY;
826
+ boxY = bounds.max[1];
827
+ }
828
+ else {
829
+ segmentY = clamp(position[1], Math.max(segmentMinY, bounds.min[1]), Math.min(segmentMaxY, bounds.max[1]));
830
+ boxY = segmentY;
831
+ }
832
+ const segmentPoint = [position[0], segmentY, position[2]];
833
+ const boxPoint = [
834
+ clamp(position[0], bounds.min[0], bounds.max[0]),
835
+ boxY,
836
+ clamp(position[2], bounds.min[2], bounds.max[2])
837
+ ];
838
+ return { segmentPoint, boxPoint };
839
+ }
840
+ function axisFromBoxCenter(boxPosition, spherePosition, halfExtents) {
841
+ const local = subVec3(spherePosition, boxPosition);
842
+ const distances = [
843
+ [halfExtents[0] - Math.abs(local[0]), [Math.sign(local[0]) || 1, 0, 0]],
844
+ [halfExtents[1] - Math.abs(local[1]), [0, Math.sign(local[1]) || 1, 0]],
845
+ [halfExtents[2] - Math.abs(local[2]), [0, 0, Math.sign(local[2]) || 1]]
846
+ ];
847
+ return distances.reduce((best, candidate) => candidate[0] < best[0] ? candidate : best)[1];
848
+ }
849
+ function clamp(value, min, max) {
850
+ return Math.max(min, Math.min(max, value));
851
+ }
852
+ function supportRadius(collider) {
853
+ switch (collider.shape.kind) {
854
+ case "box":
855
+ return collider.shape.halfExtents[1];
856
+ case "sphere":
857
+ return collider.shape.radius;
858
+ case "capsule":
859
+ return collider.shape.radius + collider.shape.halfHeight;
860
+ case "plane":
861
+ return 0;
862
+ case "mesh": {
863
+ const bounds = collider.bounds([0, 0, 0]);
864
+ return Math.max(Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
865
+ }
866
+ }
867
+ }
868
+ //# sourceMappingURL=PhysicsWorld.js.map