@needle-tools/engine 5.1.0-canary.e9062c0 → 5.1.0-canary.fbdfce3

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 (49) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{needle-engine.bundle-CDKcuALa.js → needle-engine.bundle-BFSj2Fz8.js} +5420 -5413
  3. package/dist/{needle-engine.bundle-w22UkaT_.min.js → needle-engine.bundle-CmxIO5uH.min.js} +135 -135
  4. package/dist/{needle-engine.bundle--q_w7iXI.umd.cjs → needle-engine.bundle-tJIZukCz.umd.cjs} +135 -135
  5. package/dist/needle-engine.d.ts +57 -65
  6. package/dist/needle-engine.js +188 -186
  7. package/dist/needle-engine.min.js +1 -1
  8. package/dist/needle-engine.umd.cjs +1 -1
  9. package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
  10. package/lib/engine/debug/debug_spatial_console.js +10 -7
  11. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  12. package/lib/engine/engine_addressables.d.ts +2 -0
  13. package/lib/engine/engine_addressables.js +6 -3
  14. package/lib/engine/engine_addressables.js.map +1 -1
  15. package/lib/engine/engine_context.d.ts +16 -19
  16. package/lib/engine/engine_context.js +12 -15
  17. package/lib/engine/engine_context.js.map +1 -1
  18. package/lib/engine/engine_init.js +10 -0
  19. package/lib/engine/engine_init.js.map +1 -1
  20. package/lib/engine/engine_scenedata.d.ts +5 -7
  21. package/lib/engine/engine_scenedata.js +14 -11
  22. package/lib/engine/engine_scenedata.js.map +1 -1
  23. package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
  24. package/lib/engine/engine_serialization_builtin_serializer.js +28 -41
  25. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  26. package/lib/engine/webcomponents/jsx.d.ts +9 -2
  27. package/lib/engine-components/AnimatorController.d.ts +2 -0
  28. package/lib/engine-components/AnimatorController.js +4 -1
  29. package/lib/engine-components/AnimatorController.js.map +1 -1
  30. package/lib/engine-components/Light.js +4 -2
  31. package/lib/engine-components/Light.js.map +1 -1
  32. package/lib/engine-components/ReflectionProbe.js +2 -0
  33. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  34. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
  35. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
  36. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  37. package/package.json +1 -1
  38. package/plugins/vite/reload.js +18 -17
  39. package/src/engine/debug/debug_spatial_console.ts +10 -7
  40. package/src/engine/engine_addressables.ts +6 -3
  41. package/src/engine/engine_context.ts +18 -20
  42. package/src/engine/engine_init.ts +10 -0
  43. package/src/engine/engine_scenedata.ts +13 -10
  44. package/src/engine/engine_serialization_builtin_serializer.ts +32 -43
  45. package/src/engine/webcomponents/jsx.d.ts +9 -2
  46. package/src/engine-components/AnimatorController.ts +4 -1
  47. package/src/engine-components/Light.ts +3 -2
  48. package/src/engine-components/ReflectionProbe.ts +2 -0
  49. package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
@@ -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
 
@@ -39,7 +40,7 @@ export function needleReload(_command, config, userSettings) {
39
40
  const buildDirectory = projectConfig?.buildDirectory?.length ? process.cwd().replaceAll("\\", "/") + "/" + projectConfig?.buildDirectory : "";
40
41
  if (buildDirectory?.length) {
41
42
  const relativeBuildDirectory = path.relative(process.cwd(), buildDirectory).replaceAll("\\", "/") || ".";
42
- setTimeout(() => needleLog("needle-reload", "Build directory: " + relativeBuildDirectory), 100);
43
+ setTimeout(() => needleLog(pluginName, "Build directory: " + relativeBuildDirectory), 100);
43
44
  }
44
45
 
45
46
  // These ignore patterns will be injected into user config to better control vite reloading
@@ -59,7 +60,7 @@ export function needleReload(_command, config, userSettings) {
59
60
  // @ts-ignore - watch.ignored is guaranteed to be string[] by the guards above
60
61
  config.server.watch.ignored.push(pattern);
61
62
  if (userSettings?.debug === true)
62
- 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);
63
64
  },
64
65
  /** @param {import('vite').HmrContext & {buildDirectory?: string}} args */
