@needle-tools/engine 2.36.0-pre → 2.38.0-pre

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 (143) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/needle-engine.d.ts +248 -151
  3. package/dist/needle-engine.js +451 -437
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +82 -82
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/api.d.ts +1 -0
  8. package/lib/engine/api.js +1 -0
  9. package/lib/engine/api.js.map +1 -1
  10. package/lib/engine/debug/debug.d.ts +1 -0
  11. package/lib/engine/debug/debug.js +3 -0
  12. package/lib/engine/debug/debug.js.map +1 -1
  13. package/lib/engine/engine_addressables.d.ts +3 -1
  14. package/lib/engine/engine_addressables.js +12 -5
  15. package/lib/engine/engine_addressables.js.map +1 -1
  16. package/lib/engine/engine_element.js +3 -2
  17. package/lib/engine/engine_element.js.map +1 -1
  18. package/lib/engine/engine_element_overlay.js +4 -3
  19. package/lib/engine/engine_element_overlay.js.map +1 -1
  20. package/lib/engine/engine_gameobject.js +2 -1
  21. package/lib/engine/engine_gameobject.js.map +1 -1
  22. package/lib/engine/engine_input.d.ts +2 -0
  23. package/lib/engine/engine_input.js +14 -3
  24. package/lib/engine/engine_input.js.map +1 -1
  25. package/lib/engine/engine_physics.d.ts +35 -46
  26. package/lib/engine/engine_physics.js +479 -386
  27. package/lib/engine/engine_physics.js.map +1 -1
  28. package/lib/engine/engine_physics.types.d.ts +23 -0
  29. package/lib/engine/engine_physics.types.js +27 -0
  30. package/lib/engine/engine_physics.types.js.map +1 -0
  31. package/lib/engine/engine_serialization_core.d.ts +3 -0
  32. package/lib/engine/engine_serialization_core.js +5 -0
  33. package/lib/engine/engine_serialization_core.js.map +1 -1
  34. package/lib/engine/engine_setup.d.ts +7 -1
  35. package/lib/engine/engine_setup.js +13 -3
  36. package/lib/engine/engine_setup.js.map +1 -1
  37. package/lib/engine/engine_types.d.ts +45 -26
  38. package/lib/engine/engine_types.js +24 -37
  39. package/lib/engine/engine_types.js.map +1 -1
  40. package/lib/engine/engine_util_decorator.d.ts +6 -0
  41. package/lib/engine/engine_util_decorator.js +54 -0
  42. package/lib/engine/engine_util_decorator.js.map +1 -0
  43. package/lib/engine/engine_utils.d.ts +1 -1
  44. package/lib/engine/engine_utils.js +1 -1
  45. package/lib/engine/engine_utils.js.map +1 -1
  46. package/lib/engine/extensions/NEEDLE_gameobject_data.js +2 -0
  47. package/lib/engine/extensions/NEEDLE_gameobject_data.js.map +1 -1
  48. package/lib/engine-components/Animation.d.ts +7 -5
  49. package/lib/engine-components/Animation.js +7 -7
  50. package/lib/engine-components/Animation.js.map +1 -1
  51. package/lib/engine-components/AnimatorController.js +14 -7
  52. package/lib/engine-components/AnimatorController.js.map +1 -1
  53. package/lib/engine-components/BoxHelperComponent.js +1 -0
  54. package/lib/engine-components/BoxHelperComponent.js.map +1 -1
  55. package/lib/engine-components/Camera.d.ts +1 -0
  56. package/lib/engine-components/Camera.js +20 -5
  57. package/lib/engine-components/Camera.js.map +1 -1
  58. package/lib/engine-components/CharacterController.d.ts +31 -0
  59. package/lib/engine-components/CharacterController.js +167 -0
  60. package/lib/engine-components/CharacterController.js.map +1 -0
  61. package/lib/engine-components/Collider.d.ts +16 -5
  62. package/lib/engine-components/Collider.js +45 -23
  63. package/lib/engine-components/Collider.js.map +1 -1
  64. package/lib/engine-components/Component.d.ts +6 -15
  65. package/lib/engine-components/Component.js +7 -112
  66. package/lib/engine-components/Component.js.map +1 -1
  67. package/lib/engine-components/DragControls.js +9 -6
  68. package/lib/engine-components/DragControls.js.map +1 -1
  69. package/lib/engine-components/Light.d.ts +2 -0
  70. package/lib/engine-components/Light.js +13 -2
  71. package/lib/engine-components/Light.js.map +1 -1
  72. package/lib/engine-components/NavMesh.d.ts +0 -5
  73. package/lib/engine-components/NavMesh.js +100 -10
  74. package/lib/engine-components/NavMesh.js.map +1 -1
  75. package/lib/engine-components/NestedGltf.js +2 -0
  76. package/lib/engine-components/NestedGltf.js.map +1 -1
  77. package/lib/engine-components/Renderer.js +4 -0
  78. package/lib/engine-components/Renderer.js.map +1 -1
  79. package/lib/engine-components/RigidBody.d.ts +45 -25
  80. package/lib/engine-components/RigidBody.js +290 -142
  81. package/lib/engine-components/RigidBody.js.map +1 -1
  82. package/lib/engine-components/SmoothFollow.d.ts +2 -1
  83. package/lib/engine-components/SmoothFollow.js +25 -17
  84. package/lib/engine-components/SmoothFollow.js.map +1 -1
  85. package/lib/engine-components/SpatialTrigger.js +1 -1
  86. package/lib/engine-components/SpatialTrigger.js.map +1 -1
  87. package/lib/engine-components/SpectatorCamera.d.ts +1 -0
  88. package/lib/engine-components/SpectatorCamera.js +9 -2
  89. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  90. package/lib/engine-components/SpringJoint.d.ts +0 -13
  91. package/lib/engine-components/SpringJoint.js +42 -41
  92. package/lib/engine-components/SpringJoint.js.map +1 -1
  93. package/lib/engine-components/VideoPlayer.js.map +1 -1
  94. package/lib/engine-components/WebXR.d.ts +1 -0
  95. package/lib/engine-components/WebXR.js +13 -6
  96. package/lib/engine-components/WebXR.js.map +1 -1
  97. package/lib/engine-components/WebXRController.js +12 -6
  98. package/lib/engine-components/WebXRController.js.map +1 -1
  99. package/lib/engine-components/codegen/components.d.ts +4 -3
  100. package/lib/engine-components/codegen/components.js +4 -3
  101. package/lib/engine-components/codegen/components.js.map +1 -1
  102. package/package.json +3 -4
  103. package/src/engine/api.ts +2 -1
  104. package/src/engine/codegen/register_types.js +16 -12
  105. package/src/engine/debug/debug.ts +4 -0
  106. package/src/engine/dist/engine_physics.js +739 -0
  107. package/src/engine/dist/engine_setup.js +777 -0
  108. package/src/engine/engine_addressables.ts +18 -8
  109. package/src/engine/engine_element.ts +3 -2
  110. package/src/engine/engine_element_overlay.ts +4 -3
  111. package/src/engine/engine_gameobject.ts +4 -4
  112. package/src/engine/engine_input.ts +12 -3
  113. package/src/engine/engine_physics.ts +492 -418
  114. package/src/engine/engine_physics.types.ts +28 -0
  115. package/src/engine/engine_serialization_core.ts +8 -1
  116. package/src/engine/engine_setup.ts +31 -18
  117. package/src/engine/engine_types.ts +83 -56
  118. package/src/engine/engine_util_decorator.ts +69 -0
  119. package/src/engine/engine_utils.ts +3 -3
  120. package/src/engine/extensions/NEEDLE_gameobject_data.ts +2 -0
  121. package/src/engine-components/Animation.ts +18 -12
  122. package/src/engine-components/AnimatorController.ts +16 -11
  123. package/src/engine-components/BoxHelperComponent.ts +1 -0
  124. package/src/engine-components/Camera.ts +21 -4
  125. package/src/engine-components/CharacterController.ts +171 -0
  126. package/src/engine-components/Collider.ts +49 -39
  127. package/src/engine-components/Component.ts +15 -130
  128. package/src/engine-components/DragControls.ts +9 -5
  129. package/src/engine-components/Light.ts +17 -3
  130. package/src/engine-components/NavMesh.ts +114 -115
  131. package/src/engine-components/NestedGltf.ts +2 -0
  132. package/src/engine-components/Renderer.ts +5 -1
  133. package/src/engine-components/RigidBody.ts +292 -149
  134. package/src/engine-components/SmoothFollow.ts +21 -18
  135. package/src/engine-components/SpatialTrigger.ts +1 -1
  136. package/src/engine-components/SpectatorCamera.ts +10 -2
  137. package/src/engine-components/SpringJoint.ts +41 -41
  138. package/src/engine-components/VideoPlayer.ts +1 -2
  139. package/src/engine-components/WebXR.ts +15 -6
  140. package/src/engine-components/WebXRController.ts +16 -7
  141. package/src/engine-components/codegen/components.ts +4 -3
  142. package/src/engine-components/dist/CharacterController.js +123 -0
  143. package/src/engine-components/dist/RigidBody.js +458 -0
