entangled-worlds-orbiters-shared 1.0.0

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 (38) hide show
  1. package/README.md +75 -0
  2. package/package.json +49 -0
  3. package/public/config/textures.json +332 -0
  4. package/src/arrow-ui/BooleanController.js +31 -0
  5. package/src/arrow-ui/ColorController.js +108 -0
  6. package/src/arrow-ui/Controller.js +447 -0
  7. package/src/arrow-ui/FunctionController.js +27 -0
  8. package/src/arrow-ui/GUI.js +595 -0
  9. package/src/arrow-ui/NumberController.js +567 -0
  10. package/src/arrow-ui/OptionController.js +64 -0
  11. package/src/arrow-ui/StringController.js +41 -0
  12. package/src/arrow-ui/index.js +13 -0
  13. package/src/arrow-ui/lilGuiLoader.js +10 -0
  14. package/src/arrow-ui/utils/getColorFormat.js +75 -0
  15. package/src/arrow-ui/utils/injectStyles.js +10 -0
  16. package/src/arrow-ui/utils/normalizeColorString.js +29 -0
  17. package/src/config/index.js +8 -0
  18. package/src/config/textureConfig.js +299 -0
  19. package/src/core/index.js +3 -0
  20. package/src/core/lightFresnel.js +64 -0
  21. package/src/core/lights.js +43 -0
  22. package/src/index.js +8 -0
  23. package/src/loaders/bodyTextureLoader.js +39 -0
  24. package/src/loaders/cubeMapLoader.js +264 -0
  25. package/src/loaders/glbLoader.js +71 -0
  26. package/src/loaders/index.js +4 -0
  27. package/src/moons/index.js +3 -0
  28. package/src/moons/moonsGeneration.js +228 -0
  29. package/src/moons/moonsManager.js +64 -0
  30. package/src/utils/dispose.js +20 -0
  31. package/src/utils/index.js +3 -0
  32. package/src/utils/materials.js +31 -0
  33. package/src/world/cloudShell.js +89 -0
  34. package/src/world/index.js +6 -0
  35. package/src/world/worldGLB.js +7 -0
  36. package/src/world/worldManager.js +93 -0
  37. package/src/world/worldNormal.js +25 -0
  38. package/src/world/worldTexture.js +239 -0