65
66
  handleHotUpdate(args) {
@@ -118,7 +119,7 @@ function getHot(server) {
118
119
 
119
120
  /** @param {import('vite').ViteDevServer} server @param {string} [file] */
120
121
  function notifyClientWillReload(server, file) {
121
- console.log("Send reload notification");
122
+ needleLog(pluginName, "Send reload notification");
122
123
  getHot(server).send('needle:reload', { type: 'will-reload', file: file });
123
124
  }
124
125
 
@@ -134,7 +135,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
134
135
 
135
136
  // Dont reload the whole server when a file that is using hot reload changes
136
137
  if (filesUsingHotReload.has(file)) {
137
- console.log("File is using hot reload: " + file);
138
+ needleLog(pluginName, "File is using hot reload: " + file);
138
139
  return null;
139
140
  }
140
141
 
@@ -153,7 +154,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
153
154
  // instead of relying on the vite server watch ignore array
154
155
  // we could here also match paths that we know we dont want to track
155
156
  if (ignorePatterns.length > 0 && ignoreRegex.test(file)) {
156
- console.log("Ignore change in file: " + getFileNameLog(file));
157
+ needleLog(pluginName, "Ignore change in file: " + getFileNameLog(file));
157
158
  return [];
158
159
  }
159
160
 
@@ -163,7 +164,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
163
164
  if (buildDirectory?.length) {
164
165
  const dir = path.dirname(file).replaceAll("\\", "/");
165
166
  if (dir.startsWith(buildDirectory)) {
166
- console.log("Ignore change in build directory: " + getFileNameLog(file));
167
+ needleLog(pluginName, "Ignore change in build directory: " + getFileNameLog(file));
167
168
  return [];
168
169
  }
169
170
  }
@@ -178,13 +179,13 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
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);
@@ -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
+ }
@@ -478,13 +478,14 @@ export class Context implements IContext {
478
478
  * Use `setCurrentCamera` for updating the main camera.
479
479
  */
480
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.
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.
484
483
  *
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)`.
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.
488
489
  *
489
490
  * @example
490
491
  * // Toggle auto-rotate on the orbit camera
@@ -493,12 +494,6 @@ export class Context implements IContext {
493
494
  * @example
494
495
  * // Change the background color
495
496
  * 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
497
  */
503
498
  get sceneData(): SceneData {
504
499
  return getSceneData(this);
@@ -543,8 +538,10 @@ export class Context implements IContext {
543
538
  /** @deprecated AssetDatabase is deprecated */
544
539
  assets: AssetDatabase;
545
540
 
546
- /** All registered lights in the scene, maintained by the Light component */
547
- readonly lights: Set<ILight> = new Set();
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>();
548
545
 
549
546
  /** The brightest registered directional light, or null if none are registered
550
547
  * @see lights
@@ -559,12 +556,13 @@ export class Context implements IContext {
559
556
  }
560
557
 
561
558
  /** @deprecated Use sceneLighting */
562
- get rendererData() { return this.sceneLighting }
559
+ private get rendererData() { return this.sceneLighting }
560
+
563
561
  /** Access the scene lighting manager to control lighting settings in the context */
564
- sceneLighting: SceneLighting;
565
- addressables: Addressables;
566
- lightmaps: ILightDataRegistry;
567
- players: PlayerViewManager;
562
+ readonly sceneLighting: SceneLighting;
563
+ readonly addressables: Addressables;
564
+ readonly lightmaps: ILightDataRegistry;
565
+ readonly players: PlayerViewManager;
568
566
 
569
567
  /** Access the LODs manager to control LOD behavior in the context */
570
568
  readonly lodsManager: LODsManager;
@@ -872,7 +870,7 @@ export class Context implements IContext {
872
870
  this._onBeforeRenderListeners.clear();
873
871
  this._onAfterRenderListeners.clear();
874
872
 
875
- this.lights.clear();
873
+ this.lights.length = 0;
876
874
 
877
875
  if (!this.isManagedExternally) {
878
876
  if (this.renderer) {
@@ -1,10 +1,14 @@
1
+ import { initAnimatorControllerSerializer } from "../engine-components/AnimatorController.js";
1
2
  import { initAnimationAutoplay } from "../engine-components/AnimationUtilsAutoplay.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";
7
+ import { initAddressableSerializers } from "./engine_addressables.js";
5
8
  import { initBuiltinTypes } from "./codegen/register_types.js";
6
9
  import { ensureAudioContextIsResumed } from "./engine_audio.js";
7
10
  import { initNeedleLoader } from "./engine_loaders.js";
11
+ import { initBuiltinSerializers } from "./engine_serialization_builtin_serializer.js";
8
12
  import { initShims } from "./engine_shims.js";
9
13
  import { patchTonemapping } from "./engine_tonemapping.js";
10
14
  import { initCameraExtensions } from "./js-extensions/Camera.js";
@@ -15,6 +19,7 @@ import { SSR } from "./engine_ssr.js";
15
19
  import { initLicense } from "./engine_license.js";
16
20
  import { initWebComponents } from "./webcomponents/init.js";
17
21
  import { initPhysics } from "./engine_physics_rapier.js";
22
+ import { initSpatialConsole } from "./debug/debug_spatial_console.js";
18
23
  import { initXR } from "./xr/init.js";
19
24
 
20
25
  let initialized = false;
@@ -35,6 +40,10 @@ export function initEngine() {
35
40
 
36
41
  initWebComponents();
37
42
  initShims();
43
+ initBuiltinSerializers();
44
+ initAddressableSerializers();
45
+ initAnimatorControllerSerializer();
46
+ initVolumeParameterSerializer();
38
47
  patchTonemapping();
39
48
  patchLayers();
40
49
  initCameraExtensions();
@@ -49,5 +58,6 @@ export function initEngine() {
49
58
  initSceneSwitcherAttributes();
50
59
  initPhysics();
51
60
  initXR();
61
+ initSpatialConsole();
52
62
  initLicense();
53
63
  }
@@ -7,15 +7,13 @@ import { isDevEnvironment } from "./debug/index.js";
7
7
  import { ContextRegistry } from "./engine_context_registry.js";
8
8
 
9
9
  /**
10
- * Global proxy for the primary Needle Engine context.
11
- * Resolves lazily on property access via `ContextRegistry.Current` —
12
- * safe to import at module level, including in SSR environments
13
- * (returns a silent error proxy when no context is active).
10
+ * Quick access to the current Needle Engine context from anywhere — no need to pass `ctx` around.
11
+ * Use it in React/Svelte/Vue components, button handlers, or plain JavaScript.
14
12
  *
15
- * Use this outside of Needle component lifecycle (e.g. in Svelte/React/Vue
16
- * components, button handlers, or vanilla JS) instead of threading `ctx` around.
13
+ * Safe to import at module level, including in SSR environments.
14
+ * For pages with multiple `<needle-engine>` elements, use `ctx` directly instead.
17
15
  *
18
- * For multiple `<needle-engine>` elements on the same page, use `ctx` directly.
16
+ * @experimental This API may change in future releases.
19
17
  *
20
18
  * @example
21
19
  * import { needle } from "@needle-tools/engine";
@@ -29,7 +27,7 @@ export const needle: IContext = new Proxy({} as IContext, {
29
27
  const ctx = ContextRegistry.Current;
30
28
  if (!ctx) {
31
29
  const fn = isDevEnvironment() ? console.error : console.warn;
32
- fn(`[needle] needle.${prop} accessed before scene started`);
30
+ fn(`[needle] needle.${prop} was accessed before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`);
33
31
  return makeErrorProxy(`needle not ready — scene hasn't started yet`);
