@needle-tools/engine 4.5.8 → 4.6.0-next.ffc175e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/{gltf-progressive-Bx_ZJgfQ.min.js → gltf-progressive-Bm9eEfgu.min.js} +1 -1
  3. package/dist/{gltf-progressive-Ccf3INjK.umd.cjs → gltf-progressive-Dn6o99rH.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-D8GP6sjZ.js → gltf-progressive-GjIqwSG3.js} +2 -2
  5. package/dist/{needle-engine.bundle-A-KGqR38.js → needle-engine.bundle-B3vWYoFH.js} +2579 -2574
  6. package/dist/{needle-engine.bundle-DsPSVnUc.min.js → needle-engine.bundle-CZu9qkBv.min.js} +99 -99
  7. package/dist/{needle-engine.bundle-2--i65ba.umd.cjs → needle-engine.bundle-DUkg_M5A.umd.cjs} +105 -105
  8. package/dist/needle-engine.js +4 -4
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/dist/{postprocessing-CLwnKusi.umd.cjs → postprocessing-CRQa6Qxn.umd.cjs} +177 -97
  12. package/dist/{postprocessing-DS5I4Iim.js → postprocessing-D6W1EyZ-.js} +1095 -1001
  13. package/dist/{postprocessing-CdIScgB8.min.js → postprocessing-DF8AlRgW.min.js} +178 -98
  14. package/dist/{three-CFtLGNtV.min.js → three-Boa-jOq-.min.js} +18 -18
  15. package/dist/{three-1JG7vpNC.js → three-Bz6X1mrw.js} +0 -1
  16. package/dist/{three-f-JuZtOs.umd.cjs → three-DMrv-4ar.umd.cjs} +20 -20
  17. package/dist/{three-examples-BeoqFh8m.umd.cjs → three-examples-C7ryg8vN.umd.cjs} +1 -1
  18. package/dist/{three-examples-CPlN41GO.min.js → three-examples-DuVhxqft.min.js} +1 -1
  19. package/dist/{three-examples-CyWe9wwo.js → three-examples-GggCDHv0.js} +1 -1
  20. package/dist/{three-mesh-ui-B18kdQmk.js → three-mesh-ui-CLNOfsWn.js} +1 -1
  21. package/dist/{three-mesh-ui-BMeLuYD5.min.js → three-mesh-ui-CY6Izc7C.min.js} +1 -1
  22. package/dist/{three-mesh-ui-DMdtSVhY.umd.cjs → three-mesh-ui-CwlN0FUC.umd.cjs} +1 -1
  23. package/dist/{vendor-BbM-oI6p.js → vendor-BSD1RQIh.js} +1 -1
  24. package/dist/{vendor-CJFfVMoq.umd.cjs → vendor-DHr4aqIZ.umd.cjs} +1 -1
  25. package/dist/{vendor-CTtGKiDe.min.js → vendor-zxXa3Dmr.min.js} +1 -1
  26. package/lib/engine/engine_utils.js +1 -1
  27. package/lib/engine/engine_utils.js.map +1 -1
  28. package/lib/engine-components/AudioSource.d.ts +1 -1
  29. package/lib/engine-components/AudioSource.js +5 -4
  30. package/lib/engine-components/AudioSource.js.map +1 -1
  31. package/lib/engine-components/Skybox.d.ts +7 -1
  32. package/lib/engine-components/Skybox.js +30 -9
  33. package/lib/engine-components/Skybox.js.map +1 -1
  34. package/package.json +2 -2
  35. package/plugins/types/userconfig.d.ts +1 -1
  36. package/plugins/vite/asap.js +26 -20
  37. package/plugins/vite/local-files.js +268 -59
  38. package/src/engine/engine_utils.ts +1 -1
  39. package/src/engine-components/AudioSource.ts +5 -5
  40. 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 { runInNewContext } from 'vm';
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?.makeFilesLocal?.enabled) {
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
- return {
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, id) {
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
- // find all google font urls fonts.googleapis.com
56
- const googleFontRegex = /["'](https:\/\/fonts\.googleapis\.com\/.+?)["']/g;
57
- let match;
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
- while ((match = googleFontRegex.exec(src)) !== null) {
60
- const fontUrl = match[1];
61
- console.log(`\nFound google font URL: ${fontUrl}`);
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(fontUrl);
110
+ const cachedPath = context.cache.getFromCache(url);
64
111
  if (cachedPath) {
65
- console.log(`Using cached font URL: ${cachedPath}`);
66
- src = src.replace(fontUrl, cachedPath);
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(fontUrl);
70
- const familyNameMatch = /family=([^&]+)/.exec(fontUrl);
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
- const referenceId = context.pluginContext.emitFile({
76
- type: 'asset',
77
- fileName: outputPath,
78
- source: font,
79
- });
80
- const localPath = `${context.pluginContext.getFileName(referenceId)}`;
81
- const newPath = getRelativeToBasePath(localPath, currentDir);
82
- context.cache.addToCache(fontUrl, newPath);
83
- src = src.replace(fontUrl, newPath);
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
- const gstatic = /["'(](https:\/\/fonts\.gstatic\.com\/)(.+?)["')]/g;
88
- while ((match = gstatic.exec(src)) !== null) {
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 fontUrl = match[1] + fontPath;
91
- console.log(`\nFound gstatic URL: ${fontUrl}`);
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(fontUrl);
154
+ const cachedPath = context.cache.getFromCache(url);
94
155
  if (cachedPath) {
95
- console.log(`Using cached gstatic font URL: ${cachedPath}`);
96
- src = src.replace(fontUrl, cachedPath);
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
- let font = await downloadBinary(fontUrl);
160
+ const font = await downloadBinary(url);
100
161
  const filename = getValidFilename(fontPath, font);
101
- console.log(`Saving font to: ${basePath + filename}`);
102
- const referenceId = context.pluginContext.emitFile({
103
- type: 'asset',
104
- fileName: basePath + filename,
105
- source: font,
106
- });
107
- const localPath = `${context.pluginContext.getFileName(referenceId)}`;
108
- const newPath = getRelativeToBasePath(localPath, currentDir);
109
- context.cache.addToCache(fontUrl, newPath);
110
- src = src.replace(fontUrl, newPath);
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.error(`Failed to download font from ${url}: ${response.statusCode}`);
197
- res(null);
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.error(`Failed to download font from ${url}: ${response.statusCode}`);
218
- rej(new Error(`Failed to download font from ${url}: ${response.statusCode}`));
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 from ${url}`);
436
+ if (debug) console.log(`Downloaded from ${url}`);
228
437
  res(Buffer.concat(chunks));
229
438
  });
230
439
  });
@@ -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.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js";
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 false
99
+ * @default true
100
100
  */
101
101
  @serializable()
102
- preload: boolean = false;
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.applySkybox();
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 envMap = await this.loadTexture(url, name);
235
- if (!envMap) return false;
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) return false;
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
- envMap.name = url.substring(nameIndex >= 0 ? nameIndex + 1 : 0);
261
+ texture.name = url.substring(nameIndex >= 0 ? nameIndex + 1 : 0);
242
262
  if (this._loader instanceof TextureLoader) {
243
- envMap.colorSpace = SRGBColorSpace;
263
+ texture.colorSpace = SRGBColorSpace;
244
264
  }
245
- this._prevLoadedEnvironment = envMap;
246
- this.applySkybox();
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 applySkybox() {
307
+ private apply() {
288
308
  const envMap = this._prevLoadedEnvironment;
289
309
  if (!envMap) return;
290
310