entangled-worlds-orbiters-shared 1.5.1 → 2.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.
- package/package.json +1 -1
- package/src/core/lights.js +28 -24
- package/src/loaders/cubeMapLoader.js +10 -6
- package/src/loaders/glbLoader.js +8 -2
- package/src/loaders/index.js +2 -1
- package/src/loaders/ktx2.js +7 -4
- package/src/moons/moonsGeneration.js +7 -12
- package/src/moons/moonsManager.js +20 -1
- package/src/utils/dispose.js +3 -0
- package/src/world/worldGLB.js +2 -2
package/package.json
CHANGED
package/src/core/lights.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
|
|
3
|
-
// `hemiRef`/`ambientRef` track the MOST-RECENTLY ensured scene's lights, only so the legacy
|
|
4
|
-
// `updateDefaultLights()` (which takes no scene) keeps targeting the active scene. They are NOT a
|
|
5
|
-
// "lights already created" cache — see ensureDefaultLights.
|
|
6
|
-
let hemiRef = null;
|
|
7
|
-
let ambientRef = null;
|
|
8
|
-
|
|
9
3
|
/**
|
|
10
4
|
* Ensure a scene has the default hemisphere + ambient lights. Idempotent PER SCENE.
|
|
11
5
|
*
|
|
@@ -29,29 +23,39 @@ export function ensureDefaultLights(scene) {
|
|
|
29
23
|
ambient.name = 'ew_AmbientLight';
|
|
30
24
|
scene.add(ambient);
|
|
31
25
|
}
|
|
32
|
-
// updateDefaultLights() (no scene arg) targets the most-recently ensured scene's lights.
|
|
33
|
-
hemiRef = hemi;
|
|
34
|
-
ambientRef = ambient;
|
|
35
26
|
return { hemi, ambient };
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Apply a lighting patch to ONE scene's default lights.
|
|
31
|
+
*
|
|
32
|
+
* The scene is required. This used to address module-global light refs pointing at whichever scene
|
|
33
|
+
* was ensured last, so on a page rendering several scenes every lighting update landed on one of
|
|
34
|
+
* them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
|
|
35
|
+
*
|
|
36
|
+
* @param {import('three').Scene} scene
|
|
37
|
+
*/
|
|
38
|
+
export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection } = {}) {
|
|
39
|
+
if (!scene?.isObject3D) {
|
|
40
|
+
console.error('[lights] updateDefaultLights(scene, patch) requires the scene to update; ignoring patch.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { hemi, ambient } = ensureDefaultLights(scene);
|
|
44
|
+
|
|
45
|
+
if (typeof ambientIntensity === 'number') ambient.intensity = Math.max(0, ambientIntensity);
|
|
46
|
+
if (typeof diffuseIntensity === 'number') hemi.intensity = Math.max(0, diffuseIntensity);
|
|
47
|
+
if (lightColor != null) {
|
|
48
|
+
if (lightColor instanceof THREE.Color) {
|
|
49
|
+
hemi.color.copy(lightColor);
|
|
50
|
+
} else if (typeof lightColor === 'number') {
|
|
51
|
+
hemi.color.setHex(lightColor);
|
|
52
|
+
} else if (typeof lightColor === 'string') {
|
|
53
|
+
hemi.color.set(lightColor);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
if (lightDirection && typeof lightDirection.x === 'number') {
|
|
57
|
+
hemi.position.set(lightDirection.x, lightDirection.y ?? 0, lightDirection.z ?? 0).normalize();
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
// Back-compat helper
|
|
@@ -140,10 +140,14 @@ function resolveUrls(faceKeys) {
|
|
|
140
140
|
return resolved;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Main loader function - always uses explicit face URLs.
|
|
145
|
+
* `renderer` binds the KTX2 transcoder to the right WebGL context; it falls back to the standalone
|
|
146
|
+
* app's `window.__EW_RENDERER` only transitionally. Pass it explicitly.
|
|
147
|
+
*/
|
|
148
|
+
export async function loadCubeMap({ faceKeys, skycubeName, preferKTX2 = true, forceLow, renderer: rendererOpt } = {}) {
|
|
149
|
+
const renderer = rendererOpt ?? (typeof window !== 'undefined' ? window.__EW_RENDERER : null);
|
|
150
|
+
|
|
147
151
|
// If no face URLs provided, build them from skycube name
|
|
148
152
|
if (!faceKeys && skycubeName) {
|
|
149
153
|
const low = typeof forceLow === 'boolean' ? forceLow : isLowPerformanceDevice();
|
|
@@ -248,8 +252,8 @@ export function setBackground(scene, resource) {
|
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
|
-
export async function initSpaceScene(scene) {
|
|
255
|
+
export async function initSpaceScene(scene, { renderer } = {}) {
|
|
252
256
|
const faceKeys = buildSkycubefaceKeys('001');
|
|
253
|
-
const cube = await loadCubeMap({ faceKeys, preferKTX2: true });
|
|
257
|
+
const cube = await loadCubeMap({ faceKeys, preferKTX2: true, renderer });
|
|
254
258
|
setBackground(scene, cube);
|
|
255
259
|
}
|
package/src/loaders/glbLoader.js
CHANGED
|
@@ -3,8 +3,14 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
|
3
3
|
import { ensureStandardMaterial, applyFlagsToMaterial } from '../utils/materials.js';
|
|
4
4
|
import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} [opts]
|
|
8
|
+
* @param {import('three').WebGLRenderer} [opts.renderer] the renderer this GLB's textures bind to.
|
|
9
|
+
* Transitional default: `window.__EW_RENDERER` (the standalone app's publisher). Pass it explicitly
|
|
10
|
+
* — a page with several renderers has no single correct global, and the default goes away with it.
|
|
11
|
+
*/
|
|
12
|
+
export async function loadGLB(url, { retries = 3, signal, onProgress, applyFlags, withCredentials = false, renderer: rendererOpt } = {}) {
|
|
13
|
+
const renderer = rendererOpt ?? (typeof window !== 'undefined' ? window.__EW_RENDERER : null);
|
|
8
14
|
if (!renderer) throw new Error('Renderer not ready for KTX2');
|
|
9
15
|
|
|
10
16
|
let attempt = 0;
|
package/src/loaders/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Loaders exports
|
|
2
2
|
export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
|
|
3
3
|
export { loadGLB } from './glbLoader.js';
|
|
4
|
-
export { loadTexture } from './bodyTextureLoader.js';
|
|
4
|
+
export { loadTexture } from './bodyTextureLoader.js';
|
|
5
|
+
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
package/src/loaders/ktx2.js
CHANGED
|
@@ -25,11 +25,14 @@ function makeLoader(renderer) {
|
|
|
25
25
|
* @param {import('three').WebGLRenderer} [renderer]
|
|
26
26
|
* @returns {KTX2Loader}
|
|
27
27
|
*/
|
|
28
|
-
export function getKTX2LoaderForRenderer(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export function getKTX2LoaderForRenderer(rendererArg) {
|
|
29
|
+
// `??` not a default param: callers thread an OPTIONAL renderer down several layers, so `null`
|
|
30
|
+
// arrives as often as `undefined` and must fall back the same way.
|
|
31
|
+
const renderer = rendererArg ?? (typeof window !== 'undefined' ? window.__EW_RENDERER : null);
|
|
31
32
|
if (!renderer) {
|
|
32
|
-
|
|
33
|
+
// No renderer anywhere: an unbound loader can't transcode (detectSupport needs a context), but
|
|
34
|
+
// returning one keeps the caller's error local to the texture instead of throwing mid-scene.
|
|
35
|
+
if (!fallbackLoader) fallbackLoader = new KTX2Loader().setTranscoderPath('/basis/');
|
|
33
36
|
return fallbackLoader;
|
|
34
37
|
}
|
|
35
38
|
let loader = loadersByRenderer.get(renderer);
|
|
@@ -40,6 +40,9 @@ export async function generateMoons({
|
|
|
40
40
|
// behavior flags
|
|
41
41
|
preserveInstanceTint = false, // keep per-instance color even when textured
|
|
42
42
|
useEmissiveMapFromTexture = true, // reuse color texture as emissiveMap to lift dark sides
|
|
43
|
+
// The renderer whose KTX2 transcoder decodes the moon texture. Required on any page with more than
|
|
44
|
+
// one renderer; falls back to the standalone app's global inside the loader.
|
|
45
|
+
renderer,
|
|
43
46
|
} = {}) {
|
|
44
47
|
if (count <= 0) return null;
|
|
45
48
|
|
|
@@ -95,11 +98,11 @@ export async function generateMoons({
|
|
|
95
98
|
const fallbackTexture = textureFallback || texturePNG;
|
|
96
99
|
|
|
97
100
|
try {
|
|
98
|
-
sharedTexture = await loadTexture(primaryTexture);
|
|
101
|
+
sharedTexture = await loadTexture(primaryTexture, { renderer });
|
|
99
102
|
} catch (eKTX) {
|
|
100
103
|
if (debug) console.warn('[Moons] Primary texture failed, trying fallback:', eKTX);
|
|
101
104
|
try {
|
|
102
|
-
sharedTexture = await loadTexture(fallbackTexture);
|
|
105
|
+
sharedTexture = await loadTexture(fallbackTexture, { renderer });
|
|
103
106
|
} catch (ePNG) {
|
|
104
107
|
if (debug) console.warn('[Moons] Both primary and fallback texture loading failed');
|
|
105
108
|
}
|
|
@@ -195,15 +198,7 @@ export async function generateMoons({
|
|
|
195
198
|
group.add(instanced);
|
|
196
199
|
group.name = 'moonsGroup';
|
|
197
200
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
if (moonGeometry) moonGeometry.dispose();
|
|
201
|
-
if (moonMaterial.map && moonMaterial.map !== sharedTexture) moonMaterial.map.dispose();
|
|
202
|
-
if (moonMaterial.emissiveMap && moonMaterial.emissiveMap !== sharedTexture) moonMaterial.emissiveMap.dispose();
|
|
203
|
-
if (moonMaterial.bumpMap && moonMaterial.bumpMap !== sharedTexture) moonMaterial.bumpMap.dispose();
|
|
204
|
-
moonMaterial.dispose();
|
|
205
|
-
instanced.dispose();
|
|
206
|
-
};
|
|
207
|
-
|
|
201
|
+
// Teardown goes through `disposeObject3D` (MoonsManager.dispose / setAmount), which frees the
|
|
202
|
+
// geometry, the material's textures and the instance buffers.
|
|
208
203
|
return group;
|
|
209
204
|
}
|
|
@@ -3,8 +3,14 @@ import * as THREE from 'three';
|
|
|
3
3
|
import { generateMoons } from './moonsGeneration.js';
|
|
4
4
|
|
|
5
5
|
export class MoonsManager {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @param {import('three').Scene} scene
|
|
8
|
+
* @param {object} [opts]
|
|
9
|
+
* @param {import('three').WebGLRenderer} [opts.renderer] renderer the moon texture binds to (KTX2).
|
|
10
|
+
*/
|
|
11
|
+
constructor(scene, { renderer } = {}) {
|
|
7
12
|
this.scene = scene;
|
|
13
|
+
this.renderer = renderer;
|
|
8
14
|
this.group = null;
|
|
9
15
|
this._opId = 0; // concurrency guard
|
|
10
16
|
}
|
|
@@ -44,6 +50,7 @@ export class MoonsManager {
|
|
|
44
50
|
textureKey, // Pass texture key for custom moon textures
|
|
45
51
|
textureFallback,
|
|
46
52
|
moonColor, // Pass moon color to generator
|
|
53
|
+
renderer: this.renderer,
|
|
47
54
|
});
|
|
48
55
|
} catch (e) {
|
|
49
56
|
console.warn('[MoonsManager] Failed to generate moons:', e);
|
|
@@ -63,4 +70,16 @@ export class MoonsManager {
|
|
|
63
70
|
console.warn('[MoonsManager] generateMoons did not return an Object3D');
|
|
64
71
|
}
|
|
65
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove the moons from the scene and free their GPU resources. `scene.remove(group)` alone leaks
|
|
76
|
+
* the instanced geometry, materials and the shared moon texture on every view teardown.
|
|
77
|
+
*/
|
|
78
|
+
dispose() {
|
|
79
|
+
this._opId++; // abandon any in-flight setAmount
|
|
80
|
+
if (this.group) {
|
|
81
|
+
disposeObject3D(this.group);
|
|
82
|
+
this.group = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
66
85
|
}
|
package/src/utils/dispose.js
CHANGED
|
@@ -30,6 +30,9 @@ export function disposeObject3D(obj) {
|
|
|
30
30
|
if (child.isMesh) {
|
|
31
31
|
if (child.geometry) child.geometry.dispose();
|
|
32
32
|
disposeMaterial(child.material);
|
|
33
|
+
// An InstancedMesh also owns `instanceMatrix` / `instanceColor` GPU buffers, which hang off the
|
|
34
|
+
// mesh rather than the (shared) geometry — only its own dispose() frees them.
|
|
35
|
+
if (child.isInstancedMesh) child.dispose();
|
|
33
36
|
}
|
|
34
37
|
});
|
|
35
38
|
if (obj.parent) obj.parent.remove(obj);
|
package/src/world/worldGLB.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadGLB } from '../loaders/glbLoader.js';
|
|
2
2
|
|
|
3
|
-
export async function makeGLB({ modelUrl, flags, retries = 3, withCredentials = false } = {}) {
|
|
3
|
+
export async function makeGLB({ modelUrl, flags, retries = 3, withCredentials = false, renderer } = {}) {
|
|
4
4
|
if (!modelUrl) throw new Error('modelUrl is required');
|
|
5
|
-
const obj = await loadGLB(modelUrl, { retries, applyFlags: flags, withCredentials });
|
|
5
|
+
const obj = await loadGLB(modelUrl, { retries, applyFlags: flags, withCredentials, renderer });
|
|
6
6
|
return obj;
|
|
7
7
|
}
|