@@ -0,0 +1,75 @@
1
+ import normalizeColorString from './normalizeColorString';
2
+
3
+ const STRING = {
4
+ isPrimitive: true,
5
+ match: v => typeof v === 'string',
6
+ fromHexString: normalizeColorString,
7
+ toHexString: normalizeColorString
8
+ };
9
+
10
+ const INT = {
11
+ isPrimitive: true,
12
+ match: v => typeof v === 'number',
13
+ fromHexString: string => parseInt( string.substring( 1 ), 16 ),
14
+ toHexString: value => '#' + value.toString( 16 ).padStart( 6, 0 )
15
+ };
16
+
17
+ const ARRAY = {
18
+ isPrimitive: false,
19
+
20
+ // The arrow function is here to appease tree shakers like esbuild or webpack.
21
+ // See https://esbuild.github.io/api/#tree-shaking
22
+ match: v => Array.isArray( v ),
23
+
24
+ fromHexString( string, target, rgbScale = 1 ) {
25
+
26
+ const int = INT.fromHexString( string );
27
+
28
+ target[ 0 ] = ( int >> 16 & 255 ) / 255 * rgbScale;
29
+ target[ 1 ] = ( int >> 8 & 255 ) / 255 * rgbScale;
30
+ target[ 2 ] = ( int & 255 ) / 255 * rgbScale;
31
+
32
+ },
33
+ toHexString( [ r, g, b ], rgbScale = 1 ) {
34
+
35
+ rgbScale = 255 / rgbScale;
36
+
37
+ const int = ( r * rgbScale ) << 16 ^
38
+ ( g * rgbScale ) << 8 ^
39
+ ( b * rgbScale ) << 0;
40
+
41
+ return INT.toHexString( int );
42
+
43
+ }
44
+ };
45
+
46
+ const OBJECT = {
47
+ isPrimitive: false,
48
+ match: v => Object( v ) === v,
49
+ fromHexString( string, target, rgbScale = 1 ) {
50
+
51
+ const int = INT.fromHexString( string );
52
+
53
+ target.r = ( int >> 16 & 255 ) / 255 * rgbScale;
54
+ target.g = ( int >> 8 & 255 ) / 255 * rgbScale;
55
+ target.b = ( int & 255 ) / 255 * rgbScale;
56
+
57
+ },
58
+ toHexString( { r, g, b }, rgbScale = 1 ) {
59
+
60
+ rgbScale = 255 / rgbScale;
61
+
62
+ const int = ( r * rgbScale ) << 16 ^
63
+ ( g * rgbScale ) << 8 ^
64
+ ( b * rgbScale ) << 0;
65
+
66
+ return INT.toHexString( int );
67
+
68
+ }
69
+ };
70
+
71
+ const FORMATS = [ STRING, INT, ARRAY, OBJECT ];
72
+
73
+ export default function( value ) {
74
+ return FORMATS.find( format => format.match( value ) );
75
+ }
@@ -0,0 +1,10 @@
1
+ export default function( cssContent ) {
2
+ const injected = document.createElement( 'style' );
3
+ injected.innerHTML = cssContent;
4
+ const before = document.querySelector( 'head link[rel=stylesheet], head style' );
5
+ if ( before ) {
6
+ document.head.insertBefore( injected, before );
7
+ } else {
8
+ document.head.appendChild( injected );
9
+ }
10
+ }
@@ -0,0 +1,29 @@
1
+ /* eslint-disable no-cond-assign */
2
+
3
+ export default function( string ) {
4
+
5
+ let match, result;
6
+
7
+ if ( match = string.match( /(#|0x)?([a-f0-9]{6})/i ) ) {
8
+
9
+ result = match[ 2 ];
10
+
11
+ } else if ( match = string.match( /rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/ ) ) {
12
+
13
+ result = parseInt( match[ 1 ] ).toString( 16 ).padStart( 2, 0 )
14
+ + parseInt( match[ 2 ] ).toString( 16 ).padStart( 2, 0 )
15
+ + parseInt( match[ 3 ] ).toString( 16 ).padStart( 2, 0 );
16
+
17
+ } else if ( match = string.match( /^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i ) ) {
18
+
19
+ result = match[ 1 ] + match[ 1 ] + match[ 2 ] + match[ 2 ] + match[ 3 ] + match[ 3 ];
20
+
21
+ }
22
+
23
+ if ( result ) {
24
+ return '#' + result;
25
+ }
26
+
27
+ return false;
28
+
29
+ }
@@ -0,0 +1,8 @@
1
+ // Configuration exports
2
+ export {
3
+ loadTextureConfig,
4
+ getTextureOptions,
5
+ getDefaultTexture,
6
+ getSkyboxOptions,
7
+ getDefaultSkybox
8
+ } from './textureConfig.js';
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Texture Configuration Loader
3
+ * Loads texture definitions from local config file or remote endpoint
4
+ */
5
+
6
+ const DEFAULT_CONFIG_URL = '/config/textures.json';
7
+ const FALLBACK_CONFIG_URL = 'https://api.entangled-worlds.com/config/textures.json'; // Future endpoint
8
+
9
+ let cachedConfig = null;
10
+ let configPromise = null;
11
+
12
+ /**
13
+ * Load texture configuration from local file or remote endpoint
14
+ * @param {string} configUrl - Override default config URL
15
+ * @param {boolean} useCache - Whether to use cached config if available
16
+ * @returns {Promise<Object>} Texture configuration object
17
+ */
18
+ export async function loadTextureConfig(configUrl = DEFAULT_CONFIG_URL, useCache = true) {
19
+ if (useCache && cachedConfig) {
20
+ console.log('[TextureConfig] Using cached config');
21
+ return cachedConfig;
22
+ }
23
+
24
+ if (configPromise) {
25
+ console.log('[TextureConfig] Using in-flight request');
26
+ return configPromise;
27
+ }
28
+
29
+ configPromise = (async () => {
30
+ try {
31
+ console.log('[TextureConfig] Loading from:', configUrl);
32
+ const response = await fetch(configUrl);
33
+
34
+ if (!response.ok) {
35
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
36
+ }
37
+
38
+ const config = await response.json();
39
+
40
+ // Validate configuration structure
41
+ if (!config.textures || typeof config.textures !== 'object') {
42
+ throw new Error('Invalid configuration: missing textures object');
43
+ }
44
+
45
+ cachedConfig = config;
46
+ console.log('[TextureConfig] Loaded successfully:', {
47
+ version: config.version,
48
+ categories: Object.keys(config.textures),
49
+ lastUpdated: config.lastUpdated
50
+ });
51
+
52
+ return config;
53
+ } catch (error) {
54
+ console.error('[TextureConfig] Failed to load config:', error);
55
+
56
+ // If primary URL fails and it's not already the fallback, try fallback
57
+ if (configUrl !== FALLBACK_CONFIG_URL) {
58
+ console.log('[TextureConfig] Trying fallback endpoint...');
59
+ try {
60
+ return await loadTextureConfig(FALLBACK_CONFIG_URL, false);
61
+ } catch (fallbackError) {
62
+ console.error('[TextureConfig] Fallback also failed:', fallbackError);
63
+ }
64
+ }
65
+
66
+ // Return minimal fallback configuration
67
+ console.warn('[TextureConfig] Using hardcoded fallback config');
68
+ return getHardcodedFallback();
69
+ } finally {
70
+ configPromise = null;
71
+ }
72
+ })();
73
+
74
+ return configPromise;
75
+ }
76
+
77
+ /**
78
+ * Get texture options for a specific category (clouds, moons, etc.)
79
+ * @param {string} category - Texture category (clouds, moons, skyboxes)
80
+ * @param {Object} config - Optional pre-loaded config object
81
+ * @returns {Promise<Object>} Options object suitable for lil-gui dropdowns
82
+ */
83
+ export async function getTextureOptions(category, config = null) {
84
+ const textureConfig = config || await loadTextureConfig();
85
+ const categoryConfig = textureConfig.textures[category];
86
+
87
+ if (!categoryConfig) {
88
+ console.warn(`[TextureConfig] Category '${category}' not found`);
89
+ return {};
90
+ }
91
+
92
+ const options = {};
93
+ const baseUrl = textureConfig.baseUrl || '/default';
94
+ const categoryPath = categoryConfig.basePath || `/${category}`;
95
+
96
+ categoryConfig.items.forEach(item => {
97
+ const key = item.label || item.id;
98
+ const relativePath = `${baseUrl}${categoryPath}/${item.filename || item.id}`;
99
+ options[key] = relativePath;
100
+ });
101
+
102
+ return options;
103
+ }
104
+
105
+ /**
106
+ * Get default texture for a category
107
+ * @param {string} category - Texture category
108
+ * @param {Object} config - Optional pre-loaded config object
109
+ * @returns {Promise<string>} Default texture path
110
+ */
111
+ export async function getDefaultTexture(category, config = null) {
112
+ const textureConfig = config || await loadTextureConfig();
113
+ const categoryConfig = textureConfig.textures[category];
114
+
115
+ if (!categoryConfig) {
116
+ return null;
117
+ }
118
+
119
+ const baseUrl = textureConfig.baseUrl || '/default';
120
+ const categoryPath = categoryConfig.basePath || `/${category}`;
121
+ const defaultFilename = categoryConfig.defaultTexture;
122
+
123
+ if (defaultFilename) {
124
+ return `${baseUrl}${categoryPath}/${defaultFilename}`;
125
+ }
126
+
127
+ // Fallback to first item
128
+ if (categoryConfig.items && categoryConfig.items.length > 0) {
129
+ const firstItem = categoryConfig.items[0];
130
+ return `${baseUrl}${categoryPath}/${firstItem.filename || firstItem.id}`;
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ /**
137
+ * Get texture by ID within a category
138
+ * @param {string} category - Texture category
139
+ * @param {string} textureId - Texture ID
140
+ * @param {Object} config - Optional pre-loaded config object
141
+ * @returns {Promise<Object|null>} Texture definition object
142
+ */
143
+ export async function getTextureById(category, textureId, config = null) {
144
+ const textureConfig = config || await loadTextureConfig();
145
+ const categoryConfig = textureConfig.textures[category];
146
+
147
+ if (!categoryConfig || !categoryConfig.items) {
148
+ return null;
149
+ }
150
+
151
+ return categoryConfig.items.find(item => item.id === textureId) || null;
152
+ }
153
+
154
+ /**
155
+ * Get full URL for a texture
156
+ * @param {string} category - Texture category
157
+ * @param {string} textureId - Texture ID
158
+ * @param {Object} config - Optional pre-loaded config object
159
+ * @returns {Promise<string|null>} Full texture URL
160
+ */
161
+ export async function getTextureUrl(category, textureId, config = null) {
162
+ const texture = await getTextureById(category, textureId, config);
163
+ if (!texture) return null;
164
+
165
+ const textureConfig = config || await loadTextureConfig();
166
+ const categoryConfig = textureConfig.textures[category];
167
+ const baseUrl = textureConfig.baseUrl || '/default';
168
+ const categoryPath = categoryConfig.basePath || `/${category}`;
169
+
170
+ return `${baseUrl}${categoryPath}/${texture.filename || texture.id}`;
171
+ }
172
+
173
+ /**
174
+ * Search textures by tags
175
+ * @param {string} category - Texture category
176
+ * @param {string[]} tags - Tags to search for
177
+ * @param {Object} config - Optional pre-loaded config object
178
+ * @returns {Promise<Object[]>} Array of matching texture definitions
179
+ */
180
+ export async function searchTexturesByTags(category, tags, config = null) {
181
+ const textureConfig = config || await loadTextureConfig();
182
+ const categoryConfig = textureConfig.textures[category];
183
+
184
+ if (!categoryConfig || !categoryConfig.items) {
185
+ return [];
186
+ }
187
+
188
+ return categoryConfig.items.filter(item =>
189
+ item.tags && tags.some(tag => item.tags.includes(tag))
190
+ );
191
+ }
192
+
193
+ /**
194
+ * Get skybox options formatted for GUI dropdowns
195
+ * @param {Object} config - Optional pre-loaded config object
196
+ * @returns {Promise<Object>} Options object with labels and IDs
197
+ */
198
+ export async function getSkyboxOptions(config = null) {
199
+ const textureConfig = config || await loadTextureConfig();
200
+ const skyboxConfig = textureConfig.textures.skyboxes;
201
+
202
+ if (!skyboxConfig) {
203
+ console.warn('[TextureConfig] Skyboxes category not found');
204
+ return {};
205
+ }
206
+
207
+ const options = {};
208
+
209
+ skyboxConfig.items.forEach(item => {
210
+ const label = item.label || item.id;
211
+ const id = item.id;
212
+ options[label] = id;
213
+ });
214
+
215
+ return options;
216
+ }
217
+
218
+ /**
219
+ * Get default skybox ID
220
+ * @param {Object} config - Optional pre-loaded config object
221
+ * @returns {Promise<string>} Default skybox ID
222
+ */
223
+ export async function getDefaultSkybox(config = null) {
224
+ const textureConfig = config || await loadTextureConfig();
225
+ const skyboxConfig = textureConfig.textures.skyboxes;
226
+
227
+ if (!skyboxConfig) {
228
+ return '001'; // fallback
229
+ }
230
+
231
+ return skyboxConfig.defaultSkybox || '001';
232
+ }
233
+
234
+ /**
235
+ * Hardcoded fallback configuration (minimal)
236
+ * Used when remote config loading fails
237
+ */
238
+ function getHardcodedFallback() {
239
+ return {
240
+ version: "1.0-fallback",
241
+ lastUpdated: new Date().toISOString(),
242
+ baseUrl: "/default",
243
+ textures: {
244
+ clouds: {
245
+ category: "atmospheric",
246
+ basePath: "/clouds",
247
+ defaultTexture: "04-clouds.ktx2",
248
+ items: [
249
+ { id: "clouds-1", filename: "04-clouds.ktx2", label: "Clouds", tags: ["natural"] },
250
+ { id: "entangled-1", filename: "06-entangled.ktx2", label: "Entangled", tags: ["quantum"] }
251
+ ]
252
+ },
253
+ moons: {
254
+ category: "celestial",
255
+ basePath: "/textures/moons",
256
+ defaultTexture: "moon-equirectangular.ktx2",
257
+ items: [
258
+ { id: "default-moon", filename: "moon-equirectangular.ktx2", label: "Default Moon", tags: ["lunar"] }
259
+ ]
260
+ },
261
+ skyboxes: {
262
+ category: "environment",
263
+ basePath: "/skybox",
264
+ defaultSkybox: "001",
265
+ items: [
266
+ { id: "001", label: "Deep Space", tags: ["space"] }
267
+ ]
268
+ }
269
+ }
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Reload configuration (force refresh)
275
+ * @param {string} configUrl - Override config URL
276
+ * @returns {Promise<Object>} Fresh configuration object
277
+ */
278
+ export async function reloadTextureConfig(configUrl = DEFAULT_CONFIG_URL) {
279
+ cachedConfig = null;
280
+ configPromise = null;
281
+ return loadTextureConfig(configUrl, false);
282
+ }
283
+
284
+ /**
285
+ * Get configuration metadata
286
+ * @param {Object} config - Optional pre-loaded config object
287
+ * @returns {Promise<Object>} Configuration metadata
288
+ */
289
+ export async function getConfigMetadata(config = null) {
290
+ const textureConfig = config || await loadTextureConfig();
291
+ return {
292
+ version: textureConfig.version,
293
+ lastUpdated: textureConfig.lastUpdated,
294
+ categories: Object.keys(textureConfig.textures),
295
+ totalTextures: Object.values(textureConfig.textures).reduce(
296
+ (sum, cat) => sum + (cat.items ? cat.items.length : 0), 0
297
+ )
298
+ };
299
+ }
@@ -0,0 +1,3 @@
1
+ // Core rendering exports
2
+ export { ensureDefaultLights, updateDefaultLights } from './lights.js';
3
+ export { getLightFresnel } from './lightFresnel.js';
@@ -0,0 +1,64 @@
1
+ import * as THREE from "three";
2
+
3
+ function getLightFresnel({rimHex = 0x0088ff, facingHex = 0x000000} = {}) {
4
+ const uniforms = {
5
+ color1: { value: new THREE.Color(rimHex) },
6
+ color2: { value: new THREE.Color(facingHex) },
7
+ fresnelBias: { value: 0.1 },
8
+ fresnelScale: { value: 1.0 },
9
+ fresnelPower: { value: 4.0 },
10
+ };
11
+ const vs = `
12
+ uniform float fresnelBias;
13
+ uniform float fresnelScale;
14
+ uniform float fresnelPower;
15
+
16
+ varying float vReflectionFactor;
17
+
18
+ void main() {
19
+ vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
20
+ vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
21
+
22
+ // Transform normal to world space
23
+ vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
24
+
25
+ // Proper view direction (from surface point towards camera)
26
+ vec3 viewDir = normalize( cameraPosition - worldPosition.xyz );
27
+
28
+ // Classic rim: stronger at grazing angles
29
+ float ndotv = max( dot( worldNormal, viewDir ), 0.0 );
30
+ vReflectionFactor = fresnelBias + fresnelScale * pow( 1.0 - ndotv, fresnelPower );
31
+
32
+ gl_Position = projectionMatrix * mvPosition;
33
+ }
34
+ `;
35
+ const fs = `
36
+ uniform vec3 color1;
37
+ uniform vec3 color2;
38
+
39
+ varying float vReflectionFactor;
40
+
41
+ void main() {
42
+ float f = clamp( vReflectionFactor, 0.0, 1.0 );
43
+ gl_FragColor = vec4(mix(color2, color1, vec3(f)), f);
44
+ }
45
+ `;
46
+ const lightFresnel = new THREE.ShaderMaterial({
47
+ uniforms: uniforms,
48
+ vertexShader: vs,
49
+ fragmentShader: fs,
50
+ transparent: true,
51
+ blending: THREE.CustomBlending,
52
+ blendSrc: THREE.OneFactor,
53
+ blendDst: THREE.OneFactor,
54
+ blendEquation: THREE.AddEquation,
55
+ depthWrite: false,
56
+ depthTest: true,
57
+ depthFunc: THREE.LessDepth,
58
+ side: THREE.FrontSide,
59
+ toneMapped: false,
60
+ // wireframe: true,
61
+ });
62
+ return lightFresnel;
63
+ }
64
+ export { getLightFresnel };
@@ -0,0 +1,43 @@
1
+ import * as THREE from 'three';
2
+
3
+ let hemiRef = null;
4
+ let ambientRef = null;
5
+
6
+ export function ensureDefaultLights(scene) {
7
+ if (!hemiRef) {
8
+ hemiRef = new THREE.HemisphereLight(0xffffff, 0xffffff, 1.0);
9
+ hemiRef.position.set(1, 1, 1);
10
+ hemiRef.name = 'ew_HemisphereLight';
11
+ scene.add(hemiRef);
12
+ }
13
+ if (!ambientRef) {
14
+ ambientRef = new THREE.AmbientLight(0xffffff, 1.0);
15
+ ambientRef.name = 'ew_AmbientLight';
16
+ scene.add(ambientRef);
17
+ }
18
+ return { hemi: hemiRef, ambient: ambientRef };
19
+ }
20
+
21
+ export function updateDefaultLights({ ambientIntensity, diffuseIntensity, lightColor, lightDirection } = {}) {
22
+ if (typeof ambientIntensity === 'number' && ambientRef) ambientRef.intensity = Math.max(0, ambientIntensity);
23
+ if (hemiRef) {
24
+ if (typeof diffuseIntensity === 'number') hemiRef.intensity = Math.max(0, diffuseIntensity);
25
+ if (lightColor != null) {
26
+ if (lightColor instanceof THREE.Color) {
27
+ hemiRef.color.copy(lightColor);
28
+ } else if (typeof lightColor === 'number') {
29
+ hemiRef.color.setHex(lightColor);
30
+ } else if (typeof lightColor === 'string') {
31
+ hemiRef.color.set(lightColor);
32
+ }
33
+ }
34
+ if (lightDirection && typeof lightDirection.x === 'number') {
35
+ hemiRef.position.set(lightDirection.x, lightDirection.y ?? 0, lightDirection.z ?? 0).normalize();
36
+ }
37
+ }
38
+ }
39
+
40
+ // Back-compat helper
41
+ export function addDefaultLights(scene) {
42
+ return ensureDefaultLights(scene);
43
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Main exports for entangled-worlds-orbiters-shared library
2
+ export * from './world/index.js';
3
+ export * from './moons/index.js';
4
+ export * from './arrow-ui/index.js';
5
+ export * from './loaders/index.js';
6
+ export * from './core/index.js';
7
+ export * from './config/index.js';
8
+ export * from './utils/index.js';
@@ -0,0 +1,39 @@
1
+ import * as THREE from 'three';
2
+ import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
3
+
4
+ const textureLoader = new THREE.TextureLoader();
5
+
6
+ // Shared singleton accessor (align with cubeMapLoader)
7
+ function getSharedKTX2Loader() {
8
+ if (window.__EW_KTX2_LOADER) return window.__EW_KTX2_LOADER;
9
+ const renderer = window.__EW_RENDERER;
10
+ const loader = new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
11
+ window.__EW_KTX2_LOADER = loader;
12
+ return loader;
13
+ }
14
+
15
+ export function loadTexture(url, { signal } = {}) {
16
+ return new Promise((resolve, reject) => {
17
+ if (url && url.endsWith('.ktx2')) {
18
+ const ktx2 = getSharedKTX2Loader();
19
+ const onAbort = () => reject(new Error('aborted'));
20
+ if (signal) signal.addEventListener('abort', onAbort, { once: true });
21
+ ktx2.load(
22
+ url,
23
+ (tex) => { if (signal) signal.removeEventListener('abort', onAbort); resolve(tex); },
24
+ undefined,
25
+ (err) => { if (signal) signal.removeEventListener('abort', onAbort); reject(err); }
26
+ );
27
+ return;
28
+ }
29
+ const tex = textureLoader.load(
30
+ url,
31
+ () => resolve(tex),
32
+ undefined,
33
+ (err) => reject(err)
34
+ );
35
+ if (signal) {
36
+ signal.addEventListener('abort', () => reject(new Error('aborted')));
37
+ }
38
+ });
39
+ }