Needle Engine

Changes between version 3.25.2 and 3.25.3
Files changed (14) hide show
  1. src/engine/api.ts +1 -0
  2. src/engine-components/AudioSource.ts +9 -3
  3. src/engine-components/Collider.ts +3 -3
  4. src/engine/engine_context.ts +8 -0
  5. src/engine/engine_mainloop_utils.ts +7 -7
  6. src/engine/engine_physics_rapier.ts +69 -46
  7. src/engine/engine_texture.ts +12 -1
  8. src/engine/engine_types.ts +4 -3
  9. src/engine-components/js-extensions/Object3D.ts +10 -0
  10. src/engine/codegen/register_types.ts +2 -2
  11. src/engine-components/TransformGizmo.ts +54 -75
  12. src/engine-components/webxr/WebXRImageTracking.ts +5 -2
  13. src/engine/engine_lifecycle_api.ts +16 -0
  14. src/engine/engine_lifecycle_functions_internal.ts +35 -0
src/engine/api.ts CHANGED
@@ -34,6 +34,7 @@
34
34
  export * from "./engine_physics_rapier.js";
35
35
  export * from "./engine_scenelighting.js";
36
36
  export * from "./engine_input.js";
37
+ export * from "./engine_lifecycle_api.js";
37
38
  export * from "./engine_math.js";
38
39
  export * from "./js-extensions/index.js";
39
40
  export * from "./engine_scenetools.js";
src/engine-components/AudioSource.ts CHANGED
@@ -27,8 +27,14 @@
27
27
 
28
28
 
29
29
  let userInteractionRegistered = false;
30
+ const userInteractionCallbacks: Function[] = [];
30
31
  function onUserInteraction() {
32
+ if(userInteractionRegistered) return;
33
+ if(isDevEnvironment()) console.log("User interaction registered: audio can now be played");
31
34
  userInteractionRegistered = true;
35
+ const copy = [...userInteractionCallbacks];
36
+ userInteractionCallbacks.length = 0;
37
+ copy.forEach(cb => cb());
32
38
  }
33
39
  document.addEventListener('pointerdown', onUserInteraction);
34
40
  document.addEventListener('click', onUserInteraction);
@@ -41,15 +47,14 @@
41
47
  return userInteractionRegistered;
42
48
  }
43
49
 
44
- private static callbacks: Function[] = [];
45
50
  public static registerWaitForAllowAudio(cb: Function) {
46
51
  if (cb !== null) {
47
52
  if (userInteractionRegistered) {
48
53
  cb();
49
54
  return;
50
55
  }
51
- if (this.callbacks.indexOf(cb) === -1)
52
- this.callbacks.push(cb);
56
+ if (userInteractionCallbacks.indexOf(cb) === -1)
57
+ userInteractionCallbacks.push(cb);
53
58
  }
54
59
  }
55
60
 
@@ -146,6 +151,7 @@
146
151
 
147
152
 
148
153
  awake() {
154
+ if(debug) console.log(this);
149
155
  this.audioLoader = new AudioLoader();
150
156
  if (this.playOnAwake) this.shouldPlay = true;
151
157
  }
src/engine-components/Collider.ts CHANGED
@@ -73,7 +73,7 @@
73
73
 
74
74
  onEnable() {
75
75
  super.onEnable();
76
- this.context.physics.engine?.addSphereCollider(this, this.center);
76
+ this.context.physics.engine?.addSphereCollider(this);
77
77
  watchWrite(this.gameObject.scale, this.updateProperties);
78
78
  }
79
79
 
@@ -98,7 +98,7 @@
98
98
 
99
99
  onEnable() {
100
100
  super.onEnable();
101
- this.context.physics.engine?.addBoxCollider(this, this.center, this.size);
101
+ this.context.physics.engine?.addBoxCollider(this, this.size);
102
102
  }
103
103
 
104
104
  onValidate(): void {
@@ -155,7 +155,7 @@
155
155
 
156
156
  onEnable() {
157
157
  super.onEnable();
158
- this.context.physics.engine?.addCapsuleCollider(this, this.center, this.height, this.radius);
158
+ this.context.physics.engine?.addCapsuleCollider(this, this.height, this.radius);
159
159
  }
160
160
 
161
161
  }
