@needle-tools/engine 4.14.0 → 4.15.0-next.f391a30
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/CHANGELOG.md +8 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-BttGBXw6.umd.cjs → gltf-progressive-CMwJPwEt.umd.cjs} +1 -1
- package/dist/{gltf-progressive-Bm_6aEi4.js → gltf-progressive-CTlvpS3A.js} +1 -1
- package/dist/{gltf-progressive-T5WKTux5.min.js → gltf-progressive-DYL3SLVb.min.js} +1 -1
- package/dist/materialx-4jJLLe9Q.js +4174 -0
- package/dist/materialx-Bt9FHwco.min.js +158 -0
- package/dist/materialx-NDD0y4JY.umd.cjs +158 -0
- package/dist/{needle-engine.bundle-COL2Bar3.umd.cjs → needle-engine.bundle-C1BFRZDF.umd.cjs} +150 -140
- package/dist/{needle-engine.bundle-Z_gAD7Kg.js → needle-engine.bundle-DB4kLWO_.js} +6651 -6400
- package/dist/{needle-engine.bundle-NolzHLqO.min.js → needle-engine.bundle-DsTdfmeb.min.js} +151 -141
- package/dist/needle-engine.d.ts +345 -88
- package/dist/needle-engine.js +322 -322
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-06AXuvdv.min.js → postprocessing-BN-f4viE.min.js} +1 -1
- package/dist/{postprocessing-CPDcA21P.umd.cjs → postprocessing-DYmYOVm4.umd.cjs} +1 -1
- package/dist/{postprocessing-CI2x8Cln.js → postprocessing-De9ZpJrk.js} +1 -1
- package/dist/{three-examples-BMmNgNCN.umd.cjs → three-examples-BHqRVpO_.umd.cjs} +12 -12
- package/dist/{three-examples-CMYCd5nH.js → three-examples-C0ZCCA_K.js} +182 -192
- package/dist/{three-examples-CQl1fFZp.min.js → three-examples-DmTY8tGr.min.js} +14 -14
- package/lib/engine/api.d.ts +0 -2
- package/lib/engine/api.js +0 -2
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug.js +1 -1
- package/lib/engine/debug/debug.js.map +1 -1
- package/lib/engine/debug/debug_spatial_console.js +1 -1
- package/lib/engine/debug/debug_spatial_console.js.map +1 -1
- package/lib/engine/engine_accessibility.d.ts +77 -0
- package/lib/engine/engine_accessibility.js +162 -0
- package/lib/engine/engine_accessibility.js.map +1 -0
- package/lib/engine/engine_context.d.ts +2 -0
- package/lib/engine/engine_context.js +8 -1
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.js +1 -1
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gizmos.js +1 -1
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_license.js +7 -2
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
- package/lib/engine/engine_materialpropertyblock.js +97 -7
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_math.d.ts +34 -1
- package/lib/engine/engine_math.js +34 -1
- package/lib/engine/engine_math.js.map +1 -1
- package/lib/engine/engine_networking.js +1 -1
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_types.d.ts +2 -0
- package/lib/engine/engine_types.js +2 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
- package/lib/engine/export/gltf/index.js +1 -1
- package/lib/engine/export/gltf/index.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +3 -0
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +7 -3
- package/lib/engine/webcomponents/logo-element.js +21 -1
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +10 -7
- package/lib/engine/webcomponents/needle menu/needle-menu.js +14 -4
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.d.ts +37 -11
- package/lib/engine/webcomponents/needle-button.js +42 -11
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +10 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +13 -2
- package/lib/engine/webcomponents/needle-engine.js +23 -3
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.d.ts +1 -2
- package/lib/engine-components/Component.js +1 -3
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +1 -0
- package/lib/engine-components/DragControls.js +21 -0
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +2 -0
- package/lib/engine-components/NeedleMenu.js +2 -0
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +28 -3
- package/lib/engine-components/Networking.js +28 -3
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +25 -2
- package/lib/engine-components/ReflectionProbe.js +46 -2
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Skybox.js +4 -2
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
- package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +15 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +77 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/ui/Button.d.ts +1 -0
- package/lib/engine-components/ui/Button.js +11 -0
- package/lib/engine-components/ui/Button.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +11 -0
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/package.json +18 -14
- package/plugins/common/buildinfo.js +46 -10
- package/plugins/common/files.js +2 -1
- package/plugins/common/license.js +144 -69
- package/plugins/common/logger.js +172 -11
- package/plugins/common/needle-engine-skill.md +175 -0
- package/plugins/common/worker.js +5 -4
- package/plugins/types/userconfig.d.ts +40 -2
- package/plugins/vite/ai.js +71 -0
- package/plugins/vite/alias.js +6 -5
- package/plugins/vite/asap.js +6 -5
- package/plugins/vite/build-pipeline.js +224 -41
- package/plugins/vite/buildinfo.js +66 -6
- package/plugins/vite/copyfiles.js +41 -12
- package/plugins/vite/custom-element-data.js +26 -16
- package/plugins/vite/defines.js +8 -5
- package/plugins/vite/dependencies.js +16 -10
- package/plugins/vite/dependency-watcher.js +35 -7
- package/plugins/vite/drop-client.js +7 -5
- package/plugins/vite/drop.js +16 -14
- package/plugins/vite/editor-connection.js +18 -16
- package/plugins/vite/imports-logger.js +12 -2
- package/plugins/vite/index.js +8 -3
- package/plugins/vite/local-files-analysis.js +789 -0
- package/plugins/vite/local-files-core.js +992 -0
- package/plugins/vite/local-files-internals.js +28 -0
- package/plugins/vite/local-files-types.d.ts +111 -0
- package/plugins/vite/local-files-utils.js +359 -0
- package/plugins/vite/local-files.js +2 -441
- package/plugins/vite/logger.client.js +45 -35
- package/plugins/vite/logger.js +6 -3
- package/plugins/vite/logging.js +129 -0
- package/plugins/vite/meta.js +18 -4
- package/plugins/vite/needle-app.js +4 -3
- package/plugins/vite/peer.js +2 -1
- package/plugins/vite/pwa.js +33 -17
- package/plugins/vite/reload.js +24 -2
- package/src/engine/api.ts +0 -3
- package/src/engine/debug/debug.ts +1 -1
- package/src/engine/debug/debug_spatial_console.ts +5 -1
- package/src/engine/engine_accessibility.ts +198 -0
- package/src/engine/engine_context.ts +10 -1
- package/src/engine/engine_create_objects.ts +1 -1
- package/src/engine/engine_gizmos.ts +9 -5
- package/src/engine/engine_license.ts +7 -2
- package/src/engine/engine_materialpropertyblock.ts +102 -11
- package/src/engine/engine_math.ts +34 -1
- package/src/engine/engine_networking.ts +1 -1
- package/src/engine/engine_types.ts +5 -0
- package/src/engine/engine_utils.ts +2 -2
- package/src/engine/export/gltf/index.ts +1 -1
- package/src/engine/webcomponents/icons.ts +3 -0
- package/src/engine/webcomponents/logo-element.ts +24 -4
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +23 -11
- package/src/engine/webcomponents/needle-button.ts +44 -13
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +13 -2
- package/src/engine/webcomponents/needle-engine.ts +31 -8
- package/src/engine-components/Component.ts +2 -5
- package/src/engine-components/DragControls.ts +29 -4
- package/src/engine-components/NeedleMenu.ts +5 -3
- package/src/engine-components/Networking.ts +29 -4
- package/src/engine-components/ReflectionProbe.ts +52 -9
- package/src/engine-components/Skybox.ts +4 -2
- package/src/engine-components/export/gltf/GltfExport.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
- package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +108 -32
- package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
- package/src/engine-components/ui/Button.ts +12 -0
- package/src/engine-components/ui/Text.ts +13 -0
- package/dist/materialx-CJyQZtjt.min.js +0 -90
- package/dist/materialx-DMs1E08Z.js +0 -4636
- package/dist/materialx-DaKKOoVk.umd.cjs +0 -90
- package/lib/engine/engine_test_utils.d.ts +0 -39
- package/lib/engine/engine_test_utils.js +0 -84
- package/lib/engine/engine_test_utils.js.map +0 -1
- package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
- package/src/engine/engine_test_utils.ts +0 -109
- package/src/include/draco/draco_decoder.js +0 -34
- package/src/include/draco/draco_decoder.wasm +0 -0
- package/src/include/draco/draco_wasm_wrapper.js +0 -117
- package/src/include/ktx2/basis_transcoder.js +0 -19
- package/src/include/ktx2/basis_transcoder.wasm +0 -0
- package/src/include/needle/arial-msdf.json +0 -1472
- package/src/include/needle/arial.png +0 -0
- package/src/include/needle/poweredbyneedle.webp +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
- /package/src/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, openSync, readSync, closeSync } from 'fs';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
|
+
import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('./local-files-types.js').LocalizationOptions} LocalizationOptions */
|
|
7
|
+
/** @typedef {import('./local-files-types.js').AutoPolicy} AutoPolicy */
|
|
8
|
+
/** @typedef {import('./local-files-types.js').SceneAnalysisReport} SceneAnalysisReport */
|
|
9
|
+
/** @typedef {import('./local-files-types.js').SceneFile} SceneFile */
|
|
10
|
+
/** @typedef {import('./local-files-types.js').UrlHandler} UrlHandler */
|
|
11
|
+
/** @typedef {import('./local-files-types.js').LocalizationContext} LocalizationContext */
|
|
12
|
+
|
|
13
|
+
const NEEDLE_COMPONENTS_EXTENSION = "NEEDLE_components";
|
|
14
|
+
export const SKYBOX_MAGIC_KEYWORDS = ["studio", "blurred-skybox", "quicklook", "quicklook-ar"];
|
|
15
|
+
const SKYBOX_BASE_URL = "https://cdn.needle.tools/static/skybox/";
|
|
16
|
+
const HLS_CDN_SEGMENT = "/npm/hls.js@";
|
|
17
|
+
|
|
18
|
+
/** @type {Map<string, {autoPolicy: AutoPolicy|null, report: SceneAnalysisReport, hasLogged: boolean}>} */
|
|
19
|
+
const analysisByProject = new Map();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} command
|
|
23
|
+
* @param {unknown} _config
|
|
24
|
+
* @param {import('../types').userSettings} userSettings
|
|
25
|
+
*/
|
|
26
|
+
export function needleLocalFilesSceneAnalysis(command, _config, userSettings) {
|
|
27
|
+
if (!makeFilesLocalIsEnabled(userSettings)) return null;
|
|
28
|
+
|
|
29
|
+
const options = resolveOptions(userSettings);
|
|
30
|
+
let pluginRoot = process.cwd();
|
|
31
|
+
let pluginCommand = command;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
name: "needle:local-files-scene-analysis",
|
|
35
|
+
enforce: "pre",
|
|
36
|
+
/** @param {import('vite').ResolvedConfig} config */
|
|
37
|
+
configResolved(config) {
|
|
38
|
+
pluginRoot = config?.root || process.cwd();
|
|
39
|
+
pluginCommand = config?.command || command;
|
|
40
|
+
},
|
|
41
|
+
/** @param {import('vite').ViteDevServer} server */
|
|
42
|
+
configureServer(server) {
|
|
43
|
+
const projectDir = server?.config?.root || pluginRoot || process.cwd();
|
|
44
|
+
ensureProjectAnalysis(projectDir, options);
|
|
45
|
+
logSceneAnalysisReport(projectDir, "serve");
|
|
46
|
+
server?.httpServer?.once("close", () => {
|
|
47
|
+
logSceneAnalysisReport(projectDir, "serve");
|
|
48
|
+
analysisByProject.delete(projectDir);
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
buildStart() {
|
|
52
|
+
const projectDir = pluginRoot || process.cwd();
|
|
53
|
+
ensureProjectAnalysis(projectDir, options);
|
|
54
|
+
},
|
|
55
|
+
buildEnd() {
|
|
56
|
+
const projectDir = pluginRoot || process.cwd();
|
|
57
|
+
logSceneAnalysisReport(projectDir, "build");
|
|
58
|
+
analysisByProject.delete(projectDir);
|
|
59
|
+
},
|
|
60
|
+
closeBundle() {
|
|
61
|
+
const activeCommand = pluginCommand || command;
|
|
62
|
+
if (activeCommand !== "serve") return;
|
|
63
|
+
const projectDir = pluginRoot || process.cwd();
|
|
64
|
+
logSceneAnalysisReport(projectDir, "serve");
|
|
65
|
+
analysisByProject.delete(projectDir);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} projectDir
|
|
72
|
+
* @returns {AutoPolicy | null}
|
|
73
|
+
*/
|
|
74
|
+
export function getLocalFilesAutoPolicy(projectDir) {
|
|
75
|
+
const entry = analysisByProject.get(projectDir);
|
|
76
|
+
return entry?.autoPolicy ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {import('../types').userSettings | undefined | null} userSettings
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
export function makeFilesLocalIsEnabled(userSettings) {
|
|
84
|
+
if (typeof userSettings?.makeFilesLocal === "object") return userSettings?.makeFilesLocal?.enabled !== false;
|
|
85
|
+
if (userSettings?.makeFilesLocal === "auto") return true;
|
|
86
|
+
return userSettings?.makeFilesLocal === true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {import('../types').userSettings | undefined | null} userSettings
|
|
91
|
+
* @returns {LocalizationOptions}
|
|
92
|
+
*/
|
|
93
|
+
export function resolveOptions(userSettings) {
|
|
94
|
+
if (typeof userSettings?.makeFilesLocal === "object") {
|
|
95
|
+
const raw = userSettings.makeFilesLocal;
|
|
96
|
+
const opts = { ...raw };
|
|
97
|
+
if (!opts.exclude?.length && opts.excludeUrls?.length) {
|
|
98
|
+
opts.exclude = opts.excludeUrls;
|
|
99
|
+
}
|
|
100
|
+
return opts;
|
|
101
|
+
}
|
|
102
|
+
if (userSettings?.makeFilesLocal === "auto") {
|
|
103
|
+
return { features: "auto" };
|
|
104
|
+
}
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string | string[] | undefined} features
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
export function hasAutoFeatureSelection(features) {
|
|
113
|
+
if (features === "auto") return true;
|
|
114
|
+
if (Array.isArray(features)) return features.includes("auto");
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {LocalizationOptions} options
|
|
120
|
+
* @param {string} projectDir
|
|
121
|
+
* @returns {AutoPolicy | null}
|
|
122
|
+
*/
|
|
123
|
+
export function buildAutoPolicy(options, projectDir) {
|
|
124
|
+
if (!hasAutoFeatureSelection(options.features)) return null;
|
|
125
|
+
return detectAutoPolicy(projectDir, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} projectDir
|
|
130
|
+
* @param {LocalizationOptions} options
|
|
131
|
+
* @returns {{ autoPolicy: AutoPolicy|null, report: SceneAnalysisReport, hasLogged: boolean }}
|
|
132
|
+
*/
|
|
133
|
+
function ensureProjectAnalysis(projectDir, options) {
|
|
134
|
+
const existing = analysisByProject.get(projectDir);
|
|
135
|
+
if (existing) return existing;
|
|
136
|
+
|
|
137
|
+
const report = analyzeProjectGlbs(projectDir);
|
|
138
|
+
const autoPolicy = hasAutoFeatureSelection(options.features)
|
|
139
|
+
? detectAutoPolicy(projectDir, options, report)
|
|
140
|
+
: null;
|
|
141
|
+
|
|
142
|
+
const entry = {
|
|
143
|
+
autoPolicy,
|
|
144
|
+
report,
|
|
145
|
+
hasLogged: false,
|
|
146
|
+
};
|
|
147
|
+
analysisByProject.set(projectDir, entry);
|
|
148
|
+
return entry;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} projectDir
|
|
153
|
+
* @param {string} mode
|
|
154
|
+
*/
|
|
155
|
+
function logSceneAnalysisReport(projectDir, mode) {
|
|
156
|
+
const entry = analysisByProject.get(projectDir);
|
|
157
|
+
if (!entry || entry.hasLogged) return;
|
|
158
|
+
|
|
159
|
+
const message = formatSceneAnalysisReport(entry.report, entry.autoPolicy, projectDir, mode);
|
|
160
|
+
needleLog("needle:local-files", message, "log", { dimBody: false });
|
|
161
|
+
entry.hasLogged = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {string} projectDir
|
|
166
|
+
* @param {LocalizationOptions} options
|
|
167
|
+
* @param {SceneAnalysisReport | null} [cachedAnalysis]
|
|
168
|
+
* @returns {AutoPolicy}
|
|
169
|
+
*/
|
|
170
|
+
export function detectAutoPolicy(projectDir, options, cachedAnalysis = null) {
|
|
171
|
+
const features = /** @type {Set<string>} */ (new Set());
|
|
172
|
+
|
|
173
|
+
features.add("draco");
|
|
174
|
+
features.add("ktx2");
|
|
175
|
+
features.add("fonts");
|
|
176
|
+
features.add("cdn-scripts");
|
|
177
|
+
features.add("needle-uploads");
|
|
178
|
+
features.add("needle-fonts");
|
|
179
|
+
features.add("needle-branding");
|
|
180
|
+
|
|
181
|
+
const pkgJsonPath = join(projectDir, "package.json");
|
|
182
|
+
/** @type {{dependencies?: Record<string,string>, devDependencies?: Record<string,string>} | null} */
|
|
183
|
+
let pkgJson = null;
|
|
184
|
+
try {
|
|
185
|
+
if (existsSync(pkgJsonPath)) {
|
|
186
|
+
pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (_e) { }
|
|
190
|
+
|
|
191
|
+
if (pkgJson) {
|
|
192
|
+
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
193
|
+
if (allDeps["@needle-tools/materialx"]) {
|
|
194
|
+
features.add("materialx");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const indexHtmlPath = join(projectDir, "index.html");
|
|
199
|
+
let indexHtml = "";
|
|
200
|
+
try {
|
|
201
|
+
if (existsSync(indexHtmlPath)) {
|
|
202
|
+
indexHtml = readFileSync(indexHtmlPath, "utf-8");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (_e) { }
|
|
206
|
+
|
|
207
|
+
const indexSkyboxUrls = collectIndexHtmlSkyboxUrls(indexHtml);
|
|
208
|
+
const selectedSkyboxUrls = resolveSkyboxSelectionUrls(options.skybox, indexSkyboxUrls);
|
|
209
|
+
if (options.skybox === "all") {
|
|
210
|
+
features.add("skybox");
|
|
211
|
+
}
|
|
212
|
+
if (selectedSkyboxUrls?.size) {
|
|
213
|
+
features.add("skybox");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const srcDir = join(projectDir, "src");
|
|
217
|
+
let srcContent = "";
|
|
218
|
+
try {
|
|
219
|
+
srcContent = collectSourceFiles(srcDir);
|
|
220
|
+
}
|
|
221
|
+
catch (_e) { }
|
|
222
|
+
|
|
223
|
+
const registerTypesPath = join(projectDir, "src", "generated", "register_types.ts");
|
|
224
|
+
let registerTypes = "";
|
|
225
|
+
try {
|
|
226
|
+
if (existsSync(registerTypesPath)) {
|
|
227
|
+
registerTypes = readFileSync(registerTypesPath, "utf-8");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (_e) { }
|
|
231
|
+
|
|
232
|
+
const allSrc = srcContent + "\n" + registerTypes + "\n" + indexHtml;
|
|
233
|
+
const sceneAnalysis = cachedAnalysis || analyzeProjectGlbs(projectDir);
|
|
234
|
+
const hasWebXRComponent = sceneAnalysis.hasWebXRComponent;
|
|
235
|
+
const hasVideoPlayerComponent = sceneAnalysis.hasVideoPlayerComponent;
|
|
236
|
+
|
|
237
|
+
if (hasWebXRComponent) {
|
|
238
|
+
features.add("xr");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (/polyhaven\.org/i.test(allSrc)) {
|
|
242
|
+
features.add("polyhaven");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (/raw\.githubusercontent\.com/i.test(allSrc)) {
|
|
246
|
+
features.add("github-content");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (/threejs\.org\/examples\/models/i.test(allSrc)) {
|
|
250
|
+
features.add("threejs-models");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (/cdn\.needle\.tools\/static\/models/i.test(allSrc)) {
|
|
254
|
+
features.add("needle-models");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (/cdn\.needle\.tools\/static\/avatars/i.test(allSrc) || /\bAvatarModel\b|\bAvatarLoader\b/i.test(allSrc) || hasWebXRComponent) {
|
|
258
|
+
features.add("needle-avatars");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (/materialx|MaterialXLoader/i.test(allSrc)) {
|
|
262
|
+
features.add("materialx");
|
|
263
|
+
}
|
|
264
|
+
if (Array.from(sceneAnalysis.extensions).some(ext => /materialx/i.test(ext))) {
|
|
265
|
+
features.add("materialx");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (selectedSkyboxUrls && selectedSkyboxUrls.size > 0) {
|
|
269
|
+
features.add("skybox");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
features,
|
|
274
|
+
hasWebXR: hasWebXRComponent,
|
|
275
|
+
hasVideoPlayer: hasVideoPlayerComponent,
|
|
276
|
+
allowedSkyboxUrls: selectedSkyboxUrls,
|
|
277
|
+
selectedWebXRProfiles: getWebXRProfilesForMode(options.webxr),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @param {string} projectDir
|
|
283
|
+
* @returns {SceneAnalysisReport}
|
|
284
|
+
*/
|
|
285
|
+
export function analyzeProjectGlbs(projectDir) {
|
|
286
|
+
const sceneFiles = collectAssetSceneFiles(projectDir);
|
|
287
|
+
|
|
288
|
+
const extensions = /** @type {Set<string>} */ (new Set());
|
|
289
|
+
const componentTypes = /** @type {Set<string>} */ (new Set());
|
|
290
|
+
const componentCounts = /** @type {Map<string, number>} */ (new Map());
|
|
291
|
+
const needleExtensionBlobs = /** @type {unknown[]} */ ([]);
|
|
292
|
+
|
|
293
|
+
let hasWebXRComponent = false;
|
|
294
|
+
let hasVideoPlayerComponent = false;
|
|
295
|
+
|
|
296
|
+
let totalNodeCount = 0;
|
|
297
|
+
let totalVertexCount = 0;
|
|
298
|
+
let totalTextureCount = 0;
|
|
299
|
+
let totalMeshCount = 0;
|
|
300
|
+
let totalPrimitiveCount = 0;
|
|
301
|
+
let dracoPrimitiveCount = 0;
|
|
302
|
+
let meshoptBufferViewCount = 0;
|
|
303
|
+
|
|
304
|
+
for (const file of sceneFiles) {
|
|
305
|
+
try {
|
|
306
|
+
const json = file.type === "glb"
|
|
307
|
+
? readGlbJsonChunk(file.path)
|
|
308
|
+
: readGltfJsonFile(file.path);
|
|
309
|
+
if (!json || typeof json !== "object") continue;
|
|
310
|
+
|
|
311
|
+
const used = Array.isArray(json.extensionsUsed) ? json.extensionsUsed : [];
|
|
312
|
+
for (const ext of used) {
|
|
313
|
+
if (typeof ext === "string") extensions.add(ext);
|
|
314
|
+
}
|
|
315
|
+
if (json.extensions && typeof json.extensions === "object") {
|
|
316
|
+
for (const ext of Object.keys(json.extensions)) {
|
|
317
|
+
extensions.add(ext);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
totalNodeCount += Array.isArray(json.nodes) ? json.nodes.length : 0;
|
|
322
|
+
totalTextureCount += Array.isArray(json.textures) ? json.textures.length : 0;
|
|
323
|
+
|
|
324
|
+
if (Array.isArray(json.bufferViews)) {
|
|
325
|
+
for (const view of json.bufferViews) {
|
|
326
|
+
const hasMeshopt = !!view?.extensions?.EXT_meshopt_compression;
|
|
327
|
+
if (hasMeshopt) meshoptBufferViewCount++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const accessors = Array.isArray(json.accessors) ? json.accessors : [];
|
|
332
|
+
const meshes = Array.isArray(json.meshes) ? json.meshes : [];
|
|
333
|
+
totalMeshCount += meshes.length;
|
|
334
|
+
|
|
335
|
+
for (const mesh of meshes) {
|
|
336
|
+
const primitives = Array.isArray(mesh?.primitives) ? mesh.primitives : [];
|
|
337
|
+
totalPrimitiveCount += primitives.length;
|
|
338
|
+
|
|
339
|
+
for (const primitive of primitives) {
|
|
340
|
+
const hasDraco = !!primitive?.extensions?.KHR_draco_mesh_compression;
|
|
341
|
+
if (hasDraco) dracoPrimitiveCount++;
|
|
342
|
+
|
|
343
|
+
const positionAccessorIndex = getPrimitivePositionAccessorIndex(primitive);
|
|
344
|
+
if (positionAccessorIndex < 0) continue;
|
|
345
|
+
|
|
346
|
+
const accessor = accessors[positionAccessorIndex];
|
|
347
|
+
if (!accessor || typeof accessor.count !== "number") continue;
|
|
348
|
+
totalVertexCount += accessor.count;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const componentNames = collectNeedleComponentNames(json);
|
|
353
|
+
needleExtensionBlobs.push(...collectNeedleComponentExtensionBlobs(json));
|
|
354
|
+
for (const component of componentNames) {
|
|
355
|
+
componentTypes.add(component);
|
|
356
|
+
componentCounts.set(component, (componentCounts.get(component) || 0) + 1);
|
|
357
|
+
if (!hasWebXRComponent && /\bWebXR\b|\bXRRig\b|\bWebARSessionRoot\b/i.test(component)) {
|
|
358
|
+
hasWebXRComponent = true;
|
|
359
|
+
}
|
|
360
|
+
if (!hasVideoPlayerComponent && /\bVideoPlayer\b/i.test(component)) {
|
|
361
|
+
hasVideoPlayerComponent = true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if ((!hasWebXRComponent || !hasVideoPlayerComponent) && componentNames.length > 0) {
|
|
366
|
+
const blob = componentNames.join(" ");
|
|
367
|
+
if (!hasWebXRComponent && /\bWebXR\b|\bXRRig\b|\bWebARSessionRoot\b/i.test(blob)) {
|
|
368
|
+
hasWebXRComponent = true;
|
|
369
|
+
}
|
|
370
|
+
if (!hasVideoPlayerComponent && /\bVideoPlayer\b/i.test(blob)) {
|
|
371
|
+
hasVideoPlayerComponent = true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (_e) {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!hasWebXRComponent || !hasVideoPlayerComponent) {
|
|
380
|
+
for (const blob of needleExtensionBlobs) {
|
|
381
|
+
if (!hasWebXRComponent && /\bWebXR\b|\bXRRig\b|\bWebARSessionRoot\b/i.test(blob)) {
|
|
382
|
+
hasWebXRComponent = true;
|
|
383
|
+
}
|
|
384
|
+
if (!hasVideoPlayerComponent && /\bVideoPlayer\b/i.test(blob)) {
|
|
385
|
+
hasVideoPlayerComponent = true;
|
|
386
|
+
}
|
|
387
|
+
if (hasWebXRComponent && hasVideoPlayerComponent) break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
hasWebXRComponent,
|
|
393
|
+
hasVideoPlayerComponent,
|
|
394
|
+
extensions,
|
|
395
|
+
componentTypes,
|
|
396
|
+
componentCounts,
|
|
397
|
+
glbFileCount: sceneFiles.filter(f => f.type === "glb").length,
|
|
398
|
+
gltfFileCount: sceneFiles.filter(f => f.type === "gltf").length,
|
|
399
|
+
totalSceneFileCount: sceneFiles.length,
|
|
400
|
+
totalNodeCount,
|
|
401
|
+
totalVertexCount,
|
|
402
|
+
totalTextureCount,
|
|
403
|
+
totalMeshCount,
|
|
404
|
+
totalPrimitiveCount,
|
|
405
|
+
dracoPrimitiveCount,
|
|
406
|
+
meshoptBufferViewCount,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @param {Record<string, unknown> | null | undefined} primitive
|
|
412
|
+
* @returns {number}
|
|
413
|
+
*/
|
|
414
|
+
function getPrimitivePositionAccessorIndex(primitive) {
|
|
415
|
+
const direct = primitive?.attributes?.POSITION;
|
|
416
|
+
if (typeof direct === "number") return direct;
|
|
417
|
+
|
|
418
|
+
const draco = primitive?.extensions?.KHR_draco_mesh_compression?.attributes?.POSITION;
|
|
419
|
+
if (typeof draco === "number") return draco;
|
|
420
|
+
|
|
421
|
+
const fallback = firstNumericValue(primitive?.attributes);
|
|
422
|
+
if (fallback >= 0) return fallback;
|
|
423
|
+
|
|
424
|
+
const dracoFallback = firstNumericValue(primitive?.extensions?.KHR_draco_mesh_compression?.attributes);
|
|
425
|
+
if (dracoFallback >= 0) return dracoFallback;
|
|
426
|
+
|
|
427
|
+
return -1;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @param {Record<string, unknown> | null | undefined} obj
|
|
432
|
+
* @returns {number}
|
|
433
|
+
*/
|
|
434
|
+
function firstNumericValue(obj) {
|
|
435
|
+
if (!obj || typeof obj !== "object") return -1;
|
|
436
|
+
const values = Object.values(obj);
|
|
437
|
+
for (const value of values) {
|
|
438
|
+
if (typeof value === "number") return value;
|
|
439
|
+
}
|
|
440
|
+
return -1;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @param {unknown} json
|
|
445
|
+
* @returns {string[]}
|
|
446
|
+
*/
|
|
447
|
+
function collectNeedleComponentNames(json) {
|
|
448
|
+
const names = new Set();
|
|
449
|
+
|
|
450
|
+
/** @param {unknown} node */
|
|
451
|
+
function visit(node) {
|
|
452
|
+
if (!node || typeof node !== "object") return;
|
|
453
|
+
if (Array.isArray(node)) {
|
|
454
|
+
for (const item of node) visit(item);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
for (const [key, value] of Object.entries(node)) {
|
|
459
|
+
if (key === NEEDLE_COMPONENTS_EXTENSION && value) {
|
|
460
|
+
collectComponentNamesFromNeedleExtension(value, names);
|
|
461
|
+
}
|
|
462
|
+
visit(value);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
visit(json);
|
|
467
|
+
return Array.from(names);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {import('./local-files-types.js').NeedleComponentExtension | null | undefined} value
|
|
472
|
+
* @param {Set<string>} names
|
|
473
|
+
*/
|
|
474
|
+
function collectComponentNamesFromNeedleExtension(value, names) {
|
|
475
|
+
const builtinComponents = Array.isArray(value?.builtin_components)
|
|
476
|
+
? value.builtin_components
|
|
477
|
+
: [];
|
|
478
|
+
|
|
479
|
+
for (const entry of builtinComponents) {
|
|
480
|
+
const candidateName = getBuiltinComponentName(entry);
|
|
481
|
+
if (candidateName) names.add(candidateName);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @param {import('./local-files-types.js').NeedleComponentEntry | null | undefined} node
|
|
487
|
+
* @returns {string}
|
|
488
|
+
*/
|
|
489
|
+
function getBuiltinComponentName(node) {
|
|
490
|
+
if (!node || typeof node !== "object") return "";
|
|
491
|
+
if (typeof node.name === "string" && node.name.trim()) return node.name.trim();
|
|
492
|
+
return "";
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @param {SceneAnalysisReport} report
|
|
497
|
+
* @param {AutoPolicy | null} autoPolicy
|
|
498
|
+
* @param {string} projectDir
|
|
499
|
+
* @param {string} mode
|
|
500
|
+
* @returns {string}
|
|
501
|
+
*/
|
|
502
|
+
function formatSceneAnalysisReport(report, autoPolicy, projectDir, mode) {
|
|
503
|
+
const supportsColor = needleSupportsColor();
|
|
504
|
+
const projectName = relative(process.cwd(), projectDir) || ".";
|
|
505
|
+
const componentList = report.componentCounts?.size > 0
|
|
506
|
+
? Array.from(report.componentCounts.entries())
|
|
507
|
+
.sort((a, b) => String(a[0]).localeCompare(String(b[0])))
|
|
508
|
+
.map(([name, count]) => {
|
|
509
|
+
const label = String(name);
|
|
510
|
+
if (!count || count <= 1) return label;
|
|
511
|
+
const suffix = ` ${count}×`;
|
|
512
|
+
return supportsColor ? `${label}${needleDim(suffix)}` : `${label}${suffix}`;
|
|
513
|
+
})
|
|
514
|
+
.join(", ")
|
|
515
|
+
: "(none)";
|
|
516
|
+
const extensionList = report.extensions.size > 0
|
|
517
|
+
? Array.from(report.extensions).sort((a, b) => a.localeCompare(b)).join(", ")
|
|
518
|
+
: "(none)";
|
|
519
|
+
const autoFeatures = autoPolicy?.features?.size
|
|
520
|
+
? Array.from(autoPolicy.features).sort((a, b) => a.localeCompare(b)).join(", ")
|
|
521
|
+
: "(n/a)";
|
|
522
|
+
|
|
523
|
+
const key = (text) => supportsColor ? needleBlue(text) : text;
|
|
524
|
+
|
|
525
|
+
const lines = [
|
|
526
|
+
"Scene analysis report",
|
|
527
|
+
key("Project") + " : " + projectName,
|
|
528
|
+
key("Scene files") + " : " + report.totalSceneFileCount + " (" + report.glbFileCount + " .glb, " + report.gltfFileCount + " .gltf)",
|
|
529
|
+
key("Nodes") + " : " + report.totalNodeCount,
|
|
530
|
+
key("Meshes / Primitives") + " : " + report.totalMeshCount + " / " + report.totalPrimitiveCount,
|
|
531
|
+
key("Vertices") + " : " + report.totalVertexCount,
|
|
532
|
+
key("Textures") + " : " + report.totalTextureCount,
|
|
533
|
+
key("Compression usage") + " : Draco primitives=" + report.dracoPrimitiveCount + ", Meshopt bufferViews=" + report.meshoptBufferViewCount,
|
|
534
|
+
key("Components") + " : " + componentList,
|
|
535
|
+
key("Extensions") + " : " + extensionList,
|
|
536
|
+
key("Detected Features") + " : " + autoFeatures,
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
return lines.join("\n");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @param {string} projectDir
|
|
544
|
+
* @returns {string[]}
|
|
545
|
+
*/
|
|
546
|
+
export function collectAssetGlbs(projectDir) {
|
|
547
|
+
const files = collectAssetSceneFiles(projectDir);
|
|
548
|
+
return files.filter(file => file.type === "glb").map(file => file.path);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @param {string} projectDir
|
|
553
|
+
* @returns {SceneFile[]}
|
|
554
|
+
*/
|
|
555
|
+
function collectAssetSceneFiles(projectDir) {
|
|
556
|
+
const assetsDir = join(projectDir, "assets");
|
|
557
|
+
if (!existsSync(assetsDir)) return [];
|
|
558
|
+
|
|
559
|
+
/** @type {SceneFile[]} */
|
|
560
|
+
const out = [];
|
|
561
|
+
|
|
562
|
+
/** @param {string} dir */
|
|
563
|
+
function walk(dir) {
|
|
564
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
const fullPath = join(dir, entry.name);
|
|
567
|
+
if (entry.isDirectory()) {
|
|
568
|
+
walk(fullPath);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (/^image_\d+_.*\.glb$/i.test(entry.name)) continue;
|
|
573
|
+
if (/^mesh_lod_\d+_.*\.glb$/i.test(entry.name)) continue;
|
|
574
|
+
|
|
575
|
+
if (/\.glb$/i.test(entry.name)) {
|
|
576
|
+
out.push({ path: fullPath, type: "glb" });
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (/\.gltf$/i.test(entry.name)) {
|
|
580
|
+
out.push({ path: fullPath, type: "gltf" });
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
walk(assetsDir);
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @param {string} filePath
|
|
592
|
+
* @returns {unknown}
|
|
593
|
+
*/
|
|
594
|
+
function readGltfJsonFile(filePath) {
|
|
595
|
+
const text = readFileSync(filePath, "utf8");
|
|
596
|
+
return JSON.parse(text);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @param {string} filePath
|
|
601
|
+
* @returns {Record<string, unknown>}
|
|
602
|
+
*/
|
|
603
|
+
export function readGlbJsonChunk(filePath) {
|
|
604
|
+
const fd = openSync(filePath, "r");
|
|
605
|
+
try {
|
|
606
|
+
const header = Buffer.allocUnsafe(20);
|
|
607
|
+
const bytesRead = readSync(fd, header, 0, header.length, 0);
|
|
608
|
+
if (bytesRead < 20) throw new Error("Invalid GLB header: " + filePath);
|
|
609
|
+
|
|
610
|
+
const magic = header.readUInt32LE(0);
|
|
611
|
+
const version = header.readUInt32LE(4);
|
|
612
|
+
const chunkLength = header.readUInt32LE(12);
|
|
613
|
+
const chunkType = header.readUInt32LE(16);
|
|
614
|
+
|
|
615
|
+
if (magic !== 0x46546c67 || version !== 2) throw new Error("Not a GLB v2: " + filePath);
|
|
616
|
+
if (chunkType !== 0x4E4F534A) throw new Error("First GLB chunk is not JSON: " + filePath);
|
|
617
|
+
|
|
618
|
+
const jsonBuffer = Buffer.allocUnsafe(chunkLength);
|
|
619
|
+
const jsonBytesRead = readSync(fd, jsonBuffer, 0, chunkLength, 20);
|
|
620
|
+
if (jsonBytesRead < chunkLength) throw new Error("Failed to read GLB JSON chunk: " + filePath);
|
|
621
|
+
|
|
622
|
+
const jsonText = jsonBuffer.toString("utf8").replace(/\u0000+$/g, "");
|
|
623
|
+
return JSON.parse(jsonText);
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
closeSync(fd);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* @param {unknown} json
|
|
632
|
+
* @returns {string[]}
|
|
633
|
+
*/
|
|
634
|
+
export function collectNeedleComponentExtensionBlobs(json) {
|
|
635
|
+
const blobs = /** @type {string[]} */ ([]);
|
|
636
|
+
|
|
637
|
+
/** @param {unknown} node */
|
|
638
|
+
function visit(node) {
|
|
639
|
+
if (!node || typeof node !== "object") return;
|
|
640
|
+
if (Array.isArray(node)) {
|
|
641
|
+
for (const item of node) visit(item);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const [key, value] of Object.entries(node)) {
|
|
646
|
+
if (key === NEEDLE_COMPONENTS_EXTENSION && value) {
|
|
647
|
+
try {
|
|
648
|
+
blobs.push(JSON.stringify(value));
|
|
649
|
+
}
|
|
650
|
+
catch (_e) { }
|
|
651
|
+
}
|
|
652
|
+
visit(value);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
visit(json);
|
|
657
|
+
return blobs;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* @param {string} indexHtml
|
|
662
|
+
* @returns {Set<string>}
|
|
663
|
+
*/
|
|
664
|
+
export function collectIndexHtmlSkyboxUrls(indexHtml) {
|
|
665
|
+
const urls = new Set();
|
|
666
|
+
if (!indexHtml) return urls;
|
|
667
|
+
|
|
668
|
+
const attrRegex = /\b(background-image|environment-image)\s*=\s*["']([^"']+)["']/gi;
|
|
669
|
+
let match;
|
|
670
|
+
while ((match = attrRegex.exec(indexHtml)) !== null) {
|
|
671
|
+
const value = (match[2] || "").trim();
|
|
672
|
+
const resolved = resolveSkyboxValueToUrl(value);
|
|
673
|
+
if (resolved) urls.add(resolved);
|
|
674
|
+
}
|
|
675
|
+
return urls;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @param {string | string[] | undefined | null} skyboxOption
|
|
680
|
+
* @param {Set<string>} indexSkyboxUrls
|
|
681
|
+
* @returns {Set<string> | null}
|
|
682
|
+
*/
|
|
683
|
+
export function resolveSkyboxSelectionUrls(skyboxOption, indexSkyboxUrls) {
|
|
684
|
+
if (skyboxOption === "all") return null;
|
|
685
|
+
|
|
686
|
+
if (Array.isArray(skyboxOption)) {
|
|
687
|
+
const urls = new Set();
|
|
688
|
+
for (const entry of skyboxOption) {
|
|
689
|
+
const resolved = resolveSkyboxValueToUrl(entry);
|
|
690
|
+
if (resolved) urls.add(resolved);
|
|
691
|
+
}
|
|
692
|
+
return urls;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return indexSkyboxUrls;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* @param {string | null | undefined} value
|
|
700
|
+
* @returns {string | null}
|
|
701
|
+
*/
|
|
702
|
+
export function resolveSkyboxValueToUrl(value) {
|
|
703
|
+
if (!value) return null;
|
|
704
|
+
if (/^https?:\/\//i.test(value)) return value;
|
|
705
|
+
|
|
706
|
+
const normalized = value.replace(/^\/+/, "");
|
|
707
|
+
if (/\.ktx2$/i.test(normalized)) return SKYBOX_BASE_URL + normalized;
|
|
708
|
+
|
|
709
|
+
if (/^[a-z0-9\-]+$/i.test(normalized)) {
|
|
710
|
+
return SKYBOX_BASE_URL + normalized + ".ktx2";
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* @param {string | undefined} mode
|
|
717
|
+
* @returns {string[]}
|
|
718
|
+
*/
|
|
719
|
+
export function getWebXRProfilesForMode(mode) {
|
|
720
|
+
const allProfiles = [
|
|
721
|
+
"generic-hand",
|
|
722
|
+
"generic-trigger",
|
|
723
|
+
"oculus-touch-v2",
|
|
724
|
+
"oculus-touch-v3",
|
|
725
|
+
"meta-quest-touch-pro",
|
|
726
|
+
"pico-4",
|
|
727
|
+
"pico-neo3",
|
|
728
|
+
];
|
|
729
|
+
|
|
730
|
+
if (mode === "minimal") return ["generic-hand", "generic-trigger"];
|
|
731
|
+
if (mode === "quest") return ["generic-hand", "generic-trigger", "oculus-touch-v2", "oculus-touch-v3", "meta-quest-touch-pro"];
|
|
732
|
+
if (mode === "pico") return ["generic-hand", "generic-trigger", "pico-4", "pico-neo3"];
|
|
733
|
+
return allProfiles;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @param {string} dir
|
|
738
|
+
* @param {number} [depth]
|
|
739
|
+
* @returns {string}
|
|
740
|
+
*/
|
|
741
|
+
function collectSourceFiles(dir, depth = 0) {
|
|
742
|
+
if (depth > 5) return "";
|
|
743
|
+
if (!existsSync(dir)) return "";
|
|
744
|
+
|
|
745
|
+
let content = "";
|
|
746
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
747
|
+
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
750
|
+
|
|
751
|
+
const fullPath = join(dir, entry.name);
|
|
752
|
+
if (entry.isDirectory()) {
|
|
753
|
+
content += collectSourceFiles(fullPath, depth + 1);
|
|
754
|
+
}
|
|
755
|
+
else if (/\.(ts|js|tsx|jsx|html|vue|svelte)$/i.test(entry.name)) {
|
|
756
|
+
try {
|
|
757
|
+
content += readFileSync(fullPath, "utf-8") + "\n";
|
|
758
|
+
}
|
|
759
|
+
catch (_e) { }
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return content;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* @param {string} url
|
|
767
|
+
* @param {UrlHandler} handler
|
|
768
|
+
* @param {{ options: LocalizationOptions, autoPolicy: AutoPolicy | null }} context
|
|
769
|
+
* @returns {boolean}
|
|
770
|
+
*/
|
|
771
|
+
export function shouldHandleUrlInAutoMode(url, handler, context) {
|
|
772
|
+
if (!hasAutoFeatureSelection(context.options.features) || !context.autoPolicy) return true;
|
|
773
|
+
|
|
774
|
+
if (handler.feature === "cdn-scripts" && url.includes(HLS_CDN_SEGMENT)) {
|
|
775
|
+
return context.autoPolicy.hasVideoPlayer;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (handler.feature === "needle-avatars" && /\/static\/avatars\/default/i.test(url) && !context.autoPolicy.hasWebXR) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (handler.feature === "skybox") {
|
|
783
|
+
const allowed = context.autoPolicy.allowedSkyboxUrls;
|
|
784
|
+
if (!allowed) return true;
|
|
785
|
+
return allowed.has(url);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return true;
|
|
789
|
+
}
|