import * as THREE from 'three'; import { PART } from '../character/body.js'; /** * Materials for one skater. * * Placeholder by design: spike 1 renders the bare procedural body from Ludus, * team-tinted so three agents can be told apart at a glance. Real gear is a * later swap onto the same meshes. The only thing that has to hold now is that * every skater owns its own material instances, so recolouring one never * touches another. */ export const TEAMS = [ { name: 'home', jersey: 0xb8342c, accent: 0xf0e6d2 }, { name: 'away', jersey: 0x2b5d8f, accent: 0xf0e6d2 }, { name: 'third', jersey: 0x3d8c5a, accent: 0xf0e6d2 }, ]; const SKIN_TONES = [0xd8a07a, 0xc98d63, 0xa86b45, 0x8a5334, 0xe8bd9a]; const PANTS = 0x1c1f26; export function buildMaterials(rng, teamIndex = 0) { const team = TEAMS[teamIndex % TEAMS.length]; const skinColor = rng.pick(SKIN_TONES); // One material, vertex-coloured. `paintKit` writes the colours; keeping it to // a single material means the skinned body is still one draw call. const skin = new THREE.MeshStandardMaterial({ color: 0xffffff, vertexColors: true, roughness: 0.68, metalness: 0.03, }); skin.userData.skinColor = new THREE.Color(skinColor); return { skin, team, teamIndex: teamIndex % TEAMS.length, skinColor }; } /** * Write the placeholder kit into the geometry's vertex colours. * * The loft carries `aPart` (which limb) and `aT` (0..1 along it), so the kit * can be blocked in without any texture work: sweater over the torso and arms, * pants over the hips and thighs, socks in the team colour down the shin. * * Overwrites the skin-weight heatmap `computeSkin` leaves behind; that array is * kept on `userData` so the debug view can still be switched back on. */ export function paintKit(geo, { jersey, skinColor }) { const partAttr = geo.attributes.aPart; const tAttr = geo.attributes.aT; const existing = geo.attributes.color; if (existing && !geo.userData.heatColors) geo.userData.heatColors = existing.array.slice(); const n = geo.attributes.position.count; const colors = new Float32Array(n * 3); const c = new THREE.Color(); const flesh = new THREE.Color(skinColor); const sweater = new THREE.Color(jersey); const pants = new THREE.Color(PANTS); for (let i = 0; i < n; i++) { const part = partAttr ? partAttr.getX(i) : PART.TORSO; const t = tAttr ? tAttr.getX(i) : 0.5; if (part === PART.HEAD) { // Helmet from the crown down to the brow; face left bare. c.copy(t > 0.62 ? sweater : flesh); } else if (part === PART.TORSO) { c.copy(t < 0.16 ? pants : sweater); } else if (part === PART.ARM_L || part === PART.ARM_R) { // Sleeve, then a dark glove at the cuff. c.copy(t > 0.88 ? pants : sweater); } else { // Leg: pants to mid-thigh, team sock below, black skate at the ankle. c.copy(t < 0.36 ? pants : t > 0.87 ? pants : sweater); } colors[i * 3] = c.r; colors[i * 3 + 1] = c.g; colors[i * 3 + 2] = c.b; } geo.setAttribute('color', new THREE.BufferAttribute(colors, 3)); } /** * Base layer for a skater who is actually wearing gear. * * `paintKit` draws the kit *onto* the body, which is the right answer while the * body is all there is. Once a jersey, pants and socks are real meshes over the * top, painting a second jersey underneath only shows up as the wrong colour * peeking out at a collar or a cuff. So: face and neck bare, everything else * the dark under layer a player has on beneath the pads. */ export function paintUnderLayer(geo, { skinColor, under = 0x24262c }) { const partAttr = geo.attributes.aPart; const existing = geo.attributes.color; if (existing && !geo.userData.heatColors) geo.userData.heatColors = existing.array.slice(); const n = geo.attributes.position.count; const colors = new Float32Array(n * 3); const flesh = new THREE.Color(skinColor); const base = new THREE.Color(under); for (let i = 0; i < n; i++) { const part = partAttr ? partAttr.getX(i) : PART.TORSO; const c = part === PART.HEAD ? flesh : base; colors[i * 3] = c.r; colors[i * 3 + 1] = c.g; colors[i * 3 + 2] = c.b; } geo.setAttribute('color', new THREE.BufferAttribute(colors, 3)); } /** Shared rink materials — one set for the whole scene, not per skater. */ export function buildRinkMaterials() { return { ice: new THREE.MeshStandardMaterial({ color: 0xeaf2fa, roughness: 0.16, metalness: 0.0, }), lines: new THREE.MeshBasicMaterial({ color: 0xffffff }), boards: new THREE.MeshStandardMaterial({ color: 0xf2f2f0, roughness: 0.5, metalness: 0.02, side: THREE.DoubleSide, }), kickplate: new THREE.MeshStandardMaterial({ color: 0xd6c33c, roughness: 0.6, side: THREE.DoubleSide, }), glass: new THREE.MeshStandardMaterial({ color: 0xc4dcea, roughness: 0.06, metalness: 0, transparent: true, opacity: 0.1, side: THREE.DoubleSide, depthWrite: false, }), }; }