Add sail UVs and Lane E's weave texture

E's recipe verbatim: grid (i,j) -> (u,v), repeat 6x6, sRGB. Without the
uv attribute three defaults every vertex to (0,0), the map samples one
texel and the membrane reads as flat colour — which looks like the
texture failing rather than like a bug, so E flagged it ahead of time.

Texture URL resolves against import.meta.url rather than the server root,
same as weather.js's STORM_DIR and the same thing the integrator's
/world/ -> relative pass was fixing. A missing texture warns and falls
back to flat colour instead of throwing: the cloth is the game, the weave
is a finish, and it must not be able to take the sail down.

Added anisotropy 4 — the sail is mostly viewed at a raking angle from
underneath, which is exactly where an unfiltered weave turns to moire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 01:29:22 +10:00
parent 624a72e458
commit 9b8aabe0db

View File

@ -755,9 +755,37 @@ export async function createSailView(rig, { color = 0xd8c48a } = {}) {
geo.setAttribute('position', new THREE.BufferAttribute(verts, 3));
geo.setIndex(new THREE.BufferAttribute(new Uint16Array(rig.tris), 1));
// UVs: the grid IS the UV space, so (i, j) maps straight to (u, v). Without
// this three defaults every vertex to (0,0), the map samples one texel, and
// the membrane reads as flat colour — which looks like the texture failing
// rather than like a bug. (Lane E's recipe, THREADS.)
const N = rig.N;
const uv = new Float32Array(N * N * 2);
for (let j = 0, k = 0; j < N; j++) {
for (let i = 0; i < N; i++, k += 2) { uv[k] = i / (N - 1); uv[k + 1] = j / (N - 1); }
}
geo.setAttribute('uv', new THREE.BufferAttribute(uv, 2));
const mat = new THREE.MeshStandardMaterial({
color, side: THREE.DoubleSide, roughness: 0.92, metalness: 0.0,
});
// Resolved against this module rather than the server root: the same reason
// weather.js builds STORM_DIR this way, and it's what the integrator's
// /world/ -> relative pass was fixing. A missing texture must not take the
// sail down — the cloth is the game, the weave is a finish.
try {
const tex = await new THREE.TextureLoader().loadAsync(
new URL('../models/textures/sail_weave.png', import.meta.url).href,
);
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(6, 6); // ~6 tiles across a 5 m sail (E's density)
tex.colorSpace = THREE.SRGBColorSpace; // r175 spelling — `encoding` is gone
tex.anisotropy = 4; // it's viewed at a raking angle from underneath
mat.map = tex; // keep mat.color: the weave multiplies it
} catch (err) {
console.warn('[sail] weave texture missing, falling back to flat colour:', err.message);
}
const mesh = new THREE.Mesh(geo, mat);
mesh.castShadow = true; // the shadow IS the product
mesh.receiveShadow = true;