diff --git a/the-matrix/js/agents.js b/the-matrix/js/agents.js index 058740d..d88285b 100644 --- a/the-matrix/js/agents.js +++ b/the-matrix/js/agents.js @@ -78,15 +78,63 @@ function buildTimmy(sc) { const group = new THREE.Group(); group.position.copy(TIMMY_POS); - // Robe - const robeMat = new THREE.MeshStandardMaterial({ color: 0x2d1b4e, roughness: 0.82, metalness: 0.08 }); + // ── Robe — royal purple with subtle inner glow ──────────────────────────── + const robeMat = new THREE.MeshStandardMaterial({ + color: 0x5c14b0, // vivid royal purple + emissive: 0x2a0060, + emissiveIntensity: 0.12, + roughness: 0.72, + metalness: 0.05, + }); const robe = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.72, 2.2, 8), robeMat); robe.position.y = 1.1; robe.castShadow = true; group.add(robe); - // Head (pivot for face — tilts via rotation.z in updateAgents) - const headMat = new THREE.MeshStandardMaterial({ color: 0xf2d0a0, roughness: 0.7 }); + // ── Celestial decorations on robe (tiny gold stars/symbols) ────────────── + const celestialMat = new THREE.MeshStandardMaterial({ + color: 0xffd060, + emissive: 0xffaa00, + emissiveIntensity: 0.9, + roughness: 0.3, + metalness: 0.6, + }); + // Scattered octahedra on robe surface — chest, shoulders, lower body + const celestialPositions = [ + [ 0.00, 1.80, 0.30 ], // chest centre (moon clasp) + [ -0.22, 1.60, 0.26 ], // chest left + [ 0.22, 1.60, 0.26 ], // chest right + [ -0.28, 1.30, 0.22 ], // mid left + [ 0.28, 1.30, 0.22 ], // mid right + [ 0.00, 0.95, 0.32 ], // lower centre + [ -0.18, 0.70, 0.30 ], // lower left + [ 0.18, 0.70, 0.30 ], // lower right + ]; + celestialPositions.forEach(([x, y, z]) => { + const sz = y > 1.5 ? 0.038 : 0.026; // chest stars larger + const cs = new THREE.Mesh(new THREE.OctahedronGeometry(sz, 0), celestialMat); + cs.position.set(x, y, z); + cs.rotation.y = Math.random() * Math.PI; + group.add(cs); + }); + + // Moon crescent on chest — a torus segment (big gold torus, small tube) + const moonMat = new THREE.MeshStandardMaterial({ + color: 0xffd700, + emissive: 0xffcc00, + emissiveIntensity: 0.8, + roughness: 0.25, + metalness: 0.7, + }); + const moon = new THREE.Mesh(new THREE.TorusGeometry(0.065, 0.016, 5, 14, Math.PI * 1.3), moonMat); + moon.position.set(-0.06, 1.82, 0.32); + moon.rotation.x = -0.3; + moon.rotation.z = -0.5; + group.add(moon); + + // ── Head ───────────────────────────────────────────────────────────────── + // Slightly older, weathered skin tone + const headMat = new THREE.MeshStandardMaterial({ color: 0xd8a878, roughness: 0.72 }); const head = new THREE.Mesh(new THREE.SphereGeometry(0.38, 16, 16), headMat); head.position.y = 2.6; head.castShadow = true; @@ -94,7 +142,6 @@ function buildTimmy(sc) { // ── Face (ALL parented to head — follow head tilt naturally) ───────────── - // White sclera background for each eye const scleraMat = new THREE.MeshStandardMaterial({ color: 0xf5f2e8, emissive: 0x777777, @@ -103,8 +150,6 @@ function buildTimmy(sc) { }); const scleraGeo = new THREE.SphereGeometry(0.079, 10, 10); - // Head-relative positions: head center = (0,0,0), face = +z direction - // group y=2.65, z=0.31 → head-relative y=0.05, z=0.31 const eyeL = new THREE.Mesh(scleraGeo, scleraMat); eyeL.position.set(-0.14, 0.05, 0.31); head.add(eyeL); @@ -113,53 +158,140 @@ function buildTimmy(sc) { eyeR.position.set(0.14, 0.05, 0.31); head.add(eyeR); - // Dark pupils — children of sclera so they scale with eye Y (squint effect) const pupilMat = new THREE.MeshBasicMaterial({ color: 0x07070f }); const pupilGeo = new THREE.SphereGeometry(0.037, 8, 8); const pupilL = new THREE.Mesh(pupilGeo, pupilMat); - pupilL.position.set(0, 0, 0.057); // forward from sclera center + pupilL.position.set(0, 0, 0.057); eyeL.add(pupilL); const pupilR = new THREE.Mesh(pupilGeo, pupilMat.clone()); pupilR.position.set(0, 0, 0.057); eyeR.add(pupilR); - // Mouth — TubeGeometry arc, child of head so it follows head tilt - const mouthMat = new THREE.MeshStandardMaterial({ - color: 0x8a4a28, - roughness: 0.7, - metalness: 0.0, - }); - const mouth = new THREE.Mesh(_pickMouthGeo(0.08), mouthMat); // start at idle smile - // Position in head-local space: lower face (y=-0.18), on the face surface (z≈0.30) + // Mouth arc + const mouthMat = new THREE.MeshStandardMaterial({ color: 0x8a4a28, roughness: 0.7, metalness: 0.0 }); + const mouth = new THREE.Mesh(_pickMouthGeo(0.08), mouthMat); mouth.position.set(0, -0.18, 0.30); head.add(mouth); - // ── Hat ────────────────────────────────────────────────────────────────── + // ── Beard — silver-white, below chin ───────────────────────────────────── + const beardMat = new THREE.MeshStandardMaterial({ + color: 0xd8d4cc, + emissive: 0x888880, + emissiveIntensity: 0.08, + roughness: 0.88, + }); + // Main beard volume: wide cone hanging below chin + const beard = new THREE.Mesh(new THREE.ConeGeometry(0.18, 0.38, 7), beardMat); + beard.position.set(0, -0.32, 0.20); + beard.rotation.x = 0.22; // tip slightly forward + head.add(beard); + // Moustache: small cylinder across upper lip + const moustache = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.22, 6), beardMat.clone()); + moustache.position.set(0, -0.10, 0.35); + moustache.rotation.z = Math.PI / 2; + head.add(moustache); - const hatMat = new THREE.MeshStandardMaterial({ color: 0x1a0a2e, roughness: 0.82 }); - const brim = new THREE.Mesh(new THREE.TorusGeometry(0.46, 0.07, 6, 24), hatMat); + // ── Hair — silver-white wisps at sides ─────────────────────────────────── + const hairMat = new THREE.MeshStandardMaterial({ + color: 0xc8c4bc, + emissive: 0x888880, + emissiveIntensity: 0.06, + roughness: 0.90, + }); + // Side hair puffs + [[-0.34, 0.04, 0.06], [0.34, 0.04, 0.06], [-0.30, -0.10, -0.08], [0.30, -0.10, -0.08]].forEach(([x, y, z]) => { + const h = new THREE.Mesh(new THREE.SphereGeometry(0.14, 7, 7), hairMat); + h.position.set(x, y, z); + h.scale.set(1, 0.7, 0.9); + head.add(h); + }); + // Back of head hair mass + const hairBack = new THREE.Mesh(new THREE.SphereGeometry(0.30, 8, 8), hairMat.clone()); + hairBack.position.set(0, 0.0, -0.22); + hairBack.scale.set(1, 0.8, 0.7); + head.add(hairBack); + + // ── Hat — deep royal purple, taller cone ───────────────────────────────── + const hatMat = new THREE.MeshStandardMaterial({ + color: 0x3a0880, + emissive: 0x18044a, + emissiveIntensity: 0.10, + roughness: 0.78, + }); + const brim = new THREE.Mesh(new THREE.TorusGeometry(0.47, 0.07, 6, 24), hatMat); brim.position.y = 2.94; brim.rotation.x = Math.PI / 2; group.add(brim); - const hat = new THREE.Mesh(new THREE.ConeGeometry(0.33, 0.78, 8), hatMat.clone()); - hat.position.y = 3.33; + const hat = new THREE.Mesh(new THREE.ConeGeometry(0.33, 0.82, 8), hatMat.clone()); + hat.position.y = 3.35; group.add(hat); - const starMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xffaa00, emissiveIntensity: 1.0 }); - const star = new THREE.Mesh(new THREE.OctahedronGeometry(0.07, 0), starMat); - star.position.y = 3.76; + // Hat band — thin gold torus just above brim + const hatBandMat = new THREE.MeshStandardMaterial({ + color: 0xffd700, emissive: 0xffaa00, emissiveIntensity: 0.7, + roughness: 0.3, metalness: 0.6, + }); + const hatBand = new THREE.Mesh(new THREE.TorusGeometry(0.36, 0.022, 5, 20), hatBandMat); + hatBand.position.y = 3.02; + hatBand.rotation.x = Math.PI / 2; + group.add(hatBand); + + // Star on hat tip + const starMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xffcc00, emissiveIntensity: 1.2 }); + const star = new THREE.Mesh(new THREE.OctahedronGeometry(0.08, 0), starMat); + star.position.y = 3.80; group.add(star); - // Belt - const beltMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xaa8800, emissiveIntensity: 0.4 }); - const belt = new THREE.Mesh(new THREE.TorusGeometry(0.52, 0.04, 6, 24), beltMat); + // ── Belt — wide gold with central gemstone ──────────────────────────────── + const beltMat = new THREE.MeshStandardMaterial({ + color: 0xffd700, + emissive: 0xcc8800, + emissiveIntensity: 0.5, + roughness: 0.28, + metalness: 0.65, + }); + const belt = new THREE.Mesh(new THREE.TorusGeometry(0.52, 0.055, 6, 24), beltMat); belt.position.y = 0.72; belt.rotation.x = Math.PI / 2; group.add(belt); + // Belt gemstone clasp — glowing teal + const claspMat = new THREE.MeshStandardMaterial({ + color: 0x22ddcc, + emissive: 0x008888, + emissiveIntensity: 1.2, + roughness: 0.1, + metalness: 0.0, + }); + const clasp = new THREE.Mesh(new THREE.OctahedronGeometry(0.055, 0), claspMat); + clasp.position.set(0, 0.72, 0.52); + group.add(clasp); + + // ── Magic energy hand — right side, glowing orange ─────────────────────── + const magicMat = new THREE.MeshStandardMaterial({ + color: 0xff8800, + emissive: 0xff5500, + emissiveIntensity: 2.2, + roughness: 0.1, + metalness: 0.0, + }); + const magicOrb = new THREE.Mesh(new THREE.SphereGeometry(0.10, 10, 10), magicMat); + magicOrb.position.set(0.55, 1.55, 0.30); // right-hand side, chest height + group.add(magicOrb); + + const magicLight = new THREE.PointLight(0xff6600, 1.4, 3.5); + magicOrb.add(magicLight); + + // Trailing sparks — two tiny satellite orbs + [[0.12, 0.10, 0.06], [-0.08, 0.14, 0.05]].forEach(([dx, dy, dz]) => { + const spark = new THREE.Mesh(new THREE.SphereGeometry(0.033, 6, 6), magicMat.clone()); + spark.position.set(0.55 + dx, 1.55 + dy, 0.30 + dz); + group.add(spark); + }); + sc.add(group); // ── Crystal ball ───────────────────────────────────────────────────────── @@ -222,16 +354,14 @@ function buildTimmy(sc) { group, robe, head, hat, star, eyeL, eyeR, pupilL, pupilR, mouth, + magicOrb, magicLight, cb, cbMat, crystalGroup, crystalLight, pip, pipLight, pipMat, bubble, bubbleCanvas, bubbleTex, bubbleMat, pulsePhase: Math.random() * Math.PI * 2, speechTimer: 0, - // Current lerped face parameters faceParams: { lidScale: 0.44, pupilScale: 0.90, smileAmount: 0.08 }, - // Target face parameters (set by emotion or derived state) faceTarget: { lidScale: 0.44, pupilScale: 0.90, smileAmount: 0.08 }, - // External override: when non-null, setFaceEmotion() takes precedence over derived state _overrideMood: null, }; } @@ -285,8 +415,14 @@ export function updateAgents(time) { const cbScale = 1 + Math.sin(t * cbPulseSpeed) * 0.022; timmy.cb.scale.setScalar(cbScale); - timmy.robe.material.emissive = new THREE.Color(0x4400aa); - timmy.robe.material.emissiveIntensity = robeGlow; + timmy.robe.material.emissive = new THREE.Color(0x5c14b0); + timmy.robe.material.emissiveIntensity = 0.10 + robeGlow; + + // Magic energy orb — pulsing orange glow in Timmy's hand + const magicPulse = 0.85 + Math.sin(t * 3.4) * 0.35; + timmy.magicOrb.scale.setScalar(magicPulse * (vs === 'working' ? 1.35 : 1.0)); + timmy.magicLight.intensity = 1.0 + Math.sin(t * 4.2) * 0.5 + (vs === 'working' ? 0.8 : 0.0); + timmy.magicOrb.position.y = 1.55 + Math.sin(t * 2.8) * 0.04; const pipX = Math.sin(t * 0.38 + 1.4) * 3.2; const pipZ = Math.sin(t * 0.65 + 0.8) * 2.2 - 1.8;