Initial commit

This commit is contained in:
ryanfitzpatrickio
2026-08-03 06:43:21 -05:00
commit 7ee3e9d02f
63 changed files with 15792 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
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,
}),
};
}