@needle-tools/engine 5.1.0-alpha.1 → 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 (250) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +52 -0
  3. package/components.needle.json +1 -1
  4. package/dist/{gltf-progressive-DJBMx-zB.umd.cjs → gltf-progressive-BmblPzFj.umd.cjs} +4 -4
  5. package/dist/{gltf-progressive-BryRjllq.min.js → gltf-progressive-CN_mbb66.min.js} +2 -2
  6. package/dist/{gltf-progressive-Cl167Vjx.js → gltf-progressive-DUlhxdv4.js} +5 -2
  7. package/dist/needle-engine.bundle-C-ixARur.umd.cjs +1733 -0
  8. package/dist/needle-engine.bundle-CHmXdnE1.min.js +1733 -0
  9. package/dist/{needle-engine.bundle-BGyKqxBH.js → needle-engine.bundle-DF01sSGQ.js} +10841 -10434
  10. package/dist/needle-engine.d.ts +244 -54
  11. package/dist/needle-engine.js +541 -536
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/dist/{postprocessing-B_9sKVU7.min.js → postprocessing-B571qGWR.min.js} +34 -34
  15. package/dist/{postprocessing-WDc9WwI3.js → postprocessing-CfrLAbLX.js} +0 -1
  16. package/dist/{postprocessing-B2wb6pzI.umd.cjs → postprocessing-CiGkAeM9.umd.cjs} +17 -17
  17. package/dist/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
  18. package/dist/vendor-CJmyOrCq.min.js +1116 -0
  19. package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
  20. package/lib/engine/api.d.ts +14 -0
  21. package/lib/engine/api.js +4 -0
  22. package/lib/engine/api.js.map +1 -1
  23. package/lib/engine/debug/debug_environment.js +1 -1
  24. package/lib/engine/debug/debug_environment.js.map +1 -1
  25. package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
  26. package/lib/engine/debug/debug_spatial_console.js +10 -7
  27. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  28. package/lib/engine/engine_addressables.d.ts +2 -0
  29. package/lib/engine/engine_addressables.js +6 -3
  30. package/lib/engine/engine_addressables.js.map +1 -1
  31. package/lib/engine/engine_application.js +8 -6
  32. package/lib/engine/engine_application.js.map +1 -1
  33. package/lib/engine/engine_audio.d.ts +68 -0
  34. package/lib/engine/engine_audio.js +172 -0
  35. package/lib/engine/engine_audio.js.map +1 -1
  36. package/lib/engine/engine_components.js +1 -1
  37. package/lib/engine/engine_components.js.map +1 -1
  38. package/lib/engine/engine_constants.js +6 -0
  39. package/lib/engine/engine_constants.js.map +1 -1
  40. package/lib/engine/engine_context.d.ts +33 -7
  41. package/lib/engine/engine_context.js +40 -2
  42. package/lib/engine/engine_context.js.map +1 -1
  43. package/lib/engine/engine_context_registry.js +1 -1
  44. package/lib/engine/engine_context_registry.js.map +1 -1
  45. package/lib/engine/engine_gameobject.js +2 -2
  46. package/lib/engine/engine_gameobject.js.map +1 -1
  47. package/lib/engine/engine_init.js +16 -1
  48. package/lib/engine/engine_init.js.map +1 -1
  49. package/lib/engine/engine_input.d.ts +3 -2
  50. package/lib/engine/engine_input.js +3 -2
  51. package/lib/engine/engine_input.js.map +1 -1
  52. package/lib/engine/engine_license.d.ts +2 -0
  53. package/lib/engine/engine_license.js +25 -15
  54. package/lib/engine/engine_license.js.map +1 -1
  55. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  56. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  57. package/lib/engine/engine_mainloop_utils.js +5 -2
  58. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  59. package/lib/engine/engine_networking_blob.d.ts +1 -1
  60. package/lib/engine/engine_networking_blob.js +5 -11
  61. package/lib/engine/engine_networking_blob.js.map +1 -1
  62. package/lib/engine/engine_physics_rapier.js +0 -1
  63. package/lib/engine/engine_physics_rapier.js.map +1 -1
  64. package/lib/engine/engine_pmrem.js +2 -2
  65. package/lib/engine/engine_pmrem.js.map +1 -1
  66. package/lib/engine/engine_scenedata.d.ts +32 -0
  67. package/lib/engine/engine_scenedata.js +138 -0
  68. package/lib/engine/engine_scenedata.js.map +1 -0
  69. package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
  70. package/lib/engine/engine_serialization_builtin_serializer.js +55 -41
  71. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  72. package/lib/engine/engine_ssr.d.ts +18 -0
  73. package/lib/engine/engine_ssr.js +40 -0
  74. package/lib/engine/engine_ssr.js.map +1 -0
  75. package/lib/engine/engine_three_utils.d.ts +14 -7
  76. package/lib/engine/engine_three_utils.js +14 -7
  77. package/lib/engine/engine_three_utils.js.map +1 -1
  78. package/lib/engine/engine_types.d.ts +2 -0
  79. package/lib/engine/engine_types.js.map +1 -1
  80. package/lib/engine/engine_utils.js +2 -0
  81. package/lib/engine/engine_utils.js.map +1 -1
  82. package/lib/engine/engine_utils_hash.d.ts +9 -0
  83. package/lib/engine/engine_utils_hash.js +112 -0
  84. package/lib/engine/engine_utils_hash.js.map +1 -0
  85. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  86. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  87. package/lib/engine/webcomponents/jsx.d.ts +51 -0
  88. package/lib/engine/webcomponents/logo-element.d.ts +2 -1
  89. package/lib/engine/webcomponents/logo-element.js +2 -1
  90. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  91. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -4
  92. package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
  93. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  94. package/lib/engine/webcomponents/needle-button.d.ts +2 -1
  95. package/lib/engine/webcomponents/needle-button.js +2 -1
  96. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  97. package/lib/engine/webcomponents/needle-engine.d.ts +11 -4
  98. package/lib/engine/webcomponents/needle-engine.js +2 -1
  99. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  100. package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
  101. package/lib/engine/xr/NeedleXRSession.js +51 -15
  102. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  103. package/lib/engine/xr/events.d.ts +1 -1
  104. package/lib/engine/xr/events.js.map +1 -1
  105. package/lib/engine-components/Animation.js +17 -16
  106. package/lib/engine-components/Animation.js.map +1 -1
  107. package/lib/engine-components/AnimatorController.d.ts +2 -0
  108. package/lib/engine-components/AnimatorController.js +4 -1
  109. package/lib/engine-components/AnimatorController.js.map +1 -1
  110. package/lib/engine-components/AudioSource.d.ts +19 -3
  111. package/lib/engine-components/AudioSource.js +121 -68
  112. package/lib/engine-components/AudioSource.js.map +1 -1
  113. package/lib/engine-components/DragControls.d.ts +7 -0
  114. package/lib/engine-components/DragControls.js +19 -0
  115. package/lib/engine-components/DragControls.js.map +1 -1
  116. package/lib/engine-components/Light.d.ts +6 -8
  117. package/lib/engine-components/Light.js +40 -27
  118. package/lib/engine-components/Light.js.map +1 -1
  119. package/lib/engine-components/Networking.d.ts +1 -1
  120. package/lib/engine-components/Networking.js +1 -1
  121. package/lib/engine-components/OrbitControls.js +16 -11
  122. package/lib/engine-components/OrbitControls.js.map +1 -1
  123. package/lib/engine-components/ReflectionProbe.js +2 -0
  124. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  125. package/lib/engine-components/RigidBody.js +3 -3
  126. package/lib/engine-components/RigidBody.js.map +1 -1
  127. package/lib/engine-components/SceneSwitcher.js +2 -0
  128. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  129. package/lib/engine-components/SeeThrough.js +2 -2
  130. package/lib/engine-components/SeeThrough.js.map +1 -1
  131. package/lib/engine-components/api.d.ts +1 -1
  132. package/lib/engine-components/api.js +1 -1
  133. package/lib/engine-components/api.js.map +1 -1
  134. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  135. package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
  136. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  137. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  138. package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
  139. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  140. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
  141. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
  142. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  143. package/lib/engine-components/ui/Canvas.d.ts +1 -1
  144. package/lib/engine-components/ui/Canvas.js +2 -8
  145. package/lib/engine-components/ui/Canvas.js.map +1 -1
  146. package/lib/engine-components/ui/Text.d.ts +1 -0
  147. package/lib/engine-components/ui/Text.js +10 -7
  148. package/lib/engine-components/ui/Text.js.map +1 -1
  149. package/lib/engine-components/web/CursorFollow.js +21 -12
  150. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  151. package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
  152. package/lib/engine-components/web/ScrollFollow.js +3 -2
  153. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  154. package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
  155. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  156. package/lib/needle-engine.d.ts +2 -0
  157. package/lib/needle-engine.js +2 -0
  158. package/lib/needle-engine.js.map +1 -1
  159. package/package.json +6 -4
  160. package/plugins/dts-generator/dts.codegen.js +334 -0
  161. package/plugins/dts-generator/dts.scan.js +99 -0
  162. package/plugins/dts-generator/dts.writer.js +59 -0
  163. package/plugins/dts-generator/glb.discovery.js +279 -0
  164. package/plugins/dts-generator/glb.extractor.js +215 -0
  165. package/plugins/dts-generator/glb.reader.js +167 -0
  166. package/plugins/dts-generator/index.js +36 -0
  167. package/plugins/dts-generator/manifest.types.js +174 -0
  168. package/plugins/types/index.d.ts +2 -1
  169. package/plugins/types/needle-bindings.d.ts +30 -0
  170. package/plugins/types/userconfig.d.ts +21 -2
  171. package/plugins/vite/asap.js +18 -9
  172. package/plugins/vite/dependencies.js +29 -0
  173. package/plugins/vite/dependency-watcher.d.ts +2 -2
  174. package/plugins/vite/dependency-watcher.js +3 -4
  175. package/plugins/vite/drop.d.ts +2 -2
  176. package/plugins/vite/drop.js +3 -4
  177. package/plugins/vite/dts-generator.d.ts +7 -0
  178. package/plugins/vite/dts-generator.js +191 -0
  179. package/plugins/vite/index.d.ts +10 -3
  180. package/plugins/vite/index.js +27 -10
  181. package/plugins/vite/local-files-core.js +3 -3
  182. package/plugins/vite/local-files-utils.d.ts +3 -1
  183. package/plugins/vite/local-files-utils.js +29 -5
  184. package/plugins/vite/logging.js +2 -2
  185. package/plugins/vite/meta.js +4 -2
  186. package/plugins/vite/poster.d.ts +2 -2
  187. package/plugins/vite/poster.js +3 -5
  188. package/plugins/vite/reload.d.ts +2 -2
  189. package/plugins/vite/reload.js +23 -22
  190. package/src/engine/api.ts +18 -1
  191. package/src/engine/debug/debug_environment.ts +1 -1
  192. package/src/engine/debug/debug_spatial_console.ts +10 -7
  193. package/src/engine/engine_addressables.ts +6 -3
  194. package/src/engine/engine_application.ts +8 -6
  195. package/src/engine/engine_audio.ts +184 -0
  196. package/src/engine/engine_components.ts +1 -1
  197. package/src/engine/engine_constants.ts +11 -6
  198. package/src/engine/engine_context.ts +50 -7
  199. package/src/engine/engine_context_registry.ts +1 -1
  200. package/src/engine/engine_gameobject.ts +2 -2
  201. package/src/engine/engine_init.ts +15 -1
  202. package/src/engine/engine_input.ts +3 -2
  203. package/src/engine/engine_license.ts +23 -19
  204. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  205. package/src/engine/engine_mainloop_utils.ts +5 -2
  206. package/src/engine/engine_networking_blob.ts +5 -11
  207. package/src/engine/engine_physics_rapier.ts +0 -3
  208. package/src/engine/engine_pmrem.ts +3 -3
  209. package/src/engine/engine_scenedata.ts +136 -0
  210. package/src/engine/engine_serialization_builtin_serializer.ts +63 -46
  211. package/src/engine/engine_ssr.ts +48 -0
  212. package/src/engine/engine_three_utils.ts +15 -7
  213. package/src/engine/engine_types.ts +2 -0
  214. package/src/engine/engine_utils.ts +1 -0
  215. package/src/engine/engine_utils_hash.ts +65 -0
  216. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  217. package/src/engine/webcomponents/jsx.d.ts +51 -0
  218. package/src/engine/webcomponents/logo-element.ts +3 -1
  219. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -2
  220. package/src/engine/webcomponents/needle-button.ts +3 -1
  221. package/src/engine/webcomponents/needle-engine.ts +12 -4
  222. package/src/engine/xr/NeedleXRSession.ts +49 -14
  223. package/src/engine/xr/events.ts +1 -1
  224. package/src/engine-components/Animation.ts +19 -16
  225. package/src/engine-components/AnimatorController.ts +4 -1
  226. package/src/engine-components/AudioSource.ts +130 -79
  227. package/src/engine-components/DragControls.ts +18 -2
  228. package/src/engine-components/Light.ts +40 -26
  229. package/src/engine-components/Networking.ts +1 -1
  230. package/src/engine-components/OrbitControls.ts +18 -9
  231. package/src/engine-components/ReflectionProbe.ts +2 -0
  232. package/src/engine-components/RigidBody.ts +3 -3
  233. package/src/engine-components/SceneSwitcher.ts +1 -0
  234. package/src/engine-components/SeeThrough.ts +2 -2
  235. package/src/engine-components/api.ts +1 -1
  236. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
  237. package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
  238. package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
  239. package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
  240. package/src/engine-components/ui/Canvas.ts +2 -8
  241. package/src/engine-components/ui/Text.ts +12 -8
  242. package/src/engine-components/web/CursorFollow.ts +21 -13
  243. package/src/engine-components/web/ScrollFollow.ts +2 -2
  244. package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
  245. package/src/needle-engine.ts +3 -0
  246. package/src/vite-env.d.ts +16 -0
  247. package/dist/needle-engine.bundle-CiYtOO2O.min.js +0 -1732
  248. package/dist/needle-engine.bundle-DzVx9Z8D.umd.cjs +0 -1732
  249. package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
  250. package/dist/vendor-HRlxIBga.min.js +0 -1116
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
 
