Needle Engine

Changes between version 3.32.9-alpha and 3.32.10-alpha
Files changed (11) hide show
  1. src/engine-components/AnimationCurve.ts +18 -2
  2. src/engine-components/Component.ts +5 -1
  3. src/engine/engine_context.ts +6 -4
  4. src/engine/engine_types.ts +3 -11
  5. src/engine/xr/NeedleXRSession.ts +3 -1
  6. src/engine-components/ParticleSystem.ts +22 -26
  7. src/engine-components/ParticleSystemModules.ts +56 -15
  8. src/engine-components/export/usdz/USDZExporter.ts +1 -1
  9. src/engine-components/webxr/WebARSessionRoot.ts +2 -2
  10. src/engine-components/webxr/WebXR.ts +3 -2
  11. src/engine-components/webxr/controllers/XRControllerFollow.ts +11 -2
src/engine-components/AnimationCurve.ts CHANGED
@@ -23,6 +23,22 @@
23
23
  @serializable(Keyframe)
24
24
  keys!: Array<Keyframe>;
25
25
 
26
+ clone() {
27
+ const curve = new AnimationCurve();
28
+ curve.keys = this.keys?.map(k => {
29
+ const key = new Keyframe();
30
+ key.time = k.time;
31
+ key.value = k.value;
32
+ key.inTangent = k.inTangent;
33
+ key.inWeight = k.inWeight;
34
+ key.outTangent = k.outTangent;
35
+ key.outWeight = k.outWeight;
36
+ key.weightedMode = k.weightedMode;
37
+ return key;
38
+ }) || [];
39
+ return curve;
40
+ }
41
+
26
42
  get duration(): number {
27
43
  if (!this.keys || this.keys.length == 0) return 0;
28
44
  return this.keys[this.keys.length - 1].time;
@@ -38,9 +54,9 @@
38
54
  for (let i = 0; i < this.keys.length; i++) {
39
55
  const kf = this.keys[i];
40
56
  if (kf.time <= time) {
41
- const hasNextKeyframe = i+1 < this.keys.length;
57
+ const hasNextKeyframe = i + 1 < this.keys.length;
42
58
  if (hasNextKeyframe) {
43
- const nextKf = this.keys[i+1];
59
+ const nextKf = this.keys[i + 1];
44
60
  // if the next
45
61
  if (nextKf.time < time) continue;
46
62
  // tangents are set to Infinity if interpolation is set to constant - in that case we should always return the floored value
src/engine-components/Component.ts CHANGED
@@ -552,7 +552,11 @@
552
552
 
553
553
  /** @internal */
554
554
  constructor() {
555
- this.__internalNewInstanceCreated();
555
+ this.__didAwake = false;
556
+ this.__didStart = false;
557
+ this.__didEnable = false;
558
+ this.__isEnabled = undefined;
559
+ this.__destroyed = false;
556
560
  }
557
561
 
558
562
 
src/engine/engine_context.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  import { getLoader } from './engine_gltf.js';
19
19
  import { Input } from './engine_input.js';
20
20
  import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
21
- import { type ILightDataRegistry,LightDataRegistry } from './engine_lightdata.js';
21
+ import { type ILightDataRegistry, LightDataRegistry } from './engine_lightdata.js';
22
22
  import * as looputils from './engine_mainloop_utils.js';
23
23
  import { NetworkConnection } from './engine_networking.js';
24
24
  import { isLocalNetwork } from './engine_networking_utils.js';
@@ -30,7 +30,7 @@
30
30
  import { type CoroutineData, type GLTF, type ICamera, type IComponent, type IContext, type ILight, INeedleXRSession, type LoadedGLTF } from "./engine_types.js";
31
31
  import * as utils from "./engine_utils.js";
32
32
  import { delay, getParam } from './engine_utils.js';
33
- import type { INeedleXRSessionEventReceiver } from './engine_xr.js';
33
+ import type { INeedleXRSessionEventReceiver, NeedleXRSession } from './engine_xr.js';
34
34
 
35
35
 
36
36
  const debug = utils.getParam("debugcontext");
@@ -242,8 +242,10 @@
242
242
  }
243
243
  get isInXR() { return this.renderer?.xr?.isPresenting || false; }
244
244
  /** shorthand for `NeedleXRSession.active`
245
- * Automatically set by NeedleXRSession when a XR session is active */
246
- xr: INeedleXRSession | null = null;
245
+ * Automatically set by NeedleXRSession when a XR session is active
246
+ * @returns the active XR session or null if no session is active
247
+ * */
248
+ xr: NeedleXRSession | null = null;
247
249
  get xrSessionMode() { return this.xr?.mode; }
248
250
  get isInVR() { return this.xrSessionMode === "immersive-vr"; }
249
251
  get isInAR() { return this.xrSessionMode === "immersive-ar"; }
src/engine/engine_types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Camera, Color, Material, Mesh,Object3D, Quaternion, Ray, Scene, WebGLRenderer } from "three";
1
+ import type { Camera, Color, Material, Mesh, Object3D, Quaternion, Ray, Scene, WebGLRenderer } from "three";
2
2
  import { Vector3 } from "three";
3
3
  import { type GLTF as GLTF3 } from "three/examples/jsm/loaders/GLTFLoader.js";
4
4
 
@@ -6,7 +6,7 @@
6
6
  import { CollisionDetectionMode, type PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types.js";
7
7
  import { RenderTexture } from "./engine_texture.js";
8
8
  import { CircularBuffer } from "./engine_utils.js";
9
- import { type INeedleXRSessionEventReceiver } from "./engine_xr.js";
9
+ import type { INeedleXRSessionEventReceiver, NeedleXRSession } from "./engine_xr.js";
10
10
 
11
11
  export type GLTF = GLTF3 & {
12
12
  // asset: { generator: string, version: string }
@@ -98,15 +98,7 @@
98
98
  stopAllCoroutinesFrom(script: IComponent);
99
99
  }
100
100
 
101
- export interface INeedleXRSession {
102
- get running(): boolean;
103
- readonly mode: XRSessionMode;
104
- readonly session: XRSession;
105
-
106
- get isVR();
107
- get isAR();
108
- get isPassThrough();
109
- }
101
+ export type INeedleXRSession = NeedleXRSession;
110
102
 
111
103
  export declare interface INeedleEngineComponent extends HTMLElement {
112
104
  getAROverlayContainer(): HTMLElement;
src/engine/xr/NeedleXRSession.ts CHANGED
@@ -32,6 +32,7 @@
32
32
  export type NeedleXRHitTestResult = { hit: XRHitTestResult, position: Vector3, quaternion: Quaternion };
33
33
 
34
34
  const debug = getParam("debugwebxr");
35
+ const debugFPS = getParam("stats");
35
36
 
36
37
  // TODO: move this into the IComponent interface!?
37
38
  export interface INeedleXRSessionEventReceiver extends Pick<IComponent, "destroyed"> {
@@ -765,6 +766,7 @@
765
766
 
766
767
  const newController = new NeedleXRController(this, newInputSource, index);
767
768
  this.controllers.push(newController);
769
+ this.controllers.sort((a, b) => a.index - b.index);
768
770
  this._newControllers.push(newController);
769
771
  this.invokeControllerEvent(newController, this._controllerAdded, "added");
770
772
 
@@ -883,7 +885,7 @@
883
885
  this.updateActiveXRRig();
884
886
  }
885
887
 
886
- if (debug && this.rig) {
888
+ if ((debug || debugFPS) && this.rig) {
887
889
  const pos = this.rig.gameObject.worldPosition;
888
890
  const forward = this.rig.gameObject.worldForward;
889
891
  pos.add(forward.multiplyScalar(1.5));
src/engine-components/ParticleSystem.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as THREE from "three";
2
- import { AxesHelper, BufferGeometry, Color, Material, Matrix4, Mesh, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, PlaneGeometry, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
3
- import type { BatchedRenderer, Behavior, BehaviorPlugin, BillBoardSettings, BurstParameters, ColorGenerator, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystemParameters, PointEmitter, RecordState, RotationGenerator, SizeOverLife, TrailSettings, ValueGenerator,VFXBatchSettings } from "three.quarks";
4
- import { BatchedParticleRenderer, ConstantColor, ConstantValue, ParticleSystem as _ParticleSystem, RenderMode,TrailBatch, TrailParticle } from "three.quarks";
2
+ import { AxesHelper, BackSide, BufferGeometry, Color, FrontSide, Material, Matrix4, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, PlaneGeometry, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
3
+ import type { BatchedRenderer, Behavior, BehaviorPlugin, BillBoardSettings, BurstParameters, ColorGenerator, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystemParameters, PointEmitter, RecordState, RotationGenerator, SizeOverLife, TrailSettings, ValueGenerator, VFXBatchSettings } from "three.quarks";
4
+ import { BatchedParticleRenderer, ConstantColor, ConstantValue, ParticleSystem as _ParticleSystem, RenderMode, TrailBatch, TrailParticle } from "three.quarks";
5
5
 
6
6
  import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
7
7
  import { Gizmos } from "../engine/engine_gizmos.js";
@@ -17,7 +17,7 @@
17
17
  import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js";
18
18
  import { Behaviour, GameObject } from "./Component.js";
19
19
  import { RGBAColor } from "./js-extensions/RGBAColor.js";
20
- import { ColorBySpeedModule, ColorOverLifetimeModule, EmissionModule, InheritVelocityModule, type IParticleSystem, LimitVelocityOverLifetimeModule, MainModule, MinMaxCurve, MinMaxGradient, NoiseModule, ParticleBurst, ParticleSystemRenderMode, ParticleSystemScalingMode,ParticleSystemShapeType, ParticleSystemSimulationSpace, RotationBySpeedModule, RotationOverLifetimeModule, ShapeModule, SizeBySpeedModule, SizeOverLifetimeModule, TextureSheetAnimationModule, TrailModule, VelocityOverLifetimeModule } from "./ParticleSystemModules.js"
20
+ import { ColorBySpeedModule, ColorOverLifetimeModule, EmissionModule, InheritVelocityModule, type IParticleSystem, LimitVelocityOverLifetimeModule, MainModule, MinMaxCurve, MinMaxGradient, NoiseModule, ParticleBurst, ParticleSystemRenderMode, ParticleSystemScalingMode, ParticleSystemShapeType, ParticleSystemSimulationSpace, RotationBySpeedModule, RotationOverLifetimeModule, ShapeModule, SizeBySpeedModule, SizeOverLifetimeModule, TextureSheetAnimationModule, TrailModule, VelocityOverLifetimeModule } from "./ParticleSystemModules.js"
21
21
  import { ParticleSubEmitter } from "./ParticleSystemSubEmitter.js";
22
22
 
23
23
  const debug = getParam("debugparticles");
@@ -25,7 +25,7 @@
25
25
  const debugProgressiveLoading = getParam("debugprogressive");
26
26
 
27
27
 
28
- export type { Particle as QParticle,Behavior as QParticleBehaviour } from "three.quarks"
28
+ export type { Particle as QParticle, Behavior as QParticleBehaviour } from "three.quarks"
29
29
 
30
30
 
31
31
 
@@ -80,23 +80,17 @@
80
80
  return res;
81
81
  }
82
82
 
83
- private static _havePatchedQuarkShaders = false;
84
-
85
83
  getMaterial(trailEnabled: boolean = false) {
84
+ const material = (trailEnabled === true && this.trailMaterial) ? this.trailMaterial : this.particleMaterial;
86
85
 
87
- if (!ParticleSystemRenderer._havePatchedQuarkShaders) {
88
- ParticleSystemRenderer._havePatchedQuarkShaders = true;
89
-
90
- // HACK patch three.quarks fo three152+, see https://github.com/Alchemist0823/three.quarks/issues/56#issuecomment-1560825038
91
- const _rebuild = TrailBatch.prototype.rebuildMaterial;
92
- TrailBatch.prototype.rebuildMaterial = function () {
93
- _rebuild.call(this);
94
- this.material.defines.MAP_UV = "uv";
86
+ if (material) {
87
+ if (trailEnabled) {
88
+ // the particle material for trails must be DoubleSide or BackSide (since otherwise the trail is invisible)
89
+ if (material.side === FrontSide)
90
+ material.side = BackSide;
95
91
  }
96
92
  }
97
93
 
98
- const material = (trailEnabled === true && this.trailMaterial) ? this.trailMaterial : this.particleMaterial;
99
-
100
94
  // progressive load on start
101
95
  // TODO: figure out how to do this before particle system rendering so we only load textures for visible materials
102
96
  if (material && !suppressProgressiveLoading && material["_didRequestTextureLOD"] === undefined) {
@@ -397,7 +391,7 @@
397
391
  let size = particle.size;
398
392
  if (size <= 0 && !this.system.trails.sizeAffectsWidth) {
399
393
  // Not sure where we get to 100* from, tested in SOC trong com
400
- size = 100 * this.system.trails.widthOverTrail.evaluate(.5, trailParticle[$trailWidthRandom]);
394
+ size = 20 * this.system.trails.widthOverTrail.evaluate(.5, trailParticle[$trailWidthRandom]);
401
395
  }
402
396
  state.size = this.system.trails.getWidth(size, age01, pos01, trailParticle[$trailWidthRandom]);
403
397
  state.color.copy(particle.color);
@@ -429,8 +423,7 @@
429
423
  initialize(particle: Particle): void {
430
424
  const simulationSpeed = this.system.main.simulationSpeed;
431
425
 
432
- const factor = 1;
433
- particle.startSpeed = this.system.main.startSpeed.evaluate(Math.random(), Math.random()) * factor;
426
+ particle.startSpeed = this.system.main.startSpeed.evaluate(Math.random(), Math.random());
434
427
  particle.velocity.copy(this.system.shape.getDirection(particle.position)).multiplyScalar(particle.startSpeed);
435
428
  if (this.system.inheritVelocity?.enabled) {
436
429
  this.system.inheritVelocity.applyInitial(particle.velocity);
@@ -615,8 +608,7 @@
615
608
  if (mat && mat["map"]) {
616
609
  const original = mat["map"]! as THREE.Texture;
617
610
  // cache the last original one so we're not creating tons of clones
618
- if (this.clonedTexture.original !== original || !this.clonedTexture.clone)
619
- {
611
+ if (this.clonedTexture.original !== original || !this.clonedTexture.clone) {
620
612
  const tex = original.clone();
621
613
  tex.premultiplyAlpha = false;
622
614
  tex.colorSpace = THREE.LinearSRGBColorSpace;
@@ -755,7 +747,7 @@
755
747
  readonly limitVelocityOverLifetime!: LimitVelocityOverLifetimeModule;
756
748
 
757
749
  @serializable(InheritVelocityModule)
758
- readonly inheritVelocity!: InheritVelocityModule;
750
+ inheritVelocity!: InheritVelocityModule;
759
751
 
760
752
  @serializable(ColorBySpeedModule)
761
753
  readonly colorBySpeed!: ColorBySpeedModule;
@@ -934,6 +926,8 @@
934
926
  }
935
927
 
936
928
  awake(): void {
929
+ this._worldPositionFrame = -1;
930
+
937
931
  this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
938
932
 
939
933
  if (!this.main) {
@@ -967,9 +961,12 @@
967
961
  const emitter = this._particleSystem.emitter;
968
962
  this.context.scene.add(emitter);
969
963
 
970
- this.inheritVelocity?.awake();
971
- this.inheritVelocity.system = this;
964
+ if (this.inheritVelocity.system && this.inheritVelocity.system !== this) {
965
+ this.inheritVelocity = this.inheritVelocity.clone();
966
+ }
967
+ this.inheritVelocity.awake(this);
972
968
 
969
+
973
970
  if (debug) {
974
971
  console.log(this);
975
972
  this.gameObject.add(new AxesHelper(1))
@@ -1113,7 +1110,6 @@
1113
1110
  this.shape.update(this, this.context, this.main.simulationSpace, this.gameObject);
1114
1111
  this.noise.update(this.context);
1115
1112
 
1116
- this.inheritVelocity.system = this;
1117
1113
  this.inheritVelocity?.update(this.context);
1118
1114
  this.velocityOverLifetime.update(this);
1119
1115
  }
src/engine-components/ParticleSystemModules.ts CHANGED
@@ -180,6 +180,19 @@
180
180
  @serializable()
181
181
  curveMultiplier?: number;
182
182
 
183
+ clone() {
184
+ const clone = new MinMaxCurve();
185
+ clone.mode = this.mode;
186
+ clone.constant = this.constant;
187
+ clone.constantMin = this.constantMin;
188
+ clone.constantMax = this.constantMax;
189
+ clone.curve = this.curve?.clone();
190
+ clone.curveMin = this.curveMin?.clone();
191
+ clone.curveMax = this.curveMax?.clone();
192
+ clone.curveMultiplier = this.curveMultiplier;
193
+ return clone;
194
+ }
195
+
183
196
  evaluate(t01: number, lerpFactor?: number): number {
184
197
  const t = lerpFactor === undefined ? Math.random() : lerpFactor;
185
198
  switch (this.mode) {
@@ -600,6 +613,7 @@
600
613
  }
601
614
  getPosition(): void {
602
615
  this._vector.set(0, 0, 0);
616
+
603
617
  const pos = this._temp.copy(this.position);
604
618
  const isWorldSpace = this._space === ParticleSystemSimulationSpace.World;
605
619
  if (isWorldSpace) {
@@ -742,7 +756,7 @@
742
756
  vec.z = z;
743
757
  }
744
758
 
745
- private randomCirclePoint(pos:Vec3, radius:number, thickness:number, arg:number, vec:Vec3){
759
+ private randomCirclePoint(pos: Vec3, radius: number, thickness: number, arg: number, vec: Vec3) {
746
760
  const u = Math.random();
747
761
  const theta = 2 * Math.PI * u * (arg / 360);
748
762
  const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
@@ -973,7 +987,7 @@
973
987
  @serializable()
974
988
  worldSpace: boolean = false;
975
989
 
976
- getWidth(size: number, _life01: number, pos01: number, t : number) {
990
+ getWidth(size: number, _life01: number, pos01: number, t: number) {
977
991
  const res = this.widthOverTrail.evaluate(pos01, t);
978
992
  size *= res;
979
993
  return size;
@@ -1385,29 +1399,54 @@
1385
1399
  @serializable()
1386
1400
  mode!: ParticleSystemInheritVelocityMode;
1387
1401
 
1402
+ clone() {
1403
+ const ni = new InheritVelocityModule();
1404
+ ni.enabled = this.enabled;
1405
+ ni.curve = this.curve?.clone();
1406
+ ni.curveMultiplier = this.curveMultiplier;
1407
+ ni.mode = this.mode;
1408
+ return ni;
1409
+ }
1410
+
1388
1411
  system!: IParticleSystem;
1389
1412
 
1390
- private _lastWorldPosition: Vector3 | null = null;
1391
- private _velocity: Vector3 = new Vector3();
1392
- private _temp: Vector3 = new Vector3();
1413
+ private get _lastWorldPosition() {
1414
+ if (!this.system['_iv_lastWorldPosition']) {
1415
+ this.system['_iv_lastWorldPosition'] = new Vector3();
1416
+ }
1417
+ return this.system['_iv_lastWorldPosition'];
1418
+ }
1419
+ private get _velocity() {
1420
+ if (!this.system['_iv_velocity']) {
1421
+ this.system['_iv_velocity'] = new Vector3();
1422
+ }
1423
+ return this.system['_iv_velocity'];
1424
+ }
1393
1425
 
1394
- awake() {
1395
- this._lastWorldPosition = null!;
1396
- this._velocity = new Vector3();
1397
- this._temp = new Vector3();
1426
+ private readonly _temp: Vector3 = new Vector3();
1427
+ private _firstUpdate: boolean = true;
1428
+
1429
+ awake(system: IParticleSystem) {
1430
+ this.system = system;
1431
+ this.reset();
1398
1432
  }
1399
1433
 
1400
- update (_context: Context) {
1434
+ reset() {
1435
+ this._firstUpdate = true;
1436
+ }
1437
+
1438
+ update(_context: Context) {
1401
1439
  if (!this.enabled) return;
1402
1440
  if (this.system.worldspace === false) return;
1403
- if (this._lastWorldPosition) {
1441
+ if (this._firstUpdate) {
1442
+ this._firstUpdate = false;
1443
+ this._velocity.set(0, 0, 0);
1444
+ this._lastWorldPosition.copy(this.system.worldPos);
1445
+ }
1446
+ else if (this._lastWorldPosition) {
1404
1447
  this._velocity.copy(this.system.worldPos).sub(this._lastWorldPosition).multiplyScalar(1 / this.system.deltaTime);
1405
1448
  this._lastWorldPosition.copy(this.system.worldPos);
1406
1449
  }
1407
- else {
1408
- this._velocity.set(0, 0, 0);
1409
- this._lastWorldPosition = this.system.worldPos.clone();
1410
- }
1411
1450
  }
1412
1451
 
1413
1452
  // TODO: make work for subsystems
@@ -1421,8 +1460,10 @@
1421
1460
  }
1422
1461
  }
1423
1462
 
1463
+ private _frames = 0;
1424
1464
  applyCurrent(vel: Vector3, t01: number, lerpFactor: number) {
1425
1465
  if (!this.enabled) return;
1466
+ if (!this.system) return;
1426
1467
  if (this.system.worldspace === false) return;
1427
1468
  if (this.mode === ParticleSystemInheritVelocityMode.Current) {
1428
1469
  const factor = this.curve.evaluate(t01, lerpFactor);
src/engine-components/export/usdz/USDZExporter.ts CHANGED
@@ -403,7 +403,7 @@
403
403
  const scale = 1 / sessionRoot!.arScale;
404
404
  if (debug) console.log("AR Session Root scale", scale, target);
405
405
  target.matrix.makeScale(scale, scale, scale);
406
- if (sessionRoot.invertForward) {
406
+ if (sessionRoot.invertForward == false) {
407
407
  target.matrix.multiply(new Matrix4().makeRotationY(Math.PI));
408
408
  }
409
409
  }
src/engine-components/webxr/WebARSessionRoot.ts CHANGED
@@ -36,7 +36,7 @@
36
36
  }
37
37
  private _arScale: number = 1;
38
38
 
39
- /** When enabled the scene will be rotated by 180° in the Y axes */
39
+ /** When enabled the placed scene forward direction will towards the XRRig */
40
40
  @serializable()
41
41
  invertForward: boolean = false;
42
42
 
@@ -357,7 +357,7 @@
357
357
  rigObject.updateMatrix();
358
358
  // if invert forward is disabled we need to invert the forward rotation
359
359
  // we want to look into positive Z direction (if invertForward is enabled we look into negative Z direction)
360
- if (!this.invertForward)
360
+ if (this.invertForward == false)
361
361
  rigObject.matrix.premultiply(invertForwardMatrix);
362
362
  rigObject.matrix.premultiply(this._startOffset);
363
363
 
src/engine-components/webxr/WebXR.ts CHANGED
@@ -75,8 +75,8 @@
75
75
 
76
76
  onEnable(): void {
77
77
  if (this.useQuicklookExport) {
78
- this._usdzExporter = GameObject.findObjectOfType(USDZExporter) || undefined;
79
- if (!this._usdzExporter) {
78
+ const existingUSDZExporter = GameObject.findObjectOfType(USDZExporter);
79
+ if (!existingUSDZExporter) {
80
80
  // if no USDZ Exporter is found we add one and assign the scene to be exported
81
81
  if (debug) console.log("WebXR: Adding USDZExporter");
82
82
  this._usdzExporter = GameObject.addNewComponent(this.gameObject, USDZExporter);
@@ -106,6 +106,7 @@
106
106
  onDisable(): void {
107
107
  // remove the container automatically if it was added to the shadow root
108
108
  this._container?.remove();
109
+ this._usdzExporter?.destroy();
109
110
  }
110
111
 
111
112
  private async handleOfferSession() {
src/engine-components/webxr/controllers/XRControllerFollow.ts CHANGED
@@ -29,6 +29,9 @@
29
29
  */
30
30
  controlVisibility: boolean = true;
31
31
 
32
+ /** when true it will use the grip space, otherwise the ray space */
33
+ useGripSpace = false;
34
+
32
35
  onUpdateXR(args: NeedleXREventArgs): void {
33
36
 
34
37
  // try to get the controller
@@ -49,8 +52,14 @@
49
52
  // we're following a controller (or hand)
50
53
  if (this.controlVisibility)
51
54
  this.gameObject.visible = true;
52
- this.gameObject.worldPosition = ctrl.gripWorldPosition;
53
- this.gameObject.worldQuaternion = ctrl.gripWorldQuaternion;
55
+ if (this.useGripSpace) {
56
+ this.gameObject.worldPosition = ctrl.gripWorldPosition;
57
+ this.gameObject.worldQuaternion = ctrl.gripWorldQuaternion;
58
+ }
59
+ else {
60
+ this.gameObject.worldPosition = ctrl.rayWorldPosition;
61
+ this.gameObject.worldQuaternion = ctrl.rayWorldQuaternion;
62
+ }
54
63
  }
55
64
 
56
65
  }