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.
- package/README.md +3 -2
- 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 +0 -2
- package/dist/{types/loaders → loaders}/ktx2.d.ts +4 -6
- 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/{types/render → render}/frameBudget.d.ts +18 -17
- 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/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 +0 -2
- 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 +29 -39
- 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} +21 -21
- package/src/loaders/{bodyTextureLoader.js → bodyTextureLoader.ts} +10 -7
- package/src/loaders/{cubeMapLoader.js → cubeMapLoader.ts} +83 -49
- package/src/loaders/{glbLoader.js → glbLoader.ts} +41 -18
- 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} +45 -32
- 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/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} +19 -12
- package/src/world/index.ts +7 -0
- package/src/world/worldGLB.ts +41 -0
- package/src/world/{worldManager.js → worldManager.ts} +67 -32
- package/src/world/{worldNormal.js → worldNormal.ts} +25 -20
- package/src/world/{worldTexture.js → worldTexture.ts} +70 -51
- package/dist/types/config/env.d.ts +0 -12
- package/dist/types/config/index.d.ts +0 -2
- package/dist/types/config/textureConfig.d.ts +0 -121
- package/dist/types/core/index.d.ts +0 -2
- package/dist/types/core/lightFresnel.d.ts +0 -5
- package/dist/types/core/lights.d.ts +0 -58
- package/dist/types/index.d.ts +0 -6
- package/dist/types/loaders/bodyTextureLoader.d.ts +0 -12
- package/dist/types/loaders/cubeMapLoader.d.ts +0 -70
- package/dist/types/loaders/glbLoader.d.ts +0 -24
- package/dist/types/loaders/index.d.ts +0 -5
- package/dist/types/moons/index.d.ts +0 -2
- package/dist/types/moons/moonsGeneration.d.ts +0 -72
- package/dist/types/moons/moonsManager.d.ts +0 -27
- package/dist/types/render/index.d.ts +0 -2
- package/dist/types/render/viewportCompositor.d.ts +0 -58
- package/dist/types/utils/dispose.d.ts +0 -2
- package/dist/types/utils/index.d.ts +0 -2
- package/dist/types/utils/materials.d.ts +0 -2
- package/dist/types/world/cloudShell.d.ts +0 -25
- package/dist/types/world/index.d.ts +0 -6
- package/dist/types/world/worldGLB.d.ts +0 -20
- package/dist/types/world/worldManager.d.ts +0 -63
- package/dist/types/world/worldNormal.d.ts +0 -43
- package/dist/types/world/worldTexture.d.ts +0 -80
- package/src/config/env.js +0 -38
- package/src/utils/dispose.js +0 -39
- package/src/utils/materials.js +0 -31
- package/src/world/worldGLB.js +0 -19
- /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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { loadTexture } from '../loaders/bodyTextureLoader.js';
|
|
3
|
+
import { applyFlagsToMaterial } from '../utils/materials.js';
|
|
4
|
+
import { getLightFresnel } from '../core/lightFresnel.js';
|
|
5
|
+
import { createCloudShell } from './cloudShell.js';
|
|
6
|
+
import { resolvePublicUrl } from '../config/env.js';
|
|
7
|
+
// Debug tag so you can verify the loaded file version in console
|
|
8
|
+
export const WORLD_TEXTURE_DEBUG_TAG = 'WT:vFlipGeom-r3@2025-10-28T17:22:00Z';
|
|
9
|
+
// Enable debug if query/localStorage says so; expose tag
|
|
10
|
+
if (typeof window !== 'undefined') {
|
|
11
|
+
const q = (window.location && window.location.search) || '';
|
|
12
|
+
const qsDebug = /(?:[?&])debugTextures=1(?:&|$)/.test(q);
|
|
13
|
+
const lsDebug = (window.localStorage && window.localStorage.DEBUG_TEXTURE_FLIP === '1') || false;
|
|
14
|
+
if (!window.DEBUG_TEXTURE_FLIP && (qsDebug || lsDebug)) {
|
|
15
|
+
window.DEBUG_TEXTURE_FLIP = true;
|
|
16
|
+
}
|
|
17
|
+
window.EW_WT_DEBUG_TAG = WORLD_TEXTURE_DEBUG_TAG;
|
|
18
|
+
if (window.DEBUG_TEXTURE_FLIP) {
|
|
19
|
+
console.info('[worldTexture] loaded', WORLD_TEXTURE_DEBUG_TAG);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const WT_LOGGED_FIRST_CALL = false;
|
|
23
|
+
export const EARTH_RADIUS_UNITS = 0.5; // scene units
|
|
24
|
+
/**
|
|
25
|
+
* Simplified detection: width = height -> rectangular, width = 2×height -> equirectangular
|
|
26
|
+
*/
|
|
27
|
+
function isLikelyEquirectangular(img) {
|
|
28
|
+
const aspectRatio = img.width / img.height;
|
|
29
|
+
// Check for 2:1 aspect ratio (equirectangular)
|
|
30
|
+
const is2to1 = Math.abs(aspectRatio - 2.0) < 0.1;
|
|
31
|
+
// Check for 1:1 aspect ratio (square -> treat as rectangular)
|
|
32
|
+
const is1to1 = Math.abs(aspectRatio - 1.0) < 0.1;
|
|
33
|
+
if (is1to1) {
|
|
34
|
+
return false; // Square images are rectangular
|
|
35
|
+
}
|
|
36
|
+
if (is2to1) {
|
|
37
|
+
return true; // 2:1 images are equirectangular
|
|
38
|
+
}
|
|
39
|
+
// For other aspect ratios, default to rectangular
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The textured world sphere. Every option is optional. The offset, repeat, rotation, flip and sphere
|
|
44
|
+
* geometry options are read only when they have their type; the rest are used as given (`glow`,
|
|
45
|
+
* `cloudsEnabled` and `mirrorX` by truthiness).
|
|
46
|
+
*
|
|
47
|
+
* - `renderer`: REQUIRED for a KTX2 texture. `flags`: material flags (`forceOpaque`, `wireframe`).
|
|
48
|
+
* - `uOffset`: horizontal texture offset, 0..1 (wins over `textureOffsetDeg`, in degrees).
|
|
49
|
+
* - `mirrorDuplicateX`: explicit override of the equirectangular detection
|
|
50
|
+
* (`autoDetectEquirectangular` true, `forceEquirectangular` / `forceRectangular` false).
|
|
51
|
+
* - `wrapS` ('repeat') / `wrapT` ('clamp'): `'clamp'`, `'repeat'` or `'mirror'`.
|
|
52
|
+
* - `center` ([0.5, 0.5]): texture rotation centre, `[u, v]`.
|
|
53
|
+
* - `materialRoughness` / `materialMetalness`: accepted from saved worlds; no longer change the look.
|
|
54
|
+
* - Defaults: `glow` true, `glowRimHex` 0x66ccff, `glowFacingHex` 0x000000, `glowScale` 1.18,
|
|
55
|
+
* `glowBias` 0.02, `glowFresnelScale` 1, `glowPower` 3.5, `cloudsEnabled` false, `cloudsColor`
|
|
56
|
+
* 0xffffff, `cloudsOpacity` 0.6, `cloudsSpeed` 0.015.
|
|
57
|
+
*/
|
|
58
|
+
export async function makeSphere({ textureUrl, renderer, flags, uOffset, textureOffsetDeg, mirrorX, mirrorDuplicateX, // optional, still supported for explicit override
|
|
59
|
+
autoDetectEquirectangular = true, forceEquirectangular = false, forceRectangular = false,
|
|
60
|
+
// Wrapping params
|
|
61
|
+
wrapS = 'repeat', wrapT = 'clamp', repeatX, repeatY, rotationDeg, center = [0.5, 0.5], flipY,
|
|
62
|
+
// NEW: sphere geometry controls (optional)
|
|
63
|
+
sphereRadius, sphereWidthSegments, sphereHeightSegments,
|
|
64
|
+
// PBR material tuning (optional)
|
|
65
|
+
materialRoughness, materialMetalness,
|
|
66
|
+
// NEW: glow controls
|
|
67
|
+
glow = true, glowRimHex = 0x66ccff, glowFacingHex = 0x000000, glowScale = 1.18, glowBias = 0.02, glowFresnelScale = 1.0, glowPower = 3.5,
|
|
68
|
+
// texture clouds controls (simplified)
|
|
69
|
+
cloudsEnabled = false, cloudsTextureUrl = '/assets/texture/2k_earth_clouds.jpg', cloudsColor = 0xffffff, cloudsOpacity = 0.6, cloudsSpeed = 0.015, } = {}) {
|
|
70
|
+
const radius = typeof sphereRadius === 'number' ? Math.max(0.0001, sphereRadius) : EARTH_RADIUS_UNITS;
|
|
71
|
+
// `Number.isFinite` does not narrow: past it, each count is a number.
|
|
72
|
+
const widthSegs = Number.isFinite(sphereWidthSegments) ? Math.max(3, Math.floor(sphereWidthSegments)) : 64;
|
|
73
|
+
const heightSegs = Number.isFinite(sphereHeightSegments) ? Math.max(2, Math.floor(sphereHeightSegments)) : 64;
|
|
74
|
+
const geometry = new THREE.SphereGeometry(radius, widthSegs, heightSegs);
|
|
75
|
+
let meshGeometry = geometry; // may clone/flip UVs for compressed textures
|
|
76
|
+
// Diffuse-only (Lambert) at every tier: under the star's key light the physical (Standard) material
|
|
77
|
+
// cost +20% GPU on low for a barely visible sheen. `materialRoughness` /
|
|
78
|
+
// `materialMetalness` stay accepted from saved worlds but no longer change the look.
|
|
79
|
+
const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
|
|
80
|
+
const WRAP = {
|
|
81
|
+
clamp: THREE.ClampToEdgeWrapping,
|
|
82
|
+
repeat: THREE.RepeatWrapping,
|
|
83
|
+
mirror: THREE.MirroredRepeatWrapping,
|
|
84
|
+
};
|
|
85
|
+
// For compressed textures (e.g., KTX2) WebGL cannot apply UNPACK_FLIP_Y at upload.
|
|
86
|
+
// To normalize orientation across loaders, emulate vertical flip in UV sampling:
|
|
87
|
+
// v' = 1 - v => repeat.y = -repeat.y, offset.y = 1 - offset.y
|
|
88
|
+
const normalizeCompressedFlip = (t) => {
|
|
89
|
+
if (!t || !t.isCompressedTexture)
|
|
90
|
+
return;
|
|
91
|
+
const wantFlip = (typeof flipY === 'boolean') ? flipY : true; // match uncompressed default
|
|
92
|
+
if (!wantFlip)
|
|
93
|
+
return;
|
|
94
|
+
const rY = (t.repeat && typeof t.repeat.y === 'number') ? t.repeat.y : 1;
|
|
95
|
+
const oY = (t.offset && typeof t.offset.y === 'number') ? t.offset.y : 0;
|
|
96
|
+
t.repeat.y = -rY;
|
|
97
|
+
t.offset.y = 1 - oY;
|
|
98
|
+
t.needsUpdate = true;
|
|
99
|
+
};
|
|
100
|
+
// Emulate vertical flip for compressed textures (e.g., KTX2) using UV transform
|
|
101
|
+
const normalizeCompressedFlipY = (t, desired) => {
|
|
102
|
+
if (!t || !t.isCompressedTexture)
|
|
103
|
+
return;
|
|
104
|
+
const wantFlip = (typeof desired === 'boolean') ? desired : true; // default to match uncompressed loader
|
|
105
|
+
const rY = t.repeat?.y ?? 1;
|
|
106
|
+
const oY = t.offset?.y ?? 0;
|
|
107
|
+
if (wantFlip) {
|
|
108
|
+
t.repeat.y = -Math.abs(rY);
|
|
109
|
+
t.offset.y = 1 - oY;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
t.repeat.y = Math.abs(rY);
|
|
113
|
+
t.offset.y = 0;
|
|
114
|
+
}
|
|
115
|
+
t.needsUpdate = true;
|
|
116
|
+
};
|
|
117
|
+
// Flip UV.y on a cloned geometry (keeps other layers safe)
|
|
118
|
+
const flipGeometryUVY = (geom) => {
|
|
119
|
+
if (!geom || !geom.attributes || !geom.attributes.uv)
|
|
120
|
+
return 0;
|
|
121
|
+
const uv = geom.attributes.uv;
|
|
122
|
+
const arr = uv.array;
|
|
123
|
+
for (let i = 1; i < arr.length; i += 2) {
|
|
124
|
+
arr[i] = 1 - arr[i]; // v = 1 - v
|
|
125
|
+
}
|
|
126
|
+
uv.needsUpdate = true;
|
|
127
|
+
return uv.count;
|
|
128
|
+
};
|
|
129
|
+
// Attach a Fresnel glow mesh as a child of the sphere mesh
|
|
130
|
+
function createGlow(rimHex, facingHex) {
|
|
131
|
+
const fresnelMat = getLightFresnel({ rimHex, facingHex });
|
|
132
|
+
// tweak uniforms for stronger rim
|
|
133
|
+
fresnelMat.uniforms.fresnelBias.value = glowBias;
|
|
134
|
+
fresnelMat.uniforms.fresnelScale.value = glowFresnelScale;
|
|
135
|
+
fresnelMat.uniforms.fresnelPower.value = glowPower;
|
|
136
|
+
const glowGeom = geometry.clone();
|
|
137
|
+
const fresnelMesh = new THREE.Mesh(glowGeom, fresnelMat);
|
|
138
|
+
// Slightly larger to avoid z-fighting and create a halo
|
|
139
|
+
const s = Math.max(glowScale, 1.0);
|
|
140
|
+
fresnelMesh.scale.set(s, s, s);
|
|
141
|
+
fresnelMesh.renderOrder = 999; // render after base mesh
|
|
142
|
+
fresnelMesh.name = 'worldTextureGlow';
|
|
143
|
+
return fresnelMesh;
|
|
144
|
+
}
|
|
145
|
+
const chosenTexture = textureUrl; // require explicit textureUrl — no implicit default
|
|
146
|
+
if (chosenTexture) {
|
|
147
|
+
try {
|
|
148
|
+
const tex = await loadTexture(chosenTexture, { renderer });
|
|
149
|
+
tex.colorSpace = THREE.SRGBColorSpace;
|
|
150
|
+
tex.anisotropy = 8;
|
|
151
|
+
if (typeof flipY === 'boolean')
|
|
152
|
+
tex.flipY = flipY;
|
|
153
|
+
// Only enable mipmaps for non-compressed, power-of-two textures
|
|
154
|
+
// A KTX2 load is a CompressedTexture, which says so; a plain Texture has no such field.
|
|
155
|
+
const isCompressed = tex.isCompressedTexture;
|
|
156
|
+
const img = tex.image;
|
|
157
|
+
const isPowerOfTwo = img && ((img.width & (img.width - 1)) === 0) && ((img.height & (img.height - 1)) === 0);
|
|
158
|
+
tex.generateMipmaps = !isCompressed && isPowerOfTwo;
|
|
159
|
+
// Decide mapping approach
|
|
160
|
+
let shouldMirrorDuplicate = mirrorDuplicateX;
|
|
161
|
+
if (tex.image) {
|
|
162
|
+
const img = tex.image;
|
|
163
|
+
let isEquirectangular = false;
|
|
164
|
+
if (forceEquirectangular) {
|
|
165
|
+
isEquirectangular = true;
|
|
166
|
+
}
|
|
167
|
+
else if (forceRectangular) {
|
|
168
|
+
isEquirectangular = false;
|
|
169
|
+
}
|
|
170
|
+
else if (autoDetectEquirectangular && typeof mirrorDuplicateX === 'undefined') {
|
|
171
|
+
const aspectDetection = isLikelyEquirectangular(img);
|
|
172
|
+
isEquirectangular = aspectDetection && Math.abs(img.width / img.height - 2.0) < 0.05;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
isEquirectangular = !shouldMirrorDuplicate;
|
|
176
|
+
}
|
|
177
|
+
shouldMirrorDuplicate = !isEquirectangular;
|
|
178
|
+
}
|
|
179
|
+
// Default to rectangular duplicate if still undefined
|
|
180
|
+
if (typeof shouldMirrorDuplicate === 'undefined') {
|
|
181
|
+
shouldMirrorDuplicate = true;
|
|
182
|
+
}
|
|
183
|
+
// Seam alignment (offset only)
|
|
184
|
+
let offsetX = 0;
|
|
185
|
+
if (typeof uOffset === 'number') {
|
|
186
|
+
offsetX = ((uOffset % 1) + 1) % 1;
|
|
187
|
+
}
|
|
188
|
+
else if (typeof textureOffsetDeg === 'number') {
|
|
189
|
+
offsetX = (((textureOffsetDeg / 360) % 1) + 1) % 1;
|
|
190
|
+
}
|
|
191
|
+
const applyWrapOptions = (t) => {
|
|
192
|
+
t.wrapS = WRAP[wrapS] ?? THREE.RepeatWrapping;
|
|
193
|
+
t.wrapT = WRAP[wrapT] ?? THREE.ClampToEdgeWrapping;
|
|
194
|
+
// Compressed textures + NPOT prefer clamp to avoid artifacts
|
|
195
|
+
if (isCompressed) {
|
|
196
|
+
t.wrapS = THREE.ClampToEdgeWrapping;
|
|
197
|
+
t.wrapT = THREE.ClampToEdgeWrapping;
|
|
198
|
+
}
|
|
199
|
+
if (Array.isArray(center) && center.length === 2)
|
|
200
|
+
t.center.set(center[0], center[1]);
|
|
201
|
+
if (typeof rotationDeg === 'number')
|
|
202
|
+
t.rotation = (rotationDeg * Math.PI) / 180;
|
|
203
|
+
if (typeof repeatX === 'number' || typeof repeatY === 'number') {
|
|
204
|
+
t.repeat.set(typeof repeatX === 'number' ? repeatX : t.repeat.x, typeof repeatY === 'number' ? repeatY : t.repeat.y);
|
|
205
|
+
}
|
|
206
|
+
t.offset.x = offsetX;
|
|
207
|
+
if (mirrorX) {
|
|
208
|
+
t.repeat.x = -Math.abs(t.repeat.x || 1);
|
|
209
|
+
t.offset.x = (t.offset.x + 1) % 1;
|
|
210
|
+
}
|
|
211
|
+
t.needsUpdate = true;
|
|
212
|
+
};
|
|
213
|
+
if (shouldMirrorDuplicate && tex.image && typeof document !== 'undefined') {
|
|
214
|
+
// Create a mirrored duplicate without any blending
|
|
215
|
+
const img = tex.image;
|
|
216
|
+
try {
|
|
217
|
+
const canvas = document.createElement('canvas');
|
|
218
|
+
canvas.width = img.width * 2;
|
|
219
|
+
canvas.height = img.height;
|
|
220
|
+
// A missing 2D context throws on first use and lands in the fallback below, as before.
|
|
221
|
+
const ctx = canvas.getContext('2d');
|
|
222
|
+
// Left half: original
|
|
223
|
+
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
224
|
+
// Right half: horizontally flipped copy
|
|
225
|
+
ctx.save();
|
|
226
|
+
ctx.translate(img.width, 0);
|
|
227
|
+
ctx.scale(-1, 1);
|
|
228
|
+
ctx.drawImage(img, -img.width, 0, img.width, img.height);
|
|
229
|
+
ctx.restore();
|
|
230
|
+
const processedTex = new THREE.CanvasTexture(canvas);
|
|
231
|
+
processedTex.colorSpace = THREE.SRGBColorSpace;
|
|
232
|
+
processedTex.generateMipmaps = true;
|
|
233
|
+
processedTex.anisotropy = 8;
|
|
234
|
+
applyWrapOptions(processedTex);
|
|
235
|
+
material.map = processedTex;
|
|
236
|
+
material.needsUpdate = true;
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
// Fallback: use mirrored repeat directly
|
|
240
|
+
tex.wrapS = THREE.MirroredRepeatWrapping;
|
|
241
|
+
tex.wrapT = WRAP[wrapT] ?? THREE.ClampToEdgeWrapping;
|
|
242
|
+
tex.repeat.x = Math.max(2, typeof repeatX === 'number' ? repeatX : 2);
|
|
243
|
+
applyWrapOptions(tex);
|
|
244
|
+
// If compressed (e.g., KTX2), normalize vertical orientation using sampling transform
|
|
245
|
+
normalizeCompressedFlip(tex);
|
|
246
|
+
material.map = tex;
|
|
247
|
+
material.needsUpdate = true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Equirectangular or no canvas: just use original texture
|
|
252
|
+
applyWrapOptions(tex);
|
|
253
|
+
const wantFlip = (typeof flipY === 'boolean') ? flipY : true;
|
|
254
|
+
if (isCompressed && wantFlip) {
|
|
255
|
+
meshGeometry = geometry.clone();
|
|
256
|
+
flipGeometryUVY(meshGeometry);
|
|
257
|
+
}
|
|
258
|
+
else if (isCompressed) {
|
|
259
|
+
tex.repeat.y = Math.abs(tex.repeat?.y ?? 1);
|
|
260
|
+
tex.offset.y = 0;
|
|
261
|
+
tex.needsUpdate = true;
|
|
262
|
+
}
|
|
263
|
+
material.map = tex;
|
|
264
|
+
material.needsUpdate = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
console.warn('[worldTexture] Failed to load texture:', e);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (flags)
|
|
272
|
+
applyFlagsToMaterial(material, flags);
|
|
273
|
+
const mesh = new THREE.Mesh(meshGeometry, material);
|
|
274
|
+
// Named so consumers can find the planet body by name (mirrors the
|
|
275
|
+
// normal-mode body's `worldNormalSphere`) — e.g. to modulate its material.
|
|
276
|
+
mesh.name = 'worldTextureSphere';
|
|
277
|
+
mesh.position.set(0, 0, 0);
|
|
278
|
+
// NEW: add cloud shell before glow
|
|
279
|
+
if (cloudsEnabled) {
|
|
280
|
+
const cloudMesh = await createCloudShell({
|
|
281
|
+
baseGeometry: geometry,
|
|
282
|
+
baseRadius: radius,
|
|
283
|
+
widthSegments: widthSegs,
|
|
284
|
+
heightSegments: heightSegs,
|
|
285
|
+
textureUrl: resolvePublicUrl(cloudsTextureUrl),
|
|
286
|
+
color: cloudsColor,
|
|
287
|
+
opacity: cloudsOpacity,
|
|
288
|
+
speed: cloudsSpeed,
|
|
289
|
+
renderer,
|
|
290
|
+
});
|
|
291
|
+
mesh.add(cloudMesh);
|
|
292
|
+
}
|
|
293
|
+
// Optionally add glow as a child of the mesh so it follows transforms
|
|
294
|
+
if (glow) {
|
|
295
|
+
const glowMesh = createGlow(glowRimHex, glowFacingHex);
|
|
296
|
+
mesh.add(glowMesh);
|
|
297
|
+
}
|
|
298
|
+
return mesh;
|
|
299
|
+
}
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entangled-worlds-orbiters-shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
7
|
-
"types": "./dist/
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./dist/
|
|
12
|
-
"default": "./
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./world": {
|
|
15
|
-
"types": "./dist/
|
|
16
|
-
"default": "./
|
|
15
|
+
"types": "./dist/world/index.d.ts",
|
|
16
|
+
"default": "./dist/world/index.js"
|
|
17
17
|
},
|
|
18
18
|
"./moons": {
|
|
19
|
-
"types": "./dist/
|
|
20
|
-
"default": "./
|
|
19
|
+
"types": "./dist/moons/index.d.ts",
|
|
20
|
+
"default": "./dist/moons/index.js"
|
|
21
21
|
},
|
|
22
22
|
"./loaders": {
|
|
23
|
-
"types": "./dist/
|
|
24
|
-
"default": "./
|
|
23
|
+
"types": "./dist/loaders/index.d.ts",
|
|
24
|
+
"default": "./dist/loaders/index.js"
|
|
25
25
|
},
|
|
26
26
|
"./render": {
|
|
27
|
-
"types": "./dist/
|
|
28
|
-
"default": "./
|
|
27
|
+
"types": "./dist/render/index.d.ts",
|
|
28
|
+
"default": "./dist/render/index.js"
|
|
29
29
|
},
|
|
30
30
|
"./core": {
|
|
31
|
-
"types": "./dist/
|
|
32
|
-
"default": "./
|
|
31
|
+
"types": "./dist/core/index.d.ts",
|
|
32
|
+
"default": "./dist/core/index.js"
|
|
33
33
|
},
|
|
34
34
|
"./config": {
|
|
35
|
-
"types": "./dist/
|
|
36
|
-
"default": "./
|
|
35
|
+
"types": "./dist/config/index.d.ts",
|
|
36
|
+
"default": "./dist/config/index.js"
|
|
37
37
|
},
|
|
38
38
|
"./utils": {
|
|
39
|
-
"types": "./dist/
|
|
40
|
-
"default": "./
|
|
39
|
+
"types": "./dist/utils/index.d.ts",
|
|
40
|
+
"default": "./dist/utils/index.js"
|
|
41
41
|
},
|
|
42
42
|
"./clock": {
|
|
43
43
|
"types": "./dist/clock/index.d.ts",
|
|
@@ -91,7 +91,6 @@
|
|
|
91
91
|
"files": [
|
|
92
92
|
"src/**/*",
|
|
93
93
|
"!src/**/*.test.ts",
|
|
94
|
-
"!src/**/*.test.js",
|
|
95
94
|
"!src/**/fixtures/**",
|
|
96
95
|
"dist/**/*",
|
|
97
96
|
"public/**/*",
|
|
@@ -111,21 +110,19 @@
|
|
|
111
110
|
"author": "Plantasia Space",
|
|
112
111
|
"license": "MIT",
|
|
113
112
|
"peerDependencies": {
|
|
114
|
-
"three": "^0.166.1"
|
|
115
|
-
"three-stdlib": "^2.34.0"
|
|
113
|
+
"three": "^0.166.1"
|
|
116
114
|
},
|
|
117
115
|
"peerDependenciesMeta": {
|
|
118
116
|
"three": {
|
|
119
117
|
"optional": true
|
|
120
|
-
},
|
|
121
|
-
"three-stdlib": {
|
|
122
|
-
"optional": true
|
|
123
118
|
}
|
|
124
119
|
},
|
|
125
120
|
"devDependencies": {
|
|
126
121
|
"@eslint/js": "^10.0.1",
|
|
127
122
|
"@types/node": "^20.19.0",
|
|
128
123
|
"@types/three": "^0.166.0",
|
|
124
|
+
"@typescript-eslint/eslint-plugin": "^8.72.1",
|
|
125
|
+
"@typescript-eslint/parser": "^8.72.1",
|
|
129
126
|
"eslint": "^10.0.0",
|
|
130
127
|
"globals": "^17.3.0",
|
|
131
128
|
"three": "^0.166.1",
|
|
@@ -144,21 +141,14 @@
|
|
|
144
141
|
"scripts": {
|
|
145
142
|
"lint": "eslint .",
|
|
146
143
|
"lint:strict": "eslint . --max-warnings=0",
|
|
147
|
-
"
|
|
148
|
-
"build:ts": "
|
|
149
|
-
"
|
|
150
|
-
"prepare": "npm run build:ts",
|
|
144
|
+
"build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
145
|
+
"build:ts": "npm run build",
|
|
146
|
+
"prepare": "npm run build",
|
|
151
147
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
152
|
-
"test
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"test:uploads": "node --test --import tsx src/uploads/*.test.js",
|
|
157
|
-
"test:audio": "node --test --import tsx src/audio/*.test.ts",
|
|
158
|
-
"test:render": "node --test src/render/*.test.js",
|
|
159
|
-
"test": "npm run test:clock && npm run test:notes && npm run test:faust && npm run test:studio && npm run test:uploads && npm run test:audio && npm run test:render && npm run test:moons && npm run test:star",
|
|
160
|
-
"test:moons": "node --test src/moons/*.test.js",
|
|
161
|
-
"test:star": "node --test --import tsx src/star/*.test.ts",
|
|
148
|
+
"test": "node --test --import tsx $(find src -name '*.test.ts' | sort)",
|
|
149
|
+
"baseline:update": "tsx scripts/api-baseline.ts update",
|
|
150
|
+
"baseline:release": "tsx scripts/api-baseline.ts release",
|
|
151
|
+
"check:exports": "tsx scripts/check-exports.ts",
|
|
162
152
|
"bench:star:build": "esbuild bench/star/main.ts --bundle --format=esm --target=es2022 --outfile=bench/star/build/main.js --log-level=warning",
|
|
163
153
|
"bench:star": "npm run bench:star:build && tsx bench/star/run.ts"
|
|
164
154
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Runtime public base URL helper for building absolute asset URLs at runtime
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `import.meta` as this module reads it. A consumer's bundler (Vite) fills `import.meta.env` and
|
|
5
|
+
* replaces `import.meta.env.X` at build time; under plain Node `env` is undefined, so the read keeps
|
|
6
|
+
* its guard. The cast compiles to the same `import.meta.env.X`. Not exported: no global type is added.
|
|
7
|
+
*/
|
|
8
|
+
type ViteImportMeta = { env?: Record<string, string | undefined> };
|
|
9
|
+
|
|
10
|
+
/** The `window` global this module mirrors the public base to. Not exported: no global type is added. */
|
|
11
|
+
type PublicBaseWindow = Window & { VITE_PUBLIC_HERBARIUM_BASE?: string | null };
|
|
12
|
+
|
|
13
|
+
// Mutable module-scoped variable to allow apps to override at startup
|
|
14
|
+
export let __PUBLIC_BASE: string | null = null;
|
|
15
|
+
|
|
16
|
+
// Set the public base (strips trailing slash) and mirror to window for convenience
|
|
17
|
+
export function setPublicBase(url: string | null | undefined): void {
|
|
18
|
+
__PUBLIC_BASE = (url || '').replace(/\/$/, '');
|
|
19
|
+
if (typeof window !== 'undefined') (window as PublicBaseWindow).VITE_PUBLIC_HERBARIUM_BASE = __PUBLIC_BASE;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Get the current public base from (in order): explicit set value, envs, or default
|
|
23
|
+
export function getPublicBase(): string {
|
|
24
|
+
// The cast stays inline on each read so the emitted JS is the literal `import.meta.env.X` a
|
|
25
|
+
// bundler looks for.
|
|
26
|
+
const envVal =
|
|
27
|
+
(typeof import.meta !== 'undefined' &&
|
|
28
|
+
(import.meta as ViteImportMeta).env &&
|
|
29
|
+
(import.meta as ViteImportMeta).env!.VITE_PUBLIC_HERBARIUM_BASE) ||
|
|
30
|
+
(typeof window !== 'undefined' && (window as PublicBaseWindow).VITE_PUBLIC_HERBARIUM_BASE) ||
|
|
31
|
+
null;
|
|
32
|
+
const base = (__PUBLIC_BASE || envVal || '').toString();
|
|
33
|
+
return base ? base.replace(/\/$/, '') : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Resolve a possibly-relative path against the current public base
|
|
37
|
+
/**
|
|
38
|
+
* @param path root-relative, relative, or an absolute http(s) URL (returned unchanged).
|
|
39
|
+
* An empty string comes back as is.
|
|
40
|
+
*/
|
|
41
|
+
export function resolvePublicUrl(path: string): string {
|
|
42
|
+
if (!path) return path;
|
|
43
|
+
if (/^https?:\/\//i.test(path)) return path;
|
|
44
|
+
const base = getPublicBase();
|
|
45
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
46
|
+
// If base is empty, return a root-relative path (same-origin)
|
|
47
|
+
return base ? `${base}${p}` : p;
|
|
48
|
+
}
|
|
@@ -8,6 +8,13 @@ export {
|
|
|
8
8
|
getDefaultSkybox,
|
|
9
9
|
getTextureFallbackUrl
|
|
10
10
|
} from './textureConfig.js';
|
|
11
|
+
export type {
|
|
12
|
+
TextureConfig,
|
|
13
|
+
TextureCategory,
|
|
14
|
+
TextureItem,
|
|
15
|
+
TextureChoice,
|
|
16
|
+
TextureConfigMetadata
|
|
17
|
+
} from './textureConfig.js';
|
|
11
18
|
|
|
12
19
|
// runtime base helpers
|
|
13
20
|
export {
|