entangled-worlds-orbiters-shared 3.3.0 → 4.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 (118) hide show
  1. package/README.md +3 -2
  2. package/dist/config/env.d.ts +8 -0
  3. package/dist/config/env.js +36 -0
  4. package/dist/config/index.d.ts +3 -0
  5. package/dist/config/index.js +4 -0
  6. package/dist/config/textureConfig.d.ts +140 -0
  7. package/dist/config/textureConfig.js +318 -0
  8. package/dist/core/index.d.ts +2 -0
  9. package/dist/core/index.js +3 -0
  10. package/dist/core/lightFresnel.d.ts +6 -0
  11. package/dist/core/lightFresnel.js +63 -0
  12. package/dist/core/lights.d.ts +48 -0
  13. package/dist/core/lights.js +79 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +7 -0
  16. package/dist/loaders/bodyTextureLoader.d.ts +10 -0
  17. package/dist/loaders/bodyTextureLoader.js +28 -0
  18. package/dist/loaders/cubeMapLoader.d.ts +58 -0
  19. package/dist/loaders/cubeMapLoader.js +291 -0
  20. package/dist/loaders/glbLoader.d.ts +24 -0
  21. package/dist/loaders/glbLoader.js +72 -0
  22. package/dist/loaders/index.d.ts +5 -0
  23. package/{src → dist}/loaders/index.js +0 -2
  24. package/dist/{types/loaders → loaders}/ktx2.d.ts +4 -6
  25. package/dist/loaders/ktx2.js +48 -0
  26. package/dist/moons/index.d.ts +3 -0
  27. package/dist/moons/index.js +3 -0
  28. package/dist/moons/moonsGeneration.d.ts +64 -0
  29. package/dist/moons/moonsGeneration.js +238 -0
  30. package/dist/moons/moonsManager.d.ts +43 -0
  31. package/dist/moons/moonsManager.js +93 -0
  32. package/dist/{types/render → render}/frameBudget.d.ts +18 -17
  33. package/dist/render/frameBudget.js +42 -0
  34. package/dist/render/index.d.ts +4 -0
  35. package/dist/render/viewportCompositor.d.ts +124 -0
  36. package/dist/render/viewportCompositor.js +643 -0
  37. package/dist/studio/headerBroadcast.d.ts +10 -1
  38. package/dist/studio/hostTransport.d.ts +2 -2
  39. package/dist/studio/index.d.ts +1 -1
  40. package/dist/utils/dispose.d.ts +5 -0
  41. package/dist/utils/dispose.js +50 -0
  42. package/dist/utils/index.d.ts +3 -0
  43. package/{src → dist}/utils/index.js +1 -1
  44. package/dist/utils/materials.d.ts +10 -0
  45. package/dist/utils/materials.js +36 -0
  46. package/dist/world/cloudShell.d.ts +16 -0
  47. package/dist/world/cloudShell.js +73 -0
  48. package/dist/world/index.d.ts +6 -0
  49. package/{src → dist}/world/index.js +0 -2
  50. package/dist/world/worldGLB.d.ts +17 -0
  51. package/dist/world/worldGLB.js +22 -0
  52. package/dist/world/worldManager.d.ts +57 -0
  53. package/dist/world/worldManager.js +125 -0
  54. package/dist/world/worldNormal.d.ts +30 -0
  55. package/dist/world/worldNormal.js +58 -0
  56. package/dist/world/worldTexture.d.ts +56 -0
  57. package/dist/world/worldTexture.js +299 -0
  58. package/package.json +29 -39
  59. package/src/config/env.ts +48 -0
  60. package/src/config/{index.js → index.ts} +7 -0
  61. package/src/config/{textureConfig.js → textureConfig.ts} +126 -60
  62. package/src/core/{lightFresnel.js → lightFresnel.ts} +1 -1
  63. package/src/core/{lights.js → lights.ts} +21 -21
  64. package/src/loaders/{bodyTextureLoader.js → bodyTextureLoader.ts} +10 -7
  65. package/src/loaders/{cubeMapLoader.js → cubeMapLoader.ts} +83 -49
  66. package/src/loaders/{glbLoader.js → glbLoader.ts} +41 -18
  67. package/src/loaders/index.ts +6 -0
  68. package/src/loaders/{ktx2.js → ktx2.ts} +5 -7
  69. package/src/moons/{index.js → index.ts} +1 -0
  70. package/src/moons/{moonsGeneration.js → moonsGeneration.ts} +45 -32
  71. package/src/moons/{moonsManager.js → moonsManager.ts} +37 -11
  72. package/src/render/{frameBudget.js → frameBudget.ts} +15 -8
  73. package/src/render/index.ts +14 -0
  74. package/src/render/{viewportCompositor.js → viewportCompositor.ts} +121 -59
  75. package/src/studio/headerBroadcast.ts +14 -1
  76. package/src/studio/hostTransport.ts +2 -1
  77. package/src/studio/index.ts +1 -1
  78. package/src/utils/dispose.ts +47 -0
  79. package/src/utils/index.ts +4 -0
  80. package/src/utils/materials.ts +44 -0
  81. package/src/world/{cloudShell.js → cloudShell.ts} +19 -12
  82. package/src/world/index.ts +7 -0
  83. package/src/world/worldGLB.ts +41 -0
  84. package/src/world/{worldManager.js → worldManager.ts} +67 -32
  85. package/src/world/{worldNormal.js → worldNormal.ts} +25 -20
  86. package/src/world/{worldTexture.js → worldTexture.ts} +70 -51
  87. package/dist/types/config/env.d.ts +0 -12
  88. package/dist/types/config/index.d.ts +0 -2
  89. package/dist/types/config/textureConfig.d.ts +0 -121
  90. package/dist/types/core/index.d.ts +0 -2
  91. package/dist/types/core/lightFresnel.d.ts +0 -5
  92. package/dist/types/core/lights.d.ts +0 -58
  93. package/dist/types/index.d.ts +0 -6
  94. package/dist/types/loaders/bodyTextureLoader.d.ts +0 -12
  95. package/dist/types/loaders/cubeMapLoader.d.ts +0 -70
  96. package/dist/types/loaders/glbLoader.d.ts +0 -24
  97. package/dist/types/loaders/index.d.ts +0 -5
  98. package/dist/types/moons/index.d.ts +0 -2
  99. package/dist/types/moons/moonsGeneration.d.ts +0 -72
  100. package/dist/types/moons/moonsManager.d.ts +0 -27
  101. package/dist/types/render/index.d.ts +0 -2
  102. package/dist/types/render/viewportCompositor.d.ts +0 -58
  103. package/dist/types/utils/dispose.d.ts +0 -2
  104. package/dist/types/utils/index.d.ts +0 -2
  105. package/dist/types/utils/materials.d.ts +0 -2
  106. package/dist/types/world/cloudShell.d.ts +0 -25
  107. package/dist/types/world/index.d.ts +0 -6
  108. package/dist/types/world/worldGLB.d.ts +0 -20
  109. package/dist/types/world/worldManager.d.ts +0 -63
  110. package/dist/types/world/worldNormal.d.ts +0 -43
  111. package/dist/types/world/worldTexture.d.ts +0 -80
  112. package/src/config/env.js +0 -38
  113. package/src/utils/dispose.js +0 -39
  114. package/src/utils/materials.js +0 -31
  115. package/src/world/worldGLB.js +0 -19
  116. /package/{src → dist}/render/index.js +0 -0
  117. /package/src/core/{index.js → index.ts} +0 -0
  118. /package/src/{index.js → index.ts} +0 -0
