Cursor Particles
A disc of 6,000 points, each idling in a small slow orbit around its own home position. Move the pointer over the scene and nearby points get pushed away, brightening as they go, then ease back — a damped spring, not a snap — once the pointer moves on. The dots are additive soft-round sprites baked from a CanvasTexture radial gradient, colored on a blue-to-violet hue ramp by distance from center, over a deep near-black backdrop.
How it works
- Pointer-to-world, without touching
root.raycasterdirectly: an invisiblemesh(aplaneGeometrylying flat aty=0,meshBasicMaterialwithopacity: 0) sits under the whole disc purely to catchonPointerMove. ItsThreeEvent.pointfield is already the world-space raycast hit — no manual NDC/unproject math needed. See Events. onPointerOuton that same plane flips apointerActiveflag off, so repulsion stops the instant the cursor leaves the disc (or the canvas) instead of freezing the last known position forever.- Per-point damped spring, in plain
Float32Arrays:velocityX/velocityZanddisplacementX/displacementZare module-level typed arrays, one slot per particle. Each frame: the pointer (if active and withinREPEL_RADIUS) adds an outward velocity kick; a spring term (-displacement * SPRING) pulls that velocity back toward zero; a damping multiplier bleeds it off. Integrating velocity into displacement and adding it to a drifting home position is what makes a repelled point ease back instead of snapping. - Glow from displacement: each point's color is its base hue-ramp color (
#86b1ffnear the center to#e0aaffat the rim, viaColor.lerp) mixed toward white by how far it's currently displaced — no separate "glow" attribute, just the same spring math driving both position and color. self.geometry.attributes.position.needsUpdate = true(and the matchingcolorattribute) everyonFrame, at 6,000 points — the samebufferGeometry/bufferAttributewiring as a static particle cloud (seeargsreconstruction), but mutated and re-uploaded every tick instead of written once. See Animation & Loop.- Soft round sprites:
pointsMaterial.mapis a plainCanvasTexture— a 64×64 canvas filled withcreateRadialGradient, no image asset. Assigning it is a duck-typed static prop:maphas no.set/.copyon a fresh material, so it falls through to direct assignment. - Additive blending:
blending: AdditiveBlending,depthWrite: false,transparent: true— overlapping sprites brighten instead of occluding, same recipe as the Galaxy particle cloud. frameloop: "always": the idle drift alone requires continuous rendering — nothing here is purely reactive-prop-driven, so"demand"would leave the disc static between pointer moves. See frameloop modes.