entangled-worlds-orbiters-shared 3.0.0 → 3.3.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 +1 -1
- package/dist/star/framing.d.ts +17 -6
- package/dist/star/framing.js +16 -52
- package/dist/star/index.d.ts +1 -1
- package/dist/star/index.js +1 -1
- package/dist/star/layout.js +1 -2
- package/dist/star/params.js +3 -2
- package/dist/types/config/env.d.ts +12 -0
- package/dist/types/config/index.d.ts +2 -0
- package/dist/types/config/textureConfig.d.ts +121 -0
- package/dist/types/core/index.d.ts +2 -0
- package/dist/types/core/lightFresnel.d.ts +5 -0
- package/dist/types/core/lights.d.ts +58 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/loaders/bodyTextureLoader.d.ts +12 -0
- package/dist/types/loaders/cubeMapLoader.d.ts +70 -0
- package/dist/types/loaders/glbLoader.d.ts +24 -0
- package/dist/types/loaders/index.d.ts +5 -0
- package/dist/types/loaders/ktx2.d.ts +16 -0
- package/dist/types/moons/index.d.ts +2 -0
- package/dist/types/moons/moonsGeneration.d.ts +72 -0
- package/dist/types/moons/moonsManager.d.ts +27 -0
- package/dist/types/render/frameBudget.d.ts +29 -0
- package/dist/types/render/index.d.ts +2 -0
- package/dist/types/render/viewportCompositor.d.ts +58 -0
- package/dist/types/utils/dispose.d.ts +2 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/materials.d.ts +2 -0
- package/dist/types/world/cloudShell.d.ts +25 -0
- package/dist/types/world/index.d.ts +6 -0
- package/dist/types/world/worldGLB.d.ts +20 -0
- package/dist/types/world/worldManager.d.ts +63 -0
- package/dist/types/world/worldNormal.d.ts +43 -0
- package/dist/types/world/worldTexture.d.ts +80 -0
- package/package.json +35 -10
- package/src/config/env.js +8 -0
- package/src/core/lights.js +12 -0
- package/src/loaders/bodyTextureLoader.js +7 -0
- package/src/loaders/cubeMapLoader.js +27 -0
- package/src/loaders/glbLoader.js +7 -1
- package/src/loaders/index.js +3 -1
- package/src/moons/moonsGeneration.js +32 -2
- package/src/moons/moonsManager.js +1 -1
- package/src/render/viewportCompositor.js +59 -1
- package/src/star/framing.ts +17 -55
- package/src/star/index.ts +1 -1
- package/src/star/layout.ts +1 -2
- package/src/star/params.ts +3 -2
- package/src/world/cloudShell.js +13 -0
- package/src/world/index.js +3 -1
- package/src/world/worldGLB.js +12 -1
- package/src/world/worldManager.js +32 -1
- package/src/world/worldNormal.js +19 -0
- package/src/world/worldTexture.js +41 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A frame gate. Not a clock and not a scheduler: it answers one question and remembers the last time
|
|
3
|
+
* the answer was yes.
|
|
4
|
+
*
|
|
5
|
+
* @param {{ fps?: number, toleranceMs?: number }} [options]
|
|
6
|
+
* @returns {{ shouldRender(nowMs: number): boolean, reset(): void }}
|
|
7
|
+
*/
|
|
8
|
+
export function createFrameBudget({ fps, toleranceMs }?: {
|
|
9
|
+
fps?: number;
|
|
10
|
+
toleranceMs?: number;
|
|
11
|
+
}): {
|
|
12
|
+
shouldRender(nowMs: number): boolean;
|
|
13
|
+
reset(): void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* @file src/render/frameBudget.js
|
|
17
|
+
* @description "Has enough time passed to draw again?" — the whole of a frame cap, kept apart from
|
|
18
|
+
* whatever is being capped so it can be reasoned about and tested on its own.
|
|
19
|
+
*
|
|
20
|
+
* The tolerance is the part that is easy to get wrong. rAF does not deliver frames at arbitrary
|
|
21
|
+
* times; it delivers them on the display's ticks. On a 60Hz display those arrive every ~16.7ms, so a
|
|
22
|
+
* strict `elapsed >= 33.3` test rejects the frame at 33.3ms by a hair and waits for the next one at
|
|
23
|
+
* 50ms — 20fps, not the 30 that was asked for. A few milliseconds of slack lets the frame that is
|
|
24
|
+
* near enough through, and the cap lands where it was aimed.
|
|
25
|
+
*/
|
|
26
|
+
/** Frames a capped loop is allowed per second. */
|
|
27
|
+
export const CAPPED_FPS: 30;
|
|
28
|
+
/** Slack for rAF landing on display ticks — see the file note. */
|
|
29
|
+
export const FRAME_TOLERANCE_MS: 4;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} [opts]
|
|
3
|
+
* @param {{ acquireCell: (index:number, total:number) => HTMLElement, releaseCell?: (index:number) => void } | null} [opts.cellSource]
|
|
4
|
+
* external owner of the cell DOM. When present, `createCell` delegates to it and the compositor
|
|
5
|
+
* builds no grid of its own; when absent, the default `⌈√n⌉` grid is used.
|
|
6
|
+
* @param {'fixed-overlay'|'per-cell'} [opts.presentation='fixed-overlay'] how pixels reach the
|
|
7
|
+
* screen — the displayed fixed canvas (full-screen stage layouts) or a per-cell blit canvas the
|
|
8
|
+
* DOM positions (cells inside scrolling content). See the module doc.
|
|
9
|
+
* @param {Partial<typeof DEFAULT_RENDERER_PROFILE>} [opts.rendererProfile] renderer construction +
|
|
10
|
+
* pixel-ratio cap. Omit for the historical defaults.
|
|
11
|
+
* @param {boolean} [opts.performanceMode=false] start with the realm's renderer savings on — pixel
|
|
12
|
+
* ratio clamped to 1, shadow map off, this loop capped at 30fps. Off by default, and off means the
|
|
13
|
+
* renderer and the cadence are exactly what they were before this option existed. Toggle it live
|
|
14
|
+
* with `setPerformanceMode`.
|
|
15
|
+
* @param {string|null} [opts.devHandleKey=null] when set, publish the returned api on
|
|
16
|
+
* `window[devHandleKey]` so tooling can force a composited frame while rAF is throttled (a hidden
|
|
17
|
+
* tab during automated checks). Off by default — a library does not name globals for its host.
|
|
18
|
+
* @returns {{
|
|
19
|
+
* renderer: THREE.WebGLRenderer,
|
|
20
|
+
* canvas: HTMLCanvasElement,
|
|
21
|
+
* createCell: (index: number, total: number) => HTMLElement,
|
|
22
|
+
* addVoice: (voice: { voiceId: string, cell: HTMLElement, controller: object }) => void,
|
|
23
|
+
* removeVoice: (voiceId: string) => void,
|
|
24
|
+
* renderOnce: () => void,
|
|
25
|
+
* setPerformanceMode: (enabled: boolean) => boolean,
|
|
26
|
+
* dispose: () => void,
|
|
27
|
+
* }}
|
|
28
|
+
*/
|
|
29
|
+
export function createViewportCompositor({ cellSource, presentation, rendererProfile, devHandleKey, performanceMode, }?: {
|
|
30
|
+
cellSource?: {
|
|
31
|
+
acquireCell: (index: number, total: number) => HTMLElement;
|
|
32
|
+
releaseCell?: (index: number) => void;
|
|
33
|
+
} | null | undefined;
|
|
34
|
+
presentation?: "fixed-overlay" | "per-cell" | undefined;
|
|
35
|
+
rendererProfile?: Partial<{
|
|
36
|
+
antialias: boolean;
|
|
37
|
+
alpha: boolean;
|
|
38
|
+
preserveDrawingBuffer: boolean;
|
|
39
|
+
powerPreference: string;
|
|
40
|
+
maxDevicePixelRatio: number;
|
|
41
|
+
}> | undefined;
|
|
42
|
+
performanceMode?: boolean | undefined;
|
|
43
|
+
devHandleKey?: string | null | undefined;
|
|
44
|
+
}): {
|
|
45
|
+
renderer: THREE.WebGLRenderer;
|
|
46
|
+
canvas: HTMLCanvasElement;
|
|
47
|
+
createCell: (index: number, total: number) => HTMLElement;
|
|
48
|
+
addVoice: (voice: {
|
|
49
|
+
voiceId: string;
|
|
50
|
+
cell: HTMLElement;
|
|
51
|
+
controller: object;
|
|
52
|
+
}) => void;
|
|
53
|
+
removeVoice: (voiceId: string) => void;
|
|
54
|
+
renderOnce: () => void;
|
|
55
|
+
setPerformanceMode: (enabled: boolean) => boolean;
|
|
56
|
+
dispose: () => void;
|
|
57
|
+
};
|
|
58
|
+
import * as THREE from 'three';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} [opts]
|
|
3
|
+
* @param {import('three').BufferGeometry} [opts.baseGeometry] cloned for the shell; when absent a
|
|
4
|
+
* sphere is built from `baseRadius` and the segment counts.
|
|
5
|
+
* @param {number} [opts.baseRadius=0.5]
|
|
6
|
+
* @param {number} [opts.widthSegments=64]
|
|
7
|
+
* @param {number} [opts.heightSegments=64]
|
|
8
|
+
* @param {string} [opts.textureUrl]
|
|
9
|
+
* @param {number} [opts.color=0xffffff]
|
|
10
|
+
* @param {number} [opts.opacity=0.6]
|
|
11
|
+
* @param {number} [opts.speed=0.015]
|
|
12
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer]
|
|
13
|
+
*/
|
|
14
|
+
export function createCloudShell({ baseGeometry, baseRadius, widthSegments, heightSegments, textureUrl, color, opacity, speed, renderer, }?: {
|
|
15
|
+
baseGeometry?: THREE.BufferGeometry<THREE.NormalBufferAttributes> | undefined;
|
|
16
|
+
baseRadius?: number | undefined;
|
|
17
|
+
widthSegments?: number | undefined;
|
|
18
|
+
heightSegments?: number | undefined;
|
|
19
|
+
textureUrl?: string | undefined;
|
|
20
|
+
color?: number | undefined;
|
|
21
|
+
opacity?: number | undefined;
|
|
22
|
+
speed?: number | undefined;
|
|
23
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
24
|
+
}): Promise<THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes>, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;
|
|
25
|
+
import * as THREE from 'three';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { WorldManager } from "./worldManager.js";
|
|
2
|
+
export { makeNormalSphere } from "./worldNormal.js";
|
|
3
|
+
export { makeGLB } from "./worldGLB.js";
|
|
4
|
+
export { createCloudShell } from "./cloudShell.js";
|
|
5
|
+
export type WorldCreator = import("./worldManager.js").WorldCreator;
|
|
6
|
+
export { makeSphere, EARTH_RADIUS_UNITS } from "./worldTexture.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light).
|
|
3
|
+
*
|
|
4
|
+
* @param {object} [opts]
|
|
5
|
+
* @param {string} opts.modelUrl REQUIRED; throws without it.
|
|
6
|
+
* @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
|
|
7
|
+
* @param {number} [opts.retries=3]
|
|
8
|
+
* @param {boolean} [opts.withCredentials=false]
|
|
9
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED at runtime (`loadGLB` throws
|
|
10
|
+
* without one); nullable here because `WorldManager` threads its own, which may be null.
|
|
11
|
+
* @param {number} [opts.emissiveIntensity]
|
|
12
|
+
*/
|
|
13
|
+
export function makeGLB({ modelUrl, flags, retries, withCredentials, renderer, emissiveIntensity }?: {
|
|
14
|
+
modelUrl: string;
|
|
15
|
+
flags?: object | undefined;
|
|
16
|
+
retries?: number | undefined;
|
|
17
|
+
withCredentials?: boolean | undefined;
|
|
18
|
+
renderer?: import("three").WebGLRenderer | null | undefined;
|
|
19
|
+
emissiveIntensity?: number | undefined;
|
|
20
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
|
|
3
|
+
* flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
|
|
4
|
+
* manager awaits either).
|
|
5
|
+
* @typedef {(args: { flags: object, renderer: import('three').WebGLRenderer | null, [key: string]: unknown }) => import('three').Object3D | Promise<import('three').Object3D>} WorldCreator
|
|
6
|
+
*/
|
|
7
|
+
export class WorldManager {
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('three').Scene} scene
|
|
10
|
+
* @param {object} [opts]
|
|
11
|
+
* @param {() => void} [opts.onLoadStart]
|
|
12
|
+
* @param {() => void} [opts.onLoadEnd]
|
|
13
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer]
|
|
14
|
+
* @param {boolean} [opts.externalTick=false]
|
|
15
|
+
*/
|
|
16
|
+
constructor(scene: import("three").Scene, { onLoadStart, onLoadEnd, renderer, externalTick }?: {
|
|
17
|
+
onLoadStart?: (() => void) | undefined;
|
|
18
|
+
onLoadEnd?: (() => void) | undefined;
|
|
19
|
+
renderer?: import("three").WebGLRenderer | null | undefined;
|
|
20
|
+
externalTick?: boolean | undefined;
|
|
21
|
+
});
|
|
22
|
+
scene: import("three").Scene;
|
|
23
|
+
renderer: import("three").WebGLRenderer | null;
|
|
24
|
+
externalTick: boolean;
|
|
25
|
+
current: any;
|
|
26
|
+
/** @type {Record<string, any>} */
|
|
27
|
+
flags: Record<string, any>;
|
|
28
|
+
onLoadStart: (() => void) | undefined;
|
|
29
|
+
onLoadEnd: (() => void) | undefined;
|
|
30
|
+
token: number;
|
|
31
|
+
customCreators: Map<any, any>;
|
|
32
|
+
/**
|
|
33
|
+
* Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise).
|
|
34
|
+
* @param {number} timeSec
|
|
35
|
+
*/
|
|
36
|
+
tick(timeSec: number): void;
|
|
37
|
+
/** @param {object} flags merged over the current flags */
|
|
38
|
+
setFlags(flags: object): void;
|
|
39
|
+
disposeCurrent(): void;
|
|
40
|
+
/**
|
|
41
|
+
* @param {number} mode
|
|
42
|
+
* @param {WorldCreator} creatorFunction
|
|
43
|
+
*/
|
|
44
|
+
registerWorldCreator(mode: number, creatorFunction: WorldCreator): void;
|
|
45
|
+
/**
|
|
46
|
+
* Build and show the world for `mode` (a registered creator, 2 = GLB, 3 = normal sphere, else the
|
|
47
|
+
* textured sphere), replacing the current one. A newer call supersedes one still loading.
|
|
48
|
+
* @param {number} mode
|
|
49
|
+
* @param {Record<string, any>} [args]
|
|
50
|
+
* @returns {Promise<void>}
|
|
51
|
+
*/
|
|
52
|
+
setMode(mode: number, args?: Record<string, any>): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
|
|
56
|
+
* flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
|
|
57
|
+
* manager awaits either).
|
|
58
|
+
*/
|
|
59
|
+
export type WorldCreator = (args: {
|
|
60
|
+
flags: object;
|
|
61
|
+
renderer: import("three").WebGLRenderer | null;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}) => import("three").Object3D | Promise<import("three").Object3D>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a simple sphere using THREE.MeshNormalMaterial.
|
|
3
|
+
* Accepts the same basic geometry params as textured sphere so the manager API is consistent.
|
|
4
|
+
*
|
|
5
|
+
* @param {object} [opts]
|
|
6
|
+
* @param {number} [opts.sphereRadius]
|
|
7
|
+
* @param {number} [opts.sphereWidthSegments]
|
|
8
|
+
* @param {number} [opts.sphereHeightSegments]
|
|
9
|
+
* @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
|
|
10
|
+
* @param {boolean} [opts.glow] add the fresnel halo.
|
|
11
|
+
* @param {number} [opts.glowScale=1.18]
|
|
12
|
+
* @param {number} [opts.glowRimHex=0x66ccff]
|
|
13
|
+
* @param {number} [opts.glowFacingHex=0x000000]
|
|
14
|
+
* @param {number} [opts.glowBias=0.02]
|
|
15
|
+
* @param {number} [opts.glowFresnelScale=1]
|
|
16
|
+
* @param {number} [opts.glowPower=3.5]
|
|
17
|
+
* @param {boolean} [opts.cloudsEnabled]
|
|
18
|
+
* @param {string} [opts.cloudsTextureUrl]
|
|
19
|
+
* @param {number} [opts.cloudsColor=0xffffff]
|
|
20
|
+
* @param {number} [opts.cloudsOpacity=0.6]
|
|
21
|
+
* @param {number} [opts.cloudsSpeed=0.015]
|
|
22
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer]
|
|
23
|
+
*/
|
|
24
|
+
export function makeNormalSphere({ sphereRadius, sphereWidthSegments, sphereHeightSegments, flags, glow, glowScale, glowRimHex, glowFacingHex, glowBias, glowFresnelScale, glowPower, cloudsEnabled, cloudsTextureUrl, cloudsColor, cloudsOpacity, cloudsSpeed, renderer, }?: {
|
|
25
|
+
sphereRadius?: number | undefined;
|
|
26
|
+
sphereWidthSegments?: number | undefined;
|
|
27
|
+
sphereHeightSegments?: number | undefined;
|
|
28
|
+
flags?: object | undefined;
|
|
29
|
+
glow?: boolean | undefined;
|
|
30
|
+
glowScale?: number | undefined;
|
|
31
|
+
glowRimHex?: number | undefined;
|
|
32
|
+
glowFacingHex?: number | undefined;
|
|
33
|
+
glowBias?: number | undefined;
|
|
34
|
+
glowFresnelScale?: number | undefined;
|
|
35
|
+
glowPower?: number | undefined;
|
|
36
|
+
cloudsEnabled?: boolean | undefined;
|
|
37
|
+
cloudsTextureUrl?: string | undefined;
|
|
38
|
+
cloudsColor?: number | undefined;
|
|
39
|
+
cloudsOpacity?: number | undefined;
|
|
40
|
+
cloudsSpeed?: number | undefined;
|
|
41
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
42
|
+
}): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshNormalMaterial, THREE.Object3DEventMap>>;
|
|
43
|
+
import * as THREE from 'three';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The textured world sphere. Every option is optional. The offset, repeat, rotation, flip and sphere
|
|
3
|
+
* geometry options are read only when they have their type; the rest are used as given (`glow`,
|
|
4
|
+
* `cloudsEnabled` and `mirrorX` by truthiness).
|
|
5
|
+
*
|
|
6
|
+
* @param {object} [opts]
|
|
7
|
+
* @param {string} [opts.textureUrl]
|
|
8
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED for a KTX2 texture.
|
|
9
|
+
* @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
|
|
10
|
+
* @param {number} [opts.uOffset] horizontal texture offset, 0..1 (wins over `textureOffsetDeg`).
|
|
11
|
+
* @param {number} [opts.textureOffsetDeg] horizontal texture offset in degrees.
|
|
12
|
+
* @param {boolean} [opts.mirrorX]
|
|
13
|
+
* @param {boolean} [opts.mirrorDuplicateX] explicit override of the equirectangular detection.
|
|
14
|
+
* @param {boolean} [opts.autoDetectEquirectangular=true]
|
|
15
|
+
* @param {boolean} [opts.forceEquirectangular=false]
|
|
16
|
+
* @param {boolean} [opts.forceRectangular=false]
|
|
17
|
+
* @param {string} [opts.wrapS='repeat'] `'clamp'`, `'repeat'` or `'mirror'`.
|
|
18
|
+
* @param {string} [opts.wrapT='clamp'] `'clamp'`, `'repeat'` or `'mirror'`.
|
|
19
|
+
* @param {number} [opts.repeatX]
|
|
20
|
+
* @param {number} [opts.repeatY]
|
|
21
|
+
* @param {number} [opts.rotationDeg]
|
|
22
|
+
* @param {number[]} [opts.center=[0.5, 0.5]] texture rotation centre, `[u, v]`.
|
|
23
|
+
* @param {boolean} [opts.flipY]
|
|
24
|
+
* @param {number} [opts.sphereRadius]
|
|
25
|
+
* @param {number} [opts.sphereWidthSegments]
|
|
26
|
+
* @param {number} [opts.sphereHeightSegments]
|
|
27
|
+
* @param {number} [opts.materialRoughness] accepted from saved worlds; no longer changes the look.
|
|
28
|
+
* @param {number} [opts.materialMetalness] accepted from saved worlds; no longer changes the look.
|
|
29
|
+
* @param {boolean} [opts.glow=true]
|
|
30
|
+
* @param {number} [opts.glowRimHex=0x66ccff]
|
|
31
|
+
* @param {number} [opts.glowFacingHex=0x000000]
|
|
32
|
+
* @param {number} [opts.glowScale=1.18]
|
|
33
|
+
* @param {number} [opts.glowBias=0.02]
|
|
34
|
+
* @param {number} [opts.glowFresnelScale=1]
|
|
35
|
+
* @param {number} [opts.glowPower=3.5]
|
|
36
|
+
* @param {boolean} [opts.cloudsEnabled=false]
|
|
37
|
+
* @param {string} [opts.cloudsTextureUrl]
|
|
38
|
+
* @param {number} [opts.cloudsColor=0xffffff]
|
|
39
|
+
* @param {number} [opts.cloudsOpacity=0.6]
|
|
40
|
+
* @param {number} [opts.cloudsSpeed=0.015]
|
|
41
|
+
*/
|
|
42
|
+
export function makeSphere({ textureUrl, renderer, flags, uOffset, textureOffsetDeg, mirrorX, mirrorDuplicateX, 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, }?: {
|
|
43
|
+
textureUrl?: string | undefined;
|
|
44
|
+
renderer?: THREE.WebGLRenderer | null | undefined;
|
|
45
|
+
flags?: object | undefined;
|
|
46
|
+
uOffset?: number | undefined;
|
|
47
|
+
textureOffsetDeg?: number | undefined;
|
|
48
|
+
mirrorX?: boolean | undefined;
|
|
49
|
+
mirrorDuplicateX?: boolean | undefined;
|
|
50
|
+
autoDetectEquirectangular?: boolean | undefined;
|
|
51
|
+
forceEquirectangular?: boolean | undefined;
|
|
52
|
+
forceRectangular?: boolean | undefined;
|
|
53
|
+
wrapS?: string | undefined;
|
|
54
|
+
wrapT?: string | undefined;
|
|
55
|
+
repeatX?: number | undefined;
|
|
56
|
+
repeatY?: number | undefined;
|
|
57
|
+
rotationDeg?: number | undefined;
|
|
58
|
+
center?: number[] | undefined;
|
|
59
|
+
flipY?: boolean | undefined;
|
|
60
|
+
sphereRadius?: number | undefined;
|
|
61
|
+
sphereWidthSegments?: number | undefined;
|
|
62
|
+
sphereHeightSegments?: number | undefined;
|
|
63
|
+
materialRoughness?: number | undefined;
|
|
64
|
+
materialMetalness?: number | undefined;
|
|
65
|
+
glow?: boolean | undefined;
|
|
66
|
+
glowRimHex?: number | undefined;
|
|
67
|
+
glowFacingHex?: number | undefined;
|
|
68
|
+
glowScale?: number | undefined;
|
|
69
|
+
glowBias?: number | undefined;
|
|
70
|
+
glowFresnelScale?: number | undefined;
|
|
71
|
+
glowPower?: number | undefined;
|
|
72
|
+
cloudsEnabled?: boolean | undefined;
|
|
73
|
+
cloudsTextureUrl?: string | undefined;
|
|
74
|
+
cloudsColor?: number | undefined;
|
|
75
|
+
cloudsOpacity?: number | undefined;
|
|
76
|
+
cloudsSpeed?: number | undefined;
|
|
77
|
+
}): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;
|
|
78
|
+
export const WORLD_TEXTURE_DEBUG_TAG: "WT:vFlipGeom-r3@2025-10-28T17:22:00Z";
|
|
79
|
+
export const EARTH_RADIUS_UNITS: 0.5;
|
|
80
|
+
import * as THREE from 'three';
|
package/package.json
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entangled-worlds-orbiters-shared",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
7
8
|
"sideEffects": false,
|
|
8
9
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"./
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./world": {
|
|
15
|
+
"types": "./dist/types/world/index.d.ts",
|
|
16
|
+
"default": "./src/world/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./moons": {
|
|
19
|
+
"types": "./dist/types/moons/index.d.ts",
|
|
20
|
+
"default": "./src/moons/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./loaders": {
|
|
23
|
+
"types": "./dist/types/loaders/index.d.ts",
|
|
24
|
+
"default": "./src/loaders/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./render": {
|
|
27
|
+
"types": "./dist/types/render/index.d.ts",
|
|
28
|
+
"default": "./src/render/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./core": {
|
|
31
|
+
"types": "./dist/types/core/index.d.ts",
|
|
32
|
+
"default": "./src/core/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./config": {
|
|
35
|
+
"types": "./dist/types/config/index.d.ts",
|
|
36
|
+
"default": "./src/config/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./utils": {
|
|
39
|
+
"types": "./dist/types/utils/index.d.ts",
|
|
40
|
+
"default": "./src/utils/index.js"
|
|
41
|
+
},
|
|
17
42
|
"./clock": {
|
|
18
43
|
"types": "./dist/clock/index.d.ts",
|
|
19
44
|
"import": "./dist/clock/index.js"
|
|
@@ -120,7 +145,7 @@
|
|
|
120
145
|
"lint": "eslint .",
|
|
121
146
|
"lint:strict": "eslint . --max-warnings=0",
|
|
122
147
|
"lint:fallback": "node scripts/lint.js",
|
|
123
|
-
"build:ts": "tsc -p tsconfig.build.json",
|
|
148
|
+
"build:ts": "tsc -p tsconfig.build.json && tsc -p tsconfig.types.json",
|
|
124
149
|
"build:clock": "npm run build:ts",
|
|
125
150
|
"prepare": "npm run build:ts",
|
|
126
151
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
package/src/config/env.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// Runtime public base URL helper for building absolute asset URLs at runtime
|
|
2
2
|
|
|
3
3
|
// Mutable module-scoped variable to allow apps to override at startup
|
|
4
|
+
/** @type {string | null} */
|
|
4
5
|
export let __PUBLIC_BASE = null;
|
|
5
6
|
|
|
6
7
|
// Set the public base (strips trailing slash) and mirror to window for convenience
|
|
8
|
+
/** @param {string | null | undefined} url */
|
|
7
9
|
export function setPublicBase(url) {
|
|
8
10
|
__PUBLIC_BASE = (url || '').replace(/\/$/, '');
|
|
9
11
|
if (typeof window !== 'undefined') window.VITE_PUBLIC_HERBARIUM_BASE = __PUBLIC_BASE;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
// Get the current public base from (in order): explicit set value, envs, or default
|
|
15
|
+
/** @returns {string} */
|
|
13
16
|
export function getPublicBase() {
|
|
14
17
|
const envVal =
|
|
15
18
|
(typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_PUBLIC_HERBARIUM_BASE) ||
|
|
@@ -20,6 +23,11 @@ export function getPublicBase() {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
// Resolve a possibly-relative path against the current public base
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} path root-relative, relative, or an absolute http(s) URL (returned unchanged).
|
|
28
|
+
* An empty string comes back as is.
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
23
31
|
export function resolvePublicUrl(path) {
|
|
24
32
|
if (!path) return path;
|
|
25
33
|
if (/^https?:\/\//i.test(path)) return path;
|
package/src/core/lights.js
CHANGED
|
@@ -8,6 +8,9 @@ import * as THREE from 'three';
|
|
|
8
8
|
* live in just one scene (adding it to a second removes it from the first), so scenes 2..N rendered
|
|
9
9
|
* DARK ("only one entangled world"). So create + add the lights PER scene, keyed by name so a
|
|
10
10
|
* re-call on the same scene is a no-op. Single-orbiter (one scene) is unchanged.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('three').Scene} scene
|
|
13
|
+
* @returns {{ hemi: import('three').HemisphereLight, ambient: import('three').AmbientLight }}
|
|
11
14
|
*/
|
|
12
15
|
export function ensureDefaultLights(scene) {
|
|
13
16
|
let hemi = scene.getObjectByName('ew_HemisphereLight');
|
|
@@ -33,7 +36,15 @@ export function ensureDefaultLights(scene) {
|
|
|
33
36
|
* was ensured last, so on a page rendering several scenes every lighting update landed on one of
|
|
34
37
|
* them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
|
|
35
38
|
*
|
|
39
|
+
* Every field is optional; an omitted one leaves that part of the lighting as it is.
|
|
40
|
+
*
|
|
36
41
|
* @param {import('three').Scene} scene
|
|
42
|
+
* @param {object} [patch]
|
|
43
|
+
* @param {number} [patch.ambientIntensity] ambient light intensity (clamped at 0)
|
|
44
|
+
* @param {number} [patch.diffuseIntensity] hemisphere light intensity (clamped at 0)
|
|
45
|
+
* @param {import('three').Color | number | string} [patch.lightColor] hemisphere light colour
|
|
46
|
+
* @param {{ x: number, y?: number, z?: number }} [patch.lightDirection] hemisphere light direction
|
|
47
|
+
* @param {boolean} [patch.hemisphere] switch the hemisphere light on or off
|
|
37
48
|
*/
|
|
38
49
|
export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere } = {}) {
|
|
39
50
|
if (!scene?.isObject3D) {
|
|
@@ -75,6 +86,7 @@ export function setHemisphereEnabled(scene, on) {
|
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
// Back-compat helper
|
|
89
|
+
/** @param {import('three').Scene} scene */
|
|
78
90
|
export function addDefaultLights(scene) {
|
|
79
91
|
return ensureDefaultLights(scene);
|
|
80
92
|
}
|
|
@@ -3,6 +3,13 @@ import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
|
3
3
|
|
|
4
4
|
const textureLoader = new THREE.TextureLoader();
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} url `.ktx2` goes through the renderer's KTX2 transcoder; anything else through
|
|
8
|
+
* THREE.TextureLoader.
|
|
9
|
+
* @param {object} [opts]
|
|
10
|
+
* @param {AbortSignal} [opts.signal] an abort during the load rejects the promise.
|
|
11
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED for a `.ktx2` url.
|
|
12
|
+
*/
|
|
6
13
|
export function loadTexture(url, { signal, renderer } = {}) {
|
|
7
14
|
return new Promise((resolve, reject) => {
|
|
8
15
|
if (url && url.endsWith('.ktx2')) {
|
|
@@ -167,6 +167,15 @@ function resolveUrls(faceKeys) {
|
|
|
167
167
|
* `renderer` binds the KTX2 transcoder to the right WebGL context. REQUIRED (3.0) when the KTX2
|
|
168
168
|
* path is taken — the old `window.__EW_RENDERER` fallback is gone; thread the owning view's
|
|
169
169
|
* renderer. (The PNG/JPG path is renderer-agnostic, so its absence only bites on KTX2.)
|
|
170
|
+
*
|
|
171
|
+
* @param {object} [opts]
|
|
172
|
+
* @param {{ right: string, left: string, top: string, bottom: string, front: string, back: string }} [opts.faceKeys]
|
|
173
|
+
* explicit face URLs; when absent they are built from `skycubeName`.
|
|
174
|
+
* @param {string} [opts.skycubeName] skybox id (e.g. `'001'`), used when `faceKeys` is absent.
|
|
175
|
+
* @param {boolean} [opts.preferKTX2=true]
|
|
176
|
+
* @param {boolean} [opts.forceLow] force (or forbid) the low-resolution faces; omitted = detect the device.
|
|
177
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer]
|
|
178
|
+
* @returns {Promise<SkyboxResource>} pass it to `setBackground`.
|
|
170
179
|
*/
|
|
171
180
|
export async function loadCubeMap({ faceKeys, skycubeName, preferKTX2 = true, forceLow, renderer } = {}) {
|
|
172
181
|
|
|
@@ -235,6 +244,20 @@ function disposeSkyboxMesh(mesh) {
|
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
246
|
|
|
247
|
+
/**
|
|
248
|
+
* What `loadCubeMap` resolves to: a skybox mesh wrapper (the KTX2 path) or a cube texture (the
|
|
249
|
+
* PNG/JPG path). Read `mesh` to tell them apart.
|
|
250
|
+
* @typedef {{ __ewType: 'skybox-mesh', mesh: import('three').Mesh } | (import('three').CubeTexture & { mesh?: undefined })} SkyboxResource
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Show a loaded skybox as the scene's background. A previous skybox mesh or cube-texture background is
|
|
255
|
+
* disposed (a plain-texture background is not). Takes what `loadCubeMap` resolved to (or any texture);
|
|
256
|
+
* anything else, `null` included, clears it.
|
|
257
|
+
*
|
|
258
|
+
* @param {import('three').Scene} scene
|
|
259
|
+
* @param {unknown} resource
|
|
260
|
+
*/
|
|
238
261
|
export function setBackground(scene, resource) {
|
|
239
262
|
// Clean up previous skybox mesh
|
|
240
263
|
if (scene.__EW_SKYBOX_MESH && scene.__EW_SKYBOX_MESH.parent === scene) {
|
|
@@ -274,6 +297,10 @@ export function setBackground(scene, resource) {
|
|
|
274
297
|
}
|
|
275
298
|
}
|
|
276
299
|
|
|
300
|
+
/**
|
|
301
|
+
* @param {import('three').Scene} scene
|
|
302
|
+
* @param {{ renderer?: import('three').WebGLRenderer | null }} [opts]
|
|
303
|
+
*/
|
|
277
304
|
export async function initSpaceScene(scene, { renderer } = {}) {
|
|
278
305
|
const faceKeys = buildSkycubefaceKeys('001');
|
|
279
306
|
const cube = await loadCubeMap({ faceKeys, preferKTX2: true, renderer });
|
package/src/loaders/glbLoader.js
CHANGED
|
@@ -4,7 +4,13 @@ import { ensureStandardMaterial, applyFlagsToMaterial } from '../utils/materials
|
|
|
4
4
|
import { getKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @param {
|
|
7
|
+
* @param {string} url
|
|
8
|
+
* @param {object} [opts] `renderer` is REQUIRED at runtime (throws without one).
|
|
9
|
+
* @param {number} [opts.retries=3] load attempts before giving up.
|
|
10
|
+
* @param {AbortSignal} [opts.signal] rejects the load on abort.
|
|
11
|
+
* @param {(event: ProgressEvent) => void} [opts.onProgress]
|
|
12
|
+
* @param {object} [opts.applyFlags] material flags (`forceOpaque`, `wireframe`) applied to every mesh.
|
|
13
|
+
* @param {boolean} [opts.withCredentials=false]
|
|
8
14
|
* @param {number} [opts.emissiveIntensity=1] self-glow of textured materials (their map doubles as the
|
|
9
15
|
* emissive map). 1 keeps the old flat-lit look; with a directional star light pass ≈0.1–0.15 so the
|
|
10
16
|
* night side reads.
|
package/src/loaders/index.js
CHANGED
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
|
|
3
3
|
export { loadGLB } from './glbLoader.js';
|
|
4
4
|
export { loadTexture } from './bodyTextureLoader.js';
|
|
5
|
-
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
|
+
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('./cubeMapLoader.js').SkyboxResource} SkyboxResource */
|
|
@@ -21,6 +21,36 @@ export const MOON_ORBIT_SCATTER = 0.22;
|
|
|
21
21
|
/** Each moon sits up to ± half of this above or below the orbit plane. */
|
|
22
22
|
export const MOON_HEIGHT_SCATTER = 0.12;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} [opts]
|
|
26
|
+
* @param {number} [opts.count=0] 0 or less resolves to null.
|
|
27
|
+
* @param {number} [opts.orbitRadius=1.5]
|
|
28
|
+
* @param {string} [opts.textureKTX2]
|
|
29
|
+
* @param {string} [opts.texturePNG]
|
|
30
|
+
* @param {string | null} [opts.textureFallback] used instead of `texturePNG` when set.
|
|
31
|
+
* @param {boolean} [opts.useTexture=true]
|
|
32
|
+
* @param {number | string | null} [opts.seed] deterministic positions when set.
|
|
33
|
+
* @param {string | null} [opts.textureKey] replaces `textureKTX2` when set (the fallback stays
|
|
34
|
+
* `textureFallback` or `texturePNG`).
|
|
35
|
+
* @param {string} [opts.moonColor='#ffffff']
|
|
36
|
+
* @param {number} [opts.brightness=1.15]
|
|
37
|
+
* @param {number} [opts.emissiveColor=0x333333]
|
|
38
|
+
* @param {number} [opts.emissiveIntensity=0.18]
|
|
39
|
+
* @param {number} [opts.emissiveLift] night-side self-glow 0..1; omitted = `emissiveIntensity`.
|
|
40
|
+
* @param {number} [opts.roughness=0.85]
|
|
41
|
+
* @param {number} [opts.metalness=0]
|
|
42
|
+
* @param {boolean} [opts.debug=true]
|
|
43
|
+
* @param {boolean} [opts.smooth=true]
|
|
44
|
+
* @param {number} [opts.geometryWidthSegments=16]
|
|
45
|
+
* @param {number} [opts.geometryHeightSegments=12]
|
|
46
|
+
* @param {boolean} [opts.useBumpFromColor=true]
|
|
47
|
+
* @param {number} [opts.bumpScale=0.15]
|
|
48
|
+
* @param {boolean} [opts.debugShowUvGrid=false]
|
|
49
|
+
* @param {boolean} [opts.debugShowReferenceMoon=false]
|
|
50
|
+
* @param {number} [opts.referenceMoonScale=0.25]
|
|
51
|
+
* @param {boolean} [opts.useEmissiveMapFromTexture=true]
|
|
52
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer] binds the KTX2 moon texture.
|
|
53
|
+
*/
|
|
24
54
|
export async function generateMoons({
|
|
25
55
|
count = 0,
|
|
26
56
|
orbitRadius = 1.5,
|
|
@@ -55,8 +85,8 @@ export async function generateMoons({
|
|
|
55
85
|
referenceMoonScale = 0.25,
|
|
56
86
|
// behavior flags
|
|
57
87
|
useEmissiveMapFromTexture = true, // reuse color texture as emissiveMap to lift dark sides
|
|
58
|
-
// The renderer whose KTX2 transcoder decodes the moon texture.
|
|
59
|
-
//
|
|
88
|
+
// The renderer whose KTX2 transcoder decodes the moon texture. REQUIRED for the KTX2 texture (3.0:
|
|
89
|
+
// the loader has no global fallback).
|
|
60
90
|
renderer,
|
|
61
91
|
} = {}) {
|
|
62
92
|
if (count <= 0) return null;
|
|
@@ -6,7 +6,7 @@ export class MoonsManager {
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {import('three').Scene} scene
|
|
8
8
|
* @param {object} [opts]
|
|
9
|
-
* @param {import('three').WebGLRenderer} [opts.renderer] renderer the moon texture binds to (KTX2).
|
|
9
|
+
* @param {import('three').WebGLRenderer | null} [opts.renderer] renderer the moon texture binds to (KTX2).
|
|
10
10
|
*/
|
|
11
11
|
constructor(scene, { renderer } = {}) {
|
|
12
12
|
this.scene = scene;
|