@needle-tools/engine 5.1.0-alpha.1 → 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 (162) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +18 -0
  3. package/components.needle.json +1 -1
  4. package/dist/{gltf-progressive-DJBMx-zB.umd.cjs → gltf-progressive-BmblPzFj.umd.cjs} +4 -4
  5. package/dist/{gltf-progressive-BryRjllq.min.js → gltf-progressive-CN_mbb66.min.js} +2 -2
  6. package/dist/{gltf-progressive-Cl167Vjx.js → gltf-progressive-DUlhxdv4.js} +5 -2
  7. package/dist/{needle-engine.bundle-CiYtOO2O.min.js → needle-engine.bundle-B-5Q2CpC.min.js} +163 -163
  8. package/dist/{needle-engine.bundle-BGyKqxBH.js → needle-engine.bundle-dit3f1l5.js} +7501 -7307
  9. package/dist/{needle-engine.bundle-DzVx9Z8D.umd.cjs → needle-engine.bundle-qZfVf_v-.umd.cjs} +163 -163
  10. package/dist/needle-engine.d.ts +119 -27
  11. package/dist/needle-engine.js +172 -170
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/dist/{postprocessing-B_9sKVU7.min.js → postprocessing-B571qGWR.min.js} +34 -34
  15. package/dist/{postprocessing-WDc9WwI3.js → postprocessing-CfrLAbLX.js} +0 -1
  16. package/dist/{postprocessing-B2wb6pzI.umd.cjs → postprocessing-CiGkAeM9.umd.cjs} +17 -17
  17. package/dist/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
  18. package/dist/vendor-CJmyOrCq.min.js +1116 -0
  19. package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
  20. package/lib/engine/api.d.ts +12 -0
  21. package/lib/engine/api.js +2 -0
  22. package/lib/engine/api.js.map +1 -1
  23. package/lib/engine/debug/debug_environment.js +1 -1
  24. package/lib/engine/debug/debug_environment.js.map +1 -1
  25. package/lib/engine/engine_application.js +8 -6
  26. package/lib/engine/engine_application.js.map +1 -1
  27. package/lib/engine/engine_constants.js +6 -0
  28. package/lib/engine/engine_constants.js.map +1 -1
  29. package/lib/engine/engine_context.d.ts +33 -7
  30. package/lib/engine/engine_context.js +40 -2
  31. package/lib/engine/engine_context.js.map +1 -1
  32. package/lib/engine/engine_context_registry.js +1 -1
  33. package/lib/engine/engine_context_registry.js.map +1 -1
  34. package/lib/engine/engine_init.js +5 -0
  35. package/lib/engine/engine_init.js.map +1 -1
  36. package/lib/engine/engine_input.d.ts +3 -2
  37. package/lib/engine/engine_input.js +3 -2
  38. package/lib/engine/engine_input.js.map +1 -1
  39. package/lib/engine/engine_license.d.ts +2 -0
  40. package/lib/engine/engine_license.js +25 -15
  41. package/lib/engine/engine_license.js.map +1 -1
  42. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  43. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  44. package/lib/engine/engine_networking_blob.d.ts +1 -1
  45. package/lib/engine/engine_networking_blob.js +5 -11
  46. package/lib/engine/engine_networking_blob.js.map +1 -1
  47. package/lib/engine/engine_physics_rapier.js +0 -1
  48. package/lib/engine/engine_physics_rapier.js.map +1 -1
  49. package/lib/engine/engine_pmrem.js +2 -2
  50. package/lib/engine/engine_pmrem.js.map +1 -1
  51. package/lib/engine/engine_scenedata.d.ts +30 -0
  52. package/lib/engine/engine_scenedata.js +136 -0
  53. package/lib/engine/engine_scenedata.js.map +1 -0
  54. package/lib/engine/engine_ssr.d.ts +18 -0
  55. package/lib/engine/engine_ssr.js +40 -0
  56. package/lib/engine/engine_ssr.js.map +1 -0
  57. package/lib/engine/engine_three_utils.d.ts +14 -7
  58. package/lib/engine/engine_three_utils.js +14 -7
  59. package/lib/engine/engine_three_utils.js.map +1 -1
  60. package/lib/engine/engine_types.d.ts +2 -0
  61. package/lib/engine/engine_types.js.map +1 -1
  62. package/lib/engine/engine_utils.js +2 -0
  63. package/lib/engine/engine_utils.js.map +1 -1
  64. package/lib/engine/engine_utils_hash.d.ts +9 -0
  65. package/lib/engine/engine_utils_hash.js +112 -0
  66. package/lib/engine/engine_utils_hash.js.map +1 -0
  67. package/lib/engine/webcomponents/jsx.d.ts +51 -0
  68. package/lib/engine/webcomponents/logo-element.d.ts +2 -1
  69. package/lib/engine/webcomponents/logo-element.js +2 -1
  70. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  71. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -4
  72. package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
  73. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  74. package/lib/engine/webcomponents/needle-button.d.ts +2 -1
  75. package/lib/engine/webcomponents/needle-button.js +2 -1
  76. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  77. package/lib/engine/webcomponents/needle-engine.d.ts +2 -1
  78. package/lib/engine/webcomponents/needle-engine.js +2 -1
  79. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  80. package/lib/engine/xr/NeedleXRSession.js +1 -1
  81. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  82. package/lib/engine-components/Light.d.ts +6 -8
  83. package/lib/engine-components/Light.js +40 -27
  84. package/lib/engine-components/Light.js.map +1 -1
  85. package/lib/engine-components/RigidBody.js +3 -3
  86. package/lib/engine-components/RigidBody.js.map +1 -1
  87. package/lib/engine-components/SceneSwitcher.js +2 -0
  88. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  89. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  90. package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
  91. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  92. package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
  93. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  94. package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
  95. package/lib/engine-components/web/ScrollFollow.js +3 -2
  96. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  97. package/lib/needle-engine.d.ts +2 -0
  98. package/lib/needle-engine.js +2 -0
  99. package/lib/needle-engine.js.map +1 -1
  100. package/package.json +6 -4
  101. package/plugins/dts-generator/dts.codegen.js +334 -0
  102. package/plugins/dts-generator/dts.scan.js +99 -0
  103. package/plugins/dts-generator/dts.writer.js +59 -0
  104. package/plugins/dts-generator/glb.discovery.js +279 -0
  105. package/plugins/dts-generator/glb.extractor.js +215 -0
  106. package/plugins/dts-generator/glb.reader.js +167 -0
  107. package/plugins/dts-generator/index.js +36 -0
  108. package/plugins/dts-generator/manifest.types.js +174 -0
  109. package/plugins/types/index.d.ts +2 -1
  110. package/plugins/types/needle-bindings.d.ts +30 -0
  111. package/plugins/types/userconfig.d.ts +21 -2
  112. package/plugins/vite/asap.js +1 -1
  113. package/plugins/vite/dependency-watcher.d.ts +2 -2
  114. package/plugins/vite/dependency-watcher.js +3 -4
  115. package/plugins/vite/drop.d.ts +2 -2
  116. package/plugins/vite/drop.js +3 -4
  117. package/plugins/vite/dts-generator.d.ts +7 -0
  118. package/plugins/vite/dts-generator.js +191 -0
  119. package/plugins/vite/index.d.ts +10 -3
  120. package/plugins/vite/index.js +27 -10
  121. package/plugins/vite/logging.js +2 -2
  122. package/plugins/vite/meta.js +4 -2
  123. package/plugins/vite/poster.d.ts +2 -2
  124. package/plugins/vite/poster.js +3 -5
  125. package/plugins/vite/reload.d.ts +2 -2
  126. package/plugins/vite/reload.js +23 -22
  127. package/src/engine/api.ts +15 -1
  128. package/src/engine/debug/debug_environment.ts +1 -1
  129. package/src/engine/engine_application.ts +8 -6
  130. package/src/engine/engine_constants.ts +11 -6
  131. package/src/engine/engine_context.ts +50 -7
  132. package/src/engine/engine_context_registry.ts +1 -1
  133. package/src/engine/engine_init.ts +4 -0
  134. package/src/engine/engine_input.ts +3 -2
  135. package/src/engine/engine_license.ts +23 -19
  136. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  137. package/src/engine/engine_networking_blob.ts +5 -11
  138. package/src/engine/engine_physics_rapier.ts +0 -3
  139. package/src/engine/engine_pmrem.ts +3 -3
  140. package/src/engine/engine_scenedata.ts +134 -0
  141. package/src/engine/engine_ssr.ts +48 -0
  142. package/src/engine/engine_three_utils.ts +15 -7
  143. package/src/engine/engine_types.ts +2 -0
  144. package/src/engine/engine_utils.ts +1 -0
  145. package/src/engine/engine_utils_hash.ts +65 -0
  146. package/src/engine/webcomponents/jsx.d.ts +51 -0
  147. package/src/engine/webcomponents/logo-element.ts +3 -1
  148. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -2
  149. package/src/engine/webcomponents/needle-button.ts +3 -1
  150. package/src/engine/webcomponents/needle-engine.ts +3 -1
  151. package/src/engine/xr/NeedleXRSession.ts +1 -1
  152. package/src/engine-components/Light.ts +40 -26
  153. package/src/engine-components/RigidBody.ts +3 -3
  154. package/src/engine-components/SceneSwitcher.ts +1 -0
  155. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
  156. package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
  157. package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
  158. package/src/engine-components/web/ScrollFollow.ts +2 -2
  159. package/src/needle-engine.ts +3 -0
  160. package/src/vite-env.d.ts +16 -0
  161. package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
  162. package/dist/vendor-HRlxIBga.min.js +0 -1116
