entangled-worlds-orbiters-shared 3.1.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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/star/framing.d.ts +17 -6
  3. package/dist/star/framing.js +16 -52
  4. package/dist/star/index.d.ts +1 -1
  5. package/dist/star/index.js +1 -1
  6. package/dist/star/layout.js +1 -2
  7. package/dist/star/params.js +3 -2
  8. package/dist/types/config/env.d.ts +12 -0
  9. package/dist/types/config/index.d.ts +2 -0
  10. package/dist/types/config/textureConfig.d.ts +121 -0
  11. package/dist/types/core/index.d.ts +2 -0
  12. package/dist/types/core/lightFresnel.d.ts +5 -0
  13. package/dist/types/core/lights.d.ts +58 -0
  14. package/dist/types/index.d.ts +6 -0
  15. package/dist/types/loaders/bodyTextureLoader.d.ts +12 -0
  16. package/dist/types/loaders/cubeMapLoader.d.ts +70 -0
  17. package/dist/types/loaders/glbLoader.d.ts +24 -0
  18. package/dist/types/loaders/index.d.ts +5 -0
  19. package/dist/types/loaders/ktx2.d.ts +16 -0
  20. package/dist/types/moons/index.d.ts +2 -0
  21. package/dist/types/moons/moonsGeneration.d.ts +72 -0
  22. package/dist/types/moons/moonsManager.d.ts +27 -0
  23. package/dist/types/render/frameBudget.d.ts +29 -0
  24. package/dist/types/render/index.d.ts +2 -0
  25. package/dist/types/render/viewportCompositor.d.ts +58 -0
  26. package/dist/types/utils/dispose.d.ts +2 -0
  27. package/dist/types/utils/index.d.ts +2 -0
  28. package/dist/types/utils/materials.d.ts +2 -0
  29. package/dist/types/world/cloudShell.d.ts +25 -0
  30. package/dist/types/world/index.d.ts +6 -0
  31. package/dist/types/world/worldGLB.d.ts +20 -0
  32. package/dist/types/world/worldManager.d.ts +63 -0
  33. package/dist/types/world/worldNormal.d.ts +43 -0
  34. package/dist/types/world/worldTexture.d.ts +80 -0
  35. package/package.json +35 -10
  36. package/src/config/env.js +8 -0
  37. package/src/core/lights.js +12 -0
  38. package/src/loaders/bodyTextureLoader.js +7 -0
  39. package/src/loaders/cubeMapLoader.js +27 -0
  40. package/src/loaders/glbLoader.js +7 -1
  41. package/src/loaders/index.js +3 -1
  42. package/src/moons/moonsGeneration.js +32 -2
  43. package/src/moons/moonsManager.js +1 -1
  44. package/src/star/framing.ts +17 -55
  45. package/src/star/index.ts +1 -1
  46. package/src/star/layout.ts +1 -2
  47. package/src/star/params.ts +3 -2
  48. package/src/world/cloudShell.js +13 -0
  49. package/src/world/index.js +3 -1
  50. package/src/world/worldGLB.js +12 -1
  51. package/src/world/worldManager.js +32 -1
  52. package/src/world/worldNormal.js +19 -0
  53. package/src/world/worldTexture.js +41 -0
@@ -1,8 +1,7 @@
1
1
  // Camera framing helpers (pure, no three): how far a camera must sit to fit a focus sphere, and a
2
- // host-camera-aware default placement for the star so it reads beside the planet, not behind it.
2
+ // default placement for the star that lights the planet's side facing the camera.
3
3
 
4
- import { FOCUS_GLOW_RADII } from './layout.js';
5
- import { DEFAULT_STAR_PARAMS, STAR_PARAM_RANGES } from './params.js';
4
+ import { DEFAULT_STAR_PARAMS } from './params.js';
6
5
 
7
6
  const DEG = Math.PI / 180;
8
7
 
