@needle-tools/engine 5.1.0-alpha → 5.1.0-canary.0d9f44e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.needle/generated/needle-bindings.gen.d.ts +5 -0
- package/CHANGELOG.md +9 -1
- package/SKILL.md +39 -21
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-DJBMx-zB.umd.cjs → gltf-progressive-BmblPzFj.umd.cjs} +4 -4
- package/dist/{gltf-progressive-BryRjllq.min.js → gltf-progressive-CN_mbb66.min.js} +2 -2
- package/dist/{gltf-progressive-Cl167Vjx.js → gltf-progressive-DUlhxdv4.js} +5 -2
- package/dist/{needle-engine.bundle-CwhCzjep.js → needle-engine.bundle-B35n_IHX.js} +13637 -13081
- package/dist/{needle-engine.bundle-wM-BWPX9.umd.cjs → needle-engine.bundle-CDj15wRB.umd.cjs} +250 -174
- package/dist/needle-engine.bundle-D5zzggEG.min.js +1732 -0
- package/dist/needle-engine.d.ts +316 -26
- package/dist/needle-engine.js +569 -563
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-B_9sKVU7.min.js → postprocessing-B571qGWR.min.js} +34 -34
- package/dist/{postprocessing-WDc9WwI3.js → postprocessing-CfrLAbLX.js} +0 -1
- package/dist/{postprocessing-B2wb6pzI.umd.cjs → postprocessing-CiGkAeM9.umd.cjs} +17 -17
- package/dist/three-examples.js +4289 -3778
- package/dist/three-examples.min.js +301 -14
- package/dist/three-examples.umd.cjs +301 -14
- package/dist/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
- package/dist/vendor-CJmyOrCq.min.js +1116 -0
- package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
- package/lib/engine/api.d.ts +12 -0
- package/lib/engine/api.js +2 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug_environment.js +1 -1
- package/lib/engine/debug/debug_environment.js.map +1 -1
- package/lib/engine/engine_application.js +8 -6
- package/lib/engine/engine_application.js.map +1 -1
- package/lib/engine/engine_components.js +5 -1
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_constants.js +6 -0
- package/lib/engine/engine_constants.js.map +1 -1
- package/lib/engine/engine_context.d.ts +31 -2
- package/lib/engine/engine_context.js +43 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_registry.js +1 -1
- package/lib/engine/engine_context_registry.js.map +1 -1
- package/lib/engine/engine_init.js +7 -0
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +3 -2
- package/lib/engine/engine_input.js +3 -2
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_license.d.ts +2 -0
- package/lib/engine/engine_license.js +25 -15
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
- package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
- package/lib/engine/engine_networking_blob.d.ts +1 -1
- package/lib/engine/engine_networking_blob.js +5 -11
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +3 -0
- package/lib/engine/engine_physics_rapier.js +13 -10
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_pmrem.js +2 -2
- package/lib/engine/engine_pmrem.js.map +1 -1
- package/lib/engine/engine_scenedata.d.ts +34 -0
- package/lib/engine/engine_scenedata.js +135 -0
- package/lib/engine/engine_scenedata.js.map +1 -0
- package/lib/engine/engine_ssr.d.ts +18 -0
- package/lib/engine/engine_ssr.js +40 -0
- package/lib/engine/engine_ssr.js.map +1 -0
- package/lib/engine/engine_three_utils.d.ts +14 -7
- package/lib/engine/engine_three_utils.js +14 -7
- package/lib/engine/engine_three_utils.js.map +1 -1
- package/lib/engine/engine_types.d.ts +2 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_utils.js +4 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/engine_utils_hash.d.ts +9 -0
- package/lib/engine/engine_utils_hash.js +112 -0
- package/lib/engine/engine_utils_hash.js.map +1 -0
- package/lib/engine/webcomponents/logo-element.d.ts +10 -1
- package/lib/engine/webcomponents/logo-element.js +2 -1
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +12 -4
- package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.d.ts +15 -1
- package/lib/engine/webcomponents/needle-button.js +2 -1
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +7 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -1
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +1 -0
- package/lib/engine/xr/NeedleXRSession.js +5 -5
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/events.d.ts +30 -3
- package/lib/engine/xr/events.js +38 -0
- package/lib/engine/xr/events.js.map +1 -1
- package/lib/engine/xr/init.js +1 -7
- package/lib/engine/xr/init.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +135 -2
- package/lib/engine-components/AnimatorController.js +218 -2
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/GroundProjection.d.ts +1 -0
- package/lib/engine-components/GroundProjection.js +184 -48
- package/lib/engine-components/GroundProjection.js.map +1 -1
- package/lib/engine-components/Light.d.ts +25 -8
- package/lib/engine-components/Light.js +132 -27
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/RigidBody.js +3 -3
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +2 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
- package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
- package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
- package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
- package/lib/engine-components/web/ScrollFollow.js +3 -2
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/needle-engine.d.ts +1 -0
- package/lib/needle-engine.js +1 -0
- package/lib/needle-engine.js.map +1 -1
- package/package.json +7 -5
- package/plugins/common/logger.js +42 -19
- package/plugins/dts-generator/dts.codegen.js +334 -0
- package/plugins/dts-generator/dts.scan.js +99 -0
- package/plugins/dts-generator/dts.writer.js +59 -0
- package/plugins/dts-generator/glb.discovery.js +279 -0
- package/plugins/dts-generator/glb.extractor.js +215 -0
- package/plugins/dts-generator/glb.reader.js +167 -0
- package/plugins/dts-generator/index.js +36 -0
- package/plugins/dts-generator/manifest.types.js +174 -0
- package/plugins/types/index.d.ts +2 -1
- package/plugins/types/needle-bindings.d.ts +30 -0
- package/plugins/types/userconfig.d.ts +21 -2
- package/plugins/vite/asap.js +1 -1
- package/plugins/vite/dependency-watcher.d.ts +2 -2
- package/plugins/vite/dependency-watcher.js +3 -4
- package/plugins/vite/drop.d.ts +2 -2
- package/plugins/vite/drop.js +3 -4
- package/plugins/vite/dts-generator.d.ts +7 -0
- package/plugins/vite/dts-generator.js +191 -0
- package/plugins/vite/index.d.ts +10 -3
- package/plugins/vite/index.js +27 -10
- package/plugins/vite/logger.client.js +4 -3
- package/plugins/vite/logging.js +2 -2
- package/plugins/vite/meta.js +4 -2
- package/plugins/vite/poster.d.ts +2 -2
- package/plugins/vite/poster.js +3 -5
- package/plugins/vite/reload.d.ts +2 -2
- package/plugins/vite/reload.js +5 -5
- package/src/engine/api.ts +15 -1
- package/src/engine/debug/debug_environment.ts +1 -1
- package/src/engine/engine_application.ts +8 -6
- package/src/engine/engine_components.ts +7 -4
- package/src/engine/engine_constants.ts +11 -6
- package/src/engine/engine_context.ts +47 -2
- package/src/engine/engine_context_registry.ts +1 -1
- package/src/engine/engine_init.ts +6 -0
- package/src/engine/engine_input.ts +3 -2
- package/src/engine/engine_license.ts +23 -19
- package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
- package/src/engine/engine_networking_blob.ts +5 -11
- package/src/engine/engine_physics_rapier.ts +14 -12
- package/src/engine/engine_pmrem.ts +3 -3
- package/src/engine/engine_scenedata.ts +133 -0
- package/src/engine/engine_ssr.ts +48 -0
- package/src/engine/engine_three_utils.ts +15 -7
- package/src/engine/engine_types.ts +2 -0
- package/src/engine/engine_utils.ts +3 -2
- package/src/engine/engine_utils_hash.ts +65 -0
- package/src/engine/webcomponents/logo-element.ts +10 -1
- package/src/engine/webcomponents/needle menu/needle-menu.ts +11 -2
- package/src/engine/webcomponents/needle-button.ts +15 -1
- package/src/engine/webcomponents/needle-engine.ts +8 -1
- package/src/engine/xr/NeedleXRSession.ts +6 -6
- package/src/engine/xr/events.ts +44 -1
- package/src/engine/xr/init.ts +0 -7
- package/src/engine-components/AnimatorController.ts +286 -4
- package/src/engine-components/GroundProjection.ts +226 -52
- package/src/engine-components/Light.ts +132 -27
- package/src/engine-components/RigidBody.ts +3 -3
- package/src/engine-components/SceneSwitcher.ts +1 -0
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
- package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
- package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
- package/src/engine-components/web/ScrollFollow.ts +2 -2
- package/src/needle-engine.ts +2 -0
- package/src/vite-env.d.ts +16 -0
- package/dist/needle-engine.bundle-qDahLTqW.min.js +0 -1656
- package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
- package/dist/vendor-HRlxIBga.min.js +0 -1116
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
* 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).
|
|
14
|
+
*
|
|
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.
|
|
17
|
+
*
|
|
18
|
+
* For multiple `<needle-engine>` elements on the same page, use `ctx` directly.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { needle } from "@needle-tools/engine";
|
|
22
|
+
* button.onclick = () => {
|
|
23
|
+
* needle.sceneData.Camera.OrbitControls.autoRotate = true;
|
|
24
|
+
* };
|
|
25
|
+
*/
|
|
26
|
+
export const needle: IContext = new Proxy({} as IContext, {
|
|
27
|
+
get(_target, prop: string) {
|
|
28
|
+
if (prop === "then") return undefined; // not a Promise
|
|
29
|
+
const ctx = ContextRegistry.Current;
|
|
30
|
+
if (!ctx) {
|
|
31
|
+
const fn = isDevEnvironment() ? console.error : console.warn;
|
|
32
|
+
fn(`[needle] needle.${prop} accessed before scene started`);
|
|
33
|
+
return makeErrorProxy(`needle not ready — scene hasn't started yet`);
|
|
34
|
+
}
|
|
35
|
+
const val = (ctx as any)[prop];
|
|
36
|
+
return typeof val === "function" ? val.bind(ctx) : val;
|
|
37
|
+
},
|
|
38
|
+
set(_target, prop: string, value: unknown) {
|
|
39
|
+
const ctx = ContextRegistry.Current;
|
|
40
|
+
if (!ctx) {
|
|
41
|
+
const fn = isDevEnvironment() ? console.error : console.warn;
|
|
42
|
+
fn(`[needle] needle.${prop} set before scene started`);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
(ctx as any)[prop] = value;
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const cache = new WeakMap<IContext, SceneData>();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns a proxy that silently absorbs any property get/set and logs a
|
|
54
|
+
* developer-friendly warning. Used when a node or component lookup fails so
|
|
55
|
+
* that chained access like `ctx.sceneData.Foo.Bar.baz = 1` never throws.
|
|
56
|
+
*/
|
|
57
|
+
function makeErrorProxy(message: string): object {
|
|
58
|
+
const handler: ProxyHandler<object> = {
|
|
59
|
+
get(_t, prop: string) {
|
|
60
|
+
if (prop === "then") return undefined; // not a Promise
|
|
61
|
+
const fn = isDevEnvironment() ? console.error : console.warn;
|
|
62
|
+
fn(`[SceneData] ${message}`);
|
|
63
|
+
return makeErrorProxy(message);
|
|
64
|
+
},
|
|
65
|
+
set(_t, prop: string) {
|
|
66
|
+
const fn = isDevEnvironment() ? console.error : console.warn;
|
|
67
|
+
fn(`[SceneData] ${message} (tried to set "${prop}")`);
|
|
68
|
+
return true; // suppress TypeError
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
return new Proxy({}, handler);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a proxy for a scene node that exposes `$object`, `$components`,
|
|
76
|
+
* and child nodes as nested properties.
|
|
77
|
+
*/
|
|
78
|
+
function makeNodeProxy(ctx: IContext, node: import("three").Object3D): object {
|
|
79
|
+
return new Proxy({}, {
|
|
80
|
+
get(_t, prop: string) {
|
|
81
|
+
if (prop === "$object") return node;
|
|
82
|
+
if (prop === "$components") {
|
|
83
|
+
return new Proxy({}, {
|
|
84
|
+
get(_t2, compName: string) {
|
|
85
|
+
if (compName === "then") return undefined;
|
|
86
|
+
const ctor = TypeStore.get(compName);
|
|
87
|
+
if (!ctor) return makeErrorProxy(`Component type "${compName}" not registered (node "${node.name}")`);
|
|
88
|
+
const comp = getComponent(node, ctor);
|
|
89
|
+
if (!comp) return makeErrorProxy(`Component "${compName}" not found on node "${node.name}"`);
|
|
90
|
+
return comp;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Child node lookup by name
|
|
95
|
+
const child = node.children.find(c => c.name === prop) ?? null;
|
|
96
|
+
if (!child) return makeErrorProxy(`Child "${prop}" not found on node "${node.name}"`);
|
|
97
|
+
return makeNodeProxy(ctx, child);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns a lazily-resolved proxy typed as {@link SceneData}.
|
|
104
|
+
* The proxy is cached per context — each context gets exactly one instance.
|
|
105
|
+
*
|
|
106
|
+
* Shape mirrors the generated `needle-bindings.gen.d.ts`:
|
|
107
|
+
* ctx.sceneData.MyGlb.Camera.$components.OrbitControls.autoRotate = true;
|
|
108
|
+
* ctx.sceneData.MyGlb.Camera.$object // → THREE.Camera
|
|
109
|
+
* ctx.sceneData.MyGlb.UI.Button.$components.Button // → Needle Button component
|
|
110
|
+
*
|
|
111
|
+
* GLB name is ignored at runtime (scene is already loaded).
|
|
112
|
+
* Node lookup starts at the scene root.
|
|
113
|
+
*/
|
|
114
|
+
export function getSceneData(ctx: IContext): SceneData {
|
|
115
|
+
let proxy = cache.get(ctx);
|
|
116
|
+
if (!proxy) {
|
|
117
|
+
proxy = new Proxy({} as SceneData, {
|
|
118
|
+
get(_target, _glbName: string) {
|
|
119
|
+
// GLB name level — ignored at runtime, return node-name proxy
|
|
120
|
+
return new Proxy({}, {
|
|
121
|
+
get(_target, nodeName: string) {
|
|
122
|
+
if (nodeName === "then") return undefined;
|
|
123
|
+
const node = ctx.scene.getObjectByName(nodeName) ?? null;
|
|
124
|
+
if (!node) return makeErrorProxy(`Node "${nodeName}" not found in scene`);
|
|
125
|
+
return makeNodeProxy(ctx, node);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
cache.set(ctx, proxy);
|
|
131
|
+
}
|
|
132
|
+
return proxy;
|
|
133
|
+
}
|
|
@@ -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
|
|
497
|
-
private static
|
|
498
|
-
private static
|
|
499
|
-
private static
|
|
500
|
-
private static
|
|
501
|
-
private static
|
|
502
|
-
private static
|
|
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 {
|
|
@@ -184,12 +184,12 @@ export function setOrAddParamsToUrl(url: URLSearchParams, paramName: string, par
|
|
|
184
184
|
|
|
185
185
|
/** Adds an entry to the browser history. Internally uses `window.history.pushState` */
|
|
186
186
|
export function pushState(title: string, urlParams: URLSearchParams, state?: any) {
|
|
187
|
-
window.history.pushState(state, title, "?" + urlParams.toString());
|
|
187
|
+
window.history.pushState(state, title, "?" + urlParams.toString() + window.location.hash);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** Replaces the current entry in the browser history. Internally uses `window.history.replaceState` */
|
|
191
191
|
export function setState(title: string, urlParams: URLSearchParams, state?: any) {
|
|
192
|
-
window.history.replaceState(state, title, "?" + urlParams.toString());
|
|
192
|
+
window.history.replaceState(state, title, "?" + urlParams.toString() + window.location.hash);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// for room id
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
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,13 +7,21 @@ declare global {
|
|
|
6
7
|
interface HTMLElementTagNameMap {
|
|
7
8
|
"needle-logo-element": NeedleLogoElement;
|
|
8
9
|
}
|
|
10
|
+
namespace JSX {
|
|
11
|
+
interface IntrinsicElements {
|
|
12
|
+
"needle-logo-element": NeedleLogoElementJSXAttributes;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
interface NeedleLogoElementJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
|
|
16
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
17
|
+
}
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* Needle logo web component used in the hosting UI (small, compact logo or full)
|
|
13
22
|
* @element needle-logo-element
|
|
14
23
|
*/
|
|
15
|
-
export class NeedleLogoElement extends
|
|
24
|
+
export class NeedleLogoElement extends HTMLElementBase {
|
|
16
25
|
|
|
17
26
|
static get elementName() { return elementName; }
|
|
18
27
|
|
|
@@ -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";
|
|
@@ -13,7 +14,15 @@ import { NeedleSpatialMenu } from "./needle-menu-spatial.js";
|
|
|
13
14
|
|
|
14
15
|
declare global {
|
|
15
16
|
interface HTMLElementTagNameMap {
|
|
16
|
-
"needle-
|
|
17
|
+
"needle-menu": NeedleMenuElement;
|
|
18
|
+
}
|
|
19
|
+
namespace JSX {
|
|
20
|
+
interface IntrinsicElements {
|
|
21
|
+
"needle-menu": NeedleMenuJSXAttributes;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
interface NeedleMenuJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
|
|
25
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
17
26
|
}
|
|
18
27
|
}
|
|
19
28
|
|
|
@@ -303,7 +312,7 @@ export class NeedleMenu {
|
|
|
303
312
|
*
|
|
304
313
|
* @element needle-menu
|
|
305
314
|
*/
|
|
306
|
-
export class NeedleMenuElement extends
|
|
315
|
+
export class NeedleMenuElement extends HTMLElementBase {
|
|
307
316
|
|
|
308
317
|
static create() {
|
|
309
318
|
// Ensure the element is registered before creating — guards against
|
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -7,6 +8,19 @@ declare global {
|
|
|
7
8
|
interface HTMLElementTagNameMap {
|
|
8
9
|
"needle-button": NeedleButtonElement;
|
|
9
10
|
}
|
|
11
|
+
namespace JSX {
|
|
12
|
+
interface IntrinsicElements {
|
|
13
|
+
"needle-button": NeedleButtonJSXAttributes;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
interface NeedleButtonJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
|
|
17
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
18
|
+
ar?: boolean | string;
|
|
19
|
+
vr?: boolean | string;
|
|
20
|
+
quicklook?: boolean | string;
|
|
21
|
+
qrcode?: boolean | string;
|
|
22
|
+
unstyled?: boolean | string;
|
|
23
|
+
}
|
|
10
24
|
}
|
|
11
25
|
|
|
12
26
|
const isDev = isDevEnvironment();
|
|
@@ -65,7 +79,7 @@ const isDev = isDevEnvironment();
|
|
|
65
79
|
* @see {@link NeedleEngineWebComponent} for the main <needle-engine> element
|
|
66
80
|
* @see {@link NeedleMenu} for the built-in menu component that can display similar buttons
|
|
67
81
|
*/
|
|
68
|
-
export class NeedleButtonElement extends
|
|
82
|
+
export class NeedleButtonElement extends HTMLElementBase {
|
|
69
83
|
|
|
70
84
|
static observedAttributes = ["ar", "vr", "quicklook", "qrcode"];
|
|
71
85
|
|
|
@@ -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";
|
|
@@ -20,6 +21,12 @@ declare global {
|
|
|
20
21
|
interface HTMLElementTagNameMap {
|
|
21
22
|
"needle-engine": NeedleEngineWebComponent;
|
|
22
23
|
}
|
|
24
|
+
// JSX support — allows <needle-engine> in React, Preact, SolidJS and any other JSX-based framework.
|
|
25
|
+
namespace JSX {
|
|
26
|
+
interface IntrinsicElements {
|
|
27
|
+
"needle-engine": NeedleEngineAttributes;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
const debug = getParam("debugwebcomponent");
|
|
@@ -153,7 +160,7 @@ const observedAttributes = [
|
|
|
153
160
|
* @see {@link NeedleButtonElement} for adding AR/VR/Quicklook buttons via <needle-button>
|
|
154
161
|
* @see {@link NeedleMenu} for the built-in menu configuration component
|
|
155
162
|
*/
|
|
156
|
-
export class NeedleEngineWebComponent extends
|
|
163
|
+
export class NeedleEngineWebComponent extends HTMLElementBase implements INeedleEngineComponent {
|
|
157
164
|
|
|
158
165
|
static get observedAttributes() {
|
|
159
166
|
return observedAttributes;
|
|
@@ -32,7 +32,7 @@ declare type NeedleXRFrame = XRFrame & { fillPoses?: FillPosesFunction };
|
|
|
32
32
|
* Use `args.xr` to access the NeedleXRSession */
|
|
33
33
|
export type NeedleXREventArgs = { readonly xr: NeedleXRSession }
|
|
34
34
|
export type SessionChangedEvt = (args: NeedleXREventArgs) => void;
|
|
35
|
-
export type SessionRequestedEvent = (args: { readonly mode: XRSessionMode, readonly init: XRSessionInit }) => void;
|
|
35
|
+
export type SessionRequestedEvent = (args: { readonly mode: XRSessionMode, readonly init: XRSessionInit, readonly context: Context }) => void;
|
|
36
36
|
export type SessionRequestedEndEvent = (args: { readonly mode: XRSessionMode, readonly init: XRSessionInit, newSession: XRSession | null }) => void;
|
|
37
37
|
|
|
38
38
|
/** Result of a XR hit-test
|
|
@@ -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) {
|
|
@@ -492,7 +492,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
492
492
|
|
|
493
493
|
if (!arSupported && (mode === "immersive-ar" || mode === "ar")) {
|
|
494
494
|
|
|
495
|
-
this.invokeSessionRequestStart("immersive-ar", init);
|
|
495
|
+
this.invokeSessionRequestStart("immersive-ar", init, context ?? Context.Current);
|
|
496
496
|
|
|
497
497
|
// if we are in an iframe, we need to navigate the top window
|
|
498
498
|
const topWindow = window.top || window;
|
|
@@ -662,7 +662,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
662
662
|
script.onBeforeXR(mode, init);
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
|
-
this.invokeSessionRequestStart(mode, init);
|
|
665
|
+
this.invokeSessionRequestStart(mode, init, context ?? Context.Current);
|
|
666
666
|
if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
|
|
667
667
|
Telemetry.sendEvent(Context.Current, "xr", {
|
|
668
668
|
action: "session_request",
|
|
@@ -693,9 +693,9 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
693
693
|
return session;
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
-
private static invokeSessionRequestStart(mode: XRSessionMode, init: XRSessionInit) {
|
|
696
|
+
private static invokeSessionRequestStart(mode: XRSessionMode, init: XRSessionInit, context: Context) {
|
|
697
697
|
for (const listener of this._sessionRequestStartListeners) {
|
|
698
|
-
listener({ mode, init });
|
|
698
|
+
listener({ mode, init, context });
|
|
699
699
|
}
|
|
700
700
|
}
|
|
701
701
|
private static invokeSessionRequestEnd(mode: XRSessionMode, init: XRSessionInit, session: XRSession | null | undefined | void) {
|
package/src/engine/xr/events.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NeedleXRSession } from "./NeedleXRSession.js";
|
|
2
|
+
import type { Context } from "../engine_context.js";
|
|
2
3
|
|
|
3
4
|
export declare type XRSessionEventArgs = { session: NeedleXRSession };
|
|
4
5
|
|
|
@@ -14,11 +15,13 @@ const onXRSessionStartListeners: ((evt: XRSessionEventArgs) => void)[] = [];
|
|
|
14
15
|
* console.log("XR session started", evt);
|
|
15
16
|
* });
|
|
16
17
|
* ```
|
|
18
|
+
* @returns A function to remove the listener
|
|
17
19
|
*/
|
|
18
20
|
export function onXRSessionStart(fn: (evt: XRSessionEventArgs) => void) {
|
|
19
21
|
if (onXRSessionStartListeners.indexOf(fn) === -1) {
|
|
20
22
|
onXRSessionStartListeners.push(fn);
|
|
21
23
|
}
|
|
24
|
+
return () => offXRSessionStart(fn);
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Remove a listener for when an XR session starts
|
|
@@ -51,11 +54,13 @@ const onXRSessionEndListeners: ((evt: XRSessionEventArgs) => void)[] = [];
|
|
|
51
54
|
* console.log("XR session ended", evt);
|
|
52
55
|
* });
|
|
53
56
|
* ```
|
|
57
|
+
* @returns A function to remove the listener
|
|
54
58
|
*/
|
|
55
59
|
export function onXRSessionEnd(fn: (evt: XRSessionEventArgs) => void) {
|
|
56
60
|
if (onXRSessionEndListeners.indexOf(fn) === -1) {
|
|
57
61
|
onXRSessionEndListeners.push(fn);
|
|
58
62
|
}
|
|
63
|
+
return () => offXRSessionEnd(fn);
|
|
59
64
|
};
|
|
60
65
|
|
|
61
66
|
/**
|
|
@@ -78,6 +83,44 @@ export function offXRSessionEnd(fn: (evt: XRSessionEventArgs) => void) {
|
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
|
|
86
|
+
export declare type XRSessionRequestEventArgs = { readonly mode: XRSessionMode, readonly init: XRSessionInit, readonly context: Context };
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add a listener that fires before an XR session is requested.
|
|
90
|
+
* Use this to modify the session init options, e.g. to add optional features like `camera-access`.
|
|
91
|
+
* @param fn The function to call before the XR session is requested
|
|
92
|
+
* @example
|
|
93
|
+
* ```js
|
|
94
|
+
* onBeforeXRSession((args) => {
|
|
95
|
+
* args.init.optionalFeatures ??= [];
|
|
96
|
+
* args.init.optionalFeatures.push("camera-access");
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
* @return A function to remove the listener
|
|
100
|
+
*/
|
|
101
|
+
export function onBeforeXRSession(fn: (args: XRSessionRequestEventArgs) => void) {
|
|
102
|
+
if (onBeforeXRSessionListeners.indexOf(fn) === -1) {
|
|
103
|
+
onBeforeXRSessionListeners.push(fn);
|
|
104
|
+
// Delegate to NeedleXRSession which owns the actual invocation
|
|
105
|
+
NeedleXRSession.onSessionRequestStart(fn);
|
|
106
|
+
}
|
|
107
|
+
return () => offBeforeXRSession(fn);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Remove a listener for before an XR session is requested
|
|
112
|
+
* @param fn The function to remove from the listeners
|
|
113
|
+
*/
|
|
114
|
+
export function offBeforeXRSession(fn: (args: XRSessionRequestEventArgs) => void) {
|
|
115
|
+
const index = onBeforeXRSessionListeners.indexOf(fn);
|
|
116
|
+
if (index !== -1) {
|
|
117
|
+
onBeforeXRSessionListeners.splice(index, 1);
|
|
118
|
+
NeedleXRSession.offSessionRequestStart(fn);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const onBeforeXRSessionListeners: ((args: XRSessionRequestEventArgs) => void)[] = [];
|
|
123
|
+
|
|
81
124
|
/**
|
|
82
125
|
* @internal
|
|
83
126
|
* Invoke the XRSessionStart event
|
package/src/engine/xr/init.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isDevEnvironment, showBalloonMessage } from "../debug/index.js";
|
|
2
1
|
import { DeviceUtilities, setParamWithoutReload } from "../engine_utils.js";
|
|
3
2
|
import { NeedleXRSession } from "./NeedleXRSession.js";
|
|
4
3
|
|
|
@@ -13,12 +12,6 @@ export function initXR() {
|
|
|
13
12
|
|
|
14
13
|
if (DeviceUtilities.isiOS()) {
|
|
15
14
|
|
|
16
|
-
if (isDevEnvironment()) {
|
|
17
|
-
const randomParameterValue = Date.now().toString();
|
|
18
|
-
setParamWithoutReload("debug_appclip", randomParameterValue);
|
|
19
|
-
showBalloonMessage("iOS appclip debug: " + randomParameterValue);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
15
|
// Prefetch
|
|
23
16
|
const url = new URL("https://appclip.apple.com/id?p=tools.needle.launch-app.Clip");
|
|
24
17
|
url.searchParams.set("url", location.href);
|