@needle-tools/engine 5.1.0-alpha.2 → 5.1.0-alpha.3

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 (133) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-qZfVf_v-.umd.cjs → needle-engine.bundle-C-ixARur.umd.cjs} +128 -127
  4. package/dist/{needle-engine.bundle-B-5Q2CpC.min.js → needle-engine.bundle-CHmXdnE1.min.js} +138 -137
  5. package/dist/{needle-engine.bundle-dit3f1l5.js → needle-engine.bundle-DF01sSGQ.js} +7815 -7602
  6. package/dist/needle-engine.d.ts +125 -27
  7. package/dist/needle-engine.js +524 -521
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/api.d.ts +2 -0
  11. package/lib/engine/api.js +2 -0
  12. package/lib/engine/api.js.map +1 -1
  13. package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
  14. package/lib/engine/debug/debug_spatial_console.js +10 -7
  15. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  16. package/lib/engine/engine_addressables.d.ts +2 -0
  17. package/lib/engine/engine_addressables.js +6 -3
  18. package/lib/engine/engine_addressables.js.map +1 -1
  19. package/lib/engine/engine_audio.d.ts +68 -0
  20. package/lib/engine/engine_audio.js +172 -0
  21. package/lib/engine/engine_audio.js.map +1 -1
  22. package/lib/engine/engine_components.js +1 -1
  23. package/lib/engine/engine_components.js.map +1 -1
  24. package/lib/engine/engine_context.d.ts +1 -1
  25. package/lib/engine/engine_context.js +1 -1
  26. package/lib/engine/engine_context.js.map +1 -1
  27. package/lib/engine/engine_gameobject.js +2 -2
  28. package/lib/engine/engine_gameobject.js.map +1 -1
  29. package/lib/engine/engine_init.js +13 -3
  30. package/lib/engine/engine_init.js.map +1 -1
  31. package/lib/engine/engine_input.d.ts +1 -1
  32. package/lib/engine/engine_input.js +1 -1
  33. package/lib/engine/engine_input.js.map +1 -1
  34. package/lib/engine/engine_license.js +1 -1
  35. package/lib/engine/engine_license.js.map +1 -1
  36. package/lib/engine/engine_mainloop_utils.js +5 -2
  37. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  38. package/lib/engine/engine_networking_blob.js +1 -1
  39. package/lib/engine/engine_networking_blob.js.map +1 -1
  40. package/lib/engine/engine_scenedata.d.ts +2 -0
  41. package/lib/engine/engine_scenedata.js +4 -2
  42. package/lib/engine/engine_scenedata.js.map +1 -1
  43. package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
  44. package/lib/engine/engine_serialization_builtin_serializer.js +55 -41
  45. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  46. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  47. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  48. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -1
  49. package/lib/engine/webcomponents/needle menu/needle-menu.js +1 -1
  50. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  51. package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
  52. package/lib/engine/webcomponents/needle-engine.js +1 -1
  53. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  54. package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
  55. package/lib/engine/xr/NeedleXRSession.js +50 -14
  56. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  57. package/lib/engine/xr/events.d.ts +1 -1
  58. package/lib/engine/xr/events.js.map +1 -1
  59. package/lib/engine-components/Animation.js +17 -16
  60. package/lib/engine-components/Animation.js.map +1 -1
  61. package/lib/engine-components/AnimatorController.d.ts +2 -0
  62. package/lib/engine-components/AnimatorController.js +4 -1
  63. package/lib/engine-components/AnimatorController.js.map +1 -1
  64. package/lib/engine-components/AudioSource.d.ts +19 -3
  65. package/lib/engine-components/AudioSource.js +121 -68
  66. package/lib/engine-components/AudioSource.js.map +1 -1
  67. package/lib/engine-components/DragControls.d.ts +7 -0
  68. package/lib/engine-components/DragControls.js +19 -0
  69. package/lib/engine-components/DragControls.js.map +1 -1
  70. package/lib/engine-components/Networking.d.ts +1 -1
  71. package/lib/engine-components/Networking.js +1 -1
  72. package/lib/engine-components/OrbitControls.js +16 -11
  73. package/lib/engine-components/OrbitControls.js.map +1 -1
  74. package/lib/engine-components/ReflectionProbe.js +2 -0
  75. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  76. package/lib/engine-components/SeeThrough.js +2 -2
  77. package/lib/engine-components/SeeThrough.js.map +1 -1
  78. package/lib/engine-components/api.d.ts +1 -1
  79. package/lib/engine-components/api.js +1 -1
  80. package/lib/engine-components/api.js.map +1 -1
  81. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  82. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
  83. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
  84. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  85. package/lib/engine-components/ui/Canvas.d.ts +1 -1
  86. package/lib/engine-components/ui/Canvas.js +2 -8
  87. package/lib/engine-components/ui/Canvas.js.map +1 -1
  88. package/lib/engine-components/ui/Text.d.ts +1 -0
  89. package/lib/engine-components/ui/Text.js +10 -7
  90. package/lib/engine-components/ui/Text.js.map +1 -1
  91. package/lib/engine-components/web/CursorFollow.js +21 -12
  92. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  93. package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
  94. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  95. package/package.json +1 -1
  96. package/plugins/vite/asap.js +17 -8
  97. package/plugins/vite/dependencies.js +29 -0
  98. package/plugins/vite/local-files-core.js +3 -3
  99. package/plugins/vite/local-files-utils.d.ts +3 -1
  100. package/plugins/vite/local-files-utils.js +29 -5
  101. package/src/engine/api.ts +3 -0
  102. package/src/engine/debug/debug_spatial_console.ts +10 -7
  103. package/src/engine/engine_addressables.ts +6 -3
  104. package/src/engine/engine_audio.ts +184 -0
  105. package/src/engine/engine_components.ts +1 -1
  106. package/src/engine/engine_context.ts +2 -2
  107. package/src/engine/engine_gameobject.ts +2 -2
  108. package/src/engine/engine_init.ts +13 -3
  109. package/src/engine/engine_input.ts +1 -1
  110. package/src/engine/engine_license.ts +1 -1
  111. package/src/engine/engine_mainloop_utils.ts +5 -2
  112. package/src/engine/engine_networking_blob.ts +1 -1
  113. package/src/engine/engine_scenedata.ts +5 -3
  114. package/src/engine/engine_serialization_builtin_serializer.ts +63 -46
  115. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  116. package/src/engine/webcomponents/needle menu/needle-menu.ts +1 -1
  117. package/src/engine/webcomponents/needle-engine.ts +10 -4
  118. package/src/engine/xr/NeedleXRSession.ts +48 -13
  119. package/src/engine/xr/events.ts +1 -1
  120. package/src/engine-components/Animation.ts +19 -16
  121. package/src/engine-components/AnimatorController.ts +4 -1
  122. package/src/engine-components/AudioSource.ts +130 -79
  123. package/src/engine-components/DragControls.ts +18 -2
  124. package/src/engine-components/Networking.ts +1 -1
  125. package/src/engine-components/OrbitControls.ts +18 -9
  126. package/src/engine-components/ReflectionProbe.ts +2 -0
  127. package/src/engine-components/SeeThrough.ts +2 -2
  128. package/src/engine-components/api.ts +1 -1
  129. package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
  130. package/src/engine-components/ui/Canvas.ts +2 -8
  131. package/src/engine-components/ui/Text.ts +12 -8
  132. package/src/engine-components/web/CursorFollow.ts +21 -13
  133. package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
