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

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 (193) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +27 -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-qDahLTqW.min.js → needle-engine.bundle-B-5Q2CpC.min.js} +249 -173
  9. package/dist/{needle-engine.bundle-CwhCzjep.js → needle-engine.bundle-dit3f1l5.js} +13238 -12724
  10. package/dist/{needle-engine.bundle-wM-BWPX9.umd.cjs → needle-engine.bundle-qZfVf_v-.umd.cjs} +250 -174
  11. package/dist/needle-engine.d.ts +295 -31
  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/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
  19. package/dist/vendor-CJmyOrCq.min.js +1116 -0
  20. package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
  21. package/lib/engine/api.d.ts +12 -0
  22. package/lib/engine/api.js +2 -0
  23. package/lib/engine/api.js.map +1 -1
  24. package/lib/engine/debug/debug_environment.js +1 -1
  25. package/lib/engine/debug/debug_environment.js.map +1 -1
  26. package/lib/engine/engine_application.js +8 -6
  27. package/lib/engine/engine_application.js.map +1 -1
  28. package/lib/engine/engine_components.js +5 -1
  29. package/lib/engine/engine_components.js.map +1 -1
  30. package/lib/engine/engine_constants.js +6 -0
  31. package/lib/engine/engine_constants.js.map +1 -1
  32. package/lib/engine/engine_context.d.ts +33 -7
  33. package/lib/engine/engine_context.js +40 -2
  34. package/lib/engine/engine_context.js.map +1 -1
  35. package/lib/engine/engine_context_registry.js +1 -1
  36. package/lib/engine/engine_context_registry.js.map +1 -1
  37. package/lib/engine/engine_init.js +7 -0
  38. package/lib/engine/engine_init.js.map +1 -1
  39. package/lib/engine/engine_input.d.ts +3 -2
  40. package/lib/engine/engine_input.js +3 -2
  41. package/lib/engine/engine_input.js.map +1 -1
  42. package/lib/engine/engine_license.d.ts +2 -0
  43. package/lib/engine/engine_license.js +25 -15
  44. package/lib/engine/engine_license.js.map +1 -1
  45. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  46. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  47. package/lib/engine/engine_networking_blob.d.ts +1 -1
  48. package/lib/engine/engine_networking_blob.js +5 -11
  49. package/lib/engine/engine_networking_blob.js.map +1 -1
  50. package/lib/engine/engine_physics_rapier.d.ts +3 -0
  51. package/lib/engine/engine_physics_rapier.js +13 -10
  52. package/lib/engine/engine_physics_rapier.js.map +1 -1
  53. package/lib/engine/engine_pmrem.js +2 -2
  54. package/lib/engine/engine_pmrem.js.map +1 -1
  55. package/lib/engine/engine_scenedata.d.ts +30 -0
  56. package/lib/engine/engine_scenedata.js +136 -0
  57. package/lib/engine/engine_scenedata.js.map +1 -0
  58. package/lib/engine/engine_ssr.d.ts +18 -0
  59. package/lib/engine/engine_ssr.js +40 -0
  60. package/lib/engine/engine_ssr.js.map +1 -0
  61. package/lib/engine/engine_three_utils.d.ts +14 -7
  62. package/lib/engine/engine_three_utils.js +14 -7
  63. package/lib/engine/engine_three_utils.js.map +1 -1
  64. package/lib/engine/engine_types.d.ts +2 -0
  65. package/lib/engine/engine_types.js.map +1 -1
  66. package/lib/engine/engine_utils.js +4 -2
  67. package/lib/engine/engine_utils.js.map +1 -1
  68. package/lib/engine/engine_utils_hash.d.ts +9 -0
  69. package/lib/engine/engine_utils_hash.js +112 -0
  70. package/lib/engine/engine_utils_hash.js.map +1 -0
  71. package/lib/engine/webcomponents/jsx.d.ts +51 -0
  72. package/lib/engine/webcomponents/logo-element.d.ts +2 -1
  73. package/lib/engine/webcomponents/logo-element.js +2 -1
  74. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  75. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -4
  76. package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
  77. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  78. package/lib/engine/webcomponents/needle-button.d.ts +2 -1
  79. package/lib/engine/webcomponents/needle-button.js +2 -1
  80. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  81. package/lib/engine/webcomponents/needle-engine.d.ts +2 -1
  82. package/lib/engine/webcomponents/needle-engine.js +2 -1
  83. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  84. package/lib/engine/xr/NeedleXRSession.d.ts +1 -0
  85. package/lib/engine/xr/NeedleXRSession.js +5 -5
  86. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  87. package/lib/engine/xr/events.d.ts +30 -3
  88. package/lib/engine/xr/events.js +38 -0
  89. package/lib/engine/xr/events.js.map +1 -1
  90. package/lib/engine/xr/init.js +1 -7
  91. package/lib/engine/xr/init.js.map +1 -1
  92. package/lib/engine-components/AnimatorController.d.ts +135 -2
  93. package/lib/engine-components/AnimatorController.js +218 -2
  94. package/lib/engine-components/AnimatorController.js.map +1 -1
  95. package/lib/engine-components/GroundProjection.d.ts +1 -0
  96. package/lib/engine-components/GroundProjection.js +184 -48
  97. package/lib/engine-components/GroundProjection.js.map +1 -1
  98. package/lib/engine-components/Light.d.ts +6 -8
  99. package/lib/engine-components/Light.js +40 -27
  100. package/lib/engine-components/Light.js.map +1 -1
  101. package/lib/engine-components/RigidBody.js +3 -3
  102. package/lib/engine-components/RigidBody.js.map +1 -1
  103. package/lib/engine-components/SceneSwitcher.js +2 -0
  104. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  105. package/lib/engine-components/api.d.ts +1 -0
  106. package/lib/engine-components/api.js +1 -0
  107. package/lib/engine-components/api.js.map +1 -1
  108. package/lib/engine-components/codegen/components.d.ts +1 -0
  109. package/lib/engine-components/codegen/components.js +1 -0
  110. package/lib/engine-components/codegen/components.js.map +1 -1
  111. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  112. package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
  113. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  114. package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
  115. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  116. package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
  117. package/lib/engine-components/web/ScrollFollow.js +3 -2
  118. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  119. package/lib/needle-engine.d.ts +2 -0
  120. package/lib/needle-engine.js +2 -0
  121. package/lib/needle-engine.js.map +1 -1
  122. package/package.json +6 -4
  123. package/plugins/common/logger.js +42 -19
  124. package/plugins/dts-generator/dts.codegen.js +334 -0
  125. package/plugins/dts-generator/dts.scan.js +99 -0
  126. package/plugins/dts-generator/dts.writer.js +59 -0
  127. package/plugins/dts-generator/glb.discovery.js +279 -0
  128. package/plugins/dts-generator/glb.extractor.js +215 -0
  129. package/plugins/dts-generator/glb.reader.js +167 -0
  130. package/plugins/dts-generator/index.js +36 -0
  131. package/plugins/dts-generator/manifest.types.js +174 -0
  132. package/plugins/types/index.d.ts +2 -1
  133. package/plugins/types/needle-bindings.d.ts +30 -0
  134. package/plugins/types/userconfig.d.ts +21 -2
  135. package/plugins/vite/asap.js +1 -1
  136. package/plugins/vite/dependency-watcher.d.ts +2 -2
  137. package/plugins/vite/dependency-watcher.js +3 -4
  138. package/plugins/vite/drop.d.ts +2 -2
  139. package/plugins/vite/drop.js +3 -4
  140. package/plugins/vite/dts-generator.d.ts +7 -0
  141. package/plugins/vite/dts-generator.js +191 -0
  142. package/plugins/vite/index.d.ts +10 -3
  143. package/plugins/vite/index.js +27 -10
  144. package/plugins/vite/logger.client.js +4 -3
  145. package/plugins/vite/logging.js +2 -2
  146. package/plugins/vite/meta.js +4 -2
  147. package/plugins/vite/poster.d.ts +2 -2
  148. package/plugins/vite/poster.js +3 -5
  149. package/plugins/vite/reload.d.ts +2 -2
  150. package/plugins/vite/reload.js +23 -22
  151. package/src/engine/api.ts +15 -1
  152. package/src/engine/debug/debug_environment.ts +1 -1
  153. package/src/engine/engine_application.ts +8 -6
  154. package/src/engine/engine_components.ts +7 -4
  155. package/src/engine/engine_constants.ts +11 -6
  156. package/src/engine/engine_context.ts +50 -7
  157. package/src/engine/engine_context_registry.ts +1 -1
  158. package/src/engine/engine_init.ts +6 -0
  159. package/src/engine/engine_input.ts +3 -2
  160. package/src/engine/engine_license.ts +23 -19
  161. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  162. package/src/engine/engine_networking_blob.ts +5 -11
  163. package/src/engine/engine_physics_rapier.ts +14 -12
  164. package/src/engine/engine_pmrem.ts +3 -3
  165. package/src/engine/engine_scenedata.ts +134 -0
  166. package/src/engine/engine_ssr.ts +48 -0
  167. package/src/engine/engine_three_utils.ts +15 -7
  168. package/src/engine/engine_types.ts +2 -0
  169. package/src/engine/engine_utils.ts +3 -2
  170. package/src/engine/engine_utils_hash.ts +65 -0
  171. package/src/engine/webcomponents/jsx.d.ts +51 -0
  172. package/src/engine/webcomponents/logo-element.ts +3 -1
  173. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -2
  174. package/src/engine/webcomponents/needle-button.ts +3 -1
  175. package/src/engine/webcomponents/needle-engine.ts +3 -1
  176. package/src/engine/xr/NeedleXRSession.ts +6 -6
  177. package/src/engine/xr/events.ts +44 -1
  178. package/src/engine/xr/init.ts +0 -7
  179. package/src/engine-components/AnimatorController.ts +286 -4
  180. package/src/engine-components/GroundProjection.ts +226 -52
  181. package/src/engine-components/Light.ts +40 -26
  182. package/src/engine-components/RigidBody.ts +3 -3
  183. package/src/engine-components/SceneSwitcher.ts +1 -0
  184. package/src/engine-components/api.ts +1 -0
  185. package/src/engine-components/codegen/components.ts +1 -0
  186. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
  187. package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
  188. package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
  189. package/src/engine-components/web/ScrollFollow.ts +2 -2
  190. package/src/needle-engine.ts +3 -0
  191. package/src/vite-env.d.ts +16 -0
  192. package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
  193. 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;
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, statSync } from 'fs';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { needleLog } from './logging.js';
9
9
 
