@needle-tools/engine 2.35.5-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 (168) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/needle-engine.d.ts +312 -232
  3. package/dist/needle-engine.js +456 -437
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +81 -76
  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/debug/debug_overlay.js +12 -1
  14. package/lib/engine/debug/debug_overlay.js.map +1 -1
  15. package/lib/engine/engine_element_loading.js +1 -1
  16. package/lib/engine/engine_element_loading.js.map +1 -1
  17. package/lib/engine/engine_gameobject.d.ts +1 -0
  18. package/lib/engine/engine_gameobject.js +15 -2
  19. package/lib/engine/engine_gameobject.js.map +1 -1
  20. package/lib/engine/engine_gltf_builtin_components.js +4 -0
  21. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  22. package/lib/engine/engine_mainloop_utils.d.ts +1 -1
  23. package/lib/engine/engine_mainloop_utils.js +7 -3
  24. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  25. package/lib/engine/engine_physics.d.ts +49 -57
  26. package/lib/engine/engine_physics.js +464 -417
  27. package/lib/engine/engine_physics.js.map +1 -1
  28. package/lib/engine/engine_physics.types.d.ts +16 -0
  29. package/lib/engine/engine_physics.types.js +19 -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 +19 -6
  33. package/lib/engine/engine_serialization_core.js.map +1 -1
  34. package/lib/engine/engine_setup.js +4 -2
  35. package/lib/engine/engine_setup.js.map +1 -1
  36. package/lib/engine/engine_time.d.ts +1 -0
  37. package/lib/engine/engine_time.js +1 -0
  38. package/lib/engine/engine_time.js.map +1 -1
  39. package/lib/engine/engine_types.d.ts +46 -26
  40. package/lib/engine/engine_types.js +24 -37
  41. package/lib/engine/engine_types.js.map +1 -1
  42. package/lib/engine/engine_typestore.d.ts +1 -0
  43. package/lib/engine/engine_typestore.js +1 -0
  44. package/lib/engine/engine_typestore.js.map +1 -1
  45. package/lib/engine/engine_util_decorator.d.ts +6 -0
  46. package/lib/engine/engine_util_decorator.js +54 -0
  47. package/lib/engine/engine_util_decorator.js.map +1 -0
  48. package/lib/engine/engine_utils.d.ts +1 -1
  49. package/lib/engine/engine_utils.js +2 -2
  50. package/lib/engine/engine_utils.js.map +1 -1
  51. package/lib/engine/extensions/NEEDLE_animator_controller_model.js.map +1 -1
  52. package/lib/engine/extensions/NEEDLE_gameobject_data.js +2 -0
  53. package/lib/engine/extensions/NEEDLE_gameobject_data.js.map +1 -1
  54. package/lib/engine/extensions/NEEDLE_techniques_webgl.js +5 -0
  55. package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
  56. package/lib/engine-components/Animation.d.ts +5 -0
  57. package/lib/engine-components/Animation.js +14 -0
  58. package/lib/engine-components/Animation.js.map +1 -1
  59. package/lib/engine-components/AnimatorController.d.ts +1 -0
  60. package/lib/engine-components/AnimatorController.js +14 -7
  61. package/lib/engine-components/AnimatorController.js.map +1 -1
  62. package/lib/engine-components/BoxHelperComponent.d.ts +2 -2
  63. package/lib/engine-components/BoxHelperComponent.js +28 -9
  64. package/lib/engine-components/BoxHelperComponent.js.map +1 -1
  65. package/lib/engine-components/Collider.d.ts +7 -2
  66. package/lib/engine-components/Collider.js +27 -15
  67. package/lib/engine-components/Collider.js.map +1 -1
  68. package/lib/engine-components/Component.d.ts +8 -16
  69. package/lib/engine-components/Component.js +18 -117
  70. package/lib/engine-components/Component.js.map +1 -1
  71. package/lib/engine-components/DragControls.js +9 -6
  72. package/lib/engine-components/DragControls.js.map +1 -1
  73. package/lib/engine-components/GroundProjection.d.ts +2 -0
  74. package/lib/engine-components/GroundProjection.js +18 -6
  75. package/lib/engine-components/GroundProjection.js.map +1 -1
  76. package/lib/engine-components/NavMesh.d.ts +0 -5
  77. package/lib/engine-components/NavMesh.js +100 -10
  78. package/lib/engine-components/NavMesh.js.map +1 -1
  79. package/lib/engine-components/NestedGltf.js +2 -0
  80. package/lib/engine-components/NestedGltf.js.map +1 -1
  81. package/lib/engine-components/ReflectionProbe.d.ts +22 -0
  82. package/lib/engine-components/ReflectionProbe.js +134 -0
  83. package/lib/engine-components/ReflectionProbe.js.map +1 -0
  84. package/lib/engine-components/Renderer.d.ts +13 -2
  85. package/lib/engine-components/Renderer.js +96 -45
  86. package/lib/engine-components/Renderer.js.map +1 -1
  87. package/lib/engine-components/RigidBody.d.ts +40 -25
  88. package/lib/engine-components/RigidBody.js +253 -142
  89. package/lib/engine-components/RigidBody.js.map +1 -1
  90. package/lib/engine-components/SpatialTrigger.js +1 -1
  91. package/lib/engine-components/SpatialTrigger.js.map +1 -1
  92. package/lib/engine-components/SpectatorCamera.d.ts +1 -0
  93. package/lib/engine-components/SpectatorCamera.js +9 -2
  94. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  95. package/lib/engine-components/SpringJoint.d.ts +0 -13
  96. package/lib/engine-components/SpringJoint.js +42 -41
  97. package/lib/engine-components/SpringJoint.js.map +1 -1
  98. package/lib/engine-components/VideoPlayer.js.map +1 -1
  99. package/lib/engine-components/WebARSessionRoot.d.ts +7 -7
  100. package/lib/engine-components/WebARSessionRoot.js +7 -7
  101. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  102. package/lib/engine-components/WebXR.d.ts +10 -8
  103. package/lib/engine-components/WebXR.js +50 -26
  104. package/lib/engine-components/WebXR.js.map +1 -1
  105. package/lib/engine-components/WebXRAvatar.d.ts +4 -5
  106. package/lib/engine-components/WebXRAvatar.js +9 -8
  107. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  108. package/lib/engine-components/WebXRController.d.ts +21 -21
  109. package/lib/engine-components/WebXRController.js +90 -68
  110. package/lib/engine-components/WebXRController.js.map +1 -1
  111. package/lib/engine-components/WebXRGrabRendering.d.ts +3 -3
  112. package/lib/engine-components/WebXRGrabRendering.js +2 -2
  113. package/lib/engine-components/WebXRGrabRendering.js.map +1 -1
  114. package/lib/engine-components/WebXRSync.d.ts +8 -8
  115. package/lib/engine-components/WebXRSync.js +15 -15
  116. package/lib/engine-components/WebXRSync.js.map +1 -1
  117. package/lib/engine-components/codegen/components.d.ts +2 -3
  118. package/lib/engine-components/codegen/components.js +2 -3
  119. package/lib/engine-components/codegen/components.js.map +1 -1
  120. package/lib/engine-components/ui/EventSystem.d.ts +1 -0
  121. package/lib/engine-components/ui/EventSystem.js +21 -1
  122. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  123. package/package.json +3 -4
  124. package/src/engine/api.ts +2 -1
  125. package/src/engine/codegen/register_types.js +291 -6
  126. package/src/engine/debug/debug.ts +4 -0
  127. package/src/engine/debug/debug_overlay.ts +9 -2
  128. package/src/engine/engine_element_loading.ts +1 -1
  129. package/src/engine/engine_gameobject.ts +19 -6
  130. package/src/engine/engine_gltf_builtin_components.ts +5 -1
  131. package/src/engine/engine_mainloop_utils.ts +7 -3
  132. package/src/engine/engine_physics.ts +508 -469
  133. package/src/engine/engine_physics.types.ts +19 -0
  134. package/src/engine/engine_serialization_core.ts +22 -8
  135. package/src/engine/engine_setup.ts +6 -2
  136. package/src/engine/engine_time.ts +2 -0
  137. package/src/engine/engine_types.ts +82 -55
  138. package/src/engine/engine_typestore.ts +2 -0
  139. package/src/engine/engine_util_decorator.ts +69 -0
  140. package/src/engine/engine_utils.ts +6 -5
  141. package/src/engine/extensions/EXT_texture_exr.js +1 -1
  142. package/src/engine/extensions/NEEDLE_animator_controller_model.ts +2 -1
  143. package/src/engine/extensions/NEEDLE_gameobject_data.ts +2 -0
  144. package/src/engine/extensions/NEEDLE_techniques_webgl.ts +7 -0
  145. package/src/engine-components/Animation.ts +14 -1
  146. package/src/engine-components/AnimatorController.ts +19 -9
  147. package/src/engine-components/BoxHelperComponent.ts +30 -9
  148. package/src/engine-components/Collider.ts +29 -29
  149. package/src/engine-components/Component.ts +26 -135
  150. package/src/engine-components/DragControls.ts +9 -5
  151. package/src/engine-components/GroundProjection.ts +22 -7
  152. package/src/engine-components/NavMesh.ts +114 -115
  153. package/src/engine-components/NestedGltf.ts +2 -0
  154. package/src/engine-components/ReflectionProbe.ts +141 -0
  155. package/src/engine-components/Renderer.ts +796 -737
  156. package/src/engine-components/RigidBody.ts +258 -149
  157. package/src/engine-components/SpatialTrigger.ts +1 -1
  158. package/src/engine-components/SpectatorCamera.ts +10 -2
  159. package/src/engine-components/SpringJoint.ts +41 -41
  160. package/src/engine-components/VideoPlayer.ts +1 -2
  161. package/src/engine-components/WebARSessionRoot.ts +16 -16
  162. package/src/engine-components/WebXR.ts +65 -50
  163. package/src/engine-components/WebXRAvatar.ts +16 -16
  164. package/src/engine-components/WebXRController.ts +143 -112
  165. package/src/engine-components/WebXRGrabRendering.ts +6 -6
  166. package/src/engine-components/WebXRSync.ts +20 -20
  167. package/src/engine-components/codegen/components.ts +2 -3
  168. package/src/engine-components/ui/EventSystem.ts +26 -3