@@ -0,0 +1,134 @@
1
+ import type { SceneData } from "needle-bindings";
2
+ export type { SceneData };
3
+ import type { IContext } from "./engine_types.js";
4
+ import { getComponent } from "./engine_components.js";
5
+ import { TypeStore } from "./engine_typestore.js";
6
+ import { isDevEnvironment } from "./debug/index.js";
7
+ import { ContextRegistry } from "./engine_context_registry.js";
8
+
9
+ /**
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.
12
+ *
13
+ * Safe to import at module level, including in SSR environments.
14
+ * For pages with multiple `<needle-engine>` elements, use `ctx` directly instead.
15
+ *
16
+ * @example
17
+ * import { needle } from "@needle-tools/engine";
18
+ * button.onclick = () => {
19
+ * needle.sceneData.Camera.OrbitControls.autoRotate = true;
20
+ * };
21
+ */
22
+ export const needle: IContext = new Proxy({} as IContext, {
23
+ get(_target, prop: string) {
24
+ if (prop === "then") return undefined; // not a Promise
25
+ const ctx = ContextRegistry.Current;
26
+ if (!ctx) {
27
+ const fn = isDevEnvironment() ? console.error : console.warn;
28
+ 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 => { ... })`);
29
+ return makeErrorProxy(`needle not ready — scene hasn't started yet`);
30
+ }
31
+ const val = (ctx as any)[prop];
32
+ return typeof val === "function" ? val.bind(ctx) : val;
33
+ },
34
+ set(_target, prop: string, value: unknown) {
35
+ const ctx = ContextRegistry.Current;
36
+ if (!ctx) {
37
+ const fn = isDevEnvironment() ? console.error : console.warn;
38
+ 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 => { ... })`);
39
+ return true;
40
+ }
41
+ (ctx as any)[prop] = value;
42
+ return true;
43
+ },
44
+ });
45
+
46
+ const cache = new WeakMap<IContext, SceneData>();
47
+
48
+ /**
49
+ * Returns a proxy that silently absorbs any property get/set and logs a
50
+ * developer-friendly warning. Used when a node or component lookup fails so
51
+ * that chained access like `ctx.sceneData.Foo.Bar.baz = 1` never throws.
52
+ */
53
+ function makeErrorProxy(message: string): object {
54
+ const handler: ProxyHandler<object> = {
55
+ get(_t, prop: string) {
56
+ if (prop === "then") return undefined; // not a Promise
57
+ const fn = isDevEnvironment() ? console.error : console.warn;
58
+ fn(`[SceneData] ${message}`);
59
+ return makeErrorProxy(message);
60
+ },
61
+ set(_t, prop: string) {
62
+ const fn = isDevEnvironment() ? console.error : console.warn;
63
+ fn(`[SceneData] ${message} (tried to set "${prop}")`);
64
+ return true; // suppress TypeError
65
+ },
66
+ };
67
+ return new Proxy({}, handler);
68
+ }
69
+
70
+ /**
71
+ * Returns a proxy for a scene node that exposes `$object`, `$components`,
72
+ * and child nodes as nested properties.
73
+ */
74
+ function makeNodeProxy(ctx: IContext, node: import("three").Object3D): object {
75
+ return new Proxy({}, {
76
+ get(_t, prop: string) {
77
+ if (prop === "$object") return node;
78
+ if (prop === "$components") {
79
+ return new Proxy({}, {
80
+ get(_t2, compName: string) {
81
+ if (compName === "then") return undefined;
82
+ const ctor = TypeStore.get(compName);
83
+ if (!ctor) return makeErrorProxy(`Component type "${compName}" not registered (node "${node.name}")`);
84
+ const comp = getComponent(node, ctor);
85
+ if (!comp) return makeErrorProxy(`Component "${compName}" not found on node "${node.name}"`);
86
+ return comp;
87
+ }
88
+ });
89
+ }
90
+ if (prop === "then") return undefined; // not a Promise
91
+ // Child node lookup by name
92
+ const child = node.children.find(c => c.name === prop) ?? null;
93
+ if (!child) {
94
+ const fn = isDevEnvironment() ? console.error : console.warn;
95
+ 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.`);
96
+ return makeErrorProxy(`"${prop}" not found on node "${node.name}"`);
97
+ }
98
+ return makeNodeProxy(ctx, child);
99
+ }
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Returns a lazily-resolved proxy typed as {@link SceneData}.
105
+ * The proxy is cached per context — each context gets exactly one instance.
106
+ *
107
+ * Shape mirrors the generated `needle-bindings.gen.d.ts`:
108
+ * ctx.sceneData.MyGlb.Camera.$components.OrbitControls.autoRotate = true;
109
+ * ctx.sceneData.MyGlb.Camera.$object // → THREE.Camera
110
+ * ctx.sceneData.MyGlb.UI.Button.$components.Button // → Needle Button component
111
+ *
112
+ * GLB name is ignored at runtime (scene is already loaded).
113
+ * Node lookup starts at the scene root.
114
+ */
115
+ export function getSceneData(ctx: IContext): SceneData {
116
+ let proxy = cache.get(ctx);
117
+ if (!proxy) {
118
+ proxy = new Proxy({} as SceneData, {
119
+ get(_target, _glbName: string) {
120
+ // GLB name level — ignored at runtime, return node-name proxy
121
+ return new Proxy({}, {
122
+ get(_target, nodeName: string) {
123
+ if (nodeName === "then") return undefined;
124
+ const node = ctx.scene.getObjectByName(nodeName) ?? null;
125
+ if (!node) return makeErrorProxy(`Node "${nodeName}" not found in scene`);
126
+ return makeNodeProxy(ctx, node);
127
+ }
128
+ });
129
+ }
130
+ });
131
+ cache.set(ctx, proxy);
132
+ }
133
+ return proxy;
134
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SSR-safe base classes for browser globals that are not available in Node/SSR environments.
3
+ *
4
+ * Use these instead of extending browser globals directly so that class definitions
5
+ * do not throw a ReferenceError at module evaluation time in SSR/Node contexts
6
+ * (SvelteKit, Next.js, etc.).
7
+ *
8
+ * In browser environments each constant is the real global; in SSR it falls back
9
+ * to a plain empty class so that `class Foo extends HTMLElementBase` is valid.
10
+ */
11
+
12
+
13
+ /** True when running in an SSR/Node environment (no browser globals). */
14
+ export const SSR: boolean = typeof window === "undefined";
15
+
16
+ /** SSR-safe base class for web components. */
17
+ export const HTMLElementBase: typeof HTMLElement =
18
+ typeof HTMLElement !== "undefined" ? HTMLElement : (class { } as unknown as typeof HTMLElement);
19
+
20
+ /** SSR-safe base class for pointer events. */
21
+ export const PointerEventBase: typeof PointerEvent =
22
+ typeof PointerEvent !== "undefined" ? PointerEvent : (class { } as unknown as typeof PointerEvent);
23
+
24
+ /** SSR-safe base class for keyboard events. */
25
+ export const KeyboardEventBase: typeof KeyboardEvent =
26
+ typeof KeyboardEvent !== "undefined" ? KeyboardEvent : (class { } as unknown as typeof KeyboardEvent);
27
+
28
+
29
+
30
+
31
+ // #region minimal polyfills
32
+ // Three.js FileLoader uses ProgressEvent in fetch stream callbacks.
33
+ // It doesn't exist in Node — install a minimal stub so SSR doesn't crash.
34
+ if (typeof globalThis.ProgressEvent === "undefined") {
35
+ (globalThis as any).ProgressEvent = class ProgressEvent {
36
+ readonly type: string;
37
+ readonly lengthComputable: boolean;
38
+ readonly loaded: number;
39
+ readonly total: number;
40
+ constructor(type: string, init?: { lengthComputable?: boolean; loaded?: number; total?: number }) {
41
+ this.type = type;
42
+ this.lengthComputable = init?.lengthComputable ?? false;
43
+ this.loaded = init?.loaded ?? 0;
44
+ this.total = init?.total ?? 0;
45
+ }
46
+ };
47
+ }
48
+ // #endregion
@@ -493,13 +493,21 @@ void main(){
493
493
  * Utility class to perform various graphics operations like copying textures to canvas
494
494
  */
495
495
  export class Graphics {
496
- private static readonly planeGeometry = new PlaneGeometry(2, 2, 1, 1);
497
- private static readonly renderer: WebGLRenderer = new WebGLRenderer({ antialias: false, alpha: true });
498
- private static readonly perspectiveCam = new PerspectiveCamera();
499
- private static readonly orthographicCam = new OrthographicCamera();
500
- private static readonly scene = new Scene();
501
- private static readonly blitMaterial: BlitMaterial = new BlitMaterial();
502
- private static readonly mesh: Mesh = new Mesh(Graphics.planeGeometry, Graphics.blitMaterial);
496
+ private static _planeGeometry: PlaneGeometry | undefined;
497
+ private static _renderer: WebGLRenderer | undefined;
498
+ private static _perspectiveCam: PerspectiveCamera | undefined;
499
+ private static _orthographicCam: OrthographicCamera | undefined;
500
+ private static _scene: Scene | undefined;
501
+ private static _blitMaterial: BlitMaterial | undefined;
502
+ private static _mesh: Mesh | undefined;
503
+
504
+ private static get planeGeometry() { return this._planeGeometry ??= new PlaneGeometry(2, 2, 1, 1); }
505
+ private static get renderer() { return this._renderer ??= new WebGLRenderer({ antialias: false, alpha: true }); }
506
+ private static get perspectiveCam() { return this._perspectiveCam ??= new PerspectiveCamera(); }
507
+ private static get orthographicCam() { return this._orthographicCam ??= new OrthographicCamera(); }
508
+ private static get scene() { return this._scene ??= new Scene(); }
509
+ private static get blitMaterial() { return this._blitMaterial ??= new BlitMaterial(); }
510
+ private static get mesh() { return this._mesh ??= new Mesh(Graphics.planeGeometry, Graphics.blitMaterial); }
503
511
 
504
512
 
505
513
  /**
@@ -258,6 +258,8 @@ export declare interface ICameraController {
258
258
  export declare interface ILight extends IComponent {
259
259
  intensity: number;
260
260
  color: Color;
261
+ /** The type of light as a lowercase string: `"directional"`, `"point"`, `"spot"` */
262
+ readonly type: "directional" | "point" | "spot";
261
263
  }
262
264
 
263
265
  export declare interface ISharedMaterials {
@@ -597,6 +597,7 @@ export namespace DeviceUtilities {
597
597
  /** @returns `true` for MacOS or Windows devices. `false` for Hololens and other headsets. */
598
598
  export function isDesktop() {
599
599
  if (_isDesktop !== undefined) return _isDesktop;
600
+ if (typeof window === "undefined") return _isDesktop = false;
600
601
  const ua = window.navigator.userAgent;
601
602
  const standalone = /Windows|MacOS|Mac OS/.test(ua);
602
603
  const isHololens = /Windows NT/.test(ua) && /Edg/.test(ua) && !/Win64/.test(ua);
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure-JS hashing utilities — no external dependencies, works in browser and Node/SSR.
3
+ */
4
+
5
+ // MD5 based on the public-domain RSA Data Security MD5 algorithm.
6
+ function md5Bytes(input: Uint8Array): number[] {
7
+ function safeAdd(x: number, y: number) { const lsw = (x & 0xffff) + (y & 0xffff); return ((x >> 16) + (y >> 16) + (lsw >> 16)) << 16 | lsw & 0xffff; }
8
+ function bitRotateLeft(num: number, cnt: number) { return num << cnt | num >>> 32 - cnt; }
9
+ function md5cmn(q: number, a: number, b: number, x: number, s: number, t: number) { return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); }
10
+ function md5ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b & c | ~b & d, a, b, x, s, t); }
11
+ function md5gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b & d | c & ~d, a, b, x, s, t); }
12
+ function md5hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b ^ c ^ d, a, b, x, s, t); }
13
+ function md5ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(c ^ (b | ~d), a, b, x, s, t); }
14
+
15
+ const len8 = input.length;
16
+ const padded = new Uint8Array((Math.ceil((len8 + 9) / 64) * 64));
17
+ padded.set(input);
18
+ padded[len8] = 0x80;
19
+ const view = new DataView(padded.buffer);
20
+ view.setUint32(padded.length - 8, len8 << 3, true);
21
+ view.setUint32(padded.length - 4, (len8 / 0x20000000) | 0, true);
22
+
23
+ let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476;
24
+ for (let i = 0; i < padded.length >> 2; i += 16) {
25
+ const M = (j: number) => view.getInt32((i + j) * 4, true);
26
+ const [oa, ob, oc, od] = [a, b, c, d];
27
+ a = md5ff(a,b,c,d,M(0),7,-680876936); d=md5ff(d,a,b,c,M(1),12,-389564586); c=md5ff(c,d,a,b,M(2),17,606105819); b=md5ff(b,c,d,a,M(3),22,-1044525330);
28
+ a=md5ff(a,b,c,d,M(4),7,-176418897); d=md5ff(d,a,b,c,M(5),12,1200080426); c=md5ff(c,d,a,b,M(6),17,-1473231341); b=md5ff(b,c,d,a,M(7),22,-45705983);
29
+ a=md5ff(a,b,c,d,M(8),7,1770035416); d=md5ff(d,a,b,c,M(9),12,-1958414417); c=md5ff(c,d,a,b,M(10),17,-42063); b=md5ff(b,c,d,a,M(11),22,-1990404162);
30
+ a=md5ff(a,b,c,d,M(12),7,1804603682); d=md5ff(d,a,b,c,M(13),12,-40341101); c=md5ff(c,d,a,b,M(14),17,-1502002290); b=md5ff(b,c,d,a,M(15),22,1236535329);
31
+ a=md5gg(a,b,c,d,M(1),5,-165796510); d=md5gg(d,a,b,c,M(6),9,-1069501632); c=md5gg(c,d,a,b,M(11),14,643717713); b=md5gg(b,c,d,a,M(0),20,-373897302);
32
+ a=md5gg(a,b,c,d,M(5),5,-701558691); d=md5gg(d,a,b,c,M(10),9,38016083); c=md5gg(c,d,a,b,M(15),14,-660478335); b=md5gg(b,c,d,a,M(4),20,-405537848);
33
+ a=md5gg(a,b,c,d,M(9),5,568446438); d=md5gg(d,a,b,c,M(14),9,-1019803690); c=md5gg(c,d,a,b,M(3),14,-187363961); b=md5gg(b,c,d,a,M(8),20,1163531501);
34
+ a=md5gg(a,b,c,d,M(13),5,-1444681467); d=md5gg(d,a,b,c,M(2),9,-51403784); c=md5gg(c,d,a,b,M(7),14,1735328473); b=md5gg(b,c,d,a,M(12),20,-1926607734);
35
+ a=md5hh(a,b,c,d,M(5),4,-378558); d=md5hh(d,a,b,c,M(8),11,-2022574463); c=md5hh(c,d,a,b,M(11),16,1839030562); b=md5hh(b,c,d,a,M(14),23,-35309556);
36
+ a=md5hh(a,b,c,d,M(1),4,-1530992060); d=md5hh(d,a,b,c,M(4),11,1272893353); c=md5hh(c,d,a,b,M(7),16,-155497632); b=md5hh(b,c,d,a,M(10),23,-1094730640);
37
+ a=md5hh(a,b,c,d,M(13),4,681279174); d=md5hh(d,a,b,c,M(0),11,-358537222); c=md5hh(c,d,a,b,M(3),16,-722521979); b=md5hh(b,c,d,a,M(6),23,76029189);
38
+ a=md5hh(a,b,c,d,M(9),4,-640364487); d=md5hh(d,a,b,c,M(12),11,-421815835); c=md5hh(c,d,a,b,M(15),16,530742520); b=md5hh(b,c,d,a,M(2),23,-995338651);
39
+ a=md5ii(a,b,c,d,M(0),6,-198630844); d=md5ii(d,a,b,c,M(7),10,1126891415); c=md5ii(c,d,a,b,M(14),15,-1416354905); b=md5ii(b,c,d,a,M(5),21,-57434055);
40
+ a=md5ii(a,b,c,d,M(12),6,1700485571); d=md5ii(d,a,b,c,M(3),10,-1894986606); c=md5ii(c,d,a,b,M(10),15,-1051523); b=md5ii(b,c,d,a,M(1),21,-2054922799);
41
+ a=md5ii(a,b,c,d,M(8),6,1873313359); d=md5ii(d,a,b,c,M(15),10,-30611744); c=md5ii(c,d,a,b,M(6),15,-1560198380); b=md5ii(b,c,d,a,M(13),21,1309151649);
42
+ a=md5ii(a,b,c,d,M(4),6,-145523070); d=md5ii(d,a,b,c,M(11),10,-1120210379); c=md5ii(c,d,a,b,M(2),15,718787259); b=md5ii(b,c,d,a,M(9),21,-343485551);
43
+ a=safeAdd(a,oa); b=safeAdd(b,ob); c=safeAdd(c,oc); d=safeAdd(d,od);
44
+ }
45
+ const result = new DataView(new ArrayBuffer(16));
46
+ result.setUint32(0, a, true); result.setUint32(4, b, true); result.setUint32(8, c, true); result.setUint32(12, d, true);
47
+ return Array.from(new Uint8Array(result.buffer));
48
+ }
49
+
50
+ /** Returns the MD5 hash of the given bytes as a lowercase hex string. */
51
+ export function md5Hex(input: Uint8Array): string {
52
+ return md5Bytes(input).map(b => b.toString(16).padStart(2, "0")).join("");
53
+ }
54
+
55
+ /** Returns the MD5 hash of the given bytes as a raw byte array. */
56
+ export function md5AsBytes(input: Uint8Array): number[] {
57
+ return md5Bytes(input);
58
+ }
59
+
60
+ /** Returns the SHA-256 hash of the given buffer as a base64 string. */
61
+ export function sha256Base64(buffer: ArrayBuffer): Promise<string> {
62
+ return crypto.subtle.digest("SHA-256", new Uint8Array(buffer)).then(res =>
63
+ btoa(String.fromCharCode(...new Uint8Array(res)))
64
+ );
65
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * JSX IntrinsicElements declarations for Needle Engine web components.
3
+ *
4
+ * Covers both the global JSX namespace (Preact, SolidJS, Svelte, vanilla TS)
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).
11
+ */
12
+
13
+ import type { NeedleEngineAttributes } from "./needle-engine.js";
14
+
15
+ interface NeedleButtonJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
16
+ style?: Partial<CSSStyleDeclaration>;
17
+ ar?: boolean | string;
18
+ vr?: boolean | string;
19
+ quicklook?: boolean | string;
20
+ qrcode?: boolean | string;
21
+ unstyled?: boolean | string;
22
+ }
23
+
24
+ interface NeedleMenuJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
25
+ style?: Partial<CSSStyleDeclaration>;
26
+ }
27
+
28
+ interface NeedleLogoElementJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
29
+ style?: Partial<CSSStyleDeclaration>;
30
+ }
31
+
32
+ interface NeedleElements {
33
+ "needle-engine": Partial<NeedleEngineAttributes>;
34
+ "needle-button": NeedleButtonJSXAttributes;
35
+ "needle-menu": NeedleMenuJSXAttributes;
36
+ "needle-logo-element": NeedleLogoElementJSXAttributes;
37
+ }
38
+
39
+ // Global JSX namespace — Preact, SolidJS, Svelte, vanilla TS
40
+ declare global {
41
+ namespace JSX {
42
+ interface IntrinsicElements extends NeedleElements {}
43
+ }
44
+ }
45
+
46
+ // React module-scoped JSX namespace (module augmentation, not ambient declaration)
47
+ declare module "react" {
48
+ namespace JSX {
49
+ interface IntrinsicElements extends NeedleElements {}
50
+ }
51
+ }
@@ -1,7 +1,9 @@
1
1
  import { madeWithNeedleSVG, needleLogoOnlySVG, needleLogoSVG } from "../assets/index.js";
