forked from Rockachopa/Timmy-time-dashboard
Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
100 lines
2.8 KiB
JavaScript
100 lines
2.8 KiB
JavaScript
/**
|
|
* 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 };
|
|
}
|