1

I'm trying to create particle trails very similar to those depicted in windy.com. Most of my googling attempts led me down the three.js route, and then I found react-three-fiber, which I'm hoping can simplify things, given I'm really not much of a javascript coder, much less a three.js expert.

I found a cool postprocessing tool in react-three-fiber's @react-three/drei add-on, called Trail.

I also found a few examples of how to create particles that move around without my CPU catching fire, and the below is a working example.

import * as THREE from 'three';
import React, { useRef, useMemo } from 'react';
import { Canvas, useFrame } from "@react-three/fiber";
import Random from 'canvas-sketch-util/random';
import { Sphere, Trail } from "@react-three/drei"
import "./App.css"


function Particles({ count }) {
  const mesh = useRef();
  const light = useRef();

  const particles = useMemo(() => {
    const temp = [];
    for (let i = 0; i < count; i++) {
      const time = Random.range(0, 100);
      const factor = Random.range(20, 120);
      const speed = Random.range(0.01, 0.015) / 2;
      const x = Random.range(-50, 50);
      const y = Random.range(-50, 50);
      const z = Random.range(-50, 50);

      temp.push({ time, factor, speed, x, y, z });
    }
    return temp;
  }, [count]);

  const dummy = useMemo(() => new THREE.Object3D(), []);

  useFrame(() => {
    particles.forEach((particle, index) => {
      let { factor, speed, x, y, z } = particle;
      const t = (particle.time += speed);
      dummy.position.set(
        x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10,
        y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10,
        z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10
      );
      const s = Math.cos(t);
      dummy.scale.set(s, s, s);
      dummy.rotation.set(s * 5, s * 5, s * 5);
      dummy.updateMatrix();
      mesh.current.setMatrixAt(index, dummy.matrix);
    });
    mesh.current.instanceMatrix.needsUpdate = true;
  });

  return (
    <>
      <pointLight ref={light} distance={40} intensity={8} color="lightblue" />
      <instancedMesh ref={mesh} args={[null, null, count]}>
        <dodecahedronBufferGeometry args={[0.2, 0]} />
        <meshPhongMaterial color="#050505" />
      </instancedMesh>
    </>
  );
}



function Comet(props) {
  const sphere = useRef();

  useFrame(({ clock }) => {
    const t = clock.getElapsedTime()
    sphere.current.position.x = props.factor*Math.sin(t) * 3
    sphere.current.position.y = Math.cos(t) * 3
    sphere.current.position.z = Math.cos(t) * 3
  })

  return (
    <>
      <Trail
        width={1}
        length={4}
        color={'black'}
        attenuation={(t) => {
          return t * t
        }}
      >
        <Sphere ref={sphere} args={[0.05, 32, 32]} position-x={props.position} position-y={0}>
          <meshBasicMaterial color="black" />
        </Sphere>
      </Trail>
    </>
  )
}


function App() {

  return (
    <div className="Parent">
      <Canvas>
        <Comet factor={1}/>
        <Particles count={400} />
      </Canvas>
    </div>
  );
}

export default App;

You'll need a create-react-app to paste the above App.js file into, but for simplicity, here's a repo you can clone with the simple animation.

You'll see the <Comet factor={1}/> component with its trail flying around the screen.

My goal is to add such a trail to every particle, much like what Windy do, again without the CPU catching fire.

What I'm lacking is the correct approach here. I think there are other kinds of ways to do the trail, such as three's older postprocessing tools like RenderPass and EffectComposer, but I've not been able to understand how to achieve this field of particle trails.

Can anyone point me in the right direction?

John Kealy
  • 1,503
  • 1
  • 13
  • 32

0 Answers0