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:
parent
624a72e458
commit
9b8aabe0db
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user