src/engine/engine_context.ts CHANGED
@@ -34,6 +34,7 @@
34
34
  import { getLoader } from './engine_gltf.js';
35
35
  import { isLocalNetwork } from './engine_networking_utils.js';
36
36
  import { WaitForPromise } from './engine_coroutine.js';
37
+ import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
37
38
 
38
39
 
39
40
  const debug = utils.getParam("debugcontext");
@@ -88,6 +89,7 @@
88
89
  }
89
90
 
90
91
  export enum FrameEvent {
92
+ Start = -1,
91
93
  EarlyUpdate = 0,
92
94
  Update = 1,
93
95
  LateUpdate = 2,
@@ -1000,6 +1002,7 @@
1000
1002
  looputils.processNewScripts(this);
1001
1003
  looputils.updateIsActive(this.scene);
1002
1004
  looputils.processStart(this);
1005
+ invokeLifecycleFunctions(this, FrameEvent.Start);
1003
1006
 
1004
1007
  while (this._cameraStack.length > 0 && (!this.mainCameraComponent || this.mainCameraComponent.destroyed)) {
1005
1008
  this._cameraStack.splice(this._cameraStack.length - 1, 1);
@@ -1024,6 +1027,7 @@
1024
1027
  }
1025
1028
  }
1026
1029
  this.executeCoroutines(FrameEvent.EarlyUpdate);
1030
+ invokeLifecycleFunctions(this, FrameEvent.EarlyUpdate);
1027
1031
  if (this.onHandlePaused()) return false;
1028
1032
 
1029
1033
  this._currentFrameEvent = FrameEvent.Update;
@@ -1037,6 +1041,7 @@
1037
1041
  }
1038
1042
  }
1039
1043
  this.executeCoroutines(FrameEvent.Update);
1044
+ invokeLifecycleFunctions(this, FrameEvent.Update);
1040
1045
  if (this.onHandlePaused()) return false;
1041
1046
 
1042
1047
  this._currentFrameEvent = FrameEvent.LateUpdate;
@@ -1052,6 +1057,7 @@
1052
1057
 
1053
1058
  // this.mainLight = null;
1054
1059
  this.executeCoroutines(FrameEvent.LateUpdate);
1060
+ invokeLifecycleFunctions(this, FrameEvent.LateUpdate);
1055
1061
  if (this.onHandlePaused()) return false;
1056
1062
 
1057
1063
  if (this.physicsSteps === undefined) {
@@ -1079,6 +1085,7 @@
1079
1085
  }
1080
1086
 
1081
1087
  this.executeCoroutines(FrameEvent.OnBeforeRender);
1088
+ invokeLifecycleFunctions(this, FrameEvent.OnBeforeRender);
1082
1089
 
1083
1090
  if (this._sizeChanged)
1084
1091
  this.updateSize();
@@ -1130,6 +1137,7 @@
1130
1137
  }
1131
1138
 
1132
1139
  this.executeCoroutines(FrameEvent.OnAfterRender);
1140
+ invokeLifecycleFunctions(this, FrameEvent.OnAfterRender);
1133
1141
 
1134
1142
  if (this.post_render_callbacks) {
1135
1143
  for (const i in this.post_render_callbacks) {
src/engine/engine_mainloop_utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as utils from "./engine_generic_utils.js";
1
+ import { safeInvoke } from "./engine_generic_utils.js";
2
2
  import * as constants from "./engine_constants.js";
3
3
  import { getParam } from './engine_utils.js';
4
4
  import { CubeCamera, Object3D, Scene, WebGLCubeRenderTarget } from 'three';
@@ -95,7 +95,7 @@
95
95
  }
96
96
  updateActiveInHierarchyWithoutEventCall(script.gameObject);
97
97
  if (script.activeAndEnabled)
98
- utils.safeInvoke(script.__internalAwake.bind(script));
98
+ safeInvoke(script.__internalAwake.bind(script));
99
99
 
100
100
  // registerPrewarmObject(script.gameObject, context);
101
101
  }
