entangled-worlds-orbiters-shared 1.0.4 → 1.0.6

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,10 +1,10 @@
1
1
  import * as THREE from 'three';
2
2
  import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
- import { getPublicBase } from '../config/env.js';
3
+ import { resolvePublicUrl } from '../config/env.js';
4
4
 
5
- // Resolve public herbarium base (strip trailing slash)
6
- const HERBARIUM_BASE = getPublicBase();
7
- function moonTexPath(file) { return `${HERBARIUM_BASE}/default/textures/moons/${file}`; }
5
+ function moonTexPath(file) {
6
+ return resolvePublicUrl(`default/textures/moons/${file}`);
7
+ }
8
8
  export const DEFAULT_MOON_TEX_KTX2 = moonTexPath('moon-equirectangular.ktx2');
9
9
  export const DEFAULT_MOON_TEX_PNG = moonTexPath('moon-equirectangular.png');
10
10
 
@@ -90,16 +90,9 @@ export async function generateMoons({
90
90
  try { sharedTexture = await buildUvGridTexture(); usedUvGrid = true; } catch(_) {}
91
91
  } else if (useTexture) {
92
92
  // Use textureKey if provided, otherwise fall back to default textures
93
- let primaryTexture = textureKey || textureKTX2;
93
+ let primaryTexture = textureKey ? resolvePublicUrl(textureKey) : textureKTX2;
94
94
  const fallbackTexture = texturePNG;
95
95
 
96
- // Resolve textureKey to absolute URL if it's relative
97
- if (textureKey && textureKey.startsWith('/')) {
98
- primaryTexture = `${HERBARIUM_BASE}${textureKey}`;
99
- } else if (textureKey) {
100
- primaryTexture = textureKey; // already absolute
101
- }
102
-
103
96
  try {
104
97
  sharedTexture = await loadTexture(primaryTexture);
105
98
  } catch (eKTX) {
@@ -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 without any edge processing
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(geometry, material);
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