@@ -336,6 +339,9 @@ export { loadPMREM } from "./engine_pmrem.js";
336
339
  /** Scene lighting data and environment configuration */
337
340
  export * from "./engine_scenelighting.js";
338
341
 
342
+ /** Typed scene data proxy — maps SceneData interface to live component instances */
343
+ export { getSceneData, needle } from "./engine_scenedata.js";
344
+
339
345
 
340
346
  // ============================================================================
341
347
  // SERIALIZATION
@@ -436,4 +442,15 @@ export * from "./webcomponents/needle-engine.loading.js";
436
442
  * XR API: NeedleXRSession, NeedleXRController, XRRig.
437
443
  * @see {@link https://engine.needle.tools/docs/xr.html | XR Documentation}
438
444
  */
439
- export * from "./xr/api.js"
445
+ export * from "./xr/api.js"
446
+
447
+ /**
448
+ * HTML ↔ 3D component binding types, similar to Svelte's `PageData`.
449
+ * `SceneData` is a nested interface augmented per-project by the `needle:dts-generator`
450
+ * Vite plugin — it maps scene node names to their components and typed fields.
451
+ *
452
+ * @example
453
+ * import type { SceneData } from "@needle-tools/engine";
454
+ * type OrbitSettings = SceneData["Camera"]["OrbitControls"];
455
+ */
456
+ export type { SceneData } from "needle-bindings";
@@ -15,7 +15,7 @@ export function isDevEnvironment(): boolean {
15
15
  let res = isLocalNetwork();
16
16
  if (!res) {
17
17
  // is stackblitz?
18
- res = window.location.hostname.endsWith(".local-credentialless.webcontainer.io");
18
+ res = typeof window !== "undefined" && window.location.hostname.endsWith(".local-credentialless.webcontainer.io");
19
19
  }
20
20
  _cachedDevEnvironment = res;
21
21
  return res;
@@ -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
+ }
@@ -31,12 +31,14 @@ export function internalOnUserInputRegistered() {
31
31
  userInteractionCallbacks.length = 0;
32
32
  copy.forEach(cb => cb());
33
33
  }
34
- document.addEventListener('mousedown', internalOnUserInputRegistered);
35
- document.addEventListener('pointerup', internalOnUserInputRegistered);
36
- document.addEventListener('click', internalOnUserInputRegistered);
37
- document.addEventListener('dragstart', internalOnUserInputRegistered);
38
- document.addEventListener('touchend', internalOnUserInputRegistered);
39
- document.addEventListener('keydown', internalOnUserInputRegistered);
34
+ if (typeof document !== "undefined") {
35
+ document.addEventListener('mousedown', internalOnUserInputRegistered);
36
+ document.addEventListener('pointerup', internalOnUserInputRegistered);
37
+ document.addEventListener('click', internalOnUserInputRegistered);
38
+ document.addEventListener('dragstart', internalOnUserInputRegistered);
39
+ document.addEventListener('touchend', internalOnUserInputRegistered);
40
+ document.addEventListener('keydown', internalOnUserInputRegistered);
41
+ }
40
42
 
41
43
 
42
44
  // User Activation should be available across browsers since November 2023 https://developer.mozilla.org/en-US/docs/Web/API/UserActivation
@@ -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,6 +1,8 @@
1
1
  import { getParam } from "../engine/engine_utils.js";
2
2
  const debug = getParam("debugdefines");
3
3
 
4
+ // #region global defines
5
+
4
6
  // We jump through hoops like this to support 3 cases:
5
7
  // 1) Vanilla js or angular js where global defines are not guaranteed to be made
6
8
  // 2) Vite where global defines are made, vite defines are also automatically set to globalThis
@@ -11,11 +13,6 @@ tryEval(`if(!globalThis["NEEDLE_ENGINE_GENERATOR"]) globalThis["NEEDLE_ENGINE_GE
11
13
  tryEval(`if(!globalThis["NEEDLE_PROJECT_BUILD_TIME"]) globalThis["NEEDLE_PROJECT_BUILD_TIME"] = "unknown";`);
12
14
  tryEval(`if(!globalThis["NEEDLE_PUBLIC_KEY"]) globalThis["NEEDLE_PUBLIC_KEY"] = "unknown";`);
13
15
 
14
- declare const NEEDLE_ENGINE_VERSION: string
15
- declare const NEEDLE_ENGINE_GENERATOR: string;
16
- declare const NEEDLE_PROJECT_BUILD_TIME: string;
17
- declare const NEEDLE_PUBLIC_KEY: string;
18
-
19
16
  // Make sure to wrap the new global this define in underscores to prevent the bundler from replacing it with the actual value
20
17
  tryEval(`globalThis["__NEEDLE_ENGINE_VERSION__"] = "` + NEEDLE_ENGINE_VERSION + `";`);
21
18
  tryEval(`globalThis["__NEEDLE_ENGINE_GENERATOR__"] = "` + NEEDLE_ENGINE_GENERATOR + `";`);
@@ -50,4 +47,12 @@ function tryEval(str: string) {
50
47
  if (debug)
51
48
  console.error(err);
52
49
  }
53
- }
50
+ }
51
+
52
+
53
+
54
+ // #region treeshake flags
55
+ // globalThis fallbacks for vanilla JS environments (no bundler define)
56
+ globalThis["NEEDLE_USE_RAPIER"] = globalThis["NEEDLE_USE_RAPIER"] !== undefined ? globalThis["NEEDLE_USE_RAPIER"] : true;
57
+ globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
58
+ // #endregion treeshake flags
@@ -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,6 +37,7 @@ 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';
40
+ import { getSceneData } from './engine_scenedata.js';
39
41
  import { RendererData as SceneLighting } from './engine_scenelighting.js';
40
42
  import { getTempColor, logHierarchy } from './engine_three_utils.js';
41
43
  import { Time } from './engine_time.js';
@@ -475,6 +477,28 @@ export class Context implements IContext {
475
477
  * The main camera of the scene - this camera is used for rendering
476
478
  * Use `setCurrentCamera` for updating the main camera.
477
479
  */
480
+ /**
481
+ * Access your scene's full hierarchy, objects, and components directly by name — with full autocomplete.
482
+ * Types are generated automatically from your GLB files when the dev server starts.
483
+ *
484
+ * You can store references to objects or components for later use.
485
+ * Note that the scene hierarchy can change at runtime (e.g. when objects are added, removed, or re-parented),
486
+ * in which case stored references may become stale.
487
+ *
488
+ * @experimental This API may change in future versions.
489
+ *
490
+ * @example
491
+ * // Toggle auto-rotate on the orbit camera
492
+ * ctx.sceneData.Camera.OrbitControls.autoRotate = true;
493
+ *
494
+ * @example
495
+ * // Change the background color
496
+ * ctx.sceneData.Camera.Camera.backgroundColor = new RGBAColor(1, 0, 0, 1);
497
+ */
498
+ get sceneData(): SceneData {
499
+ return getSceneData(this);
500
+ }
501
+
478
502
  get mainCamera(): Camera {
479
503
  if (this._mainCamera) {
480
504
  return this._mainCamera;
@@ -513,15 +537,32 @@ export class Context implements IContext {
513
537
  connection: NetworkConnection;
514
538
  /** @deprecated AssetDatabase is deprecated */
515
539
  assets: AssetDatabase;
516
- /** The main light in the scene */
517
- mainLight: ILight | null = null;
540
+
541
+ /** All registered lights in the scene, maintained by the Light component.
542
+ * @see mainLight for accessing the main directional light in the scene
543
+ */
544
+ readonly lights = new Array<ILight>();
545
+
546
+ /** The brightest registered directional light, or null if none are registered
547
+ * @see lights
548
+ */
549
+ get mainLight(): ILight | null {
550
+ let best: ILight | null = null;
551
+ for (const light of this.lights) {
552
+ if (light.type !== "directional") continue;
553
+ if (!best || light.intensity > best.intensity) best = light;
554
+ }
555
+ return best;
556
+ }
557
+
518
558
  /** @deprecated Use sceneLighting */
519
- get rendererData() { return this.sceneLighting }
559
+ private get rendererData() { return this.sceneLighting }
560
+
520
561
  /** Access the scene lighting manager to control lighting settings in the context */
521
- sceneLighting: SceneLighting;
522
- addressables: Addressables;
523
- lightmaps: ILightDataRegistry;
524
- players: PlayerViewManager;
562
+ readonly sceneLighting: SceneLighting;
563
+ readonly addressables: Addressables;
564
+ readonly lightmaps: ILightDataRegistry;
565
+ readonly players: PlayerViewManager;
525
566
 
526
567
  /** Access the LODs manager to control LOD behavior in the context */
527
568
  readonly lodsManager: LODsManager;
@@ -829,6 +870,8 @@ export class Context implements IContext {
829
870
  this._onBeforeRenderListeners.clear();
830
871
  this._onAfterRenderListeners.clear();
831
872
 
873
+ this.lights.length = 0;
874
+
832
875
  if (!this.isManagedExternally) {
833
876
  if (this.renderer) {
834
877
  this.renderer.renderLists.dispose();
@@ -1,6 +1,6 @@
1
1
  import { type IComponent, type IContext, type LoadedModel } from "./engine_types.js";
2
2
 
3
- const debug = typeof window !== undefined ? window.location.search.includes("debugcontext") : false;
3
+ const debug = typeof window !== "undefined" ? window.location.search.includes("debugcontext") : false;
4
4
 
5
5
  /** The various events that can be dispatched by a Needle Engine {@link IContext} instance
6
6
  */
@@ -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,18 +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
22
  import { initWebComponents } from "./webcomponents/init.js";
15
- import { initPhysics } from "./engine_physics_rapier.js";
16
23
  import { initXR } from "./xr/init.js";
17
24
 
18
25
  let initialized = false;
@@ -28,10 +35,15 @@ let initialized = false;
28
35
  */
29
36
  export function initEngine() {
30
37
  if (initialized) return;
38
+ if (SSR) return;
31
39
  initialized = true;
32
40
 
33
41
  initWebComponents();
34
42
  initShims();
43
+ initBuiltinSerializers();
44
+ initAddressableSerializers();
45
+ initAnimatorControllerSerializer();
46
+ initVolumeParameterSerializer();
35
47
  patchTonemapping();
36
48
  patchLayers();
37
49
  initCameraExtensions();
@@ -46,4 +58,6 @@ export function initEngine() {
46
58
  initSceneSwitcherAttributes();
47
59
  initPhysics();
48
60
  initXR();
61
+ initSpatialConsole();
62
+ initLicense();
49
63
  }
@@ -2,6 +2,7 @@ import { Intersection, Matrix4, Object3D, Ray, Vector2, Vector3 } from 'three';
2
2
 
3
3
  import { showBalloonMessage, showBalloonWarning } from './debug/debug.js';
4
4
  import { Context } from './engine_setup.js';
5
+ import { KeyboardEventBase,PointerEventBase } from './engine_ssr.js';
5
6
  import { getTempVector, getWorldQuaternion } from './engine_three_utils.js';
6
7
  import type { ButtonName, CursorTypeName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
7
8
  import { DeviceUtilities, type EnumToPrimitiveUnion, getParam } from './engine_utils.js';
@@ -116,7 +117,7 @@ export declare type NEPointerEventIntersection = Intersection & { event?: NEPoin
116
117
  * @see {@link Input} for the input management system
117
118
  * @see {@link PointerType} for available pointer types
118
119
  */
119
- export class NEPointerEvent extends PointerEvent {
120
+ export class NEPointerEvent extends PointerEventBase {
120
121
 
121
122
  /**
122
123
  * Spatial input data
@@ -242,7 +243,7 @@ export class NEPointerEvent extends PointerEvent {
242
243
  if (debug) console.warn("Stop propagation...", this.pointerId, this.pointerType)
243
244
  }
244
245
  }
245
- export class NEKeyboardEvent extends KeyboardEvent {
246
+ export class NEKeyboardEvent extends KeyboardEventBase {
246
247
  source?: Event
247
248
  constructor(type: InputEvents, source: Event, init: KeyboardEventInit) {
248
249
  super(type, init)
@@ -1,14 +1,11 @@
1
- import { dof } from "three/src/nodes/TSL.js";
2
-
3
- import { isDevEnvironment } from "./debug/index.js";
4
1
  import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
5
2
  import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
6
3
  import { onInitialized } from "./engine_lifecycle_api.js";
7
4
  import { isLocalNetwork } from "./engine_networking_utils.js";
8
5
  import { Context } from "./engine_setup.js";
6
+ import { SSR } from "./engine_ssr.js";
9
7
  import type { IContext } from "./engine_types.js";
10
8
  import { getParam } from "./engine_utils.js";
11
- import { InternalAttributeUtils } from "./engine_utils_attributes.js";
12
9
 
13
10
  const debug = getParam("debuglicense");
14
11
 
@@ -77,18 +74,22 @@ function invokeLicenseCheckResultChanged(result: boolean) {
77
74
  // #region Telemetry
78
75
  export namespace Telemetry {
79
76
 
80
- window.addEventListener("error", (event: ErrorEvent) => {
81
- sendError(Context.Current, "unhandled_error", event);
82
- });
83
- window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
84
- sendError(Context.Current, "unhandled_promise_rejection", {
85
- message: event.reason?.message,
86
- stack: event.reason?.stack,
87
- timestamp: Date.now(),
77
+ if (typeof window !== "undefined") {
78
+ window.addEventListener("error", (event: ErrorEvent) => {
79
+ sendError(Context.Current, "unhandled_error", event);
88
80
  });
89
- });
81
+ window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
82
+ sendError(Context.Current, "unhandled_promise_rejection", {
83
+ message: event.reason?.message,
84
+ stack: event.reason?.stack,
85
+ timestamp: Date.now(),
86
+ });
87
+ });
88
+ }
90
89
 
91
- onInitialized((ctx => sendPageViewEvent(ctx)), { once: true });
90
+ export function init() {
91
+ if(!SSR) onInitialized((ctx => sendPageViewEvent(ctx)), { once: true });
92
+ }
92
93
 
93
94
  function sendPageViewEvent(ctx: IContext): Promise<void> | void {
94
95
  if (!isAllowed(ctx)) {
@@ -241,11 +242,14 @@ export namespace Telemetry {
241
242
  }
242
243
 
243
244
 
244
- ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
245
- showLicenseInfo(evt.context);
246
- handleForbidden(evt.context);
247
- setTimeout(() => sendUsageMessageToAnalyticsBackend(evt.context), 2000);
248
- });
245
+ export function initLicense() {
246
+ Telemetry.init();
247
+ ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
248
+ showLicenseInfo(evt.context);
249
+ handleForbidden(evt.context);
250
+ setTimeout(() => sendUsageMessageToAnalyticsBackend(evt.context), 2000);
251
+ });
252
+ }
249
253
 
250
254
  export let runtimeLicenseCheckPromise: Promise<void> | undefined = undefined;
251
255
  let applicationIsForbidden = false;
@@ -1,5 +1,6 @@
1
1
  import { type Context, FrameEvent } from "./engine_context.js";
2
2
  import { ContextEvent } from "./engine_context_registry.js";
3
+ import { SSR } from "./engine_ssr.js";
3
4
 
4
5
  export declare type Event = ContextEvent | FrameEvent;
5
6
 
@@ -38,6 +39,12 @@ let methodsWarningCounter = 0;
38
39
  * @param evt the event to call the function at
39
40
  */
40
41
  export function registerFrameEventCallback(cb: LifecycleMethod, evt: Event, opts?: LifecycleMethodOptions) {
42
+
43
+ // Don't register the callback if we are in SSR, because it will never be called and might cause memory leaks
44
+ if(SSR) {
45
+ return;
46
+ }
47
+
41
48
  if (!newMethods.has(evt)) {
42
49
  newMethods.set(evt, new Array());
43
50
  }
@@ -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(); }