Morph Particles
An 8000-point points cloud that auto-cycles between three target shapes — a sphere and a torus knot sampled straight from real THREE geometries' position attributes, and a parametric 3D spiral with no built-in geometry equivalent. Every particle eases toward the new target on its own delay, hashed from its index, so a shape switch ripples outward through the cloud over about a second and a half rather than every point snapping into place on the same frame. Color rides the same per-particle ease, lerping between each shape's assigned hue.
How it works
- Sampling real geometry into a fixed-size target:
sampleGeometryPositionsreads aTHREE.SphereGeometry/THREE.TorusKnotGeometry'sattributes.position.arrayand walks it at evenly-spaced indices (Math.floor((index / count) * vertexCount)) rather than a contiguous slice — the sample spreads across the whole surface regardless of how the particle count compares to the source vertex count. The spiral target has no built-in geometry to sample from, so it's generated directly as a parametric shape of the same size. - One
bufferGeometrywith two live typed arrays:positions/colorsare the exactFloat32Arrays passed asargstobufferAttribute(attach: "attributes-position"/"attributes-color") —onFramemutates them in place and flipsneedsUpdate, the same live-buffer pattern as the Wave Field recipe'sInstancedMeshmatrix rewrite. See Attach inference. - Per-particle stagger:
hashUnit(index)derives a deterministic pseudo-random[0, 1)value from each index (a cheapsin-based hash, noMath.randomat runtime) and scales it into a delay up toMAX_STAGGERseconds. A particle's local progress is(transitionElapsed - staggerDelays[index]) / EASE_DURATION, clamped and run througheaseInOutCubic— nearby indices land close in delay, but since particle index has no spatial meaning here, the "ripple" reads as a staggered shimmer through the cloud rather than a directional wave. - Interruptible transitions:
beginTransitionsnapshots whateverpositions/colorscurrently hold — not necessarily a settled shape — intofromPositions/fromColorsbefore retargeting. Clicking mid-transition restarts the ease from wherever the cloud actually is, with no visible snap. onFrame: (root, delta, self)drives a slow continuous spin (self.rotation.y += delta * 0.05, always on) plus the auto-cycle timer (cycleElapsed >= CYCLE_INTERVALtriggers the nextbeginTransition) and the per-vertex morph loop itself, only while a transition is in flight. See Animation & Loop.frameloop: "always": both the idle spin and the auto-cycle timer are free-running, not driven by anyStatechange, so"demand"(which only renders afterinvalidate()) would leave the cycle frozen. See frameloop modes.- Click-to-advance via an invisible bounds mesh: a
sphereGeometrymesh withmeshBasicMaterial({ transparent: true, opacity: 0 })carries theonClickhandler instead of the particles themselves — raycasting 8000 individual points on every pointer move would be wasteful, andpointshere has no pointer props at all so it's never registered as an interactive target. See Excluding an object from raycasting and the pointer event props table foronClick. - Additive blending, per-shape hue:
vertexColors: trueplusblending: AdditiveBlending,depthWrite: false, andopacity: 0.85is the same particle-cloud recipe as Galaxy — overlapping points brighten instead of occluding, and since color eases through the same staggered per-particle progress as position, the hue shift ripples in lockstep with the shape change.