34
32
  }
35
33
  const val = (ctx as any)[prop];
@@ -39,7 +37,7 @@ export const needle: IContext = new Proxy({} as IContext, {
39
37
  const ctx = ContextRegistry.Current;
40
38
  if (!ctx) {
41
39
  const fn = isDevEnvironment() ? console.error : console.warn;
42
- fn(`[needle] needle.${prop} set before scene started`);
40
+ fn(`[needle] needle.${prop} was set before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`);
43
41
  return true;
44
42
  }
45
43
  (ctx as any)[prop] = value;
@@ -91,9 +89,14 @@ function makeNodeProxy(ctx: IContext, node: import("three").Object3D): object {
91
89
  }
92
90
  });
93
91
  }
92
+ if (prop === "then") return undefined; // not a Promise
94
93
  // Child node lookup by name
95
94
  const child = node.children.find(c => c.name === prop) ?? null;
96
- if (!child) return makeErrorProxy(`Child "${prop}" not found on node "${node.name}"`);
95
+ if (!child) {
96
+ const fn = isDevEnvironment() ? console.error : console.warn;
97
+ fn(`[SceneData] "${prop}" is not a child of "${node.name}". Use .$object to get the Three.js object or .$components.Name to access a component.`);
98
+ return makeErrorProxy(`"${prop}" not found on node "${node.name}"`);
99
+ }
97
100
  return makeNodeProxy(ctx, child);
98
101
  }
99
102
  });
@@ -11,24 +11,6 @@ import { IComponent } from "./engine_types.js";
11
11
  import { resolveUrl } from "./engine_utils.js";
12
12
  import { RGBAColor } from "./js-extensions/index.js";
13
13
 
