Files
Kimi Agent ab3546ae4b
All checks were successful
Tests / lint (push) Successful in 4s
Tests / test (push) Successful in 1m1s
feat: Workshop Phase 2 — Scene MVP (Three.js room) (#401)
Co-authored-by: Kimi Agent <kimi@timmy.local>
Co-committed-by: Kimi Agent <kimi@timmy.local>
2026-03-19 02:14:09 -04:00

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 };
}