diff --git a/frontend/src/components/forge/ForgeVisualization.tsx b/frontend/src/components/forge/ForgeVisualization.tsx new file mode 100644 index 000000000..98a2530e1 --- /dev/null +++ b/frontend/src/components/forge/ForgeVisualization.tsx @@ -0,0 +1,309 @@ +import React, { useRef, useMemo, useEffect, useState } from 'react'; +import { Canvas, useFrame, useThree } from '@react-three/fiber'; +import { OrbitControls, Environment, Float, Sparkles } from '@react-three/drei'; +import * as THREE from 'three'; + +// --- Anvil --- + +function Anvil() { + const meshRef = useRef(null); + + return ( + + {/* Anvil body - simplified box with beveled edges */} + + + + ); +} + +// --- Hammer --- + +function Hammer({ isStriking }: { isStriking: boolean }) { + const groupRef = useRef(null); + + useFrame((state) => { + if (groupRef.current) { + if (isStriking) { + const t = state.clock.elapsedTime * 8; + const swing = Math.sin(t) * 0.5; + groupRef.current.rotation.x = -0.8 + swing; + } else { + groupRef.current.rotation.x = THREE.MathUtils.lerp( + groupRef.current.rotation.x, + -0.3, + 0.05 + ); + } + } + }); + + return ( + + {/* Handle */} + + + + + {/* Head */} + + + + + + ); +} + +// --- Molten Metal (Glowing) --- + +function MoltenMetal() { + const meshRef = useRef(null); + + useFrame((state) => { + if (meshRef.current) { + const t = state.clock.elapsedTime; + // Gentle deformation for molten effect + meshRef.current.scale.y = 0.15 + Math.sin(t * 2) * 0.02; + meshRef.current.scale.x = 1 + Math.sin(t * 1.5) * 0.03; + } + }); + + return ( + + + + + ); +} + +// --- Gear Wheel --- + +function Gear({ position, radius, speed, teeth = 8 }: { + position: [number, number, number]; + radius: number; + speed: number; + teeth?: number; +}) { + const groupRef = useRef(null); + + // Create gear shape + const shape = useMemo(() => { + const s = new THREE.Shape(); + const innerR = radius * 0.7; + const outerR = radius; + const step = (Math.PI * 2) / (teeth * 2); + + for (let i = 0; i < teeth * 2; i++) { + const angle = i * step; + const r = i % 2 === 0 ? outerR : innerR; + const x = Math.cos(angle) * r; + const y = Math.sin(angle) * r; + if (i === 0) s.moveTo(x, y); + else s.lineTo(x, y); + } + s.closePath(); + + // Center hole + const hole = new THREE.Path(); + hole.absarc(0, 0, radius * 0.2, 0, Math.PI * 2, true); + s.holes.push(hole); + + return s; + }, [radius, teeth]); + + useFrame((state) => { + if (groupRef.current) { + groupRef.current.rotation.z += speed * 0.01; + } + }); + + return ( + + + + + + + ); +} + +// --- Fire Particles --- + +function ForgeFire() { + const count = 50; + const meshRef = useRef(null); + const dummy = useMemo(() => new THREE.Object3D(), []); + + const particles = useMemo(() => { + return Array.from({ length: count }, () => ({ + x: (Math.random() - 0.5) * 0.8, + y: 0.4 + Math.random() * 0.3, + z: (Math.random() - 0.5) * 0.4, + speed: 0.5 + Math.random() * 1.5, + offset: Math.random() * Math.PI * 2, + scale: 0.02 + Math.random() * 0.04, + })); + }, []); + + useFrame((state) => { + if (!meshRef.current) return; + const t = state.clock.elapsedTime; + + particles.forEach((p, i) => { + const phase = (t * p.speed + p.offset) % 3; + dummy.position.set( + p.x + Math.sin(t * 2 + p.offset) * 0.1, + p.y + phase * 0.5, + p.z + Math.cos(t * 2 + p.offset) * 0.1 + ); + const s = p.scale * (1 - phase / 3); + dummy.scale.set(s, s, s); + dummy.updateMatrix(); + meshRef.current!.setMatrixAt(i, dummy.matrix); + }); + meshRef.current.instanceMatrix.needsUpdate = true; + }); + + return ( + + + + + ); +} + +// --- Floor --- + +function ForgeFloor() { + return ( + + + + + ); +} + +// --- Light Setup --- + +function ForgeLighting() { + return ( + <> + {/* Main forge glow */} + + {/* Ambient fill */} + + {/* Rim light */} + + {/* Background glow */} + + + ); +} + +// --- Main Scene --- + +function ForgeScene() { + const [isStriking, setIsStriking] = useState(false); + + // Periodic hammer strikes + useEffect(() => { + const interval = setInterval(() => { + setIsStriking(true); + setTimeout(() => setIsStriking(false), 1500); + }, 4000); + return () => clearInterval(interval); + }, []); + + return ( + <> + + + + + + + {/* Gears */} + + + + + + {/* Fire particles */} + + + {/* Ambient sparkles */} + + + {/* Floating elements */} + + + + + + + + {/* Camera controls */} + + + ); +} + +// --- Exported Component --- + +export function ForgeVisualization() { + return ( +
+ + + + +
+ ); +} + +export default ForgeVisualization;