import React, { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import * as THREE from 'three'; import './SwordStream.css'; const SwordStream = () => { const containerRef = useRef(null); const requestRef = useRef(); const [mode, setMode] = useState('HOVER'); // HOVER, ORBIT useEffect(() => { if (!containerRef.current) return; const COUNT = 1500; const SWORD_COLOR = 0x44aadd; const SWORD_EMISSIVE = 0x112244; const rand = (min, max) => Math.random() * (max - min) + min; const width = containerRef.current.clientWidth; const height = containerRef.current.clientHeight; const scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x050505, 0.002); const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); camera.position.z = 80; const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; containerRef.current.appendChild(renderer.domElement); const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const pointLight = new THREE.PointLight(SWORD_COLOR, 2, 100); scene.add(pointLight); const geometry = new THREE.CylinderGeometry(0.1, 0.4, 4, 8); geometry.rotateX(Math.PI / 2); const material = new THREE.MeshPhongMaterial({ color: SWORD_COLOR, emissive: SWORD_EMISSIVE, shininess: 100, flatShading: true, }); const mesh = new THREE.InstancedMesh(geometry, material, COUNT); scene.add(mesh); const dummy = new THREE.Object3D(); const particles = []; for (let i = 0; i < COUNT; i++) { particles.push({ pos: new THREE.Vector3(rand(-100, 100), rand(-100, 100), rand(-50, 50)), vel: new THREE.Vector3(), acc: new THREE.Vector3(), angle: rand(0, Math.PI * 2), radius: rand(15, 30), speed: rand(0.02, 0.05), offset: new THREE.Vector3(rand(-5, 5), rand(-5, 5), rand(-5, 5)), }); } const mouse = new THREE.Vector3(); const target = new THREE.Vector3(); const mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); const raycaster = new THREE.Raycaster(); let isMouseDown = false; let time = 0; const handleMouseMove = (e) => { const rect = renderer.domElement.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 2 - 1; const y = -((e.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(new THREE.Vector2(x, y), camera); raycaster.ray.intersectPlane(mousePlane, mouse); pointLight.position.copy(mouse); pointLight.position.z = 20; }; const handleMouseDown = () => { isMouseDown = true; setMode('ORBIT'); }; const handleMouseUp = () => { isMouseDown = false; setMode('HOVER'); }; window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mousedown', handleMouseDown); window.addEventListener('mouseup', handleMouseUp); const animate = () => { requestRef.current = requestAnimationFrame(animate); time += 0.01; for (let i = 0; i < COUNT; i++) { const p = particles[i]; if (isMouseDown) { p.angle += p.speed + 0.02; const orbitX = mouse.x + Math.cos(p.angle + time) * p.radius; const orbitY = mouse.y + Math.sin(p.angle + time) * p.radius; const orbitZ = Math.sin(p.angle * 2 + time) * 10; target.set(orbitX, orbitY, orbitZ); p.acc.subVectors(target, p.pos).multiplyScalar(0.08); } else { const noiseX = Math.sin(time + i * 0.1) * 5; const noiseY = Math.cos(time + i * 0.1) * 5; target.copy(mouse).add(p.offset).add(new THREE.Vector3(noiseX, noiseY, 0)); p.acc.subVectors(target, p.pos).multiplyScalar(0.008); } p.vel.add(p.acc); p.vel.multiplyScalar(isMouseDown ? 0.90 : 0.94); p.pos.add(p.vel); dummy.position.copy(p.pos); const lookPos = new THREE.Vector3().copy(p.pos).add(p.vel.clone().multiplyScalar(5)); if (p.vel.lengthSq() > 0.01) { dummy.lookAt(lookPos); } const speedScale = 1 + Math.min(p.vel.length(), 2) * 0.5; dummy.scale.set(1, 1, speedScale); dummy.updateMatrix(); mesh.setMatrixAt(i, dummy.matrix); } mesh.instanceMatrix.needsUpdate = true; renderer.render(scene, camera); }; animate(); const handleResize = () => { if (!containerRef.current) return; const newWidth = containerRef.current.clientWidth; const newHeight = containerRef.current.clientHeight; camera.aspect = newWidth / newHeight; camera.updateProjectionMatrix(); renderer.setSize(newWidth, newHeight); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mousedown', handleMouseDown); window.removeEventListener('mouseup', handleMouseUp); window.removeEventListener('resize', handleResize); cancelAnimationFrame(requestRef.current); if (containerRef.current && renderer.domElement) { containerRef.current.removeChild(renderer.domElement); } geometry.dispose(); material.dispose(); renderer.dispose(); }; }, []); return (
Move to Guide | Click & Hold to Orbit & Charge