@needle-tools/engine 5.1.0-alpha.8 → 5.1.0-beta

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 (122) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{materialx-CE2sUv2B.umd.cjs → materialx-B47Bz-xs.umd.cjs} +1 -1
  4. package/dist/{materialx-u1EqYrhu.min.js → materialx-B85WjP7E.min.js} +1 -1
  5. package/dist/{materialx-vyB2Zbt4.js → materialx-jkHmVPez.js} +1 -1
  6. package/dist/{needle-engine.bundle-3LOy9tj8.js → needle-engine.bundle-BWkDSRyF.js} +6248 -6001
  7. package/dist/{needle-engine.bundle-BuI2pjv1.min.js → needle-engine.bundle-BvgS8xMP.min.js} +171 -171
  8. package/dist/{needle-engine.bundle-DQy9vQdJ.umd.cjs → needle-engine.bundle-CdVr_iIe.umd.cjs} +142 -142
  9. package/dist/needle-engine.d.ts +359 -46
  10. package/dist/needle-engine.js +618 -606
  11. package/dist/needle-engine.min.js +1 -1
  12. package/dist/needle-engine.umd.cjs +1 -1
  13. package/dist/{vendor-ButPLzor.umd.cjs → vendor-1UvpPPSB.umd.cjs} +2 -2
  14. package/dist/{vendor-DqZC4Is7.min.js → vendor-BQ2Vuntm.min.js} +4 -4
  15. package/dist/{vendor-DkWSNjMV.js → vendor-BslSKZPo.js} +113 -106
  16. package/lib/engine/api.d.ts +1 -1
  17. package/lib/engine/api.js +1 -1
  18. package/lib/engine/api.js.map +1 -1
  19. package/lib/engine/engine_audio.d.ts +30 -1
  20. package/lib/engine/engine_audio.js +57 -13
  21. package/lib/engine/engine_audio.js.map +1 -1
  22. package/lib/engine/engine_init.js +2 -2
  23. package/lib/engine/engine_init.js.map +1 -1
  24. package/lib/engine/engine_license.d.ts +7 -7
  25. package/lib/engine/engine_license.js +72 -72
  26. package/lib/engine/engine_license.js.map +1 -1
  27. package/lib/engine/engine_math.d.ts +1 -1
  28. package/lib/engine/engine_math.js +10 -4
  29. package/lib/engine/engine_math.js.map +1 -1
  30. package/lib/engine/engine_networking_blob.js +3 -3
  31. package/lib/engine/engine_networking_blob.js.map +1 -1
  32. package/lib/engine/engine_scenedata.js +7 -0
  33. package/lib/engine/engine_scenedata.js.map +1 -1
  34. package/lib/engine/engine_serialization_core.js +7 -2
  35. package/lib/engine/engine_serialization_core.js.map +1 -1
  36. package/lib/engine/engine_time_utils.d.ts +10 -6
  37. package/lib/engine/engine_time_utils.js +10 -6
  38. package/lib/engine/engine_time_utils.js.map +1 -1
  39. package/lib/engine/engine_utils_qrcode.js +2 -2
  40. package/lib/engine/engine_utils_qrcode.js.map +1 -1
  41. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  42. package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
  43. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  44. package/lib/engine/webcomponents/needle-engine.js +2 -2
  45. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  46. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  47. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  48. package/lib/engine/xr/TempXRContext.js +2 -2
  49. package/lib/engine/xr/TempXRContext.js.map +1 -1
  50. package/lib/engine-components/AudioSource.d.ts +46 -2
  51. package/lib/engine-components/AudioSource.js +268 -60
  52. package/lib/engine-components/AudioSource.js.map +1 -1
  53. package/lib/engine-components/Component.js +2 -1
  54. package/lib/engine-components/Component.js.map +1 -1
  55. package/lib/engine-components/DragControls.js +2 -2
  56. package/lib/engine-components/DragControls.js.map +1 -1
  57. package/lib/engine-components/DragControlsConstraints.d.ts +0 -15
  58. package/lib/engine-components/DragControlsConstraints.js +14 -8
  59. package/lib/engine-components/DragControlsConstraints.js.map +1 -1
  60. package/lib/engine-components/EventList.js +1 -1
  61. package/lib/engine-components/EventList.js.map +1 -1
  62. package/lib/engine-components/SceneSwitcher.d.ts +2 -0
  63. package/lib/engine-components/SceneSwitcher.js +43 -16
  64. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  65. package/lib/engine-components/SeeThrough.d.ts +39 -0
  66. package/lib/engine-components/SeeThrough.js +69 -1
  67. package/lib/engine-components/SeeThrough.js.map +1 -1
  68. package/lib/engine-components/api.d.ts +1 -0
  69. package/lib/engine-components/api.js +1 -0
  70. package/lib/engine-components/api.js.map +1 -1
  71. package/lib/engine-components/codegen/components.d.ts +10 -0
  72. package/lib/engine-components/codegen/components.js +10 -0
  73. package/lib/engine-components/codegen/components.js.map +1 -1
  74. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  75. package/lib/engine-components/timeline/PlayableDirector.js +1 -1
  76. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  77. package/lib/engine-components/web/ScrollFollow.d.ts +33 -0
  78. package/lib/engine-components/web/ScrollFollow.js +164 -22
  79. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  80. package/lib/needle-app.d.ts +18 -0
  81. package/lib/needle-app.js +19 -0
  82. package/lib/needle-app.js.map +1 -0
  83. package/package.json +3 -2
  84. package/plugins/common/license.js +25 -4
  85. package/plugins/common/needle-engine.js +5 -2
  86. package/plugins/types/userconfig.d.ts +22 -0
  87. package/plugins/vite/dts-generator.d.ts +24 -0
  88. package/plugins/vite/dts-generator.js +40 -7
  89. package/plugins/vite/index.d.ts +2 -0
  90. package/plugins/vite/index.js +17 -0
  91. package/plugins/vite/license.js +4 -4
  92. package/plugins/vite/needle-app.js +78 -5
  93. package/plugins/vite/remote.d.ts +28 -0
  94. package/plugins/vite/remote.js +124 -0
  95. package/src/engine/api.ts +1 -1
  96. package/src/engine/engine_audio.ts +68 -15
  97. package/src/engine/engine_gameobject.ts +1 -1
  98. package/src/engine/engine_init.ts +2 -2
  99. package/src/engine/engine_license.ts +69 -69
  100. package/src/engine/engine_math.ts +11 -4
  101. package/src/engine/engine_networking_blob.ts +3 -3
  102. package/src/engine/engine_scenedata.ts +7 -2
  103. package/src/engine/engine_serialization_core.ts +7 -2
  104. package/src/engine/engine_time_utils.ts +10 -6
  105. package/src/engine/engine_utils_qrcode.ts +2 -2
  106. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  107. package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
  108. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  109. package/src/engine/webcomponents/needle-engine.ts +2 -2
  110. package/src/engine/xr/TempXRContext.ts +2 -2
  111. package/src/engine-components/AudioSource.ts +256 -55
  112. package/src/engine-components/Component.ts +2 -1
  113. package/src/engine-components/DragControls.ts +3 -2
  114. package/src/engine-components/DragControlsConstraints.ts +16 -8
  115. package/src/engine-components/EventList.ts +1 -1
  116. package/src/engine-components/SceneSwitcher.ts +37 -15
  117. package/src/engine-components/SeeThrough.ts +75 -1
  118. package/src/engine-components/api.ts +1 -0
  119. package/src/engine-components/codegen/components.ts +10 -0
  120. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
  121. package/src/engine-components/timeline/PlayableDirector.ts +1 -1
  122. package/src/engine-components/web/ScrollFollow.ts +168 -22
