I have created a particle system using GLSL shaders, adhering to a texture. When the mouse moves, the particles collide and scatter around, simulating a bouncing effect. They gradually come back together smoothly as the mouse passes through.
I’ve achieved something close to the desired outcome, but I’m facing an issue where the mouse layer and the particles are not aligned properly. This results in the particles overlaying on top of the mouse, causing interference. Where should I focus to fix this issue?
setMouseGeometry() {
const hdrEquirect = new RGBELoader().load(
"./gltf/empty_warehouse_01_2k.hdr",
() => {
hdrEquirect.mapping = EquirectangularReflectionMapping;
}
);
const textureLoader = new TextureLoader();
const normalMapTexture = textureLoader.load("src/normal.jpg");
normalMapTexture.wrapS = RepeatWrapping;
normalMapTexture.wrapT = RepeatWrapping;
normalMapTexture.repeat.set(3, 3);
const mouse = new Vector2();
const plane = new Plane(new Vector3(0, 0, 1), 0);
const raycaster = new Raycaster();
const mouseGeometry = new SphereGeometry(17, 32, 16)
mouseGeometry.scale(1, 1, 0.5);
const material = new MeshPhysicalMaterial({
color: 'salmon',
emissive: 0x000000,
metalness: 0,
roughness: 0,
ior: 1.5,
reflectivity: 0,
iridescence: 0,
iridescenceIOR: 1,
sheen: 0,
sheenRoughness: 0,
sheenColor: 0xffffff,
clearcoat: 0,
clearcoatRoughness: 0,
opacity: 1,
depthTest: true,
depthWrite: false,
transmission: 0,
envMapIntensity: 1,
envMap: hdrEquirect
})
const customCursor = new Mesh(mouseGeometry, material)
this.scene.add(customCursor)
window.addEventListener("mousemove", (e) => {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, this.camera);
raycaster.ray.intersectPlane(plane, customCursor.position);
});
}
setParticlesGrid() {
const geometry = new BufferGeometry();
this.geometry = new BufferGeometry();
const initPositions = []
const particles = []
const multiplier = 18
const nbColumns = 16 * multiplier
const nbLines = 9 * multiplier
const rotations = [];
const timeOffsets = [];
const randomValuesX = [];
const randomValuesY = [];
this.nbColumns = nbColumns
this.nbLines = nbLines
for (let line = 0; line < nbLines; line++) {
for (let column = 0; column < nbColumns; column++) {
let x = Math.random() * nbLines - nbLines / 2;
let y = Math.random() * nbColumns - nbColumns / 2;
particles.push(x, y, 0);
let initPoint = [randFloat(-300, 300), randFloat(-300, 300), randFloat(-300, 300)]
initPositions.push(...initPoint)
let rotation = Math.random() * Math.PI * 2;
rotations.push(rotation);
timeOffsets.push(Math.random());
randomValuesX.push(Math.random());
randomValuesY.push(Math.random());
}
}
const vertices = new Float32Array(particles);
const initPositionsFloat = new Float32Array(initPositions);
const timeOffsetsAttribute = new Float32Array(timeOffsets);
const randomValuesXAttribute = new Float32Array(randomValuesX);
const randomValuesYAttribute = new Float32Array(randomValuesY);
geometry.setAttribute('position', new BufferAttribute(vertices, 3));
geometry.setAttribute('initPosition', new BufferAttribute(initPositionsFloat, 3));
geometry.setAttribute('aTimeOffset', new BufferAttribute(timeOffsetsAttribute, 1));
geometry.setAttribute('aRandomX', new BufferAttribute(randomValuesXAttribute, 1));
geometry.setAttribute('aRandomY', new BufferAttribute(randomValuesYAttribute, 1));
const defaultPositions = vertices.slice();
geometry.setAttribute('defaultPosition', new BufferAttribute(defaultPositions, 3));
geometry.center()
const texture = LoaderManager.assets['background'].texture;
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
this.dpr = 2
this.uniforms = {
uColor: { value: new Color(0xffffff) },
uPointSize: { value: 1.0 },
uTexture: { value: texture },
uNbLines: { value: nbLines },
uNbColumns: { value: nbColumns },
uProgress: { value: 3.0 },
uTime: { value: 0 },
uTouch: { value: this.touch.texture },
uScaleHeightPointSize: { value: (this.dpr * this.height) / 2 },
uMouse: { value: new Vector2(0, 0) },
}
this.customMaterial = new ShaderMaterial({
uniforms: this.uniforms,
vertexShader,
fragmentShader,
transparent: true,
depthTest: false,
depthWrite: false,
})
this.mesh = new Points(geometry, this.customMaterial)
this.mesh.renderOrder = 998;
this.scene.add(this.mesh)
}
handleMouseMove = (e) => {
const radius = 17;
const baseSeparationFactor = 0.5;
const returnFactor = 0.1;
this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
this.ray.setFromCamera(this.mouse, this.camera);
const intersects = this.ray.intersectObject(this.mesh);
if (intersects.length) {
const mousePosition = intersects[0].point;
const positions = this.mesh.geometry.attributes.position.array;
const defaultPositions = this.mesh.geometry.attributes.defaultPosition.array;
for (let i = 0; i < positions.length; i += 3) {
const particleX = positions[i];
const particleY = positions[i + 1];
const particleZ = positions[i + 2];
const dx = mousePosition.x - particleX;
const dy = mousePosition.y - particleY;
const dz = mousePosition.z - particleZ;
const distanceToMouse = Math.sqrt(dx * dx + dy * dy + dz * dz);
const separationFactor = baseSeparationFactor * distanceToMouse;
if (distanceToMouse < radius) {
const forceMultiplier = 0.05;
positions[i] -= dx * forceMultiplier * separationFactor;
positions[i + 1] -= dy * forceMultiplier * separationFactor;
positions[i + 2] -= dz * forceMultiplier * separationFactor;
} else {
positions[i] += (defaultPositions[i] - positions[i]) * returnFactor;
positions[i + 1] += (defaultPositions[i + 1] - positions[i + 1]) * returnFactor;
positions[i + 2] += (defaultPositions[i + 2] - positions[i + 2]) * returnFactor;
}
}
this.mesh.geometry.attributes.position.needsUpdate = true;
}
};