10
+ const pluginName = "needle-reload";
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
12
13
 
@@ -15,14 +16,12 @@ const filesUsingHotReload = new Set();
15
16
  let assetsDirectory = "";
16
17
 
17
18
  /**
18
- * @param {"build" | "serve"} command
19
+ * @param {"build" | "serve" | undefined} _command
19
20
  * @param {import('../types/needleConfig').needleMeta | null} config
20
21
  * @param {import('../types').userSettings} userSettings
21
22
  * @returns {import('vite').Plugin | undefined}
22
23
  */
23
- export function needleReload(command, config, userSettings) {
24
- if (command === "build") return undefined;
25
-
24
+ export function needleReload(_command, config, userSettings) {
26
25
  if (userSettings?.noReload === true) return undefined;
27
26
 
28
27
 
@@ -41,7 +40,7 @@ export function needleReload(command, config, userSettings) {
41
40
  const buildDirectory = projectConfig?.buildDirectory?.length ? process.cwd().replaceAll("\\", "/") + "/" + projectConfig?.buildDirectory : "";
42
41
  if (buildDirectory?.length) {
43
42
  const relativeBuildDirectory = path.relative(process.cwd(), buildDirectory).replaceAll("\\", "/") || ".";
44
- setTimeout(() => needleLog("needle-reload", "Build directory: " + relativeBuildDirectory), 100);
43
+ setTimeout(() => needleLog(pluginName, "Build directory: " + relativeBuildDirectory), 100);
45
44
  }
46
45
 
47
46
  // These ignore patterns will be injected into user config to better control vite reloading
@@ -51,6 +50,7 @@ export function needleReload(command, config, userSettings) {
51
50
 
52
51
  return {
53
52
  name: 'needle:reload',
53
+ apply: 'serve',
54
54
  /** @param {import('vite').UserConfig} config */
55
55
  config(config) {
56
56
  if (!config.server) config.server = { watch: { ignored: [] } };
@@ -60,7 +60,7 @@ export function needleReload(command, config, userSettings) {
60
60
  // @ts-ignore - watch.ignored is guaranteed to be string[] by the guards above
61
61
  config.server.watch.ignored.push(pattern);
62
62
  if (userSettings?.debug === true)
63
- setTimeout(() => needleLog("needle-reload", "Updated server ignore patterns: " + JSON.stringify(config.server?.watch?.ignored)), 100);
63
+ setTimeout(() => needleLog(pluginName, "Updated server ignore patterns: " + JSON.stringify(config.server?.watch?.ignored)), 100);
64
64
  },
65
65
  /** @param {import('vite').HmrContext & {buildDirectory?: string}} args */
66
66
  handleHotUpdate(args) {
@@ -119,7 +119,7 @@ function getHot(server) {
119
119
 
120
120
  /** @param {import('vite').ViteDevServer} server @param {string} [file] */
121
121
  function notifyClientWillReload(server, file) {
122
- console.log("Send reload notification");
122
+ needleLog(pluginName, "Send reload notification");
123
123
  getHot(server).send('needle:reload', { type: 'will-reload', file: file });
124
124
  }
125
125
 
@@ -135,7 +135,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
135
135
 
136
136
  // Dont reload the whole server when a file that is using hot reload changes
137
137
  if (filesUsingHotReload.has(file)) {
138
- console.log("File is using hot reload: " + file);
138
+ needleLog(pluginName, "File is using hot reload: " + file);
139
139
  return null;
140
140
  }
141
141
 
@@ -154,7 +154,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
154
154
  // instead of relying on the vite server watch ignore array
155
155
  // we could here also match paths that we know we dont want to track
156
156
  if (ignorePatterns.length > 0 && ignoreRegex.test(file)) {
157
- console.log("Ignore change in file: " + getFileNameLog(file));
157
+ needleLog(pluginName, "Ignore change in file: " + getFileNameLog(file));
158
158
  return [];
159
159
  }
160
160
 
@@ -164,7 +164,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
164
164
  if (buildDirectory?.length) {
165
165
  const dir = path.dirname(file).replaceAll("\\", "/");
166
166
  if (dir.startsWith(buildDirectory)) {
167
- console.log("Ignore change in build directory: " + getFileNameLog(file));
167
+ needleLog(pluginName, "Ignore change in build directory: " + getFileNameLog(file));
168
168
  return [];
169
169
  }
170
170
  }
@@ -172,19 +172,20 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
172
172
  // Check if codegen files actually changed their content
173
173
  // this will return false if its the first update
174
174
  // meaning if its the first export after the server starts those will not trigger a reload
175
+ // Ignore d.ts
175
176
  const shouldCheckIfContentChanged = file.includes("/codegen/") || file.includes("/generated/") || file.endsWith("gen.js");// || file.endsWith(".glb") || file.endsWith(".gltf") || file.endsWith(".bin");
176
- if (shouldCheckIfContentChanged) {
177
+ if (!file.includes("/.svelte-kit/") && !file.endsWith(".d.ts") && !file.endsWith("needle-html-data.json") && shouldCheckIfContentChanged) {
177
178
  if (reloadIsScheduled) {
178
179
  return [];
179
180
  }
180
181
  if (await testIfFileContentChanged(file, read) === false) {
181
- console.log("File content didnt change: " + getFileNameLog(file));
182
+ needleLog(pluginName, "File content didnt change: " + getFileNameLog(file));
182
183
  return [];
183
184
  }
184
185
  }
185
186
 
186
187
  // these are known file types we export from integrations
187
- const knownExportFileTypes = [ ".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm" ];
188
+ const knownExportFileTypes = [".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm"];
188
189
  if (!knownExportFileTypes.some((type) => file.endsWith(type)))
189
190
  return null;
190
191
 
@@ -208,7 +209,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
208
209
  }
209
210
  }
210
211
 
211
- console.log("> Detected file change: ", getFileNameLog(file) + " (" + ((fileSize / (1024 * 1024)).toFixed(1)) + " MB)");
212
+ needleLog(pluginName, "> Detected file change: " + getFileNameLog(file) + " (" + ((fileSize / (1024 * 1024)).toFixed(1)) + " MB)");
212
213
  notifyClientWillReload(server);
213
214
  scheduleReload(server);
214
215
  return [];
@@ -223,7 +224,7 @@ async function scheduleReload(server, level = 0) {
223
224
  const lockFile = path.join(process.cwd(), lockFileName);
224
225
  if (existsSync(lockFile)) {
225
226
  if (level === 0)
226
- console.log("Lock file exists, waiting for export to finish...");
227
+ needleLog(pluginName, "Lock file exists, waiting for export to finish...");
227
228
  setTimeout(() => scheduleReload(server, level += 1), 300);
228
229
  return;
229
230
  }
@@ -234,13 +235,13 @@ async function scheduleReload(server, level = 0) {
234
235
  if (timeDiff < 1000) {
235
236
  // Sometimes file changes happen immediately after triggering a reload
236
237
  // we dont want to reload again in that case
237
- console.log("Ignoring reload, last reload was too recent", timeDiff);
238
+ needleLog(pluginName, "Ignoring reload, last reload was too recent" + timeDiff + "ms ago");
238
239
  return;
239
240
  }
240
241
 
241
242
  lastReloadTime = Date.now();
242
243
  const readableTime = new Date(lastReloadTime).toLocaleTimeString();
243
- console.log("< Reloading... " + readableTime)
244
+ needleLog(pluginName, "< Reloading... " + readableTime);
244
245
  getHot(server).send({
245
246
  type: 'full-reload',
246
247
  path: '*'
@@ -299,10 +300,10 @@ function removeVersionQueryArgument(content) {
299
300
  function insertScriptRegisterHotReloadCode(src, filePath) {
300
301
 
301
302
  // We only want to inject the hot reload code in the needle-engine root file
302
- if(!filePath.includes("/src/needle-engine.ts")) {
303
+ if (!filePath.includes("/src/needle-engine.ts")) {
303
304
  return src;
304
305
  }
305
- console.log("[Needle HMR] Hot reload is enabled");
306
+ needleLog(pluginName, "[Needle HMR] Hot reload is enabled");
306
307
  // this code let's the engine know that we are in hot reload mode
307
308
  const code = `
308
309
  globalThis.NEEDLE_HOT_RELOAD_ENABLED = true;
@@ -338,12 +339,12 @@ function insertScriptHotReloadCode(src, filePath) {
338
339
  return undefined;
339
340
  // make import path from engine package
340
341
  const fullPathToHotReload = process.cwd() + "/node_modules/@needle-tools/engine/src/engine/engine_hot_reload.ts";
341
- // console.log(fullPathToHotReload);
342
+ // needleLog(pluginName, fullPathToHotReload);
342
343
  const fileDirectory = path.dirname(filePath);
343
- // console.log("DIR", fileDirectory)
344
+ // needleLog(pluginName, "DIR", fileDirectory)
344
345
  const relativePath = path.relative(fileDirectory, fullPathToHotReload);
345
346
  importPath = relativePath.replace(/\\/g, "/");
346
- // console.log("importPath: ", importPath);
347
+ // needleLog(pluginName, "importPath: ", importPath);
347
348
  }
348
349
 
349
350
  // console.log(importPath, ">", filePath);
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,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
  */
@@ -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;