@@ -163,6 +163,8 @@ export class KeepScaleConstraint implements IDragConstraint {
163
163
  * new ScaleLimitConstraint(0.01, 10000, true, object.worldScale.clone());
164
164
  * ```
165
165
  */
166
+ const _scaleLimitWs = new Vector3();
167
+
166
168
  export class ScaleLimitConstraint implements IDragConstraint {
167
169
  /**
168
170
  * @param min Lower bound. Relative mode: fraction of initial scale. Raw mode: absolute world-scale per axis.
@@ -184,27 +186,33 @@ export class ScaleLimitConstraint implements IDragConstraint {
184
186
  }
185
187
 
186
188
  apply(followObject: GameObject): void {
187
- const ws = followObject.worldScale;
189
+ // Read world scale directly from the matrix columns (col 0/1/2 lengths) rather than
190
+ // going through the worldScale getter, which internally calls updateWorldMatrix() again.
191
+ // We call it once here so the matrix is fresh, then read the elements directly.
192
+ const obj = followObject as unknown as Object3D;
193
+ obj.updateWorldMatrix(true, false);
194
+ const e = obj.matrixWorld.elements;
195
+ const sx = Math.sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]);
196
+ const sy = Math.sqrt(e[4] * e[4] + e[5] * e[5] + e[6] * e[6]);
197
+ const sz = Math.sqrt(e[8] * e[8] + e[9] * e[9] + e[10] * e[10]);
198
+ _scaleLimitWs.set(sx, sy, sz);
199
+ const ws = _scaleLimitWs;
200
+
188
201
  if (this.relativeToInitialScale) {
189
- // Derive the uniform scale ratio from the magnitude of the current world scale
190
- // relative to the initial world scale magnitude.
191
202
  const initLen = this._initialWorldScale.length();
192
203
  if (initLen < 1e-10) return;
193
204
  const ratio = ws.length() / initLen;
194
205
  const clamped = Math.max(this.min, Math.min(this.max, ratio));
195
206
  if (Math.abs(clamped - ratio) > 1e-9) {
196
- ws.copy(this._initialWorldScale).multiplyScalar(clamped);
197
- followObject.worldScale = ws;
207
+ followObject.worldScale = ws.copy(this._initialWorldScale).multiplyScalar(clamped);
198
208
  followObject.updateMatrix();
199
209
  }
200
210
  } else {
201
- // Absolute per-axis clamp.
202
211
  const cx = Math.max(this.min, Math.min(this.max, ws.x));
203
212
  const cy = Math.max(this.min, Math.min(this.max, ws.y));
204
213
  const cz = Math.max(this.min, Math.min(this.max, ws.z));
205
214
  if (cx !== ws.x || cy !== ws.y || cz !== ws.z) {
206
- ws.set(cx, cy, cz);
207
- followObject.worldScale = ws;
215
+ followObject.worldScale = ws.set(cx, cy, cz);
208
216
  followObject.updateMatrix();
209
217
  }
210
218
  }
@@ -56,7 +56,7 @@ export class CallInfo {
56
56
  if (args !== undefined && args.length > 0)
57
57
  argumentsBuffer.push(...args);
58
58
  argumentsBuffer.push(...this.arguments);
59
- this.target(...this.arguments);
59
+ this.target(...argumentsBuffer);
60
60
  argumentsBuffer.length = 0;
61
61
  }
62
62
  else {
@@ -614,7 +614,8 @@ export class SceneSwitcher extends Behaviour {
614
614
  this._currentScene = undefined;
615
615
  if (current) {
616
616
  if (debug) console.log("[SceneSwitcher] UNLOAD", current.url, "HasURL?: " + current.hasUrl)
617
- const sceneListener = this.tryGetSceneEventListener(current.asset as any as Object3D);
617
+ const activeSceneInstance = this._currentSceneAsset;
618
+ const sceneListener = this.tryGetSceneEventListener(activeSceneInstance as any as Object3D);
618
619
  if (sceneListener?.sceneClosing) {
619
620
  const res = sceneListener.sceneClosing();
620
621
  if (res instanceof Promise) await res;
@@ -624,20 +625,24 @@ export class SceneSwitcher extends Behaviour {
624
625
  // // then we unload it
625
626
  if (current.hasUrl) {
626
627
  current.unload();
627
- if (this._currentSceneAsset) destroy(this._currentSceneAsset, true, false);
628
+ if (activeSceneInstance) destroy(activeSceneInstance, true, false);
628
629
  }
629
630
  else {
630
- GameObject.remove(this._currentSceneAsset);
631
+ GameObject.remove(activeSceneInstance);
631
632
  }
633
+ this._currentSceneAsset = undefined;
632
634
  }
633
635
  }
634
636
 
635
637
  private _currentlyLoadingScene?: AssetReference;
638
+ private _loadingRequestId: number = 0;
639
+ private _activeLoadingRequestId: number = 0;
636
640
  /** @internal */
637
641
  private async __internalSwitchScene(scene: AssetReference): Promise<boolean> {
638
642
 
639
643
  await this.__unloadCurrentScene();
640
644
  const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
645
+ const loadingRequestId = ++this._loadingRequestId;
641
646
 
642
647
  try {
643
648
  if (debug) console.debug(`${Date.now()} [SceneSwitcher] Loading scene start: ${scene.url} (index: ${index})`);
@@ -647,9 +652,10 @@ export class SceneSwitcher extends Behaviour {
647
652
  const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
648
653
  this.dispatchEvent(loadStartEvt);
649
654
  this.sceneLoadingStart?.invoke(loadStartEvt.detail);
650
- await this.onStartLoading();
655
+ await this.onStartLoading(loadingRequestId);
651
656
  // start loading and wait for the scene to be loaded
652
657
  await scene.loadAssetAsync((_, prog) => {
658
+ if (loadingRequestId !== this._activeLoadingRequestId) return;
653
659
  if (debug) {
654
660
  const t01 = prog.loaded / prog.total;
655
661
  const progressBarString = "[" + "=".repeat(Math.floor(t01 * 20)) + "-".repeat(20 - Math.floor(t01 * 20)) + "]";
@@ -659,12 +665,14 @@ export class SceneSwitcher extends Behaviour {
659
665
  this.dispatchEvent(prog);
660
666
  this.sceneLoadingProgress?.invoke(prog);
661
667
  }).catch(console.error);
662
- await this.onEndLoading();
668
+ await this.onEndLoading(loadingRequestId);
663
669
  const finishedEvt = new CustomEvent<LoadSceneEvent>("loadscene-finished", { detail: { scene: scene, switcher: this, index: index } });
664
670
  this.dispatchEvent(finishedEvt);
665
671
 
666
- this._currentLoadingProgress = undefined;
667
- this._currentlyLoadingScene = undefined;
672
+ if (loadingRequestId === this._activeLoadingRequestId) {
673
+ this._currentLoadingProgress = undefined;
674
+ this._currentlyLoadingScene = undefined;
675
+ }
668
676
 
669
677
  if (finishedEvt.defaultPrevented) {
670
678
  if (debug) console.warn("[SceneSwitcher] Adding loaded scene prevented:", scene, finishedEvt);
@@ -737,8 +745,8 @@ export class SceneSwitcher extends Behaviour {
737
745
  history.pushState(stateName, "unused", location.href);
738
746
  }
739
747
  }
740
- // Call SceneListener opened callback (if a SceneListener is in the scene)
741
- const sceneListener = this.tryGetSceneEventListener(scene.asset as any as Object3D);
748
+ // Call SceneListener opened callback on the scene instance that was actually added.
749
+ const sceneListener = this.tryGetSceneEventListener(this._currentSceneAsset as any as Object3D);
742
750
  if (sceneListener?.sceneOpened) {
743
751
  const res = sceneListener.sceneOpened(this);
744
752
  if (res instanceof Promise) await res;
@@ -791,8 +799,20 @@ export class SceneSwitcher extends Behaviour {
791
799
  for (let i = 0; i < this.scenes.length; i++) {
792
800
  const scene = this.scenes[i];
793
801
  if (!scene) continue;
794
- const name = scene instanceof Object3D ? scene.name : sceneUriToName(scene.url);
795
- if (name.toLowerCase().includes(lowerCaseValue)) {
802
+ // Match against the url-derived name AND, for in-scene object
803
+ // references (which have an empty url), the referenced object's
804
+ // name. Without the latter, `?scene=<name>` never resolves for
805
+ // non-prefab/object scenes and the switcher falls back to the
806
+ // default scene (scenes is AssetReference[], so the
807
+ // `scene instanceof Object3D` branch alone never matched them).
808
+ const candidateNames: string[] = [];
809
+ if (scene instanceof Object3D) candidateNames.push(scene.name);
810
+ else {
811
+ if (scene.url) candidateNames.push(sceneUriToName(scene.url));
812
+ const asset = scene.asset;
813
+ if (asset instanceof Object3D && asset.name) candidateNames.push(asset.name);
814
+ }
815
+ if (candidateNames.some(n => n?.toLowerCase().includes(lowerCaseValue))) {
796
816
  return this.select(i);
797
817
  }
798
818
  }
@@ -814,7 +834,8 @@ export class SceneSwitcher extends Behaviour {
814
834
  private _isCurrentlyLoading: boolean = false;
815
835
  private _currentLoadingProgress: ProgressEvent | undefined = undefined;
816
836
 
817
- private async onStartLoading() {
837
+ private async onStartLoading(loadingRequestId: number) {
838
+ this._activeLoadingRequestId = loadingRequestId;
818
839
  this._isCurrentlyLoading = true;
819
840
  if (this.loadingScene) {
820
841
  // save the last loading scene reference so that it can be changed at runtime
@@ -827,7 +848,7 @@ export class SceneSwitcher extends Behaviour {
827
848
  this._loadingScenePromise = this.loadingScene?.loadAssetAsync().then(res => res != null);
828
849
  }
829
850
  await this._loadingScenePromise;
830
- if (this._isCurrentlyLoading && this.loadingScene?.asset) {
851
+ if (loadingRequestId === this._activeLoadingRequestId && this._isCurrentlyLoading && this.loadingScene?.asset) {
831
852
  if (debug) console.log("Add loading scene", this.loadingScene.url, this.loadingScene.asset)
832
853
  const loadingScene = this.loadingScene.asset as any as Object3D;
833
854
  GameObject.add(loadingScene, this.gameObject);
@@ -839,7 +860,7 @@ export class SceneSwitcher extends Behaviour {
839
860
  }
840
861
  }
841
862
  // Invoke the event on a loading listener on the same object as the scene switcher
842
- if (this._isCurrentlyLoading) {
863
+ if (loadingRequestId === this._activeLoadingRequestId && this._isCurrentlyLoading) {
843
864
  const listener = this.tryGetSceneEventListener(this.gameObject);
844
865
  if (listener) {
845
866
  if (listener.sceneOpened) {
@@ -849,7 +870,8 @@ export class SceneSwitcher extends Behaviour {
849
870
  }
850
871
  }
851
872
  }
852
- private async onEndLoading() {
873
+ private async onEndLoading(loadingRequestId: number) {
874
+ if (loadingRequestId !== this._activeLoadingRequestId) return;
853
875
  this._isCurrentlyLoading = false;
854
876
  if (this.loadingScene?.asset) {
855
877
  if (debug) console.log("Remove loading scene", this.loadingScene.url);
@@ -11,7 +11,7 @@ import { Behaviour } from "./Component.js";
11
11
  import { IUSDExporterExtension } from "./export/usdz/Extension.js";
12
12
  import { USDZExporter } from "./export/usdz/USDZExporter.js";
13
13
  import type { OrbitControls } from "./OrbitControls.js";
14
- import { Renderer } from "./Renderer.js";
14
+ import { Renderer, ShadowCastingMode } from "./Renderer.js";
15
15
 
16
16
  const debugSeeThrough = getParam("debugseethrough");
17
17
 
@@ -164,6 +164,35 @@ export class SeeThrough extends Behaviour {
164
164
  @serializable()
165
165
  autoUpdate: boolean = true;
166
166
 
167
+ /**
168
+ * The alpha (opacity) the object is currently faded to, in the `[0, 1]` range.
169
+ * `1` means fully visible, `0` means fully hidden. This is the live, animated value
170
+ * (it lerps toward the target over {@link fadeDuration}), updated every frame the fade runs.
171
+ *
172
+ * External components can read this to react to the see-through state — e.g. toggling a
173
+ * collider, muting audio, or driving a UI prompt as the object fades in or out.
174
+ *
175
+ * @example React to the object becoming (almost) hidden
176
+ * ```ts
177
+ * if (seeThrough.currentAlpha < 0.1) {
178
+ * // object is essentially see-through now
179
+ * }
180
+ * ```
181
+ * @default 1
182
+ */
183
+ get currentAlpha(): number {
184
+ return this._currentAlpha;
185
+ }
186
+
187
+
188
+ /**
189
+ * Below this alpha the object stops casting a shadow. The shadow map only stores depth, so a
190
+ * faded object would otherwise keep casting a fully solid shadow (three.js does not fade shadows
191
+ * by opacity). Toggling shadow casting off near-invisible is the cleanest fix; we drive it via
192
+ * the {@link Renderer.shadowCastingMode} so it cooperates with the per-frame shadow settings the
193
+ * Renderer re-applies. Note this is a hard on/off, not a fade.
194
+ */
195
+ public static readonly shadowHideThreshold = 0.05;
167
196
 
168
197
  private readonly _referencePointVector: Vector3 = new Vector3();
169
198
  private readonly _referencePointDir: Vector3 = new Vector3();
@@ -171,6 +200,9 @@ export class SeeThrough extends Behaviour {
171
200
  private _renderer: Renderer[] | null = null;
172
201
  private _needsUpdate = true;
173
202
  private _id = i++;
203
+ private _currentAlpha: number = 1;
204
+ /** Renderers whose shadow casting we turned off while hidden, mapped to their original mode for restore. */
205
+ private readonly _suppressedShadowCasting = new Map<Renderer, ShadowCastingMode>();
174
206
 
175
207
  /** * @internal */
176
208
  onEnable() {
@@ -178,6 +210,12 @@ export class SeeThrough extends Behaviour {
178
210
  this._renderer = null;
179
211
  }
180
212
 
213
+ /** @internal */
214
+ onDestroy() {
215
+ // Don't leave renderers permanently non-casting if this component is removed.
216
+ this.restoreSuppressedShadowCasting();
217
+ }
218
+
181
219
  /**
182
220
  * @internal
183
221
  */
@@ -258,13 +296,49 @@ export class SeeThrough extends Behaviour {
258
296
  if (newAlpha >= 0.99) newAlpha = 1;
259
297
  else if (newAlpha <= 0.01) newAlpha = 0;
260
298
 
299
+ // Expose the live fade value so external components can react to it (see `currentAlpha`).
300
+ // Renderers are driven by the same target/duration, so the last applied value is representative.
301
+ this._currentAlpha = newAlpha;
302
+
261
303
  // const currentTransparent = (block.getOverride("transparent")?.value ?? materials[0].transparent ?? false) as boolean;
262
304
 
263
305
  block.setOverride("alphaHash", this.useAlphaHash);
264
306
  block.setOverride("opacity", newAlpha);
265
307
  block.setOverride("transparent", newAlpha >= 0.99999 ? false : !this.useAlphaHash);
308
+
309
+ // Stop casting a shadow once the object is essentially invisible (and restore it when it
310
+ // comes back). A faded object otherwise keeps a fully solid shadow — see shadowHideThreshold.
311
+ this.updateShadowCasting(renderer, newAlpha);
266
312
  });
267
313
  }
268
314
 
315
+ /**
316
+ * Suppress or restore a renderer's shadow casting based on its current alpha.
317
+ * Driving {@link Renderer.shadowCastingMode} (rather than the raw `mesh.castShadow`) is required
318
+ * because the Renderer re-applies that setting every frame; we remember the original mode so it
319
+ * is restored exactly when the object fades back in.
320
+ */
321
+ private updateShadowCasting(renderer: Renderer, alpha: number) {
322
+ const shouldHideShadow = alpha < SeeThrough.shadowHideThreshold;
323
+ if (shouldHideShadow) {
324
+ if (renderer.shadowCastingMode !== ShadowCastingMode.Off) {
325
+ this._suppressedShadowCasting.set(renderer, renderer.shadowCastingMode);
326
+ renderer.shadowCastingMode = ShadowCastingMode.Off;
327
+ }
328
+ }
329
+ else if (this._suppressedShadowCasting.has(renderer)) {
330
+ renderer.shadowCastingMode = this._suppressedShadowCasting.get(renderer)!;
331
+ this._suppressedShadowCasting.delete(renderer);
332
+ }
333
+ }
334
+
335
+ /** Restore every renderer we suppressed shadow casting on, then clear the bookkeeping. */
336
+ private restoreSuppressedShadowCasting() {
337
+ for (const [renderer, mode] of this._suppressedShadowCasting) {
338
+ if (!renderer.destroyed) renderer.shadowCastingMode = mode;
339
+ }
340
+ this._suppressedShadowCasting.clear();
341
+ }
342
+
269
343
  }
270
344
 
@@ -44,6 +44,7 @@ export { Behaviour, Component, GameObject } from "./Component.js";
44
44
  export { AudioRolloffMode } from "./AudioSource.js";
45
45
  export { ClearFlags } from "./Camera.js"
46
46
  export { DragMode } from "./DragControls.js";
47
+ export { ShadowCastingMode, ReflectionProbeUsage } from "./Renderer.js";
47
48
  export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
48
49
  export * from "./export/index.js"
49
50
  export * from "./particlesystem/api.js"
@@ -25,7 +25,17 @@ export { LogStats } from "../debug/LogStats.js";
25
25
  export { DeleteBox } from "../DeleteBox.js";
26
26
  export { Deletable } from "../DeleteBox.js";
27
27
  export { DeviceFlag } from "../DeviceFlag.js";
28
+ export { DragProfile } from "../DragControls.js";
28
29
  export { DragControls } from "../DragControls.js";
30
+ export { GridSnapConstraint } from "../DragControlsConstraints.js";
31
+ export { GrabPointPlaneConstraint } from "../DragControlsConstraints.js";
32
+ export { KeepRotationConstraint } from "../DragControlsConstraints.js";
33
+ export { KeepScaleConstraint } from "../DragControlsConstraints.js";
34
+ export { ScaleLimitConstraint } from "../DragControlsConstraints.js";
35
+ export { FixedRotationAxesConstraint } from "../DragControlsConstraints.js";
36
+ export { AxisRotationConstraint } from "../DragControlsConstraints.js";
37
+ export { PlaneHeightLockConstraint } from "../DragControlsConstraints.js";
38
+ export { SnapToSurfaceConstraint } from "../DragControlsConstraints.js";
29
39
  export { DropListener } from "../DropListener.js";
30
40
  export { Duplicatable } from "../Duplicatable.js";
31
41
  export { CallInfo } from "../EventList.js";
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
3
3
 
4
4
  import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
5
5
  import { findObjectOfType } from "../../../engine/engine_components.js";
6
- import { _$QyV } from "../../../engine/engine_license.js";
6
+ import { _XtEQ } from "../../../engine/engine_license.js";
7
7
  import { serializable } from "../../../engine/engine_serialization.js";
8
8
  import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
9
9
  import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
277
277
  let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
278
278
  name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
279
279
 
280
- if (!_$QyV()) {
280
+ if (!_XtEQ()) {
281
281
  if (name !== "") name += "-";
282
282
  name += "MadeWithNeedle";
283
283
  }
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
682
682
  if (debug)
683
683
  showBalloonMessage("Quicklook url: " + callToActionURL);
684
684
  if (callToActionURL) {
685
- if (!_$QyV()) {
685
+ if (!_XtEQ()) {
686
686
  console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
687
687
  }
688
688
  else {
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
697
697
  private buildQuicklookOverlay(): CustomBranding {
698
698
  const obj: CustomBranding = {};
699
699
  if (this.customBranding) Object.assign(obj, this.customBranding);
700
- if (!_$QyV()) {
700
+ if (!_XtEQ()) {
701
701
  console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
702
702
  obj.callToAction = "Close";
703
703
  obj.checkoutTitle = "🌵 Made with Needle";
@@ -222,7 +222,7 @@ export class PlayableDirector extends Behaviour {
222
222
 
223
223
  /** @internal */
224
224
  onEnable() {
225
- if (debug) console.log("[Timeline] OnEnable", this.name, this.playOnAwake);
225
+ if (debug) console.log("[Timeline] OnEnable", { name: this.name, playOnAwake: this.playOnAwake });
226
226
 
227
227
  for (const tracks of this._allTracks) {
228
228
  for (const track of tracks) {