@@ -1,57 +1,37 @@
1
- import { Body, Box, Constraint, Vec3, World, Sphere as PhysicsSphere, Quaternion as PhysicsQuaternion } from 'cannon-es'
2
- import { Box3, Camera, Intersection, Layers, Matrix4, Mesh, Object3D, Quaternion, Ray, Raycaster, Sphere, Vector2, Vector3 } from 'three'
1
+ import { BasicDepthPacking, Box3, BufferAttribute, BufferGeometry, Camera, Intersection, Layers, LineBasicMaterial, LineSegments, Matrix4, Mesh, NormalAnimationBlendMode, Object3D, Quaternion, Ray, Raycaster, Sphere, Vector2, Vector3 } from 'three'
3
2
  import { Context } from './engine_setup';
4
- import cannonDebugger from 'cannon-es-debugger'
5
3
  import { getParam } from "./engine_utils"
6
- import { getWorldPosition, getWorldQuaternion, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils"
4
+ import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils"
7
5
  import {
8
- IComponent as Component,
9
- IGameObject as GameObject,
6
+ IComponent,
10
7
  ICollider,
11
- IRigidbody as Rigidbody,
12
- $physicsKey,
13
- Collision,
14
- CannonCollision,
15
- ICollisionContext
8
+ IRigidbody,
9
+ Collision,
10
+ ContactPoint
16
11
  } from './engine_types';
17
- import { Shape } from 'cannon-es';
18
12
  import { InstancingUtil } from './engine_instancing';
19
13
  import { foreachComponent } from './engine_gameobject';
20
14
 
15
+ import RAPIER, { ActiveEvents, Collider, ColliderDesc, EventQueue, RigidBody, RigidBodyType, World } from '@dimforge/rapier3d-compat';
16
+ import { CollisionDetectionMode } from '../engine/engine_physics.types';
17
+ export type Rapier = typeof RAPIER;
18
+
21
19
 
22
20
  const debugPhysics = getParam("debugphysics");
21
+ const debugColliderPlacement = getParam("debugphysicscolliders");
23
22
  const debugCollisions = getParam("debugcollisions");
24
23
 
25
- export class BodyOptions {
26
- mass: number = 1;
27
- kinematic: boolean = false;
28
- physicsEvents: boolean = false;
29
- drag: number = 0;
30
- angularDrag: number = 0.05;
31
- sleepThreshold: number = .01;
32
- }
33
-
34
-
35
- // TODO: refactor to return some kind of handle for adding/removing
36
- class PhysicsObject {
37
- obj: Object3D;
38
- parent: Object3D | null;
39
- body: Body | null;
40
- shapes: Array<Shape> = [];
41
- collisonCallback: Function | null = null;
42
-
43
- _hasRigidbody: boolean = false;
44
- _didSleepLastStep: boolean = false;
45
24
 
46
- constructor(obj: Object3D, body: Body | null) {
47
- this.obj = obj;
48
- this.parent = obj.parent;
49
- this.body = body;
50
- if (this.body)
51
- this.body[$physicsKey] = obj;
52
- }
25
+ declare type PhysicsBody = {
26
+ translation(): { x: number, y: number, z: number }
27
+ rotation(): { x: number, y: number, z: number, w: number }
53
28
  }
54
29
 
30
+ const $componentKey = Symbol("needle component");
31
+ const $bodyKey = Symbol("physics body");
32
+ const $colliderRigidbody = Symbol("rigidbody");
33
+ // const $removed = Symbol("removed");
34
+
55
35
  export class RaycastOptions {
56
36
  ray: Ray | undefined = undefined;
57
37
  cam: Camera | undefined | null = undefined;
@@ -225,484 +205,578 @@ export class Physics {
225
205
 
226
206
  // physics simulation
227
207
 
228
- get isUpdating(): boolean { return this._isUpdatingPhysicsWorld; }
208
+ private _tempPosition: Vector3 = new Vector3();
209
+ private _tempQuaternion: Quaternion = new Quaternion();
210
+ private _tempScale: Vector3 = new Vector3();
211
+ private _tempMatrix: Matrix4 = new Matrix4();
212
+
213
+ private static _didLoadPhysicsEngine: boolean = false;
229
214
 
230
215
  private _isUpdatingPhysicsWorld: boolean = false;
216
+ get isUpdating(): boolean { return this._isUpdatingPhysicsWorld; }
217
+
231
218
 
232
219
  private context: Context;
220
+ private world?: World;
221
+ private _hasCreatedWorld: boolean = false;
222
+ private eventQueue?: EventQueue;
223
+ private collisionHandler?: PhysicsCollisionHandler;
233
224
 
234
- private world: World = new World();
235
- private objects: Array<PhysicsObject> = [];
236
225
 
237
- private tempPosition: Vector3 = new Vector3();
238
- private tempQuaternion: Quaternion = new Quaternion();
226
+ // private rigidbodies: Array<IRigidbody | null> = [];
227
+ private objects: IComponent[] = [];
228
+ private bodies: PhysicsBody[] = [];
229
+ // private rigidbodiesLookup: Map<IRigidbody, RigidBody> = new Map<IRigidbody, RigidBody>();
230
+ // private kinematicColliders: Array<IComponent> = [];
231
+ // private rigidbodyLookup: Map<IRigidbody, IComponent[]> = new Map<IRigidbody, IComponent[]>();
232
+ // private objectLookup: Map<Object3D, IRigidbody> = new Map<Object3D, IRigidbody>();
233
+
239
234
 
240
235
  constructor(context: Context) {
241
236
  this.context = context;
242
- this.world.gravity.set(0, -9.82, 0);
243
- if (debugPhysics) {
244
- // https://www.npmjs.com/package/cannon-es-debugger
245
- const opts = {};
246
- opts["onInit"] = (_body: Body, mesh: Mesh, _shape: Shape) => {
247
- // ignore in raycast
248
- mesh.layers.set(-1);
249
- };
250
- cannonDebugger(context.scene, this.world.bodies, opts);
251
- }
252
-
253
- this.world.addEventListener("beginContact", this.onBeginContact.bind(this));
254
- this.world.addEventListener("endContact", this.onEndContact.bind(this))
255
237
  }
256
238
 
257
- public addPreStepListener(listener: (evt) => void) {
258
- this.world.addEventListener("preStep", listener);
239
+ async createWorld() {
240
+ if (this._hasCreatedWorld) {
241
+ console.error("Invalid call to create physics world: world is already created");
242
+ return;
243
+ }
244
+ this._hasCreatedWorld = true;
245
+ if (!Physics._didLoadPhysicsEngine) {
246
+ await RAPIER.init().then(() => RAPIER)
247
+ Physics._didLoadPhysicsEngine = true;
248
+ }
249
+ const gravity = { x: 0.0, y: -9.81, z: 0.0 };
250
+ this.world = new World(gravity);
259
251
  }
260
252
 
261
- public addPostStepListener(listener: (evt) => void) {
262
- this.world.addEventListener("postStep", listener);
253
+ addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
254
+ const obj = collider.gameObject;
255
+ const scale = getWorldScale(obj, this._tempPosition).multiply(size);
256
+ scale.multiplyScalar(0.5);
257
+ const desc = ColliderDesc.cuboid(scale.x, scale.y, scale.z);
258
+ this.createCollider(collider, desc, center);
263
259
  }
264
260
 
265
- public addConstraint(constraint: Constraint) {
266
- this.world.addConstraint(constraint);
261
+ addSphereCollider(collider: ICollider, center: Vector3, radius: number) {
262
+ const obj = collider.gameObject;
263
+ const scale = getWorldScale(obj, this._tempPosition).multiplyScalar(radius);
264
+ const desc = ColliderDesc.ball(scale.x);
265
+ this.createCollider(collider, desc, center);
267
266
  }
268
267
 
269
- public setGravity(vec: Vector3) {
270
- this.world.gravity.set(vec.x, vec.y, vec.z);
268
+ addCapsuleCollider(collider: ICollider, center: Vector3, height: number, radius: number) {
269
+ const obj = collider.gameObject;
270
+ const scale = getWorldScale(obj, this._tempPosition);
271
+ if(debugPhysics) console.log("capsule scale", scale, height, radius);
272
+ const desc = ColliderDesc.capsule(height * .5 * scale.y - radius, radius * scale.x);
273
+ this.createCollider(collider, desc, center);
271
274
  }
272
275
 
273
- public multiplyGravity(vec: Vector3) {
274
- this.world.gravity.x *= vec.x;
275
- this.world.gravity.y *= vec.y;
276
- this.world.gravity.z *= vec.z;
277
- }
276
+ addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean) {
277
+ const geo = mesh.geometry;
278
+ if (!geo) {
279
+ if (debugPhysics) console.warn("Missing mesh geometry", mesh.name);
280
+ return;
281
+ }
278
282
 
279
- public addBody(go: GameObject, body: Body) {
280
- for (let i = 0; i < this.objects.length; i++) {
281
- const reg = this.objects[i];
282
- if (reg.obj === go) {
283
- reg._hasRigidbody = true;
284
- break;
283
+ let positions = geo.getAttribute("position").array as Float32Array;
284
+ const indices = geo.index?.array as Uint32Array;
285
+
286
+ // console.log(geo.center())
287
+
288
+ // scaling seems not supported yet https://github.com/dimforge/rapier/issues/243
289
+ const scale = getWorldScale(mesh, this._tempPosition)
290
+ if (Math.abs(scale.x - 1) > 0.0001 || Math.abs(scale.y - 1) > 0.0001 || Math.abs(scale.z - 1) > 0.0001) {
291
+ console.warn("Your model is using scaled mesh colliders which is not optimal for performance", mesh.name, Object.assign({}, scale), mesh);
292
+ // showBalloonWarning("Your model is using scaled mesh colliders which is not optimal for performance: " + mesh.name + ", consider using unscaled objects");
293
+ const scaledPositions = new Float32Array(positions.length);
294
+ for (let i = 0; i < positions.length; i += 3) {
295
+ scaledPositions[i] = positions[i] * scale.x;
296
+ scaledPositions[i + 1] = positions[i + 1] * scale.y;
297
+ scaledPositions[i + 2] = positions[i + 2] * scale.z;
285
298
  }
299
+ positions = scaledPositions;
286
300
  }
287
- // dont add the body before it has shapes
288
- // otherwise things like forces appplied in the frame before the shapes exist will be zeroed out
289
- if (body.shapes.length > 0)
290
- this.world.addBody(body);
291
- }
292
301
 
293
- public removeBody(go: GameObject, body: Body, removeCompletely: boolean = true) {
294
- this.world.removeBody(body);
295
- for (let i = 0; i < this.objects.length; i++) {
296
- const reg = this.objects[i];
297
- if (reg.obj === go) {
298
- reg._hasRigidbody = false;
299
- if (removeCompletely)
300
- this.objects.splice(i, 1);
301
- break;
302
- }
302
+ const desc = convex ? ColliderDesc.convexMesh(positions) : ColliderDesc.trimesh(positions, indices);
303
+ if (desc) {
304
+ this.createCollider(collider, desc);
305
+ // col.setTranslationWrtParent(new Vector3(0,2,0));
306
+
303
307
  }
304
308
  }
305
309
 
306
- public removeShape(obj: Object3D, shape: Shape) {
307
- for (const reg of this.objects) {
308
- if (reg.obj === obj) {
309
- if (reg.body) {
310
- reg.body.removeShape(shape);
311
- reg.body.updateMassProperties();
312
- }
313
- return;
314
- }
310
+ private createCollider(collider: ICollider, desc: ColliderDesc, center?: Vector3) {
311
+ if (!this.world) throw new Error("Physics world not initialized");
312
+ const matrix = this._tempMatrix;
313
+ const {
314
+ rigidBody,
315
+ useExplicitMassProperties
316
+ } = this.getRigidbody(collider, this._tempMatrix);
317
+
318
+ matrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
319
+ getWorldScale(collider.gameObject, this._tempScale);
320
+ if (center)
321
+ this._tempPosition.add(center).multiply(this._tempScale);
322
+ desc.setTranslation(this._tempPosition.x, this._tempPosition.y, this._tempPosition.z);
323
+ desc.setRotation(this._tempQuaternion);
324
+
325
+ desc.setSensor(collider.isTrigger);
326
+
327
+ // if we want to use explicit mass properties, we need to set the collider density to 0
328
+ // otherwise rapier will compute the mass properties based on the collider shape and density
329
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
330
+ if (useExplicitMassProperties) {
331
+ // desc.setDensity(0);
315
332
  }
316
- }
317
333
 
318
- // TODO: make it work with rigibody in parent
319
- public createBody(obj: Object3D, settings: BodyOptions): Body {
320
- const body = this.internalCreateBody(obj, null);
321
- if (settings.mass)
322
- body.mass = settings.mass;
323
- if (settings.kinematic)
324
- body.type = Body.KINEMATIC;
325
- else body.type = Body.DYNAMIC;
326
- if (settings.drag)
327
- body.linearDamping = settings.drag;
328
- if (settings.angularDrag)
329
- body.angularDamping = settings.angularDrag;
330
- if (settings.sleepThreshold)
331
- body.sleepSpeedLimit = settings.sleepThreshold;
334
+ const col = this.world.createCollider(desc, rigidBody);
335
+ col[$componentKey] = collider;
336
+ collider[$bodyKey] = col;
337
+ col.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
338
+ this.objects.push(collider);
339
+ this.bodies.push(col);
340
+ return col;
341
+ }
342
+
343
+ private getRigidbody(collider: ICollider, _matrix: Matrix4): { rigidBody: RigidBody, useExplicitMassProperties: boolean } {
344
+
345
+ if (!this.world) throw new Error("Physics world not initialized");
346
+ let rigidBody: RigidBody | null = null;
347
+ let useExplicitMassProperties = false;
348
+
349
+ if (collider.attachedRigidbody) {
350
+
351
+ const rb = collider.attachedRigidbody;
352
+ rigidBody = rb[$bodyKey];
353
+ useExplicitMassProperties = true;
354
+ if (!rigidBody) {
355
+ const kinematic = rb.isKinematic && !debugColliderPlacement;
356
+ if (debugPhysics)
357
+ console.log("Create rigidbody", kinematic);
358
+ const rigidBodyDesc = kinematic ? RAPIER.RigidBodyDesc.kinematicPositionBased() : RAPIER.RigidBodyDesc.dynamic();
359
+ const pos = getWorldPosition(collider.attachedRigidbody.gameObject);
360
+ rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
361
+ rigidBodyDesc.setRotation(getWorldQuaternion(collider.attachedRigidbody.gameObject));
362
+ rigidBody = this.world.createRigidBody(rigidBodyDesc);
363
+ this.bodies.push(rigidBody);
364
+ this.objects.push(rb);
365
+ }
366
+ rigidBody[$componentKey] = rb;
367
+ rb[$bodyKey] = rigidBody;
368
+ this.internalUpdateProperties(rb, rigidBody);
369
+ this.getRigidbodyRelativeMatrix(collider.gameObject, rb.gameObject, _matrix);
370
+
371
+ }
372
+ else {
332
373
 
333
- if (body.shapes.length > 0)
334
- this.world.addBody(body);
335
- const po = new PhysicsObject(obj, body);
336
- po._hasRigidbody = true;
337
- this.objects.push(po);
374
+ const rigidBodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased();
375
+ const pos = getWorldPosition(collider.gameObject);
376
+ rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
377
+ rigidBodyDesc.setRotation(getWorldQuaternion(collider.gameObject));
378
+ rigidBody = this.world.createRigidBody(rigidBodyDesc);
379
+ _matrix.identity();
380
+ rigidBody[$componentKey] = null;
338
381
 
339
- if (debugPhysics) {
340
- console.log("created new body", obj.name, body, body.sleepState, this.world.gravity);
341
382
  }
342
383
 
343
- if (settings.physicsEvents)
344
- this.registerCollisionEvents(po);
384
+ collider[$colliderRigidbody] = rigidBody;
345
385
 
346
- return body;
386
+ return { rigidBody: rigidBody, useExplicitMassProperties: useExplicitMassProperties };
347
387
  }
348
388
 
349
- public addBoxCollider(obj: Object3D, trigger: boolean, center: Vector3, size: Vector3, rb: Rigidbody | null): Shape {
350
-
351
- const scale = this.tempPosition;
352
- obj.getWorldScale(scale);
389
+ removeBody(obj: IComponent) {
390
+ const body = obj[$bodyKey];
391
+ obj[$bodyKey] = null;
392
+ if (body && this.world) {
393
+ const index = this.objects.findIndex(o => o === obj);
394
+ if (index >= 0) {
395
+ const body = this.bodies[index];
396
+ this.bodies.splice(index, 1);
397
+ this.objects.splice(index, 1);
353
398
 
354
- const pos = new Vec3(
355
- .5 * scale.x * size.x,
356
- .5 * scale.y * size.y,
357
- .5 * scale.z * size.z
358
- );
359
- const shape = new Box(pos);
360
- shape.collisionResponse = !trigger;
399
+ if (body instanceof Collider) {
400
+ const collider = body as Collider;
401
+ this.world?.removeCollider(collider, true);
361
402
 
362
- center = center.clone();
363
- center.multiply(scale);
403
+ // remove the rigidbody if it doesnt have colliders anymore
404
+ const rb = collider.parent();
405
+ if (rb && rb.numColliders() <= 0) {
406
+ this.world?.removeRigidBody(rb);
407
+ }
408
+ }
409
+ else if (body instanceof RigidBody) {
410
+ // TODO: running this code below causes a crash in rapier
411
+ // const rb = body as RigidBody;
412
+ // console.log("colliders", rb.numColliders())
413
+ // for (let i = 0; i < rb.numColliders(); i++) {
414
+ // const col = rb.collider(i);
415
+ // this.world?.removeCollider(col, true);
416
+ // }
417
+ // console.log("colliders", rb.numColliders(), rb)
418
+ // console.log(rb.handle, rb.userData);
419
+ // if (rb.userData === undefined)
420
+ // this.world?.removeRigidBody(rb);
421
+ }
364
422
 
365
- const body = this.addShape(obj, shape, center, rb);
366
- if (body !== null) {
367
- this.world.addBody(body);
368
- if (this.isAlreadyRegistered(body)) return shape;
369
- const po = new PhysicsObject(obj, body);
370
- this.objects.push(po);
423
+ // check if we need to remove the rigidbody too
424
+ // const col = obj as ICollider;
425
+ // if (col.isCollider && col.attachedRigidbody) {
426
+ // const rb = col.attachedRigidbody[$bodyKey] as RigidBody;
427
+ // if (rb && rb.numColliders() <= 0) {
428
+ // // this.world?.removeRigidBody(rb);
429
+ // }
430
+ // }
431
+ }
371
432
  }
372
- return shape;
373
433
  }
374
434
 
375
- public addSphereCollider(obj: Object3D, center: Vector3, radius: number, rb: Rigidbody | null): Shape {
376
- const scale = this.tempPosition;
377
- obj.getWorldScale(scale);
378
-
379
- const factor = Math.max(scale.x, scale.y, scale.z);
380
- const shape = new PhysicsSphere(radius * factor);
381
- // shape.collisionResponse = !trigger;
382
-
383
- center = center.clone();
384
- center.multiply(scale);
435
+ updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean) {
436
+ if (comp.destroyed || !comp.gameObject) return;
437
+ if (!translation && !rotation) return;
385
438
 
386
- const body = this.addShape(obj, shape, center, rb);
387
- if (body !== null) {
388
- this.world.addBody(body);
389
- if (this.isAlreadyRegistered(body)) return shape;
390
- const po = new PhysicsObject(obj, body);
391
- this.objects.push(po);
439
+ if ((comp as ICollider).isCollider === true) {
440
+ // const collider = comp as ICollider;
441
+ console.warn("TODO: implement updating collider position");
442
+ }
443
+ else {
444
+ const rigidbody = comp as IRigidbody;
445
+ const body = rigidbody[$bodyKey];
446
+ if (body) {
447
+ this.syncPhysicsBody(rigidbody.gameObject, body, translation, rotation);
448
+ }
392
449
  }
393
- return shape;
394
450
  }
395
451
 
396
- public addMeshCollider(_obj: Object3D) {
397
- // see https://github.com/schteppe/js/blob/master/demos/bunny.html
398
- if (debugPhysics)
399
- console.warn("TODO mesh collider not yet supported")
400
- // const geometry: BufferGeometry = obj["geometry"];
401
- // console.log(geometry);
402
- // const size = geometry.boundingBox.max.clone();
403
- // size.sub(geometry.boundingBox.min);
404
- // console.log(size);
405
- // this.addBoxCollider(obj, size);
406
-
407
- // const verts = geometry.getAttribute("position").array;
408
- // const faces = new Array<Array<number>>();
409
-
410
- // console.log(geometry);
411
-
412
- // for (let i = 0; i < geometry.index.array.length; i += 3) {
413
- // const i0 = geometry.index.array[i];
414
- // const i1 = geometry.index.array[i + 1];
415
- // const i2 = geometry.index.array[i + 2];
416
- // const v0 = new Vector3(verts[i0 * 3], verts[i0 * 3 + 1], verts[i0 * 3 + 2]);
417
- // const v1 = new Vector3(verts[i1 * 3], verts[i1 * 3 + 1], verts[i1 * 3 + 2]);
418
- // const v2 = new Vector3(verts[i2 * 3], verts[i2 * 3 + 1], verts[i2 * 3 + 2]);
419
- // const face = [v0, v1, v2];
420
- // faces.push(face);
421
- // }
422
- // const convex = new ConvexBufferGeometry(faces);
423
-
424
- // var shape = new ConvexPolyhedron({ verts, faces });
425
- // this.addShape(obj, shape);
452
+ updateProperties(rigidbody: IRigidbody) {
453
+ const physicsBody = rigidbody[$bodyKey]
454
+ if (physicsBody) {
455
+ this.internalUpdateProperties(rigidbody, physicsBody);
456
+ }
426
457
  }
427
458
 
428
- private isAlreadyRegistered(body: Body): boolean {
429
- for (const obj of this.objects) {
430
- if (obj.body === body) return true;
431
- }
432
- return false;
459
+ internal_getRigidbody(rb: IRigidbody): RigidBody | null {
460
+ return rb[$bodyKey] as RigidBody;
433
461
  }
434
462
 
435
- private readonly tempMat1: Matrix4 = new Matrix4();
436
- private readonly tempMat2: Matrix4 = new Matrix4();
463
+ private internalUpdateProperties(rb: IRigidbody, rigidbody: RigidBody) {
464
+ // continuous collision detection
465
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#continuous-collision-detection
466
+ rigidbody.enableCcd(rb.collisionDetectionMode !== CollisionDetectionMode.Discrete);
467
+ rigidbody.setLinearDamping(rb.drag);
468
+ rigidbody.setAngularDamping(rb.angularDrag);
469
+ rigidbody.setGravityScale(rb.useGravity ? 1 : 0, true);
437
470
 
438
- private addShape(obj: Object3D, shape: Shape, center: Vector3, rb: Rigidbody | null): Body | null {
471
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
472
+ // rigidbody.setAdditionalMass(rb.mass, true);
473
+ // for (let i = 0; i < rigidbody.numColliders(); i++) {
474
+ // const collider = rigidbody.collider(i);
475
+ // if (collider) {
476
+ // collider.setMass(rb.mass);
477
+ // // const density = rb.mass / collider.shape.computeMassProperties().mass;
478
+ // }
479
+ // }
439
480
 
440
- let body: Body | null = null;
481
+ // lock rotations
482
+ rigidbody.setEnabledRotations(!rb.lockRotationX, !rb.lockRotationY, !rb.lockRotationZ, true);
483
+ rigidbody.setEnabledTranslations(!rb.lockPositionX, !rb.lockPositionY, !rb.lockPositionZ, true);
441
484
 
442
- if (rb) {
443
- // if (debugPhysics)
444
- // console.log("get rb body", rb);
445
- rb.initialize();
446
- console.assert(rb.body ? true : false, "rigidbody didn't initialize / produce a physics body", rb);
447
- body = rb.body;
485
+ if (rb.isKinematic) {
486
+ rigidbody.setBodyType(RAPIER.RigidBodyType.KinematicPositionBased);
448
487
  }
449
488
  else {
450
- // console.log("has no rb", obj);
451
- body = this.internalCreateBody(obj, null);
452
- body.type = Body.KINEMATIC;
453
- }
454
-
455
- if (body) {
456
- // console.log(obj.name, obj.position, obj.rotation)
457
-
458
- // the center is serialized from Unity so we need to move it into threejs space
459
- // this should probably happen on export for colliders
460
- center.x *= -1;
461
-
462
- let wp = obj.position;
463
- let wr = obj.quaternion;
464
-
465
- // console.log(obj.name, wp)
466
-
467
- if (rb && rb.gameObject !== obj) {
468
- this.tempMat1.copy(obj.matrixWorld);
469
- this.tempMat2.copy(rb.gameObject.matrixWorld).invert();
470
- this.tempMat1.premultiply(this.tempMat2);
471
- this.tempMat1.decompose(wp, wr, this.tempPosition);
472
- }
473
- else {
474
- wp = getWorldPosition(obj);
475
- const bp = body.position;
476
- wp.x -= bp.x;
477
- wp.y -= bp.y;
478
- wp.z -= bp.z;
479
-
480
- wr = getWorldQuaternion(obj);
481
- const r = new Quaternion(body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
482
- wr.multiply(r.invert());
483
- }
484
- // get rotation difference
485
-
486
- wp.add(center);
487
-
488
-
489
-
490
-
491
- // if (rb) {
492
- // this.tempMat.setPosition(wp);
493
- // this.tempMat.makeRotationFromQuaternion(wr);
494
- // this.tempMat.multiplyMatrices(this.tempMat, rb?.gameObject.matrix);
495
- // this.tempMat.decompose(this.tempPosition, this.tempQuaternion, new Vector3());
496
- // wp.copy(this.tempPosition);
497
- // }
498
-
499
- // wp.applyQuaternion(wr);
500
-
501
- const pos = new Vec3(wp.x, wp.y, wp.z);
502
- const rot = new PhysicsQuaternion(wr.x, wr.y, wr.z, wr.w);
503
- body.addShape(shape, pos, rot);
504
- body.updateMassProperties();
505
- this.world.addBody(body);
489
+ rigidbody.setBodyType(RAPIER.RigidBodyType.Dynamic);
506
490
  }
507
- return body;
508
491
  }
509
492
 
510
493
  // private _lastStepTime: number | undefined = 0;
494
+ private lines?: LineSegments;
511
495
 
512
- public step(deltaTime: number) {
496
+ public step(_deltaTime?: number) {
497
+ if (!this.world) return;
513
498
  this._isUpdatingPhysicsWorld = true;
514
- deltaTime = Math.min(deltaTime, 1 / 30);
515
- this.world.step(deltaTime);
516
- this._isUpdatingPhysicsWorld = false;
517
- if (debugPhysics && this.context.time.frameCount % 60 === 0) {
518
- // console.log("physics world has " + this.world.bodies.length + " bodies", this.world);
499
+ if (!this.eventQueue) {
500
+ this.eventQueue = new EventQueue(false);
519
501
  }
502
+ this.world.step(this.eventQueue);
503
+ this._isUpdatingPhysicsWorld = false;
504
+ this.updateDebugRendering(this.world);
520
505
  }
521
506
 
522
- private temp: Vector3 = new Vector3();
523
- private tempQuat: Quaternion = new Quaternion();
507
+ private updateDebugRendering(world: World) {
508
+ if (debugPhysics || debugColliderPlacement) {
509
+ if (!this.lines) {
510
+ let material = new LineBasicMaterial({
511
+ color: 0xffffff,
512
+ // vertexColors: THREE.VertexColors
513
+ });
514
+ let geometry = new BufferGeometry();
515
+ this.lines = new LineSegments(geometry, material);
516
+ this.context.scene.add(this.lines);
517
+ }
518
+ const buffers = world.debugRender();
519
+ this.lines.geometry.setAttribute('position', new BufferAttribute(buffers.vertices, 3));
520
+ this.lines.geometry.setAttribute('color', new BufferAttribute(buffers.colors, 4));
521
+ }
522
+ }
524
523
 
525
524
  public postStep() {
525
+ if (!this.world) return;
526
526
  this._isUpdatingPhysicsWorld = true;
527
- for (let i = 0; i < this.objects.length; i++) {
528
- const entry = this.objects[i];
529
- const body = entry.body;
530
- if (!body || !body.world) continue;
531
- const obj = entry.obj;
527
+ this.syncObjects();
528
+ this._isUpdatingPhysicsWorld = false;
532
529
 
533
- body.sleepTick(this.context.time.time);
530
+ if (this.eventQueue && !this.collisionHandler) {
531
+ this.collisionHandler = new PhysicsCollisionHandler(this.world, this.eventQueue);
532
+ }
533
+ if (this.collisionHandler) {
534
+ this.collisionHandler.handleCollisionEvents();
535
+ this.collisionHandler.update();
536
+ }
537
+ }
534
538
 
535
- if (debugPhysics) {
536
- if (!entry._didSleepLastStep && body.sleepState === Body.SLEEPING) {
537
- console.log("BODY SLEEPING", body);
538
- }
539
- else if (entry._didSleepLastStep && body.sleepState !== Body.SLEEPING) {
540
- console.log("BODY WOKE UP", body);
541
- }
542
- }
543
- entry._didSleepLastStep = body.sleepState === Body.SLEEPING;
544
- // if(body.sleepState === Body.SLEEPING) {
545
- // console.log("SLEEP", body.name);
546
- // }
547
- // if (body.type == Body.KINEMATIC) continue;
548
-
549
- if (body.type === Body.KINEMATIC) {
550
- const wp = getWorldPosition(obj, this.temp);
551
- body.position.set(wp.x, wp.y, wp.z);
552
- const rot = getWorldQuaternion(obj, this.tempQuat);
553
- body.quaternion.set(rot.x, rot.y, rot.z, rot.w);
539
+ /** sync rendered objects with physics world (except for colliders without rigidbody) */
540
+ private syncObjects() {
541
+ if (debugColliderPlacement) return;
542
+ for (let i = 0; i < this.bodies.length; i++) {
543
+ const obj = this.objects[i];
544
+ const body = this.bodies[i] as Collider;
545
+
546
+ // if the collider is not attached to a rigidbody
547
+ // it means that its kinematic so we need to update its position
548
+ const col = (obj as ICollider);
549
+ if (col?.isCollider === true && !col.attachedRigidbody) {
550
+ const rigidbody = body.parent();
551
+ if (rigidbody)
552
+ this.syncPhysicsBody(obj.gameObject, rigidbody, true, true);
554
553
  continue;
555
554
  }
556
555
 
557
- if ((isNaN(body.position.x) || isNaN(body.position.y) || isNaN(body.position.z))) {
558
- console.error("body position is NaN on", obj.name, "this usually means some colliders are overlapping", body.previousPosition, obj.position);
559
- this.world.removeBody(body);
560
- continue;
556
+
557
+ // sync
558
+ const pos = body.translation();
559
+ const rot = body.rotation();
560
+ // make sure to keep the collider offset
561
+ const center = obj["center"] as Vector3;
562
+ if (center && center.isVector3) {
563
+ this._tempQuaternion.set(rot.x, rot.y, rot.z, rot.w);
564
+ const offset = this._tempPosition.copy(center).applyQuaternion(this._tempQuaternion);
565
+ const scale = getWorldScale(obj.gameObject);
566
+ offset.multiply(scale);
567
+ pos.x -= offset.x;
568
+ pos.y -= offset.y;
569
+ pos.z -= offset.z;
561
570
  }
571
+ setWorldPositionXYZ(obj.gameObject, pos.x, pos.y, pos.z);
572
+ setWorldQuaternionXYZW(obj.gameObject, rot.x, rot.y, rot.z, rot.w);
573
+ }
574
+ }
562
575
 
576
+ private syncPhysicsBody(obj: Object3D, body: RigidBody, translation: boolean, rotation: boolean) {
563
577
 
564
- // when reparenting (e.g. attached to controller) I think it doesnt work with previous parent? need to test again, to tired now
565
- if (entry.parent && obj.parent === entry.parent) {
578
+ // const bodyType = body.bodyType();
579
+ // const previous = physicsBody.translation();
580
+ // const vel = physicsBody.linvel();
566
581
 
567
- setWorldQuaternionXYZW(obj,
568
- body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w
569
- );
582
+ const worldPosition = getWorldPosition(obj, this._tempPosition);
583
+ const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
584
+ const type = body.bodyType();
585
+ switch (type) {
586
+ case RigidBodyType.Fixed:
587
+ case RigidBodyType.KinematicPositionBased:
588
+ case RigidBodyType.KinematicVelocityBased:
589
+ if (translation)
590
+ body.setNextKinematicTranslation(worldPosition);
591
+ if (rotation)
592
+ body.setNextKinematicRotation(worldQuaternion);
593
+ break;
594
+ default:
595
+ if (translation)
596
+ body.setTranslation(worldPosition, false);
597
+ if (rotation)
598
+ body.setRotation(worldQuaternion, false);
599
+ break;
570
600
 
571
- const p = body.position;
572
- setWorldPositionXYZ(obj, p.x, p.y, p.z);
601
+ }
602
+ body.wakeUp();
603
+ // physicsBody.setBodyType(RAPIER.RigidBodyType.Fixed);
604
+ // physicsBody.setLinvel(vel, false);
605
+
606
+ // update velocity
607
+ // const pos = physicsBody.translation();
608
+ // pos.x -= previous.x;
609
+ // pos.y -= previous.y;
610
+ // pos.z -= previous.z;
611
+ // // threhold
612
+ // const t = 1;
613
+ // const canUpdateVelocity = Math.abs(pos.x) < t && Math.abs(pos.y) < t && Math.abs(pos.z) < t;
614
+ // if (canUpdateVelocity) {
615
+ // const damping = 1 + this.context.time.deltaTime;
616
+ // vel.x *= damping;
617
+ // vel.y *= damping;
618
+ // vel.z *= damping;
619
+ // vel.x += pos.x;
620
+ // vel.y += pos.y;
621
+ // vel.z += pos.z;
622
+ // console.log(vel);
623
+ // physicsBody.setLinvel(vel, true);
624
+ // }
625
+ // else if(debugPhysics) console.warn("Movement exceeded threshold, not updating velocity", pos);
573
626
 
574
- if (body.velocity.length() > body.sleepSpeedLimit) {
575
- InstancingUtil.markDirty(obj);
576
- }
577
- // this.worldToLocal.x = body.position.x;
578
- // this.worldToLocal.y = body.position.y;
579
- // this.worldToLocal.z = body.position.z;
580
- // const pos = entry.parent.worldToLocal(this.worldToLocal);
581
- // obj.position.x = pos.x;
582
- // obj.position.y = pos.y;
583
- // obj.position.z = pos.z;
584
-
585
- // if (entry.center) {
586
- // this.rotatedCenter.copy(entry.center);
587
- // const rot = this.tempQuaternion;
588
- // rot.copy(obj.quaternion);
589
- // // obj.getWorldQuaternion(this.tempQuaternion)
590
- // this.rotatedCenter.applyQuaternion(rot);
591
- // obj.getWorldScale(this.tempVector);
592
- // this.rotatedCenter.divide(this.tempVector);
593
- // obj.position.sub(this.rotatedCenter);
594
- // }
627
+ // body.setBodyType(bodyType);
628
+ }
629
+
630
+ private static _matricesBuffer: Matrix4[] = [];
631
+ private getRigidbodyRelativeMatrix(comp: Object3D, rigidbody: Object3D, mat: Matrix4, matrices?: Matrix4[]): Matrix4 {
632
+ // collect all matrices to the rigidbody and then build the rigidbody relative matrix
633
+ if (matrices === undefined) {
634
+ matrices = Physics._matricesBuffer;
635
+ matrices.length = 0;
636
+ }
637
+ if (comp === rigidbody) {
638
+ const scale = getWorldScale(comp, this._tempPosition);
639
+ mat.makeScale(scale.x, scale.y, scale.z);
640
+ for (let i = matrices.length - 1; i >= 0; i--) {
641
+ mat.multiply(matrices[i]);
595
642
  }
643
+ return mat;
596
644
  }
597
- this._isUpdatingPhysicsWorld = false;
645
+ matrices.push(comp.matrix);
646
+ if (comp.parent) {
647
+ this.getRigidbodyRelativeMatrix(comp.parent, rigidbody, mat, matrices);
648
+ }
649
+ return mat;
598
650
  }
599
651
 
600
- private internalCreateBody(obj: Object3D, shape: Shape | undefined | null): Body {
601
652
 
602
- const body = new Body();
603
- body["_owner"] = obj;
604
- body["_name"] = obj.name;
605
- obj.getWorldPosition(this.tempPosition);
606
- const pos = this.tempPosition;
607
- body.position = new Vec3(pos.x, pos.y, pos.z);
653
+ }
608
654
 
609
- const quat = this.tempQuaternion;
610
- obj.getWorldQuaternion(quat);
611
- body.quaternion = new PhysicsQuaternion(quat.x, quat.y, quat.z, quat.w);
612
655
 
613
- body.type = Body.KINEMATIC;
614
- if (shape) {
615
- body.addShape(shape);
616
- body.updateMassProperties();
617
- }
618
- return body;
619
- }
620
656
 
621
- // private findObject(obj: Object3D): PhysicsObject | null {
622
- // for (let i = 0; i < this.objects.length; i++) {
623
- // const entry = this.objects[i];
624
- // if (entry.obj == obj)
625
- // return entry;
626
- // }
627
- // return null;
628
- // }
657
+ /** responsible of processing collision events for the component system */
658
+ class PhysicsCollisionHandler {
629
659
 
630
- private registerCollisionEvents(obj: PhysicsObject) {
631
- if (obj.collisonCallback) this.unregisterCollisionEvents(obj);
632
- if (!obj.body) return;
633
- const evt = evt => this.raiseCollisionEvents(obj.obj, evt);
634
- obj.collisonCallback = evt.bind(this);
635
- obj.body.addEventListener("collide", obj.collisonCallback);
636
- }
660
+ readonly world: World;
661
+ readonly eventQueue: EventQueue;
637
662
 
638
- private unregisterCollisionEvents(obj: PhysicsObject) {
639
- if (!obj.collisonCallback) return;
640
- if (!obj.body) return;
641
- obj.body.removeEventListener("collide", obj.collisonCallback);
663
+ constructor(world: World, eventQueue: EventQueue) {
664
+ this.world = world;
665
+ this.eventQueue = eventQueue;
642
666
  }
643
667
 
644
- private onBeginContact(_) {
645
- // this is called after the object collide event so we dont really need it
646
- // console.log("START");
668
+ private activeCollisions: Array<{ collider: ICollider, component: IComponent, collision: Collision }> = [];
669
+ private activeTriggers: Array<{ collider: ICollider, component: IComponent, otherCollider: ICollider }> = [];
670
+
671
+ handleCollisionEvents() {
672
+ if (!this.eventQueue) return;
673
+ if (!this.world) return;
674
+ this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
675
+ const col1 = this.world!.getCollider(handle1);
676
+ const col2 = this.world!.getCollider(handle2);
677
+ const colliderComponent1 = col1[$componentKey];
678
+ const colliderComponent2 = col2[$componentKey];
679
+ if (debugCollisions)
680
+ console.log("EVT", colliderComponent1.name, colliderComponent2.name, started, col1, col2);
681
+ if (colliderComponent1 && colliderComponent2) {
682
+ if (started) {
683
+ this.onCollisionStarted(colliderComponent1, col1, colliderComponent2, col2);
684
+ this.onCollisionStarted(colliderComponent2, col2, colliderComponent1, col1);
685
+ }
686
+ else {
687
+ this.onCollisionEnded(colliderComponent1, colliderComponent2);
688
+ this.onCollisionEnded(colliderComponent2, colliderComponent1);
689
+ }
690
+ }
691
+ });
647
692
  }
648
693
 
649
- private readonly collisionContext: ICollisionContext = new CollisionContext();
694
+ update() {
695
+ this.onHandleCollisionStay();
696
+ }
650
697
 
651
- private raiseCollisionEvents(obj: Object3D, event: CannonCollision) {
652
- const collision = new Collision(obj, event, this.collisionContext);
653
- if (debugCollisions)
654
- console.log("collision between", event.contact.bi, event.contact.bj, obj, event);
655
- foreachComponent(obj, (c: Component) => {
656
- c.__internalHandleCollision(collision, false);
657
- });
698
+ private onCollisionStarted(self: ICollider, selfBody: Collider, other: ICollider, otherBody: Collider) {
699
+ let collision: Collision | null = null;
658
700
 
659
- // handle triggers
660
- if (collision.collider && !collision.collider.attachedRigidbody && collision.collider.isTrigger) {
661
- const collision2 = new Collision(collision.gameObject, event, this.collisionContext, true);
662
- foreachComponent(collision.gameObject, (c: Component) => {
663
- c.__internalHandleCollision(collision2, true);
701
+ // if one is a trigger we dont get collisions but want to raise the trigger events
702
+ if (self.isTrigger || other.isTrigger) {
703
+ foreachComponent(self.gameObject, (c: IComponent) => {
704
+ if (c.onTriggerEnter) {
705
+ c.onTriggerEnter(other);
706
+ }
707
+ this.activeTriggers.push({ collider: self, component: c, otherCollider: other });
708
+ });
709
+ }
710
+ else {
711
+ const object = self.gameObject;
712
+ // TODO: we dont respect the flip value here!
713
+ this.world.contactPair(selfBody, otherBody, (manifold, _flipped) => {
714
+ foreachComponent(object, (c: IComponent) => {
715
+ if (c.onCollisionEnter) {
716
+ if (!collision) {
717
+ const contacts: Array<ContactPoint> = [];
718
+ const normal = manifold.normal();
719
+ for (let i = 0; i < manifold.numContacts(); i++) {
720
+ const pt1 = manifold.localContactPoint1(i);
721
+ const dist = manifold.contactDist(i);
722
+ if (pt1) {
723
+ const contact = new ContactPoint(pt1, dist, normal);
724
+ contacts.push(contact);
725
+ }
726
+ }
727
+ collision = new Collision(object, other, contacts);
728
+ }
729
+ c.onCollisionEnter.call(c, collision);
730
+ this.activeCollisions.push({ collider: self, component: c, collision });
731
+ }
732
+ });
664
733
  });
665
734
  }
666
735
  }
667
736
 
668
- private onEndContact(args: { bodyA: Body, bodyB: Body }) {
669
- // if(args.bodyB.sleepState !== Body.AWAKE) return;
670
- // console.log("END", BODY_SLEEP_STATES, args.bodyB.sleepState);
671
- const obj1 = args.bodyA[$physicsKey];
672
- const obj2 = args.bodyB[$physicsKey];
673
- // console.log(obj2);
674
-
675
- foreachComponent(obj2, (c: Component) => {
676
- c.__internalHandleExitCollisionEvent(obj1, false);
677
- });
678
-
679
- // TODO: stop iterating when we found the collider
680
- foreachComponent(obj1, c => {
681
- const collider = c as ICollider;
682
- if (collider.isCollider && !collider.attachedRigidbody && collider.isTrigger) {
683
- foreachComponent(collider.gameObject, (c: Component) => {
684
- c.__internalHandleExitCollisionEvent(obj2, true);
685
- });
737
+ private onHandleCollisionStay() {
738
+ for (const active of this.activeCollisions) {
739
+ const c = active.component;
740
+ if (c.activeAndEnabled && c.onCollisionStay) {
741
+ const arg = active.collision;
742
+ c.onCollisionStay(arg);
686
743
  }
687
- });
744
+ }
745
+ for (const active of this.activeTriggers) {
746
+ const c = active.component;
747
+ if (c.activeAndEnabled && c.onTriggerStay) {
748
+ const arg = active.otherCollider;
749
+ c.onTriggerStay(arg);
750
+ }
751
+ }
688
752
  }
689
753
 
690
- }
691
-
692
- export interface IColliderProvider {
693
- getCollider(obj: Object3D): ICollider;
694
- }
695
-
696
- let colliderProvider: IColliderProvider | null = null;
697
- export function registerColliderProvider(prov: IColliderProvider) {
698
- colliderProvider = prov;
699
- }
700
-
701
- class CollisionContext implements ICollisionContext {
702
-
703
- getCollider(obj: Object3D<Event>): ICollider {
704
- return colliderProvider!.getCollider(obj);
754
+ private onCollisionEnded(self: ICollider, other: ICollider) {
755
+ for (let i = 0; i < this.activeCollisions.length; i++) {
756
+ const active = this.activeCollisions[i];
757
+ const collider = active.collider;
758
+ if (collider === self && active.collision.collider === other) {
759
+ const c = active.component;
760
+ this.activeCollisions.splice(i, 1);
761
+ i--;
762
+ if (c.activeAndEnabled && c.onCollisionExit) {
763
+ const collision = active.collision;
764
+ c.onCollisionExit(collision);
765
+ }
766
+ }
767
+ }
768
+ for (let i = 0; i < this.activeTriggers.length; i++) {
769
+ const active = this.activeTriggers[i];
770
+ const collider = active.collider;
771
+ if (collider === self && active.otherCollider === other) {
772
+ const c = active.component;
773
+ this.activeTriggers.splice(i, 1);
774
+ i--;
775
+ if (c.activeAndEnabled && c.onTriggerExit) {
776
+ const collision = active.otherCollider;
777
+ c.onTriggerExit(collision);
778
+ }
779
+ }
780
+ }
705
781
  }
706
-
707
782
  }
708
-