Update Timmy's appearance to match reference with new colors and details
Refactors the `buildTimmy` function to update Timmy's robe color to royal purple, add celestial gold star decorations, and implement a silver beard and hair, along with a pulsing orange magic orb effect. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7cc95df8-ef94-4761-8b47-9c13fedbba9a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user