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
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
 
@@ -145,10 +146,10 @@ Effects:
145
146
 
146
147
  ## Peer Dependencies
147
148
 
148
- This library requires Three.js and three-stdlib as peer dependencies:
149
+ This library requires Three.js as a peer dependency:
149
150
 
150
151
  ```bash
151
- npm install three three-stdlib
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,2 @@
1
+ export { ensureDefaultLights, updateDefaultLights, setHemisphereEnabled } from './lights.js';
2
+ export { getLightFresnel } from './lightFresnel.js';
@@ -0,0 +1,3 @@
1
+ // Core rendering exports
2
+ export { ensureDefaultLights, updateDefaultLights, setHemisphereEnabled } from './lights.js';
3
+ export { getLightFresnel } from './lightFresnel.js';
@@ -0,0 +1,6 @@
1
+ import * as THREE from "three";
2
+ declare function getLightFresnel({ rimHex, facingHex }?: {
3
+ rimHex?: number;
4
+ facingHex?: number;
5
+ }): THREE.ShaderMaterial;
6
+ export { getLightFresnel };
@@ -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
+ };