185 lines
6.2 KiB
JavaScript
185 lines
6.2 KiB
JavaScript
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 (
|
|
<group position={position} ref={groupRef}>
|
|
<points ref={pointsRef}>
|
|
<bufferGeometry>
|
|
<bufferAttribute attach="attributes-position" array={positions.slice()} count={GALAXY_COUNT} itemSize={3} />
|
|
<bufferAttribute attach="attributes-color" array={colors} count={GALAXY_COUNT} itemSize={3} />
|
|
</bufferGeometry>
|
|
<pointsMaterial transparent depthWrite={false} vertexColors opacity={0.6} size={0.012} blending={THREE.AdditiveBlending} />
|
|
</points>
|
|
<pointLight intensity={preset.lightIntensity} color={preset.lightColor} distance={3} />
|
|
</group>
|
|
);
|
|
}
|
|
|
|
// --- 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 },
|
|
],
|
|
lightColor: '#aaccff',
|
|
lightIntensity: 0.3,
|
|
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 (
|
|
<group position={position} ref={groupRef}>
|
|
{preset.layers.map((layer, i) => (
|
|
<group key={i} ref={el => layerRefs.current[i] = el}>
|
|
<points>
|
|
<bufferGeometry>
|
|
<bufferAttribute attach="attributes-position" array={layerData[i].positions} count={layerData[i].positions.length / 3} itemSize={3} />
|
|
<bufferAttribute attach="attributes-color" array={layerData[i].colors} count={layerData[i].colors.length / 3} itemSize={3} />
|
|
</bufferGeometry>
|
|
<pointsMaterial transparent depthWrite={false} vertexColors opacity={layer.opacity} size={layer.size} blending={THREE.AdditiveBlending} />
|
|
</points>
|
|
</group>
|
|
))}
|
|
</group>
|
|
);
|
|
}
|
|
|
|
// --- Unified export ---
|
|
|
|
function EnergySphere({ type = 'sun', position = [0, 1.0, 0], scale = 1 }) {
|
|
if (type === 'earth' || type === 'moon') {
|
|
return <SphereEffect position={position} scale={scale} />;
|
|
}
|
|
return <GalaxyEffect position={position} scale={scale} />;
|
|
}
|
|
|
|
export { EnergySphere };
|
|
export default EnergySphere;
|