@needle-tools/engine 4.5.8-next.e775d5d → 4.6.0-next.1bf6c80
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 +10 -0
- package/dist/{gltf-progressive-Bx_ZJgfQ.min.js → gltf-progressive-Bm9eEfgu.min.js} +1 -1
- package/dist/{gltf-progressive-Ccf3INjK.umd.cjs → gltf-progressive-Dn6o99rH.umd.cjs} +1 -1
- package/dist/{gltf-progressive-D8GP6sjZ.js → gltf-progressive-GjIqwSG3.js} +2 -2
- package/dist/{needle-engine.bundle-DJj-8wQv.js → needle-engine.bundle-CAOdqmHM.js} +2595 -2584
- package/dist/{needle-engine.bundle-BiAJyneD.min.js → needle-engine.bundle-DIa3s45l.min.js} +100 -100
- package/dist/{needle-engine.bundle-CTuPA4BD.umd.cjs → needle-engine.bundle-DVMOm-oS.umd.cjs} +106 -106
- package/dist/needle-engine.js +4 -4
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-CLwnKusi.umd.cjs → postprocessing-CRQa6Qxn.umd.cjs} +177 -97
- package/dist/{postprocessing-DS5I4Iim.js → postprocessing-D6W1EyZ-.js} +1095 -1001
- package/dist/{postprocessing-CdIScgB8.min.js → postprocessing-DF8AlRgW.min.js} +178 -98
- package/dist/{three-CFtLGNtV.min.js → three-Boa-jOq-.min.js} +18 -18
- package/dist/{three-1JG7vpNC.js → three-Bz6X1mrw.js} +0 -1
- package/dist/{three-f-JuZtOs.umd.cjs → three-DMrv-4ar.umd.cjs} +20 -20
- package/dist/{three-examples-BeoqFh8m.umd.cjs → three-examples-C7ryg8vN.umd.cjs} +1 -1
- package/dist/{three-examples-CPlN41GO.min.js → three-examples-DuVhxqft.min.js} +1 -1
- package/dist/{three-examples-CyWe9wwo.js → three-examples-GggCDHv0.js} +1 -1
- package/dist/{three-mesh-ui-B18kdQmk.js → three-mesh-ui-CLNOfsWn.js} +1 -1
- package/dist/{three-mesh-ui-BMeLuYD5.min.js → three-mesh-ui-CY6Izc7C.min.js} +1 -1
- package/dist/{three-mesh-ui-DMdtSVhY.umd.cjs → three-mesh-ui-CwlN0FUC.umd.cjs} +1 -1
- package/dist/{vendor-BbM-oI6p.js → vendor-BSD1RQIh.js} +1 -1
- package/dist/{vendor-CJFfVMoq.umd.cjs → vendor-DHr4aqIZ.umd.cjs} +1 -1
- package/dist/{vendor-CTtGKiDe.min.js → vendor-zxXa3Dmr.min.js} +1 -1
- package/lib/engine/engine_assetdatabase.js +3 -1
- package/lib/engine/engine_assetdatabase.js.map +1 -1
- package/lib/engine/engine_context.d.ts +2 -2
- package/lib/engine/engine_context.js +11 -10
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_utils.js +1 -1
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +1 -1
- package/lib/engine-components/AudioSource.js +5 -4
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Skybox.d.ts +7 -1
- package/lib/engine-components/Skybox.js +30 -9
- package/lib/engine-components/Skybox.js.map +1 -1
- package/package.json +1 -1
- package/plugins/types/userconfig.d.ts +1 -1
- package/plugins/vite/asap.js +26 -20
- package/plugins/vite/local-files.js +268 -59
- package/src/engine/engine_assetdatabase.ts +3 -1
- package/src/engine/engine_context.ts +14 -11
- package/src/engine/engine_utils.ts +1 -1
- package/src/engine-components/AudioSource.ts +5 -5
- package/src/engine-components/Skybox.ts +30 -10
|
@@ -1,116 +1,290 @@
|
|
|
1
1
|
import https from 'https';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { start } from 'repl';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
* @typedef {{pluginContext:import('rollup').TransformPluginContext, cache:Cache}} Context
|
|
8
|
+
* @typedef {{pluginContext:import('rollup').TransformPluginContext, cache:Cache, command:string, viteConfig:import("vite").ResolvedConfig | null}} Context
|
|
7
9
|
*/
|
|
8
10
|
|
|
11
|
+
const debug = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the local files plugin is enabled in user settings.
|
|
15
|
+
* @param {import('../types/userconfig.js').userSettings} userSettings - The user settings object
|
|
16
|
+
*/
|
|
17
|
+
export const makeFilesLocalIsEnabled = (userSettings) => {
|
|
18
|
+
if (typeof userSettings?.makeFilesLocal === "object") return userSettings?.makeFilesLocal?.enabled === true;
|
|
19
|
+
return userSettings?.makeFilesLocal === true;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
/**
|
|
10
23
|
* Download files and rewrite code
|
|
11
24
|
* @param {string} command - The command that is being run
|
|
12
25
|
* @param {object} config - The config object
|
|
13
26
|
* @param {import('../types/userconfig.js').userSettings} userSettings
|
|
14
|
-
* @returns {import('vite').Plugin}
|
|
27
|
+
* @returns {import('vite').Plugin | null}
|
|
15
28
|
*/
|
|
16
29
|
export const needleMakeFilesLocal = (command, config, userSettings) => {
|
|
17
30
|
|
|
18
|
-
if (!userSettings
|
|
19
|
-
return;
|
|
31
|
+
if (!makeFilesLocalIsEnabled(userSettings)) {
|
|
32
|
+
return null;
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
console.log(`[needle:local-files] Local files plugin is enabled`);
|
|
23
36
|
|
|
24
37
|
const cache = new Cache();
|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
/** @type {import("vite").ResolvedConfig | null} */
|
|
40
|
+
let viteConfig = null;
|
|
41
|
+
|
|
42
|
+
/** @type {import("vite").Plugin} */
|
|
43
|
+
const plugin = {
|
|
27
44
|
name: "needle:local-files",
|
|
28
|
-
enforce: 'pre',
|
|
45
|
+
// enforce: 'pre', // explictly DON'T define enforce:pre because of svelte postcss compat
|
|
29
46
|
apply: "build",
|
|
47
|
+
configResolved(config) {
|
|
48
|
+
viteConfig = config;
|
|
49
|
+
},
|
|
30
50
|
// transform bundle
|
|
31
|
-
async transform(src,
|
|
51
|
+
async transform(src, _id) {
|
|
32
52
|
src = await makeLocal(src, "ext/", "", {
|
|
33
53
|
pluginContext: this,
|
|
34
54
|
cache: cache,
|
|
55
|
+
command: command,
|
|
56
|
+
viteConfig: viteConfig,
|
|
35
57
|
});
|
|
36
58
|
return {
|
|
37
59
|
code: src,
|
|
38
60
|
map: null,
|
|
39
61
|
};
|
|
62
|
+
},
|
|
63
|
+
buildEnd() {
|
|
64
|
+
const map = cache.map;
|
|
65
|
+
console.log(""); // Make a new line for better readability
|
|
66
|
+
console.log(`[needle:local-files] Made ${map.size} files local:`);
|
|
67
|
+
for (const [key, value] of map.entries()) {
|
|
68
|
+
console.log(`- ${key} → ${value}`);
|
|
69
|
+
}
|
|
40
70
|
}
|
|
41
71
|
}
|
|
42
|
-
|
|
72
|
+
return plugin;
|
|
43
73
|
}
|
|
44
74
|
|
|
45
|
-
|
|
46
75
|
/**
|
|
47
76
|
* Rewrites the source code to make local files
|
|
48
77
|
* @param {string} src - The source code to rewrite
|
|
49
78
|
* @param {string} basePath - The base path where the files will be saved
|
|
50
79
|
* @param {string} currentDir - The current directory of the file being processed
|
|
51
|
-
* @param {Context} context - The Vite plugin context
|
|
80
|
+
* @param {Context} context - The Vite plugin context and command
|
|
52
81
|
*/
|
|
53
82
|
async function makeLocal(src, basePath, currentDir, context) {
|
|
54
83
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
const command = context.command;
|
|
85
|
+
|
|
86
|
+
if (debug) {
|
|
87
|
+
// Find all urls in the source code.
|
|
88
|
+
// Exclude URLs inside comments, like:
|
|
89
|
+
// - // https://example.com
|
|
90
|
+
// - /* ... https://example.com ... */
|
|
91
|
+
// - @link https://example.com
|
|
92
|
+
// - * ... https://example.com
|
|
93
|
+
const urlRegexExcludingComments = /(?<!\/\/.*)(?<!\/\*.*)(?<!@link\s+)(["'])(https?:\/\/[^\s'"]+?)\1/g;
|
|
94
|
+
let match0;
|
|
95
|
+
while ((match0 = urlRegexExcludingComments.exec(src)) !== null) {
|
|
96
|
+
const url = match0[2];
|
|
97
|
+
if (debug) console.log(`\nFound URL: ${url} in ${currentDir}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
// Google Fonts URLs
|
|
102
|
+
while (true) {
|
|
103
|
+
const match = /["'](https:\/\/fonts\.googleapis\.com\/.+?)["']/g.exec(src);
|
|
104
|
+
if (match === null) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
const url = match[1];
|
|
108
|
+
if (debug) console.log(`\nFound google font URL: ${url}`);
|
|
62
109
|
// Check if the font URL is already in the cache
|
|
63
|
-
const cachedPath = context.cache.getFromCache(
|
|
110
|
+
const cachedPath = context.cache.getFromCache(url);
|
|
64
111
|
if (cachedPath) {
|
|
65
|
-
console.log(`Using cached font URL: ${cachedPath}`);
|
|
66
|
-
src = src.replace(
|
|
112
|
+
if (debug) console.log(`Using cached font URL: ${cachedPath}`);
|
|
113
|
+
src = src.replace(url, cachedPath);
|
|
67
114
|
continue; // Skip downloading if already cached
|
|
68
115
|
}
|
|
69
|
-
let font = await downloadText(
|
|
70
|
-
const familyNameMatch = /family=([^&]+)/.exec(
|
|
71
|
-
const familyName = getValidFilename(familyNameMatch[1], font);
|
|
116
|
+
let font = await downloadText(url);
|
|
117
|
+
const familyNameMatch = /family=([^&]+)/.exec(url);
|
|
118
|
+
const familyName = familyNameMatch ? getValidFilename(familyNameMatch[1], font) : (new URL(url).pathname.split('/').pop());
|
|
72
119
|
font = await makeLocal(font, basePath, basePath, context);
|
|
73
120
|
const fontFileName = `font-${familyName}.css`;
|
|
74
121
|
const outputPath = basePath + fontFileName;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
122
|
+
let newPath;
|
|
123
|
+
if (command === 'build') {
|
|
124
|
+
const referenceId = context.pluginContext.emitFile({
|
|
125
|
+
type: 'asset',
|
|
126
|
+
fileName: outputPath,
|
|
127
|
+
source: font,
|
|
128
|
+
});
|
|
129
|
+
const localPath = `${context.pluginContext.getFileName(referenceId)}`;
|
|
130
|
+
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
131
|
+
// ensureFileExists(localPath, font);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Create base64 URL for dev mode
|
|
135
|
+
const base64Font = Buffer.from(font).toString('base64');
|
|
136
|
+
newPath = `data:text/css;base64,${base64Font}`;
|
|
137
|
+
}
|
|
138
|
+
if (newPath) {
|
|
139
|
+
context.cache.addToCache(url, newPath);
|
|
140
|
+
src = src.replace(url, newPath);
|
|
141
|
+
}
|
|
84
142
|
}
|
|
85
143
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
144
|
+
// Google Fonts gstatic URLs
|
|
145
|
+
while (true) {
|
|
146
|
+
const match = /["'(](https:\/\/fonts\.gstatic\.com\/)(.+?)["')]/g.exec(src);
|
|
147
|
+
if (match === null) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
89
150
|
const fontPath = match[2];
|
|
90
|
-
const
|
|
91
|
-
console.log(`\nFound gstatic URL: ${
|
|
151
|
+
const url = match[1] + fontPath;
|
|
152
|
+
if (debug) console.log(`\nFound gstatic URL: ${url}`);
|
|
92
153
|
// Check if the font URL is already in the cache
|
|
93
|
-
const cachedPath = context.cache.getFromCache(
|
|
154
|
+
const cachedPath = context.cache.getFromCache(url);
|
|
94
155
|
if (cachedPath) {
|
|
95
|
-
console.log(`Using cached gstatic font URL: ${cachedPath}`);
|
|
96
|
-
src = src.replace(
|
|
156
|
+
if (debug) console.log(`Using cached gstatic font URL: ${cachedPath}`);
|
|
157
|
+
src = src.replace(url, cachedPath);
|
|
97
158
|
continue; // Skip downloading if already cached
|
|
98
159
|
}
|
|
99
|
-
|
|
160
|
+
const font = await downloadBinary(url);
|
|
100
161
|
const filename = getValidFilename(fontPath, font);
|
|
101
|
-
console.log(`Saving font to: ${basePath + filename}`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
if (debug) console.log(`Saving font to: ${basePath + filename}`);
|
|
163
|
+
let newPath;
|
|
164
|
+
if (command === 'build') {
|
|
165
|
+
const referenceId = context.pluginContext.emitFile({
|
|
166
|
+
type: 'asset',
|
|
167
|
+
fileName: basePath + filename,
|
|
168
|
+
source: font,
|
|
169
|
+
});
|
|
170
|
+
const localPath = `${context.pluginContext.getFileName(referenceId)}`;
|
|
171
|
+
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
172
|
+
// ensureFileExists(localPath, font);
|
|
173
|
+
} else {
|
|
174
|
+
// Create base64 URL for dev mode
|
|
175
|
+
const base64Font = Buffer.from(font).toString('base64');
|
|
176
|
+
newPath = `data:text/css;base64,${base64Font}`;
|
|
177
|
+
}
|
|
178
|
+
context.cache.addToCache(url, newPath);
|
|
179
|
+
src = src.replace(url, newPath);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Load QRCode.js
|
|
183
|
+
while (true) {
|
|
184
|
+
// https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs@gh-pages/qrcode.min.js
|
|
185
|
+
const match = /["'](https:\/\/cdn\.jsdelivr\.net\/gh\/davidshimjs\/qrcodejs@[^'"]+?\/qrcode\.min\.js)["']/g.exec(src);
|
|
186
|
+
if (match === null) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
const url = match[1];
|
|
190
|
+
if (debug) console.log(`\nFound QR code URL: ${url}`);
|
|
191
|
+
// Check if the QR code URL is already in the cache
|
|
192
|
+
const cachedPath = context.cache.getFromCache(url);
|
|
193
|
+
if (cachedPath) {
|
|
194
|
+
if (debug) console.log(`Using cached QR code URL: ${cachedPath}`);
|
|
195
|
+
src = src.replace(url, cachedPath);
|
|
196
|
+
continue; // Skip downloading if already cached
|
|
197
|
+
}
|
|
198
|
+
const qrCode = await downloadBinary(url);
|
|
199
|
+
const filename = getValidFilename(url, qrCode);
|
|
200
|
+
if (debug) console.log(`Saving QR code to: ${basePath + filename}`);
|
|
201
|
+
let newPath;
|
|
202
|
+
if (command === 'build') {
|
|
203
|
+
const referenceId = context.pluginContext.emitFile({
|
|
204
|
+
type: 'asset',
|
|
205
|
+
fileName: basePath + filename,
|
|
206
|
+
source: qrCode,
|
|
207
|
+
});
|
|
208
|
+
const localPath = `${context.pluginContext.getFileName(referenceId)}`;
|
|
209
|
+
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
210
|
+
// ensureFileExists(localPath, qrCode);
|
|
211
|
+
} else {
|
|
212
|
+
// create base64 URL for dev mode
|
|
213
|
+
const base64QrCode = Buffer.from(qrCode).toString('base64');
|
|
214
|
+
newPath = `data:application/javascript;base64,${base64QrCode}`;
|
|
215
|
+
}
|
|
216
|
+
context.cache.addToCache(url, newPath);
|
|
217
|
+
src = src.replace(url, newPath);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Polyhaven.org URLs
|
|
221
|
+
let startIndex = 0;
|
|
222
|
+
while (true) {
|
|
223
|
+
const match = /["'](https:\/\/dl\.polyhaven\.org\/file\/.+?)["']/g.exec(src.slice(startIndex));
|
|
224
|
+
if (match === null) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
startIndex += match.index + match[0].length; // Update startIndex to continue searching
|
|
228
|
+
const url = match[1];
|
|
229
|
+
if (url.endsWith("/")) {
|
|
230
|
+
if (debug) console.warn(`Skipping Polyhaven URL that ends with a slash: ${url}`);
|
|
231
|
+
continue; // Skip URLs that end with a slash
|
|
232
|
+
}
|
|
233
|
+
if (url.includes("\"") || url.includes("'")) {
|
|
234
|
+
if (debug) console.warn(`Skipping Polyhaven URL with quotes: ${url}`);
|
|
235
|
+
continue; // Skip URLs with quotes
|
|
236
|
+
}
|
|
237
|
+
if (debug) console.log(`\nFound Polyhaven URL: ${url}`);
|
|
238
|
+
// Check if the Polyhaven URL is already in the cache
|
|
239
|
+
const cachedPath = context.cache.getFromCache(url);
|
|
240
|
+
if (cachedPath) {
|
|
241
|
+
if (debug) console.log(`Using cached Polyhaven URL: ${cachedPath}`);
|
|
242
|
+
src = src.replace(url, cachedPath);
|
|
243
|
+
continue; // Skip downloading if already cached
|
|
244
|
+
}
|
|
245
|
+
const polyhavenFile = await downloadBinary(url);
|
|
246
|
+
const filename = getValidFilename(url, polyhavenFile);
|
|
247
|
+
if (debug) console.log(`Saving Polyhaven file to: ${basePath + filename}`);
|
|
248
|
+
let newPath;
|
|
249
|
+
if (command === 'build') {
|
|
250
|
+
const referenceId = context.pluginContext.emitFile({
|
|
251
|
+
type: 'asset',
|
|
252
|
+
fileName: basePath + filename,
|
|
253
|
+
source: polyhavenFile,
|
|
254
|
+
});
|
|
255
|
+
const localPath = `${context.pluginContext.getFileName(referenceId)}`;
|
|
256
|
+
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
257
|
+
// ensureFileExists(localPath, polyhavenFile);
|
|
258
|
+
} else {
|
|
259
|
+
// Create base64 URL for dev mode
|
|
260
|
+
const base64PolyhavenFile = Buffer.from(polyhavenFile).toString('base64');
|
|
261
|
+
newPath = `data:application/octet-stream;base64,${base64PolyhavenFile}`;
|
|
262
|
+
}
|
|
263
|
+
if (newPath) {
|
|
264
|
+
context.cache.addToCache(url, newPath);
|
|
265
|
+
src = src.replace(url, newPath);
|
|
266
|
+
}
|
|
111
267
|
}
|
|
112
268
|
|
|
113
269
|
return src;
|
|
270
|
+
|
|
271
|
+
// /**
|
|
272
|
+
// * Ensures that a file exists at the specified relative path with the given content.
|
|
273
|
+
// * If the file does not exist, it will be created with the provided content.
|
|
274
|
+
// * @param {string} relPath - The relative path to the file
|
|
275
|
+
// * @param {string|Uint8Array} content - The content to write to the file
|
|
276
|
+
// * @returns {void}
|
|
277
|
+
// */
|
|
278
|
+
// function ensureFileExists(relPath, content) {
|
|
279
|
+
// const outputPath = context.viteConfig?.build?.outDir || "dist";
|
|
280
|
+
// const fullPath = resolve(outputPath, relPath);
|
|
281
|
+
// if (!existsSync(fullPath)) {
|
|
282
|
+
// if (debug) console.log(`Creating file: ${fullPath}`);
|
|
283
|
+
// const dir = resolve(fullPath, '..');
|
|
284
|
+
// mkdirSync(dir, { recursive: true });
|
|
285
|
+
// writeFileSync(fullPath, content);
|
|
286
|
+
// }
|
|
287
|
+
// }
|
|
114
288
|
}
|
|
115
289
|
|
|
116
290
|
|
|
@@ -124,7 +298,7 @@ class Cache {
|
|
|
124
298
|
*/
|
|
125
299
|
addToCache(key, value) {
|
|
126
300
|
if (this.__cache.has(key)) {
|
|
127
|
-
console.warn(`Key ${key} already exists in cache, overwriting.`);
|
|
301
|
+
if (debug) console.warn(`Key ${key} already exists in cache, overwriting.`);
|
|
128
302
|
}
|
|
129
303
|
this.__cache.set(key, value);
|
|
130
304
|
}
|
|
@@ -139,9 +313,18 @@ class Cache {
|
|
|
139
313
|
return null;
|
|
140
314
|
}
|
|
141
315
|
}
|
|
316
|
+
get map() {
|
|
317
|
+
return this.__cache;
|
|
318
|
+
}
|
|
142
319
|
}
|
|
143
320
|
|
|
144
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Returns a relative path based on the base path.
|
|
324
|
+
* @param {string} path - The path to check
|
|
325
|
+
* @param {string | undefined | null} basePath - The base path to compare against
|
|
326
|
+
* @return {string} - The relative path if it starts with the base path, otherwise the original path
|
|
327
|
+
*/
|
|
145
328
|
function getRelativeToBasePath(path, basePath) {
|
|
146
329
|
if (basePath?.length && path.startsWith(basePath)) {
|
|
147
330
|
return "./" + path.substring(basePath.length);
|
|
@@ -156,6 +339,15 @@ function getRelativeToBasePath(path, basePath) {
|
|
|
156
339
|
* @param {string|Uint8Array} content - The content to hash for uniqueness (not used in this example)
|
|
157
340
|
*/
|
|
158
341
|
function getValidFilename(path, content) {
|
|
342
|
+
if (path.startsWith("http:") || path.startsWith("https:")) {
|
|
343
|
+
const url = new URL(path);
|
|
344
|
+
const pathParts = url.pathname.split('/');
|
|
345
|
+
const filename = pathParts.pop() || 'file';
|
|
346
|
+
const nameParts = filename.split('.');
|
|
347
|
+
const nameWithoutExt = nameParts.slice(0, -1).join('.');
|
|
348
|
+
path = `${nameWithoutExt}-${createContentMd5(url.host + pathParts.join('/'))}.${nameParts.pop() || 'unknown'}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
159
351
|
// Remove any characters that are not valid in filenames
|
|
160
352
|
let name = path.replace(/[^a-z0-9_\-\.\+]/gi, '-');
|
|
161
353
|
|
|
@@ -190,20 +382,25 @@ function createContentMd5(str) {
|
|
|
190
382
|
* @param {string} url - The URL of the font to download
|
|
191
383
|
*/
|
|
192
384
|
function downloadText(url) {
|
|
193
|
-
return new Promise((res => {
|
|
385
|
+
return new Promise(((res, rej) => {
|
|
194
386
|
https.get(url, (response) => {
|
|
195
387
|
if (response.statusCode !== 200) {
|
|
196
|
-
console.
|
|
197
|
-
|
|
388
|
+
console.log();
|
|
389
|
+
console.error(`Failed to download (${response.statusCode}): ${url}`);
|
|
390
|
+
rej(new Error(`Failed to download (${response.statusCode}): ${url}`));
|
|
198
391
|
return;
|
|
199
392
|
}
|
|
393
|
+
|
|
394
|
+
console.log(""); // Make a new line for better readability
|
|
395
|
+
console.log(`[needle:local-files] Make local: ${url}`);
|
|
396
|
+
|
|
200
397
|
let data = '';
|
|
201
398
|
response.on('data', (chunk) => {
|
|
202
399
|
data += chunk;
|
|
203
400
|
});
|
|
204
401
|
response.on('end', () => {
|
|
205
402
|
// Here you can save the data to a file or process it as needed
|
|
206
|
-
console.log(`Downloaded from ${url}`);
|
|
403
|
+
if (debug) console.log(`Downloaded from ${url}`);
|
|
207
404
|
res(data);
|
|
208
405
|
});
|
|
209
406
|
});
|
|
@@ -213,18 +410,30 @@ function downloadText(url) {
|
|
|
213
410
|
function downloadBinary(url) {
|
|
214
411
|
return new Promise((res, rej) => {
|
|
215
412
|
https.get(url, (response) => {
|
|
413
|
+
|
|
414
|
+
// Handle redirects
|
|
415
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
416
|
+
if (debug) console.log(`Redirecting to ${response.headers.location}`);
|
|
417
|
+
return downloadBinary(response.headers.location).then(res).catch(rej);
|
|
418
|
+
}
|
|
419
|
+
|
|
216
420
|
if (response.statusCode !== 200) {
|
|
217
|
-
console.
|
|
218
|
-
|
|
421
|
+
console.log();
|
|
422
|
+
console.error(`Failed to download (${response.statusCode}): ${url}`);
|
|
423
|
+
rej(new Error(`Failed to download (${response.statusCode}): ${url}`));
|
|
219
424
|
return;
|
|
220
425
|
}
|
|
426
|
+
|
|
427
|
+
console.log(""); // Make a new line for better readability
|
|
428
|
+
console.log(`[needle:local-files] Make local: ${url}`);
|
|
429
|
+
|
|
221
430
|
const chunks = [];
|
|
222
431
|
response.on('data', (chunk) => {
|
|
223
432
|
chunks.push(chunk);
|
|
224
433
|
});
|
|
225
434
|
response.on('end', () => {
|
|
226
435
|
// Here you can save the data to a file or process it as needed
|
|
227
|
-
console.log(`Downloaded
|
|
436
|
+
if (debug) console.log(`Downloaded from ${url}`);
|
|
228
437
|
res(Buffer.concat(chunks));
|
|
229
438
|
});
|
|
230
439
|
});
|
|
@@ -61,7 +61,9 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
obj
|
|
64
|
+
if(typeof obj === "object") {
|
|
65
|
+
obj[$disposed] = true;
|
|
66
|
+
}
|
|
65
67
|
|
|
66
68
|
if (obj instanceof Scene) {
|
|
67
69
|
disposeObjectResources(obj.environment);
|
|
@@ -758,6 +758,9 @@ export class Context implements IContext {
|
|
|
758
758
|
this.physics?.engine?.clearCaches();
|
|
759
759
|
this.lodsManager.disable();
|
|
760
760
|
|
|
761
|
+
this._onBeforeRenderListeners.clear();
|
|
762
|
+
this._onAfterRenderListeners.clear();
|
|
763
|
+
|
|
761
764
|
if (!this.isManagedExternally) {
|
|
762
765
|
if (this.renderer) {
|
|
763
766
|
this.renderer.renderLists.dispose();
|
|
@@ -889,16 +892,17 @@ export class Context implements IContext {
|
|
|
889
892
|
}
|
|
890
893
|
}
|
|
891
894
|
|
|
892
|
-
private _onBeforeRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
893
|
-
private _onAfterRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
895
|
+
private readonly _onBeforeRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
896
|
+
private readonly _onAfterRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
894
897
|
|
|
895
898
|
/** Use to subscribe to onBeforeRender events on threejs objects.
|
|
896
899
|
* @link https://threejs.org/docs/#api/en/core/Object3D.onBeforeRender
|
|
897
900
|
*/
|
|
898
901
|
addBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
899
902
|
if (!this._onBeforeRenderListeners.has(target.uuid)) {
|
|
900
|
-
|
|
901
|
-
|
|
903
|
+
const arr: OnRenderCallback[] = [];
|
|
904
|
+
this._onBeforeRenderListeners.set(target.uuid, arr);
|
|
905
|
+
target.onBeforeRender = this._createRenderCallbackWrapper(arr);
|
|
902
906
|
}
|
|
903
907
|
this._onBeforeRenderListeners.get(target.uuid)!.push(callback);
|
|
904
908
|
}
|
|
@@ -919,8 +923,9 @@ export class Context implements IContext {
|
|
|
919
923
|
*/
|
|
920
924
|
addAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
921
925
|
if (!this._onAfterRenderListeners.has(target.uuid)) {
|
|
922
|
-
|
|
923
|
-
|
|
926
|
+
const arr = [];
|
|
927
|
+
this._onAfterRenderListeners.set(target.uuid, arr);
|
|
928
|
+
target.onAfterRender = this._createRenderCallbackWrapper(arr);
|
|
924
929
|
}
|
|
925
930
|
this._onAfterRenderListeners.get(target.uuid)?.push(callback);
|
|
926
931
|
}
|
|
@@ -937,12 +942,10 @@ export class Context implements IContext {
|
|
|
937
942
|
}
|
|
938
943
|
|
|
939
944
|
|
|
940
|
-
private _createRenderCallbackWrapper(
|
|
945
|
+
private _createRenderCallbackWrapper(array: OnRenderCallback[]): OnRenderCallback {
|
|
941
946
|
return (renderer, scene, camera, geometry, material, group) => {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
for (let i = 0; i < arr.length; i++) {
|
|
945
|
-
const fn = arr[i];
|
|
947
|
+
for (let i = 0; i < array.length; i++) {
|
|
948
|
+
const fn = array[i];
|
|
946
949
|
fn(renderer, scene, camera, geometry, material, group);
|
|
947
950
|
}
|
|
948
951
|
}
|
|
@@ -905,7 +905,7 @@ export async function generateQRCode(args: { domElement?: HTMLElement, text: str
|
|
|
905
905
|
|
|
906
906
|
// Ensure that the QRCode library is loaded
|
|
907
907
|
if (!globalThis["QRCode"]) {
|
|
908
|
-
const url = "https://cdn.
|
|
908
|
+
const url = "https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs@gh-pages/qrcode.min.js";
|
|
909
909
|
let script = document.head.querySelector(`script[src="${url}"]`) as HTMLScriptElement;
|
|
910
910
|
if (!script) {
|
|
911
911
|
script = document.createElement("script");
|
|
@@ -96,10 +96,10 @@ export class AudioSource extends Behaviour {
|
|
|
96
96
|
/**
|
|
97
97
|
* When true, the audio clip will be loaded during initialization rather than when play() is called.
|
|
98
98
|
* This can reduce playback delay but increases initial loading time.
|
|
99
|
-
* @default
|
|
99
|
+
* @default true
|
|
100
100
|
*/
|
|
101
101
|
@serializable()
|
|
102
|
-
preload: boolean =
|
|
102
|
+
preload: boolean = true;
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* When true, audio will continue playing when the browser tab loses focus.
|
|
@@ -347,7 +347,8 @@ export class AudioSource extends Behaviour {
|
|
|
347
347
|
|
|
348
348
|
/** @internal */
|
|
349
349
|
awake() {
|
|
350
|
-
if (debug) console.log(this);
|
|
350
|
+
if (debug) console.log("[AudioSource]", this);
|
|
351
|
+
|
|
351
352
|
this.audioLoader = new AudioLoader();
|
|
352
353
|
if (this.playOnAwake) this.shouldPlay = true;
|
|
353
354
|
|
|
@@ -431,7 +432,6 @@ export class AudioSource extends Behaviour {
|
|
|
431
432
|
if (this.context.application.muted) sound.setVolume(0);
|
|
432
433
|
else sound.setVolume(this.volume);
|
|
433
434
|
sound.autoplay = this.shouldPlay && AudioSource.userInteractionRegistered;
|
|
434
|
-
|
|
435
435
|
this.applySpatialDistanceSettings();
|
|
436
436
|
|
|
437
437
|
if (sound.isPlaying)
|
|
@@ -509,7 +509,7 @@ export class AudioSource extends Behaviour {
|
|
|
509
509
|
console.log("load audio", clip);
|
|
510
510
|
const buffer = await this.audioLoader.loadAsync(clip).catch(console.error);
|
|
511
511
|
if(this.destroyed) return;
|
|
512
|
-
this._lastClipStartedLoading = null;
|
|
512
|
+
if(this._lastClipStartedLoading === clip) this._lastClipStartedLoading = null;
|
|
513
513
|
if (buffer) this.createAudio(buffer);
|
|
514
514
|
}
|
|
515
515
|
else console.warn("Unsupported audio clip type", clip)
|
|
@@ -124,11 +124,17 @@ function registerLoadedTexture(src: string, texture: Promise<Texture>) {
|
|
|
124
124
|
* evt.detail.apply(url);
|
|
125
125
|
* });
|
|
126
126
|
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example update skybox url
|
|
129
|
+
* ```ts
|
|
130
|
+
* skybox.setSkybox("https://example.com/skybox.hdr");
|
|
131
|
+
* ```
|
|
127
132
|
*/
|
|
128
133
|
export class RemoteSkybox extends Behaviour {
|
|
129
134
|
|
|
130
135
|
/**
|
|
131
|
-
* URL to a remote skybox. This value can also use a magic skybox name. Options are "quicklook", "quicklook-ar", "studio", "blurred-skybox".
|
|
136
|
+
* URL to a remote skybox. This value can also use a magic skybox name. Options are "quicklook", "quicklook-ar", "studio", "blurred-skybox".
|
|
137
|
+
* To update the skybox/environment map use `setSkybox(url)`
|
|
132
138
|
* @example
|
|
133
139
|
* ```ts
|
|
134
140
|
* skybox.url = "https://example.com/skybox.hdr";
|
|
@@ -197,6 +203,9 @@ export class RemoteSkybox extends Behaviour {
|
|
|
197
203
|
if (this.isRemoteTexture(this.url)) {
|
|
198
204
|
this.setSkybox(this.url);
|
|
199
205
|
}
|
|
206
|
+
else if (debug) {
|
|
207
|
+
console.warn(`RemoteSkybox: Not setting skybox: ${this.url} is not a remote texture. If you want to set a local texture, set allowNetworking to false.`);
|
|
208
|
+
}
|
|
200
209
|
}
|
|
201
210
|
}
|
|
202
211
|
|
|
@@ -222,7 +231,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
222
231
|
if (debug) console.log("Set remote skybox url: " + url);
|
|
223
232
|
|
|
224
233
|
if (this._prevUrl === url && this._prevLoadedEnvironment) {
|
|
225
|
-
this.
|
|
234
|
+
this.apply();
|
|
226
235
|
return true;
|
|
227
236
|
}
|
|
228
237
|
else {
|
|
@@ -231,19 +240,30 @@ export class RemoteSkybox extends Behaviour {
|
|
|
231
240
|
}
|
|
232
241
|
this._prevUrl = url;
|
|
233
242
|
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
243
|
+
const texture = await this.loadTexture(url, name);
|
|
244
|
+
if (!texture) {
|
|
245
|
+
if (debug) console.warn("RemoteSkybox: Failed to load texture from url", url);
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
236
248
|
// Check if we're still enabled
|
|
237
|
-
if (!this.enabled)
|
|
249
|
+
if (!this.enabled) {
|
|
250
|
+
if (debug) console.warn("RemoteSkybox: Component is not enabled, aborting setSkybox");
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// Check if the url has changed while loading
|
|
254
|
+
if (this._prevUrl !== url) {
|
|
255
|
+
if (debug) console.warn("RemoteSkybox: URL changed while loading texture, aborting setSkybox");
|
|
256
|
+
return false; // URL changed while loading
|
|
257
|
+
}
|
|
238
258
|
// Update the current url
|
|
239
259
|
this.url = url;
|
|
240
260
|
const nameIndex = url.lastIndexOf("/");
|
|
241
|
-
|
|
261
|
+
texture.name = url.substring(nameIndex >= 0 ? nameIndex + 1 : 0);
|
|
242
262
|
if (this._loader instanceof TextureLoader) {
|
|
243
|
-
|
|
263
|
+
texture.colorSpace = SRGBColorSpace;
|
|
244
264
|
}
|
|
245
|
-
this._prevLoadedEnvironment =
|
|
246
|
-
this.
|
|
265
|
+
this._prevLoadedEnvironment = texture;
|
|
266
|
+
this.apply();
|
|
247
267
|
return true;
|
|
248
268
|
}
|
|
249
269
|
|
|
@@ -284,7 +304,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
284
304
|
return envMap;
|
|
285
305
|
}
|
|
286
306
|
|
|
287
|
-
private
|
|
307
|
+
private apply() {
|
|
288
308
|
const envMap = this._prevLoadedEnvironment;
|
|
289
309
|
if (!envMap) return;
|
|
290
310
|
|