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