entangled-worlds-orbiters-shared 1.0.4 → 1.0.5
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 +10 -0
- package/package.json +1 -1
- package/src/world/worldTexture.js +80 -2
package/README.md
CHANGED
|
@@ -16,6 +16,16 @@ A shared library containing core components for 3D world rendering, moons system
|
|
|
16
16
|
```bash
|
|
17
17
|
npm install entangled-worlds-orbiters-shared
|
|
18
18
|
```
|
|
19
|
+
## Test locally
|
|
20
|
+
|
|
21
|
+
In /entangled-worlds-orbiters-shared:
|
|
22
|
+
npm install
|
|
23
|
+
npm link
|
|
24
|
+
|
|
25
|
+
In /entangled-worlds-preview or /orbiters:
|
|
26
|
+
|
|
27
|
+
npm link entangled-worlds-orbiters-shared
|
|
28
|
+
|
|
19
29
|
|
|
20
30
|
## Usage
|
|
21
31
|
|
package/package.json
CHANGED
|
@@ -5,6 +5,25 @@ import { getLightFresnel } from '../core/lightFresnel.js';
|
|
|
5
5
|
import { createCloudShell } from './cloudShell.js';
|
|
6
6
|
import { resolvePublicUrl } from '../config/env.js';
|
|
7
7
|
|
|
8
|
+
// Debug tag so you can verify the loaded file version in console
|
|
9
|
+
export const WORLD_TEXTURE_DEBUG_TAG = 'WT:vFlipGeom-r3@2025-10-28T17:22:00Z';
|
|
10
|
+
|
|
11
|
+
// Enable debug if query/localStorage says so; expose tag
|
|
12
|
+
if (typeof window !== 'undefined') {
|
|
13
|
+
const q = (window.location && window.location.search) || '';
|
|
14
|
+
const qsDebug = /(?:[?&])debugTextures=1(?:&|$)/.test(q);
|
|
15
|
+
const lsDebug = (window.localStorage && window.localStorage.DEBUG_TEXTURE_FLIP === '1') || false;
|
|
16
|
+
if (!window.DEBUG_TEXTURE_FLIP && (qsDebug || lsDebug)) {
|
|
17
|
+
window.DEBUG_TEXTURE_FLIP = true;
|
|
18
|
+
}
|
|
19
|
+
window.EW_WT_DEBUG_TAG = WORLD_TEXTURE_DEBUG_TAG;
|
|
20
|
+
if (window.DEBUG_TEXTURE_FLIP) {
|
|
21
|
+
console.info('[worldTexture] loaded', WORLD_TEXTURE_DEBUG_TAG);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let WT_LOGGED_FIRST_CALL = false;
|
|
26
|
+
|
|
8
27
|
export const EARTH_RADIUS_UNITS = 0.5; // scene units
|
|
9
28
|
|
|
10
29
|
/**
|
|
@@ -75,6 +94,7 @@ export async function makeSphere({
|
|
|
75
94
|
const widthSegs = Number.isFinite(sphereWidthSegments) ? Math.max(3, Math.floor(sphereWidthSegments)) : 64;
|
|
76
95
|
const heightSegs = Number.isFinite(sphereHeightSegments) ? Math.max(2, Math.floor(sphereHeightSegments)) : 64;
|
|
77
96
|
const geometry = new THREE.SphereGeometry(radius, widthSegs, heightSegs);
|
|
97
|
+
let meshGeometry = geometry; // may clone/flip UVs for compressed textures
|
|
78
98
|
|
|
79
99
|
const rough = (typeof materialRoughness === 'number') ? THREE.MathUtils.clamp(materialRoughness, 0, 1) : 0.7;
|
|
80
100
|
const metal = (typeof materialMetalness === 'number') ? THREE.MathUtils.clamp(materialMetalness, 0, 1) : 0.0;
|
|
@@ -86,6 +106,48 @@ export async function makeSphere({
|
|
|
86
106
|
mirror: THREE.MirroredRepeatWrapping,
|
|
87
107
|
};
|
|
88
108
|
|
|
109
|
+
// For compressed textures (e.g., KTX2) WebGL cannot apply UNPACK_FLIP_Y at upload.
|
|
110
|
+
// To normalize orientation across loaders, emulate vertical flip in UV sampling:
|
|
111
|
+
// v' = 1 - v => repeat.y = -repeat.y, offset.y = 1 - offset.y
|
|
112
|
+
const normalizeCompressedFlip = (t) => {
|
|
113
|
+
if (!t || !t.isCompressedTexture) return;
|
|
114
|
+
const wantFlip = (typeof flipY === 'boolean') ? flipY : true; // match uncompressed default
|
|
115
|
+
if (!wantFlip) return;
|
|
116
|
+
const rY = (t.repeat && typeof t.repeat.y === 'number') ? t.repeat.y : 1;
|
|
117
|
+
const oY = (t.offset && typeof t.offset.y === 'number') ? t.offset.y : 0;
|
|
118
|
+
t.repeat.y = -rY;
|
|
119
|
+
t.offset.y = 1 - oY;
|
|
120
|
+
t.needsUpdate = true;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Emulate vertical flip for compressed textures (e.g., KTX2) using UV transform
|
|
124
|
+
const normalizeCompressedFlipY = (t, desired) => {
|
|
125
|
+
if (!t || !t.isCompressedTexture) return;
|
|
126
|
+
const wantFlip = (typeof desired === 'boolean') ? desired : true; // default to match uncompressed loader
|
|
127
|
+
const rY = t.repeat?.y ?? 1;
|
|
128
|
+
const oY = t.offset?.y ?? 0;
|
|
129
|
+
if (wantFlip) {
|
|
130
|
+
t.repeat.y = -Math.abs(rY);
|
|
131
|
+
t.offset.y = 1 - oY;
|
|
132
|
+
} else {
|
|
133
|
+
t.repeat.y = Math.abs(rY);
|
|
134
|
+
t.offset.y = 0;
|
|
135
|
+
}
|
|
136
|
+
t.needsUpdate = true;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Flip UV.y on a cloned geometry (keeps other layers safe)
|
|
140
|
+
const flipGeometryUVY = (geom) => {
|
|
141
|
+
if (!geom || !geom.attributes || !geom.attributes.uv) return 0;
|
|
142
|
+
const uv = geom.attributes.uv;
|
|
143
|
+
const arr = uv.array;
|
|
144
|
+
for (let i = 1; i < arr.length; i += 2) {
|
|
145
|
+
arr[i] = 1 - arr[i]; // v = 1 - v
|
|
146
|
+
}
|
|
147
|
+
uv.needsUpdate = true;
|
|
148
|
+
return uv.count;
|
|
149
|
+
};
|
|
150
|
+
|
|
89
151
|
// Attach a Fresnel glow mesh as a child of the sphere mesh
|
|
90
152
|
function createGlow(rimHex, facingHex) {
|
|
91
153
|
const fresnelMat = getLightFresnel({ rimHex, facingHex });
|
|
@@ -153,6 +215,11 @@ export async function makeSphere({
|
|
|
153
215
|
const applyWrapOptions = (t) => {
|
|
154
216
|
t.wrapS = WRAP[wrapS] ?? THREE.RepeatWrapping;
|
|
155
217
|
t.wrapT = WRAP[wrapT] ?? THREE.ClampToEdgeWrapping;
|
|
218
|
+
// Compressed textures + NPOT prefer clamp to avoid artifacts
|
|
219
|
+
if (isCompressed) {
|
|
220
|
+
t.wrapS = THREE.ClampToEdgeWrapping;
|
|
221
|
+
t.wrapT = THREE.ClampToEdgeWrapping;
|
|
222
|
+
}
|
|
156
223
|
if (Array.isArray(center) && center.length === 2) t.center.set(center[0], center[1]);
|
|
157
224
|
if (typeof rotationDeg === 'number') t.rotation = (rotationDeg * Math.PI) / 180;
|
|
158
225
|
if (typeof repeatX === 'number' || typeof repeatY === 'number') {
|
|
@@ -201,12 +268,23 @@ export async function makeSphere({
|
|
|
201
268
|
tex.wrapT = WRAP[wrapT] ?? THREE.ClampToEdgeWrapping;
|
|
202
269
|
tex.repeat.x = Math.max(2, typeof repeatX === 'number' ? repeatX : 2);
|
|
203
270
|
applyWrapOptions(tex);
|
|
271
|
+
// If compressed (e.g., KTX2), normalize vertical orientation using sampling transform
|
|
272
|
+
normalizeCompressedFlip(tex);
|
|
204
273
|
material.map = tex;
|
|
205
274
|
material.needsUpdate = true;
|
|
206
275
|
}
|
|
207
276
|
} else {
|
|
208
|
-
// Equirectangular or no canvas: just use original texture
|
|
277
|
+
// Equirectangular or no canvas: just use original texture
|
|
209
278
|
applyWrapOptions(tex);
|
|
279
|
+
const wantFlip = (typeof flipY === 'boolean') ? flipY : true;
|
|
280
|
+
if (isCompressed && wantFlip) {
|
|
281
|
+
meshGeometry = geometry.clone();
|
|
282
|
+
flipGeometryUVY(meshGeometry);
|
|
283
|
+
} else if (isCompressed) {
|
|
284
|
+
tex.repeat.y = Math.abs(tex.repeat?.y ?? 1);
|
|
285
|
+
tex.offset.y = 0;
|
|
286
|
+
tex.needsUpdate = true;
|
|
287
|
+
}
|
|
210
288
|
material.map = tex;
|
|
211
289
|
material.needsUpdate = true;
|
|
212
290
|
}
|
|
@@ -216,7 +294,7 @@ export async function makeSphere({
|
|
|
216
294
|
}
|
|
217
295
|
|
|
218
296
|
if (flags) applyFlagsToMaterial(material, flags);
|
|
219
|
-
const mesh = new THREE.Mesh(
|
|
297
|
+
const mesh = new THREE.Mesh(meshGeometry, material);
|
|
220
298
|
mesh.position.set(0, 0, 0);
|
|
221
299
|
|
|
222
300
|
// NEW: add cloud shell before glow
|