import React, { useRef, useMemo } from 'react'; import { useFrame } from '@react-three/fiber'; import * as THREE from 'three'; // --- Galaxy Effect (skill) --- const GALAXY_COUNT = 600; const ARM_COUNT = 4; const MAX_RADIUS = 0.6; const GALAXY_PRESETS = { sun: { coreColor: [1.0, 0.8, 0.3], armColor: [1.0, 0.5, 0.1], tipColor: [1.0, 1.0, 0.7], lightColor: '#ffaa22', lightIntensity: 1.5, rotateSpeed: 0.8, spread: 1.0, }, }; function buildGalaxy(preset) { const positions = new Float32Array(GALAXY_COUNT * 3); const colors = new Float32Array(GALAXY_COUNT * 3); const randoms = new Float32Array(GALAXY_COUNT); for (let i = 0; i < GALAXY_COUNT; i++) { const i3 = i * 3; const arm = i % ARM_COUNT; const armAngle = (arm / ARM_COUNT) * Math.PI * 2; const t = Math.random(); const r = t * MAX_RADIUS; const twist = t * Math.PI * 2.5; const angle = armAngle + twist; const scatter = (1 - t * 0.5) * 0.08 * preset.spread; positions[i3] = Math.cos(angle) * r + (Math.random() - 0.5) * scatter; positions[i3 + 1] = (Math.random() - 0.5) * 0.04 * (1 - t * 0.5); positions[i3 + 2] = Math.sin(angle) * r + (Math.random() - 0.5) * scatter; const core = preset.coreColor; const arm_ = preset.armColor; const tip = preset.tipColor; const c = t < 0.3 ? core.map((v, j) => v + (arm_[j] - v) * (t / 0.3)) : arm_.map((v, j) => v + (tip[j] - v) * ((t - 0.3) / 0.7)); colors[i3] = c[0]; colors[i3 + 1] = c[1]; colors[i3 + 2] = c[2]; randoms[i] = Math.random(); } return { positions, colors, randoms }; } function GalaxyEffect({ position = [0, 1.0, 0], scale = 1 }) { const preset = GALAXY_PRESETS.sun; const groupRef = useRef(); const pointsRef = useRef(); const { positions, colors, randoms } = useMemo(() => buildGalaxy(preset), []); const basePositions = useRef(positions); useFrame(({ clock }) => { if (!groupRef.current || !pointsRef.current) return; const t = clock.getElapsedTime(); groupRef.current.rotation.y = t * preset.rotateSpeed; groupRef.current.rotation.x = Math.sin(t * 0.3) * 0.15; const pulse = 1 + Math.sin(t * 2.0) * 0.1; groupRef.current.scale.setScalar(scale * pulse); const pos = pointsRef.current.geometry.attributes.position.array; const base = basePositions.current; for (let i = 0; i < GALAXY_COUNT; i++) { const i3 = i * 3; const drift = Math.sin(t * 1.5 + randoms[i] * 10) * 0.02; pos[i3] = base[i3] + drift; pos[i3 + 1] = base[i3 + 1] + Math.sin(t * 2 + randoms[i] * 6) * 0.01; pos[i3 + 2] = base[i3 + 2] + drift * 0.7; } pointsRef.current.geometry.attributes.position.needsUpdate = true; }); return ( ); } // --- Sphere Effect (teleport) --- const SPHERE_PRESET = { layers: [ { radius: 0.2, segments: 48, size: 0.01, opacity: 0.4 }, { radius: 0.26, segments: 32, size: 0.006, opacity: 0.15 }, ], rotateSpeed: 0.8, pulseSpeed: 1.0, pulseRange: 0.12, colorFn: (normDist, layerIdx) => { if (layerIdx === 0) return [1.0, 0.8 + normDist * 0.15, 0.2]; return [1.0, 0.7, 0.1]; }, }; function buildSphereLayer(radius, segments, colorFn, layerIdx) { const geo = new THREE.SphereGeometry(radius, segments, segments); const posArr = new Float32Array(geo.attributes.position.array); const colArr = new Float32Array(posArr.length); for (let i = 0; i < posArr.length; i += 3) { const x = posArr[i], y = posArr[i + 1], z = posArr[i + 2]; const dist = Math.sqrt(x * x + y * y + z * z); const normDist = dist / (radius || 1); const [r, g, b] = colorFn(normDist, layerIdx); colArr[i] = r; colArr[i + 1] = g; colArr[i + 2] = b; } geo.dispose(); return { positions: posArr, colors: colArr }; } function SphereEffect({ position = [0, 1.0, 0], scale = 1 }) { const groupRef = useRef(); const layerRefs = useRef([]); const preset = SPHERE_PRESET; const layerData = useMemo(() => preset.layers.map((l, i) => buildSphereLayer(l.radius, l.segments, preset.colorFn, i)), []); useFrame(({ clock }) => { if (!groupRef.current) return; const t = clock.getElapsedTime(); const pulse = 1 + Math.sin(t * preset.pulseSpeed) * preset.pulseRange; groupRef.current.scale.setScalar(scale * pulse); layerRefs.current.forEach((ref, i) => { if (!ref) return; const speed = preset.rotateSpeed * (1 + i * 0.4); const dir = i % 2 === 0 ? 1 : -1; ref.rotation.y = t * speed * dir; ref.rotation.x = Math.sin(t * speed * 0.3 + i) * 0.4; ref.rotation.z = Math.cos(t * speed * 0.2 + i * 2) * 0.3; }); }); return ( {preset.layers.map((layer, i) => ( layerRefs.current[i] = el}> ))} ); } // --- BlackHole Effect (burst) - fullscreen raymarching --- const bhVertexShader = ` attribute vec3 position; varying vec2 vUv; void main() { vUv = position.xy; gl_Position = vec4(position.xy, 0.0, 1.0); } `; const bhFragmentShader = ` precision highp float; varying vec2 vUv; uniform vec2 uResolution; uniform float uTime; uniform float uPhase; // 0-1: blackhole, 1-2: explode, 2-3: dissipate #define MAX_STEPS 48 #define STEP_SIZE 0.1 #define PI 3.14159265 float hash(float n){ return fract(sin(n)*43758.5453123); } float hash2(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453); } float noise(vec3 p){ vec3 i=floor(p); vec3 f=fract(p); f=f*f*(3.0-2.0*f); float n=i.x+i.y*57.0+113.0*i.z; return mix(mix(mix(hash(n),hash(n+1.0),f.x),mix(hash(n+57.0),hash(n+58.0),f.x),f.y), mix(mix(hash(n+113.0),hash(n+114.0),f.x),mix(hash(n+170.0),hash(n+171.0),f.x),f.y),f.z); } vec3 starField(vec3 dir){ vec3 col=vec3(0.0); col+=smoothstep(0.4,0.9,noise(dir*3.0+vec3(0.5,1.0,0.2))+0.5*noise(dir*7.0))*vec3(0.05,0.06,0.15); for(int i=0;i<3;i++){ float sc=pow(8.0,float(i+1)); vec3 g=fract(dir*sc)-0.5; float id=floor(dir.x*sc)*1.3+floor(dir.y*sc)*4.7+floor(dir.z*sc)*9.1; col+=step(0.995-float(i)*0.003,hash(id))*smoothstep(0.12,0.0,length(g))*vec3(0.9,0.95,1.0)*(1.0-float(i)*0.3); } return col; } // --- Phase 1: Black Hole --- vec3 traceBlackHole(vec3 ro, vec3 rd, float mass){ float rs=1.5*mass, rs2=rs*rs; vec3 pos=ro, vel=normalize(rd); for(int i=0;i20.0) break; } return starField(normalize(vel)); } // --- Phase 2: Supernova Explosion --- vec3 supernova(vec2 uv, float t, float sr){ vec3 col=vec3(0.0); float angle=atan(uv.y,uv.x); // sharp shockwave rings for(int i=0;i<3;i++){ float delay=float(i)*0.15; float rt=max(0.0, t-delay); float ringR=rt*(2.0+float(i)*0.6); // very thin ring with sharp falloff float ringDist=abs(sr-ringR); float ringW=0.008+float(i)*0.004; float ring=exp(-ringDist*ringDist/(ringW*ringW)); // bright edge glow just outside ring float edgeGlow=exp(-ringDist*ringDist/(ringW*ringW*8.0))*0.3; vec3 ringCol=mix(vec3(1.0,0.95,0.85),vec3(1.0,0.75,0.3),float(i)/3.0); float fade=exp(-rt*1.5)*(3.0-float(i)*0.5); col+=ringCol*(ring+edgeGlow)*fade; } // intense central flash with slow decay float flash=1.0/(sr*6.0+0.03)*max(0.0,1.0-t*1.2); col+=vec3(1.0,0.97,0.92)*flash*0.6; // hot debris particles with trails for(int i=0;i<50;i++){ float fi=float(i); float a=hash(fi*1.17)*PI*2.0; float speed=0.8+hash(fi*2.31)*2.5; float r=t*speed; float life=1.0-smoothstep(0.0,0.6+hash(fi*3.7)*0.6, t); vec2 dir=vec2(cos(a),sin(a)); vec2 ppos=dir*r; // spiral motion float spin=hash(fi*7.3)*2.0-1.0; ppos+=vec2(-dir.y,dir.x)*sin(t*3.0+fi)*0.1*spin; float d=length(uv-ppos); // sharp tiny particle with subtle trail float trail=exp(-d*d*8000.0)+exp(-pow(dot(uv-ppos,dir),2.0)*20000.0-pow(length(uv-ppos-dir*0.01),2.0)*5000.0)*0.3; float bright=life*trail; // temperature gradient: white-hot core -> gold -> amber float temp=hash(fi*9.1); vec3 pcol=mix(vec3(1.0,0.95,0.85),mix(vec3(1.0,0.75,0.3),vec3(0.9,0.5,0.1),temp),t*0.8); col+=pcol*bright*1.2; } return col; } // --- Phase 3: Supernova afterglow (reuse explosion, fade out) --- vec3 stardust(vec2 uv, float t, float sr){ // Continue supernova at t=1.0 state, then fade everything out float fade=pow(1.0-t, 2.0); // Run supernova frozen near end, slowly expanding vec2 expandedUv=uv/(1.0+t*0.3); float expandedSr=length(expandedUv); vec3 col=supernova(expandedUv, 0.85+t*0.15, expandedSr)*fade; return col; } void main(){ float aspect=uResolution.x/uResolution.y; vec2 uv=vec2(vUv.x*aspect, vUv.y); float sr=length(uv); vec3 col=vec3(0.0); float alpha=0.0; if(uPhase<1.0){ // Phase 1: Black hole forming (0 -> 1) float grow=uPhase*uPhase; vec2 bhUv=uv*16.0; float mass=3.0*grow; vec3 cp=vec3(0.0,0.0,3.0); vec3 rd=normalize(vec3(bhUv*(1.4-mass*0.1),-1.0)); col=traceBlackHole(cp,rd,mass); float bhSr=length(bhUv); float rs=1.5*mass; float g=max(grow,0.001); col+=vec3(0.3,0.5,1.0)*0.5*0.01*g/(bhSr*5.0/g+0.008)*rs; col+=vec3(0.6,0.75,1.0)*exp(-abs(bhSr-0.055*g)*50.0/g)*g*0.6; col=pow(col/(1.0+col),vec3(0.45)); alpha=clamp(grow*2.0,0.0,1.0)*clamp(length(col)*3.0,0.0,1.0); } else if(uPhase<1.1){ // White flash transition float t=(uPhase-1.0)/0.1; float flash=1.0-t; col=vec3(1.0,0.98,0.95)*flash*flash; alpha=flash; } else if(uPhase<2.0){ // Phase 2: Supernova explosion float t=(uPhase-1.1)/0.9; col=supernova(uv, t, sr); col=min(col, vec3(1.0)); alpha=clamp(length(col)*5.0,0.0,1.0); } else { // Phase 3: Stardust fade out float t=(uPhase-2.0)/1.0; col=stardust(uv, t, sr); col=min(col, vec3(1.0)); alpha=clamp(length(col)*5.0,0.0,1.0)*(1.0-t*t); } gl_FragColor=vec4(col,alpha); } `; const BURST_TOTAL_DURATION = 4.5; // seconds for full animation function BlackHoleEffect() { const matRef = useRef(); const startRef = useRef(null); const uniforms = useMemo(() => ({ uTime: { value: 0 }, uPhase: { value: 0 }, uResolution: { value: new THREE.Vector2(1, 1) }, }), []); useFrame(({ clock, gl }) => { if (!matRef.current) return; const t = clock.getElapsedTime(); if (startRef.current === null) startRef.current = t; const elapsed = t - startRef.current; // Map elapsed time to 0-3 phase range over BURST_TOTAL_DURATION const phase = Math.min(3.0, (elapsed / BURST_TOTAL_DURATION) * 3.0); matRef.current.uniforms.uTime.value = t; matRef.current.uniforms.uPhase.value = phase; const size = gl.getSize(new THREE.Vector2()); matRef.current.uniforms.uResolution.value.copy(size); }); return ( ); } // --- Unified export --- function EnergySphere({ type = 'sun', position = [0, 1.0, 0], scale = 1 }) { if (type === 'earth' || type === 'moon') { return ; } if (type === 'burst') { return ; } return ; } export { EnergySphere }; export default EnergySphere;