@@ -1,81 +1,62 @@
1
- import * as CANNON from 'cannon-es'
2
- import * as THREE 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
- import * as utils from "./engine_utils"
6
- import * as threeutils from "./engine_three_utils"
3
+ import { getParam } from "./engine_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, $physicsKey,
12
- Collision, CannonCollision,
9
+ IRigidbody,
10
+ Collision,
13
11
  ICollisionContext,
14
- IComponent
15
- }
16
- from './engine_types';
17
- import { Shape } from 'cannon-es';
12
+ ContactPoint,
13
+ Vec3
14
+ } from './engine_types';
18
15
  import { InstancingUtil } from './engine_instancing';
19
16
  import { foreachComponent } from './engine_gameobject';
20
- import { getComponentInChildren } from './engine_components';
21
-
22
17
 
23
- const debugPhysics = utils.getParam("debugphysics");
24
- const debugCollisions = utils.getParam("debugcollisions");
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;
25
22
 
26
- export class BodyOptions {
27
- mass: number = 1;
28
- kinematic: boolean = false;
29
- physicsEvents: boolean = false;
30
- drag: number = 0;
31
- angularDrag: number = 0.05;
32
- sleepThreshold: number = .01;
33
- }
34
23
 
24
+ const debugPhysics = getParam("debugphysics");
25
+ const debugColliderPlacement = getParam("debugphysicscolliders");
26
+ const debugCollisions = getParam("debugcollisions");
35
27
 
