entangled-worlds-orbiters-shared 3.1.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.
- package/README.md +4 -3
- package/dist/config/env.d.ts +8 -0
- package/dist/config/env.js +36 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +4 -0
- package/dist/config/textureConfig.d.ts +140 -0
- package/dist/config/textureConfig.js +318 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/lightFresnel.d.ts +6 -0
- package/dist/core/lightFresnel.js +63 -0
- package/dist/core/lights.d.ts +48 -0
- package/dist/core/lights.js +79 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/loaders/bodyTextureLoader.d.ts +10 -0
- package/dist/loaders/bodyTextureLoader.js +28 -0
- package/dist/loaders/cubeMapLoader.d.ts +58 -0
- package/dist/loaders/cubeMapLoader.js +291 -0
- package/dist/loaders/glbLoader.d.ts +24 -0
- package/dist/loaders/glbLoader.js +72 -0
- package/dist/loaders/index.d.ts +5 -0
- package/{src → dist}/loaders/index.js +1 -1
- package/dist/loaders/ktx2.d.ts +14 -0
- package/dist/loaders/ktx2.js +48 -0
- package/dist/moons/index.d.ts +3 -0
- package/dist/moons/index.js +3 -0
- package/dist/moons/moonsGeneration.d.ts +64 -0
- package/dist/moons/moonsGeneration.js +238 -0
- package/dist/moons/moonsManager.d.ts +43 -0
- package/dist/moons/moonsManager.js +93 -0
- package/dist/render/frameBudget.d.ts +30 -0
- package/dist/render/frameBudget.js +42 -0
- package/dist/render/index.d.ts +4 -0
- package/dist/render/viewportCompositor.d.ts +124 -0
- package/dist/render/viewportCompositor.js +643 -0
- package/dist/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/studio/headerBroadcast.d.ts +10 -1
- package/dist/studio/hostTransport.d.ts +2 -2
- package/dist/studio/index.d.ts +1 -1
- package/dist/utils/dispose.d.ts +5 -0
- package/dist/utils/dispose.js +50 -0
- package/dist/utils/index.d.ts +3 -0
- package/{src → dist}/utils/index.js +1 -1
- package/dist/utils/materials.d.ts +10 -0
- package/dist/utils/materials.js +36 -0
- package/dist/world/cloudShell.d.ts +16 -0
- package/dist/world/cloudShell.js +73 -0
- package/dist/world/index.d.ts +6 -0
- package/{src → dist}/world/index.js +1 -1
- package/dist/world/worldGLB.d.ts +17 -0
- package/dist/world/worldGLB.js +22 -0
- package/dist/world/worldManager.d.ts +57 -0
- package/dist/world/worldManager.js +125 -0
- package/dist/world/worldNormal.d.ts +30 -0
- package/dist/world/worldNormal.js +58 -0
- package/dist/world/worldTexture.d.ts +56 -0
- package/dist/world/worldTexture.js +299 -0
- package/package.json +45 -30
- package/src/config/env.ts +48 -0
- package/src/config/{index.js → index.ts} +7 -0
- package/src/config/{textureConfig.js → textureConfig.ts} +126 -60
- package/src/core/{lightFresnel.js → lightFresnel.ts} +1 -1
- package/src/core/{lights.js → lights.ts} +22 -10
- package/src/loaders/{bodyTextureLoader.js → bodyTextureLoader.ts} +12 -2
- package/src/loaders/{cubeMapLoader.js → cubeMapLoader.ts} +95 -34
- package/src/loaders/{glbLoader.js → glbLoader.ts} +41 -12
- package/src/loaders/index.ts +6 -0
- package/src/loaders/{ktx2.js → ktx2.ts} +5 -7
- package/src/moons/{index.js → index.ts} +1 -0
- package/src/moons/{moonsGeneration.js → moonsGeneration.ts} +49 -6
- package/src/moons/{moonsManager.js → moonsManager.ts} +37 -11
- package/src/render/{frameBudget.js → frameBudget.ts} +15 -8
- package/src/render/index.ts +14 -0
- package/src/render/{viewportCompositor.js → viewportCompositor.ts} +121 -59
- package/src/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/studio/headerBroadcast.ts +14 -1
- package/src/studio/hostTransport.ts +2 -1
- package/src/studio/index.ts +1 -1
- package/src/utils/dispose.ts +47 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/materials.ts +44 -0
- package/src/world/{cloudShell.js → cloudShell.ts} +21 -1
- package/src/world/index.ts +7 -0
- package/src/world/worldGLB.ts +41 -0
- package/src/world/{worldManager.js → worldManager.ts} +76 -10
- package/src/world/{worldNormal.js → worldNormal.ts} +26 -2
- package/src/world/{worldTexture.js → worldTexture.ts} +76 -16
- package/src/config/env.js +0 -30
- package/src/utils/dispose.js +0 -39
- package/src/utils/materials.js +0 -31
- package/src/world/worldGLB.js +0 -8
- /package/{src → dist}/render/index.js +0 -0
- /package/src/core/{index.js → index.ts} +0 -0
- /package/src/{index.js → index.ts} +0 -0
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ In `entangled-worlds-orbiters-shared/`:
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
npm install
|
|
24
|
+
npm run build # consumers run dist/: build again after every change
|
|
24
25
|
npm link
|
|
25
26
|
```
|
|
26
27
|
|
|
@@ -76,7 +77,7 @@ import * as EntangledWorlds from 'entangled-worlds-orbiters-shared';
|
|
|
76
77
|
- `DEFAULT_STAR_PARAMS`, `normalizeStarParams` - Author params (temperature, radius, luminosity, activity, multiplicity, separation, seed, placement) + validator for stored params
|
|
77
78
|
- `starParamsFromStellarData` - Catalogue host-star data (Teff, radius, mass, luminosity, spectral type, star count or a "(AB)b" name suffix) → params
|
|
78
79
|
- `blackbodyColor`, `spectralClass`, `starLayout`, `starPositions` - Pure colour / layout / orbit helpers
|
|
79
|
-
- `getFocus` (on `createStar` / `StarManager`), `framingDistance`, `defaultStarPlacement` - Camera focus on the system barycentre and
|
|
80
|
+
- `getFocus` (on `createStar` / `StarManager`), `framingDistance`, `defaultStarPlacement` - Camera focus on the system barycentre and the default placement (over the camera's shoulder, so worlds open on their day side)
|
|
80
81
|
- The star lights the scene: `StarManager` key + camera-side fill lights, `setLighting`, `lightingFromLegacy`, `DEFAULT_AMBIENT` (pair with `setHemisphereEnabled(scene, false)` from `./core`; moons `emissiveLift`, GLB `emissiveIntensity` for night sides)
|
|
81
82
|
- Real binaries (close pair from component masses, a distant companion for wide pairs) and an optional debris disc; `mainSequenceTemperature` / `Radius` / `Luminosity` helpers
|
|
82
83
|
- Perf + look harness: `npm run bench:star` (see `bench/star/results/REPORT.md`)
|
|
@@ -145,10 +146,10 @@ Effects:
|
|
|
145
146
|
|
|
146
147
|
## Peer Dependencies
|
|
147
148
|
|
|
148
|
-
This library requires Three.js
|
|
149
|
+
This library requires Three.js as a peer dependency:
|
|
149
150
|
|
|
150
151
|
```bash
|
|
151
|
-
npm install three
|
|
152
|
+
npm install three
|
|
152
153
|
```
|
|
153
154
|
|
|
154
155
|
## License
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare let __PUBLIC_BASE: string | null;
|
|
2
|
+
export declare function setPublicBase(url: string | null | undefined): void;
|
|
3
|
+
export declare function getPublicBase(): string;
|
|
4
|
+
/**
|
|
5
|
+
* @param path root-relative, relative, or an absolute http(s) URL (returned unchanged).
|
|
6
|
+
* An empty string comes back as is.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePublicUrl(path: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Runtime public base URL helper for building absolute asset URLs at runtime
|
|
2
|
+
// Mutable module-scoped variable to allow apps to override at startup
|
|
3
|
+
export let __PUBLIC_BASE = null;
|
|
4
|
+
// Set the public base (strips trailing slash) and mirror to window for convenience
|
|
5
|
+
export function setPublicBase(url) {
|
|
6
|
+
__PUBLIC_BASE = (url || '').replace(/\/$/, '');
|
|
7
|
+
if (typeof window !== 'undefined')
|
|
8
|
+
window.VITE_PUBLIC_HERBARIUM_BASE = __PUBLIC_BASE;
|
|
9
|
+
}
|
|
10
|
+
// Get the current public base from (in order): explicit set value, envs, or default
|
|
11
|
+
export function getPublicBase() {
|
|
12
|
+
// The cast stays inline on each read so the emitted JS is the literal `import.meta.env.X` a
|
|
13
|
+
// bundler looks for.
|
|
14
|
+
const envVal = (typeof import.meta !== 'undefined' &&
|
|
15
|
+
import.meta.env &&
|
|
16
|
+
import.meta.env.VITE_PUBLIC_HERBARIUM_BASE) ||
|
|
17
|
+
(typeof window !== 'undefined' && window.VITE_PUBLIC_HERBARIUM_BASE) ||
|
|
18
|
+
null;
|
|
19
|
+
const base = (__PUBLIC_BASE || envVal || '').toString();
|
|
20
|
+
return base ? base.replace(/\/$/, '') : '';
|
|
21
|
+
}
|
|
22
|
+
// Resolve a possibly-relative path against the current public base
|
|
23
|
+
/**
|
|
24
|
+
* @param path root-relative, relative, or an absolute http(s) URL (returned unchanged).
|
|
25
|
+
* An empty string comes back as is.
|
|
26
|
+
*/
|
|
27
|
+
export function resolvePublicUrl(path) {
|
|
28
|
+
if (!path)
|
|
29
|
+
return path;
|
|
30
|
+
if (/^https?:\/\//i.test(path))
|
|
31
|
+
return path;
|
|
32
|
+
const base = getPublicBase();
|
|
33
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
34
|
+
// If base is empty, return a root-relative path (same-origin)
|
|
35
|
+
return base ? `${base}${p}` : p;
|
|
36
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture, getSkyboxOptions, getDefaultSkybox, getTextureFallbackUrl } from './textureConfig.js';
|
|
2
|
+
export type { TextureConfig, TextureCategory, TextureItem, TextureChoice, TextureConfigMetadata } from './textureConfig.js';
|
|
3
|
+
export { setPublicBase, getPublicBase, resolvePublicUrl } from './env.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Configuration exports
|
|
2
|
+
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture, getSkyboxOptions, getDefaultSkybox, getTextureFallbackUrl } from './textureConfig.js';
|
|
3
|
+
// runtime base helpers
|
|
4
|
+
export { setPublicBase, getPublicBase, resolvePublicUrl } from './env.js';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** One catalogue item. */
|
|
2
|
+
export interface TextureItem {
|
|
3
|
+
id: string;
|
|
4
|
+
filename?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Picture of the item (a thumbnail), relative like `filename`. */
|
|
7
|
+
still?: string;
|
|
8
|
+
/** Texture to use when this one fails to load, relative like `filename`. */
|
|
9
|
+
fallback?: string;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
}
|
|
12
|
+
/** One catalogue category (clouds, moons, skyboxes, …). */
|
|
13
|
+
export interface TextureCategory {
|
|
14
|
+
category?: string;
|
|
15
|
+
basePath?: string;
|
|
16
|
+
defaultTexture?: string;
|
|
17
|
+
defaultSkybox?: string;
|
|
18
|
+
items?: TextureItem[];
|
|
19
|
+
}
|
|
20
|
+
/** The texture catalogue as loaded from the config URL. */
|
|
21
|
+
export interface TextureConfig {
|
|
22
|
+
version?: string;
|
|
23
|
+
lastUpdated?: string;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
/** By category name; a category the catalogue does not list is missing. */
|
|
26
|
+
textures: Partial<Record<string, TextureCategory>>;
|
|
27
|
+
}
|
|
28
|
+
/** What `getConfigMetadata` reports about a catalogue. */
|
|
29
|
+
export interface TextureConfigMetadata {
|
|
30
|
+
version: string | undefined;
|
|
31
|
+
lastUpdated: string | undefined;
|
|
32
|
+
categories: string[];
|
|
33
|
+
totalTextures: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Load texture configuration from remote endpoint or use passed config
|
|
37
|
+
* @param {string} configUrl - Override config URL
|
|
38
|
+
* @param {boolean} useCache - Whether to use cached config if available
|
|
39
|
+
* @returns {Promise<TextureConfig>} Texture configuration object
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadTextureConfig(configUrl?: string, useCache?: boolean): Promise<TextureConfig>;
|
|
42
|
+
/**
|
|
43
|
+
* A choice record for one catalogue item.
|
|
44
|
+
*
|
|
45
|
+
* Every catalogue item IS a picture, so a chooser should show it rather than its name. `still` is
|
|
46
|
+
* read straight from the catalogue and never derived from `filename`: the stills do not share one
|
|
47
|
+
* extension, and a wrong guess fails silently (a broken `<img>` just shows nothing).
|
|
48
|
+
*/
|
|
49
|
+
export type TextureChoice = {
|
|
50
|
+
/** Catalogue id. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** Human-readable name. */
|
|
53
|
+
label: string;
|
|
54
|
+
/** Resolved texture URL (the .ktx2 the world renders). */
|
|
55
|
+
url: string;
|
|
56
|
+
/**
|
|
57
|
+
* Resolved URL of the item's picture — a small thumbnail suitable for showing the texture in a
|
|
58
|
+
* picker. `null` when the catalogue names none.
|
|
59
|
+
*/
|
|
60
|
+
still: string | null;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Get the full choice records for a category (clouds, moons, etc.) — label, texture URL, and the
|
|
64
|
+
* picture to show for it. The single resolution point for a category's items; `getTextureOptions`
|
|
65
|
+
* is the flat `{ label: url }` projection of this.
|
|
66
|
+
* @param {string} category - Texture category (clouds, moons)
|
|
67
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
68
|
+
* @returns {Promise<TextureChoice[]>} Choice records in catalogue order
|
|
69
|
+
*/
|
|
70
|
+
export declare function getTextureChoices(category: string, config?: TextureConfig | null): Promise<TextureChoice[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Get texture options for a specific category (clouds, moons, etc.)
|
|
73
|
+
* @param {string} category - Texture category (clouds, moons, skyboxes)
|
|
74
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
75
|
+
* @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
|
|
76
|
+
*/
|
|
77
|
+
export declare function getTextureOptions(category: string, config?: TextureConfig | null): Promise<Record<string, string>>;
|
|
78
|
+
/**
|
|
79
|
+
* Get default texture for a category
|
|
80
|
+
* @param {string} category - Texture category
|
|
81
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
82
|
+
* @returns {Promise<string|null>} Default texture path, or null when the category has no texture
|
|
83
|
+
*/
|
|
84
|
+
export declare function getDefaultTexture(category: string, config?: TextureConfig | null): Promise<string | null>;
|
|
85
|
+
/**
|
|
86
|
+
* Return the configured fallback URL for a given texture if defined.
|
|
87
|
+
* @param {string} category - Texture category (clouds, moons, etc.)
|
|
88
|
+
* @param {string} textureUrl - Resolved texture URL
|
|
89
|
+
* @param {TextureConfig|null} config - Optional texture config override
|
|
90
|
+
* @returns {string|null} Resolved fallback texture URL or null
|
|
91
|
+
*/
|
|
92
|
+
export declare function getTextureFallbackUrl(category: string, textureUrl: string, config?: TextureConfig | null): string | null;
|
|
93
|
+
/**
|
|
94
|
+
* Get texture by ID within a category
|
|
95
|
+
* @param {string} category - Texture category
|
|
96
|
+
* @param {string} textureId - Texture ID
|
|
97
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
98
|
+
* @returns {Promise<TextureItem|null>} Texture definition object
|
|
99
|
+
*/
|
|
100
|
+
export declare function getTextureById(category: string, textureId: string, config?: TextureConfig | null): Promise<TextureItem | null>;
|
|
101
|
+
/**
|
|
102
|
+
* Get full URL for a texture
|
|
103
|
+
* @param {string} category - Texture category
|
|
104
|
+
* @param {string} textureId - Texture ID
|
|
105
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
106
|
+
* @returns {Promise<string|null>} Full texture URL
|
|
107
|
+
*/
|
|
108
|
+
export declare function getTextureUrl(category: string, textureId: string, config?: TextureConfig | null): Promise<string | null>;
|
|
109
|
+
/**
|
|
110
|
+
* Search textures by tags
|
|
111
|
+
* @param {string} category - Texture category
|
|
112
|
+
* @param {string[]} tags - Tags to search for
|
|
113
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
114
|
+
* @returns {Promise<TextureItem[]>} Array of matching texture definitions
|
|
115
|
+
*/
|
|
116
|
+
export declare function searchTexturesByTags(category: string, tags: string[], config?: TextureConfig | null): Promise<TextureItem[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Get skybox options formatted for GUI dropdowns
|
|
119
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
120
|
+
* @returns {Promise<Record<string, string>>} `{ label: id }`
|
|
121
|
+
*/
|
|
122
|
+
export declare function getSkyboxOptions(config?: TextureConfig | null): Promise<Record<string, string>>;
|
|
123
|
+
/**
|
|
124
|
+
* Get default skybox ID
|
|
125
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
126
|
+
* @returns {Promise<string>} Default skybox ID
|
|
127
|
+
*/
|
|
128
|
+
export declare function getDefaultSkybox(config?: TextureConfig | null): Promise<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Reload configuration (force refresh)
|
|
131
|
+
* @param {string} configUrl - Override config URL
|
|
132
|
+
* @returns {Promise<TextureConfig>} Fresh configuration object
|
|
133
|
+
*/
|
|
134
|
+
export declare function reloadTextureConfig(configUrl?: string): Promise<TextureConfig>;
|
|
135
|
+
/**
|
|
136
|
+
* Get configuration metadata
|
|
137
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
138
|
+
* @returns {Promise<TextureConfigMetadata>} Configuration metadata
|
|
139
|
+
*/
|
|
140
|
+
export declare function getConfigMetadata(config?: TextureConfig | null): Promise<TextureConfigMetadata>;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texture Configuration Loader
|
|
3
|
+
* Loads texture definitions from remote endpoint or uses passed config
|
|
4
|
+
*/
|
|
5
|
+
import { resolvePublicUrl } from './env.js';
|
|
6
|
+
/** The catalogue a public function was handed, or the loaded one. */
|
|
7
|
+
async function resolveConfig(config) {
|
|
8
|
+
return config || (await loadTextureConfig());
|
|
9
|
+
}
|
|
10
|
+
function buildTextureUrl(baseUrl, categoryPath, value) {
|
|
11
|
+
if (!value)
|
|
12
|
+
return null;
|
|
13
|
+
if (/^https?:\/\//i.test(value))
|
|
14
|
+
return value;
|
|
15
|
+
if (value.startsWith('/'))
|
|
16
|
+
return resolvePublicUrl(value);
|
|
17
|
+
const prefix = `${baseUrl || ''}${categoryPath || ''}`;
|
|
18
|
+
return resolvePublicUrl(`${prefix}/${value}`);
|
|
19
|
+
}
|
|
20
|
+
function findTextureItem(textureConfig, category, textureUrl) {
|
|
21
|
+
if (!textureUrl || !textureConfig)
|
|
22
|
+
return null;
|
|
23
|
+
const categoryConfig = textureConfig.textures?.[category];
|
|
24
|
+
if (!categoryConfig || !categoryConfig.items)
|
|
25
|
+
return null;
|
|
26
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
27
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
28
|
+
for (const item of categoryConfig.items) {
|
|
29
|
+
const resolved = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
|
|
30
|
+
if (!resolved)
|
|
31
|
+
continue;
|
|
32
|
+
if (resolved === textureUrl) {
|
|
33
|
+
return { item, baseUrl, categoryPath };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
let cachedConfig = null;
|
|
39
|
+
let configPromise = null;
|
|
40
|
+
let lastConfigUrl = null;
|
|
41
|
+
/**
|
|
42
|
+
* Load texture configuration from remote endpoint or use passed config
|
|
43
|
+
* @param {string} configUrl - Override config URL
|
|
44
|
+
* @param {boolean} useCache - Whether to use cached config if available
|
|
45
|
+
* @returns {Promise<TextureConfig>} Texture configuration object
|
|
46
|
+
*/
|
|
47
|
+
export async function loadTextureConfig(configUrl, useCache = true) {
|
|
48
|
+
if (useCache && cachedConfig) {
|
|
49
|
+
return cachedConfig;
|
|
50
|
+
}
|
|
51
|
+
if (configPromise) {
|
|
52
|
+
return configPromise;
|
|
53
|
+
}
|
|
54
|
+
if (configUrl) {
|
|
55
|
+
lastConfigUrl = configUrl;
|
|
56
|
+
}
|
|
57
|
+
const targetUrl = configUrl || lastConfigUrl;
|
|
58
|
+
configPromise = (async () => {
|
|
59
|
+
try {
|
|
60
|
+
if (!targetUrl) {
|
|
61
|
+
throw new Error('No texture config URL provided');
|
|
62
|
+
}
|
|
63
|
+
const response = await fetch(targetUrl);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
const config = (await response.json());
|
|
68
|
+
// Validate configuration structure
|
|
69
|
+
if (!config.textures || typeof config.textures !== 'object') {
|
|
70
|
+
throw new Error('Invalid configuration: missing textures object');
|
|
71
|
+
}
|
|
72
|
+
cachedConfig = config;
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('[TextureConfig] Failed to load config:', error);
|
|
77
|
+
// Return minimal fallback configuration when loading fails
|
|
78
|
+
console.warn('[TextureConfig] Using hardcoded fallback config');
|
|
79
|
+
const fallback = getHardcodedFallback();
|
|
80
|
+
cachedConfig = fallback;
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
configPromise = null;
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
return configPromise;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the full choice records for a category (clouds, moons, etc.) — label, texture URL, and the
|
|
91
|
+
* picture to show for it. The single resolution point for a category's items; `getTextureOptions`
|
|
92
|
+
* is the flat `{ label: url }` projection of this.
|
|
93
|
+
* @param {string} category - Texture category (clouds, moons)
|
|
94
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
95
|
+
* @returns {Promise<TextureChoice[]>} Choice records in catalogue order
|
|
96
|
+
*/
|
|
97
|
+
export async function getTextureChoices(category, config) {
|
|
98
|
+
const textureConfig = await resolveConfig(config);
|
|
99
|
+
const categoryConfig = textureConfig.textures[category];
|
|
100
|
+
if (!categoryConfig) {
|
|
101
|
+
console.warn(`[TextureConfig] Category '${category}' not found`);
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
105
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
106
|
+
return (categoryConfig.items || []).flatMap((item) => {
|
|
107
|
+
const url = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
|
|
108
|
+
if (!url)
|
|
109
|
+
return [];
|
|
110
|
+
return [{
|
|
111
|
+
id: item.id,
|
|
112
|
+
label: item.label || item.id,
|
|
113
|
+
url,
|
|
114
|
+
still: item.still ? buildTextureUrl(baseUrl, categoryPath, item.still) : null,
|
|
115
|
+
}];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get texture options for a specific category (clouds, moons, etc.)
|
|
120
|
+
* @param {string} category - Texture category (clouds, moons, skyboxes)
|
|
121
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
122
|
+
* @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
|
|
123
|
+
*/
|
|
124
|
+
export async function getTextureOptions(category, config) {
|
|
125
|
+
const choices = await getTextureChoices(category, config);
|
|
126
|
+
return Object.fromEntries(choices.map(c => [c.label, c.url]));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get default texture for a category
|
|
130
|
+
* @param {string} category - Texture category
|
|
131
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
132
|
+
* @returns {Promise<string|null>} Default texture path, or null when the category has no texture
|
|
133
|
+
*/
|
|
134
|
+
export async function getDefaultTexture(category, config) {
|
|
135
|
+
const textureConfig = await resolveConfig(config);
|
|
136
|
+
const categoryConfig = textureConfig.textures[category];
|
|
137
|
+
if (!categoryConfig) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
141
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
142
|
+
const defaultFilename = categoryConfig.defaultTexture;
|
|
143
|
+
if (defaultFilename) {
|
|
144
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${defaultFilename}`);
|
|
145
|
+
}
|
|
146
|
+
// Fallback to first item
|
|
147
|
+
if (categoryConfig.items && categoryConfig.items.length > 0) {
|
|
148
|
+
const firstItem = categoryConfig.items[0];
|
|
149
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${firstItem.filename || firstItem.id}`);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Return the configured fallback URL for a given texture if defined.
|
|
155
|
+
* @param {string} category - Texture category (clouds, moons, etc.)
|
|
156
|
+
* @param {string} textureUrl - Resolved texture URL
|
|
157
|
+
* @param {TextureConfig|null} config - Optional texture config override
|
|
158
|
+
* @returns {string|null} Resolved fallback texture URL or null
|
|
159
|
+
*/
|
|
160
|
+
export function getTextureFallbackUrl(category, textureUrl, config = null) {
|
|
161
|
+
const textureConfig = config || cachedConfig;
|
|
162
|
+
if (!textureConfig)
|
|
163
|
+
return null;
|
|
164
|
+
const found = findTextureItem(textureConfig, category, textureUrl);
|
|
165
|
+
if (!found || !found.item?.fallback)
|
|
166
|
+
return null;
|
|
167
|
+
return buildTextureUrl(found.baseUrl, found.categoryPath, found.item.fallback);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get texture by ID within a category
|
|
171
|
+
* @param {string} category - Texture category
|
|
172
|
+
* @param {string} textureId - Texture ID
|
|
173
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
174
|
+
* @returns {Promise<TextureItem|null>} Texture definition object
|
|
175
|
+
*/
|
|
176
|
+
export async function getTextureById(category, textureId, config) {
|
|
177
|
+
const textureConfig = await resolveConfig(config);
|
|
178
|
+
const categoryConfig = textureConfig.textures[category];
|
|
179
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return categoryConfig.items.find(item => item.id === textureId) || null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get full URL for a texture
|
|
186
|
+
* @param {string} category - Texture category
|
|
187
|
+
* @param {string} textureId - Texture ID
|
|
188
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
189
|
+
* @returns {Promise<string|null>} Full texture URL
|
|
190
|
+
*/
|
|
191
|
+
export async function getTextureUrl(category, textureId, config) {
|
|
192
|
+
const texture = await getTextureById(category, textureId, config);
|
|
193
|
+
if (!texture)
|
|
194
|
+
return null;
|
|
195
|
+
const textureConfig = await resolveConfig(config);
|
|
196
|
+
// `getTextureById` just found the item in this category, so it is there (unless the catalogue
|
|
197
|
+
// was reloaded in between, which throws here, as it always did).
|
|
198
|
+
const categoryConfig = textureConfig.textures[category];
|
|
199
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
200
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
201
|
+
return resolvePublicUrl(`${baseUrl}${categoryPath}/${texture.filename || texture.id}`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Search textures by tags
|
|
205
|
+
* @param {string} category - Texture category
|
|
206
|
+
* @param {string[]} tags - Tags to search for
|
|
207
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
208
|
+
* @returns {Promise<TextureItem[]>} Array of matching texture definitions
|
|
209
|
+
*/
|
|
210
|
+
export async function searchTexturesByTags(category, tags, config) {
|
|
211
|
+
const textureConfig = await resolveConfig(config);
|
|
212
|
+
const categoryConfig = textureConfig.textures[category];
|
|
213
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
return categoryConfig.items.filter(item => item.tags && tags.some(tag => item.tags.includes(tag)));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get skybox options formatted for GUI dropdowns
|
|
220
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
221
|
+
* @returns {Promise<Record<string, string>>} `{ label: id }`
|
|
222
|
+
*/
|
|
223
|
+
export async function getSkyboxOptions(config) {
|
|
224
|
+
const textureConfig = await resolveConfig(config);
|
|
225
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
226
|
+
if (!skyboxConfig) {
|
|
227
|
+
console.warn('[TextureConfig] Skyboxes category not found');
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
const options = {};
|
|
231
|
+
// Reads `items` unguarded, unlike the other readers: a skyboxes category without items throws here.
|
|
232
|
+
skyboxConfig.items.forEach(item => {
|
|
233
|
+
const label = item.label || item.id;
|
|
234
|
+
const id = item.id;
|
|
235
|
+
options[label] = id;
|
|
236
|
+
});
|
|
237
|
+
return options;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get default skybox ID
|
|
241
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
242
|
+
* @returns {Promise<string>} Default skybox ID
|
|
243
|
+
*/
|
|
244
|
+
export async function getDefaultSkybox(config) {
|
|
245
|
+
const textureConfig = await resolveConfig(config);
|
|
246
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
247
|
+
if (!skyboxConfig) {
|
|
248
|
+
return '001'; // fallback
|
|
249
|
+
}
|
|
250
|
+
return skyboxConfig.defaultSkybox || '001';
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Hardcoded fallback configuration (minimal)
|
|
254
|
+
* Used when remote config loading fails
|
|
255
|
+
*/
|
|
256
|
+
function getHardcodedFallback() {
|
|
257
|
+
return {
|
|
258
|
+
version: "1.0-fallback",
|
|
259
|
+
lastUpdated: new Date().toISOString(),
|
|
260
|
+
baseUrl: "/default",
|
|
261
|
+
textures: {
|
|
262
|
+
clouds: {
|
|
263
|
+
category: "atmospheric",
|
|
264
|
+
basePath: "/clouds",
|
|
265
|
+
defaultTexture: "04-clouds.ktx2",
|
|
266
|
+
items: [
|
|
267
|
+
{ id: "clouds-1", filename: "04-clouds.ktx2", label: "Clouds", tags: ["natural"] },
|
|
268
|
+
{ id: "entangled-1", filename: "06-entangled.ktx2", label: "Entangled", tags: ["quantum"] }
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
moons: {
|
|
272
|
+
category: "celestial",
|
|
273
|
+
basePath: "/textures/moons",
|
|
274
|
+
defaultTexture: "moon-equirectangular.ktx2",
|
|
275
|
+
items: [
|
|
276
|
+
{ id: "default-moon", filename: "moon-equirectangular.ktx2", label: "Default Moon", tags: ["lunar"] }
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
skyboxes: {
|
|
280
|
+
category: "environment",
|
|
281
|
+
basePath: "/skybox",
|
|
282
|
+
defaultSkybox: "001",
|
|
283
|
+
items: [
|
|
284
|
+
{ id: "001", label: "Deep Space", tags: ["space"] }
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Reload configuration (force refresh)
|
|
292
|
+
* @param {string} configUrl - Override config URL
|
|
293
|
+
* @returns {Promise<TextureConfig>} Fresh configuration object
|
|
294
|
+
*/
|
|
295
|
+
export async function reloadTextureConfig(configUrl) {
|
|
296
|
+
cachedConfig = null;
|
|
297
|
+
configPromise = null;
|
|
298
|
+
return loadTextureConfig(configUrl || lastConfigUrl || undefined, false);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get configuration metadata
|
|
302
|
+
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
303
|
+
* @returns {Promise<TextureConfigMetadata>} Configuration metadata
|
|
304
|
+
*/
|
|
305
|
+
export async function getConfigMetadata(config) {
|
|
306
|
+
const textureConfig = await resolveConfig(config);
|
|
307
|
+
return {
|
|
308
|
+
version: textureConfig.version,
|
|
309
|
+
lastUpdated: textureConfig.lastUpdated,
|
|
310
|
+
categories: Object.keys(textureConfig.textures),
|
|
311
|
+
totalTextures: Object.values(textureConfig.textures).reduce(
|
|
312
|
+
// Only listed categories are values; a missing one is a key that is not there.
|
|
313
|
+
(sum, cat) => {
|
|
314
|
+
const items = cat.items;
|
|
315
|
+
return sum + (items ? items.length : 0);
|
|
316
|
+
}, 0)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
function getLightFresnel({ rimHex = 0x0088ff, facingHex = 0x000000 } = {}) {
|
|
3
|
+
const uniforms = {
|
|
4
|
+
color1: { value: new THREE.Color(rimHex) },
|
|
5
|
+
color2: { value: new THREE.Color(facingHex) },
|
|
6
|
+
fresnelBias: { value: 0.1 },
|
|
7
|
+
fresnelScale: { value: 1.0 },
|
|
8
|
+
fresnelPower: { value: 4.0 },
|
|
9
|
+
};
|
|
10
|
+
const vs = `
|
|
11
|
+
uniform float fresnelBias;
|
|
12
|
+
uniform float fresnelScale;
|
|
13
|
+
uniform float fresnelPower;
|
|
14
|
+
|
|
15
|
+
varying float vReflectionFactor;
|
|
16
|
+
|
|
17
|
+
void main() {
|
|
18
|
+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
19
|
+
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
|
|
20
|
+
|
|
21
|
+
// Transform normal to world space
|
|
22
|
+
vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
|
|
23
|
+
|
|
24
|
+
// Proper view direction (from surface point towards camera)
|
|
25
|
+
vec3 viewDir = normalize( cameraPosition - worldPosition.xyz );
|
|
26
|
+
|
|
27
|
+
// Classic rim: stronger at grazing angles
|
|
28
|
+
float ndotv = max( dot( worldNormal, viewDir ), 0.0 );
|
|
29
|
+
vReflectionFactor = fresnelBias + fresnelScale * pow( 1.0 - ndotv, fresnelPower );
|
|
30
|
+
|
|
31
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const fs = `
|
|
35
|
+
uniform vec3 color1;
|
|
36
|
+
uniform vec3 color2;
|
|
37
|
+
|
|
38
|
+
varying float vReflectionFactor;
|
|
39
|
+
|
|
40
|
+
void main() {
|
|
41
|
+
float f = clamp( vReflectionFactor, 0.0, 1.0 );
|
|
42
|
+
gl_FragColor = vec4(mix(color2, color1, vec3(f)), f);
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
const lightFresnel = new THREE.ShaderMaterial({
|
|
46
|
+
uniforms: uniforms,
|
|
47
|
+
vertexShader: vs,
|
|
48
|
+
fragmentShader: fs,
|
|
49
|
+
transparent: true,
|
|
50
|
+
blending: THREE.CustomBlending,
|
|
51
|
+
blendSrc: THREE.OneFactor,
|
|
52
|
+
blendDst: THREE.OneFactor,
|
|
53
|
+
blendEquation: THREE.AddEquation,
|
|
54
|
+
depthWrite: false,
|
|
55
|
+
depthTest: true,
|
|
56
|
+
depthFunc: THREE.LessDepth,
|
|
57
|
+
side: THREE.FrontSide,
|
|
58
|
+
toneMapped: false,
|
|
59
|
+
// wireframe: true,
|
|
60
|
+
});
|
|
61
|
+
return lightFresnel;
|
|
62
|
+
}
|
|
63
|
+
export { getLightFresnel };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Ensure a scene has the default hemisphere + ambient lights. Idempotent PER SCENE.
|
|
4
|
+
*
|
|
5
|
+
* A multi-orbiter realm renders N independent scenes through one renderer. A module-level
|
|
6
|
+
* singleton light is wrong twice over — it's only added to the FIRST scene, and a THREE light can
|
|
7
|
+
* live in just one scene (adding it to a second removes it from the first), so scenes 2..N rendered
|
|
8
|
+
* DARK ("only one entangled world"). So create + add the lights PER scene, keyed by name so a
|
|
9
|
+
* re-call on the same scene is a no-op. Single-orbiter (one scene) is unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureDefaultLights(scene: THREE.Scene): {
|
|
12
|
+
hemi: THREE.HemisphereLight;
|
|
13
|
+
ambient: THREE.AmbientLight;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Apply a lighting patch to ONE scene's default lights.
|
|
17
|
+
*
|
|
18
|
+
* The scene is required. This used to address module-global light refs pointing at whichever scene
|
|
19
|
+
* was ensured last, so on a page rendering several scenes every lighting update landed on one of
|
|
20
|
+
* them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
|
|
21
|
+
*
|
|
22
|
+
* Every field is optional; an omitted one leaves that part of the lighting as it is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateDefaultLights(scene: THREE.Scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere }?: {
|
|
25
|
+
/** ambient light intensity (clamped at 0) */
|
|
26
|
+
ambientIntensity?: number;
|
|
27
|
+
/** hemisphere light intensity (clamped at 0) */
|
|
28
|
+
diffuseIntensity?: number;
|
|
29
|
+
/** hemisphere light colour */
|
|
30
|
+
lightColor?: THREE.Color | number | string;
|
|
31
|
+
/** hemisphere light direction */
|
|
32
|
+
lightDirection?: {
|
|
33
|
+
x: number;
|
|
34
|
+
y?: number;
|
|
35
|
+
z?: number;
|
|
36
|
+
};
|
|
37
|
+
/** switch the hemisphere light on or off */
|
|
38
|
+
hemisphere?: boolean;
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* Switch a scene's default hemisphere light on or off (the ambient light is untouched). Hosts turn it
|
|
42
|
+
* off when a directional light — the star — lights the scene, so night sides can read.
|
|
43
|
+
*/
|
|
44
|
+
export declare function setHemisphereEnabled(scene: THREE.Scene, on: boolean): void;
|
|
45
|
+
export declare function addDefaultLights(scene: THREE.Scene): {
|
|
46
|
+
hemi: THREE.HemisphereLight;
|
|
47
|
+
ambient: THREE.AmbientLight;
|
|
48
|
+
};
|