@needle-tools/engine 5.1.0-alpha → 5.1.0-canary.0d9f44e

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 (195) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +9 -1
  3. package/SKILL.md +39 -21
  4. package/components.needle.json +1 -1
  5. package/dist/{gltf-progressive-DJBMx-zB.umd.cjs → gltf-progressive-BmblPzFj.umd.cjs} +4 -4
  6. package/dist/{gltf-progressive-BryRjllq.min.js → gltf-progressive-CN_mbb66.min.js} +2 -2
  7. package/dist/{gltf-progressive-Cl167Vjx.js → gltf-progressive-DUlhxdv4.js} +5 -2
  8. package/dist/{needle-engine.bundle-CwhCzjep.js → needle-engine.bundle-B35n_IHX.js} +13637 -13081
  9. package/dist/{needle-engine.bundle-wM-BWPX9.umd.cjs → needle-engine.bundle-CDj15wRB.umd.cjs} +250 -174
  10. package/dist/needle-engine.bundle-D5zzggEG.min.js +1732 -0
  11. package/dist/needle-engine.d.ts +316 -26
  12. package/dist/needle-engine.js +569 -563
  13. package/dist/needle-engine.min.js +1 -1
  14. package/dist/needle-engine.umd.cjs +1 -1
  15. package/dist/{postprocessing-B_9sKVU7.min.js → postprocessing-B571qGWR.min.js} +34 -34
  16. package/dist/{postprocessing-WDc9WwI3.js → postprocessing-CfrLAbLX.js} +0 -1
  17. package/dist/{postprocessing-B2wb6pzI.umd.cjs → postprocessing-CiGkAeM9.umd.cjs} +17 -17
  18. package/dist/three-examples.js +4289 -3778
  19. package/dist/three-examples.min.js +301 -14
  20. package/dist/three-examples.umd.cjs +301 -14
  21. package/dist/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
  22. package/dist/vendor-CJmyOrCq.min.js +1116 -0
  23. package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
  24. package/lib/engine/api.d.ts +12 -0
  25. package/lib/engine/api.js +2 -0
  26. package/lib/engine/api.js.map +1 -1
  27. package/lib/engine/debug/debug_environment.js +1 -1
  28. package/lib/engine/debug/debug_environment.js.map +1 -1
  29. package/lib/engine/engine_application.js +8 -6
  30. package/lib/engine/engine_application.js.map +1 -1
  31. package/lib/engine/engine_components.js +5 -1
  32. package/lib/engine/engine_components.js.map +1 -1
  33. package/lib/engine/engine_constants.js +6 -0
  34. package/lib/engine/engine_constants.js.map +1 -1
  35. package/lib/engine/engine_context.d.ts +31 -2
  36. package/lib/engine/engine_context.js +43 -2
  37. package/lib/engine/engine_context.js.map +1 -1
  38. package/lib/engine/engine_context_registry.js +1 -1
  39. package/lib/engine/engine_context_registry.js.map +1 -1
  40. package/lib/engine/engine_init.js +7 -0
  41. package/lib/engine/engine_init.js.map +1 -1
  42. package/lib/engine/engine_input.d.ts +3 -2
  43. package/lib/engine/engine_input.js +3 -2
  44. package/lib/engine/engine_input.js.map +1 -1
  45. package/lib/engine/engine_license.d.ts +2 -0
  46. package/lib/engine/engine_license.js +25 -15
  47. package/lib/engine/engine_license.js.map +1 -1
  48. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  49. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  50. package/lib/engine/engine_networking_blob.d.ts +1 -1
  51. package/lib/engine/engine_networking_blob.js +5 -11
  52. package/lib/engine/engine_networking_blob.js.map +1 -1
  53. package/lib/engine/engine_physics_rapier.d.ts +3 -0
  54. package/lib/engine/engine_physics_rapier.js +13 -10
  55. package/lib/engine/engine_physics_rapier.js.map +1 -1
  56. package/lib/engine/engine_pmrem.js +2 -2
  57. package/lib/engine/engine_pmrem.js.map +1 -1
  58. package/lib/engine/engine_scenedata.d.ts +34 -0
  59. package/lib/engine/engine_scenedata.js +135 -0
  60. package/lib/engine/engine_scenedata.js.map +1 -0
  61. package/lib/engine/engine_ssr.d.ts +18 -0
  62. package/lib/engine/engine_ssr.js +40 -0
  63. package/lib/engine/engine_ssr.js.map +1 -0
  64. package/lib/engine/engine_three_utils.d.ts +14 -7
  65. package/lib/engine/engine_three_utils.js +14 -7
  66. package/lib/engine/engine_three_utils.js.map +1 -1
  67. package/lib/engine/engine_types.d.ts +2 -0
  68. package/lib/engine/engine_types.js.map +1 -1
  69. package/lib/engine/engine_utils.js +4 -2
  70. package/lib/engine/engine_utils.js.map +1 -1
  71. package/lib/engine/engine_utils_hash.d.ts +9 -0
  72. package/lib/engine/engine_utils_hash.js +112 -0
  73. package/lib/engine/engine_utils_hash.js.map +1 -0
  74. package/lib/engine/webcomponents/logo-element.d.ts +10 -1
  75. package/lib/engine/webcomponents/logo-element.js +2 -1
  76. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  77. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +12 -4
  78. package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
  79. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  80. package/lib/engine/webcomponents/needle-button.d.ts +15 -1
  81. package/lib/engine/webcomponents/needle-button.js +2 -1
  82. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  83. package/lib/engine/webcomponents/needle-engine.d.ts +7 -1
  84. package/lib/engine/webcomponents/needle-engine.js +2 -1
  85. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  86. package/lib/engine/xr/NeedleXRSession.d.ts +1 -0
  87. package/lib/engine/xr/NeedleXRSession.js +5 -5
  88. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  89. package/lib/engine/xr/events.d.ts +30 -3
  90. package/lib/engine/xr/events.js +38 -0
  91. package/lib/engine/xr/events.js.map +1 -1
  92. package/lib/engine/xr/init.js +1 -7
  93. package/lib/engine/xr/init.js.map +1 -1
  94. package/lib/engine-components/AnimatorController.d.ts +135 -2
  95. package/lib/engine-components/AnimatorController.js +218 -2
  96. package/lib/engine-components/AnimatorController.js.map +1 -1
  97. package/lib/engine-components/GroundProjection.d.ts +1 -0
  98. package/lib/engine-components/GroundProjection.js +184 -48
  99. package/lib/engine-components/GroundProjection.js.map +1 -1
  100. package/lib/engine-components/Light.d.ts +25 -8
  101. package/lib/engine-components/Light.js +132 -27
  102. package/lib/engine-components/Light.js.map +1 -1
  103. package/lib/engine-components/RigidBody.js +3 -3
  104. package/lib/engine-components/RigidBody.js.map +1 -1
  105. package/lib/engine-components/SceneSwitcher.js +2 -0
  106. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  107. package/lib/engine-components/api.d.ts +1 -0
  108. package/lib/engine-components/api.js +1 -0
  109. package/lib/engine-components/api.js.map +1 -1
  110. package/lib/engine-components/codegen/components.d.ts +1 -0
  111. package/lib/engine-components/codegen/components.js +1 -0
  112. package/lib/engine-components/codegen/components.js.map +1 -1
  113. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  114. package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
  115. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  116. package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
  117. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  118. package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
  119. package/lib/engine-components/web/ScrollFollow.js +3 -2
  120. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  121. package/lib/needle-engine.d.ts +1 -0
  122. package/lib/needle-engine.js +1 -0
  123. package/lib/needle-engine.js.map +1 -1
  124. package/package.json +7 -5
  125. package/plugins/common/logger.js +42 -19
  126. package/plugins/dts-generator/dts.codegen.js +334 -0
  127. package/plugins/dts-generator/dts.scan.js +99 -0
  128. package/plugins/dts-generator/dts.writer.js +59 -0
  129. package/plugins/dts-generator/glb.discovery.js +279 -0
  130. package/plugins/dts-generator/glb.extractor.js +215 -0
  131. package/plugins/dts-generator/glb.reader.js +167 -0
  132. package/plugins/dts-generator/index.js +36 -0
  133. package/plugins/dts-generator/manifest.types.js +174 -0
  134. package/plugins/types/index.d.ts +2 -1
  135. package/plugins/types/needle-bindings.d.ts +30 -0
  136. package/plugins/types/userconfig.d.ts +21 -2
  137. package/plugins/vite/asap.js +1 -1
  138. package/plugins/vite/dependency-watcher.d.ts +2 -2
  139. package/plugins/vite/dependency-watcher.js +3 -4
  140. package/plugins/vite/drop.d.ts +2 -2
  141. package/plugins/vite/drop.js +3 -4
  142. package/plugins/vite/dts-generator.d.ts +7 -0
  143. package/plugins/vite/dts-generator.js +191 -0
  144. package/plugins/vite/index.d.ts +10 -3
  145. package/plugins/vite/index.js +27 -10
  146. package/plugins/vite/logger.client.js +4 -3
  147. package/plugins/vite/logging.js +2 -2
  148. package/plugins/vite/meta.js +4 -2
  149. package/plugins/vite/poster.d.ts +2 -2
  150. package/plugins/vite/poster.js +3 -5
  151. package/plugins/vite/reload.d.ts +2 -2
  152. package/plugins/vite/reload.js +5 -5
  153. package/src/engine/api.ts +15 -1
  154. package/src/engine/debug/debug_environment.ts +1 -1
  155. package/src/engine/engine_application.ts +8 -6
  156. package/src/engine/engine_components.ts +7 -4
  157. package/src/engine/engine_constants.ts +11 -6
  158. package/src/engine/engine_context.ts +47 -2
  159. package/src/engine/engine_context_registry.ts +1 -1
  160. package/src/engine/engine_init.ts +6 -0
  161. package/src/engine/engine_input.ts +3 -2
  162. package/src/engine/engine_license.ts +23 -19
  163. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  164. package/src/engine/engine_networking_blob.ts +5 -11
  165. package/src/engine/engine_physics_rapier.ts +14 -12
  166. package/src/engine/engine_pmrem.ts +3 -3
  167. package/src/engine/engine_scenedata.ts +133 -0
  168. package/src/engine/engine_ssr.ts +48 -0
  169. package/src/engine/engine_three_utils.ts +15 -7
  170. package/src/engine/engine_types.ts +2 -0
  171. package/src/engine/engine_utils.ts +3 -2
  172. package/src/engine/engine_utils_hash.ts +65 -0
  173. package/src/engine/webcomponents/logo-element.ts +10 -1
  174. package/src/engine/webcomponents/needle menu/needle-menu.ts +11 -2
  175. package/src/engine/webcomponents/needle-button.ts +15 -1
  176. package/src/engine/webcomponents/needle-engine.ts +8 -1
  177. package/src/engine/xr/NeedleXRSession.ts +6 -6
  178. package/src/engine/xr/events.ts +44 -1
  179. package/src/engine/xr/init.ts +0 -7
  180. package/src/engine-components/AnimatorController.ts +286 -4
  181. package/src/engine-components/GroundProjection.ts +226 -52
  182. package/src/engine-components/Light.ts +132 -27
  183. package/src/engine-components/RigidBody.ts +3 -3
  184. package/src/engine-components/SceneSwitcher.ts +1 -0
  185. package/src/engine-components/api.ts +1 -0
  186. package/src/engine-components/codegen/components.ts +1 -0
  187. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
  188. package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
  189. package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
  190. package/src/engine-components/web/ScrollFollow.ts +2 -2
  191. package/src/needle-engine.ts +2 -0
  192. package/src/vite-env.d.ts +16 -0
  193. package/dist/needle-engine.bundle-qDahLTqW.min.js +0 -1656
  194. package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
  195. package/dist/vendor-HRlxIBga.min.js +0 -1116