14
- // export class SourcePath {
15
- // src?:string
16
- // };
17
-
18
- // class SourcePathSerializer extends TypeSerializer{
19
- // constructor(){
20
- // super(SourcePath);
21
- // }
22
- // onDeserialize(data: any, _context: SerializationContext) {
23
- // if(data.src && typeof data.src === "string"){
24
- // return data.src;
25
- // }
26
- // }
27
- // onSerialize(_data: any, _context: SerializationContext) {
28
-
29
- // }
30
- // }
31
- // new SourcePathSerializer();
32
14
 
33
15
  class ColorSerializer extends TypeSerializer {
34
16
  constructor() {
@@ -52,7 +34,6 @@ class ColorSerializer extends TypeSerializer {
52
34
  return { r: data.r, g: data.g, b: data.b }
53
35
  }
54
36
  }
55
- export const colorSerializer = new ColorSerializer();
56
37
 
57
38
  class EulerSerializer extends TypeSerializer {
58
39
  constructor() {
@@ -72,7 +53,6 @@ class EulerSerializer extends TypeSerializer {
72
53
  return { x: data.x, y: data.y, z: data.z, order: data.order };
73
54
  }
74
55
  }
75
- export const euler = new EulerSerializer();
76
56
 
77
57
  declare type ObjectData = {
78
58
  node?: number;
@@ -107,16 +87,6 @@ class ObjectSerializer extends TypeSerializer {
107
87
  console.warn(`Wrong usage of @serializable detected in your script \"${scriptname}\"\n\nIt looks like you used @serializable(Object3D) or @serializable(GameObject) for a prefab or scene reference which is exported to a separate glTF file.\n\nTo fix this please change your code to:\n\n@serializable(AssetReference)\n${context.path}! : AssetReference;\n\0`);
108
88
  }
109
89
  // ACTUALLY: this is already handled by the extension_utils where we resolve json pointers recursively
110
- // if(data.startsWith("/nodes/")){
111
- // const node = parseInt(data.substring("/nodes/".length));
112
- // if (context.nodeToObject) {
113
- // const res = context.nodeToObject[node];
114
- // if (debugExtension)
115
- // console.log("Deserialized object reference?", data, res, context?.nodeToObject);
116
- // if (!res) console.warn("Did not find node: " + data, context.nodeToObject, context.object);
117
- // return res;
118
- // }
119
- // }
120
90
  return undefined;
121
91
  }
122
92
 
@@ -166,7 +136,6 @@ class ObjectSerializer extends TypeSerializer {
166
136
  return undefined;
167
137
  }
168
138
  }
169
- export const objectSerializer = new ObjectSerializer();
170
139
 
171
140
 
172
141
  class ComponentSerializer extends TypeSerializer {
@@ -241,7 +210,6 @@ class ComponentSerializer extends TypeSerializer {
241
210
  }
242
211
  }
243
212
  }
244
- export const componentSerializer = new ComponentSerializer();
245
213
 
246
214
 