2
+ import { HTMLElementBase } from "../engine_ssr.js";
2
3
 
3
4
  const elementName = "needle-logo-element";
4
5
 
6
+ // NOTE: keep JSX attributes in sync with ./jsx.d.ts
5
7
  declare global {
6
8
  interface HTMLElementTagNameMap {
7
9
  "needle-logo-element": NeedleLogoElement;
@@ -12,7 +14,7 @@ declare global {
12
14
  * Needle logo web component used in the hosting UI (small, compact logo or full)
13
15
  * @element needle-logo-element
14
16
  */
15
- export class NeedleLogoElement extends HTMLElement {
17
+ export class NeedleLogoElement extends HTMLElementBase {
16
18
 
17
19
  static get elementName() { return elementName; }
18
20
 
@@ -1,4 +1,5 @@
1
1
  import { showBalloonMessage } from "../../debug/debug.js";
2
+ import { HTMLElementBase } from "../../engine_ssr.js";
2
3
  import type { Context } from "../../engine_context.js";
3
4
  import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
4
5
  import { isLocalNetwork } from "../../engine_networking_utils.js";
@@ -11,9 +12,10 @@ import { NeedleLogoElement } from "../logo-element.js";
11
12
  import { getElementPriority, setElementPriority as _setElementPriority } from "./menu-priority.js";
12
13
  import { NeedleSpatialMenu } from "./needle-menu-spatial.js";
13
14
 
15
+ // NOTE: keep JSX attributes in sync with ../jsx.d.ts
14
16
  declare global {
15
17
  interface HTMLElementTagNameMap {
16
- "needle-logo-element": NeedleLogoElement;
18
+ "needle-menu": NeedleMenuElement;
17
19
  }
18
20
  }
19
21
 
@@ -303,7 +305,7 @@ export class NeedleMenu {
303
305
  *
304
306
  * @element needle-menu
305
307
  */
306
- export class NeedleMenuElement extends HTMLElement {
308
+ export class NeedleMenuElement extends HTMLElementBase {
307
309
 
308
310
  static create() {
309
311
  // Ensure the element is registered before creating — guards against
@@ -1,8 +1,10 @@
1
1
  import { isDevEnvironment } from "../debug/index.js";
2
+ import { HTMLElementBase } from "../engine_ssr.js";
2
3
  import { ButtonsFactory } from "./buttons.js";
3
4
  import { iconFontUrl, loadFont } from "./fonts.js";
4
5
  import { WebXRButtonFactory } from "./WebXRButtons.js";
5
6
 
7
+ // NOTE: keep JSX attributes in sync with ./jsx.d.ts
6
8
  declare global {
7
9
  interface HTMLElementTagNameMap {
8
10
  "needle-button": NeedleButtonElement;
@@ -65,7 +67,7 @@ const isDev = isDevEnvironment();
65
67
  * @see {@link NeedleEngineWebComponent} for the main &lt;needle-engine&gt; element
66
68
  * @see {@link NeedleMenu} for the built-in menu component that can display similar buttons
67
69
  */
68
- export class NeedleButtonElement extends HTMLElement {
70
+ export class NeedleButtonElement extends HTMLElementBase {
69
71
 
70
72
  static observedAttributes = ["ar", "vr", "quicklook", "qrcode"];
71
73
 
@@ -1,4 +1,5 @@
1
1
  import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
2
+ import { HTMLElementBase } from "../engine_ssr.js";
2
3
  import { PUBLIC_KEY, VERSION } from "../engine_constants.js";
3
4
  import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
4
5
  import { hasCommercialLicense } from "../engine_license.js";
@@ -16,6 +17,7 @@ import { arContainerClassName, AROverlayHandler } from "./needle-engine.ar-overl
16
17
  import type { registerObservableAttribute } from "./needle-engine.extras.js";
17
18
  import { calculateProgress01, EngineLoadingView, type ILoadingViewHandler } from "./needle-engine.loading.js";
18
19
 
20
+ // NOTE: keep JSX attributes in sync with ./jsx.d.ts
19
21
  declare global {
20
22
  interface HTMLElementTagNameMap {
21
23
  "needle-engine": NeedleEngineWebComponent;
@@ -153,7 +155,7 @@ const observedAttributes = [
153
155
  * @see {@link NeedleButtonElement} for adding AR/VR/Quicklook buttons via &lt;needle-button&gt;
154
156
  * @see {@link NeedleMenu} for the built-in menu configuration component
155
157
  */
156
- export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
158
+ export class NeedleEngineWebComponent extends HTMLElementBase implements INeedleEngineComponent {
157
159
 
158
160
  static get observedAttributes() {
159
161
  return observedAttributes;
@@ -207,7 +207,7 @@ function waitForContextLoadingFinished(): Promise<void> {
207
207
  }
208
208
 
209
209
 
210
- if (DeviceUtilities.isDesktop() && isDevEnvironment()) {
210
+ if (typeof window !== "undefined" && DeviceUtilities.isDesktop() && isDevEnvironment()) {
211
211
  window.addEventListener("keydown", (evt) => {
212
212
  if (evt.key === "x" || evt.key === "Escape") {
213
213
  if (NeedleXRSession.active) {
@@ -106,11 +106,40 @@ enum LightShadows {
106
106
  export class Light extends Behaviour implements ILight {
107
107
 
108
108
  /**
109
- * The type of light (spot, directional, point, etc.)
110
- * Can not be changed at runtime.
109
+ * The type of light as a lowercase string: `"directional"`, `"point"`, `"spot"`.
110
+ * Implements {@link ILight.type}. Can not be changed at runtime.
111
111
  */
112
+ get type(): ILight["type"] {
113
+ return this._type;
114
+ }
115
+
116
+ /** Numeric LightType serialized from Unity/Blender — converts to string on write */
112
117
  @serializable()
113
- private type: LightType = 0;
118
+ set type(value: LightType | ILight["type"]) {
119
+ if (this.light && this.__didAwake) {
120
+ throw new Error("Changing the light type at runtime is not supported");
121
+ }
122
+ switch (value) {
123
+ case LightType.Directional:
124
+ this._type = "directional";
125
+ break;
126
+ case LightType.Point:
127
+ this._type = "point";
128
+ break;
129
+ case LightType.Spot:
130
+ this._type = "spot";
131
+ break;
132
+ case "directional":
133
+ case "point":
134
+ case "spot":
135
+ this._type = value;
136
+ break;
137
+ default:
138
+ throw new Error("Invalid light type: " + value);
139
+ }
140
+ }
141
+ private _type: ILight["type"] = "point";
142
+
114
143
 
115
144
  /**
116
145
  * The maximum distance the light affects.
@@ -345,7 +374,7 @@ export class Light extends Behaviour implements ILight {
345
374
  */
346
375
  public getWorldPosition(vec: Vector3): Vector3 {
347
376
  if (this.light) {
348
- if (this.type === LightType.Directional) {
377
+ if (this.type === "directional") {
349
378
  return this.light.getWorldPosition(vec).multiplyScalar(1);
350
379
  }
351
380
  return this.light.getWorldPosition(vec);
@@ -372,12 +401,13 @@ export class Light extends Behaviour implements ILight {
372
401
  else if (this.light.parent !== this.gameObject)
373
402
  this.gameObject.add(this.light);
374
403
  }
375
- if (this.type === LightType.Directional)
376
- this.startCoroutine(this.updateMainLightRoutine(), FrameEvent.LateUpdate);
404
+ this.context.lights.push(this);
377
405
  }
378
406
 
379
407
  onDisable() {
380
408
  if (debug) console.log("DISABLE LIGHT", this.name);
409
+ const index = this.context.lights.indexOf(this);
410
+ if (index !== -1) this.context.lights.splice(index, 1);
381
411
  if (this.light) {
382
412
  if (this.selfIsLight)
383
413
  this.light.intensity = 0;
@@ -399,14 +429,14 @@ export class Light extends Behaviour implements ILight {
399
429
  this._intensity = this.light.intensity;
400
430
 
401
431
  switch (this.type) {
402
- case LightType.Directional:
432
+ case "directional":
403
433
  this.setDirectionalLight(this.light as DirectionalLight);
404
434
  break;
405
435
  }
406
436
  }
407
437
  else if (!this.light) {
408
438
  switch (this.type) {
409
- case LightType.Directional:
439
+ case "directional":
410
440
  // console.log(this);
411
441
  const dirLight = new DirectionalLight(this.color, this.intensity * Math.PI);
412
442
  // directional light target is at 0 0 0 by default
@@ -425,7 +455,7 @@ export class Light extends Behaviour implements ILight {
425
455
  }
426
456
  break;
427
457
 
428
- case LightType.Spot:
458
+ case "spot":
429
459
  const spotLight = new SpotLight(this.color, this.intensity * Math.PI, this.range, toRadians(this.spotAngle / 2), 1 - toRadians(this.innerSpotAngle / 2) / toRadians(this.spotAngle / 2), 2);
430
460
  spotLight.position.set(0, 0, 0);
431
461
  spotLight.rotation.set(0, 0, 0);
@@ -437,7 +467,7 @@ export class Light extends Behaviour implements ILight {
437
467
  spotLightTarget.rotation.set(0, 0, 0);
438
468
  break;
439
469
 
440
- case LightType.Point:
470
+ case "point":
441
471
  const pointLight = new PointLight(this.color, this.intensity * Math.PI, this.range);
442
472
  this.light = pointLight;
443
473
 
@@ -526,22 +556,6 @@ export class Light extends Behaviour implements ILight {
526
556
 
527
557
  }
528
558
 
529
- /**
530
- * Coroutine that updates the main light reference in the context
531
- * if this directional light should be the main light
532
- */
533
- *updateMainLightRoutine() {
534
- while (true) {
535
- if (this.type === LightType.Directional) {
536
- if (!this.context.mainLight || this.intensity > this.context.mainLight.intensity) {
537
- this.context.mainLight = this;
538
- }
539
- yield;
540
- }
541
- break;
542
- }
543
- }
544
-
545
559
  /**
546
560
  * Controls whether the renderer's shadow map type can be changed when soft shadows are used
547
561
  */
@@ -382,12 +382,12 @@ export class Rigidbody extends Behaviour implements IRigidbody {
382
382
  this._watch.start(true, true);
383
383
  this.startCoroutine(this.beforePhysics(), FrameEvent.LateUpdate);
384
384
  if (isDevEnvironment()) {
385
- if (!globalThis["NEEDLE_USE_RAPIER"])
386
- console.warn(`Rigidbody could not be created: Rapier physics are explicitly disabled.`);
385
+ if (globalThis["NEEDLE_USE_RAPIER"] === false)
386
+ console.warn(`RAPIER physics are disabled in your build. Enable them by setting NEEDLE_USE_RAPIER to true in your build config: Rigidbody could not be created.`);
387
387
  else {
388
388
  MODULES.RAPIER_PHYSICS.ready().then(async () => {
389
389
  await delayForFrames(3);
390
- if (!this.context.physics.engine?.getBody(this))
390
+ if (this.activeAndEnabled && !this.context.physics.engine?.getBody(this))
391
391
  console.warn(`Rigidbody could not be created. Ensure \"${this.name}\" has a Collider component.`);
392
392
  })
393
393
  }
@@ -723,6 +723,7 @@ export class SceneSwitcher extends Behaviour {
723
723
  // unless the user defines that he wants to use the scene name
724
724
  if (this.useSceneName) {
725
725
  if (scene instanceof Object3D) queryParameterValue = scene.name;
726
+ else if (scene instanceof AssetReference && scene.asset?.name) queryParameterValue = scene.asset.name;
726
727
  else if (scene.url) queryParameterValue = sceneUriToName(scene.url);
727
728
  }
728
729
  // save the loaded scene as an url parameter
@@ -1,4 +1,4 @@
1
- import { BloomEffect as _BloomEffect, EffectAttribute } from "postprocessing";
1
+ import type { BloomEffect as _BloomEffect } from "postprocessing";
2
2
  import { MathUtils } from "three";
3
3
 
4
4
  import { MODULES } from "../../../engine/engine_modules.js";
@@ -1,4 +1,3 @@
1
- import { EffectAttribute } from "postprocessing";
2
1
  import { Uniform } from "three";
3
2
 
4
3
  import { MODULES } from "../../../engine/engine_modules.js";
@@ -138,7 +137,7 @@ function createSharpeningEffectType() {
138
137
  ["radius", new Uniform(1)],
139
138
  // ["threshold", new Uniform(0)],
140
139
  ]),
141
- attributes: EffectAttribute.CONVOLUTION
140
+ attributes: MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION
142
141
  });
143
142
  }
144
143
  }
@@ -14,10 +14,6 @@ import { threeToneMappingToEffectMode } from "./Effects/Tonemapping.utils.js";
14
14
  import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
15
15
  import { orderEffects, PostprocessingEffectData, PostProcessingEffectOrder } from "./utils.js";
16
16
 
17
- declare const NEEDLE_USE_POSTPROCESSING: boolean;
18
- globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
19
-
20
-
21
17
  const debug = getParam("debugpost");
22
18
 
23
19
  const activeKey = Symbol("needle:postprocessing-handler");
@@ -58,13 +54,13 @@ export class PostProcessingHandler implements IPostProcessingHandler {
58
54
 
59
55
  apply(components: PostProcessingEffect[]): Promise<void> {
60
56
  if ("env" in import.meta && (import.meta as any /* webpack support */ ).env.VITE_NEEDLE_USE_POSTPROCESSING === "false") {
61
- if (debug) console.warn("Postprocessing is disabled via vite env setting");
62
- else console.debug("Postprocessing is disabled via vite env setting");
57
+ if (debug) console.warn("POSTPROCESSING is disabled via vite env setting");
58
+ else console.debug("POSTPROCESSING is disabled via vite env setting");
63
59
  return Promise.resolve();
64
60
  }
65
61
  if (!NEEDLE_USE_POSTPROCESSING) {
66
- if (debug) console.warn("Postprocessing is disabled via global vite define setting");
67
- else console.debug("Postprocessing is disabled via vite define");
62
+ if (debug || isDevEnvironment()) console.warn("POSTPROCESSING is disabled via global vite define setting");
63
+ else console.debug("POSTPROCESSING is disabled via vite define");
68
64
  return Promise.resolve();
69
65
  }
70
66