Here we render 144 particles and cycle the geometry of their formation, animating with the Cannon Physics library, in less lines of code than there are particles!
const Particles = ({ formations }: Props) => {
const radius = 0.1
const [ref, api] = useSphere(index => ({
mass: 1,
args: radius,
position: formations[0][index],
}))
const [index, setIndex] = useState(0)
const cycleIndex = () =>
void setIndex(p => (p < formations.length - 1 ? p + 1 : 0))
useInterval(cycleIndex, 5000)
useEffect(() => {
let unsubscribers: any[] = []
for (const [[x1, y1, z1], i] of formations[index].map(
(v, i) => [v, i] as const
)) {
unsubscribers.push(
api.at(i).position.subscribe(([x0, y0, z0]) => {
api.at(i).velocity.set(x1 - x0, y1 - y0, z1 - z0)
})
)
}
return () =>
void unsubscribers.reduce((_, unsubscribe) => void unsubscribe())
}, [api, formations, index])
return (
<instancedMesh ref={ref} args={[null, null, formations[0].length] as any}>
<sphereBufferGeometry args={[radius]} />
<meshBasicMaterial />
</instancedMesh>
)
}
const App = () => {
const formations = useMemo(() => {
const planeVertices = getVertices(new PlaneBufferGeometry(5, 5, 11, 11))
const icosahedronVertices = getVertices(
new IcosahedronBufferGeometry(5, 3),
1.0000000000000001
).slice(1, 145)
const heartVertices = getHeart()
return [planeVertices, icosahedronVertices, heartVertices]
}, [])
return (
<Physics gravity={[0, 0, 0]}>
<Particles formations={formations} />
</Physics>
)
}
Bonus: Make them Dance
With this simple change, the particles will dance around in a slightly chaotic yet beautiful fashion.
// useInterval(cycleIndex, 5000)
useInterval(cycleIndex, 500)
// ...
// api.at(i).velocity.set(x1 - x0, y1 - y0, z1 - z0)
api.at(i).applyImpulse([x1 - x0, y1 - y0, z1 - z0], [x0, y0, z0])
Here's another demo, check it out.
Get in touch
If you have any questions or ideas, please email me at tom@bearjam.dev.