@@ -99,8 +99,9 @@ if (import.meta && "hot" in import.meta) {
99
99
  // unpatch();
100
100
  // }, 10_000);
101
101
 
102
+ const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
102
103
  const threshold = 100;
103
- const devToolsArePotentiallyOpen = window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold;
104
+ const devToolsArePotentiallyOpen = !isMobile && (window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold);
104
105
  if (devToolsArePotentiallyOpen) {
105
106
  sendLogToServer("internal", "Console logging is disabled (devtools are open)");
106
107
  }
@@ -240,8 +241,8 @@ function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth
240
241
  const isServer = typeof window === "undefined";
241
242
  const stringify_limits = {
242
243
  string: isServer ? 100_000 : 1_000,
243
- object_keys: isServer ? 300 : 200,
244
- object_depth: isServer ? 10 : 3,
244
+ object_keys: isServer ? 10 : 10,
245
+ object_depth: isServer ? 3 : 3,
245
246
  array_items: isServer ? 2_000 : 100,
246
247
  }
247
248
 
@@ -97,8 +97,8 @@ export function needleLog(pluginName, message, level = 'log', options = undefine
97
97
  ? (dimBody ? leveledBody.split('\n').map(l => colors.dim(l)).join('\n') : leveledBody)
98
98
  : "";
99
99
  const payloadCore = emitHeader
100
- ? (body.length > 0 ? `${header}\n${body}\n` : `${header}\n`)
101
- : (body.length > 0 ? `${body}\n` : "");
100
+ ? (body.length > 0 ? `${header}\n${body}` : `${header}`)
101
+ : (body.length > 0 ? `${body}` : "");
102
102
  const payload = leadingNewline ? `\n${payloadCore}` : payloadCore;
103
103
  log(payload);
104
104
  return;
@@ -13,8 +13,7 @@ import { needleGreenBold, needleLog } from './logging.js';
13
13
  */
14
14
  export function needleMeta(command, config, userSettings) {
15
15
 
16
- // we can check if this is a build
17
- const isBuild = command === 'build';
16
+ let isBuild = command === 'build';
18
17
 
19
18
  async function updateConfig() {
20
19
  config = await loadConfig();
@@ -25,6 +24,9 @@ export function needleMeta(command, config, userSettings) {
25
24
  return {
26
25
  // replace meta tags
27
26
  name: 'needle:meta',
27
+ configResolved(resolvedConfig) {
28
+ isBuild = resolvedConfig.command === 'build';
29
+ },
28
30
  transformIndexHtml: {
29
31
  order: 'pre',
30
32
  handler(/** @type {string} */ html, /** @type {unknown} */ _ctx) {
@@ -1,8 +1,8 @@
1
1
  export function getPosterPath(): string;
2
2
  /**
3
- * @param {"build" | "serve"} command
3
+ * @param {"build" | "serve" | undefined} _command
4
4
  * @param {import('../types/needleConfig').needleMeta | null | undefined} config
5
5
  * @param {import('../types').userSettings} userSettings
6
6
  * @returns {import('vite').Plugin | undefined}
7
7
  */
8
- export function needlePoster(command: "build" | "serve", config: import("../types/needleConfig").needleMeta | null | undefined, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
8
+ export function needlePoster(_command: "build" | "serve" | undefined, config: import("../types/needleConfig").needleMeta | null | undefined, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
@@ -10,19 +10,17 @@ export function getPosterPath() {
10
10
  }
11
11
 
12
12
  /**
13
- * @param {"build" | "serve"} command
13
+ * @param {"build" | "serve" | undefined} _command
14
14
  * @param {import('../types/needleConfig').needleMeta | null | undefined} config
15
15
  * @param {import('../types').userSettings} userSettings
16
16
  * @returns {import('vite').Plugin | undefined}
17
17
  */
18
- export function needlePoster(command, config, userSettings) {
19
- // only relevant for local development
20
- if (command === 'build') return [];
21
-
18
+ export function needlePoster(_command, config, userSettings) {
22
19
  if (userSettings.noPoster) return;
23
20
 
24
21
  return {
25
22
  name: 'needle:poster',
23
+ apply: 'serve',
26
24
  configureServer(server) {
27
25
  const hot = server.hot ?? server.ws;
28
26
  hot.on('needle:screenshot', async (data, client) => {
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @param {"build" | "serve"} command
2
+ * @param {"build" | "serve" | undefined} _command
3
3
  * @param {import('../types/needleConfig').needleMeta | null} config
4
4
  * @param {import('../types').userSettings} userSettings
5
5
  * @returns {import('vite').Plugin | undefined}
6
6
  */
7
- export function needleReload(command: "build" | "serve", config: import("../types/needleConfig").needleMeta | null, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
7
+ export function needleReload(_command: "build" | "serve" | undefined, config: import("../types/needleConfig").needleMeta | null, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
@@ -15,14 +15,12 @@ const filesUsingHotReload = new Set();
15
15
  let assetsDirectory = "";
16
16
 
17
17
  /**
18
- * @param {"build" | "serve"} command
18
+ * @param {"build" | "serve" | undefined} _command
19
19
  * @param {import('../types/needleConfig').needleMeta | null} config
20
20
  * @param {import('../types').userSettings} userSettings
21
21
  * @returns {import('vite').Plugin | undefined}
22
22
  */
23
- export function needleReload(command, config, userSettings) {
24
- if (command === "build") return undefined;
25
-
23
+ export function needleReload(_command, config, userSettings) {
26
24
  if (userSettings?.noReload === true) return undefined;
27
25
 
28
26
 
@@ -51,6 +49,7 @@ export function needleReload(command, config, userSettings) {
51
49
 
52
50
  return {
53
51
  name: 'needle:reload',
52
+ apply: 'serve',
54
53
  /** @param {import('vite').UserConfig} config */
55
54
  config(config) {
56
55
  if (!config.server) config.server = { watch: { ignored: [] } };
@@ -172,8 +171,9 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
172
171
  // Check if codegen files actually changed their content
173
172
  // this will return false if its the first update
174
173
  // meaning if its the first export after the server starts those will not trigger a reload
174
+ // Ignore d.ts
175
175
  const shouldCheckIfContentChanged = file.includes("/codegen/") || file.includes("/generated/") || file.endsWith("gen.js");// || file.endsWith(".glb") || file.endsWith(".gltf") || file.endsWith(".bin");
176
- if (shouldCheckIfContentChanged) {
176
+ if (!file.includes("/.svelte-kit/") && !file.endsWith(".d.ts") && !file.endsWith("needle-html-data.json") && shouldCheckIfContentChanged) {
177
177
  if (reloadIsScheduled) {
178
178
  return [];
179
179
  }
package/src/engine/api.ts CHANGED
@@ -336,6 +336,9 @@ export { loadPMREM } from "./engine_pmrem.js";
336
336
  /** Scene lighting data and environment configuration */
337
337
  export * from "./engine_scenelighting.js";
338
338
 
339
+ /** Typed scene data proxy — maps SceneData interface to live component instances */
340
+ export { getSceneData, needle } from "./engine_scenedata.js";
341
+
339
342
 
340
343
  // ============================================================================
341
344
  // SERIALIZATION
@@ -436,4 +439,15 @@ export * from "./webcomponents/needle-engine.loading.js";
436
439
  * XR API: NeedleXRSession, NeedleXRController, XRRig.
437
440
  * @see {@link https://engine.needle.tools/docs/xr.html | XR Documentation}
438
441
  */
439
- export * from "./xr/api.js"
442
+ export * from "./xr/api.js"
443
+
444
+ /**
445
+ * HTML ↔ 3D component binding types, similar to Svelte's `PageData`.
446
+ * `SceneData` is a nested interface augmented per-project by the `needle:dts-generator`
447
+ * Vite plugin — it maps scene node names to their components and typed fields.
448
+ *
449
+ * @example
450
+ * import type { SceneData } from "@needle-tools/engine";
451
+ * type OrbitSettings = SceneData["Camera"]["OrbitControls"];
452
+ */
453
+ 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;
@@ -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
@@ -10,6 +10,7 @@ import type { ComponentInit, Constructor, ConstructorConcrete, IComponent, IGame
10
10
  import { $componentName } from "./engine_types.js";
11
11
  import { getParam } from "./engine_utils.js";
12
12
  import { apply } from "./js-extensions/index.js";
13
+ import { TypeStore } from "./engine_typestore.js";
13
14
 
14
15
  const COMPONENT_GUID_NAMESPACE = 'eff8ba80-635d-11ec-90d6-0242ac120003';
15
16
 
@@ -137,7 +138,7 @@ export function addComponent<T extends IComponent>(obj: Object3D, componentInsta
137
138
  if (componentInstance.guid === undefined || componentInstance.guid === "invalid") {
138
139
  componentInstance.guid = generateDeterministicComponentGuid(obj, componentInstance);
139
140
  }
140
- if(init) componentInstance._internalInit(init);
141
+ if (init) componentInstance._internalInit(init);
141
142
  // Register the component - make sure to provide the component instance context (if assigned)
142
143
  registerComponent(componentInstance, componentInstance.context);
143
144
  return componentInstance;
@@ -170,10 +171,12 @@ function onGetComponent<T>(obj: Object3D | null | undefined, componentType: Cons
170
171
  }
171
172
  if (!(obj?.userData?.components)) return null;
172
173
  if (typeof componentType === "string") {
173
- if (!didWarnAboutComponentAccess) {
174
+ const type = TypeStore.get(componentType);
175
+ if (!didWarnAboutComponentAccess && !type) {
174
176
  didWarnAboutComponentAccess = true;
175
177
  console.warn(`Accessing components by name is not supported.\nPlease use the component type instead. This may keep working in local development but it will fail when bundling your application.\n\nYou can import other modules your main module to get access to types\nor if you use npmdefs you can make types available globally using globalThis:\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis`, componentType);
176
178
  }
179
+ if (type) componentType = type as Constructor<T>;
177
180
  }
178
181
 
179
182
  if (debugEnabled())
@@ -248,7 +251,7 @@ export function getComponents<T extends IComponent>(obj: Object3D, componentType
248
251
  * ```
249
252
  */
250
253
  export function getComponentInChildren<T extends IComponent>(obj: Object3D, componentType: Constructor<T>, includeInactive: boolean = false): T | null {
251
- if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
254
+ if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
252
255
  const res = getComponent(obj, componentType) as IComponent | null;
253
256
  if (includeInactive === false && (res?.enabled === false || res?.activeAndEnabled === false)) return null;
254
257
  if (res) return res as T;
@@ -361,7 +364,7 @@ export function findObjectOfType<T extends IComponent>(type: Constructor<T>, con
361
364
  if (!scene) return null;
362
365
 
363
366
  const res = getComponentInChildren(scene, type, includeInactive);
364
- if(res) return res;
367
+ if (res) return res;
365
368
  return null;
366
369
  }
367
370
 
@@ -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
@@ -37,6 +37,8 @@ import { NetworkConnection } from './engine_networking.js';
37
37
  import { Physics } from './engine_physics.js';
38
38
  import { PlayerViewManager } from './engine_playerview.js';
39
39
  import { RendererData as SceneLighting } from './engine_scenelighting.js';
40
+ import { getSceneData } from './engine_scenedata.js';
41
+ import type { SceneData } from 'needle-bindings';
40
42
  import { getTempColor, logHierarchy } from './engine_three_utils.js';
41
43
  import { Time } from './engine_time.js';
42
44
  import { patchTonemapping } from './engine_tonemapping.js';
@@ -475,6 +477,33 @@ 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
+ * Typed proxy providing direct access to scene components by node and component name.
482
+ * Types are auto-generated from your GLB assets by the `needle:dts-generator` Vite plugin
483
+ * and written to `src/generated/needle-bindings.d.ts` on every dev-server start and GLB change.
484
+ *
485
+ * Each property access traverses the live scene graph on demand — no caching, always fresh.
486
+ * This is a convenience shorthand for `getComponent` lookups; the same result can be
487
+ * achieved manually via `scene.getObjectByName(name)` + `getComponent(node, Type)`.
488
+ *
489
+ * @example
490
+ * // Toggle auto-rotate on the orbit camera
491
+ * ctx.sceneData.Camera.OrbitControls.autoRotate = true;
492
+ *
493
+ * @example
494
+ * // Change the background color
495
+ * ctx.sceneData.Camera.Camera.backgroundColor = new RGBAColor(1, 0, 0, 1);
496
+ *
497
+ * @example
498
+ * // Equivalent manual approach (without sceneData)
499
+ * const node = ctx.scene.getObjectByName("Camera");
500
+ * const orbit = getComponent(node, OrbitControls);
501
+ * if (orbit) orbit.autoRotate = true;
502
+ */
503
+ get sceneData(): SceneData {
504
+ return getSceneData(this);
505
+ }
506
+
478
507
  get mainCamera(): Camera {
479
508
  if (this._mainCamera) {
480
509
  return this._mainCamera;
@@ -513,8 +542,22 @@ export class Context implements IContext {
513
542
  connection: NetworkConnection;
514
543
  /** @deprecated AssetDatabase is deprecated */
515
544
  assets: AssetDatabase;
516
- /** The main light in the scene */
517
- mainLight: ILight | null = null;
545
+
546
+ /** All registered lights in the scene, maintained by the Light component */
547
+ readonly lights: Set<ILight> = new Set();
548
+
549
+ /** The brightest registered directional light, or null if none are registered
550
+ * @see lights
551
+ */
552
+ get mainLight(): ILight | null {
553
+ let best: ILight | null = null;
554
+ for (const light of this.lights) {
555
+ if (light.type !== "directional") continue;
556
+ if (!best || light.intensity > best.intensity) best = light;
557
+ }
558
+ return best;
559
+ }
560
+
518
561
  /** @deprecated Use sceneLighting */
519
562
  get rendererData() { return this.sceneLighting }
520
563
  /** Access the scene lighting manager to control lighting settings in the context */
@@ -829,6 +872,8 @@ export class Context implements IContext {
829
872
  this._onBeforeRenderListeners.clear();
830
873
  this._onAfterRenderListeners.clear();
831
874
 
875
+ this.lights.clear();
876
+
832
877
  if (!this.isManagedExternally) {
833
878
  if (this.renderer) {
834
879
  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
  */
@@ -11,7 +11,10 @@ import { initCameraExtensions } from "./js-extensions/Camera.js";
11
11
  import { patchLayers } from "./js-extensions/Layers.js";
12
12
  import { initObject3DExtensions } from "./js-extensions/Object3D.js";
13
13
  import { initVectorExtensions } from "./js-extensions/Vector.js";
14
+ import { SSR } from "./engine_ssr.js";
15
+ import { initLicense } from "./engine_license.js";
14
16
  import { initWebComponents } from "./webcomponents/init.js";
17
+ import { initPhysics } from "./engine_physics_rapier.js";
15
18
  import { initXR } from "./xr/init.js";
16
19
 
17
20
  let initialized = false;
@@ -27,6 +30,7 @@ let initialized = false;
27
30
  */
28
31
  export function initEngine() {
29
32
  if (initialized) return;
33
+ if (SSR) return;
30
34
  initialized = true;
31
35
 
32
36
  initWebComponents();
@@ -43,5 +47,7 @@ export function initEngine() {
43
47
  initAnimationAutoplay();
44
48
  initSkyboxAttributes();
45
49
  initSceneSwitcherAttributes();
50
+ initPhysics();
46
51
  initXR();
52
+ initLicense();
47
53
  }
@@ -1,6 +1,7 @@
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';
4
5
  import { Context } from './engine_setup.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';
@@ -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,6 +1,3 @@
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";
@@ -8,7 +5,7 @@ import { isLocalNetwork } from "./engine_networking_utils.js";
8
5
  import { Context } from "./engine_setup.js";
9
6
  import type { IContext } from "./engine_types.js";
10
7
  import { getParam } from "./engine_utils.js";
11
- import { InternalAttributeUtils } from "./engine_utils_attributes.js";
8
+ import { SSR } from "./engine_ssr.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
  }
@@ -1,11 +1,9 @@
1
- import * as _md5 from "md5";
2
- // CJS interop: md5 may appear as { default: fn } or fn depending on bundler
3
- const md5 = typeof _md5 === "function" ? _md5 : (_md5 as any).default;
4
1
  import { FileLoader } from "three";
5
2
 
6
3
  import { showBalloonWarning } from "./debug/index.js";
7
4
  import { hasCommercialLicense } from "./engine_license.js";
8
5
  import { delay } from "./engine_utils.js";
6
+ import { md5Hex, md5AsBytes, sha256Base64 } from "./engine_utils_hash.js";
9
7
 
10
8
 
11
9
  export namespace BlobStorage {
@@ -22,21 +20,17 @@ export namespace BlobStorage {
22
20
  /**
23
21
  * Generates an md5 hash from a given buffer
24
22
  * @param buffer The buffer to hash
25
- * @returns The md5 hash
23
+ * @returns The md5 hash as a hex string
26
24
  */
27
25
  export function hashMD5(buffer: ArrayBuffer): string {
28
- return md5(new Uint8Array(buffer))
26
+ return md5Hex(new Uint8Array(buffer));
29
27
  }
30
28
  export function hashMD5_Base64(buffer: ArrayBuffer): string {
31
- const bytes = md5(new Uint8Array(buffer), { encoding: "binary", asBytes: true });
29
+ const bytes = md5AsBytes(new Uint8Array(buffer));
32
30
  return btoa(String.fromCharCode(...bytes));
33
31
  }
34
32
  export function hashSha256(buffer: ArrayBuffer): Promise<string> {
35
- const bytes = new Uint8Array(buffer);
36
- const hash = crypto.subtle.digest('SHA-256', bytes).then(res => {
37
- return btoa(String.fromCharCode(...new Uint8Array(res)));
38
- })
39
- return hash;
33
+ return sha256Base64(buffer);
40
34
  }
41
35
 
42
36
  export type Upload_Result = {
@@ -44,18 +44,20 @@ const $bodyKey = Symbol("physics body");
44
44
  const $colliderRigidbody = Symbol("rigidbody");
45
45
 
46
46
 
47
- declare const NEEDLE_USE_RAPIER: boolean;
48
- globalThis["NEEDLE_USE_RAPIER"] = globalThis["NEEDLE_USE_RAPIER"] !== undefined ? globalThis["NEEDLE_USE_RAPIER"] : true;
49
- if (debugPhysics)
50
- console.log("Use Rapier", NEEDLE_USE_RAPIER, globalThis["NEEDLE_USE_RAPIER"])
51
-
52
- if (NEEDLE_USE_RAPIER) {
53
- ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, evt => {
54
- if (debugPhysics)
55
- console.log("Register rapier physics backend")
56
- evt.context.physics.engine = new RapierPhysics(evt.context);
57
- // We do not initialize physics immediately to avoid loading the physics engine if it is not needed
58
- });
47
+ /** Register the Rapier physics backend. Called from {@link initEngine}
48
+ * to ensure it runs regardless of tree-shaking. */
49
+ export function initPhysics() {
50
+ if (debugPhysics)
51
+ console.log("Use Rapier", NEEDLE_USE_RAPIER, globalThis["NEEDLE_USE_RAPIER"])
52
+
53
+ if (NEEDLE_USE_RAPIER) {
54
+ ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, evt => {
55
+ if (debugPhysics)
56
+ console.log("Register rapier physics backend")
57
+ evt.context.physics.engine = new RapierPhysics(evt.context);
58
+ // We do not initialize physics immediately to avoid loading the physics engine if it is not needed
59
+ });
60
+ }
59
61
  }
60
62
 
61
63
 
@@ -1,8 +1,8 @@
1
1
  import { createLoaders } from "@needle-tools/gltf-progressive";
2
2
  import { CubeUVReflectionMapping, EquirectangularRefractionMapping, SRGBColorSpace, Texture, TextureLoader, WebGLRenderer } from "three";
3
- import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader";
4
- import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
5
- import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
3
+ import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
4
+ import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
5
+ import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
6
6
 
7
7
  const running: Map<string, Promise<Texture | null>> = new Map();
8
8