@@ -24,9 +24,11 @@ export function normalizeWebPath(path: string): string;
24
24
  export function ensureTrailingSlash(path: string): string;
25
25
  /**
26
26
  * @param {string} src
27
+ * @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
28
+ * ext/ paths are made absolute so they resolve correctly under SPA routing.
27
29
  * @returns {string}
28
30
  */
29
- export function fixRelativeNewURL(src: string): string;
31
+ export function fixRelativeNewURL(src: string, base?: string): string;
30
32
  /**
31
33
  * @param {string} src
32
34
  * @returns {string}
@@ -113,25 +113,49 @@ export function ensureTrailingSlash(path) {
113
113
 
114
114
  /**
115
115
  * @param {string} src
116
+ * @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
117
+ * ext/ paths are made absolute so they resolve correctly under SPA routing.
116
118
  * @returns {string}
117
119
  */
118
- export function fixRelativeNewURL(src) {
120
+ export function fixRelativeNewURL(src, base) {
121
+ /** @param {string} path */
122
+ function makeAbsolute(path) {
123
+ // Strip any leading ./ ../ or / prefix to get the clean ext/... path
124
+ const clean = path.replace(/^(?:\.\.?\/)+/, '').replace(/^\//, '');
125
+ if (base) return base.replace(/\/+$/, '') + '/' + clean;
126
+ return clean;
127
+ }
128
+
119
129
  src = src.replace(
120
130
  /(?<==\s*)(["'])((?:(?:\.{1,2}\/)|\/)?ext\/[^"']*\/)\1/g,
121
131
  (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
122
- const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
123
- return `new URL(${quote}${runtimePath}${quote}, self.location?.href || ${quote}${quote}).href`;
132
+ const resolved = makeAbsolute(path);
133
+ return `new URL(${quote}${resolved}${quote}, self.location?.href || ${quote}${quote}).href`;
124
134
  }
125
135
  );
126
136
 
127
137
  src = src.replace(
128
138
  /new\s+URL\s*\(\s*(["'`])((?:(?:\.{1,2}\/)|\/)?ext\/[^"'`]+)\1\s*\)/g,
129
139
  (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
130
- const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
131
- return `new URL(${quote}${runtimePath}${quote}, self.location?.href)`;
140
+ const resolved = makeAbsolute(path);
141
+ return `new URL(${quote}${resolved}${quote}, self.location?.href)`;
132
142
  }
133
143
  );
134
144
 
145
+ // Make remaining ext/ string literals absolute (e.g. font paths passed as
146
+ // function arguments like addVariant("normal","normal","ext/fonts/...")).
147
+ // The regexes above already consumed paths inside new URL() calls and
148
+ // assignment-based patterns, so this only catches leftover bare strings.
149
+ if (base) {
150
+ src = src.replace(
151
+ /(["'`])((?:\.{1,2}\/)?ext\/[^"'`]+)\1/g,
152
+ (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
153
+ const resolved = makeAbsolute(path);
154
+ return `${quote}${resolved}${quote}`;
155
+ }
156
+ );
157
+ }
158
+
135
159
  return src;
136
160
  }
137
161
 
package/src/engine/api.ts CHANGED
@@ -114,6 +114,9 @@ export * from "./engine_addressables.js";
114
114
  /** Animation playback and control utilities */
115
115
  export { AnimationUtils } from "./engine_animation.js";
116
116
 
117
+ /** Standalone audio clip for playback control */
118
+ export { AudioClip } from "./engine_audio.js";
119
+
117
120
  /** Application-level state and utilities */
118
121
  export { Application } from "./engine_application.js";
119
122
 
@@ -12,13 +12,16 @@ import { onError } from "./debug_overlay.js";
12
12
 
13
13
  let _isActive = false;
14
14
 
15
- // enable the spatial console if we receive an error while in dev session and in XR
16
- onError((...args: any[]) => {
17
- if (isDevEnvironment() && ContextRegistry.Current?.isInXR) {
18
- enableSpatialConsole(true);
19
- onLog("error", ...args);
20
- }
21
- })
15
+ /** @internal */
16
+ export function initSpatialConsole() {
17
+ // enable the spatial console if we receive an error while in dev session and in XR
18
+ onError((...args: any[]) => {
19
+ if (isDevEnvironment() && ContextRegistry.Current?.isInXR) {
20
+ enableSpatialConsole(true);
21
+ onLog("error", ...args);
22
+ }
23
+ });
24
+ }
22
25
 
23
26
 
24
27
  /** Enable a spatial debug console that follows the camera */
@@ -567,7 +567,6 @@ class AddressableSerializer extends TypeSerializer {
567
567
  }
568
568
 
569
569
  }
570
- new AddressableSerializer();
571
570
 
572
571
 
573
572
 
@@ -703,7 +702,6 @@ export class ImageReferenceSerializer extends TypeSerializer {
703
702
  return undefined;
704
703
  }
705
704
  }
706
- new ImageReferenceSerializer();
707
705
 
708
706
 
709
707
 
@@ -772,4 +770,9 @@ export class FileReferenceSerializer extends TypeSerializer {
772
770
  return undefined;
773
771
  }
774
772
  }
775
- new FileReferenceSerializer();
773
+ /** @internal */
774
+ export function initAddressableSerializers() {
775
+ new AddressableSerializer();
776
+ new ImageReferenceSerializer();
777
+ new FileReferenceSerializer();
778
+ }
@@ -21,4 +21,188 @@ export function ensureAudioContextIsResumed() {
21
21
  }, 500);
22
22
  });
23
23
  });
24
+ }
25
+
26
+
27
+ /**
28
+ * Represents an audio clip that can be loaded and played independently.
29
+ * The AudioClip class encapsulates the URL of the audio resource and provides
30
+ * methods for playback control (play, pause, stop) and querying duration.
31
+ */
32
+ export class AudioClip {
33
+
34
+ /**
35
+ * Creates a new AudioClip instance with the specified URL.
36
+ * @param url The URL of the audio resource to load. This can be a path to an audio file or a MediaStream URL.
37
+ */
38
+ constructor(public readonly url: string) {
39
+ }
40
+
41
+ /** Whether the clip is currently playing.
42
+ * @returns `true` if the clip is actively playing audio.
43
+ */
44
+ get isPlaying(): boolean {
45
+ return this._audioElement !== undefined
46
+ && !this._audioElement.paused
47
+ && !this._audioElement.ended;
48
+ }
49
+
50
+ /**
51
+ * The total duration of the audio clip in seconds.
52
+ * Loads the audio metadata if not already available.
53
+ * @returns A promise that resolves with the duration in seconds.
54
+ */
55
+ getDuration(): Promise<number> {
56
+ if (this._duration !== undefined) {
57
+ return Promise.resolve(this._duration);
58
+ }
59
+ return this.ensureAudioElement().then(audio => {
60
+ this._duration = audio.duration;
61
+ return audio.duration;
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Plays the audio clip from the current position.
67
+ * @returns A promise that resolves when playback finishes, or rejects on error.
68
+ * If the clip is looping, the promise will never resolve on its own – call {@link stop} or {@link pause} to end playback.
69
+ */
70
+ // #region Play
71
+ play(): Promise<void> {
72
+ return this.ensureAudioElement().then(audio => {
73
+ return new Promise<void>((resolve, reject) => {
74
+ const onEnded = () => {
75
+ cleanup();
76
+ resolve();
77
+ };
78
+ const onError = () => {
79
+ cleanup();
80
+ reject(new Error(`Playback error for ${this.url}`));
81
+ };
82
+ const onPause = () => {
83
+ // pause/stop also resolve the promise
84
+ cleanup();
85
+ resolve();
86
+ };
87
+ const cleanup = () => {
88
+ audio.removeEventListener("ended", onEnded);
89
+ audio.removeEventListener("error", onError);
90
+ audio.removeEventListener("pause", onPause);
91
+ };
92
+ audio.addEventListener("ended", onEnded);
93
+ audio.addEventListener("error", onError);
94
+ audio.addEventListener("pause", onPause);
95
+ audio.play().catch(err => {
96
+ cleanup();
97
+ reject(err);
98
+ });
99
+ });
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Pauses playback at the current position.
105
+ * Call {@link play} to resume.
106
+ */
107
+ // #region Pause/Stop
108
+ pause(): void {
109
+ this._audioElement?.pause();
110
+ }
111
+
112
+ /**
113
+ * Stops playback and resets the position to the beginning.
114
+ */
115
+ stop(): void {
116
+ if (this._audioElement) {
117
+ this._audioElement.pause();
118
+ this._audioElement.currentTime = 0;
119
+ }
120
+ }
121
+
122
+ /** Whether the clip should loop when reaching the end. */
123
+ get loop(): boolean { return this._loop; }
124
+ set loop(value: boolean) {
125
+ this._loop = value;
126
+ if (this._audioElement) this._audioElement.loop = value;
127
+ }
128
+
129
+ /** Playback volume from 0 (silent) to 1 (full). */
130
+ get volume(): number { return this._volume; }
131
+ set volume(value: number) {
132
+ this._volume = value;
133
+ if (this._audioElement) this._audioElement.volume = value;
134
+ }
135
+
136
+ /** Current playback position in seconds. */
137
+ get currentTime(): number { return this._audioElement?.currentTime ?? 0; }
138
+ set currentTime(value: number) {
139
+ if (this._audioElement) this._audioElement.currentTime = value;
140
+ }
141
+
142
+ /** Normalized playback progress from 0 to 1.
143
+ * @returns The current playback position as a value between 0 and 1, or 0 if the duration is unknown.
144
+ */
145
+ get progress(): number {
146
+ if (!this._audioElement || !this._duration) return 0;
147
+ return this._audioElement.currentTime / this._duration;
148
+ }
149
+
150
+ /**
151
+ * Seeks to a normalized position (0–1) in the clip.
152
+ * @param position A value between 0 (start) and 1 (end).
153
+ */
154
+ // #region Seek
155
+ seek(position: number): void {
156
+ if (this._audioElement && this._duration) {
157
+ this._audioElement.currentTime = Math.max(0, Math.min(1, position)) * this._duration;
158
+ }
159
+ }
160
+
161
+ /** The underlying HTMLAudioElement, or `undefined` if not yet created.
162
+ * Use this to connect the element to the Web Audio API via `createMediaElementSource()`.
163
+ * @returns The HTMLAudioElement if the clip has been loaded or played, otherwise `undefined`.
164
+ */
165
+ get audioElement(): HTMLAudioElement | undefined { return this._audioElement; }
166
+
167
+ private _audioElement?: HTMLAudioElement;
168
+ private _duration?: number;
169
+ private _loadPromise?: Promise<HTMLAudioElement>;
170
+ private _loop: boolean = false;
171
+ private _volume: number = 1;
172
+
173
+ /** Lazily creates and loads the shared HTMLAudioElement. */
174
+ private ensureAudioElement(): Promise<HTMLAudioElement> {
175
+ if (this._audioElement && this._loadPromise) {
176
+ return this._loadPromise;
177
+ }
178
+ const audio = this._audioElement ?? new Audio(this.url);
179
+ this._audioElement = audio;
180
+ audio.loop = this._loop;
181
+ audio.volume = this._volume;
182
+
183
+ if (audio.readyState >= HTMLMediaElement.HAVE_METADATA) {
184
+ this._duration = audio.duration;
185
+ this._loadPromise = Promise.resolve(audio);
186
+ return this._loadPromise;
187
+ }
188
+
189
+ this._loadPromise = new Promise<HTMLAudioElement>((resolve, reject) => {
190
+ const onLoaded = () => {
191
+ cleanup();
192
+ this._duration = audio.duration;
193
+ resolve(audio);
194
+ };
195
+ const onError = (e: Event) => {
196
+ cleanup();
197
+ reject(new Error(`Failed to load audio clip from ${this.url}: ${e}`));
198
+ };
199
+ const cleanup = () => {
200
+ audio.removeEventListener("loadedmetadata", onLoaded);
201
+ audio.removeEventListener("error", onError);
202
+ };
203
+ audio.addEventListener("loadedmetadata", onLoaded);
204
+ audio.addEventListener("error", onError);
205
+ });
206
+ return this._loadPromise;
207
+ }
24
208
  }
@@ -8,9 +8,9 @@ import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
8
8
  import { Context, registerComponent } from "./engine_setup.js";
9
9
  import type { ComponentInit, Constructor, ConstructorConcrete, IComponent, IGameObject } from "./engine_types.js";
10
10
  import { $componentName } from "./engine_types.js";
11
+ import { TypeStore } from "./engine_typestore.js";
11
12
  import { getParam } from "./engine_utils.js";
12
13
  import { apply } from "./js-extensions/index.js";
13
- import { TypeStore } from "./engine_typestore.js";
14
14
 
15
15
  const COMPONENT_GUID_NAMESPACE = 'eff8ba80-635d-11ec-90d6-0242ac120003';
16
16
 
@@ -1,5 +1,6 @@
1
1
  import 'three/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js';
2
2
 
3
+ import type { SceneData } from 'needle-bindings';
3
4
  import type { EffectComposer } from "postprocessing";
4
5
  import {
5
6
  BufferGeometry, Camera, Color, DepthTexture, Group,
@@ -36,9 +37,8 @@ import * as looputils from './engine_mainloop_utils.js';
36
37
  import { NetworkConnection } from './engine_networking.js';
37
38
  import { Physics } from './engine_physics.js';
38
39
  import { PlayerViewManager } from './engine_playerview.js';
39
- import { RendererData as SceneLighting } from './engine_scenelighting.js';
40
40
  import { getSceneData } from './engine_scenedata.js';
41
- import type { SceneData } from 'needle-bindings';
41
+ import { RendererData as SceneLighting } from './engine_scenelighting.js';
42
42
  import { getTempColor, logHierarchy } from './engine_three_utils.js';
43
43
  import { Time } from './engine_time.js';
44
44
  import { patchTonemapping } from './engine_tonemapping.js';
@@ -212,8 +212,8 @@ function internalDestroy(instance: Object3D | Component, recursive: boolean = tr
212
212
  if (comp[$isDontDestroy]) return;
213
213
  destroyed_components.push(comp);
214
214
  const go = comp.gameObject;
215
- comp.__internalDisable();
216
- comp.__internalDestroy();
215
+ comp.__internalDisable?.();
216
+ comp.__internalDestroy?.();
217
217
  comp.gameObject = go;
218
218
  return;
219
219
  }
@@ -1,20 +1,25 @@
1
1
  import { initAnimationAutoplay } from "../engine-components/AnimationUtilsAutoplay.js";
2
+ import { initAnimatorControllerSerializer } from "../engine-components/AnimatorController.js";
2
3
  import { initCameraUtils } from "../engine-components/CameraUtils.js";
4
+ import { initVolumeParameterSerializer } from "../engine-components/postprocessing/VolumeParameter.js";
3
5
  import { initSceneSwitcherAttributes } from "../engine-components/SceneSwitcher.js";
4
6
  import { initSkyboxAttributes } from "../engine-components/Skybox.js";
5
7
  import { initBuiltinTypes } from "./codegen/register_types.js";
8
+ import { initSpatialConsole } from "./debug/debug_spatial_console.js";
9
+ import { initAddressableSerializers } from "./engine_addressables.js";
6
10
  import { ensureAudioContextIsResumed } from "./engine_audio.js";
11
+ import { initLicense } from "./engine_license.js";
7
12
  import { initNeedleLoader } from "./engine_loaders.js";
13
+ import { initPhysics } from "./engine_physics_rapier.js";
14
+ import { initBuiltinSerializers } from "./engine_serialization_builtin_serializer.js";
8
15
  import { initShims } from "./engine_shims.js";
16
+ import { SSR } from "./engine_ssr.js";
9
17
  import { patchTonemapping } from "./engine_tonemapping.js";
10
18
  import { initCameraExtensions } from "./js-extensions/Camera.js";
11
19
  import { patchLayers } from "./js-extensions/Layers.js";
12
20
  import { initObject3DExtensions } from "./js-extensions/Object3D.js";
13
21
  import { initVectorExtensions } from "./js-extensions/Vector.js";
14
- import { SSR } from "./engine_ssr.js";
15
- import { initLicense } from "./engine_license.js";
16
22
  import { initWebComponents } from "./webcomponents/init.js";
17
- import { initPhysics } from "./engine_physics_rapier.js";
18
23
  import { initXR } from "./xr/init.js";
19
24
 
20
25
  let initialized = false;
@@ -35,6 +40,10 @@ export function initEngine() {
35
40
 
36
41
  initWebComponents();
37
42
  initShims();
43
+ initBuiltinSerializers();
44
+ initAddressableSerializers();
45
+ initAnimatorControllerSerializer();
46
+ initVolumeParameterSerializer();
38
47
  patchTonemapping();
39
48
  patchLayers();
40
49
  initCameraExtensions();
@@ -49,5 +58,6 @@ export function initEngine() {
49
58
  initSceneSwitcherAttributes();
50
59
  initPhysics();
51
60
  initXR();
61
+ initSpatialConsole();
52
62
  initLicense();
53
63
  }
@@ -1,8 +1,8 @@
1
1
  import { Intersection, Matrix4, Object3D, Ray, Vector2, Vector3 } from 'three';
2
2
 
3
3
  import { showBalloonMessage, showBalloonWarning } from './debug/debug.js';
4
- import { PointerEventBase, KeyboardEventBase } from './engine_ssr.js';
5
4
  import { Context } from './engine_setup.js';
5
+ import { KeyboardEventBase,PointerEventBase } from './engine_ssr.js';
6
6
  import { getTempVector, getWorldQuaternion } from './engine_three_utils.js';
7
7
  import type { ButtonName, CursorTypeName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
8
8
  import { DeviceUtilities, type EnumToPrimitiveUnion, getParam } from './engine_utils.js';
@@ -3,9 +3,9 @@ import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
3
3
  import { onInitialized } from "./engine_lifecycle_api.js";
4
4
  import { isLocalNetwork } from "./engine_networking_utils.js";
5
5
  import { Context } from "./engine_setup.js";
6
+ import { SSR } from "./engine_ssr.js";
6
7
  import type { IContext } from "./engine_types.js";
7
8
  import { getParam } from "./engine_utils.js";
8
- import { SSR } from "./engine_ssr.js";
9
9
 
10
10
  const debug = getParam("debuglicense");
11
11
 
@@ -344,8 +344,11 @@ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarc
344
344
  if (allowEventCall) {
345
345
  const components = go.userData?.components;
346
346
  if (components) {
347
- for (let ci = components.length - 1, cl = -1; ci > cl; ci--) {
348
- const comp = components[ci];
347
+ // We need to iterate on components in the original order right now to work-around UI related initialization bugs where RectTransform must be initialized before e.g. Graphic components. https://linear.app/needle/issue/NE-6986
348
+ // In the future we can reverse iterate this once the UI system has been replaced (that being said it's probably expected that component enable in the order in which they're added to an object)
349
+ const componentsCopy = [...components];
350
+ for (let ci = 0; ci < componentsCopy.length; ci++) {
351
+ const comp = componentsCopy[ci];
349
352
  if (activeInHierarchy) {
350
353
  if (comp?.enabled) {
351
354
  try { comp.__internalAwake(); }
@@ -3,7 +3,7 @@ import { FileLoader } from "three";
3
3
  import { showBalloonWarning } from "./debug/index.js";
4
4
  import { hasCommercialLicense } from "./engine_license.js";
5
5
  import { delay } from "./engine_utils.js";
6
- import { md5Hex, md5AsBytes, sha256Base64 } from "./engine_utils_hash.js";
6
+ import { md5AsBytes, md5Hex, sha256Base64 } from "./engine_utils_hash.js";
7
7
 
8
8
 
9
9
  export namespace BlobStorage {
@@ -1,10 +1,10 @@
1
1
  import type { SceneData } from "needle-bindings";
2
2
  export type { SceneData };
3
- import type { IContext } from "./engine_types.js";
4
- import { getComponent } from "./engine_components.js";
5
- import { TypeStore } from "./engine_typestore.js";
6
3
  import { isDevEnvironment } from "./debug/index.js";
4
+ import { getComponent } from "./engine_components.js";
7
5
  import { ContextRegistry } from "./engine_context_registry.js";
6
+ import type { IContext } from "./engine_types.js";
7
+ import { TypeStore } from "./engine_typestore.js";
8
8
 
9
9
  /**
10
10
  * Quick access to the current Needle Engine context from anywhere — no need to pass `ctx` around.
@@ -13,6 +13,8 @@ import { ContextRegistry } from "./engine_context_registry.js";
13
13
  * Safe to import at module level, including in SSR environments.
14
14
  * For pages with multiple `<needle-engine>` elements, use `ctx` directly instead.
15
15
  *
16
+ * @experimental This API may change in future releases.
17
+ *
16
18
  * @example
17
19
  * import { needle } from "@needle-tools/engine";
18
20
  * button.onclick = () => {