entangled-worlds-orbiters-shared 1.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 +75 -0
- package/package.json +49 -0
- package/public/config/textures.json +332 -0
- package/src/arrow-ui/BooleanController.js +31 -0
- package/src/arrow-ui/ColorController.js +108 -0
- package/src/arrow-ui/Controller.js +447 -0
- package/src/arrow-ui/FunctionController.js +27 -0
- package/src/arrow-ui/GUI.js +595 -0
- package/src/arrow-ui/NumberController.js +567 -0
- package/src/arrow-ui/OptionController.js +64 -0
- package/src/arrow-ui/StringController.js +41 -0
- package/src/arrow-ui/index.js +13 -0
- package/src/arrow-ui/lilGuiLoader.js +10 -0
- package/src/arrow-ui/utils/getColorFormat.js +75 -0
- package/src/arrow-ui/utils/injectStyles.js +10 -0
- package/src/arrow-ui/utils/normalizeColorString.js +29 -0
- package/src/config/index.js +8 -0
- package/src/config/textureConfig.js +299 -0
- package/src/core/index.js +3 -0
- package/src/core/lightFresnel.js +64 -0
- package/src/core/lights.js +43 -0
- package/src/index.js +8 -0
- package/src/loaders/bodyTextureLoader.js +39 -0
- package/src/loaders/cubeMapLoader.js +264 -0
- package/src/loaders/glbLoader.js +71 -0
- package/src/loaders/index.js +4 -0
- package/src/moons/index.js +3 -0
- package/src/moons/moonsGeneration.js +228 -0
- package/src/moons/moonsManager.js +64 -0
- package/src/utils/dispose.js +20 -0
- package/src/utils/index.js +3 -0
- package/src/utils/materials.js +31 -0
- package/src/world/cloudShell.js +89 -0
- package/src/world/index.js +6 -0
- package/src/world/worldGLB.js +7 -0
- package/src/world/worldManager.js +93 -0
- package/src/world/worldNormal.js +25 -0
- package/src/world/worldTexture.js +239 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import normalizeColorString from './normalizeColorString';
|
|
2
|
+
|
|
3
|
+
const STRING = {
|
|
4
|
+
isPrimitive: true,
|
|
5
|
+
match: v => typeof v === 'string',
|
|
6
|
+
fromHexString: normalizeColorString,
|
|
7
|
+
toHexString: normalizeColorString
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const INT = {
|
|
11
|
+
isPrimitive: true,
|
|
12
|
+
match: v => typeof v === 'number',
|
|
13
|
+
fromHexString: string => parseInt( string.substring( 1 ), 16 ),
|
|
14
|
+
toHexString: value => '#' + value.toString( 16 ).padStart( 6, 0 )
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ARRAY = {
|
|
18
|
+
isPrimitive: false,
|
|
19
|
+
|
|
20
|
+
// The arrow function is here to appease tree shakers like esbuild or webpack.
|
|
21
|
+
// See https://esbuild.github.io/api/#tree-shaking
|
|
22
|
+
match: v => Array.isArray( v ),
|
|
23
|
+
|
|
24
|
+
fromHexString( string, target, rgbScale = 1 ) {
|
|
25
|
+
|
|
26
|
+
const int = INT.fromHexString( string );
|
|
27
|
+
|
|
28
|
+
target[ 0 ] = ( int >> 16 & 255 ) / 255 * rgbScale;
|
|
29
|
+
target[ 1 ] = ( int >> 8 & 255 ) / 255 * rgbScale;
|
|
30
|
+
target[ 2 ] = ( int & 255 ) / 255 * rgbScale;
|
|
31
|
+
|
|
32
|
+
},
|
|
33
|
+
toHexString( [ r, g, b ], rgbScale = 1 ) {
|
|
34
|
+
|
|
35
|
+
rgbScale = 255 / rgbScale;
|
|
36
|
+
|
|
37
|
+
const int = ( r * rgbScale ) << 16 ^
|
|
38
|
+
( g * rgbScale ) << 8 ^
|
|
39
|
+
( b * rgbScale ) << 0;
|
|
40
|
+
|
|
41
|
+
return INT.toHexString( int );
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const OBJECT = {
|
|
47
|
+
isPrimitive: false,
|
|
48
|
+
match: v => Object( v ) === v,
|
|
49
|
+
fromHexString( string, target, rgbScale = 1 ) {
|
|
50
|
+
|
|
51
|
+
const int = INT.fromHexString( string );
|
|
52
|
+
|
|
53
|
+
target.r = ( int >> 16 & 255 ) / 255 * rgbScale;
|
|
54
|
+
target.g = ( int >> 8 & 255 ) / 255 * rgbScale;
|
|
55
|
+
target.b = ( int & 255 ) / 255 * rgbScale;
|
|
56
|
+
|
|
57
|
+
},
|
|
58
|
+
toHexString( { r, g, b }, rgbScale = 1 ) {
|
|
59
|
+
|
|
60
|
+
rgbScale = 255 / rgbScale;
|
|
61
|
+
|
|
62
|
+
const int = ( r * rgbScale ) << 16 ^
|
|
63
|
+
( g * rgbScale ) << 8 ^
|
|
64
|
+
( b * rgbScale ) << 0;
|
|
65
|
+
|
|
66
|
+
return INT.toHexString( int );
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const FORMATS = [ STRING, INT, ARRAY, OBJECT ];
|
|
72
|
+
|
|
73
|
+
export default function( value ) {
|
|
74
|
+
return FORMATS.find( format => format.match( value ) );
|
|
75
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function( cssContent ) {
|
|
2
|
+
const injected = document.createElement( 'style' );
|
|
3
|
+
injected.innerHTML = cssContent;
|
|
4
|
+
const before = document.querySelector( 'head link[rel=stylesheet], head style' );
|
|
5
|
+
if ( before ) {
|
|
6
|
+
document.head.insertBefore( injected, before );
|
|
7
|
+
} else {
|
|
8
|
+
document.head.appendChild( injected );
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable no-cond-assign */
|
|
2
|
+
|
|
3
|
+
export default function( string ) {
|
|
4
|
+
|
|
5
|
+
let match, result;
|
|
6
|
+
|
|
7
|
+
if ( match = string.match( /(#|0x)?([a-f0-9]{6})/i ) ) {
|
|
8
|
+
|
|
9
|
+
result = match[ 2 ];
|
|
10
|
+
|
|
11
|
+
} else if ( match = string.match( /rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/ ) ) {
|
|
12
|
+
|
|
13
|
+
result = parseInt( match[ 1 ] ).toString( 16 ).padStart( 2, 0 )
|
|
14
|
+
+ parseInt( match[ 2 ] ).toString( 16 ).padStart( 2, 0 )
|
|
15
|
+
+ parseInt( match[ 3 ] ).toString( 16 ).padStart( 2, 0 );
|
|
16
|
+
|
|
17
|
+
} else if ( match = string.match( /^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i ) ) {
|
|
18
|
+
|
|
19
|
+
result = match[ 1 ] + match[ 1 ] + match[ 2 ] + match[ 2 ] + match[ 3 ] + match[ 3 ];
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ( result ) {
|
|
24
|
+
return '#' + result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
|
|
29
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texture Configuration Loader
|
|
3
|
+
* Loads texture definitions from local config file or remote endpoint
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG_URL = '/config/textures.json';
|
|
7
|
+
const FALLBACK_CONFIG_URL = 'https://api.entangled-worlds.com/config/textures.json'; // Future endpoint
|
|
8
|
+
|
|
9
|
+
let cachedConfig = null;
|
|
10
|
+
let configPromise = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load texture configuration from local file or remote endpoint
|
|
14
|
+
* @param {string} configUrl - Override default config URL
|
|
15
|
+
* @param {boolean} useCache - Whether to use cached config if available
|
|
16
|
+
* @returns {Promise<Object>} Texture configuration object
|
|
17
|
+
*/
|
|
18
|
+
export async function loadTextureConfig(configUrl = DEFAULT_CONFIG_URL, useCache = true) {
|
|
19
|
+
if (useCache && cachedConfig) {
|
|
20
|
+
console.log('[TextureConfig] Using cached config');
|
|
21
|
+
return cachedConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (configPromise) {
|
|
25
|
+
console.log('[TextureConfig] Using in-flight request');
|
|
26
|
+
return configPromise;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
configPromise = (async () => {
|
|
30
|
+
try {
|
|
31
|
+
console.log('[TextureConfig] Loading from:', configUrl);
|
|
32
|
+
const response = await fetch(configUrl);
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const config = await response.json();
|
|
39
|
+
|
|
40
|
+
// Validate configuration structure
|
|
41
|
+
if (!config.textures || typeof config.textures !== 'object') {
|
|
42
|
+
throw new Error('Invalid configuration: missing textures object');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cachedConfig = config;
|
|
46
|
+
console.log('[TextureConfig] Loaded successfully:', {
|
|
47
|
+
version: config.version,
|
|
48
|
+
categories: Object.keys(config.textures),
|
|
49
|
+
lastUpdated: config.lastUpdated
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return config;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('[TextureConfig] Failed to load config:', error);
|
|
55
|
+
|
|
56
|
+
// If primary URL fails and it's not already the fallback, try fallback
|
|
57
|
+
if (configUrl !== FALLBACK_CONFIG_URL) {
|
|
58
|
+
console.log('[TextureConfig] Trying fallback endpoint...');
|
|
59
|
+
try {
|
|
60
|
+
return await loadTextureConfig(FALLBACK_CONFIG_URL, false);
|
|
61
|
+
} catch (fallbackError) {
|
|
62
|
+
console.error('[TextureConfig] Fallback also failed:', fallbackError);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Return minimal fallback configuration
|
|
67
|
+
console.warn('[TextureConfig] Using hardcoded fallback config');
|
|
68
|
+
return getHardcodedFallback();
|
|
69
|
+
} finally {
|
|
70
|
+
configPromise = null;
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
return configPromise;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get texture options for a specific category (clouds, moons, etc.)
|
|
79
|
+
* @param {string} category - Texture category (clouds, moons, skyboxes)
|
|
80
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
81
|
+
* @returns {Promise<Object>} Options object suitable for lil-gui dropdowns
|
|
82
|
+
*/
|
|
83
|
+
export async function getTextureOptions(category, config = null) {
|
|
84
|
+
const textureConfig = config || await loadTextureConfig();
|
|
85
|
+
const categoryConfig = textureConfig.textures[category];
|
|
86
|
+
|
|
87
|
+
if (!categoryConfig) {
|
|
88
|
+
console.warn(`[TextureConfig] Category '${category}' not found`);
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const options = {};
|
|
93
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
94
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
95
|
+
|
|
96
|
+
categoryConfig.items.forEach(item => {
|
|
97
|
+
const key = item.label || item.id;
|
|
98
|
+
const relativePath = `${baseUrl}${categoryPath}/${item.filename || item.id}`;
|
|
99
|
+
options[key] = relativePath;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return options;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get default texture for a category
|
|
107
|
+
* @param {string} category - Texture category
|
|
108
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
109
|
+
* @returns {Promise<string>} Default texture path
|
|
110
|
+
*/
|
|
111
|
+
export async function getDefaultTexture(category, config = null) {
|
|
112
|
+
const textureConfig = config || await loadTextureConfig();
|
|
113
|
+
const categoryConfig = textureConfig.textures[category];
|
|
114
|
+
|
|
115
|
+
if (!categoryConfig) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
120
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
121
|
+
const defaultFilename = categoryConfig.defaultTexture;
|
|
122
|
+
|
|
123
|
+
if (defaultFilename) {
|
|
124
|
+
return `${baseUrl}${categoryPath}/${defaultFilename}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback to first item
|
|
128
|
+
if (categoryConfig.items && categoryConfig.items.length > 0) {
|
|
129
|
+
const firstItem = categoryConfig.items[0];
|
|
130
|
+
return `${baseUrl}${categoryPath}/${firstItem.filename || firstItem.id}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get texture by ID within a category
|
|
138
|
+
* @param {string} category - Texture category
|
|
139
|
+
* @param {string} textureId - Texture ID
|
|
140
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
141
|
+
* @returns {Promise<Object|null>} Texture definition object
|
|
142
|
+
*/
|
|
143
|
+
export async function getTextureById(category, textureId, config = null) {
|
|
144
|
+
const textureConfig = config || await loadTextureConfig();
|
|
145
|
+
const categoryConfig = textureConfig.textures[category];
|
|
146
|
+
|
|
147
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return categoryConfig.items.find(item => item.id === textureId) || null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get full URL for a texture
|
|
156
|
+
* @param {string} category - Texture category
|
|
157
|
+
* @param {string} textureId - Texture ID
|
|
158
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
159
|
+
* @returns {Promise<string|null>} Full texture URL
|
|
160
|
+
*/
|
|
161
|
+
export async function getTextureUrl(category, textureId, config = null) {
|
|
162
|
+
const texture = await getTextureById(category, textureId, config);
|
|
163
|
+
if (!texture) return null;
|
|
164
|
+
|
|
165
|
+
const textureConfig = config || await loadTextureConfig();
|
|
166
|
+
const categoryConfig = textureConfig.textures[category];
|
|
167
|
+
const baseUrl = textureConfig.baseUrl || '/default';
|
|
168
|
+
const categoryPath = categoryConfig.basePath || `/${category}`;
|
|
169
|
+
|
|
170
|
+
return `${baseUrl}${categoryPath}/${texture.filename || texture.id}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Search textures by tags
|
|
175
|
+
* @param {string} category - Texture category
|
|
176
|
+
* @param {string[]} tags - Tags to search for
|
|
177
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
178
|
+
* @returns {Promise<Object[]>} Array of matching texture definitions
|
|
179
|
+
*/
|
|
180
|
+
export async function searchTexturesByTags(category, tags, config = null) {
|
|
181
|
+
const textureConfig = config || await loadTextureConfig();
|
|
182
|
+
const categoryConfig = textureConfig.textures[category];
|
|
183
|
+
|
|
184
|
+
if (!categoryConfig || !categoryConfig.items) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return categoryConfig.items.filter(item =>
|
|
189
|
+
item.tags && tags.some(tag => item.tags.includes(tag))
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get skybox options formatted for GUI dropdowns
|
|
195
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
196
|
+
* @returns {Promise<Object>} Options object with labels and IDs
|
|
197
|
+
*/
|
|
198
|
+
export async function getSkyboxOptions(config = null) {
|
|
199
|
+
const textureConfig = config || await loadTextureConfig();
|
|
200
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
201
|
+
|
|
202
|
+
if (!skyboxConfig) {
|
|
203
|
+
console.warn('[TextureConfig] Skyboxes category not found');
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const options = {};
|
|
208
|
+
|
|
209
|
+
skyboxConfig.items.forEach(item => {
|
|
210
|
+
const label = item.label || item.id;
|
|
211
|
+
const id = item.id;
|
|
212
|
+
options[label] = id;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return options;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get default skybox ID
|
|
220
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
221
|
+
* @returns {Promise<string>} Default skybox ID
|
|
222
|
+
*/
|
|
223
|
+
export async function getDefaultSkybox(config = null) {
|
|
224
|
+
const textureConfig = config || await loadTextureConfig();
|
|
225
|
+
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
226
|
+
|
|
227
|
+
if (!skyboxConfig) {
|
|
228
|
+
return '001'; // fallback
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return skyboxConfig.defaultSkybox || '001';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Hardcoded fallback configuration (minimal)
|
|
236
|
+
* Used when remote config loading fails
|
|
237
|
+
*/
|
|
238
|
+
function getHardcodedFallback() {
|
|
239
|
+
return {
|
|
240
|
+
version: "1.0-fallback",
|
|
241
|
+
lastUpdated: new Date().toISOString(),
|
|
242
|
+
baseUrl: "/default",
|
|
243
|
+
textures: {
|
|
244
|
+
clouds: {
|
|
245
|
+
category: "atmospheric",
|
|
246
|
+
basePath: "/clouds",
|
|
247
|
+
defaultTexture: "04-clouds.ktx2",
|
|
248
|
+
items: [
|
|
249
|
+
{ id: "clouds-1", filename: "04-clouds.ktx2", label: "Clouds", tags: ["natural"] },
|
|
250
|
+
{ id: "entangled-1", filename: "06-entangled.ktx2", label: "Entangled", tags: ["quantum"] }
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
moons: {
|
|
254
|
+
category: "celestial",
|
|
255
|
+
basePath: "/textures/moons",
|
|
256
|
+
defaultTexture: "moon-equirectangular.ktx2",
|
|
257
|
+
items: [
|
|
258
|
+
{ id: "default-moon", filename: "moon-equirectangular.ktx2", label: "Default Moon", tags: ["lunar"] }
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
skyboxes: {
|
|
262
|
+
category: "environment",
|
|
263
|
+
basePath: "/skybox",
|
|
264
|
+
defaultSkybox: "001",
|
|
265
|
+
items: [
|
|
266
|
+
{ id: "001", label: "Deep Space", tags: ["space"] }
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Reload configuration (force refresh)
|
|
275
|
+
* @param {string} configUrl - Override config URL
|
|
276
|
+
* @returns {Promise<Object>} Fresh configuration object
|
|
277
|
+
*/
|
|
278
|
+
export async function reloadTextureConfig(configUrl = DEFAULT_CONFIG_URL) {
|
|
279
|
+
cachedConfig = null;
|
|
280
|
+
configPromise = null;
|
|
281
|
+
return loadTextureConfig(configUrl, false);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get configuration metadata
|
|
286
|
+
* @param {Object} config - Optional pre-loaded config object
|
|
287
|
+
* @returns {Promise<Object>} Configuration metadata
|
|
288
|
+
*/
|
|
289
|
+
export async function getConfigMetadata(config = null) {
|
|
290
|
+
const textureConfig = config || await loadTextureConfig();
|
|
291
|
+
return {
|
|
292
|
+
version: textureConfig.version,
|
|
293
|
+
lastUpdated: textureConfig.lastUpdated,
|
|
294
|
+
categories: Object.keys(textureConfig.textures),
|
|
295
|
+
totalTextures: Object.values(textureConfig.textures).reduce(
|
|
296
|
+
(sum, cat) => sum + (cat.items ? cat.items.length : 0), 0
|
|
297
|
+
)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
function getLightFresnel({rimHex = 0x0088ff, facingHex = 0x000000} = {}) {
|
|
4
|
+
const uniforms = {
|
|
5
|
+
color1: { value: new THREE.Color(rimHex) },
|
|
6
|
+
color2: { value: new THREE.Color(facingHex) },
|
|
7
|
+
fresnelBias: { value: 0.1 },
|
|
8
|
+
fresnelScale: { value: 1.0 },
|
|
9
|
+
fresnelPower: { value: 4.0 },
|
|
10
|
+
};
|
|
11
|
+
const vs = `
|
|
12
|
+
uniform float fresnelBias;
|
|
13
|
+
uniform float fresnelScale;
|
|
14
|
+
uniform float fresnelPower;
|
|
15
|
+
|
|
16
|
+
varying float vReflectionFactor;
|
|
17
|
+
|
|
18
|
+
void main() {
|
|
19
|
+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
20
|
+
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
|
|
21
|
+
|
|
22
|
+
// Transform normal to world space
|
|
23
|
+
vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
|
|
24
|
+
|
|
25
|
+
// Proper view direction (from surface point towards camera)
|
|
26
|
+
vec3 viewDir = normalize( cameraPosition - worldPosition.xyz );
|
|
27
|
+
|
|
28
|
+
// Classic rim: stronger at grazing angles
|
|
29
|
+
float ndotv = max( dot( worldNormal, viewDir ), 0.0 );
|
|
30
|
+
vReflectionFactor = fresnelBias + fresnelScale * pow( 1.0 - ndotv, fresnelPower );
|
|
31
|
+
|
|
32
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
const fs = `
|
|
36
|
+
uniform vec3 color1;
|
|
37
|
+
uniform vec3 color2;
|
|
38
|
+
|
|
39
|
+
varying float vReflectionFactor;
|
|
40
|
+
|
|
41
|
+
void main() {
|
|
42
|
+
float f = clamp( vReflectionFactor, 0.0, 1.0 );
|
|
43
|
+
gl_FragColor = vec4(mix(color2, color1, vec3(f)), f);
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
const lightFresnel = new THREE.ShaderMaterial({
|
|
47
|
+
uniforms: uniforms,
|
|
48
|
+
vertexShader: vs,
|
|
49
|
+
fragmentShader: fs,
|
|
50
|
+
transparent: true,
|
|
51
|
+
blending: THREE.CustomBlending,
|
|
52
|
+
blendSrc: THREE.OneFactor,
|
|
53
|
+
blendDst: THREE.OneFactor,
|
|
54
|
+
blendEquation: THREE.AddEquation,
|
|
55
|
+
depthWrite: false,
|
|
56
|
+
depthTest: true,
|
|
57
|
+
depthFunc: THREE.LessDepth,
|
|
58
|
+
side: THREE.FrontSide,
|
|
59
|
+
toneMapped: false,
|
|
60
|
+
// wireframe: true,
|
|
61
|
+
});
|
|
62
|
+
return lightFresnel;
|
|
63
|
+
}
|
|
64
|
+
export { getLightFresnel };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
let hemiRef = null;
|
|
4
|
+
let ambientRef = null;
|
|
5
|
+
|
|
6
|
+
export function ensureDefaultLights(scene) {
|
|
7
|
+
if (!hemiRef) {
|
|
8
|
+
hemiRef = new THREE.HemisphereLight(0xffffff, 0xffffff, 1.0);
|
|
9
|
+
hemiRef.position.set(1, 1, 1);
|
|
10
|
+
hemiRef.name = 'ew_HemisphereLight';
|
|
11
|
+
scene.add(hemiRef);
|
|
12
|
+
}
|
|
13
|
+
if (!ambientRef) {
|
|
14
|
+
ambientRef = new THREE.AmbientLight(0xffffff, 1.0);
|
|
15
|
+
ambientRef.name = 'ew_AmbientLight';
|
|
16
|
+
scene.add(ambientRef);
|
|
17
|
+
}
|
|
18
|
+
return { hemi: hemiRef, ambient: ambientRef };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function updateDefaultLights({ ambientIntensity, diffuseIntensity, lightColor, lightDirection } = {}) {
|
|
22
|
+
if (typeof ambientIntensity === 'number' && ambientRef) ambientRef.intensity = Math.max(0, ambientIntensity);
|
|
23
|
+
if (hemiRef) {
|
|
24
|
+
if (typeof diffuseIntensity === 'number') hemiRef.intensity = Math.max(0, diffuseIntensity);
|
|
25
|
+
if (lightColor != null) {
|
|
26
|
+
if (lightColor instanceof THREE.Color) {
|
|
27
|
+
hemiRef.color.copy(lightColor);
|
|
28
|
+
} else if (typeof lightColor === 'number') {
|
|
29
|
+
hemiRef.color.setHex(lightColor);
|
|
30
|
+
} else if (typeof lightColor === 'string') {
|
|
31
|
+
hemiRef.color.set(lightColor);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (lightDirection && typeof lightDirection.x === 'number') {
|
|
35
|
+
hemiRef.position.set(lightDirection.x, lightDirection.y ?? 0, lightDirection.z ?? 0).normalize();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Back-compat helper
|
|
41
|
+
export function addDefaultLights(scene) {
|
|
42
|
+
return ensureDefaultLights(scene);
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Main exports for entangled-worlds-orbiters-shared library
|
|
2
|
+
export * from './world/index.js';
|
|
3
|
+
export * from './moons/index.js';
|
|
4
|
+
export * from './arrow-ui/index.js';
|
|
5
|
+
export * from './loaders/index.js';
|
|
6
|
+
export * from './core/index.js';
|
|
7
|
+
export * from './config/index.js';
|
|
8
|
+
export * from './utils/index.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
3
|
+
|
|
4
|
+
const textureLoader = new THREE.TextureLoader();
|
|
5
|
+
|
|
6
|
+
// Shared singleton accessor (align with cubeMapLoader)
|
|
7
|
+
function getSharedKTX2Loader() {
|
|
8
|
+
if (window.__EW_KTX2_LOADER) return window.__EW_KTX2_LOADER;
|
|
9
|
+
const renderer = window.__EW_RENDERER;
|
|
10
|
+
const loader = new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
|
|
11
|
+
window.__EW_KTX2_LOADER = loader;
|
|
12
|
+
return loader;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function loadTexture(url, { signal } = {}) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
if (url && url.endsWith('.ktx2')) {
|
|
18
|
+
const ktx2 = getSharedKTX2Loader();
|
|
19
|
+
const onAbort = () => reject(new Error('aborted'));
|
|
20
|
+
if (signal) signal.addEventListener('abort', onAbort, { once: true });
|
|
21
|
+
ktx2.load(
|
|
22
|
+
url,
|
|
23
|
+
(tex) => { if (signal) signal.removeEventListener('abort', onAbort); resolve(tex); },
|
|
24
|
+
undefined,
|
|
25
|
+
(err) => { if (signal) signal.removeEventListener('abort', onAbort); reject(err); }
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const tex = textureLoader.load(
|
|
30
|
+
url,
|
|
31
|
+
() => resolve(tex),
|
|
32
|
+
undefined,
|
|
33
|
+
(err) => reject(err)
|
|
34
|
+
);
|
|
35
|
+
if (signal) {
|
|
36
|
+
signal.addEventListener('abort', () => reject(new Error('aborted')));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|