entangled-worlds-orbiters-shared 3.3.0 → 4.1.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.
- package/README.md +3 -2
- package/dist/clock/Transport.d.ts +8 -2
- package/dist/clock/Transport.js +46 -7
- package/dist/clock/musical.js +13 -8
- package/dist/clock/sync.js +15 -2
- package/dist/config/env.d.ts +8 -0
- package/dist/config/env.js +36 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +4 -0
- package/dist/config/textureConfig.d.ts +140 -0
- package/dist/config/textureConfig.js +318 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/lightFresnel.d.ts +6 -0
- package/dist/core/lightFresnel.js +63 -0
- package/dist/core/lights.d.ts +48 -0
- package/dist/core/lights.js +79 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/loaders/bodyTextureLoader.d.ts +10 -0
- package/dist/loaders/bodyTextureLoader.js +28 -0
- package/dist/loaders/cubeMapLoader.d.ts +58 -0
- package/dist/loaders/cubeMapLoader.js +291 -0
- package/dist/loaders/glbLoader.d.ts +24 -0
- package/dist/loaders/glbLoader.js +85 -0
- package/dist/loaders/index.d.ts +5 -0
- package/{src → dist}/loaders/index.js +0 -2
- package/dist/{types/loaders → loaders}/ktx2.d.ts +4 -6
- package/dist/loaders/ktx2.js +48 -0
- package/dist/moons/index.d.ts +3 -0
- package/dist/moons/index.js +3 -0
- package/dist/moons/moonsGeneration.d.ts +64 -0
- package/dist/moons/moonsGeneration.js +238 -0
- package/dist/moons/moonsManager.d.ts +43 -0
- package/dist/moons/moonsManager.js +93 -0
- package/dist/{types/render → render}/frameBudget.d.ts +18 -17
- package/dist/render/frameBudget.js +42 -0
- package/dist/render/index.d.ts +4 -0
- package/dist/render/viewportCompositor.d.ts +124 -0
- package/dist/render/viewportCompositor.js +643 -0
- package/dist/studio/headerBroadcast.d.ts +10 -1
- package/dist/studio/hostTransport.d.ts +2 -2
- package/dist/studio/index.d.ts +1 -1
- package/dist/utils/dispose.d.ts +5 -0
- package/dist/utils/dispose.js +50 -0
- package/dist/utils/index.d.ts +3 -0
- package/{src → dist}/utils/index.js +1 -1
- package/dist/utils/materials.d.ts +10 -0
- package/dist/utils/materials.js +36 -0
- package/dist/world/cloudShell.d.ts +16 -0
- package/dist/world/cloudShell.js +73 -0
- package/dist/world/index.d.ts +6 -0
- package/{src → dist}/world/index.js +0 -2
- package/dist/world/worldGLB.d.ts +17 -0
- package/dist/world/worldGLB.js +22 -0
- package/dist/world/worldManager.d.ts +57 -0
- package/dist/world/worldManager.js +125 -0
- package/dist/world/worldNormal.d.ts +30 -0
- package/dist/world/worldNormal.js +58 -0
- package/dist/world/worldTexture.d.ts +56 -0
- package/dist/world/worldTexture.js +299 -0
- package/package.json +29 -39
- package/src/clock/Transport.ts +37 -6
- package/src/clock/musical.ts +18 -8
- package/src/clock/sync.ts +15 -2
- package/src/config/env.ts +48 -0
- package/src/config/{index.js → index.ts} +7 -0
- package/src/config/{textureConfig.js → textureConfig.ts} +126 -60
- package/src/core/{lightFresnel.js → lightFresnel.ts} +1 -1
- package/src/core/{lights.js → lights.ts} +21 -21
- package/src/loaders/{bodyTextureLoader.js → bodyTextureLoader.ts} +10 -7
- package/src/loaders/{cubeMapLoader.js → cubeMapLoader.ts} +83 -49
- package/src/loaders/glbLoader.ts +110 -0
- package/src/loaders/index.ts +6 -0
- package/src/loaders/{ktx2.js → ktx2.ts} +5 -7
- package/src/moons/{index.js → index.ts} +1 -0
- package/src/moons/{moonsGeneration.js → moonsGeneration.ts} +45 -32
- package/src/moons/{moonsManager.js → moonsManager.ts} +37 -11
- package/src/render/{frameBudget.js → frameBudget.ts} +15 -8
- package/src/render/index.ts +14 -0
- package/src/render/{viewportCompositor.js → viewportCompositor.ts} +121 -59
- package/src/studio/headerBroadcast.ts +14 -1
- package/src/studio/hostTransport.ts +2 -1
- package/src/studio/index.ts +1 -1
- package/src/utils/dispose.ts +47 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/materials.ts +44 -0
- package/src/world/{cloudShell.js → cloudShell.ts} +19 -12
- package/src/world/index.ts +7 -0
- package/src/world/worldGLB.ts +41 -0
- package/src/world/{worldManager.js → worldManager.ts} +67 -32
- package/src/world/{worldNormal.js → worldNormal.ts} +25 -20
- package/src/world/{worldTexture.js → worldTexture.ts} +70 -51
- package/dist/types/config/env.d.ts +0 -12
- package/dist/types/config/index.d.ts +0 -2
- package/dist/types/config/textureConfig.d.ts +0 -121
- package/dist/types/core/index.d.ts +0 -2
- package/dist/types/core/lightFresnel.d.ts +0 -5
- package/dist/types/core/lights.d.ts +0 -58
- package/dist/types/index.d.ts +0 -6
- package/dist/types/loaders/bodyTextureLoader.d.ts +0 -12
- package/dist/types/loaders/cubeMapLoader.d.ts +0 -70
- package/dist/types/loaders/glbLoader.d.ts +0 -24
- package/dist/types/loaders/index.d.ts +0 -5
- package/dist/types/moons/index.d.ts +0 -2
- package/dist/types/moons/moonsGeneration.d.ts +0 -72
- package/dist/types/moons/moonsManager.d.ts +0 -27
- package/dist/types/render/index.d.ts +0 -2
- package/dist/types/render/viewportCompositor.d.ts +0 -58
- package/dist/types/utils/dispose.d.ts +0 -2
- package/dist/types/utils/index.d.ts +0 -2
- package/dist/types/utils/materials.d.ts +0 -2
- package/dist/types/world/cloudShell.d.ts +0 -25
- package/dist/types/world/index.d.ts +0 -6
- package/dist/types/world/worldGLB.d.ts +0 -20
- package/dist/types/world/worldManager.d.ts +0 -63
- package/dist/types/world/worldNormal.d.ts +0 -43
- package/dist/types/world/worldTexture.d.ts +0 -80
- package/src/config/env.js +0 -38
- package/src/loaders/glbLoader.js +0 -78
- package/src/utils/dispose.js +0 -39
- package/src/utils/materials.js +0 -31
- package/src/world/worldGLB.js +0 -19
- /package/{src → dist}/render/index.js +0 -0
- /package/src/core/{index.js → index.ts} +0 -0
- /package/src/{index.js → index.ts} +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texture Configuration Loader
|
|
3
|
+
* Loads texture definitions from remote endpoint or uses passed config
|
|
4
|
+
*/
|
|
5
|
+
import { resolvePublicUrl } from './env.js';
|
|
6
|
+
/** The catalogue a public function was handed, or the loaded one. */
|
|
7
|
+
async function resolveConfig(config) {
|
|
8
|
+
return config || (await loadTextureConfig());
|
|
9
|
+
}
|
|
10
|
+
function buildTextureUrl(baseUrl, categoryPath, value) {
|
|
11
|
+
if (!value)
|
|
12
|
+
return null;
|
|
13
|
+
if (/^https?:\/\//i.test(value))
|
|
14
|
+
return value;
|
|
15
|
+
if (value.startsWith('/'))
|
|
16
|
+
return resolvePublicUrl(value);
|
|
17
|
+
const prefix = `${baseUrl || ''}${categoryPath || ''}`;
|
|
18
|
+
return resolvePublicUrl(`${prefix}/${value}`);
|
|
19
|
+
}
|
|
20
|
+
function findTextureItem(textureConfig, category, textureUrl) {
|
|
21
|
+
if (!textureUrl || !textureConfig)
|
|
22
|
+
return null;
|
|
23
|
+
const categoryConfig = textureConfig.textures?.[category];
|
|
24
|
+
if (!categoryConfig || !categoryConfig.items)
|
|
25
|
+
return null;
|
|
26
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
27
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
28
|
+
for (const item of categoryConfig.items) {
|
|
29
|
+
const resolved = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
|
|
30
|
+
if (!resolved)
|
|
31
|
+
continue;
|
|
32
|
+
if (resolved === textureUrl) {
|
|
33
|
+
return { item, baseUrl, categoryPath };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
let cachedConfig = null;
|
|
39
|
+
let configPromise = null;
|
|
40
|
+
let lastConfigUrl = null;
|
|
41
|
+
/**
|
|
42
|
+
* Load texture configuration from remote endpoint or use passed config
|
|
43
|
+
* @param {string} configUrl - Override config URL
|
|
44
|
+
* @param {boolean} useCache - Whether to use cached config if available
|
|
45
|
+
* @returns {Promise<TextureConfig>} Texture configuration object
|
|
46
|
+
*/
|
|
47
|
+
export async function loadTextureConfig(configUrl, useCache = true) {
|
|
48
|
+
if (useCache && cachedConfig) {
|
|
49
|
+
return cachedConfig;
|
|
50
|
+
}
|
|
51
|
+
if (configPromise) {
|
|
52
|
+
return configPromise;
|
|
53
|
+
}
|
|
54
|
+
if (configUrl) {
|
|
55
|
+
lastConfigUrl = configUrl;
|
|
56
|
+
}
|
|
57
|
+
const targetUrl = configUrl || lastConfigUrl;
|
|
58
|
+
configPromise = (async () => {
|
|
59
|
+
try {
|
|
60
|
+
if (!targetUrl) {
|
|
61
|
+
throw new Error('No texture config URL provided');
|
|
62
|
+
}
|
|
63
|
+
const response = await fetch(targetUrl);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
const config = (await response.json());
|
|
68
|
+
// Validate configuration structure
|
|
69
|
+
if (!config.textures || typeof config.textures !== 'object') {
|
|
70
|
+
throw new Error('Invalid configuration: missing textures object');
|
|
71
|
+
}
|
|
72
|
+
cachedConfig = config;
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('[TextureConfig] Failed to load config:', error);
|
|
77
|
+
// Return minimal fallback configuration when loading fails
|
|
78
|
+
console.warn('[TextureConfig] Using hardcoded fallback config');
|
|
79
|
+
const fallback = getHardcodedFallback();
|
|
80
|
+
cachedConfig = fallback;
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
configPromise = null;
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
return configPromise;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the full choice records for a category (clouds, moons, etc.) — label, texture URL, and the
|
|
91
|
+
* picture to show for it. The single resolution point for a category's items; `getTextureOptions`
|
|
92
|
+
* is the flat `{ label: url }` projection of this.
|
|
93
|
+
* @param {string} category - Texture category (clouds, moons)
|
|
94
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
95
|
+
* @returns {Promise<TextureChoice[]>} Choice records in catalogue order
|
|
96
|
+
*/
|
|
97
|
+
export async function getTextureChoices(category, config) {
|
|
98
|
+
const textureConfig = await resolveConfig(config);
|
|
99
|
+
const categoryConfig = textureConfig.textures[category];
|
|
100
|
+
if (!categoryConfig) {
|
|
101
|
+
console.warn(`[TextureConfig] Category '${category}' not found`);
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
105
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
106
|
+
return (categoryConfig.items || []).flatMap((item) => {
|
|
107
|
+
const url = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
|
|
108
|
+
if (!url)
|
|
109
|
+
return [];
|
|
110
|
+
return [{
|
|
111
|
+
id: item.id,
|
|
112
|
+
label: item.label || item.id,
|
|
113
|
+
url,
|
|
114
|
+
still: item.still ? buildTextureUrl(baseUrl, categoryPath, item.still) : null,
|
|
115
|
+
}];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get texture options for a specific category (clouds, moons, etc.)
|
|
120
|
+
* @param {string} category - Texture category (clouds, moons, skyboxes)
|
|
121
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
122
|
+
* @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
|
|
123
|
+
*/
|
|
124
|
+
export async function getTextureOptions(category, config) {
|
|
125
|
+
const choices = await getTextureChoices(category, config);
|
|
126
|
+
return Object.fromEntries(choices.map(c => [c.label, c.url]));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get default texture for a category
|
|
130
|
+
* @param {string} category - Texture category
|
|
131
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
132
|
+
* @returns {Promise<string|null>} Default texture path, or null when the category has no texture
|
|
133
|
+
*/
|
|
134
|
+
export async function getDefaultTexture(category, config) {
|
|
135
|
+
const textureConfig = await resolveConfig(config);
|
|
136
|
+
const categoryConfig = textureConfig.textures[category];
|
|
137
|
+
if (!categoryConfig) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
141
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
142
|
+
const defaultFilename = categoryConfig.defaultTexture;
|
|
143
|
+
if (defaultFilename) {
|
|
144
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${defaultFilename}`);
|
|
145
|
+
}
|
|
146
|
+
// Fallback to first item
|
|
147
|
+
if (categoryConfig.items && categoryConfig.items.length > 0) {
|
|
148
|
+
const firstItem = categoryConfig.items[0];
|
|
149
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${firstItem.filename || firstItem.id}`);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Return the configured fallback URL for a given texture if defined.
|
|
155
|
+
* @param {string} category - Texture category (clouds, moons, etc.)
|
|
156
|
+
* @param {string} textureUrl - Resolved texture URL
|
|
157
|
+
* @param {TextureConfig|null} config - Optional texture config override
|
|
158
|
+
* @returns {string|null} Resolved fallback texture URL or null
|
|
159
|
+
*/
|
|
160
|
+
export function getTextureFallbackUrl(category, textureUrl, config = null) {
|
|
161
|
+
const textureConfig = config || cachedConfig;
|
|
162
|
+
if (!textureConfig)
|
|
163
|
+
return null;
|
|
164
|
+
const found = findTextureItem(textureConfig, category, textureUrl);
|
|
165
|
+
if (!found || !found.item?.fallback)
|
|
166
|
+
return null;
|
|
167
|
+
return buildTextureUrl(found.baseUrl, found.categoryPath, found.item.fallback);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get texture by ID within a category
|
|
171
|
+
* @param {string} category - Texture category
|
|
172
|
+
* @param {string} textureId - Texture ID
|
|
173
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
174
|
+
* @returns {Promise<TextureItem|null>} Texture definition object
|
|
175
|
+
*/
|
|
176
|
+
export async function getTextureById(category, textureId, config) {
|
|
177
|
+
const textureConfig = await resolveConfig(config);
|
|
178
|
+
const categoryConfig = textureConfig.textures[category];
|
|
179
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return categoryConfig.items.find(item => item.id === textureId) || null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get full URL for a texture
|
|
186
|
+
* @param {string} category - Texture category
|
|
187
|
+
* @param {string} textureId - Texture ID
|
|
188
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
189
|
+
* @returns {Promise<string|null>} Full texture URL
|
|
190
|
+
*/
|
|
191
|
+
export async function getTextureUrl(category, textureId, config) {
|
|
192
|
+
const texture = await getTextureById(category, textureId, config);
|
|
193
|
+
if (!texture)
|
|
194
|
+
return null;
|
|
195
|
+
const textureConfig = await resolveConfig(config);
|
|
196
|
+
// `getTextureById` just found the item in this category, so it is there (unless the catalogue
|
|
197
|
+
// was reloaded in between, which throws here, as it always did).
|
|
198
|
+
const categoryConfig = textureConfig.textures[category];
|
|
199
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
200
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
201
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${texture.filename || texture.id}`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Search textures by tags
|
|
205
|
+
* @param {string} category - Texture category
|
|
206
|
+
* @param {string[]} tags - Tags to search for
|
|
207
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
208
|
+
* @returns {Promise<TextureItem[]>} Array of matching texture definitions
|
|
209
|
+
*/
|
|
210
|
+
export async function searchTexturesByTags(category, tags, config) {
|
|
211
|
+
const textureConfig = await resolveConfig(config);
|
|
212
|
+
const categoryConfig = textureConfig.textures[category];
|
|
213
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
return categoryConfig.items.filter(item => item.tags && tags.some(tag => item.tags.includes(tag)));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get skybox options formatted for GUI dropdowns
|
|
220
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
221
|
+
* @returns {Promise<Record<string, string>>} `{ label: id }`
|
|
222
|
+
*/
|
|
223
|
+
export async function getSkyboxOptions(config) {
|
|
224
|
+
const textureConfig = await resolveConfig(config);
|
|
225
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
226
|
+
if (!skyboxConfig) {
|
|
227
|
+
console.warn('[TextureConfig] Skyboxes category not found');
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
const options = {};
|
|
231
|
+
// Reads `items` unguarded, unlike the other readers: a skyboxes category without items throws here.
|
|
232
|
+
skyboxConfig.items.forEach(item => {
|
|
233
|
+
const label = item.label || item.id;
|
|
234
|
+
const id = item.id;
|
|
235
|
+
options[label] = id;
|
|
236
|
+
});
|
|
237
|
+
return options;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get default skybox ID
|
|
241
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
242
|
+
* @returns {Promise<string>} Default skybox ID
|
|
243
|
+
*/
|
|
244
|
+
export async function getDefaultSkybox(config) {
|
|
245
|
+
const textureConfig = await resolveConfig(config);
|
|
246
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
247
|
+
if (!skyboxConfig) {
|
|
248
|
+
return '001'; // fallback
|
|
249
|
+
}
|
|
250
|
+
return skyboxConfig.defaultSkybox || '001';
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Hardcoded fallback configuration (minimal)
|
|
254
|
+
* Used when remote config loading fails
|
|
255
|
+
*/
|
|
256
|
+
function getHardcodedFallback() {
|
|
257
|
+
return {
|
|
258
|
+
version: "1.0-fallback",
|
|
259
|
+
lastUpdated: new Date().toISOString(),
|
|
260
|
+
baseUrl: "/default",
|
|
261
|
+
textures: {
|
|
262
|
+
clouds: {
|
|
263
|
+
category: "atmospheric",
|
|
264
|
+
basePath: "/clouds",
|
|
265
|
+
defaultTexture: "04-clouds.ktx2",
|
|
266
|
+
items: [
|
|
267
|
+
{ id: "clouds-1", filename: "04-clouds.ktx2", label: "Clouds", tags: ["natural"] },
|
|
268
|
+
{ id: "entangled-1", filename: "06-entangled.ktx2", label: "Entangled", tags: ["quantum"] }
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
moons: {
|
|
272
|
+
category: "celestial",
|
|
273
|
+
basePath: "/textures/moons",
|
|
274
|
+
defaultTexture: "moon-equirectangular.ktx2",
|
|
275
|
+
items: [
|
|
276
|
+
{ id: "default-moon", filename: "moon-equirectangular.ktx2", label: "Default Moon", tags: ["lunar"] }
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
skyboxes: {
|
|
280
|
+
category: "environment",
|
|
281
|
+
basePath: "/skybox",
|
|
282
|
+
defaultSkybox: "001",
|
|
283
|
+
items: [
|
|
284
|
+
{ id: "001", label: "Deep Space", tags: ["space"] }
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Reload configuration (force refresh)
|
|
292
|
+
* @param {string} configUrl - Override config URL
|
|
293
|
+
* @returns {Promise<TextureConfig>} Fresh configuration object
|
|
294
|
+
*/
|
|
295
|
+
export async function reloadTextureConfig(configUrl) {
|
|
296
|
+
cachedConfig = null;
|
|
297
|
+
configPromise = null;
|
|
298
|
+
return loadTextureConfig(configUrl || lastConfigUrl || undefined, false);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get configuration metadata
|
|
302
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
303
|
+
* @returns {Promise<TextureConfigMetadata>} Configuration metadata
|
|
304
|
+
*/
|
|
305
|
+
export async function getConfigMetadata(config) {
|
|
306
|
+
const textureConfig = await resolveConfig(config);
|
|
307
|
+
return {
|
|
308
|
+
version: textureConfig.version,
|
|
309
|
+
lastUpdated: textureConfig.lastUpdated,
|
|
310
|
+
categories: Object.keys(textureConfig.textures),
|
|
311
|
+
totalTextures: Object.values(textureConfig.textures).reduce(
|
|
312
|
+
// Only listed categories are values; a missing one is a key that is not there.
|
|
313
|
+
(sum, cat) => {
|
|
314
|
+
const items = cat.items;
|
|
315
|
+
return sum + (items ? items.length : 0);
|
|
316
|
+
}, 0)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
function getLightFresnel({ rimHex = 0x0088ff, facingHex = 0x000000 } = {}) {
|
|
3
|
+
const uniforms = {
|
|
4
|
+
color1: { value: new THREE.Color(rimHex) },
|
|
5
|
+
color2: { value: new THREE.Color(facingHex) },
|
|
6
|
+
fresnelBias: { value: 0.1 },
|
|
7
|
+
fresnelScale: { value: 1.0 },
|
|
8
|
+
fresnelPower: { value: 4.0 },
|
|
9
|
+
};
|
|
10
|
+
const vs = `
|
|
11
|
+
uniform float fresnelBias;
|
|
12
|
+
uniform float fresnelScale;
|
|
13
|
+
uniform float fresnelPower;
|
|
14
|
+
|
|
15
|
+
varying float vReflectionFactor;
|
|
16
|
+
|
|
17
|
+
void main() {
|
|
18
|
+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
19
|
+
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
|
|
20
|
+
|
|
21
|
+
// Transform normal to world space
|
|
22
|
+
vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
|
|
23
|
+
|
|
24
|
+
// Proper view direction (from surface point towards camera)
|
|
25
|
+
vec3 viewDir = normalize( cameraPosition - worldPosition.xyz );
|
|
26
|
+
|
|
27
|
+
// Classic rim: stronger at grazing angles
|
|
28
|
+
float ndotv = max( dot( worldNormal, viewDir ), 0.0 );
|
|
29
|
+
vReflectionFactor = fresnelBias + fresnelScale * pow( 1.0 - ndotv, fresnelPower );
|
|
30
|
+
|
|
31
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const fs = `
|
|
35
|
+
uniform vec3 color1;
|
|
36
|
+
uniform vec3 color2;
|
|
37
|
+
|
|
38
|
+
varying float vReflectionFactor;
|
|
39
|
+
|
|
40
|
+
void main() {
|
|
41
|
+
float f = clamp( vReflectionFactor, 0.0, 1.0 );
|
|
42
|
+
gl_FragColor = vec4(mix(color2, color1, vec3(f)), f);
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
const lightFresnel = new THREE.ShaderMaterial({
|
|
46
|
+
uniforms: uniforms,
|
|
47
|
+
vertexShader: vs,
|
|
48
|
+
fragmentShader: fs,
|
|
49
|
+
transparent: true,
|
|
50
|
+
blending: THREE.CustomBlending,
|
|
51
|
+
blendSrc: THREE.OneFactor,
|
|
52
|
+
blendDst: THREE.OneFactor,
|
|
53
|
+
blendEquation: THREE.AddEquation,
|
|
54
|
+
depthWrite: false,
|
|
55
|
+
depthTest: true,
|
|
56
|
+
depthFunc: THREE.LessDepth,
|
|
57
|
+
side: THREE.FrontSide,
|
|
58
|
+
toneMapped: false,
|
|
59
|
+
// wireframe: true,
|
|
60
|
+
});
|
|
61
|
+
return lightFresnel;
|
|
62
|
+
}
|
|
63
|
+
export { getLightFresnel };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Ensure a scene has the default hemisphere + ambient lights. Idempotent PER SCENE.
|
|
4
|
+
*
|
|
5
|
+
* A multi-orbiter realm renders N independent scenes through one renderer. A module-level
|
|
6
|
+
* singleton light is wrong twice over — it's only added to the FIRST scene, and a THREE light can
|
|
7
|
+
* live in just one scene (adding it to a second removes it from the first), so scenes 2..N rendered
|
|
8
|
+
* DARK ("only one entangled world"). So create + add the lights PER scene, keyed by name so a
|
|
9
|
+
* re-call on the same scene is a no-op. Single-orbiter (one scene) is unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureDefaultLights(scene: THREE.Scene): {
|
|
12
|
+
hemi: THREE.HemisphereLight;
|
|
13
|
+
ambient: THREE.AmbientLight;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Apply a lighting patch to ONE scene's default lights.
|
|
17
|
+
*
|
|
18
|
+
* The scene is required. This used to address module-global light refs pointing at whichever scene
|
|
19
|
+
* was ensured last, so on a page rendering several scenes every lighting update landed on one of
|
|
20
|
+
* them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
|
|
21
|
+
*
|
|
22
|
+
* Every field is optional; an omitted one leaves that part of the lighting as it is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateDefaultLights(scene: THREE.Scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere }?: {
|
|
25
|
+
/** ambient light intensity (clamped at 0) */
|
|
26
|
+
ambientIntensity?: number;
|
|
27
|
+
/** hemisphere light intensity (clamped at 0) */
|
|
28
|
+
diffuseIntensity?: number;
|
|
29
|
+
/** hemisphere light colour */
|
|
30
|
+
lightColor?: THREE.Color | number | string;
|
|
31
|
+
/** hemisphere light direction */
|
|
32
|
+
lightDirection?: {
|
|
33
|
+
x: number;
|
|
34
|
+
y?: number;
|
|
35
|
+
z?: number;
|
|
36
|
+
};
|
|
37
|
+
/** switch the hemisphere light on or off */
|
|
38
|
+
hemisphere?: boolean;
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* Switch a scene's default hemisphere light on or off (the ambient light is untouched). Hosts turn it
|
|
42
|
+
* off when a directional light — the star — lights the scene, so night sides can read.
|
|
43
|
+
*/
|
|
44
|
+
export declare function setHemisphereEnabled(scene: THREE.Scene, on: boolean): void;
|
|
45
|
+
export declare function addDefaultLights(scene: THREE.Scene): {
|
|
46
|
+
hemi: THREE.HemisphereLight;
|
|
47
|
+
ambient: THREE.AmbientLight;
|
|
48
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Ensure a scene has the default hemisphere + ambient lights. Idempotent PER SCENE.
|
|
4
|
+
*
|
|
5
|
+
* A multi-orbiter realm renders N independent scenes through one renderer. A module-level
|
|
6
|
+
* singleton light is wrong twice over — it's only added to the FIRST scene, and a THREE light can
|
|
7
|
+
* live in just one scene (adding it to a second removes it from the first), so scenes 2..N rendered
|
|
8
|
+
* DARK ("only one entangled world"). So create + add the lights PER scene, keyed by name so a
|
|
9
|
+
* re-call on the same scene is a no-op. Single-orbiter (one scene) is unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export function ensureDefaultLights(scene) {
|
|
12
|
+
// Found by name: the objects under these names are the lights this function added.
|
|
13
|
+
let hemi = scene.getObjectByName('ew_HemisphereLight');
|
|
14
|
+
if (!hemi) {
|
|
15
|
+
hemi = new THREE.HemisphereLight(0xffffff, 0xffffff, 1.0);
|
|
16
|
+
hemi.position.set(1, 1, 1);
|
|
17
|
+
hemi.name = 'ew_HemisphereLight';
|
|
18
|
+
scene.add(hemi);
|
|
19
|
+
}
|
|
20
|
+
let ambient = scene.getObjectByName('ew_AmbientLight');
|
|
21
|
+
if (!ambient) {
|
|
22
|
+
ambient = new THREE.AmbientLight(0xffffff, 1.0);
|
|
23
|
+
ambient.name = 'ew_AmbientLight';
|
|
24
|
+
scene.add(ambient);
|
|
25
|
+
}
|
|
26
|
+
return { hemi, ambient };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Apply a lighting patch to ONE scene's default lights.
|
|
30
|
+
*
|
|
31
|
+
* The scene is required. This used to address module-global light refs pointing at whichever scene
|
|
32
|
+
* was ensured last, so on a page rendering several scenes every lighting update landed on one of
|
|
33
|
+
* them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
|
|
34
|
+
*
|
|
35
|
+
* Every field is optional; an omitted one leaves that part of the lighting as it is.
|
|
36
|
+
*/
|
|
37
|
+
export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere } = {}) {
|
|
38
|
+
if (!scene?.isObject3D) {
|
|
39
|
+
console.error('[lights] updateDefaultLights(scene, patch) requires the scene to update; ignoring patch.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { hemi, ambient } = ensureDefaultLights(scene);
|
|
43
|
+
// `hemisphere: false` switches the white hemisphere off (e.g. when a star lights the scene);
|
|
44
|
+
// omitted = unchanged, so old callers keep today's look.
|
|
45
|
+
if (typeof hemisphere === 'boolean')
|
|
46
|
+
hemi.visible = hemisphere;
|
|
47
|
+
if (typeof ambientIntensity === 'number')
|
|
48
|
+
ambient.intensity = Math.max(0, ambientIntensity);
|
|
49
|
+
if (typeof diffuseIntensity === 'number')
|
|
50
|
+
hemi.intensity = Math.max(0, diffuseIntensity);
|
|
51
|
+
if (lightColor != null) {
|
|
52
|
+
if (lightColor instanceof THREE.Color) {
|
|
53
|
+
hemi.color.copy(lightColor);
|
|
54
|
+
}
|
|
55
|
+
else if (typeof lightColor === 'number') {
|
|
56
|
+
hemi.color.setHex(lightColor);
|
|
57
|
+
}
|
|
58
|
+
else if (typeof lightColor === 'string') {
|
|
59
|
+
hemi.color.set(lightColor);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (lightDirection && typeof lightDirection.x === 'number') {
|
|
63
|
+
hemi.position.set(lightDirection.x, lightDirection.y ?? 0, lightDirection.z ?? 0).normalize();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Switch a scene's default hemisphere light on or off (the ambient light is untouched). Hosts turn it
|
|
68
|
+
* off when a directional light — the star — lights the scene, so night sides can read.
|
|
69
|
+
*/
|
|
70
|
+
export function setHemisphereEnabled(scene, on) {
|
|
71
|
+
if (!scene?.isObject3D)
|
|
72
|
+
return;
|
|
73
|
+
const { hemi } = ensureDefaultLights(scene);
|
|
74
|
+
hemi.visible = Boolean(on);
|
|
75
|
+
}
|
|
76
|
+
// Back-compat helper
|
|
77
|
+
export function addDefaultLights(scene) {
|
|
78
|
+
return ensureDefaultLights(scene);
|
|
79
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Main exports for entangled-worlds-orbiters-shared library
|
|
2
|
+
export * from './world/index.js';
|
|
3
|
+
export * from './moons/index.js';
|
|
4
|
+
export * from './loaders/index.js';
|
|
5
|
+
export * from './core/index.js';
|
|
6
|
+
export * from './config/index.js';
|
|
7
|
+
export * from './utils/index.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* `url`: `.ktx2` goes through the renderer's KTX2 transcoder; anything else through
|
|
4
|
+
* THREE.TextureLoader. `signal`: an abort during the load rejects the promise. `renderer`: REQUIRED
|
|
5
|
+
* for a `.ktx2` url.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadTexture(url: string, { signal, renderer }?: {
|
|
8
|
+
signal?: AbortSignal | undefined;
|
|
9
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
10
|
+
}): Promise<THREE.Texture>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
3
|
+
const textureLoader = new THREE.TextureLoader();
|
|
4
|
+
/**
|
|
5
|
+
* `url`: `.ktx2` goes through the renderer's KTX2 transcoder; anything else through
|
|
6
|
+
* THREE.TextureLoader. `signal`: an abort during the load rejects the promise. `renderer`: REQUIRED
|
|
7
|
+
* for a `.ktx2` url.
|
|
8
|
+
*/
|
|
9
|
+
export function loadTexture(url, { signal, renderer } = {}) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
if (url && url.endsWith('.ktx2')) {
|
|
12
|
+
// A missing renderer throws inside (as before): the type allows `null` only because the
|
|
13
|
+
// non-KTX2 path never needs one.
|
|
14
|
+
const ktx2 = getKTX2LoaderForRenderer(renderer);
|
|
15
|
+
const onAbort = () => reject(new Error('aborted'));
|
|
16
|
+
if (signal)
|
|
17
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
18
|
+
ktx2.load(url, (tex) => { if (signal)
|
|
19
|
+
signal.removeEventListener('abort', onAbort); resolve(tex); }, undefined, (err) => { if (signal)
|
|
20
|
+
signal.removeEventListener('abort', onAbort); reject(err); });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const tex = textureLoader.load(url, () => resolve(tex), undefined, (err) => reject(err));
|
|
24
|
+
if (signal) {
|
|
25
|
+
signal.addEventListener('abort', () => reject(new Error('aborted')));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/** A skycube id (`'7'`, `'07_skycube'`, `'skybox-007'`, …) as its three-digit folder name; `'001'` when none. */
|
|
3
|
+
export declare function normalizeSkycubeName(raw: string | null | undefined): string;
|
|
4
|
+
export declare function buildSkycubefaceKeys(skycubeId: string | null | undefined, extension?: string): {
|
|
5
|
+
right: string;
|
|
6
|
+
left: string;
|
|
7
|
+
top: string;
|
|
8
|
+
bottom: string;
|
|
9
|
+
front: string;
|
|
10
|
+
back: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Main loader function - always uses explicit face URLs.
|
|
14
|
+
* `renderer` binds the KTX2 transcoder to the right WebGL context. REQUIRED (3.0) when the KTX2
|
|
15
|
+
* path is taken — the old `window.__EW_RENDERER` fallback is gone; thread the owning view's
|
|
16
|
+
* renderer. (The PNG/JPG path is renderer-agnostic, so its absence only bites on KTX2.)
|
|
17
|
+
*
|
|
18
|
+
* - `faceKeys`: explicit face URLs; when absent they are built from `skycubeName`.
|
|
19
|
+
* - `skycubeName`: skybox id (e.g. `'001'`), used when `faceKeys` is absent.
|
|
20
|
+
* - `preferKTX2` (true).
|
|
21
|
+
* - `forceLow`: force (or forbid) the low-resolution faces; omitted = detect the device.
|
|
22
|
+
*
|
|
23
|
+
* Resolves to what `setBackground` takes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadCubeMap({ faceKeys, skycubeName, preferKTX2, forceLow, renderer, }?: {
|
|
26
|
+
faceKeys?: {
|
|
27
|
+
right: string;
|
|
28
|
+
left: string;
|
|
29
|
+
top: string;
|
|
30
|
+
bottom: string;
|
|
31
|
+
front: string;
|
|
32
|
+
back: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
skycubeName?: string | undefined;
|
|
35
|
+
preferKTX2?: boolean | undefined;
|
|
36
|
+
forceLow?: boolean | undefined;
|
|
37
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
38
|
+
}): Promise<SkyboxResource>;
|
|
39
|
+
/**
|
|
40
|
+
* What `loadCubeMap` resolves to: a skybox mesh wrapper (the KTX2 path) or a cube texture (the
|
|
41
|
+
* PNG/JPG path). Read `mesh` to tell them apart.
|
|
42
|
+
*/
|
|
43
|
+
export type SkyboxResource = {
|
|
44
|
+
__ewType: 'skybox-mesh';
|
|
45
|
+
mesh: THREE.Mesh;
|
|
46
|
+
} | (THREE.CubeTexture & {
|
|
47
|
+
mesh?: undefined;
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Show a loaded skybox as the scene's background. A previous skybox mesh or cube-texture background is
|
|
51
|
+
* disposed (a plain-texture background is not). Takes what `loadCubeMap` resolved to (or any texture);
|
|
52
|
+
* anything else, `null` included, clears it.
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
export declare function setBackground(scene: THREE.Scene, resource: unknown): void;
|
|
56
|
+
export declare function initSpaceScene(scene: THREE.Scene, { renderer }?: {
|
|
57
|
+
renderer?: THREE.WebGLRenderer | null;
|
|
58
|
+
}): Promise<void>;
|