/** * Timmy the Wizard — geometric figure built from primitives. * * Phase 1: cone body (robe), sphere head, cylinder arms. * Idle animation: gentle breathing (Y-scale oscillation), head tilt. */ import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"; const ROBE_COLOR = 0x2d1b4e; const TRIM_COLOR = 0xdaa520; /** * Create the wizard group and return { group, update }. * Call update(dt) each frame for idle animation. */ export function createWizard() { const group = new THREE.Group(); // --- Robe (cone) --- const robeGeo = new THREE.ConeGeometry(0.5, 1.6, 8); const robeMat = new THREE.MeshStandardMaterial({ color: ROBE_COLOR, roughness: 0.8, }); const robe = new THREE.Mesh(robeGeo, robeMat); robe.position.y = 0.8; group.add(robe); // --- Trim ring at robe bottom --- const trimGeo = new THREE.TorusGeometry(0.5, 0.03, 8, 24); const trimMat = new THREE.MeshStandardMaterial({ color: TRIM_COLOR, roughness: 0.4, metalness: 0.3, }); const trim = new THREE.Mesh(trimGeo, trimMat); trim.rotation.x = Math.PI / 2; trim.position.y = 0.02; group.add(trim); // --- Head (sphere) --- const headGeo = new THREE.SphereGeometry(0.22, 12, 10); const headMat = new THREE.MeshStandardMaterial({ color: 0xd4a574, roughness: 0.7, }); const head = new THREE.Mesh(headGeo, headMat); head.position.y = 1.72; group.add(head); // --- Hood (cone behind head) --- const hoodGeo = new THREE.ConeGeometry(0.35, 0.5, 8); const hoodMat = new THREE.MeshStandardMaterial({ color: ROBE_COLOR, roughness: 0.8, }); const hood = new THREE.Mesh(hoodGeo, hoodMat); hood.position.y = 1.85; hood.position.z = -0.08; group.add(hood); // --- Arms (cylinders) --- const armGeo = new THREE.CylinderGeometry(0.06, 0.08, 0.7, 6); const armMat = new THREE.MeshStandardMaterial({ color: ROBE_COLOR, roughness: 0.8, }); const leftArm = new THREE.Mesh(armGeo, armMat); leftArm.position.set(-0.45, 1.0, 0.15); leftArm.rotation.z = 0.3; leftArm.rotation.x = -0.4; group.add(leftArm); const rightArm = new THREE.Mesh(armGeo, armMat); rightArm.position.set(0.45, 1.0, 0.15); rightArm.rotation.z = -0.3; rightArm.rotation.x = -0.4; group.add(rightArm); // Position behind the desk group.position.set(0, 0, -0.8); // Animation state let elapsed = 0; function update(dt) { elapsed += dt; // Breathing: subtle Y-scale oscillation const breath = 1.0 + Math.sin(elapsed * 1.5) * 0.015; robe.scale.y = breath; // Head tilt head.rotation.z = Math.sin(elapsed * 0.7) * 0.05; head.rotation.x = Math.sin(elapsed * 0.5) * 0.03; } return { group, update }; }