import * as THREE from 'three'; /** * Per-card UV transform injected into a `MeshStandardMaterial` shader. * * Cards share one texture (the deck's sprite sheet, cached by drei) and one * geometry, but each card samples a different sprite cell. Rather than cloning * the texture per card (which re-uploads the sheet on every GPU bind), the * repeat/offset is pushed into the material as a uniform. The shader source is * identical across cards, so three.js still compiles a single shared program. * * We inject our own uniform instead of setting `texture.repeat`/`offset` * because three r185 derives the map UVs from a `mapTransform` matrix that is * refreshed from `map.matrix` every frame, overwriting any per-material * transform we set on the shared texture. */ // Uniforms added via `onBeforeCompile` are not auto-declared by three.js, so // they must be declared in the GLSL explicitly (the built-in `mapTransform` is // declared in `uv_pars_vertex.glsl.js`). const UNIFORM_DECLS = /* glsl */ ` uniform vec2 uMapRepeat; uniform vec2 uMapOffset; `; const VERTEX_INJECT = /* glsl */ ` #include vMapUv = uv * uMapRepeat + uMapOffset; `; /** * Apply a repeat/offset to a card material's map sampling. Call once per * material (the transform is baked into the shader). `repeat`/`offset` are * copied, so the caller may reuse the vectors. */ export function applyMapTransform( material: THREE.MeshStandardMaterial, repeat: THREE.Vector2, offset: THREE.Vector2, ): void { // Clone eagerly so later mutation of the caller's vectors can't leak into // the uniform once the material is compiled. const r = repeat.clone(); const o = offset.clone(); material.onBeforeCompile = (shader) => { shader.uniforms.uMapRepeat = { value: r }; shader.uniforms.uMapOffset = { value: o }; shader.vertexShader = UNIFORM_DECLS + shader.vertexShader.replace('#include ', VERTEX_INJECT); }; }