hello, i am unable to add mouse hover effect like we have in this spline design - Interactive spark letter with particle effect
open and run this and try mouse hover, i tried but its not done
here is my code
import { useEffect, useMemo, useRef, useState } from "react";
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
import { OrbitControls, Center } from "@react-three/drei";
import * as THREE from "three";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";
import { MeshSurfaceSampler } from "three/examples/jsm/math/MeshSurfaceSampler.js";
const ParticleText = ({ text = "BALL AI" }) => {
const particleCount = 10000;
const particlesRef = useRef();
const [ready, setReady] = useState(false);
const mousePos = useRef(new THREE.Vector2(0, 0)); // Normalized screen space
const mouseWorldPos = useRef(new THREE.Vector3()); // World space
// Assuming BlauerNueRegular.json is in public/assets/fonts
const font = useLoader(FontLoader, "/fonts/helvetiker_regular.typeface.json");
const { positions, initialPositions, lifetimes } = useMemo(() => {
if (!font)
return {
positions: new Float32Array(particleCount * 3),
initialPositions: new Float32Array(particleCount * 3),
lifetimes: new Float32Array(particleCount),
};
const textGeometry = new TextGeometry(text, {
font: font,
size: 1.2,
height: 0.2,
curveSegments: 12,
bevelEnabled: false,
});
textGeometry.computeBoundingBox();
const centerOffset = new THREE.Vector3();
textGeometry.boundingBox.getCenter(centerOffset).negate();
textGeometry.translate(centerOffset.x, centerOffset.y, centerOffset.z);
const textMaterial = new THREE.MeshBasicMaterial();
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
const sampler = new MeshSurfaceSampler(textMesh).build();
const positions = new Float32Array(particleCount * 3);
const initialPositions = new Float32Array(particleCount * 3);
const lifetimes = new Float32Array(particleCount);
const tempPosition = new THREE.Vector3();
for (let i = 0; i < particleCount; i++) {
sampler.sample(tempPosition);
const i3 = i * 3;
positions[i3] = tempPosition.x;
positions[i3 + 1] = tempPosition.y;
positions[i3 + 2] = tempPosition.z;
initialPositions[i3] = tempPosition.x;
initialPositions[i3 + 1] = tempPosition.y;
initialPositions[i3 + 2] = tempPosition.z;
lifetimes[i] = Math.random() * 8.0; // Match cycleTime
}
setReady(true);
return { positions, initialPositions, lifetimes };
}, [font, text, particleCount]);
const shader = useMemo(
() => ({
uniforms: {
time: { value: 0 },
gravity: { value: 0.02 },
cycleTime: { value: 8.0 },
colorStart: { value: new THREE.Color("#E1e1e1") },
colorEnd: { value: new THREE.Color("#C77518") },
mousePos: { value: new THREE.Vector3(0, 0, 0) },
attractionStrength: { value: 10.0 },
attractionRadius: { value: 0.3 },
mouseCycleTime: { value: 2.0 }, // Short cycle for mouse attraction
},
vertexShader: `uniform float time;
uniform float gravity;
uniform float cycleTime;
uniform vec3 colorStart;
uniform vec3 colorEnd;
uniform vec3 mousePos;
uniform float attractionStrength;
uniform float attractionRadius;
uniform float mouseCycleTime;
attribute vec3 initialPosition;
attribute float lifetime;
varying float vAlpha;
varying vec3 vColor;
void main() {
float particleTime = mod(time + lifetime, cycleTime);
vec3 pos = initialPosition;
// Base rise and fall motion for all particles
float upwardVelocity = 0.09;
float t = particleTime;
float verticalOffset = upwardVelocity * t - 0.5 * gravity * t * t;
// Mouse attraction calculation
// Mouse attraction calculation
vec3 directionToMouse = mousePos - pos; // Changed from initialPosition to use current pos
float distanceToMouse = length(directionToMouse);
if (distanceToMouse < attractionRadius) {
// Normalize the direction vector for proper 3D attraction
vec3 normalizedDirection = normalize(directionToMouse);
// Calculate attraction strength based on distance
float attractionFactor = smoothstep(attractionRadius, 0.0, distanceToMouse) * attractionStrength;
// Calculate how much the particle should stick to the mouse
float stickFactor = min(1.0, attractionFactor * 2.0);
// Create the normal position with vertical offset
vec3 normalPos = initialPosition + vec3(0.0, verticalOffset, 0.0);
// The mouse position to attract to (full 3D position)
vec3 attractedPos = mousePos;
// Mix between normal position and mouse position based on stickFactor
pos = mix(normalPos, attractedPos, stickFactor);
} else {
// Normal motion when outside attraction radius
pos = initialPosition + vec3(0.0, verticalOffset, 0.0);
}
// Fade calculation (uniform for all particles)
float peakTime = upwardVelocity / gravity;
float peakHeight = (upwardVelocity * upwardVelocity) / (2.0 * gravity);
float halfHeight = peakHeight * 0.5;
float fallTimeToHalf = sqrt(2.0 * (peakHeight - halfHeight) / gravity);
float fadeStartTime = peakTime + fallTimeToHalf;
vAlpha = 1.0;
if (particleTime < 0.1) {
vAlpha = particleTime / 0.1;
}
if (particleTime > fadeStartTime) {
float fadeProgress = (particleTime - fadeStartTime) / (cycleTime - fadeStartTime);
vAlpha = max(0.0, 1.0 - fadeProgress);
}
// Color transition over full cycle
float colorProgress = particleTime / cycleTime;
vColor = mix(colorStart, colorEnd, colorProgress);
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_Position = projectionMatrix * mvPosition;
float baseSize = 3.0;
gl_PointSize = baseSize * (3.0 / -mvPosition.z);
}`,
fragmentShader: `
varying float vAlpha;
varying vec3 vColor;
void main() {
if (vAlpha <= 0.01) discard;
vec2 coord = gl_PointCoord - vec2(0.5);
float dist = length(coord);
float alpha = smoothstep(0.5, 0.3, dist) * vAlpha;
gl_FragColor = vec4(vColor, alpha);
}
`,
}),
[]
);
// Mouse movement handler
useEffect(() => {
const handleMouseMove = (event) => {
const canvas = document.querySelector("canvas");
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
mousePos.current.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mousePos.current.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// Update loop
useFrame((state) => {
if (!particlesRef.current || !ready) return;
const { camera } = state;
particlesRef.current.material.uniforms.time.value = state.clock.getElapsedTime();
// Update mouse position in world space
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePos.current, camera);
const plane = new THREE.Plane(new THREE.Vector3(0, 0, -1), 0);
raycaster.ray.intersectPlane(plane, mouseWorldPos.current);
if (mouseWorldPos.current) {
particlesRef.current.material.uniforms.mousePos.value.copy(mouseWorldPos.current);
}
});
return (
<group>
{ready && (
<points ref={particlesRef}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={particleCount}
itemSize={3}
array={positions}
/>
<bufferAttribute
attach="attributes-initialPosition"
count={particleCount}
itemSize={3}
array={initialPositions}
/>
<bufferAttribute
attach="attributes-lifetime"
count={particleCount}
itemSize={1}
array={lifetimes}
/>
</bufferGeometry>
<shaderMaterial
args={[shader]}
transparent
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
)}
</group>
);
};
const BallAi = () => (
<Canvas camera={{ position: [0, 0, 2.5] }} dpr={[1, 2]}>
<color attach="background" args={["#000"]} />
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
<Center>
<ParticleText text="BroAI" />
</Center>
<OrbitControls />
</Canvas>
);
export default BallAi;
can anyone help me with this??