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,291 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { getPublicBase } from '../config/env.js';
|
|
3
|
+
import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
4
|
+
/** A skycube id (`'7'`, `'07_skycube'`, `'skybox-007'`, …) as its three-digit folder name; `'001'` when none. */
|
|
5
|
+
export function normalizeSkycubeName(raw) {
|
|
6
|
+
if (!raw)
|
|
7
|
+
return '001';
|
|
8
|
+
const legacy = raw.match(/^(\d{1,2})_skycube(?:_low)?$/);
|
|
9
|
+
if (legacy)
|
|
10
|
+
return String(parseInt(legacy[1], 10)).padStart(3, '0');
|
|
11
|
+
const num = raw.match(/(\d{1,3})/);
|
|
12
|
+
if (num)
|
|
13
|
+
return String(parseInt(num[1], 10)).padStart(3, '0');
|
|
14
|
+
return '001';
|
|
15
|
+
}
|
|
16
|
+
// Standard face order for cube maps
|
|
17
|
+
const FACE_ORDER = ['right', 'left', 'top', 'bottom', 'front', 'back'];
|
|
18
|
+
// Build complete face URL sets for a given skycube ID (now returns relative paths)
|
|
19
|
+
export function buildSkycubefaceKeys(skycubeId, extension = 'ktx2') {
|
|
20
|
+
const id = normalizeSkycubeName(skycubeId);
|
|
21
|
+
const base = `/default/skybox/${id}`;
|
|
22
|
+
return {
|
|
23
|
+
right: `${base}/right.${extension}`,
|
|
24
|
+
left: `${base}/left.${extension}`,
|
|
25
|
+
top: `${base}/top.${extension}`,
|
|
26
|
+
bottom: `${base}/bottom.${extension}`,
|
|
27
|
+
front: `${base}/front.${extension}`,
|
|
28
|
+
back: `${base}/back.${extension}`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Heuristic low-performance detection
|
|
32
|
+
function isLowPerformanceDevice() {
|
|
33
|
+
try {
|
|
34
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
35
|
+
const isMobileUA = /mobi|android|iphone|ipad|ipod/.test(ua);
|
|
36
|
+
// `deviceMemory` is not in every browser, nor in the DOM types.
|
|
37
|
+
const mem = navigator.deviceMemory || 0; // GB
|
|
38
|
+
const cores = navigator.hardwareConcurrency || 0;
|
|
39
|
+
const dpr = window.devicePixelRatio || 1;
|
|
40
|
+
return isMobileUA || mem && mem <= 4 || cores && cores <= 4 || (dpr > 2 && cores <= 6);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Validate face URLs object
|
|
47
|
+
function validatefaceKeys(faceKeys) {
|
|
48
|
+
if (!faceKeys || typeof faceKeys !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
const keys = faceKeys;
|
|
51
|
+
return FACE_ORDER.every(face => keys[face] && typeof keys[face] === 'string');
|
|
52
|
+
}
|
|
53
|
+
// Detect extension type from face URLs
|
|
54
|
+
function detectExtension(faceKeys) {
|
|
55
|
+
const url = faceKeys.right || '';
|
|
56
|
+
if (url.toLowerCase().includes('.ktx2'))
|
|
57
|
+
return 'ktx2';
|
|
58
|
+
if (url.toLowerCase().match(/\.(jpg|jpeg|png)$/))
|
|
59
|
+
return 'jpg';
|
|
60
|
+
return 'ktx2'; // default assumption
|
|
61
|
+
}
|
|
62
|
+
// How many faces may be in flight at once. Not six: the KTX2 worker pool three.js gives us holds
|
|
63
|
+
// four transcoder workers, each carrying its own copy of the basis wasm, and that same pool also
|
|
64
|
+
// serves body textures and GLBs. Asking for all six together spawned every worker and queued the
|
|
65
|
+
// other loaders behind the whole sky; measured on cold fetches it was no faster than three.
|
|
66
|
+
const FACES_IN_FLIGHT = 3;
|
|
67
|
+
// Load KTX2 faces and create skybox mesh
|
|
68
|
+
async function loadFacesKTX2(faceKeys, renderer) {
|
|
69
|
+
const loader = getKTX2LoaderForRenderer(renderer);
|
|
70
|
+
const anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
|
|
71
|
+
const loadFace = async (face) => {
|
|
72
|
+
const tex = await loader.loadAsync(faceKeys[face]);
|
|
73
|
+
tex.flipY = false;
|
|
74
|
+
tex.wrapS = THREE.ClampToEdgeWrapping;
|
|
75
|
+
tex.wrapT = THREE.ClampToEdgeWrapping;
|
|
76
|
+
tex.anisotropy = anisotropy;
|
|
77
|
+
return { face, tex };
|
|
78
|
+
};
|
|
79
|
+
// Faces overlap instead of chaining. Awaiting each one in turn made every face wait on the
|
|
80
|
+
// previous face's download *and* its transcode, so the sky arrived six round trips deep.
|
|
81
|
+
const settled = [];
|
|
82
|
+
for (let i = 0; i < FACE_ORDER.length; i += FACES_IN_FLIGHT) {
|
|
83
|
+
const wave = FACE_ORDER.slice(i, i + FACES_IN_FLIGHT).map(face => loadFace(face));
|
|
84
|
+
settled.push(...await Promise.allSettled(wave));
|
|
85
|
+
}
|
|
86
|
+
// allSettled, not all: a bare Promise.all rejects on the first failure and leaks the faces that
|
|
87
|
+
// land after it. Settle every face first, then dispose everything that arrived before rethrowing.
|
|
88
|
+
const loaded = [];
|
|
89
|
+
let failure = null;
|
|
90
|
+
for (const result of settled) {
|
|
91
|
+
if (result.status === 'fulfilled')
|
|
92
|
+
loaded.push(result.value);
|
|
93
|
+
else if (!failure)
|
|
94
|
+
failure = result.reason;
|
|
95
|
+
}
|
|
96
|
+
if (failure) {
|
|
97
|
+
// Dispose already loaded textures on failure
|
|
98
|
+
loaded.forEach(f => f.tex.dispose?.());
|
|
99
|
+
throw failure;
|
|
100
|
+
}
|
|
101
|
+
const size = 500;
|
|
102
|
+
const box = new THREE.BoxGeometry(size, size, size);
|
|
103
|
+
// Map faces to geometry indices (corrected for orientation)
|
|
104
|
+
const orderMap = { right: 0, left: 1, top: 3, bottom: 2, front: 4, back: 5 };
|
|
105
|
+
const mats = new Array(6).fill(null);
|
|
106
|
+
loaded.forEach(({ face, tex }) => {
|
|
107
|
+
mats[orderMap[face]] = new THREE.MeshBasicMaterial({
|
|
108
|
+
map: tex,
|
|
109
|
+
side: THREE.BackSide,
|
|
110
|
+
depthWrite: false
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
// Fill any missing materials with black
|
|
114
|
+
for (let i = 0; i < 6; i++) {
|
|
115
|
+
if (!mats[i]) {
|
|
116
|
+
mats[i] = new THREE.MeshBasicMaterial({
|
|
117
|
+
color: 0x000000,
|
|
118
|
+
side: THREE.BackSide,
|
|
119
|
+
depthWrite: false
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Every slot is filled by now (a face's material or black).
|
|
124
|
+
const mesh = new THREE.Mesh(box, mats);
|
|
125
|
+
mesh.name = 'SkyboxKTX2Mesh';
|
|
126
|
+
return { __ewType: 'skybox-mesh', mesh };
|
|
127
|
+
}
|
|
128
|
+
// Load JPG/PNG faces and create CubeTexture
|
|
129
|
+
function loadFacesJPG(faceKeys) {
|
|
130
|
+
const urls = FACE_ORDER.map(face => faceKeys[face]);
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
new THREE.CubeTextureLoader().load(urls, tex => {
|
|
133
|
+
tex.magFilter = THREE.LinearFilter;
|
|
134
|
+
tex.minFilter = THREE.LinearMipMapLinearFilter;
|
|
135
|
+
tex.anisotropy = 8;
|
|
136
|
+
resolve(tex);
|
|
137
|
+
}, undefined, err => reject(err));
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Resolve relative paths to full URLs
|
|
141
|
+
function resolveUrls(faceKeys) {
|
|
142
|
+
const PUBLIC_BASE = getPublicBase();
|
|
143
|
+
// Every face is set below.
|
|
144
|
+
const resolved = {};
|
|
145
|
+
FACE_ORDER.forEach(face => {
|
|
146
|
+
const url = faceKeys[face];
|
|
147
|
+
if (url && url.startsWith('/')) {
|
|
148
|
+
resolved[face] = `${PUBLIC_BASE}${url}`;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
resolved[face] = url; // already absolute or invalid
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Main loader function - always uses explicit face URLs.
|
|
158
|
+
* `renderer` binds the KTX2 transcoder to the right WebGL context. REQUIRED (3.0) when the KTX2
|
|
159
|
+
* path is taken — the old `window.__EW_RENDERER` fallback is gone; thread the owning view's
|
|
160
|
+
* renderer. (The PNG/JPG path is renderer-agnostic, so its absence only bites on KTX2.)
|
|
161
|
+
*
|
|
162
|
+
* - `faceKeys`: explicit face URLs; when absent they are built from `skycubeName`.
|
|
163
|
+
* - `skycubeName`: skybox id (e.g. `'001'`), used when `faceKeys` is absent.
|
|
164
|
+
* - `preferKTX2` (true).
|
|
165
|
+
* - `forceLow`: force (or forbid) the low-resolution faces; omitted = detect the device.
|
|
166
|
+
*
|
|
167
|
+
* Resolves to what `setBackground` takes.
|
|
168
|
+
*/
|
|
169
|
+
export async function loadCubeMap({ faceKeys, skycubeName, preferKTX2 = true, forceLow, renderer, } = {}) {
|
|
170
|
+
// If no face URLs provided, build them from skycube name
|
|
171
|
+
if (!faceKeys && skycubeName) {
|
|
172
|
+
const low = typeof forceLow === 'boolean' ? forceLow : isLowPerformanceDevice();
|
|
173
|
+
const extension = preferKTX2 ? 'ktx2' : 'jpg';
|
|
174
|
+
const id = normalizeSkycubeName(skycubeName);
|
|
175
|
+
const suffix = low ? '_low' : '';
|
|
176
|
+
const relativePath = `/default/skybox/${id}${suffix}`;
|
|
177
|
+
faceKeys = {
|
|
178
|
+
right: `${relativePath}/right.${extension}`,
|
|
179
|
+
left: `${relativePath}/left.${extension}`,
|
|
180
|
+
top: `${relativePath}/top.${extension}`,
|
|
181
|
+
bottom: `${relativePath}/bottom.${extension}`,
|
|
182
|
+
front: `${relativePath}/front.${extension}`,
|
|
183
|
+
back: `${relativePath}/back.${extension}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (!validatefaceKeys(faceKeys)) {
|
|
187
|
+
throw new Error('Invalid face URLs provided');
|
|
188
|
+
}
|
|
189
|
+
// Resolve relative paths to full URLs
|
|
190
|
+
const resolvedUrls = resolveUrls(faceKeys);
|
|
191
|
+
const extension = detectExtension(resolvedUrls);
|
|
192
|
+
// Try KTX2 first if preferred and supported
|
|
193
|
+
if (preferKTX2 && extension === 'ktx2' && renderer) {
|
|
194
|
+
try {
|
|
195
|
+
return await loadFacesKTX2(resolvedUrls, renderer);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
console.warn('[CubeMapLoader] KTX2 load failed, trying PNG fallback:', e);
|
|
199
|
+
// Fall through to PNG attempt
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Try PNG fallback (convert URLs if needed)
|
|
203
|
+
// Every face is set below.
|
|
204
|
+
const pngUrls = {};
|
|
205
|
+
FACE_ORDER.forEach(face => {
|
|
206
|
+
pngUrls[face] = resolvedUrls[face].replace(/\.ktx2$/, '.png');
|
|
207
|
+
});
|
|
208
|
+
try {
|
|
209
|
+
return await loadFacesJPG(pngUrls);
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
console.error('[CubeMapLoader] All loading attempts failed:', e);
|
|
213
|
+
throw new Error('Failed to load cube map with any format');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Dispose helpers to ensure clean swaps
|
|
217
|
+
function disposeSkyboxMesh(mesh) {
|
|
218
|
+
try {
|
|
219
|
+
// The skybox mesh's materials are the MeshBasicMaterials `loadFacesKTX2` made.
|
|
220
|
+
const material = mesh.material;
|
|
221
|
+
const mats = Array.isArray(material) ? material : [material];
|
|
222
|
+
mats.forEach(mat => {
|
|
223
|
+
if (!mat)
|
|
224
|
+
return;
|
|
225
|
+
if (mat.map && mat.map.dispose)
|
|
226
|
+
mat.map.dispose();
|
|
227
|
+
if (mat.dispose)
|
|
228
|
+
mat.dispose();
|
|
229
|
+
});
|
|
230
|
+
if (mesh.geometry && mesh.geometry.dispose)
|
|
231
|
+
mesh.geometry.dispose();
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
console.warn('[CubeMapLoader] Disposal error:', e);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Show a loaded skybox as the scene's background. A previous skybox mesh or cube-texture background is
|
|
239
|
+
* disposed (a plain-texture background is not). Takes what `loadCubeMap` resolved to (or any texture);
|
|
240
|
+
* anything else, `null` included, clears it.
|
|
241
|
+
*
|
|
242
|
+
*/
|
|
243
|
+
export function setBackground(scene, resource) {
|
|
244
|
+
const sky = scene;
|
|
245
|
+
// Read as whatever it may be: a skybox wrapper, a texture, or anything else (which clears).
|
|
246
|
+
const res = resource;
|
|
247
|
+
// Clean up previous skybox mesh
|
|
248
|
+
if (sky.__EW_SKYBOX_MESH && sky.__EW_SKYBOX_MESH.parent === scene) {
|
|
249
|
+
const oldMesh = sky.__EW_SKYBOX_MESH;
|
|
250
|
+
scene.remove(oldMesh);
|
|
251
|
+
disposeSkyboxMesh(oldMesh);
|
|
252
|
+
sky.__EW_SKYBOX_MESH = null;
|
|
253
|
+
}
|
|
254
|
+
// Dispose previous CubeTexture background
|
|
255
|
+
// A Color or a Texture; `dispose` is only called once it is a CubeTexture.
|
|
256
|
+
const background = scene.background;
|
|
257
|
+
if (background && background.isCubeTexture) {
|
|
258
|
+
try {
|
|
259
|
+
background.dispose();
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.warn('[CubeMapLoader] Background disposal error:', e);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Set new background
|
|
266
|
+
if (res?.__ewType === 'skybox-mesh') {
|
|
267
|
+
scene.background = null;
|
|
268
|
+
sky.__EW_SKYBOX_MESH = res.mesh;
|
|
269
|
+
scene.add(res.mesh);
|
|
270
|
+
}
|
|
271
|
+
else if (res?.isCubeTexture || res?.isTexture) {
|
|
272
|
+
scene.background = resource;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
scene.background = null;
|
|
276
|
+
}
|
|
277
|
+
// Dispatch update event for reactive systems
|
|
278
|
+
try {
|
|
279
|
+
window.dispatchEvent(new CustomEvent('ew:skycube-updated', {
|
|
280
|
+
detail: { type: res?.__ewType || 'texture' }
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
console.warn('[CubeMapLoader] Event dispatch error:', e);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export async function initSpaceScene(scene, { renderer } = {}) {
|
|
288
|
+
const faceKeys = buildSkycubefaceKeys('001');
|
|
289
|
+
const cube = await loadCubeMap({ faceKeys, preferKTX2: true, renderer });
|
|
290
|
+
setBackground(scene, cube);
|
|
291
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { type MaterialFlags } from '../utils/materials.js';
|
|
3
|
+
/**
|
|
4
|
+
* `renderer` is REQUIRED at runtime (throws without one).
|
|
5
|
+
* - `retries` (3): load attempts before giving up.
|
|
6
|
+
* - `signal`: rejects the load on abort.
|
|
7
|
+
* - `applyFlags`: material flags (`forceOpaque`, `wireframe`) applied to every mesh.
|
|
8
|
+
* - `withCredentials` (false).
|
|
9
|
+
* - `emissiveIntensity` (1): self-glow of textured materials (their map doubles as the emissive
|
|
10
|
+
* map). 1 keeps the old flat-lit look; with a directional star light pass ≈0.1–0.15 so the night
|
|
11
|
+
* side reads.
|
|
12
|
+
* - `renderer`: REQUIRED (3.0): the renderer this GLB's textures bind to. A page with several
|
|
13
|
+
* renderers has no single correct global, so the old `window.__EW_RENDERER` fallback is gone —
|
|
14
|
+
* thread the owning view's renderer.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadGLB(url: string, { retries, signal, onProgress, applyFlags, withCredentials, renderer, emissiveIntensity, }?: {
|
|
17
|
+
retries?: number | undefined;
|
|
18
|
+
signal?: AbortSignal | undefined;
|
|
19
|
+
onProgress?: ((event: ProgressEvent) => void) | undefined;
|
|
20
|
+
applyFlags?: MaterialFlags | undefined;
|
|
21
|
+
withCredentials?: boolean | undefined;
|
|
22
|
+
emissiveIntensity?: number | undefined;
|
|
23
|
+
renderer: THREE.WebGLRenderer;
|
|
24
|
+
}): Promise<THREE.Group | undefined>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
3
|
+
import { ensureStandardMaterial, applyFlagsToMaterial } from '../utils/materials.js';
|
|
4
|
+
import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
|
+
import { disposeObject3D } from '../utils/dispose.js';
|
|
6
|
+
/**
|
|
7
|
+
* `renderer` is REQUIRED at runtime (throws without one).
|
|
8
|
+
* - `retries` (3): load attempts before giving up.
|
|
9
|
+
* - `signal`: rejects the load on abort.
|
|
10
|
+
* - `applyFlags`: material flags (`forceOpaque`, `wireframe`) applied to every mesh.
|
|
11
|
+
* - `withCredentials` (false).
|
|
12
|
+
* - `emissiveIntensity` (1): self-glow of textured materials (their map doubles as the emissive
|
|
13
|
+
* map). 1 keeps the old flat-lit look; with a directional star light pass ≈0.1–0.15 so the night
|
|
14
|
+
* side reads.
|
|
15
|
+
* - `renderer`: REQUIRED (3.0): the renderer this GLB's textures bind to. A page with several
|
|
16
|
+
* renderers has no single correct global, so the old `window.__EW_RENDERER` fallback is gone —
|
|
17
|
+
* thread the owning view's renderer.
|
|
18
|
+
*/
|
|
19
|
+
export async function loadGLB(url, { retries = 3, signal, onProgress, applyFlags, withCredentials = false, renderer, emissiveIntensity = 1.0, } = {}) {
|
|
20
|
+
if (!renderer)
|
|
21
|
+
throw new Error('[glb] loadGLB requires the owning renderer (KTX2 textures bind per WebGL context).');
|
|
22
|
+
let attempt = 0;
|
|
23
|
+
while (attempt < retries) {
|
|
24
|
+
// An abort during the pause between attempts must stop here: a listener added to an
|
|
25
|
+
// already-aborted signal never fires, so the next attempt would load and resolve anyway.
|
|
26
|
+
if (signal?.aborted)
|
|
27
|
+
throw new Error('aborted');
|
|
28
|
+
try {
|
|
29
|
+
const loader = new GLTFLoader()
|
|
30
|
+
.setCrossOrigin(withCredentials ? 'use-credentials' : 'anonymous')
|
|
31
|
+
.setWithCredentials(Boolean(withCredentials))
|
|
32
|
+
.setKTX2Loader(getKTX2LoaderForRenderer(renderer));
|
|
33
|
+
const root = await new Promise((resolve, reject) => {
|
|
34
|
+
const onAbort = () => reject(new Error('aborted'));
|
|
35
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
36
|
+
loader.load(url, (gltf) => {
|
|
37
|
+
signal?.removeEventListener('abort', onAbort);
|
|
38
|
+
// Aborted while loading: the promise has already rejected and nobody will own this
|
|
39
|
+
// scene, so free its geometry, materials and textures now instead of leaking them.
|
|
40
|
+
if (signal?.aborted) {
|
|
41
|
+
disposeObject3D(gltf.scene);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
resolve(gltf.scene);
|
|
45
|
+
}, onProgress, (err) => { signal?.removeEventListener('abort', onAbort); reject(err); });
|
|
46
|
+
});
|
|
47
|
+
// Post-process materials
|
|
48
|
+
root.traverse((object) => {
|
|
49
|
+
// The meshes a GLB loads carry one material each, and `ensureStandardMaterial` just made it a
|
|
50
|
+
// MeshStandardMaterial, which is what the lines below write to.
|
|
51
|
+
const child = object;
|
|
52
|
+
if (child.isMesh && child.material) {
|
|
53
|
+
ensureStandardMaterial(child);
|
|
54
|
+
if (applyFlags)
|
|
55
|
+
applyFlagsToMaterial(child.material, applyFlags);
|
|
56
|
+
// Force texture updates for KTX2
|
|
57
|
+
if (child.material.map) {
|
|
58
|
+
child.material.map.anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
|
|
59
|
+
child.material.map.needsUpdate = true;
|
|
60
|
+
// Ensure proper color space for compressed textures
|
|
61
|
+
child.material.map.colorSpace = THREE.SRGBColorSpace;
|
|
62
|
+
// Ensure material color doesn't block the texture
|
|
63
|
+
child.material.color.setHex(0xffffff);
|
|
64
|
+
// Add strong emissive to ensure visibility
|
|
65
|
+
child.material.emissive.setHex(0x888888);
|
|
66
|
+
child.material.emissiveMap = child.material.map;
|
|
67
|
+
child.material.emissiveIntensity = Number.isFinite(emissiveIntensity) ? Math.max(0, emissiveIntensity) : 1.0;
|
|
68
|
+
}
|
|
69
|
+
child.material.needsUpdate = true;
|
|
70
|
+
child.castShadow = false;
|
|
71
|
+
child.receiveShadow = false;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
root.position.set(0, 0, 0);
|
|
75
|
+
return root;
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
console.warn(`GLB load attempt ${attempt + 1} failed:`, e.message);
|
|
79
|
+
if (signal?.aborted || attempt >= retries - 1)
|
|
80
|
+
throw e;
|
|
81
|
+
attempt++;
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 500)); // Brief delay between retries
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
|
|
2
|
+
export { loadGLB } from './glbLoader.js';
|
|
3
|
+
export { loadTexture } from './bodyTextureLoader.js';
|
|
4
|
+
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
|
+
export type { SkyboxResource } from './cubeMapLoader.js';
|
|
@@ -3,5 +3,3 @@ export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys
|
|
|
3
3
|
export { loadGLB } from './glbLoader.js';
|
|
4
4
|
export { loadTexture } from './bodyTextureLoader.js';
|
|
5
5
|
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
|
6
|
-
|
|
7
|
-
/** @typedef {import('./cubeMapLoader.js').SkyboxResource} SkyboxResource */
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
1
3
|
/**
|
|
2
4
|
* The KTX2Loader bound to `renderer` — REQUIRED (3.0). The old `window.__EW_RENDERER` fallback let
|
|
3
5
|
* call sites compile while silently binding the transcoder to whichever renderer a page published
|
|
4
6
|
* last; on any multi-view page that meant untextured worlds. Every caller now threads its own
|
|
5
7
|
* renderer, so a missing one is a caller bug and fails loudly here rather than as a dark planet.
|
|
6
|
-
* @param {import('three').WebGLRenderer} renderer
|
|
7
|
-
* @returns {KTX2Loader}
|
|
8
8
|
*/
|
|
9
|
-
export function getKTX2LoaderForRenderer(renderer:
|
|
9
|
+
export declare function getKTX2LoaderForRenderer(renderer: THREE.WebGLRenderer): KTX2Loader;
|
|
10
10
|
/**
|
|
11
11
|
* Dispose and forget the KTX2Loader for a renderer — call on that renderer's / scene's teardown so the
|
|
12
12
|
* transcoder workers are terminated (avoids leaking worker threads + GPU memory across view churn).
|
|
13
|
-
* @param {import('three').WebGLRenderer} renderer
|
|
14
13
|
*/
|
|
15
|
-
export function disposeKTX2LoaderForRenderer(renderer:
|
|
16
|
-
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
14
|
+
export declare function disposeKTX2LoaderForRenderer(renderer: THREE.WebGLRenderer): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
2
|
+
/**
|
|
3
|
+
* One KTX2Loader PER RENDERER.
|
|
4
|
+
*
|
|
5
|
+
* `KTX2Loader.detectSupport(renderer)` binds the loader's Basis transcoder worker pool to ONE WebGL
|
|
6
|
+
* context. A single shared loader therefore only decodes textures for the FIRST renderer it saw —
|
|
7
|
+
* every other view's planets/skyboxes silently fail and render untextured (dark). That breaks any
|
|
8
|
+
* multi-view page: N orbiters in one realm, or N Entangled World planets shown together.
|
|
9
|
+
*
|
|
10
|
+
* Keying the loader by renderer gives each view its own correctly-bound loader. Single-renderer apps
|
|
11
|
+
* (a single orbiter, today's Entangled World) get exactly ONE entry, so behaviour is byte-identical.
|
|
12
|
+
* (Was: a single `window.__EW_KTX2_LOADER` global shared by bodyTexture/glb/cubeMap loaders.)
|
|
13
|
+
*/
|
|
14
|
+
const loadersByRenderer = new WeakMap();
|
|
15
|
+
function makeLoader(renderer) {
|
|
16
|
+
return new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The KTX2Loader bound to `renderer` — REQUIRED (3.0). The old `window.__EW_RENDERER` fallback let
|
|
20
|
+
* call sites compile while silently binding the transcoder to whichever renderer a page published
|
|
21
|
+
* last; on any multi-view page that meant untextured worlds. Every caller now threads its own
|
|
22
|
+
* renderer, so a missing one is a caller bug and fails loudly here rather than as a dark planet.
|
|
23
|
+
*/
|
|
24
|
+
export function getKTX2LoaderForRenderer(renderer) {
|
|
25
|
+
if (!renderer) {
|
|
26
|
+
throw new Error('[ktx2] getKTX2LoaderForRenderer requires the owning renderer — thread it from the view that renders this texture.');
|
|
27
|
+
}
|
|
28
|
+
let loader = loadersByRenderer.get(renderer);
|
|
29
|
+
if (!loader) {
|
|
30
|
+
loader = makeLoader(renderer);
|
|
31
|
+
loadersByRenderer.set(renderer, loader);
|
|
32
|
+
}
|
|
33
|
+
return loader;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dispose and forget the KTX2Loader for a renderer — call on that renderer's / scene's teardown so the
|
|
37
|
+
* transcoder workers are terminated (avoids leaking worker threads + GPU memory across view churn).
|
|
38
|
+
*/
|
|
39
|
+
export function disposeKTX2LoaderForRenderer(renderer) {
|
|
40
|
+
const loader = renderer && loadersByRenderer.get(renderer);
|
|
41
|
+
if (loader) {
|
|
42
|
+
try {
|
|
43
|
+
loader.dispose?.();
|
|
44
|
+
}
|
|
45
|
+
catch { /* already gone */ }
|
|
46
|
+
loadersByRenderer.delete(renderer);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { MoonsManager } from './moonsManager.js';
|
|
2
|
+
export type { MoonAmountOptions } from './moonsManager.js';
|
|
3
|
+
export { generateMoons, DEFAULT_MOON_TEX_KTX2, DEFAULT_MOON_TEX_PNG, MOON_BASE_RADIUS, MOON_SCALE_MIN, MOON_SCALE_SPAN, MOON_MAX_RADIUS, MOON_ORBIT_SCATTER, MOON_HEIGHT_SCATTER, } from './moonsGeneration.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Moons system exports
|
|
2
|
+
export { MoonsManager } from './moonsManager.js';
|
|
3
|
+
export { generateMoons, DEFAULT_MOON_TEX_KTX2, DEFAULT_MOON_TEX_PNG, MOON_BASE_RADIUS, MOON_SCALE_MIN, MOON_SCALE_SPAN, MOON_MAX_RADIUS, MOON_ORBIT_SCATTER, MOON_HEIGHT_SCATTER, } from './moonsGeneration.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
export declare const DEFAULT_MOON_TEX_KTX2: string;
|
|
3
|
+
export declare const DEFAULT_MOON_TEX_PNG: string;
|
|
4
|
+
/** Radius of the shared moon sphere before per-moon scale. */
|
|
5
|
+
export declare const MOON_BASE_RADIUS = 0.1;
|
|
6
|
+
/** Per-moon scale range: MIN + random * SPAN. */
|
|
7
|
+
export declare const MOON_SCALE_MIN = 0.07;
|
|
8
|
+
export declare const MOON_SCALE_SPAN = 0.15;
|
|
9
|
+
/** Largest moon radius in scene units (base radius × largest scale). */
|
|
10
|
+
export declare const MOON_MAX_RADIUS: number;
|
|
11
|
+
/** Each moon's orbit radius varies by ± half of this around the requested orbit. */
|
|
12
|
+
export declare const MOON_ORBIT_SCATTER = 0.22;
|
|
13
|
+
/** Each moon sits up to ± half of this above or below the orbit plane. */
|
|
14
|
+
export declare const MOON_HEIGHT_SCATTER = 0.12;
|
|
15
|
+
/**
|
|
16
|
+
* - `count` (0): 0 or less resolves to null. `orbitRadius` 1.5.
|
|
17
|
+
* - `textureFallback`: used instead of `texturePNG` when set.
|
|
18
|
+
* - `seed`: deterministic positions when set.
|
|
19
|
+
* - `textureKey`: replaces `textureKTX2` when set (the fallback stays `textureFallback` or
|
|
20
|
+
* `texturePNG`).
|
|
21
|
+
* - `emissiveLift`: night-side self-glow 0..1; omitted = `emissiveIntensity`.
|
|
22
|
+
* - `renderer`: binds the KTX2 moon texture.
|
|
23
|
+
* - Defaults: `moonColor` '#ffffff', `brightness` 1.15, `emissiveColor` 0x333333,
|
|
24
|
+
* `emissiveIntensity` 0.18, `roughness` 0.85, `metalness` 0, `debug` true, `smooth` true,
|
|
25
|
+
* `geometryWidthSegments` 16, `geometryHeightSegments` 12, `useBumpFromColor` true, `bumpScale`
|
|
26
|
+
* 0.15, `debugShowUvGrid` false, `debugShowReferenceMoon` false, `referenceMoonScale` 0.25,
|
|
27
|
+
* `useEmissiveMapFromTexture` true.
|
|
28
|
+
*/
|
|
29
|
+
export declare function generateMoons({ count, orbitRadius, textureKTX2, texturePNG, textureFallback, useTexture, seed, // if provided, ensures consistent moon positions
|
|
30
|
+
textureKey, // if provided, overrides textureKTX2/texturePNG
|
|
31
|
+
moonColor, // NEW: moon color
|
|
32
|
+
brightness, emissiveColor, emissiveIntensity, emissiveLift, roughness, metalness, debug, smooth, // set false to keep flatShading
|
|
33
|
+
geometryWidthSegments, // Reduced from 24 for better performance
|
|
34
|
+
geometryHeightSegments, // Reduced from 24 for better performance
|
|
35
|
+
useBumpFromColor, bumpScale, debugShowUvGrid, debugShowReferenceMoon, // changed from true to prevent extra moon
|
|
36
|
+
referenceMoonScale, useEmissiveMapFromTexture, // reuse color texture as emissiveMap to lift dark sides
|
|
37
|
+
renderer, }?: {
|
|
38
|
+
count?: number | undefined;
|
|
39
|
+
orbitRadius?: number | undefined;
|
|
40
|
+
textureKTX2?: string | undefined;
|
|
41
|
+
texturePNG?: string | undefined;
|
|
42
|
+
textureFallback?: string | null | undefined;
|
|
43
|
+
useTexture?: boolean | undefined;
|
|
44
|
+
seed?: string | number | null | undefined;
|
|
45
|
+
textureKey?: string | null | undefined;
|
|
46
|
+
moonColor?: string | undefined;
|
|
47
|
+
brightness?: number | undefined;
|
|
48
|
+
emissiveColor?: number | undefined;
|
|
49
|
+
emissiveIntensity?: number | undefined;
|
|
50
|
+
emissiveLift?: number | undefined;
|
|
51
|
+
roughness?: number | undefined;
|
|
52
|
+
metalness?: number | undefined;
|
|
53
|
+
debug?: boolean | undefined;
|
|
54
|
+
smooth?: boolean | undefined;
|
|
55
|
+
geometryWidthSegments?: number | undefined;
|
|
56
|
+
geometryHeightSegments?: number | undefined;
|
|
57
|
+
useBumpFromColor?: boolean | undefined;
|
|
58
|
+
bumpScale?: number | undefined;
|
|
59
|
+
debugShowUvGrid?: boolean | undefined;
|
|
60
|
+
debugShowReferenceMoon?: boolean | undefined;
|
|
61
|
+
referenceMoonScale?: number | undefined;
|
|
62
|
+
useEmissiveMapFromTexture?: boolean | undefined;
|
|
63
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
64
|
+
}): Promise<THREE.Group<THREE.Object3DEventMap> | null>;
|