@@ -35,62 +34,25 @@ export interface StarPlacementInput {
35
34
  export interface StarPlacement {
36
35
  azimuth: number;
37
36
  elevation: number;
38
- /** False when no in-frame spot clears the planet (it fills the frame); the star is then just above it. */
37
+ /** Always true (kept for older hosts): the spot is behind any camera closer than ~0.79 × the star's distance (≥ 15.8 units). */
39
38
  fits: boolean;
40
39
  }
41
40
 
42
41
  /**
43
- * Azimuth / elevation that put the primary's disc (plus its bright glow) just outside the planet's
44
- * angular disc and inside the frame of a camera on +Z looking at the origin — upper-right when there
45
- * is room, swinging towards straight up as the frame narrows (portrait). Hosts use it only when the
46
- * author has not set azimuth/elevation; `DEFAULT_STAR_PARAMS` stays the host-agnostic fallback.
42
+ * The opening light: the star over the camera's right shoulder (behind it, a little up and to the
43
+ * right), so the planet opens on its day side — about 90% of the face the camera sees is lit, the
44
+ * night edge on the left — and the star itself shows when the viewer orbits round. 145° / 15° is
45
+ * ~38° off the view axis. It is `DEFAULT_STAR_PARAMS`' direction, so a host with no camera gets it too.
47
46
  */
48
- export function defaultStarPlacement(input: StarPlacementInput): StarPlacement {
49
- const fov = Number.isFinite(input.fovDeg) && input.fovDeg > 0 ? input.fovDeg : 45;
50
- const aspect = Number.isFinite(input.aspect) && input.aspect > 0 ? input.aspect : 1;
51
- const cd = Number.isFinite(input.cameraDistance) && input.cameraDistance > 0 ? input.cameraDistance : 4;
52
- const pr = Math.max(0, Number.isFinite(input.planetRadius) ? input.planetRadius : 1);
53
- const starRadius = input.starRadius ?? DEFAULT_STAR_PARAMS.radius;
54
- const [dLo, dHi] = STAR_PARAM_RANGES.distance;
55
- const D = Math.min(dHi, Math.max(dLo, input.distance ?? DEFAULT_STAR_PARAMS.distance));
47
+ export const OVER_THE_SHOULDER = { azimuth: DEFAULT_STAR_PARAMS.azimuth, elevation: DEFAULT_STAR_PARAMS.elevation } as const;
56
48
 
57
- // Work on the image plane at unit distance (tangent units): positions and radii both scale by tan.
58
- const tanV = Math.tan((fov * DEG) / 2);
59
- const tanH = tanV * aspect;
60
- const planetTan = cd > pr ? pr / Math.sqrt(cd * cd - pr * pr) : Infinity;
61
- const starTan = (starRadius * FOCUS_GLOW_RADII) / Math.sqrt(D * D + cd * cd);
62
- const rho = planetTan + starTan * 1.35; // centre distance from the view axis: disc + glow clear the planet
63
- const maxX = tanH * 0.97 - starTan * 0.5; // centre in frame, disc mostly in frame
64
- const maxY = tanV * 0.97 - starTan * 0.5;
65
-
66
- let x = 0;
67
- let y = Number.isFinite(rho) ? rho : maxY;
68
- let fits = false;
69
- for (let phiDeg = 30; phiDeg <= 90; phiDeg += 2) {
70
- const px = rho * Math.cos(phiDeg * DEG);
71
- const py = rho * Math.sin(phiDeg * DEG);
72
- if (px <= maxX && py <= maxY) {
73
- x = px;
74
- y = py;
75
- fits = true;
76
- break;
77
- }
78
- }
79
-
80
- // Ray from the camera through (x, y) on the image plane, hit on the sphere |P| = D.
81
- const len = Math.hypot(x, y, 1);
82
- const ux = x / len;
83
- const uy = y / len;
84
- const uz = -1 / len;
85
- const cu = cd * uz; // C · u with C = (0, 0, cd)
86
- const t = -cu + Math.sqrt(Math.max(0, cu * cu - cd * cd + D * D));
87
- const px = ux * t;
88
- const py = uy * t;
89
- const pz = cd + uz * t;
90
- const round = (v: number) => Math.round(v * 10) / 10;
91
- return {
92
- azimuth: round(Math.atan2(px, -pz) / DEG),
93
- elevation: round(Math.asin(Math.max(-1, Math.min(1, py / D))) / DEG),
94
- fits,
95
- };
49
+ /**
50
+ * Azimuth / elevation of the default star for a camera on +Z looking at the origin: over the
51
+ * camera's shoulder (`OVER_THE_SHOULDER`), so the world opens lit. The star sits ≥ 0.79 × its
52
+ * distance (≥ 15.8 units) out on +Z, behind any host camera, so the frame never changes it. Hosts
53
+ * use it only when the author has not set azimuth/elevation; `DEFAULT_STAR_PARAMS` stays the
54
+ * host-agnostic fallback.
55
+ */
56
+ export function defaultStarPlacement(_input?: StarPlacementInput): StarPlacement {
57
+ return { azimuth: OVER_THE_SHOULDER.azimuth, elevation: OVER_THE_SHOULDER.elevation, fits: true };
96
58
  }
package/src/star/index.ts CHANGED
@@ -19,7 +19,7 @@ export type { StarParams, StarMultiplicity, SpectralClass } from './params.js';
19
19
  export { blackbodyColor } from './blackbody.js';
20
20
  export type { RGB } from './blackbody.js';
21
21
  export { starLayout, starPositions, starSystemPosition, starFocusRadius, flareAt } from './layout.js';
22
- export { framingDistance, defaultStarPlacement } from './framing.js';
22
+ export { framingDistance, defaultStarPlacement, OVER_THE_SHOULDER } from './framing.js';
23
23
  export type { StarPlacement, StarPlacementInput } from './framing.js';
24
24
  export type { StarLayout, LaidOutStar, StarOrbit, StarRole, FlareState } from './layout.js';
25
25
  export {
@@ -203,8 +203,7 @@ export function starsReach(layout: Pick<StarLayout, 'stars' | 'pairOrbit'>): num
203
203
  function distantFor(p: StarParams, reach: number): StarLayout['distant'] {
204
204
  if (!p.distantCompanion) return null;
205
205
  const r = createLcg(`${p.seed}|distant`);
206
- // Default: up and back towards the frame's centre from the host — above the planet rather than
207
- // behind it, and inside a typical frame when the host itself is placed beside the planet.
206
+ // Default: a little above the host and swung towards the scene's −Z axis — never on top of it.
208
207
  const inward = p.azimuth >= 0 ? -1 : 1;
209
208
  let azimuth = p.distantAzimuth ?? wrapDeg(p.azimuth + inward * lerp(12, 22, r()));
210
209
  let elevation = p.distantElevation ?? Math.min(60, p.elevation + lerp(6, 11, r()));
@@ -55,8 +55,9 @@ export const DEFAULT_STAR_PARAMS: Readonly<StarParams> = Object.freeze({
55
55
  separation: 2,
56
56
  seed: 1,
57
57
  distance: 20,
58
- azimuth: 16,
59
- elevation: 10,
58
+ // Over the camera's shoulder (`OVER_THE_SHOULDER` in framing.ts): worlds open on their day side.
59
+ azimuth: 145,
60
+ elevation: 15,
60
61
  companionTemperatureK: 0,
61
62
  companionRadius: 0,
62
63
  distantCompanion: false,
@@ -53,6 +53,19 @@ async function makeTextureMaterial({
53
53
  return mat;
54
54
  }
55
55
 
56
+ /**
57
+ * @param {object} [opts]
58
+ * @param {import('three').BufferGeometry} [opts.baseGeometry] cloned for the shell; when absent a
59
+ * sphere is built from `baseRadius` and the segment counts.
60
+ * @param {number} [opts.baseRadius=0.5]
61
+ * @param {number} [opts.widthSegments=64]
62
+ * @param {number} [opts.heightSegments=64]
63
+ * @param {string} [opts.textureUrl]
64
+ * @param {number} [opts.color=0xffffff]
65
+ * @param {number} [opts.opacity=0.6]
66
+ * @param {number} [opts.speed=0.015]
67
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
68
+ */
56
69
  export async function createCloudShell({
57
70
  baseGeometry,
58
71
  baseRadius = 0.5,
@@ -3,4 +3,6 @@ export { WorldManager } from './worldManager.js';
3
3
  export { makeSphere, EARTH_RADIUS_UNITS } from './worldTexture.js';
4
4
  export { makeNormalSphere } from './worldNormal.js';
5
5
  export { makeGLB } from './worldGLB.js';
6
- export { createCloudShell } from './cloudShell.js';
6
+ export { createCloudShell } from './cloudShell.js';
7
+
8
+ /** @typedef {import('./worldManager.js').WorldCreator} WorldCreator */
@@ -1,6 +1,17 @@
1
1
  import { loadGLB } from '../loaders/glbLoader.js';
2
2
 
3
- /** `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light). */
3
+ /**
4
+ * `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light).
5
+ *
6
+ * @param {object} [opts]
7
+ * @param {string} opts.modelUrl REQUIRED; throws without it.
8
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
9
+ * @param {number} [opts.retries=3]
10
+ * @param {boolean} [opts.withCredentials=false]
11
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED at runtime (`loadGLB` throws
12
+ * without one); nullable here because `WorldManager` threads its own, which may be null.
13
+ * @param {number} [opts.emissiveIntensity]
14
+ */
4
15
  export async function makeGLB({ modelUrl, flags, retries = 3, withCredentials = false, renderer, emissiveIntensity } = {}) {
5
16
  if (!modelUrl) throw new Error('modelUrl is required');
6
17
  const obj = await loadGLB(modelUrl, { retries, applyFlags: flags, withCredentials, renderer, emissiveIntensity });
@@ -3,7 +3,22 @@ import { makeGLB } from './worldGLB.js';
3
3
  import { makeSphere } from './worldTexture.js';
4
4
  import { makeNormalSphere } from './worldNormal.js';
5
5
 
6
+ /**
7
+ * Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
8
+ * flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
9
+ * manager awaits either).
10
+ * @typedef {(args: { flags: object, renderer: import('three').WebGLRenderer | null, [key: string]: unknown }) => import('three').Object3D | Promise<import('three').Object3D>} WorldCreator
11
+ */
12
+
6
13
  export class WorldManager {
14
+ /**
15
+ * @param {import('three').Scene} scene
16
+ * @param {object} [opts]
17
+ * @param {() => void} [opts.onLoadStart]
18
+ * @param {() => void} [opts.onLoadEnd]
19
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
20
+ * @param {boolean} [opts.externalTick=false]
21
+ */
7
22
  constructor(scene, { onLoadStart, onLoadEnd, renderer, externalTick = false } = {}) {
8
23
  this.scene = scene;
9
24
  // The renderer this world belongs to — threaded to the texture loaders so each view's textures
@@ -16,6 +31,7 @@ export class WorldManager {
16
31
  // defeating the one-loop compositor.
17
32
  this.externalTick = Boolean(externalTick);
18
33
  this.current = null;
34
+ /** @type {Record<string, any>} */
19
35
  this.flags = {};
20
36
  this.onLoadStart = onLoadStart;
21
37
  this.onLoadEnd = onLoadEnd;
@@ -24,7 +40,10 @@ export class WorldManager {
24
40
  this.customCreators = new Map();
25
41
  }
26
42
 
27
- /** Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise). */
43
+ /**
44
+ * Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise).
45
+ * @param {number} timeSec
46
+ */
28
47
  tick(timeSec) {
29
48
  if (!this.externalTick || !this.current) return;
30
49
  const updater = this.current.userData?.update;
@@ -38,6 +57,7 @@ export class WorldManager {
38
57
  }
39
58
  }
40
59
 
60
+ /** @param {object} flags merged over the current flags */
41
61
  setFlags(flags) {
42
62
  this.flags = { ...this.flags, ...flags };
43
63
  }
@@ -51,10 +71,21 @@ export class WorldManager {
51
71
  }
52
72
 
53
73
  // Register custom world creators for app-specific world types
74
+ /**
75
+ * @param {number} mode
76
+ * @param {WorldCreator} creatorFunction
77
+ */
54
78
  registerWorldCreator(mode, creatorFunction) {
55
79
  this.customCreators.set(mode, creatorFunction);
56
80
  }
57
81
 
82
+ /**
83
+ * Build and show the world for `mode` (a registered creator, 2 = GLB, 3 = normal sphere, else the
84
+ * textured sphere), replacing the current one. A newer call supersedes one still loading.
85
+ * @param {number} mode
86
+ * @param {Record<string, any>} [args]
87
+ * @returns {Promise<void>}
88
+ */
58
89
  async setMode(mode, args = {}) {
59
90
  const myToken = ++this.token;
60
91
  this.onLoadStart?.();
@@ -9,6 +9,25 @@ import { resolvePublicUrl } from '../config/env.js';
9
9
  /**
10
10
  * Create a simple sphere using THREE.MeshNormalMaterial.
11
11
  * Accepts the same basic geometry params as textured sphere so the manager API is consistent.
12
+ *
13
+ * @param {object} [opts]
14
+ * @param {number} [opts.sphereRadius]
15
+ * @param {number} [opts.sphereWidthSegments]
16
+ * @param {number} [opts.sphereHeightSegments]
17
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
18
+ * @param {boolean} [opts.glow] add the fresnel halo.
19
+ * @param {number} [opts.glowScale=1.18]
20
+ * @param {number} [opts.glowRimHex=0x66ccff]
21
+ * @param {number} [opts.glowFacingHex=0x000000]
22
+ * @param {number} [opts.glowBias=0.02]
23
+ * @param {number} [opts.glowFresnelScale=1]
24
+ * @param {number} [opts.glowPower=3.5]
25
+ * @param {boolean} [opts.cloudsEnabled]
26
+ * @param {string} [opts.cloudsTextureUrl]
27
+ * @param {number} [opts.cloudsColor=0xffffff]
28
+ * @param {number} [opts.cloudsOpacity=0.6]
29
+ * @param {number} [opts.cloudsSpeed=0.015]
30
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
12
31
  */
13
32
  export async function makeNormalSphere({
14
33
  sphereRadius,
@@ -50,6 +50,47 @@ function isLikelyEquirectangular(img) {
50
50
  return false;
51
51
  }
52
52
 
53
+ /**
54
+ * The textured world sphere. Every option is optional. The offset, repeat, rotation, flip and sphere
55
+ * geometry options are read only when they have their type; the rest are used as given (`glow`,
56
+ * `cloudsEnabled` and `mirrorX` by truthiness).
57
+ *
58
+ * @param {object} [opts]
59
+ * @param {string} [opts.textureUrl]
60
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED for a KTX2 texture.
61
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
62
+ * @param {number} [opts.uOffset] horizontal texture offset, 0..1 (wins over `textureOffsetDeg`).
63
+ * @param {number} [opts.textureOffsetDeg] horizontal texture offset in degrees.
64
+ * @param {boolean} [opts.mirrorX]
65
+ * @param {boolean} [opts.mirrorDuplicateX] explicit override of the equirectangular detection.
66
+ * @param {boolean} [opts.autoDetectEquirectangular=true]
67
+ * @param {boolean} [opts.forceEquirectangular=false]
68
+ * @param {boolean} [opts.forceRectangular=false]
69
+ * @param {string} [opts.wrapS='repeat'] `'clamp'`, `'repeat'` or `'mirror'`.
70
+ * @param {string} [opts.wrapT='clamp'] `'clamp'`, `'repeat'` or `'mirror'`.
71
+ * @param {number} [opts.repeatX]
72
+ * @param {number} [opts.repeatY]
73
+ * @param {number} [opts.rotationDeg]
74
+ * @param {number[]} [opts.center=[0.5, 0.5]] texture rotation centre, `[u, v]`.
75
+ * @param {boolean} [opts.flipY]
76
+ * @param {number} [opts.sphereRadius]
77
+ * @param {number} [opts.sphereWidthSegments]
78
+ * @param {number} [opts.sphereHeightSegments]
79
+ * @param {number} [opts.materialRoughness] accepted from saved worlds; no longer changes the look.
80
+ * @param {number} [opts.materialMetalness] accepted from saved worlds; no longer changes the look.
81
+ * @param {boolean} [opts.glow=true]
82
+ * @param {number} [opts.glowRimHex=0x66ccff]
83
+ * @param {number} [opts.glowFacingHex=0x000000]
84
+ * @param {number} [opts.glowScale=1.18]
85
+ * @param {number} [opts.glowBias=0.02]
86
+ * @param {number} [opts.glowFresnelScale=1]
87
+ * @param {number} [opts.glowPower=3.5]
88
+ * @param {boolean} [opts.cloudsEnabled=false]
89
+ * @param {string} [opts.cloudsTextureUrl]
90
+ * @param {number} [opts.cloudsColor=0xffffff]
91
+ * @param {number} [opts.cloudsOpacity=0.6]
92
+ * @param {number} [opts.cloudsSpeed=0.015]
93
+ */
53
94
  export async function makeSphere({
54
95
  textureUrl,
55
96
  renderer,