@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,38 +1,15 @@
1
- import { Body, Box, Vec3, World, Sphere as PhysicsSphere, Quaternion as PhysicsQuaternion } from 'cannon-es';
2
- import { Box3, Layers, Matrix4, Quaternion, Raycaster, Sphere, Vector2, Vector3 } from 'three';
3
- import cannonDebugger from 'cannon-es-debugger';
1
+ import { Box3, BufferAttribute, BufferGeometry, Layers, LineBasicMaterial, LineSegments, Matrix4, Quaternion, Raycaster, Sphere, Vector2, Vector3 } from 'three';
4
2
  import { getParam } from "./engine_utils";
5
- import { getWorldPosition, getWorldQuaternion, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils";
6
- import { $physicsKey, Collision } from './engine_types';
7
- import { InstancingUtil } from './engine_instancing';
3
+ import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils";
4
+ import { Collision, ContactPoint } from './engine_types';
8
5
  import { foreachComponent } from './engine_gameobject';
6
+ import RAPIER, { ActiveEvents, Collider, ColliderDesc, EventQueue, RigidBody, World } from '@dimforge/rapier3d-compat';
7
+ import { CollisionDetectionMode } from '../engine/engine_physics.types';
9
8
  const debugPhysics = getParam("debugphysics");
9
+ const debugColliderPlacement = getParam("debugphysicscolliders");
10
10
  const debugCollisions = getParam("debugcollisions");
11
- export class BodyOptions {
12
- mass = 1;
13
- kinematic = false;
14
- physicsEvents = false;
15
- drag = 0;
16
- angularDrag = 0.05;
17
- sleepThreshold = .01;
18
- }
19
- // TODO: refactor to return some kind of handle for adding/removing
20
- class PhysicsObject {
21
- obj;
22
- parent;
23
- body;
24
- shapes = [];
25
- collisonCallback = null;
26
- _hasRigidbody = false;
27
- _didSleepLastStep = false;
28
- constructor(obj, body) {
29
- this.obj = obj;
30
- this.parent = obj.parent;
31
- this.body = body;
32
- if (this.body)
33
- this.body[$physicsKey] = obj;
34
- }
35
- }
11
+ const $componentKey = Symbol("needle component");
12
+ const $bodyKey = Symbol("physics body");
36
13
  export class RaycastOptions {
37
14
  ray = undefined;
38
15
  cam = undefined;
@@ -196,390 +173,343 @@ export class Physics {
196
173
  return results;
197
174
  }
198
175
  // physics simulation
199
- get isUpdating() { return this._isUpdatingPhysicsWorld; }
176
+ _tempPosition = new Vector3();
177
+ _tempQuaternion = new Quaternion();
178
+ _tempMatrix = new Matrix4();
179
+ static _didLoadPhysicsEngine = false;
200
180
  _isUpdatingPhysicsWorld = false;
181
+ get isUpdating() { return this._isUpdatingPhysicsWorld; }
201
182
  context;
202
- world = new World();
183
+ world;
184
+ _hasCreatedWorld = false;
185
+ eventQueue;
186
+ collisionHandler;
187
+ // private rigidbodies: Array<IRigidbody | null> = [];
203
188
  objects = [];
204
- tempPosition = new Vector3();
205
- tempQuaternion = new Quaternion();
189
+ bodies = [];
190
+ // private rigidbodiesLookup: Map<IRigidbody, RigidBody> = new Map<IRigidbody, RigidBody>();
191
+ // private kinematicColliders: Array<IComponent> = [];
192
+ // private rigidbodyLookup: Map<IRigidbody, IComponent[]> = new Map<IRigidbody, IComponent[]>();
193
+ // private objectLookup: Map<Object3D, IRigidbody> = new Map<Object3D, IRigidbody>();
206
194
  constructor(context) {
207
195
  this.context = context;
208
- this.world.gravity.set(0, -9.82, 0);
209
- if (debugPhysics) {
210
- // https://www.npmjs.com/package/cannon-es-debugger
211
- const opts = {};
212
- opts["onInit"] = (_body, mesh, _shape) => {
213
- // ignore in raycast
214
- mesh.layers.set(-1);
215
- };
216
- cannonDebugger(context.scene, this.world.bodies, opts);
217
- }
218
- this.world.addEventListener("beginContact", this.onBeginContact.bind(this));
219
- this.world.addEventListener("endContact", this.onEndContact.bind(this));
220
- }
221
- addPreStepListener(listener) {
222
- this.world.addEventListener("preStep", listener);
223
- }
224
- addPostStepListener(listener) {
225
- this.world.addEventListener("postStep", listener);
226
- }
227
- addConstraint(constraint) {
228
- this.world.addConstraint(constraint);
229
- }
230
- setGravity(vec) {
231
- this.world.gravity.set(vec.x, vec.y, vec.z);
232
- }
233
- multiplyGravity(vec) {
234
- this.world.gravity.x *= vec.x;
235
- this.world.gravity.y *= vec.y;
236
- this.world.gravity.z *= vec.z;
237
- }
238
- addBody(go, body) {
239
- for (let i = 0; i < this.objects.length; i++) {
240
- const reg = this.objects[i];
241
- if (reg.obj === go) {
242
- reg._hasRigidbody = true;
243
- break;
196
+ }
197
+ async createWorld() {
198
+ if (this._hasCreatedWorld) {
199
+ console.error("Invalid call to create physics world: world is already created");
200
+ return;
201
+ }
202
+ this._hasCreatedWorld = true;
203
+ if (!Physics._didLoadPhysicsEngine) {
204
+ await RAPIER.init().then(() => RAPIER);
205
+ Physics._didLoadPhysicsEngine = true;
206
+ }
207
+ const gravity = { x: 0.0, y: -9.81, z: 0.0 };
208
+ this.world = new World(gravity);
209
+ }
210
+ addBoxCollider(collider, center, size) {
211
+ const obj = collider.gameObject;
212
+ const scale = getWorldScale(obj, this._tempPosition).multiply(size);
213
+ scale.multiplyScalar(0.5);
214
+ const desc = ColliderDesc.cuboid(scale.x, scale.y, scale.z);
215
+ this.createCollider(collider, desc, center);
216
+ }
217
+ addSphereCollider(collider, center, radius) {
218
+ const obj = collider.gameObject;
219
+ const scale = getWorldScale(obj, this._tempPosition).multiplyScalar(radius);
220
+ const desc = ColliderDesc.ball(scale.x);
221
+ this.createCollider(collider, desc, center);
222
+ }
223
+ addMeshCollider(collider, mesh, convex) {
224
+ const geo = mesh.geometry;
225
+ if (!geo) {
226
+ if (debugPhysics)
227
+ console.warn("Missing mesh geometry", mesh.name);
228
+ return;
229
+ }
230
+ let positions = geo.getAttribute("position").array;
231
+ const indices = geo.index?.array;
232
+ // console.log(geo.center())
233
+ // scaling seems not supported yet https://github.com/dimforge/rapier/issues/243
234
+ const scale = getWorldScale(mesh, this._tempPosition);
235
+ if (Math.abs(scale.x - 1) > 0.0001 || Math.abs(scale.y - 1) > 0.0001 || Math.abs(scale.z - 1) > 0.0001) {
236
+ console.warn("Your model is using scaled mesh colliders which is not optimal for performance", mesh.name, Object.assign({}, scale), mesh);
237
+ // showBalloonWarning("Your model is using scaled mesh colliders which is not optimal for performance: " + mesh.name + ", consider using unscaled objects");
238
+ const scaledPositions = new Float32Array(positions.length);
239
+ for (let i = 0; i < positions.length; i += 3) {
240
+ scaledPositions[i] = positions[i] * scale.x;
241
+ scaledPositions[i + 1] = positions[i + 1] * scale.y;
242
+ scaledPositions[i + 2] = positions[i + 2] * scale.z;
244
243
  }
244
+ positions = scaledPositions;
245
+ }
246
+ const desc = convex ? ColliderDesc.convexMesh(positions) : ColliderDesc.trimesh(positions, indices);
247
+ if (desc) {
248
+ this.createCollider(collider, desc);
249
+ // col.setTranslationWrtParent(new Vector3(0,2,0));
245
250
  }
246
- // dont add the body before it has shapes
247
- // otherwise things like forces appplied in the frame before the shapes exist will be zeroed out
248
- if (body.shapes.length > 0)
249
- this.world.addBody(body);
250
- }
251
- removeBody(go, body, removeCompletely = true) {
252
- this.world.removeBody(body);
253
- for (let i = 0; i < this.objects.length; i++) {
254
- const reg = this.objects[i];
255
- if (reg.obj === go) {
256
- reg._hasRigidbody = false;
257
- if (removeCompletely)
258
- this.objects.splice(i, 1);
259
- break;
251
+ }
252
+ createCollider(collider, desc, center) {
253
+ if (!this.world)
254
+ throw new Error("Physics world not initialized");
255
+ const matrix = this._tempMatrix;
256
+ const { rigidBody, useExplicitMassProperties } = this.getRigidbody(collider, this._tempMatrix);
257
+ matrix.decompose(this._tempPosition, this._tempQuaternion, new Vector3());
258
+ if (center)
259
+ this._tempPosition.add(center);
260
+ desc.setTranslation(this._tempPosition.x, this._tempPosition.y, this._tempPosition.z);
261
+ desc.setRotation(this._tempQuaternion);
262
+ desc.setSensor(collider.isTrigger);
263
+ // if we want to use explicit mass properties, we need to set the collider density to 0
264
+ // otherwise rapier will compute the mass properties based on the collider shape and density
265
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
266
+ if (useExplicitMassProperties) {
267
+ // desc.setDensity(0);
268
+ }
269
+ const col = this.world.createCollider(desc, rigidBody);
270
+ col[$componentKey] = collider;
271
+ collider[$bodyKey] = col;
272
+ col.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
273
+ this.objects.push(collider);
274
+ this.bodies.push(col);
275
+ return col;
276
+ }
277
+ getRigidbody(collider, _matrix) {
278
+ if (!this.world)
279
+ throw new Error("Physics world not initialized");
280
+ let rigidBody = null;
281
+ let useExplicitMassProperties = false;
282
+ if (collider.attachedRigidbody) {
283
+ const rb = collider.attachedRigidbody;
284
+ rigidBody = rb[$bodyKey];
285
+ useExplicitMassProperties = true;
286
+ if (!rigidBody) {
287
+ const kinematic = rb.isKinematic && !debugColliderPlacement;
288
+ if (debugPhysics)
289
+ console.log("Create rigidbody", kinematic);
290
+ const rigidBodyDesc = kinematic ? RAPIER.RigidBodyDesc.kinematicPositionBased() : RAPIER.RigidBodyDesc.dynamic();
291
+ const pos = getWorldPosition(collider.attachedRigidbody.gameObject);
292
+ rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
293
+ rigidBodyDesc.setRotation(getWorldQuaternion(collider.attachedRigidbody.gameObject));
294
+ rigidBody = this.world.createRigidBody(rigidBodyDesc);
295
+ this.bodies.push(rigidBody);
296
+ this.objects.push(rb);
260
297
  }
298
+ rigidBody[$componentKey] = rb;
299
+ rb[$bodyKey] = rigidBody;
300
+ this.internalUpdateProperties(rb, rigidBody);
301
+ this.getRigidbodyRelativeMatrix(collider.gameObject, rb.gameObject, _matrix);
302
+ }
303
+ else {
304
+ const rigidBodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased();
305
+ const pos = getWorldPosition(collider.gameObject);
306
+ rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
307
+ rigidBodyDesc.setRotation(getWorldQuaternion(collider.gameObject));
308
+ rigidBody = this.world.createRigidBody(rigidBodyDesc);
309
+ _matrix.identity();
310
+ rigidBody[$componentKey] = null;
261
311
  }
312
+ return { rigidBody: rigidBody, useExplicitMassProperties: useExplicitMassProperties };
262
313
  }
263
- removeShape(obj, shape) {
264
- for (const reg of this.objects) {
265
- if (reg.obj === obj) {
266
- if (reg.body) {
267
- reg.body.removeShape(shape);
268
- reg.body.updateMassProperties();
314
+ removeBody(obj) {
315
+ const body = obj[$bodyKey];
316
+ obj[$bodyKey] = null;
317
+ if (body) {
318
+ const index = this.objects.findIndex(o => o === obj);
319
+ if (index >= 0) {
320
+ const body = this.bodies[index];
321
+ this.bodies.splice(index, 1);
322
+ this.objects.splice(index, 1);
323
+ if (body instanceof Collider) {
324
+ this.world?.removeCollider(body, true);
325
+ }
326
+ else if (body instanceof RigidBody) {
327
+ this.world?.removeRigidBody(body);
328
+ }
329
+ // check if we need to remove the rigidbody too
330
+ const col = obj;
331
+ if (col.isCollider && col.attachedRigidbody) {
332
+ const rb = col.attachedRigidbody[$bodyKey];
333
+ if (rb && rb.numColliders() <= 0 && rb.world() === this.world) {
334
+ this.world?.removeRigidBody(rb);
335
+ }
269
336
  }
270
- return;
271
337
  }
272
338
  }
273
339
  }
274
- // TODO: make it work with rigibody in parent
275
- createBody(obj, settings) {
276
- const body = this.internalCreateBody(obj, null);
277
- if (settings.mass)
278
- body.mass = settings.mass;
279
- if (settings.kinematic)
280
- body.type = Body.KINEMATIC;
281
- else
282
- body.type = Body.DYNAMIC;
283
- if (settings.drag)
284
- body.linearDamping = settings.drag;
285
- if (settings.angularDrag)
286
- body.angularDamping = settings.angularDrag;
287
- if (settings.sleepThreshold)
288
- body.sleepSpeedLimit = settings.sleepThreshold;
289
- if (body.shapes.length > 0)
290
- this.world.addBody(body);
291
- const po = new PhysicsObject(obj, body);
292
- po._hasRigidbody = true;
293
- this.objects.push(po);
294
- if (debugPhysics) {
295
- console.log("created new body", obj.name, body, body.sleepState, this.world.gravity);
296
- }
297
- if (settings.physicsEvents)
298
- this.registerCollisionEvents(po);
299
- return body;
300
- }
301
- addBoxCollider(obj, trigger, center, size, rb) {
302
- const scale = this.tempPosition;
303
- obj.getWorldScale(scale);
304
- const pos = new Vec3(.5 * scale.x * size.x, .5 * scale.y * size.y, .5 * scale.z * size.z);
305
- const shape = new Box(pos);
306
- shape.collisionResponse = !trigger;
307
- center = center.clone();
308
- center.multiply(scale);
309
- const body = this.addShape(obj, shape, center, rb);
310
- if (body !== null) {
311
- this.world.addBody(body);
312
- if (this.isAlreadyRegistered(body))
313
- return shape;
314
- const po = new PhysicsObject(obj, body);
315
- this.objects.push(po);
316
- }
317
- return shape;
318
- }
319
- addSphereCollider(obj, center, radius, rb) {
320
- const scale = this.tempPosition;
321
- obj.getWorldScale(scale);
322
- const factor = Math.max(scale.x, scale.y, scale.z);
323
- const shape = new PhysicsSphere(radius * factor);
324
- // shape.collisionResponse = !trigger;
325
- center = center.clone();
326
- center.multiply(scale);
327
- const body = this.addShape(obj, shape, center, rb);
328
- if (body !== null) {
329
- this.world.addBody(body);
330
- if (this.isAlreadyRegistered(body))
331
- return shape;
332
- const po = new PhysicsObject(obj, body);
333
- this.objects.push(po);
334
- }
335
- return shape;
336
- }
337
- addMeshCollider(_obj) {
338
- // see https://github.com/schteppe/js/blob/master/demos/bunny.html
339
- if (debugPhysics)
340
- console.warn("TODO mesh collider not yet supported");
341
- // const geometry: BufferGeometry = obj["geometry"];
342
- // console.log(geometry);
343
- // const size = geometry.boundingBox.max.clone();
344
- // size.sub(geometry.boundingBox.min);
345
- // console.log(size);
346
- // this.addBoxCollider(obj, size);
347
- // const verts = geometry.getAttribute("position").array;
348
- // const faces = new Array<Array<number>>();
349
- // console.log(geometry);
350
- // for (let i = 0; i < geometry.index.array.length; i += 3) {
351
- // const i0 = geometry.index.array[i];
352
- // const i1 = geometry.index.array[i + 1];
353
- // const i2 = geometry.index.array[i + 2];
354
- // const v0 = new Vector3(verts[i0 * 3], verts[i0 * 3 + 1], verts[i0 * 3 + 2]);
355
- // const v1 = new Vector3(verts[i1 * 3], verts[i1 * 3 + 1], verts[i1 * 3 + 2]);
356
- // const v2 = new Vector3(verts[i2 * 3], verts[i2 * 3 + 1], verts[i2 * 3 + 2]);
357
- // const face = [v0, v1, v2];
358
- // faces.push(face);
340
+ updateBody(comp, translation, rotation) {
341
+ if (comp.destroyed || !comp.gameObject)
342
+ return;
343
+ if (!translation && !rotation)
344
+ return;
345
+ if (comp.isCollider === true) {
346
+ // const collider = comp as ICollider;
347
+ console.warn("TODO: implement updating collider position");
348
+ }
349
+ else {
350
+ const rigidbody = comp;
351
+ const body = rigidbody[$bodyKey];
352
+ if (body) {
353
+ this.syncPhysicsBody(rigidbody.gameObject, body, translation, rotation);
354
+ }
355
+ }
356
+ }
357
+ updateProperties(rigidbody) {
358
+ const physicsBody = rigidbody[$bodyKey];
359
+ if (physicsBody) {
360
+ this.internalUpdateProperties(rigidbody, physicsBody);
361
+ }
362
+ }
363
+ internal_getRigidbody(rb) {
364
+ return rb[$bodyKey];
365
+ }
366
+ internalUpdateProperties(rb, rigidbody) {
367
+ // continuous collision detection
368
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#continuous-collision-detection
369
+ rigidbody.enableCcd(rb.collisionDetectionMode !== CollisionDetectionMode.Discrete);
370
+ rigidbody.setLinearDamping(rb.drag);
371
+ rigidbody.setAngularDamping(rb.angularDrag);
372
+ rigidbody.setGravityScale(rb.useGravity ? 1 : 0, true);
373
+ // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
374
+ // rigidbody.setAdditionalMass(rb.mass, true);
375
+ // for (let i = 0; i < rigidbody.numColliders(); i++) {
376
+ // const collider = rigidbody.collider(i);
377
+ // if (collider) {
378
+ // collider.setMass(rb.mass);
379
+ // // const density = rb.mass / collider.shape.computeMassProperties().mass;
380
+ // }
359
381
  // }
360
- // const convex = new ConvexBufferGeometry(faces);
361
- // var shape = new ConvexPolyhedron({ verts, faces });
362
- // this.addShape(obj, shape);
363
- }
364
- isAlreadyRegistered(body) {
365
- for (const obj of this.objects) {
366
- if (obj.body === body)
367
- return true;
368
- }
369
- return false;
370
- }
371
- tempMat1 = new Matrix4();
372
- tempMat2 = new Matrix4();
373
- addShape(obj, shape, center, rb) {
374
- let body = null;
375
- if (rb) {
376
- // if (debugPhysics)
377
- // console.log("get rb body", rb);
378
- rb.initialize();
379
- console.assert(rb.body ? true : false, "rigidbody didn't initialize / produce a physics body", rb);
380
- body = rb.body;
382
+ // lock rotations
383
+ rigidbody.setEnabledRotations(!rb.lockRotationX, !rb.lockRotationY, !rb.lockRotationZ, true);
384
+ rigidbody.setEnabledTranslations(!rb.lockPositionX, !rb.lockPositionY, !rb.lockPositionZ, true);
385
+ if (rb.isKinematic) {
386
+ rigidbody.setBodyType(RAPIER.RigidBodyType.KinematicPositionBased);
381
387
  }
382
388
  else {
383
- // console.log("has no rb", obj);
384
- body = this.internalCreateBody(obj, null);
385
- body.type = Body.KINEMATIC;
389
+ rigidbody.setBodyType(RAPIER.RigidBodyType.Dynamic);
386
390
  }
387
- if (body) {
388
- // console.log(obj.name, obj.position, obj.rotation)
389
- // the center is serialized from Unity so we need to move it into threejs space
390
- // this should probably happen on export for colliders
391
- center.x *= -1;
392
- let wp = obj.position;
393
- let wr = obj.quaternion;
394
- // console.log(obj.name, wp)
395
- if (rb && rb.gameObject !== obj) {
396
- this.tempMat1.copy(obj.matrixWorld);
397
- this.tempMat2.copy(rb.gameObject.matrixWorld).invert();
398
- this.tempMat1.premultiply(this.tempMat2);
399
- this.tempMat1.decompose(wp, wr, this.tempPosition);
400
- }
401
- else {
402
- wp = getWorldPosition(obj);
403
- const bp = body.position;
404
- wp.x -= bp.x;
405
- wp.y -= bp.y;
406
- wp.z -= bp.z;
407
- wr = getWorldQuaternion(obj);
408
- const r = new Quaternion(body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
409
- wr.multiply(r.invert());
410
- }
411
- // get rotation difference
412
- wp.add(center);
413
- // if (rb) {
414
- // this.tempMat.setPosition(wp);
415
- // this.tempMat.makeRotationFromQuaternion(wr);
416
- // this.tempMat.multiplyMatrices(this.tempMat, rb?.gameObject.matrix);
417
- // this.tempMat.decompose(this.tempPosition, this.tempQuaternion, new Vector3());
418
- // wp.copy(this.tempPosition);
419
- // }
420
- // wp.applyQuaternion(wr);
421
- const pos = new Vec3(wp.x, wp.y, wp.z);
422
- const rot = new PhysicsQuaternion(wr.x, wr.y, wr.z, wr.w);
423
- body.addShape(shape, pos, rot);
424
- body.updateMassProperties();
425
- this.world.addBody(body);
426
- }
427
- return body;
428
391
  }
429
392
  // private _lastStepTime: number | undefined = 0;
430
- step(deltaTime) {
393
+ lines;
394
+ step(_deltaTime) {
395
+ if (!this.world)
396
+ return;
431
397
  this._isUpdatingPhysicsWorld = true;
432
- deltaTime = Math.min(deltaTime, 1 / 30);
433
- this.world.step(deltaTime);
398
+ if (!this.eventQueue) {
399
+ this.eventQueue = new EventQueue(false);
400
+ }
401
+ this.world.step(this.eventQueue);
434
402
  this._isUpdatingPhysicsWorld = false;
435
- if (debugPhysics && this.context.time.frameCount % 60 === 0) {
436
- // console.log("physics world has " + this.world.bodies.length + " bodies", this.world);
403
+ this.updateDebugRendering(this.world);
404
+ }
405
+ updateDebugRendering(world) {
406
+ if (debugPhysics || debugColliderPlacement) {
407
+ if (!this.lines) {
408
+ let material = new LineBasicMaterial({
409
+ color: 0xffffff,
410
+ // vertexColors: THREE.VertexColors
411
+ });
412
+ let geometry = new BufferGeometry();
413
+ this.lines = new LineSegments(geometry, material);
414
+ this.context.scene.add(this.lines);
415
+ }
416
+ const buffers = world.debugRender();
417
+ this.lines.geometry.setAttribute('position', new BufferAttribute(buffers.vertices, 3));
418
+ this.lines.geometry.setAttribute('color', new BufferAttribute(buffers.colors, 4));
437
419
  }
438
420
  }
439
- temp = new Vector3();
440
- tempQuat = new Quaternion();
441
421
  postStep() {
422
+ if (!this.world)
423
+ return;
442
424
  this._isUpdatingPhysicsWorld = true;
443
- for (let i = 0; i < this.objects.length; i++) {
444
- const entry = this.objects[i];
445
- const body = entry.body;
446
- if (!body || !body.world)
447
- continue;
448
- const obj = entry.obj;
449
- body.sleepTick(this.context.time.time);
450
- if (debugPhysics) {
451
- if (!entry._didSleepLastStep && body.sleepState === Body.SLEEPING) {
452
- console.log("BODY SLEEPING", body);
453
- }
454
- else if (entry._didSleepLastStep && body.sleepState !== Body.SLEEPING) {
455
- console.log("BODY WOKE UP", body);
456
- }
457
- }
458
- entry._didSleepLastStep = body.sleepState === Body.SLEEPING;
459
- // if(body.sleepState === Body.SLEEPING) {
460
- // console.log("SLEEP", body.name);
461
- // }
462
- // if (body.type == Body.KINEMATIC) continue;
463
- if (body.type === Body.KINEMATIC) {
464
- const wp = getWorldPosition(obj, this.temp);
465
- body.position.set(wp.x, wp.y, wp.z);
466
- const rot = getWorldQuaternion(obj, this.tempQuat);
467
- body.quaternion.set(rot.x, rot.y, rot.z, rot.w);
468
- continue;
469
- }
470
- if ((isNaN(body.position.x) || isNaN(body.position.y) || isNaN(body.position.z))) {
471
- console.error("body position is NaN on", obj.name, "this usually means some colliders are overlapping", body.previousPosition, obj.position);
472
- this.world.removeBody(body);
473
- continue;
474
- }
475
- // when reparenting (e.g. attached to controller) I think it doesnt work with previous parent? need to test again, to tired now
476
- if (entry.parent && obj.parent === entry.parent) {
477
- setWorldQuaternionXYZW(obj, body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
478
- const p = body.position;
479
- setWorldPositionXYZ(obj, p.x, p.y, p.z);
480
- if (body.velocity.length() > body.sleepSpeedLimit) {
481
- InstancingUtil.markDirty(obj);
482
- }
483
- // this.worldToLocal.x = body.position.x;
484
- // this.worldToLocal.y = body.position.y;
485
- // this.worldToLocal.z = body.position.z;
486
- // const pos = entry.parent.worldToLocal(this.worldToLocal);
487
- // obj.position.x = pos.x;
488
- // obj.position.y = pos.y;
489
- // obj.position.z = pos.z;
490
- // if (entry.center) {
491
- // this.rotatedCenter.copy(entry.center);
492
- // const rot = this.tempQuaternion;
493
- // rot.copy(obj.quaternion);
494
- // // obj.getWorldQuaternion(this.tempQuaternion)
495
- // this.rotatedCenter.applyQuaternion(rot);
496
- // obj.getWorldScale(this.tempVector);
497
- // this.rotatedCenter.divide(this.tempVector);
498
- // obj.position.sub(this.rotatedCenter);
499
- // }
500
- }
501
- }
425
+ this.syncObjects();
502
426
  this._isUpdatingPhysicsWorld = false;
427
+ if (this.eventQueue && !this.collisionHandler) {
428
+ this.collisionHandler = new PhysicsCollisionHandler(this.world, this.eventQueue);
429
+ }
430
+ if (this.collisionHandler) {
431
+ this.collisionHandler.handleCollisionEvents();
432
+ this.collisionHandler.update();
433
+ }
503
434
  }
504
- internalCreateBody(obj, shape) {
505
- const body = new Body();
506
- body["_owner"] = obj;
507
- body["_name"] = obj.name;
508
- obj.getWorldPosition(this.tempPosition);
509
- const pos = this.tempPosition;
510
- body.position = new Vec3(pos.x, pos.y, pos.z);
511
- const quat = this.tempQuaternion;
512
- obj.getWorldQuaternion(quat);
513
- body.quaternion = new PhysicsQuaternion(quat.x, quat.y, quat.z, quat.w);
514
- body.type = Body.KINEMATIC;
515
- if (shape) {
516
- body.addShape(shape);
517
- body.updateMassProperties();
518
- }
519
- return body;
520
- }
521
- // private findObject(obj: Object3D): PhysicsObject | null {
522
- // for (let i = 0; i < this.objects.length; i++) {
523
- // const entry = this.objects[i];
524
- // if (entry.obj == obj)
525
- // return entry;
526
- // }
527
- // return null;
528
- // }
529
- registerCollisionEvents(obj) {
530
- if (obj.collisonCallback)
531
- this.unregisterCollisionEvents(obj);
532
- if (!obj.body)
533
- return;
534
- const evt = evt => this.raiseCollisionEvents(obj.obj, evt);
535
- obj.collisonCallback = evt.bind(this);
536
- obj.body.addEventListener("collide", obj.collisonCallback);
537
- }
538
- unregisterCollisionEvents(obj) {
539
- if (!obj.collisonCallback)
540
- return;
541
- if (!obj.body)
435
+ /** sync rendered objects with physics world (except for colliders without rigidbody) */
436
+ syncObjects() {
437
+ if (debugColliderPlacement)
542
438
  return;
543
- obj.body.removeEventListener("collide", obj.collisonCallback);
544
- }
545
- onBeginContact(_) {
546
- // this is called after the object collide event so we dont really need it
547
- // console.log("START");
548
- }
549
- collisionContext = new CollisionContext();
550
- raiseCollisionEvents(obj, event) {
551
- const collision = new Collision(obj, event, this.collisionContext);
552
- if (debugCollisions)
553
- console.log("collision between", event.contact.bi, event.contact.bj, obj, event);
554
- foreachComponent(obj, (c) => {
555
- c.__internalHandleCollision(collision, false);
556
- });
557
- // handle triggers
558
- if (collision.collider && !collision.collider.attachedRigidbody && collision.collider.isTrigger) {
559
- const collision2 = new Collision(collision.gameObject, event, this.collisionContext, true);
560
- foreachComponent(collision.gameObject, (c) => {
561
- c.__internalHandleCollision(collision2, true);
562
- });
439
+ for (let i = 0; i < this.bodies.length; i++) {
440
+ const obj = this.objects[i];
441
+ const body = this.bodies[i];
442
+ // if the collider is not attached to a rigidbody
443
+ // it means that its kinematic so we need to update its position
444
+ const col = obj;
445
+ if (col?.isCollider === true && !col.attachedRigidbody) {
446
+ const rigidbody = body.parent();
447
+ if (rigidbody)
448
+ this.syncPhysicsBody(obj.gameObject, rigidbody, true, true);
449
+ continue;
450
+ }
451
+ // sync
452
+ const pos = body.translation();
453
+ setWorldPositionXYZ(obj.gameObject, pos.x, pos.y, pos.z);
454
+ const rot = body.rotation();
455
+ setWorldQuaternionXYZW(obj.gameObject, rot.x, rot.y, rot.z, rot.w);
563
456
  }
564
457
  }
565
- onEndContact(args) {
566
- // if(args.bodyB.sleepState !== Body.AWAKE) return;
567
- // console.log("END", BODY_SLEEP_STATES, args.bodyB.sleepState);
568
- const obj1 = args.bodyA[$physicsKey];
569
- const obj2 = args.bodyB[$physicsKey];
570
- // console.log(obj2);
571
- foreachComponent(obj2, (c) => {
572
- c.__internalHandleExitCollisionEvent(obj1, false);
573
- });
574
- // TODO: stop iterating when we found the collider
575
- foreachComponent(obj1, c => {
576
- const collider = c;
577
- if (collider.isCollider && !collider.attachedRigidbody && collider.isTrigger) {
578
- foreachComponent(collider.gameObject, (c) => {
579
- c.__internalHandleExitCollisionEvent(obj2, true);
580
- });
458
+ syncPhysicsBody(obj, body, translation, rotation) {
459
+ // const bodyType = body.bodyType();
460
+ // const previous = physicsBody.translation();
461
+ // const vel = physicsBody.linvel();
462
+ const worldPosition = getWorldPosition(obj, this._tempPosition);
463
+ const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
464
+ // physicsBody.setBodyType(RAPIER.RigidBodyType.Fixed);
465
+ if (translation)
466
+ body.setTranslation(worldPosition, false);
467
+ if (rotation)
468
+ body.setRotation(worldQuaternion, false);
469
+ // physicsBody.setLinvel(vel, false);
470
+ body.wakeUp();
471
+ // update velocity
472
+ // const pos = physicsBody.translation();
473
+ // pos.x -= previous.x;
474
+ // pos.y -= previous.y;
475
+ // pos.z -= previous.z;
476
+ // // threhold
477
+ // const t = 1;
478
+ // const canUpdateVelocity = Math.abs(pos.x) < t && Math.abs(pos.y) < t && Math.abs(pos.z) < t;
479
+ // if (canUpdateVelocity) {
480
+ // const damping = 1 + this.context.time.deltaTime;
481
+ // vel.x *= damping;
482
+ // vel.y *= damping;
483
+ // vel.z *= damping;
484
+ // vel.x += pos.x;
485
+ // vel.y += pos.y;
486
+ // vel.z += pos.z;
487
+ // console.log(vel);
488
+ // physicsBody.setLinvel(vel, true);
489
+ // }
490
+ // else if(debugPhysics) console.warn("Movement exceeded threshold, not updating velocity", pos);
491
+ // body.setBodyType(bodyType);
492
+ }
493
+ static _matricesBuffer = [];
494
+ getRigidbodyRelativeMatrix(comp, rigidbody, mat, matrices) {
495
+ // collect all matrices to the rigidbody and then build the rigidbody relative matrix
496
+ if (matrices === undefined) {
497
+ matrices = Physics._matricesBuffer;
498
+ matrices.length = 0;
499
+ }
500
+ if (comp === rigidbody) {
501
+ const scale = getWorldScale(comp, this._tempPosition);
502
+ mat.makeScale(scale.x, scale.y, scale.z);
503
+ for (let i = matrices.length - 1; i >= 0; i--) {
504
+ mat.multiply(matrices[i]);
581
505
  }
582
- });
506
+ return mat;
507
+ }
508
+ matrices.push(comp.matrix);
509
+ if (comp.parent) {
510
+ this.getRigidbodyRelativeMatrix(comp.parent, rigidbody, mat, matrices);
511
+ }
512
+ return mat;
583
513
  }
584
514
  }
585
515
  let colliderProvider = null;
@@ -591,4 +521,122 @@ class CollisionContext {
591
521
  return colliderProvider.getCollider(obj);
592
522
  }
593
523
  }
524
+ /** responsible of processing collision events for the component system */
525
+ class PhysicsCollisionHandler {
526
+ world;
527
+ eventQueue;
528
+ constructor(world, eventQueue) {
529
+ this.world = world;
530
+ this.eventQueue = eventQueue;
531
+ }
532
+ activeCollisions = [];
533
+ activeTriggers = [];
534
+ handleCollisionEvents() {
535
+ if (!this.eventQueue)
536
+ return;
537
+ if (!this.world)
538
+ return;
539
+ this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
540
+ const col1 = this.world.getCollider(handle1);
541
+ const col2 = this.world.getCollider(handle2);
542
+ const colliderComponent1 = col1[$componentKey];
543
+ const colliderComponent2 = col2[$componentKey];
544
+ // console.log("EVT", colliderComponent1.name, colliderComponent2.name, started);
545
+ if (colliderComponent1 && colliderComponent2) {
546
+ if (started) {
547
+ this.onCollisionStarted(colliderComponent1, col1, colliderComponent2, col2);
548
+ this.onCollisionStarted(colliderComponent2, col2, colliderComponent1, col1);
549
+ }
550
+ else {
551
+ this.onCollisionEnded(colliderComponent1, colliderComponent2);
552
+ this.onCollisionEnded(colliderComponent2, colliderComponent1);
553
+ }
554
+ }
555
+ });
556
+ }
557
+ update() {
558
+ this.onHandleCollisionStay();
559
+ }
560
+ onCollisionStarted(self, selfBody, other, otherBody) {
561
+ let collision = null;
562
+ // if one is a trigger we dont get collisions but want to raise the trigger events
563
+ if (self.isTrigger || other.isTrigger) {
564
+ foreachComponent(self.gameObject, (c) => {
565
+ if (c.onTriggerEnter) {
566
+ c.onTriggerEnter(other);
567
+ }
568
+ this.activeTriggers.push({ collider: self, component: c, otherCollider: other });
569
+ });
570
+ }
571
+ else {
572
+ const object = self.gameObject;
573
+ // TODO: we dont respect the flip value here!
574
+ this.world.contactPair(selfBody, otherBody, (manifold, _flipped) => {
575
+ foreachComponent(object, (c) => {
576
+ if (c.onCollisionEnter) {
577
+ if (!collision) {
578
+ const contacts = [];
579
+ const normal = manifold.normal();
580
+ for (let i = 0; i < manifold.numContacts(); i++) {
581
+ const pt1 = manifold.localContactPoint1(i);
582
+ const dist = manifold.contactDist(i);
583
+ if (pt1) {
584
+ const contact = new ContactPoint(pt1, dist, normal);
585
+ contacts.push(contact);
586
+ }
587
+ }
588
+ collision = new Collision(object, other, contacts);
589
+ }
590
+ c.onCollisionEnter.call(c, collision);
591
+ this.activeCollisions.push({ collider: self, component: c, collision });
592
+ }
593
+ });
594
+ });
595
+ }
596
+ }
597
+ onHandleCollisionStay() {
598
+ for (const active of this.activeCollisions) {
599
+ const c = active.component;
600
+ if (c.activeAndEnabled && c.onCollisionStay) {
601
+ const arg = active.collision;
602
+ c.onCollisionStay(arg);
603
+ }
604
+ }
605
+ for (const active of this.activeTriggers) {
606
+ const c = active.component;
607
+ if (c.activeAndEnabled && c.onTriggerStay) {
608
+ const arg = active.collider;
609
+ c.onTriggerStay(arg);
610
+ }
611
+ }
612
+ }
613
+ onCollisionEnded(self, other) {
614
+ for (let i = 0; i < this.activeCollisions.length; i++) {
615
+ const active = this.activeCollisions[i];
616
+ const collider = active.collider;
617
+ if (collider === self && active.collision.collider === other) {
618
+ const c = active.component;
619
+ this.activeCollisions.splice(i, 1);
620
+ i--;
621
+ if (c.activeAndEnabled && c.onCollisionExit) {
622
+ const collision = active.collision;
623
+ c.onCollisionExit(collision);
624
+ }
625
+ }
626
+ }
627
+ for (let i = 0; i < this.activeTriggers.length; i++) {
628
+ const active = this.activeTriggers[i];
629
+ const collider = active.collider;
630
+ if (collider === self && active.otherCollider === other) {
631
+ const c = active.component;
632
+ this.activeTriggers.splice(i, 1);
633
+ i--;
634
+ if (c.activeAndEnabled && c.onTriggerExit) {
635
+ const collision = active.otherCollider;
636
+ c.onTriggerExit(collision);
637
+ }
638
+ }
639
+ }
640
+ }
641
+ }
594
642
  //# sourceMappingURL=engine_physics.js.map