@@ -119,7 +119,7 @@
119
119
  if (script.activeAndEnabled === false) continue;
120
120
  if (script.__internalEnable !== undefined) {
121
121
  script.enabled = true;
122
- utils.safeInvoke(script.__internalEnable.bind(script));
122
+ safeInvoke(script.__internalEnable.bind(script));
123
123
  }
124
124
  }
125
125
  catch (err) {
@@ -178,11 +178,11 @@
178
178
  }
179
179
  // keep them in queue until script has started
180
180
  // call awake if the script was inactive before
181
- utils.safeInvoke(script.__internalAwake.bind(script));
181
+ safeInvoke(script.__internalAwake.bind(script));
182
182
  if (script.enabled) {
183
- utils.safeInvoke(script.__internalEnable.bind(script));
183
+ safeInvoke(script.__internalEnable.bind(script));
184
184
  // now call start
185
- utils.safeInvoke(script.__internalStart.bind(script));
185
+ safeInvoke(script.__internalStart.bind(script));
186
186
  context.new_script_start.splice(i, 1);
187
187
  i--;
188
188
  }
@@ -285,7 +285,7 @@
285
285
  perComponent(go, comp => {
286
286
  if (activeInHierarchy) {
287
287
  if (comp.enabled) {
288
- utils.safeInvoke(comp.__internalAwake.bind(comp));
288
+ safeInvoke(comp.__internalAwake.bind(comp));
289
289
  if (comp.enabled) {
290
290
  comp["__didEnable"] = true;
291
291
  comp.onEnable();
src/engine/engine_physics_rapier.ts CHANGED
@@ -500,7 +500,7 @@
500
500
  this.world?.free();
501
501
  }
502
502
 
503
- async addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
503
+ async addBoxCollider(collider: ICollider, size: Vector3) {
504
504
  if (!this._isInitialized)
505
505
  await this.initialize(collider.context);
506
506
  if (!collider.activeAndEnabled) return;
@@ -531,10 +531,10 @@
531
531
  // const mask = objectLayerMask & ~2;
532
532
  // TODO: https://rapier.rs/docs/user_guides/javascript/colliders/#collision-groups-and-solver-groups
533
533
  // desc.setCollisionGroups(objectLayerMask);
534
- this.createCollider(collider, desc, center);
534
+ this.createCollider(collider, desc);
535
535
  }
536
536
 
537
- async addSphereCollider(collider: ICollider, center: Vector3) {
537
+ async addSphereCollider(collider: ICollider) {
538
538
  if (!this._isInitialized)
539
539
  await this.initialize(collider.context);
540
540
  if (!collider.activeAndEnabled) return;
@@ -543,11 +543,11 @@
543
543
  return;
544
544
  }
545
545
  const desc = ColliderDesc.ball(.5);
546
- this.createCollider(collider, desc, center);
546
+ this.createCollider(collider, desc);
547
547
  this.updateProperties(collider);
548
548
  }
549
549
 
550
- async addCapsuleCollider(collider: ICollider, center: Vector3, height: number, radius: number) {
550
+ async addCapsuleCollider(collider: ICollider, height: number, radius: number) {
551
551
  if (!this._isInitialized)
552
552
  await this.initialize(collider.context);
553
553
  if (!collider.activeAndEnabled) return;
@@ -565,7 +565,7 @@
565
565
  height = Math.max(height, finalRadius * 2);
566
566
  const hh = Mathf.clamp((height * .5 * scale.y) - (radius * scale.x), 0, Number.MAX_SAFE_INTEGER);
567
567
  const desc = ColliderDesc.capsule(hh, finalRadius);
568
- this.createCollider(collider, desc, center);
568
+ this.createCollider(collider, desc);
569
569
  }
570
570
 
571
571
  async addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3) {
@@ -684,22 +684,21 @@
684
684
  return component;
685
685
  }
686
686
 
687
- private createCollider(collider: ICollider, desc: ColliderDesc, center?: Vector3) {
687
+ private createCollider(collider: ICollider, desc: ColliderDesc) {
688
688
  if (!this.world) throw new Error("Physics world not initialized");
689
689
  const matrix = this._tempMatrix;
690
- const {
691
- rigidBody,
692
- useExplicitMassProperties
693
- } = this.getRigidbody(collider, this._tempMatrix);
690
+ let rigidBody: RigidBody | undefined = undefined;
691
+ if (!collider.attachedRigidbody) {
692
+ if(debugPhysics) console.log("Create collider without rigidbody", collider.name);
693
+ matrix.makeRotationFromQuaternion(getWorldQuaternion(collider.gameObject));
694
+ matrix.setPosition(getWorldPosition(collider.gameObject));
695
+ }
696
+ else {
697
+ rigidBody = this.getRigidbody(collider, this._tempMatrix);
698
+ }
694
699
 
695
700
  matrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
696
- getWorldScale(collider.gameObject, this._tempScale);
697
- if (center) {
698
- center.multiply(this._tempScale);
699
- this._tempPosition.x -= center.x;
700
- this._tempPosition.y += center.y;
701
- this._tempPosition.z += center.z;
702
- }
701
+ this.tryApplyCenter(collider, this._tempPosition);
703
702
  desc.setTranslation(this._tempPosition.x, this._tempPosition.y, this._tempPosition.z);
704
703
  desc.setRotation(this._tempQuaternion);
705
704
  desc.setSensor(collider.isTrigger);
@@ -752,7 +751,7 @@
752
751
  // if we want to use explicit mass properties, we need to set the collider density to 0
753
752
  // otherwise rapier will compute the mass properties based on the collider shape and density
754
753
  // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
755
- if (useExplicitMassProperties) {
754
+ if (collider.attachedRigidbody?.autoMass === false) {
756
755
  desc.setDensity(.000001);
757
756
  desc.setMass(.000001);
758
757
  }
@@ -778,14 +777,13 @@
778
777
  }
779
778
  }
780
779
 
781
- private getRigidbody(collider: ICollider, _matrix: Matrix4): { rigidBody: RigidBody, useExplicitMassProperties: boolean } {
780
+ private getRigidbody(collider: ICollider, _matrix: Matrix4): RigidBody {
782
781
 
783
782
  if (!this.world) throw new Error("Physics world not initialized");
784
783
  let rigidBody: RigidBody | null = null;
785
784
  let useExplicitMassProperties = false;
786
785
 
787
786
  if (collider.attachedRigidbody) {
788
-
789
787
  const rb = collider.attachedRigidbody;
790
788
  rigidBody = rb[$bodyKey];
791
789
  useExplicitMassProperties = rb.autoMass === false;
@@ -805,10 +803,9 @@
805
803
  rb[$bodyKey] = rigidBody;
806
804
  this.internalUpdateRigidbodyProperties(rb, rigidBody);
807
805
  this.getRigidbodyRelativeMatrix(collider.gameObject, rb.gameObject, _matrix);
808
-
806
+ collider[$colliderRigidbody] = rigidBody;
809
807
  }
810
808
  else {
811
-
812
809
  const rigidBodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased();
813
810
  const pos = getWorldPosition(collider.gameObject);
814
811
  rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
@@ -816,12 +813,10 @@
816
813
  rigidBody = this.world.createRigidBody(rigidBodyDesc);
817
814
  _matrix.identity();
818
815
  rigidBody[$componentKey] = null;
819
-
820
816
  }
821
817
 
822
- collider[$colliderRigidbody] = rigidBody;
823
818
 
824
- return { rigidBody: rigidBody, useExplicitMassProperties: useExplicitMassProperties };
819
+ return rigidBody;
825
820
  }
826
821
 
827
822
  private internal_getRigidbody(rb: IRigidbody | ICollider): RigidBody | null {
@@ -1001,6 +996,8 @@
1001
996
  const rigidbody = body.parent();
1002
997
  if (rigidbody)
1003
998
  this.syncPhysicsBody(obj.gameObject, rigidbody, true, true);
999
+ else
1000
+ this.syncPhysicsBody(obj.gameObject, body, true, true);
1004
1001
  continue;
1005
1002
  }
1006
1003
 
@@ -1024,33 +1021,47 @@
1024
1021
  }
1025
1022
  }
1026
1023
 
1027
- private syncPhysicsBody(obj: Object3D, body: RigidBody, translation: boolean, rotation: boolean) {
1024
+ private syncPhysicsBody(obj: Object3D, body: RigidBody | Collider, translation: boolean, rotation: boolean) {
1028
1025
 
1029
1026
  // const bodyType = body.bodyType();
1030
1027
  // const previous = physicsBody.translation();
1031
1028
  // const vel = physicsBody.linvel();
1032
1029
 
1033
- const worldPosition = getWorldPosition(obj, this._tempPosition);
1034
- const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
1035
- const type = body.bodyType();
1036
- switch (type) {
1037
- case RigidBodyType.Fixed:
1038
- case RigidBodyType.KinematicPositionBased:
1039
- case RigidBodyType.KinematicVelocityBased:
1040
- if (translation)
1041
- body.setNextKinematicTranslation(worldPosition);
1042
- if (rotation)
1043
- body.setNextKinematicRotation(worldQuaternion);
1044
- break;
1045
- default:
1046
- if (translation)
1047
- body.setTranslation(worldPosition, false);
1048
- if (rotation)
1049
- body.setRotation(worldQuaternion, false);
1050
- break;
1030
+ if (body instanceof RigidBody) {
1031
+ const worldPosition = getWorldPosition(obj, this._tempPosition);
1032
+ const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
1033
+ const type = body.bodyType();
1034
+ switch (type) {
1035
+ case RigidBodyType.Fixed:
1036
+ case RigidBodyType.KinematicPositionBased:
1037
+ case RigidBodyType.KinematicVelocityBased:
1038
+ if (translation)
1039
+ body.setNextKinematicTranslation(worldPosition);
1040
+ if (rotation)
1041
+ body.setNextKinematicRotation(worldQuaternion);
1042
+ break;
1043
+ default:
1044
+ if (translation)
1045
+ body.setTranslation(worldPosition, false);
1046
+ if (rotation)
1047
+ body.setRotation(worldQuaternion, false);
1048
+ break;
1051
1049
 
1050
+ }
1051
+ body.wakeUp();
1052
1052
  }
1053
- body.wakeUp();
1053
+ else if (body instanceof Collider) {
1054
+ const worldPosition = getWorldPosition(obj, this._tempPosition);
1055
+ const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
1056
+ const collider = body[$componentKey] as ICollider;
1057
+ this.tryApplyCenter(collider, worldPosition);
1058
+ if (translation)
1059
+ body.setTranslation(worldPosition);
1060
+ if (rotation)
1061
+ body.setRotation(worldQuaternion);
1062
+
1063
+ }
1064
+
1054
1065
  // physicsBody.setBodyType(RAPIER.RigidBodyType.Fixed);
1055
1066
  // physicsBody.setLinvel(vel, false);
1056
1067
 
@@ -1078,6 +1089,18 @@
1078
1089
  // body.setBodyType(bodyType);
1079
1090
  }
1080
1091
 
1092
+ private tryApplyCenter(collider: ICollider, targetVector: Vector3) {
1093
+ const center = collider.center;
1094
+ if (center && collider.gameObject) {
1095
+ getWorldScale(collider.gameObject, this._tempScale);
1096
+ center.multiply(this._tempScale);
1097
+ // TODO: fix export of center in editor integrations so we dont have to flip here
1098
+ targetVector.x -= center.x;
1099
+ targetVector.y += center.y;
1100
+ targetVector.z += center.z;
1101
+ }
1102
+ }
1103
+
1081
1104
  private static _matricesBuffer: Matrix4[] = [];
1082
1105
  private getRigidbodyRelativeMatrix(comp: Object3D, rigidbody: Object3D, mat: Matrix4, matrices?: Matrix4[]): Matrix4 {
1083
1106
  // collect all matrices to the rigidbody and then build the rigidbody relative matrix
src/engine/engine_texture.ts CHANGED
@@ -5,9 +5,20 @@
5
5
 
6
6
  const _prevVisible = Symbol("previous-visibility");
7
7
 
8
+ /**
9
+ * A RenderTexture can be used to render a scene to a texture automatically by assigning it to the `Camera` component's `targetTexture` property.
10
+ * You can then assign the `RenderTexture.texture` to materials to be displayed
11
+ * @example ```ts
12
+ * // create new RenderTexture with a resolution
13
+ * const rt = new RenderTexture(256, 256);
14
+ * // assign to a camera
15
+ * myCameraComponent.targetTexture = rt;
16
+ * // assign to a material
17
+ * myMaterial.map = rt.texture;
18
+ * ```
19
+ */
8
20
  export class RenderTexture extends WebGLRenderTarget {
9
21
 
10
-
11
22
  render(scene: Object3D, camera: Camera, renderer: WebGLRenderer | EffectComposer) {
12
23
  if (renderer instanceof EffectComposer) {
13
24
  if (!this["_unsupported_effectcomposer_warning"]) {
src/engine/engine_types.ts CHANGED
@@ -257,6 +257,7 @@
257
257
  attachedRigidbody: IRigidbody | null;
258
258
  isTrigger: boolean;
259
259
  sharedMaterial?: PhysicsMaterial;
260
+ center?: Vec3 & { multiply(vec: Vec3) };
260
261
  updateProperties(): void;
261
262
  updatePhysicsMaterial(): void;
262
263
  }
@@ -455,9 +456,9 @@
455
456
  sphereOverlap(point: Vector3, radius: number): Array<SphereOverlapResult>;
456
457
 
457
458
  // Collider methods
458
- addSphereCollider(collider: ICollider, center: Vector3);
459
- addBoxCollider(collider: ICollider, center: Vector3, size: Vector3);
460
- addCapsuleCollider(collider: ICollider, center: Vector3, radius: number, height: number);
459
+ addSphereCollider(collider: ICollider);
460
+ addBoxCollider(collider: ICollider, size: Vector3);
461
+ addCapsuleCollider(collider: ICollider, radius: number, height: number);
461
462
  addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3);
462
463
 
463
464
  updatePhysicsMaterial(collider:ICollider);
src/engine-components/js-extensions/Object3D.ts CHANGED
@@ -16,6 +16,9 @@
16
16
  }
17
17
  from "../../engine/engine_three_utils.js";
18
18
 
19
+ import { TransformControlsGizmo } from "three/examples/jsm/controls/TransformControls.js";
20
+
21
+
19
22
  // used to decorate cloned object3D objects with the same added components defined above
20
23
  export function apply(object: Object3D) {
21
24
  if (object && object.isObject3D === true) {
@@ -102,6 +105,10 @@
102
105
  if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldPosition")) {
103
106
  Object.defineProperty(Object3D.prototype, "worldPosition", {
104
107
  get: function () {
108
+ // TODO: would be great to remove this - just a workaround because the TransformControlsGizmo also defines this
109
+ if (this instanceof TransformControlsGizmo) {
110
+ return getWorldPosition(this["object"]);
111
+ }
105
112
  return getWorldPosition(this);
106
113
  },
107
114
  set: function (val: Vector3) {
@@ -113,6 +120,9 @@
113
120
  if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldQuaternion")) {
114
121
  Object.defineProperty(Object3D.prototype, "worldQuaternion", {
115
122
  get: function () {
123
+ if (this instanceof TransformControlsGizmo) {
124
+ return getWorldQuaternion(this["object"]);
125
+ }
116
126
  return getWorldQuaternion(this);
117
127
  },
118
128
  set: function (val: Quaternion) {
src/engine/codegen/register_types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore.js"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components.js";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -219,7 +219,7 @@
219
219
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering.js";
220
220
  import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
221
221
  import { XRState } from "../../engine-components/XRFlag.js";
222
-
222
+
223
223
  // Register types
224
224
  TypeStore.add("__Ignore", __Ignore);
225
225
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/TransformGizmo.ts CHANGED
@@ -50,21 +50,18 @@
50
50
  if (this.control) {
51
51
  this.context.scene.add(this.control);
52
52
  this.control.attach(this.gameObject);
53
- this.changeEventListener = this.onControlChangedEvent.bind(this);
54
- this.control?.addEventListener('dragging-changed', this.changeEventListener);
55
- this.addWindowEvents();
53
+
54
+ this.control?.addEventListener('dragging-changed', this.onControlChangedEvent);
55
+ window.addEventListener('keydown', this.windowKeyDownListener);
56
+ window.addEventListener('keyup', this.windowKeyUpListener);
56
57
  }
57
58
  }
58
59
 
59
- private changeEventListener?: any;
60
- private windowKeyDownListener?: any;
61
- private windowKeyUpListener?: any;
62
-
63
60
  onDisable() {
64
61
  this.control?.removeFromParent();
65
- if (this.changeEventListener)
66
- this.control?.removeEventListener('dragging-changed', this.changeEventListener);
67
- this.removeWindowEvents();
62
+ this.control?.removeEventListener('dragging-changed', this.onControlChangedEvent);
63
+ window.removeEventListener('keydown', this.windowKeyDownListener);
64
+ window.removeEventListener('keyup', this.windowKeyUpListener);
68
65
  }
69
66
 
70
67
  enableSnapping() {
@@ -83,7 +80,7 @@
83
80
  }
84
81
  }
85
82
 
86
- private onControlChangedEvent(event) {
83
+ private onControlChangedEvent = (event) => {
87
84
  const orbit = this.orbit;
88
85
  if (orbit) orbit.enabled = !event.value;
89
86
  if (event.value) {
@@ -95,85 +92,67 @@
95
92
  }
96
93
  }
97
94
 
98
- private addWindowEvents() {
99
- const control = this.control;
100
- if (!control) return;
101
95
 
102
- if (!this.windowKeyDownListener) {
103
- this.windowKeyDownListener = (event) => {
104
- if (!this.enabled) return;
105
- switch (event.keyCode) {
96
+ private windowKeyDownListener = (event) => {
97
+ if (!this.enabled) return;
98
+ if (!this.control) return;
99
+ switch (event.keyCode) {
106
100
 
107
- case 81: // Q
108
- control.setSpace(control.space === 'local' ? 'world' : 'local');
109
- break;
101
+ case 81: // Q
102
+ this.control.setSpace(this.control.space === 'local' ? 'world' : 'local');
103
+ break;
110
104
 
111
- case 16: // Shift
112
- this.enableSnapping();
113
- break;
105
+ case 16: // Shift
106
+ this.enableSnapping();
107
+ break;
114
108
 
115
- case 87: // W
116
- control.setMode('translate');
117
- break;
109
+ case 87: // W
110
+ this.control.setMode('translate');
111
+ break;
118
112
 
119
- case 69: // E
120
- control.setMode('rotate');
121
- break;
113
+ case 69: // E
114
+ this.control.setMode('rotate');
115
+ break;
122
116
 
123
- case 82: // R
124
- control.setMode('scale');
125
- break;
126
- case 187:
127
- case 107: // +, =, num+
128
- control.setSize(control.size + 0.1);
129
- break;
117
+ case 82: // R
118
+ this.control.setMode('scale');
119
+ break;
120
+ case 187:
121
+ case 107: // +, =, num+
122
+ this.control.setSize(this.control.size + 0.1);
123
+ break;
130
124
 
131
- case 189:
132
- case 109: // -, _, num-
133
- control.setSize(Math.max(control.size - 0.1, 0.1));
134
- break;
125
+ case 189:
126
+ case 109: // -, _, num-
127
+ this.control.setSize(Math.max(this.control.size - 0.1, 0.1));
128
+ break;
135
129
 
136
- case 88: // X
137
- control.showX = !control.showX;
138
- break;
130
+ case 88: // X
131
+ this.control.showX = !this.control.showX;
132
+ break;
139
133
 
140
- case 89: // Y
141
- control.showY = !control.showY;
142
- break;
134
+ case 89: // Y
135
+ this.control.showY = !this.control.showY;
136
+ break;
143
137
 
144
- case 90: // Z
145
- control.showZ = !control.showZ;
146
- break;
138
+ case 90: // Z
139
+ this.control.showZ = !this.control.showZ;
140
+ break;
147
141
 
148
- case 32: // Spacebar
149
- control.enabled = !control.enabled;
150
- break;
151
-
152
- }
153
-
154
- };
142
+ case 32: // Spacebar
143
+ this.control.enabled = !this.control.enabled;
144
+ break;
155
145
  }
146
+ }
156
147
 
157
- if (!this.windowKeyUpListener) {
158
- this.windowKeyUpListener = (event) => {
159
- if (!this.enabled) return;
160
- switch (event.keyCode) {
161
- case 16: // Shift
162
- this.disableSnapping();
163
- break;
148
+ private windowKeyUpListener = (event) => {
149
+ if (!this.enabled) return;
150
+ switch (event.keyCode) {
151
+ case 16: // Shift
152
+ this.disableSnapping();
153
+ break;
164
154
 
165
- }
166
-
167
- };
168
155
  }
169
156
 
170
-
171
- window.addEventListener('keydown', this.windowKeyDownListener);
172
- window.addEventListener('keyup', this.windowKeyUpListener);
173
157
  }
174
-
175
- private removeWindowEvents() {
176
- window.removeEventListener('keydown', this.windowKeyDownListener);
177
- window.removeEventListener('keyup', this.windowKeyUpListener);
178
- }
179
158
  }
src/engine-components/webxr/WebXRImageTracking.ts CHANGED
@@ -247,8 +247,11 @@
247
247
  if (!frame) return;
248
248
 
249
249
  if (frame.session && !("getImageTrackingResults" in frame)) {
250
- const warning = "Image tracking is currently not supported on this device. On Chrome for Android, you can enable the <a href=\"chrome://flags/#webxr-incubations\">chrome://flags/#webxr-incubations</a> flag.";
251
- console.log(warning);
250
+ const warning = "Image tracking is currently not supported on this device. On Chrome for Android, you can enable the <a target=\"_blank\" href=\"#\" onclick=\"() => console.log('I')\">chrome://flags/#webxr-incubations</a> flag.";
251
+ if (!this["didPrintWarning"]) {
252
+ this["didPrintWarning"] = true;
253
+ console.log(warning);
254
+ }
252
255
  showBalloonWarning(warning);
253
256
  return;
254
257
  }
src/engine/engine_lifecycle_api.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { FrameEvent } from "./engine_context.js";
2
+ import { LifecycleMethod, registerCallback } from "./engine_lifecycle_functions_internal.js";
3
+
4
+
5
+ /** Register a callback in the engine start event */
6
+ export function onStart(cb: LifecycleMethod) {
7
+ registerCallback(cb, FrameEvent.Start);
8
+ }
9
+
10
+
11
+ /** Register a callback in the engine update event
12
+ * called every frame
13
+ * */
14
+ export function onUpdate(cb: LifecycleMethod) {
15
+ registerCallback(cb, FrameEvent.Update);
16
+ }
src/engine/engine_lifecycle_functions_internal.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { safeInvoke } from "./engine_generic_utils.js";
2
+ import { FrameEvent, type Context } from "./engine_context.js";
3
+
4
+ export declare type LifecycleMethod = (ctx: Context) => void;
5
+
6
+ const allMethods = new Map<FrameEvent, Array<LifecycleMethod>>();
7
+
8
+ export function registerCallback(cb: LifecycleMethod, evt: FrameEvent) {
9
+ if (!allMethods.has(evt)) {
10
+ allMethods.set(evt, new Array<LifecycleMethod>());
11
+ }
12
+ allMethods.get(evt)!.push(cb);
13
+ }
14
+
15
+ export function invokeLifecycleFunctions(ctx: Context, evt: FrameEvent) {
16
+ const methods = allMethods.get(evt);
17
+ if (methods) {
18
+ if (methods.length > 0) {
19
+ let array = methods;
20
+ if (evt === FrameEvent.Start) {
21
+ array = [...methods];
22
+ methods.length = 0;
23
+ }
24
+ invoke(ctx, array);
25
+ }
26
+ }
27
+ }
28
+
29
+
30
+ function invoke(ctx: Context, methods: Array<LifecycleMethod>) {
31
+ for (let i = methods.length - 1; i >= 0; i--) {
32
+ const method = methods[i];
33
+ safeInvoke(method, ctx);
34
+ }
35
+ }