247
215
  declare class EventListData {
@@ -396,21 +364,12 @@ class EventListSerializer extends TypeSerializer {
396
364
  // };
397
365
  // }
398
366
  }
399
- export const eventListSerializer = new EventListSerializer();
400
367
 
401
368
 
402
369
  /** Map<Clone, Original> texture. This is used for compressed textures (or when the GLTFLoader is cloning RenderTextures)
403
370
  * It's a weak map so we don't have to worry about memory leaks
404
371
  */
405
372
  const cloneOriginalMap = new WeakMap<Texture, Texture>();
406
- const textureClone = Texture.prototype.clone;
407
- Texture.prototype.clone = function () {
408
- const clone = textureClone.call(this);
409
- if (!cloneOriginalMap.has(clone)) {
410
- cloneOriginalMap.set(clone, this);
411
- }
412
- return clone;
413
- }
414
373
 
415
374
  export class RenderTextureSerializer extends TypeSerializer {
416
375
  constructor() {
@@ -452,7 +411,6 @@ export class RenderTextureSerializer extends TypeSerializer {
452
411
  return undefined;
453
412
  }
454
413
  }
455
- new RenderTextureSerializer();
456
414
 
457
415
 
458
416
  export class UriSerializer extends TypeSerializer {
@@ -471,4 +429,35 @@ export class UriSerializer extends TypeSerializer {
471
429
  return undefined;
472
430
  }
473
431
  }
474
- new UriSerializer();
432
+
433
+
434
+ // Module-level references used by EventListSerializer internally
435
+ export let colorSerializer: ColorSerializer;
436
+ export let objectSerializer: ObjectSerializer;
437
+ export let componentSerializer: ComponentSerializer;
438
+ export let eventListSerializer: EventListSerializer;
439
+
440
+ /** Register all builtin serializers and prototype patches.
441
+ * Must be called from {@link initEngine} so the registrations survive tree-shaking
442
+ * when the package declares `sideEffects: false`.
443
+ */
444
+ export function initBuiltinSerializers() {
445
+ // Prototype patches
446
+ const textureClone = Texture.prototype.clone;
447
+ Texture.prototype.clone = function () {
448
+ const clone = textureClone.call(this);
449
+ if (!cloneOriginalMap.has(clone)) {
450
+ cloneOriginalMap.set(clone, this);
451
+ }
452
+ return clone;
453
+ }
454
+
455
+ // Register all serializers
456
+ colorSerializer = new ColorSerializer();
457
+ new EulerSerializer();
458
+ objectSerializer = new ObjectSerializer();
459
+ componentSerializer = new ComponentSerializer();
460
+ eventListSerializer = new EventListSerializer();
461
+ new RenderTextureSerializer();
462
+ new UriSerializer();
463
+ }
@@ -3,8 +3,15 @@
3
3
  *
4
4
  * Covers both the global JSX namespace (Preact, SolidJS, Svelte, vanilla TS)
5
5
  * and React's module-scoped JSX namespace (react-jsx / react-jsxdev transform).
6
+ *
7
+ * IMPORTANT: This file must be a module (has a top-level import/export) so that
8
+ * `declare module "react"` is treated as a *module augmentation* (merging into
9
+ * the existing React types) rather than an *ambient module declaration* (which
10
+ * would shadow/replace @types/react).
6
11
  */
7
12
 
13
+ import type { NeedleEngineAttributes } from "./needle-engine.js";
14
+
8
15
  interface NeedleButtonJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
9
16
  style?: Partial<CSSStyleDeclaration>;
10
17
  ar?: boolean | string;
@@ -23,7 +30,7 @@ interface NeedleLogoElementJSXAttributes extends Partial<Omit<HTMLElement, "styl
23
30
  }
24
31
 
25
32
  interface NeedleElements {
26
- "needle-engine": import("./needle-engine.js").NeedleEngineAttributes;
33
+ "needle-engine": Partial<NeedleEngineAttributes>;
27
34
  "needle-button": NeedleButtonJSXAttributes;
28
35
  "needle-menu": NeedleMenuJSXAttributes;
29
36
  "needle-logo-element": NeedleLogoElementJSXAttributes;
@@ -36,7 +43,7 @@ declare global {
36
43
  }
37
44
  }
38
45
 
39
- // React module-scoped JSX namespace
46
+ // React module-scoped JSX namespace (module augmentation, not ambient declaration)
40
47
  declare module "react" {
41
48
  namespace JSX {
42
49
  interface IntrinsicElements extends NeedleElements {}
@@ -1586,4 +1586,7 @@ class AnimatorControllerSerializator extends TypeSerializer {
1586
1586
  return undefined;
1587
1587
  }
1588
1588
  }
1589
- new AnimatorControllerSerializator(AnimatorController);
1589
+ /** @internal */
1590
+ export function initAnimatorControllerSerializer() {
1591
+ new AnimatorControllerSerializator(AnimatorController);
1592
+ }
@@ -401,12 +401,13 @@ export class Light extends Behaviour implements ILight {
401
401
  else if (this.light.parent !== this.gameObject)
402
402
  this.gameObject.add(this.light);
403
403
  }
404
- this.context.lights.add(this);
404
+ this.context.lights.push(this);
405
405
  }
406
406
 
407
407
  onDisable() {
408
408
  if (debug) console.log("DISABLE LIGHT", this.name);
409
- this.context.lights.delete(this);
409
+ const index = this.context.lights.indexOf(this);
410
+ if (index !== -1) this.context.lights.splice(index, 1);
410
411
  if (this.light) {
411
412
  if (this.selfIsLight)
412
413
  this.light.intensity = 0;
@@ -290,6 +290,8 @@ export class ReflectionProbe extends Behaviour {
290
290
  const current = block.getOverride("envMap")?.value;
291
291
  if (current === this.texture) {
292
292
  block.removeOveride("envMap");
293
+ block.removeOveride("envMapRotation");
294
+ block.removeOveride("envMapIntensity");
293
295
  }
294
296
  }
295
297
  }
@@ -156,4 +156,7 @@ class VolumeParameterSerializer extends TypeSerializer {
156
156
  return parameter;
157
157
  }
158
158
  }
159
- new VolumeParameterSerializer();
159
+ /** @internal */
160
+ export function initVolumeParameterSerializer() {
161
+ new VolumeParameterSerializer();
162
+ }