@@ -1,4 +1,5 @@
1
- interface WindowLike {
1
+ /** The part of a window the host transport reads: its origin, its parent and `postMessage`. */
2
+ export interface WindowLike {
2
3
  location: {
3
4
  origin: string;
4
5
  };
@@ -17,4 +18,3 @@ export declare function isEmbedded(win?: WindowLike | null): boolean;
17
18
  * origin or no parent — never falls back to a '*' broadcast for payloads.
18
19
  */
19
20
  export declare function postToHost(message: unknown, targetOrigin: string | null | undefined, transfer?: Transferable[], win?: WindowLike | null): boolean;
20
- export {};
@@ -3,4 +3,4 @@ export { createAutosaveSession, type AutosaveSession, type AutosaveState, type A
3
3
  export { createPatchStore, type PatchStore } from './patchStore.js';
4
4
  export { makeHeaderBroadcast, type HeaderBroadcast, type HeaderSinkOf } from './headerBroadcast.js';
5
5
  export { createStatusChannel, type StatusChannel, type StatusSink, type StatusShowOptions, } from './statusChannel.js';
6
- export { isTrustedHostOrigin, isEmbedded, postToHost } from './hostTransport.js';
6
+ export { isTrustedHostOrigin, isEmbedded, postToHost, type WindowLike } from './hostTransport.js';
@@ -0,0 +1,5 @@
1
+ import type * as THREE from 'three';
2
+ /** Disposes one material or an array of them, and every disposable value hanging off each. */
3
+ export declare function disposeMaterial(material: THREE.Material | readonly (THREE.Material | null | undefined)[] | null | undefined): void;
4
+ /** Disposes the geometry and materials of every mesh under `obj`, then detaches it from its parent. */
5
+ export declare function disposeObject3D(obj: THREE.Object3D | null | undefined): void;
@@ -0,0 +1,50 @@
1
+ /** Disposes one material or an array of them, and every disposable value hanging off each. */
2
+ export function disposeMaterial(material) {
3
+ if (!material)
4
+ return;
5
+ // A mesh can carry a single material or an array (multi-material meshes, some GLB).
6
+ const materials = Array.isArray(material) ? material : [material];
7
+ for (const mat of materials) {
8
+ if (!mat)
9
+ continue;
10
+ // Free the textures (and any other disposable values) hanging off the material…
11
+ for (const key in mat) {
12
+ const val = mat[key];
13
+ if (val && typeof val.dispose === 'function') {
14
+ try {
15
+ val.dispose();
16
+ }
17
+ catch { }
18
+ }
19
+ }
20
+ // …then free the material's own GPU program. Without this the shader program
21
+ // leaks even though its textures were released. `dispose` is a prototype method,
22
+ // so the loop above never reaches it — no double-dispose.
23
+ if (typeof mat.dispose === 'function') {
24
+ try {
25
+ mat.dispose();
26
+ }
27
+ catch { }
28
+ }
29
+ }
30
+ }
31
+ /** Disposes the geometry and materials of every mesh under `obj`, then detaches it from its parent. */
32
+ export function disposeObject3D(obj) {
33
+ if (!obj)
34
+ return;
35
+ const root = obj;
36
+ root.traverse((child) => {
37
+ if (child.isMesh) {
38
+ const mesh = child;
39
+ if (mesh.geometry)
40
+ mesh.geometry.dispose();
41
+ disposeMaterial(mesh.material);
42
+ // An InstancedMesh also owns `instanceMatrix` / `instanceColor` GPU buffers, which hang off the
43
+ // mesh rather than the (shared) geometry — only its own dispose() frees them.
44
+ if (child.isInstancedMesh)
45
+ child.dispose();
46
+ }
47
+ });
48
+ if (root.parent)
49
+ root.parent.remove(root);
50
+ }
@@ -0,0 +1,3 @@
1
+ export { ensureStandardMaterial, applyFlagsToMaterial } from './materials.js';
2
+ export type { MaterialFlags } from './materials.js';
3
+ export { disposeObject3D } from './dispose.js';
@@ -1,3 +1,3 @@
1
1
  // Utilities exports
2
2
  export { ensureStandardMaterial, applyFlagsToMaterial } from './materials.js';
3
- export { disposeObject3D } from './dispose.js';
3
+ export { disposeObject3D } from './dispose.js';
@@ -0,0 +1,10 @@
1
+ import * as THREE from 'three';
2
+ /** The flags `applyFlagsToMaterial` reads; other fields are ignored. */
3
+ export interface MaterialFlags {
4
+ forceOpaque?: boolean;
5
+ wireframe?: boolean;
6
+ }
7
+ /** Gives a mesh a MeshStandardMaterial (keeping its map) unless it has one; anything that is not a mesh is left alone. */
8
+ export declare function ensureStandardMaterial(mesh: THREE.Object3D | null | undefined): void;
9
+ /** Applies `flags` to one material (not an array); nothing happens without a material. */
10
+ export declare function applyFlagsToMaterial(material: THREE.Material | null | undefined, flags?: MaterialFlags): void;
@@ -0,0 +1,36 @@
1
+ import * as THREE from 'three';
2
+ /** Gives a mesh a MeshStandardMaterial (keeping its map) unless it has one; anything that is not a mesh is left alone. */
3
+ export function ensureStandardMaterial(mesh) {
4
+ if (!mesh || !mesh.isMesh)
5
+ return;
6
+ const m = mesh;
7
+ const mat = m.material;
8
+ if (mat && !(mat instanceof THREE.MeshStandardMaterial)) {
9
+ m.material = new THREE.MeshStandardMaterial({
10
+ map: mat.map || null,
11
+ metalness: 0.3,
12
+ roughness: 0.7,
13
+ });
14
+ }
15
+ }
16
+ /** Applies `flags` to one material (not an array); nothing happens without a material. */
17
+ export function applyFlagsToMaterial(material, flags = {}) {
18
+ if (!material)
19
+ return;
20
+ const mat = material;
21
+ const { forceOpaque, wireframe } = flags;
22
+ if (forceOpaque) {
23
+ if (mat.transparent || (mat.transmission && mat.transmission > 0)) {
24
+ mat.transparent = false;
25
+ mat.opacity = 1;
26
+ if ('transmission' in mat)
27
+ mat.transmission = 0;
28
+ mat.alphaTest = 0;
29
+ mat.needsUpdate = true;
30
+ }
31
+ }
32
+ if (wireframe) {
33
+ mat.wireframe = true;
34
+ mat.needsUpdate = true;
35
+ }
36
+ }
@@ -0,0 +1,16 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * `baseGeometry` is cloned for the shell; when absent a sphere is built from `baseRadius` (0.5) and
4
+ * the segment counts (64 × 64). Defaults: `color` 0xffffff, `opacity` 0.6, `speed` 0.015.
5
+ */
6
+ export declare function createCloudShell({ baseGeometry, baseRadius, widthSegments, heightSegments, textureUrl, color, opacity, speed, renderer, }?: {
7
+ baseGeometry?: THREE.BufferGeometry | undefined;
8
+ baseRadius?: number | undefined;
9
+ widthSegments?: number | undefined;
10
+ heightSegments?: number | undefined;
11
+ textureUrl?: string | undefined;
12
+ color?: number | undefined;
13
+ opacity?: number | undefined;
14
+ speed?: number | undefined;
15
+ renderer?: THREE.WebGLRenderer | null | undefined;
16
+ }): Promise<THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes>, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;
@@ -0,0 +1,73 @@
1
+ import * as THREE from 'three';
2
+ import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
+ // Texture-only clouds material (alpha mask + tint), UV-scrolled
4
+ async function makeTextureMaterial({ color = 0xffffff, opacity = 0.6, textureUrl, speed = 0.015, renderer = null, } = {}) {
5
+ // Diffuse-only (Lambert): the star's key light costs no specular pass.
6
+ const mat = new THREE.MeshLambertMaterial({
7
+ color,
8
+ transparent: true,
9
+ opacity: 1.0, // drive alpha via alphaMap
10
+ depthTest: true,
11
+ depthWrite: false,
12
+ side: THREE.FrontSide,
13
+ toneMapped: true,
14
+ });
15
+ if (textureUrl) {
16
+ // Thread the owning view's renderer: KTX2 cloud textures must transcode against the caller's
17
+ // context, not the global fallback (which shared-renderer pages don't publish at all).
18
+ const tex = await loadTexture(textureUrl, { renderer });
19
+ tex.wrapS = THREE.RepeatWrapping;
20
+ tex.wrapT = THREE.RepeatWrapping;
21
+ // Use linear space for alpha masks if available
22
+ tex.colorSpace = THREE.LinearSRGBColorSpace || THREE.SRGBColorSpace;
23
+ tex.repeat.set(1, 1); // fixed tiling
24
+ tex.anisotropy = 2;
25
+ mat.alphaMap = tex;
26
+ mat.opacity = opacity; // material opacity multiplier
27
+ mat.needsUpdate = true;
28
+ }
29
+ else {
30
+ // No texture -> invisible shell
31
+ mat.opacity = 0.0;
32
+ }
33
+ // Randomize wind direction per instance (0..360)
34
+ const dirRad = (Math.random() * 360 * Math.PI) / 180;
35
+ const wind = new THREE.Vector2(Math.cos(dirRad), Math.sin(dirRad)).multiplyScalar(speed);
36
+ mat.userData.update = (timeSec) => {
37
+ const t = mat.alphaMap;
38
+ if (t) {
39
+ t.offset.x = wind.x * timeSec;
40
+ t.offset.y = wind.y * timeSec;
41
+ t.needsUpdate = true;
42
+ }
43
+ };
44
+ return mat;
45
+ }
46
+ /**
47
+ * `baseGeometry` is cloned for the shell; when absent a sphere is built from `baseRadius` (0.5) and
48
+ * the segment counts (64 × 64). Defaults: `color` 0xffffff, `opacity` 0.6, `speed` 0.015.
49
+ */
50
+ export async function createCloudShell({ baseGeometry, baseRadius = 0.5, widthSegments = 64, heightSegments = 64, textureUrl = '/assets/texture/2k_earth_clouds.jpg', color = 0xffffff, opacity = 0.6, speed = 0.015, renderer = null, } = {}) {
51
+ const geom = baseGeometry
52
+ ? baseGeometry.clone()
53
+ : new THREE.SphereGeometry(baseRadius, widthSegments, heightSegments);
54
+ const material = await makeTextureMaterial({
55
+ color,
56
+ opacity,
57
+ textureUrl,
58
+ speed,
59
+ renderer,
60
+ });
61
+ const mesh = new THREE.Mesh(geom, material);
62
+ const s = 1.001; // fixed scale
63
+ mesh.scale.set(s, s, s);
64
+ mesh.name = 'worldCloudShell';
65
+ mesh.renderOrder = 998; // after planet, before glow
66
+ mesh.onBeforeRender = () => {
67
+ const t = performance.now() * 0.001;
68
+ if (material.userData && typeof material.userData.update === 'function') {
69
+ material.userData.update(t);
70
+ }
71
+ };
72
+ return mesh;
73
+ }
@@ -0,0 +1,6 @@
1
+ export { WorldManager } from './worldManager.js';
2
+ export { makeSphere, EARTH_RADIUS_UNITS } from './worldTexture.js';
3
+ export { makeNormalSphere } from './worldNormal.js';
4
+ export { makeGLB } from './worldGLB.js';
5
+ export { createCloudShell } from './cloudShell.js';
6
+ export type { WorldCreator, WorldModeArgs } from './worldManager.js';
@@ -4,5 +4,3 @@ export { makeSphere, EARTH_RADIUS_UNITS } from './worldTexture.js';
4
4
  export { makeNormalSphere } from './worldNormal.js';
5
5
  export { makeGLB } from './worldGLB.js';
6
6
  export { createCloudShell } from './cloudShell.js';
7
-
8
- /** @typedef {import('./worldManager.js').WorldCreator} WorldCreator */
@@ -0,0 +1,17 @@
1
+ import type * as THREE from 'three';
2
+ /**
3
+ * `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light).
4
+ * - `modelUrl`: REQUIRED; throws without it.
5
+ * - `flags`: material flags (`forceOpaque`, `wireframe`).
6
+ * - `retries` (3), `withCredentials` (false).
7
+ * - `renderer`: REQUIRED at runtime (`loadGLB` throws without one); nullable here because
8
+ * `WorldManager` threads its own, which may be null.
9
+ */
10
+ export declare function makeGLB({ modelUrl, flags, retries, withCredentials, renderer, emissiveIntensity, }?: {
11
+ modelUrl: string;
12
+ flags?: object | undefined;
13
+ retries?: number | undefined;
14
+ withCredentials?: boolean | undefined;
15
+ renderer?: THREE.WebGLRenderer | null | undefined;
16
+ emissiveIntensity?: number | undefined;
17
+ }): Promise<THREE.Group | undefined>;
@@ -0,0 +1,22 @@
1
+ import { loadGLB } from '../loaders/glbLoader.js';
2
+ /**
3
+ * `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light).
4
+ * - `modelUrl`: REQUIRED; throws without it.
5
+ * - `flags`: material flags (`forceOpaque`, `wireframe`).
6
+ * - `retries` (3), `withCredentials` (false).
7
+ * - `renderer`: REQUIRED at runtime (`loadGLB` throws without one); nullable here because
8
+ * `WorldManager` threads its own, which may be null.
9
+ */
10
+ export async function makeGLB({ modelUrl, flags, retries = 3, withCredentials = false, renderer, emissiveIntensity, } = {}) {
11
+ if (!modelUrl)
12
+ throw new Error('modelUrl is required');
13
+ // A null or missing renderer makes `loadGLB` throw, as it always did (see above).
14
+ const obj = await loadGLB(modelUrl, {
15
+ retries,
16
+ applyFlags: flags,
17
+ withCredentials,
18
+ renderer: renderer,
19
+ emissiveIntensity,
20
+ });
21
+ return obj;
22
+ }
@@ -0,0 +1,57 @@
1
+ import type * as THREE from 'three';
2
+ import { makeSphere } from './worldTexture.js';
3
+ import { makeNormalSphere } from './worldNormal.js';
4
+ /**
5
+ * Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
6
+ * flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
7
+ * manager awaits either).
8
+ */
9
+ export type WorldCreator = (args: {
10
+ flags: object;
11
+ renderer: THREE.WebGLRenderer | null;
12
+ [key: string]: unknown;
13
+ }) => THREE.Object3D | Promise<THREE.Object3D>;
14
+ /**
15
+ * What `setMode` takes: the options of the world it builds. Mode 2 reads `modelUrl` (required there:
16
+ * it throws without one), `withCredentials` and `emissiveIntensity`; mode 3 passes everything to
17
+ * `makeNormalSphere`, the textured sphere to `makeSphere`, a registered creator gets it all as given.
18
+ * The manager's own renderer is always used. `flags`, when given, replaces the manager's flags for
19
+ * that world in every mode but 2 (which always uses the manager's).
20
+ */
21
+ export type WorldModeArgs = Omit<NonNullable<Parameters<typeof makeSphere>[0]>, 'renderer'> & Omit<NonNullable<Parameters<typeof makeNormalSphere>[0]>, 'renderer'> & {
22
+ modelUrl?: string;
23
+ withCredentials?: boolean;
24
+ emissiveIntensity?: number;
25
+ [key: string]: unknown;
26
+ };
27
+ export declare class WorldManager {
28
+ scene: THREE.Scene;
29
+ renderer: THREE.WebGLRenderer | null;
30
+ externalTick: boolean;
31
+ /** The world on show, or null. */
32
+ current: THREE.Object3D | null;
33
+ /** Merged by `setFlags`, handed to every world built (material flags such as `forceOpaque`). */
34
+ flags: Record<string, unknown>;
35
+ onLoadStart: (() => void) | undefined;
36
+ onLoadEnd: (() => void) | undefined;
37
+ token: number;
38
+ /** The creators `registerWorldCreator` added, by mode. */
39
+ customCreators: Map<number, WorldCreator>;
40
+ constructor(scene: THREE.Scene, { onLoadStart, onLoadEnd, renderer, externalTick, }?: {
41
+ onLoadStart?: (() => void) | undefined;
42
+ onLoadEnd?: (() => void) | undefined;
43
+ renderer?: THREE.WebGLRenderer | null | undefined;
44
+ externalTick?: boolean | undefined;
45
+ });
46
+ /** Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise). */
47
+ tick(timeSec: number): void;
48
+ /** `flags` are merged over the current flags. */
49
+ setFlags(flags: object): void;
50
+ disposeCurrent(): void;
51
+ registerWorldCreator(mode: number, creatorFunction: WorldCreator): void;
52
+ /**
53
+ * Build and show the world for `mode` (a registered creator, 2 = GLB, 3 = normal sphere, else the
54
+ * textured sphere), replacing the current one. A newer call supersedes one still loading.
55
+ */
56
+ setMode(mode: number, args?: WorldModeArgs): Promise<void>;
57
+ }
@@ -0,0 +1,125 @@
1
+ import { disposeObject3D } from '../utils/dispose.js';
2
+ import { makeGLB } from './worldGLB.js';
3
+ import { makeSphere } from './worldTexture.js';
4
+ import { makeNormalSphere } from './worldNormal.js';
5
+ export class WorldManager {
6
+ constructor(scene, { onLoadStart, onLoadEnd, renderer, externalTick = false, } = {}) {
7
+ this.scene = scene;
8
+ // The renderer this world belongs to — threaded to the texture loaders so each view's textures
9
+ // bind to its OWN WebGL context (multi-view: N orbiters / N planets in one page). REQUIRED for
10
+ // textured worlds (3.0): the window.__EW_RENDERER fallback is gone.
11
+ this.renderer = renderer ?? null;
12
+ // Shared-renderer hosts drive world animation from THEIR loop: with `externalTick` this manager
13
+ // starts no internal rAF for `userData.update` worlds — the host calls `tick(timeSec)` per
14
+ // rendered frame instead. Otherwise N animated worlds on one page would each run their own loop,
15
+ // defeating the one-loop compositor.
16
+ this.externalTick = Boolean(externalTick);
17
+ this.current = null;
18
+ this.flags = {};
19
+ this.onLoadStart = onLoadStart;
20
+ this.onLoadEnd = onLoadEnd;
21
+ this.token = 0;
22
+ // Registry for custom world creators
23
+ this.customCreators = new Map();
24
+ }
25
+ /** Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise). */
26
+ tick(timeSec) {
27
+ if (!this.externalTick || !this.current)
28
+ return;
29
+ const updater = this.current.userData?.update;
30
+ if (typeof updater !== 'function')
31
+ return;
32
+ const renderer = this.renderer;
33
+ if (!renderer)
34
+ return;
35
+ try {
36
+ updater(renderer, timeSec);
37
+ }
38
+ catch (e) {
39
+ console.warn('[WorldManager] update tick error:', e);
40
+ }
41
+ }
42
+ /** `flags` are merged over the current flags. */
43
+ setFlags(flags) {
44
+ this.flags = { ...this.flags, ...flags };
45
+ }
46
+ disposeCurrent() {
47
+ if (this.current) {
48
+ disposeObject3D(this.current);
49
+ this.scene.remove(this.current);
50
+ this.current = null;
51
+ }
52
+ }
53
+ // Register custom world creators for app-specific world types
54
+ registerWorldCreator(mode, creatorFunction) {
55
+ this.customCreators.set(mode, creatorFunction);
56
+ }
57
+ /**
58
+ * Build and show the world for `mode` (a registered creator, 2 = GLB, 3 = normal sphere, else the
59
+ * textured sphere), replacing the current one. A newer call supersedes one still loading.
60
+ */
61
+ async setMode(mode, args = {}) {
62
+ const myToken = ++this.token;
63
+ this.onLoadStart?.();
64
+ try {
65
+ let obj;
66
+ const renderer = this.renderer;
67
+ // Check for custom creators first
68
+ if (this.customCreators.has(mode)) {
69
+ // `has` just said it is there; a creator registered as nothing (untyped callers) throws, as always.
70
+ obj = await this.customCreators.get(mode)({ flags: this.flags, ...args, renderer });
71
+ }
72
+ else if (mode === 2) {
73
+ obj = await makeGLB({
74
+ // `makeGLB` throws without a model, so a mode-2 call without one fails there, as always.
75
+ modelUrl: args.modelUrl,
76
+ flags: this.flags,
77
+ withCredentials: Boolean(args.withCredentials),
78
+ renderer,
79
+ emissiveIntensity: args.emissiveIntensity,
80
+ });
81
+ }
82
+ else if (mode === 3) {
83
+ obj = await makeNormalSphere({ flags: this.flags, ...args, renderer });
84
+ }
85
+ else {
86
+ obj = await makeSphere({ textureUrl: args.textureUrl, flags: this.flags, ...args, renderer });
87
+ }
88
+ if (this.token !== myToken) {
89
+ return;
90
+ }
91
+ this.disposeCurrent();
92
+ // An object by every type here: a creator returns one, and `makeGLB` resolves to nothing only
93
+ // with `retries` of 0 or less, while this call keeps its default of 3. Cast, not checked, as
94
+ // the JS always took it.
95
+ const world = obj;
96
+ this.scene.add(world);
97
+ this.current = world;
98
+ // If the object exposes a frame updater, run a render-time loop — unless an external host
99
+ // drives it via tick() (shared-renderer compositor: one loop for all worlds).
100
+ if (!this.externalTick && obj && obj.userData && typeof obj.userData.update === 'function' && renderer) {
101
+ const updater = obj.userData.update;
102
+ const tick = () => {
103
+ if (this.current === obj) {
104
+ try {
105
+ updater(renderer, performance.now() * 0.001);
106
+ }
107
+ catch (e) {
108
+ console.warn('[WorldManager] update loop error:', e);
109
+ }
110
+ requestAnimationFrame(tick);
111
+ }
112
+ };
113
+ requestAnimationFrame(tick);
114
+ }
115
+ }
116
+ catch (e) {
117
+ console.warn('[WorldManager] setMode error:', e);
118
+ throw e;
119
+ }
120
+ finally {
121
+ if (this.token === myToken)
122
+ this.onLoadEnd?.();
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,30 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * Create a simple sphere using THREE.MeshNormalMaterial.
4
+ * Accepts the same basic geometry params as textured sphere so the manager API is consistent.
5
+ *
6
+ * `flags`: material flags (`forceOpaque`, `wireframe`); `glow` adds the fresnel halo. Defaults:
7
+ * `glowScale` 1.18, `glowRimHex` 0x66ccff, `glowFacingHex` 0x000000, `glowBias` 0.02,
8
+ * `glowFresnelScale` 1, `glowPower` 3.5, `cloudsColor` 0xffffff, `cloudsOpacity` 0.6,
9
+ * `cloudsSpeed` 0.015.
10
+ */
11
+ export declare function makeNormalSphere({ sphereRadius, sphereWidthSegments, sphereHeightSegments, flags, glow, // halo (glow) enabled
12
+ glowScale, glowRimHex, glowFacingHex, glowBias, glowFresnelScale, glowPower, cloudsEnabled, cloudsTextureUrl, cloudsColor, cloudsOpacity, cloudsSpeed, renderer, }?: {
13
+ sphereRadius?: number | undefined;
14
+ sphereWidthSegments?: number | undefined;
15
+ sphereHeightSegments?: number | undefined;
16
+ flags?: object | undefined;
17
+ glow?: boolean | undefined;
18
+ glowScale?: number | undefined;
19
+ glowRimHex?: number | undefined;
20
+ glowFacingHex?: number | undefined;
21
+ glowBias?: number | undefined;
22
+ glowFresnelScale?: number | undefined;
23
+ glowPower?: number | undefined;
24
+ cloudsEnabled?: boolean | undefined;
25
+ cloudsTextureUrl?: string | undefined;
26
+ cloudsColor?: number | undefined;
27
+ cloudsOpacity?: number | undefined;
28
+ cloudsSpeed?: number | undefined;
29
+ renderer?: THREE.WebGLRenderer | null | undefined;
30
+ }): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshNormalMaterial, THREE.Object3DEventMap>>;
@@ -0,0 +1,58 @@
1
+ // Normal world (MeshNormalMaterial) implementation
2
+ import * as THREE from 'three';
3
+ import { EARTH_RADIUS_UNITS } from './worldTexture.js';
4
+ import { applyFlagsToMaterial } from '../utils/materials.js';
5
+ import { getLightFresnel } from '../core/lightFresnel.js';
6
+ import { createCloudShell } from './cloudShell.js';
7
+ import { resolvePublicUrl } from '../config/env.js';
8
+ /**
9
+ * Create a simple sphere using THREE.MeshNormalMaterial.
10
+ * Accepts the same basic geometry params as textured sphere so the manager API is consistent.
11
+ *
12
+ * `flags`: material flags (`forceOpaque`, `wireframe`); `glow` adds the fresnel halo. Defaults:
13
+ * `glowScale` 1.18, `glowRimHex` 0x66ccff, `glowFacingHex` 0x000000, `glowBias` 0.02,
14
+ * `glowFresnelScale` 1, `glowPower` 3.5, `cloudsColor` 0xffffff, `cloudsOpacity` 0.6,
15
+ * `cloudsSpeed` 0.015.
16
+ */
17
+ export async function makeNormalSphere({ sphereRadius, sphereWidthSegments, sphereHeightSegments, flags, glow, // halo (glow) enabled
18
+ glowScale = 1.18, glowRimHex = 0x66ccff, glowFacingHex = 0x000000, glowBias = 0.02, glowFresnelScale = 1.0, glowPower = 3.5, cloudsEnabled, cloudsTextureUrl = '/assets/texture/2k_earth_clouds.jpg', cloudsColor = 0xffffff, cloudsOpacity = 0.6, cloudsSpeed = 0.015, renderer = null, } = {}) {
19
+ const radius = typeof sphereRadius === 'number' ? Math.max(0.0001, sphereRadius) : EARTH_RADIUS_UNITS;
20
+ // `Number.isFinite` does not narrow: past it, each count is a number.
21
+ const widthSegs = Number.isFinite(sphereWidthSegments) ? Math.max(3, Math.floor(sphereWidthSegments)) : 64;
22
+ const heightSegs = Number.isFinite(sphereHeightSegments) ? Math.max(2, Math.floor(sphereHeightSegments)) : 64;
23
+ const geometry = new THREE.SphereGeometry(radius, widthSegs, heightSegs);
24
+ const material = new THREE.MeshNormalMaterial();
25
+ if (flags)
26
+ applyFlagsToMaterial(material, flags);
27
+ const mesh = new THREE.Mesh(geometry, material);
28
+ mesh.name = 'worldNormalSphere';
29
+ // Attach halo (glow) effect if enabled
30
+ if (glow) {
31
+ const fresnelMat = getLightFresnel({ rimHex: glowRimHex, facingHex: glowFacingHex });
32
+ fresnelMat.uniforms.fresnelBias.value = glowBias;
33
+ fresnelMat.uniforms.fresnelScale.value = glowFresnelScale;
34
+ fresnelMat.uniforms.fresnelPower.value = glowPower;
35
+ const glowGeom = geometry.clone();
36
+ const fresnelMesh = new THREE.Mesh(glowGeom, fresnelMat);
37
+ fresnelMesh.scale.set(glowScale, glowScale, glowScale);
38
+ fresnelMesh.renderOrder = 999;
39
+ fresnelMesh.name = 'worldNormalGlow';
40
+ mesh.add(fresnelMesh);
41
+ }
42
+ // Attach clouds effect if enabled
43
+ if (cloudsEnabled) {
44
+ const cloudMesh = await createCloudShell({
45
+ baseGeometry: geometry,
46
+ baseRadius: radius,
47
+ widthSegments: widthSegs,
48
+ heightSegments: heightSegs,
49
+ textureUrl: resolvePublicUrl(cloudsTextureUrl),
50
+ color: cloudsColor,
51
+ opacity: cloudsOpacity,
52
+ speed: cloudsSpeed,
53
+ renderer,
54
+ });
55
+ mesh.add(cloudMesh);
56
+ }
57
+ return mesh;
58
+ }
@@ -0,0 +1,56 @@
1
+ import * as THREE from 'three';
2
+ export declare const WORLD_TEXTURE_DEBUG_TAG = "WT:vFlipGeom-r3@2025-10-28T17:22:00Z";
3
+ export declare const EARTH_RADIUS_UNITS = 0.5;
4
+ /**
5
+ * The textured world sphere. Every option is optional. The offset, repeat, rotation, flip and sphere
6
+ * geometry options are read only when they have their type; the rest are used as given (`glow`,
7
+ * `cloudsEnabled` and `mirrorX` by truthiness).
8
+ *
9
+ * - `renderer`: REQUIRED for a KTX2 texture. `flags`: material flags (`forceOpaque`, `wireframe`).
10
+ * - `uOffset`: horizontal texture offset, 0..1 (wins over `textureOffsetDeg`, in degrees).
11
+ * - `mirrorDuplicateX`: explicit override of the equirectangular detection
12
+ * (`autoDetectEquirectangular` true, `forceEquirectangular` / `forceRectangular` false).
13
+ * - `wrapS` ('repeat') / `wrapT` ('clamp'): `'clamp'`, `'repeat'` or `'mirror'`.
14
+ * - `center` ([0.5, 0.5]): texture rotation centre, `[u, v]`.
15
+ * - `materialRoughness` / `materialMetalness`: accepted from saved worlds; no longer change the look.
16
+ * - Defaults: `glow` true, `glowRimHex` 0x66ccff, `glowFacingHex` 0x000000, `glowScale` 1.18,
17
+ * `glowBias` 0.02, `glowFresnelScale` 1, `glowPower` 3.5, `cloudsEnabled` false, `cloudsColor`
18
+ * 0xffffff, `cloudsOpacity` 0.6, `cloudsSpeed` 0.015.
19
+ */
20
+ export declare function makeSphere({ textureUrl, renderer, flags, uOffset, textureOffsetDeg, mirrorX, mirrorDuplicateX, // optional, still supported for explicit override
21
+ autoDetectEquirectangular, forceEquirectangular, forceRectangular, wrapS, wrapT, repeatX, repeatY, rotationDeg, center, flipY, sphereRadius, sphereWidthSegments, sphereHeightSegments, materialRoughness, materialMetalness, glow, glowRimHex, glowFacingHex, glowScale, glowBias, glowFresnelScale, glowPower, cloudsEnabled, cloudsTextureUrl, cloudsColor, cloudsOpacity, cloudsSpeed, }?: {
22
+ textureUrl?: string | undefined;
23
+ renderer?: THREE.WebGLRenderer | null | undefined;
24
+ flags?: object | undefined;
25
+ uOffset?: number | undefined;
26
+ textureOffsetDeg?: number | undefined;
27
+ mirrorX?: boolean | undefined;
28
+ mirrorDuplicateX?: boolean | undefined;
29
+ autoDetectEquirectangular?: boolean | undefined;
30
+ forceEquirectangular?: boolean | undefined;
31
+ forceRectangular?: boolean | undefined;
32
+ wrapS?: string | undefined;
33
+ wrapT?: string | undefined;
34
+ repeatX?: number | undefined;
35
+ repeatY?: number | undefined;
36
+ rotationDeg?: number | undefined;
37
+ center?: number[] | undefined;
38
+ flipY?: boolean | undefined;
39
+ sphereRadius?: number | undefined;
40
+ sphereWidthSegments?: number | undefined;
41
+ sphereHeightSegments?: number | undefined;
42
+ materialRoughness?: number | undefined;
43
+ materialMetalness?: number | undefined;
44
+ glow?: boolean | undefined;
45
+ glowRimHex?: number | undefined;
46
+ glowFacingHex?: number | undefined;
47
+ glowScale?: number | undefined;
48
+ glowBias?: number | undefined;
49
+ glowFresnelScale?: number | undefined;
50
+ glowPower?: number | undefined;
51
+ cloudsEnabled?: boolean | undefined;
52
+ cloudsTextureUrl?: string | undefined;
53
+ cloudsColor?: number | undefined;
54
+ cloudsOpacity?: number | undefined;
55
+ cloudsSpeed?: number | undefined;
56
+ }): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;