36
- // TODO: refactor to return some kind of handle for adding/removing
37
- class PhysicsObject {
38
- obj: THREE.Object3D;
39
- parent: THREE.Object3D | null;
40
- body: CANNON.Body | null;
41
- shapes: Array<Shape> = [];
42
- collisonCallback: Function | null = null;
43
28
 
44
- _hasRigidbody: boolean = false;
45
- _didSleepLastStep: boolean = false;
46
-
47
- constructor(obj: THREE.Object3D, body: CANNON.Body | null) {
48
- this.obj = obj;
49
- this.parent = obj.parent;
50
- this.body = body;
51
- if (this.body)
52
- this.body[$physicsKey] = obj;
53
- }
29
+ declare type PhysicsBody = {
30
+ translation(): { x: number, y: number, z: number }
31
+ rotation(): { x: number, y: number, z: number, w: number }
54
32
  }
55
33
 
34
+ const $componentKey = Symbol("needle component");
35
+ const $bodyKey = Symbol("physics body");
36
+
56
37
  export class RaycastOptions {
57
- ray: THREE.Ray | undefined = undefined;
58
- cam: THREE.Camera | undefined | null = undefined;
59
- screenPoint: THREE.Vector2 | undefined = undefined;
60
- raycaster: THREE.Raycaster | undefined = undefined;
61
- results: Array<THREE.Intersection> | undefined = undefined;
62
- targets: Array<THREE.Object3D> | undefined = undefined;
38
+ ray: Ray | undefined = undefined;
39
+ cam: Camera | undefined | null = undefined;
40
+ screenPoint: Vector2 | undefined = undefined;
41
+ raycaster: Raycaster | undefined = undefined;
42
+ results: Array<Intersection> | undefined = undefined;
43
+ targets: Array<Object3D> | undefined = undefined;
63
44
  recursive: boolean | undefined = true;
64
45
  minDistance: number | undefined = undefined;
65
46
  maxDistance: number | undefined = undefined;
66
47
  lineThreshold: number | undefined = undefined;
67
- layerMask: THREE.Layers | number | undefined = undefined;
68
- ignore: THREE.Object3D[] | undefined = undefined;
48
+ layerMask: Layers | number | undefined = undefined;
49
+ ignore: Object3D[] | undefined = undefined;
69
50
 
70
51
  screenPointFromOffset(ox: number, oy: number) {
71
- if (this.screenPoint === undefined) this.screenPoint = new THREE.Vector2();
52
+ if (this.screenPoint === undefined) this.screenPoint = new Vector2();
72
53
  this.screenPoint.x = ox / window.innerWidth * 2 - 1;
73
54
  this.screenPoint.y = -(oy / window.innerHeight) * 2 + 1;
74
55
  }
75
56
 
76
57
  setMask(mask: number) {
77
- if (!this.layerMask) this.layerMask = new THREE.Layers();
78
- const lm = this.layerMask as THREE.Layers;
58
+ if (!this.layerMask) this.layerMask = new Layers();
59
+ const lm = this.layerMask as Layers;
79
60
  if (lm)
80
61
  lm.mask = mask;
81
62
  else this.layerMask = mask;
@@ -84,11 +65,11 @@ export class RaycastOptions {
84
65
  public static AllLayers = 0xFFFFFFFF;
85
66
  }
86
67
 
87
- export class SphereIntersection implements THREE.Intersection {
68
+ export class SphereIntersection implements Intersection {
88
69
  distance: number;
89
- point: THREE.Vector3;
90
- object: THREE.Object3D;
91
- constructor(object: THREE.Object3D, distance: number, point: THREE.Vector3) {
70
+ point: Vector3;
71
+ object: Object3D;
72
+ constructor(object: Object3D, distance: number, point: Vector3) {
92
73
  this.object = object;
93
74
  this.distance = distance;
94
75
  this.point = point;
@@ -99,9 +80,9 @@ export class Physics {
99
80
 
100
81
  // raycasting
101
82
 
102
- private readonly raycaster: THREE.Raycaster = new THREE.Raycaster();
83
+ private readonly raycaster: Raycaster = new Raycaster();
103
84
  private readonly defaultRaycastOptions: RaycastOptions = new RaycastOptions();
104
- private readonly targetBuffer: Array<THREE.Object3D> = new Array<THREE.Object3D>(1);
85
+ private readonly targetBuffer: Array<Object3D> = new Array<Object3D>(1);
105
86
  private readonly defaultThresholds = {
106
87
  Mesh: {},
107
88
  Line: { threshold: 0 },
@@ -111,60 +92,56 @@ export class Physics {
111
92
  }
112
93
 
113
94
 
114
- private sphereResults: Array<THREE.Intersection> = new Array<THREE.Intersection>();
115
- private sphereMask: THREE.Layers = new THREE.Layers();
116
- public sphereOverlap(spherePos: THREE.Vector3, radius: number): Array<THREE.Intersection> {
95
+ private sphereResults: Array<Intersection> = new Array<Intersection>();
96
+ private sphereMask: Layers = new Layers();
97
+ public sphereOverlap(spherePos: Vector3, radius: number, traverseChildsAfterHit: boolean = true): Array<Intersection> {
117
98
  this.sphereResults.length = 0;
118
99
  if (!this.context.scene) return this.sphereResults;
119
- const sphere = new THREE.Sphere(spherePos, radius);
100
+ const sphere = new Sphere(spherePos, radius);
120
101
  const mask = this.sphereMask;
121
102
  mask.enableAll();
122
103
  mask.disable(2);
123
- // mask testing
124
- // const dummy = new THREE.Layers();
125
- // dummy.set(2);
126
- // console.log(dummy.mask, mask.test(dummy))
127
104
  for (const ch of this.context.scene.children) {
128
- const i = this.onSphereOverlap(ch, sphere, mask);
129
- if (i) this.sphereResults.push(i);
105
+ this.onSphereOverlap(ch, sphere, mask, this.sphereResults, traverseChildsAfterHit);
130
106
  }
131
107
  return this.sphereResults.sort((a, b) => a.distance - b.distance);
132
108
  }
133
- private tempBoundingBox: THREE.Box3 = new THREE.Box3();
134
- private onSphereOverlap(obj: THREE.Object3D, sp: THREE.Sphere, mask: THREE.Layers): THREE.Intersection | null {
135
- if (obj.type === "Mesh") {
136
- if (!obj.layers.test(mask)) return null;
137
- const mesh = obj as THREE.Mesh;
109
+ private tempBoundingBox: Box3 = new Box3();
110
+ private onSphereOverlap(obj: Object3D, sp: Sphere, mask: Layers, results: Array<Intersection>, traverseChildsAfterHit: boolean): void {
111
+ if (obj.type === "Mesh" && obj.layers.test(mask)) {
112
+ const mesh = obj as Mesh;
138
113
  const geo = mesh.geometry;
139
114
  if (!geo.boundingBox)
140
115
  geo.computeBoundingBox();
141
- if (!geo.boundingBox) return null;
142
- if (mesh.matrixWorldNeedsUpdate) mesh.updateMatrixWorld();
143
- const test = this.tempBoundingBox.copy(geo.boundingBox).applyMatrix4(mesh.matrixWorld);
144
- if (sp.intersectsBox(test)) {
145
- // console.log(obj, obj.layers.test(mask), obj.layers.mask, mask.mask);
146
- const wp = threeutils.getWorldPosition(obj);
147
- const dist = wp.distanceTo(sp.center);
148
- const int = new SphereIntersection(obj, dist, sp.center.clone());
149
- return int;
116
+ if (geo.boundingBox) {
117
+ if (mesh.matrixWorldNeedsUpdate) mesh.updateMatrixWorld();
118
+ const test = this.tempBoundingBox.copy(geo.boundingBox).applyMatrix4(mesh.matrixWorld);
119
+ if (sp.intersectsBox(test)) {
120
+ // console.log(obj, obj.layers.test(mask), obj.layers.mask, mask.mask);
121
+ const wp = getWorldPosition(obj);
122
+ const dist = wp.distanceTo(sp.center);
123
+ const int = new SphereIntersection(obj, dist, sp.center.clone());
124
+ results.push(int);
125
+ if (!traverseChildsAfterHit) return;
126
+ }
150
127
  }
151
128
  }
152
- else if (obj.children) {
129
+ if (obj.children) {
153
130
  for (const ch of obj.children) {
154
- const i = this.onSphereOverlap(ch, sp, mask);
155
- if (i) return i;
131
+ const len = results.length;
132
+ this.onSphereOverlap(ch, sp, mask, results, traverseChildsAfterHit);
133
+ if (len != results.length && !traverseChildsAfterHit) return;
156
134
  }
157
135
  }
158
- return null;
159
136
  }
160
137
 
161
- public raycastFromRay(ray: THREE.Ray, options: RaycastOptions | null = null): Array<THREE.Intersection> {
138
+ public raycastFromRay(ray: Ray, options: RaycastOptions | null = null): Array<Intersection> {
162
139
  const opts = options ?? this.defaultRaycastOptions;
163
140
  opts.ray = ray;
164
141
  return this.raycast(opts);
165
142
  }
166
143
 
167
- public raycast(options: RaycastOptions | null = null): Array<THREE.Intersection> {
144
+ public raycast(options: RaycastOptions | null = null): Array<Intersection> {
168
145
  if (!options) options = this.defaultRaycastOptions;
169
146
  const mp = options.screenPoint ?? this.context.input.mousePositionRC;
170
147
  const rc = options.raycaster ?? this.raycaster;
@@ -194,14 +171,14 @@ export class Physics {
194
171
  let results = options.results;
195
172
  if (!results) {
196
173
  if (!this.defaultRaycastOptions.results)
197
- this.defaultRaycastOptions.results = new Array<THREE.Intersection>();
174
+ this.defaultRaycastOptions.results = new Array<Intersection>();
198
175
  results = this.defaultRaycastOptions.results;
199
176
  }
200
177
 
201
178
  // layermask
202
- // https://github.com/mrdoob/three.js/blob/master/src/core/Layers.js
179
+ // https://github.com/mrdoob/js/blob/master/src/core/Layers.js
203
180
  if (options.layerMask !== undefined) {
204
- if (options.layerMask instanceof THREE.Layers)
181
+ if (options.layerMask instanceof Layers)
205
182
  rc.layers.mask = options.layerMask.mask;
206
183
  else
207
184
  rc.layers.mask = options.layerMask;
@@ -230,479 +207,541 @@ export class Physics {
230
207
 
231
208
  // physics simulation
232
209
 
233
- 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;
234
215
 
235
216
  private _isUpdatingPhysicsWorld: boolean = false;
217
+ get isUpdating(): boolean { return this._isUpdatingPhysicsWorld; }
218
+
236
219
 
237
220
  private context: Context;
221
+ private world?: World;
222
+ private _hasCreatedWorld: boolean = false;
223
+ private eventQueue?: EventQueue;
224
+ private collisionHandler?: PhysicsCollisionHandler;
225
+
238
226
 
239
- private world: CANNON.World = new CANNON.World();
240
- 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>();
241
234
 
242
- private tempPosition: THREE.Vector3 = new THREE.Vector3();
243
- private tempQuaternion: THREE.Quaternion = new THREE.Quaternion();
244
235
 
245
236
  constructor(context: Context) {
246
237
  this.context = context;
247
- this.world.gravity.set(0, -9.82, 0);
248
- if (debugPhysics) {
249
- // https://www.npmjs.com/package/cannon-es-debugger
250
- const opts = {};
251
- opts["onInit"] = (_body: CANNON.Body, mesh: THREE.Mesh, _shape: CANNON.Shape) => {
252
- // ignore in raycast
253
- mesh.layers.set(-1);
254
- };
255
- cannonDebugger(context.scene, this.world.bodies, opts);
256
- }
257
-
258
- this.world.addEventListener("beginContact", this.onBeginContact.bind(this));
259
- this.world.addEventListener("endContact", this.onEndContact.bind(this))
260
- }
261
-
262
- public addPreStepListener(listener: (evt) => void) {
263
- this.world.addEventListener("preStep", listener);
264
- }
265
-
266
- public addPostStepListener(listener: (evt) => void) {
267
- this.world.addEventListener("postStep", listener);
268
238
  }
269
239
 
270
- public addConstraint(constraint: CANNON.Constraint) {
271
- 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);
272
252
  }
273
253
 
274
- public setGravity(vec: THREE.Vector3) {
275
- 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);
276
260
  }
277
261
 
278
- public multiplyGravity(vec: THREE.Vector3) {
279
- this.world.gravity.x *= vec.x;
280
- this.world.gravity.y *= vec.y;
281
- 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);
282
267
  }
283
268
 
284
- public addBody(go: GameObject, body: CANNON.Body) {
285
- for (let i = 0; i < this.objects.length; i++) {
286
- const reg = this.objects[i];
287
- if (reg.obj === go) {
288
- reg._hasRigidbody = true;
289
- break;
290
- }
291
- }
292
- // dont add the body before it has shapes
293
- // otherwise things like forces appplied in the frame before the shapes exist will be zeroed out
294
- if (body.shapes.length > 0)
295
- this.world.addBody(body);
296
- }
297
-
298
- public removeBody(go: GameObject, body: CANNON.Body, removeCompletely: boolean = true) {
299
- this.world.removeBody(body);
300
- for (let i = 0; i < this.objects.length; i++) {
301
- const reg = this.objects[i];
302
- if (reg.obj === go) {
303
- reg._hasRigidbody = false;
304
- if (removeCompletely)
305
- this.objects.splice(i, 1);
306
- break;
307
- }
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;
308
274
  }
309
- }
310
275
 
311
- public removeShape(obj: THREE.Object3D, shape: CANNON.Shape) {
312
- for (const reg of this.objects) {
313
- if (reg.obj === obj) {
314
- if (reg.body) {
315
- reg.body.removeShape(shape);
316
- reg.body.updateMassProperties();
317
- }
318
- 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;
319
291
  }
292
+ positions = scaledPositions;
320
293
  }
321
- }
322
-
323
- // TODO: make it work with rigibody in parent
324
- public createBody(obj: THREE.Object3D, settings: BodyOptions): CANNON.Body {
325
- const body = this.internalCreateBody(obj, null);
326
- if (settings.mass)
327
- body.mass = settings.mass;
328
- if (settings.kinematic)
329
- body.type = CANNON.Body.KINEMATIC;
330
- else body.type = CANNON.Body.DYNAMIC;
331
- if (settings.drag)
332
- body.linearDamping = settings.drag;
333
- if (settings.angularDrag)
334
- body.angularDamping = settings.angularDrag;
335
- if (settings.sleepThreshold)
336
- body.sleepSpeedLimit = settings.sleepThreshold;
337
294
 
338
- if (body.shapes.length > 0)
339
- this.world.addBody(body);
340
- const po = new PhysicsObject(obj, body);
341
- po._hasRigidbody = true;
342
- 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));
343
299
 
344
- if (debugPhysics) {
345
- console.log("created new body", obj.name, body, body.sleepState, this.world.gravity);
346
300
  }
347
-
348
- if (settings.physicsEvents)
349
- this.registerCollisionEvents(po);
350
-
351
- return body;
352
301
  }
353
302
 
354
- public addBoxCollider(obj: THREE.Object3D, trigger: boolean, center: THREE.Vector3, size: THREE.Vector3, rb: Rigidbody | null): CANNON.Shape {
355
-
356
- const scale = this.tempPosition;
357
- obj.getWorldScale(scale);
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);
358
310
 
359
- const pos = new CANNON.Vec3(
360
- .5 * scale.x * size.x,
361
- .5 * scale.y * size.y,
362
- .5 * scale.z * size.z
363
- );
364
- const shape = new CANNON.Box(pos);
365
- shape.collisionResponse = !trigger;
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);
366
316
 
367
- center = center.clone();
368
- center.multiply(scale);
317
+ desc.setSensor(collider.isTrigger);
369
318
 
370
- const body = this.addShape(obj, shape, center, rb);
371
- if (body !== null) {
372
- this.world.addBody(body);
373
- if (this.isAlreadyRegistered(body)) return shape;
374
- const po = new PhysicsObject(obj, body);
375
- 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);
376
324
  }
377
- return shape;
378
- }
379
-
380
- public addSphereCollider(obj: THREE.Object3D, center: THREE.Vector3, radius: number, rb: Rigidbody | null): CANNON.Shape {
381
- const scale = this.tempPosition;
382
- obj.getWorldScale(scale);
383
325
 
384
- const factor = Math.max(scale.x, scale.y, scale.z);
385
- const shape = new CANNON.Sphere(radius * factor);
386
- // shape.collisionResponse = !trigger;
387
-
388
- center = center.clone();
389
- 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);
390
362
 
391
- const body = this.addShape(obj, shape, center, rb);
392
- if (body !== null) {
393
- this.world.addBody(body);
394
- if (this.isAlreadyRegistered(body)) return shape;
395
- const po = new PhysicsObject(obj, body);
396
- this.objects.push(po);
397
363
  }
398
- return shape;
399
- }
400
-
401
- public addMeshCollider(_obj: THREE.Object3D) {
402
- // see https://github.com/schteppe/cannon.js/blob/master/demos/bunny.html
403
- if (debugPhysics)
404
- console.warn("TODO mesh collider not yet supported")
405
- // const geometry: THREE.BufferGeometry = obj["geometry"];
406
- // console.log(geometry);
407
- // const size = geometry.boundingBox.max.clone();
408
- // size.sub(geometry.boundingBox.min);
409
- // console.log(size);
410
- // this.addBoxCollider(obj, size);
411
-
412
- // const verts = geometry.getAttribute("position").array;
413
- // const faces = new Array<Array<number>>();
364
+ else {
414
365
 
415
- // 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;
416
373
 
417
- // for (let i = 0; i < geometry.index.array.length; i += 3) {
418
- // const i0 = geometry.index.array[i];
419
- // const i1 = geometry.index.array[i + 1];
420
- // const i2 = geometry.index.array[i + 2];
421
- // const v0 = new THREE.Vector3(verts[i0 * 3], verts[i0 * 3 + 1], verts[i0 * 3 + 2]);
422
- // const v1 = new THREE.Vector3(verts[i1 * 3], verts[i1 * 3 + 1], verts[i1 * 3 + 2]);
423
- // const v2 = new THREE.Vector3(verts[i2 * 3], verts[i2 * 3 + 1], verts[i2 * 3 + 2]);
424
- // const face = [v0, v1, v2];
425
- // faces.push(face);
426
- // }
427
- // const convex = new THREE.ConvexBufferGeometry(faces);
374
+ }
428
375
 
429
- // var shape = new CANNON.ConvexPolyhedron({ verts, faces });
430
- // this.addShape(obj, shape);
376
+ return { rigidBody: rigidBody, useExplicitMassProperties: useExplicitMassProperties };
431
377
  }
432
378
 
433
- private isAlreadyRegistered(body: CANNON.Body): boolean {
434
- for (const obj of this.objects) {
435
- 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
+ }
436
405
  }
437
- return false;
438
406
  }
439
407
 
440
- private readonly tempMat1: THREE.Matrix4 = new THREE.Matrix4();
441
- private readonly tempMat2: THREE.Matrix4 = new THREE.Matrix4();
442
-
443
- private addShape(obj: THREE.Object3D, shape: CANNON.Shape, center: THREE.Vector3, rb: Rigidbody | null): CANNON.Body | null {
444
-
445
- let body: CANNON.Body | null = null;
408
+ updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean) {
409
+ if (comp.destroyed || !comp.gameObject) return;
410
+ if (!translation && !rotation) return;
446
411
 
447
- if (rb) {
448
- // if (debugPhysics)
449
- // console.log("get rb body", rb);
450
- rb.initialize();
451
- console.assert(rb.body ? true : false, "rigidbody didn't initialize / produce a physics body", rb);
452
- body = rb.body;
412
+ if ((comp as ICollider).isCollider === true) {
413
+ // const collider = comp as ICollider;
414
+ console.warn("TODO: implement updating collider position");
453
415
  }
454
416
  else {
455
- // console.log("has no rb", obj);
456
- body = this.internalCreateBody(obj, null);
457
- body.type = CANNON.Body.KINEMATIC;
458
- }
459
-
460
- if (body) {
461
- // console.log(obj.name, obj.position, obj.rotation)
462
-
463
- // the center is serialized from Unity so we need to move it into threejs space
464
- // this should probably happen on export for colliders
465
- center.x *= -1;
466
-
467
- let wp = obj.position;
468
- let wr = obj.quaternion;
469
-
470
- // console.log(obj.name, wp)
471
-
472
- if (rb && rb.gameObject !== obj) {
473
- this.tempMat1.copy(obj.matrixWorld);
474
- this.tempMat2.copy(rb.gameObject.matrixWorld).invert();
475
- this.tempMat1.premultiply(this.tempMat2);
476
- this.tempMat1.decompose(wp, wr, this.tempPosition);
477
- }
478
- else {
479
- wp = threeutils.getWorldPosition(obj);
480
- const bp = body.position;
481
- wp.x -= bp.x;
482
- wp.y -= bp.y;
483
- wp.z -= bp.z;
484
-
485
- wr = threeutils.getWorldQuaternion(obj);
486
- const r = new THREE.Quaternion(body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
487
- wr.multiply(r.invert());
417
+ const rigidbody = comp as IRigidbody;
418
+ const body = rigidbody[$bodyKey];
419
+ if (body) {
420
+ this.syncPhysicsBody(rigidbody.gameObject, body, translation, rotation);
488
421
  }
489
- // get rotation difference
490
-
491
- wp.add(center);
422
+ }
423
+ }
492
424
 
425
+ updateProperties(rigidbody: IRigidbody) {
426
+ const physicsBody = rigidbody[$bodyKey]
427
+ if (physicsBody) {
428
+ this.internalUpdateProperties(rigidbody, physicsBody);
429
+ }
430
+ }
493
431
 
432
+ internal_getRigidbody(rb: IRigidbody): RigidBody | null {
433
+ return rb[$bodyKey] as RigidBody;
434
+ }
494
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);
495
443
 
496
- // if (rb) {
497
- // this.tempMat.setPosition(wp);
498
- // this.tempMat.makeRotationFromQuaternion(wr);
499
- // this.tempMat.multiplyMatrices(this.tempMat, rb?.gameObject.matrix);
500
- // this.tempMat.decompose(this.tempPosition, this.tempQuaternion, new THREE.Vector3());
501
- // wp.copy(this.tempPosition);
502
- // }
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
+ // }
503
453
 
504
- // 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);
505
457
 
506
- const pos = new CANNON.Vec3(wp.x, wp.y, wp.z);
507
- const rot = new CANNON.Quaternion(wr.x, wr.y, wr.z, wr.w);
508
- body.addShape(shape, pos, rot);
509
- body.updateMassProperties();
510
- this.world.addBody(body);
458
+ if (rb.isKinematic) {
459
+ rigidbody.setBodyType(RAPIER.RigidBodyType.KinematicPositionBased);
460
+ }
461
+ else {
462
+ rigidbody.setBodyType(RAPIER.RigidBodyType.Dynamic);
511
463
  }
512
- return body;
513
464
  }
514
465
 
515
466
  // private _lastStepTime: number | undefined = 0;
467
+ private lines?: LineSegments;
516
468
 
517
- public step(deltaTime: number) {
469
+ public step(_deltaTime?: number) {
470
+ if (!this.world) return;
518
471
  this._isUpdatingPhysicsWorld = true;
519
- deltaTime = Math.min(deltaTime, 1 / 30);
520
- this.world.step(deltaTime);
521
- this._isUpdatingPhysicsWorld = false;
522
- if (debugPhysics && this.context.time.frameCount % 60 === 0) {
523
- // console.log("physics world has " + this.world.bodies.length + " bodies", this.world);
472
+ if (!this.eventQueue) {
473
+ this.eventQueue = new EventQueue(false);
524
474
  }
475
+ this.world.step(this.eventQueue);
476
+ this._isUpdatingPhysicsWorld = false;
477
+ this.updateDebugRendering(this.world);
525
478
  }
526
479
 
527
- private temp: THREE.Vector3 = new THREE.Vector3();
528
- private tempQuat: THREE.Quaternion = new THREE.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
+ }
529
496
 
530
497
  public postStep() {
498
+ if (!this.world) return;
531
499
  this._isUpdatingPhysicsWorld = true;
532
- for (let i = 0; i < this.objects.length; i++) {
533
- const entry = this.objects[i];
534
- const body = entry.body;
535
- if (!body || !body.world) continue;
500
+ this.syncObjects();
501
+ this._isUpdatingPhysicsWorld = false;
536
502
 
537
- body.sleepTick(this.context.time.time);
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
+ }
538
511
 
539
- if (debugPhysics) {
540
- if (!entry._didSleepLastStep && body.sleepState === CANNON.Body.SLEEPING) {
541
- console.log("BODY SLEEPING", body);
542
- }
543
- else if (entry._didSleepLastStep && body.sleepState !== CANNON.Body.SLEEPING) {
544
- console.log("BODY WOKE UP", body);
545
- }
546
- }
547
- entry._didSleepLastStep = body.sleepState === CANNON.Body.SLEEPING;
548
- // if(body.sleepState === CANNON.Body.SLEEPING) {
549
- // console.log("SLEEP", body.name);
550
- // }
551
- // if (body.type == CANNON.Body.KINEMATIC) continue;
552
- const obj = entry.obj;
553
-
554
- if (body.type === CANNON.Body.KINEMATIC) {
555
- const wp = threeutils.getWorldPosition(obj, this.temp);
556
- body.position.set(wp.x, wp.y, wp.z);
557
- const rot = threeutils.getWorldQuaternion(obj, this.tempQuat);
558
- body.quaternion.set(rot.x, rot.y, rot.z, rot.w);
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);
559
526
  continue;
560
527
  }
561
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
+ }
562
536
 
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);
563
573
 
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
- threeutils.setWorldQuaternionXYZW(obj,
568
- body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w
569
- );
570
-
571
- const p = body.position;
572
- threeutils.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: THREE.Object3D, shape: CANNON.Shape | undefined | null): CANNON.Body {
601
599
 
602
- const body = new CANNON.Body();
603
- body["_owner"] = obj;
604
- body["_name"] = obj.name;
605
- obj.getWorldPosition(this.tempPosition);
606
- const pos = this.tempPosition;
607
- body.position = new CANNON.Vec3(pos.x, pos.y, pos.z);
600
+ }
608
601
 
609
- const quat = this.tempQuaternion;
610
- obj.getWorldQuaternion(quat);
611
- body.quaternion = new CANNON.Quaternion(quat.x, quat.y, quat.z, quat.w);
612
602
 
613
- body.type = CANNON.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: THREE.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: THREE.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: CANNON.Body, bodyB: CANNON.Body }) {
669
- // if(args.bodyB.sleepState !== CANNON.Body.AWAKE) return;
670
- // console.log("END", CANNON.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
- }
691
-
692
- export interface IColliderProvider {
693
- getCollider(obj: THREE.Object3D): ICollider;
694
- }
659
+ update() {
660
+ this.onHandleCollisionStay();
661
+ }
695
662
 
696
- let colliderProvider: IColliderProvider | null = null;
697
- export function registerColliderProvider(prov: IColliderProvider) {
698
- colliderProvider = prov;
699
- }
663
+ private onCollisionStarted(self: ICollider, selfBody: Collider, other: ICollider, otherBody: Collider) {
664
+ let collision: Collision | null = null;
700
665
 
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: THREE.Object3D<THREE.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
-