@needle-tools/engine 4.5.8 → 4.6.0-next.3088402
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-A-KGqR38.js → needle-engine.bundle-B1JFnMRs.js} +4046 -4029
- package/dist/{needle-engine.bundle-DsPSVnUc.min.js → needle-engine.bundle-CLbeONlI.min.js} +123 -123
- package/dist/{needle-engine.bundle-2--i65ba.umd.cjs → needle-engine.bundle-CpQAQ_2N.umd.cjs} +126 -126
- 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/ReflectionProbe.d.ts +2 -1
- package/lib/engine-components/ReflectionProbe.js +4 -1
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Renderer.js +9 -5
- package/lib/engine-components/Renderer.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/lib/engine-components/postprocessing/Effects/Tonemapping.js +10 -6
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
- package/package.json +2 -2
- package/plugins/types/userconfig.d.ts +1 -1
- package/plugins/vite/asap.js +26 -20
- package/plugins/vite/dependency-watcher.js +8 -2
- 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/ReflectionProbe.ts +5 -1
- package/src/engine-components/Renderer.ts +10 -7
- package/src/engine-components/Skybox.ts +30 -10
- package/src/engine-components/postprocessing/Effects/Tonemapping.ts +9 -7
|
@@ -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)
|
|
@@ -22,6 +22,10 @@ export class ReflectionProbe extends Behaviour {
|
|
|
22
22
|
|
|
23
23
|
private static _probes: Map<Context, ReflectionProbe[]> = new Map();
|
|
24
24
|
|
|
25
|
+
static isUsingReflectionProbe(material: Material) {
|
|
26
|
+
return !!(material[$reflectionProbeKey] || material[$originalMaterial]?.[$reflectionProbeKey]);
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
public static get(object: Object3D | null | undefined, context: Context, isAnchor: boolean, anchor?: Object3D): ReflectionProbe | null {
|
|
26
30
|
if (!object || object.isObject3D !== true) return null;
|
|
27
31
|
if (disable) return null;
|
|
@@ -29,7 +33,7 @@ export class ReflectionProbe extends Behaviour {
|
|
|
29
33
|
if (probes) {
|
|
30
34
|
for (const probe of probes) {
|
|
31
35
|
if (!probe.__didAwake) probe.__internalAwake();
|
|
32
|
-
if (probe.
|
|
36
|
+
if (probe.activeAndEnabled) {
|
|
33
37
|
if (anchor) {
|
|
34
38
|
// test if anchor is reflection probe object
|
|
35
39
|
if (probe.gameObject === anchor) {
|
|
@@ -708,7 +708,15 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
708
708
|
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
|
709
709
|
this._reflectionProbe.onSet(this);
|
|
710
710
|
}
|
|
711
|
-
|
|
711
|
+
// since three 163 we need to set the envMap to the scene envMap if it is not set
|
|
712
|
+
// otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903
|
|
713
|
+
// internal issue: https://linear.app/needle/issue/NE-6363
|
|
714
|
+
for (const mat of this._sharedMaterials) {
|
|
715
|
+
// If the material has a envMap and is NOT using a reflection probe we set the envMap to the scene environment
|
|
716
|
+
if (mat && "envMap" in mat && "envMapIntensity" in mat && !ReflectionProbe.isUsingReflectionProbe(mat)) {
|
|
717
|
+
mat.envMap = this.context.scene.environment;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
712
720
|
}
|
|
713
721
|
|
|
714
722
|
private onBeforeRenderThree = (_renderer, _scene, _camera, _geometry, material, _group) => {
|
|
@@ -716,11 +724,6 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
716
724
|
const factor = this.hasLightmap ? Math.PI : 1;
|
|
717
725
|
const environmentIntensity = this.context.mainCameraComponent?.environmentIntensity ?? 1;
|
|
718
726
|
material.envMapIntensity = Math.max(0, environmentIntensity * this.context.sceneLighting.environmentIntensity / factor);
|
|
719
|
-
|
|
720
|
-
// since three 163 we need to set the envMap to the scene envMap if it is not set
|
|
721
|
-
// otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903
|
|
722
|
-
// internal issue: https://linear.app/needle/issue/NE-6363
|
|
723
|
-
if (!material.envMap) material.envMap = this.context.scene.environment;
|
|
724
727
|
}
|
|
725
728
|
|
|
726
729
|
if (this._lightmaps) {
|
|
@@ -856,7 +859,7 @@ export class SkinnedMeshRenderer extends MeshRenderer {
|
|
|
856
859
|
mesh.computeBoundingSphere();
|
|
857
860
|
mesh.geometry = geometry;
|
|
858
861
|
}
|
|
859
|
-
catch(err) {
|
|
862
|
+
catch (err) {
|
|
860
863
|
console.error(`Error updating bounding sphere for ${mesh.name}`, err);
|
|
861
864
|
}
|
|
862
865
|
}
|