ThreeJs codepen to React component

I've been trying to move this codepen.io to a react component, this what I did:

React Custom Hook

import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { createNoise4D } from 'simplex-noise';
import { random } from 'chroma-js';

interface Config {
  el: string;
  fov: number;
  cameraZ: number;
  xyCoef: number;
  zCoef: number;
  lightIntensity: number;
  ambientColor: number;
  light1Color: string;
  light2Color: string;
  light3Color: string;
  light4Color: string;
}

interface UseBackgroundAnimationReturn {
  noiseInputRef: React.RefObject<HTMLInputElement>;
  heightInputRef: React.RefObject<HTMLInputElement>;
}

const useBackgroundAnimation = (config: Config): UseBackgroundAnimationReturn => {
  const defaultConfig: Config = {
    fov: 75,
    cameraZ: 75,
    xyCoef: 70,
    zCoef: 20,
    lightIntensity: 1,
    ambientColor: 0xff0000,
    light1Color: '#0E09DC',
    light2Color: '#1CD1E1',
    light3Color: '#18C02C',
    light4Color: '#ee3bcf',
    ...config,
  };

  const conf = { ...defaultConfig, ...config };
  const rendererRef = useRef<THREE.WebGLRenderer>();
  const cameraRef = useRef<THREE.PerspectiveCamera>();
  const sceneRef = useRef<THREE.Scene>();
  const planeRef = useRef<THREE.Mesh>();
  const lightRefs = useRef<THREE.PointLight[]>([]);
  const mouseRef = useRef(new THREE.Vector2());

  const noiseInputRef = useRef<HTMLInputElement>(null);
  const heightInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    const init = () => {
      const canvas = document.getElementById(conf.el) as HTMLCanvasElement;
      if (!canvas) return;

      const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
      const camera = new THREE.PerspectiveCamera(conf.fov);
      camera.position.z = conf.cameraZ;

      rendererRef.current = renderer;
      cameraRef.current = camera;

      updateSize();
      window.addEventListener('resize', updateSize);

      initScene();
      initGui();
      animate();
    };

    const initGui = () => {
      if (noiseInputRef.current) {
        noiseInputRef.current.value = (101 - conf.xyCoef).toString();
        noiseInputRef.current.addEventListener('change', (e: Event) => {
          conf.xyCoef = 101 - (e.target as HTMLInputElement).valueAsNumber;
        });
      }

      if (heightInputRef.current) {
        heightInputRef.current.value = ((conf.zCoef * 100) / 25).toString();
        heightInputRef.current.addEventListener('change', (e: Event) => {
          conf.zCoef = ((e.target as HTMLInputElement).valueAsNumber * 25) / 100;
        });
      }

      document.getElementById('trigger')?.addEventListener('click', updateLightsColors);
    };

    const initScene = () => {
      const scene = new THREE.Scene();
      sceneRef.current = scene;
      initLights();

      const mat = new THREE.MeshLambertMaterial({ color: 0xff0000, side: THREE.DoubleSide });
      // const mat = new THREE.MeshStandardMaterial({ color: 0xffff00, side: THREE.DoubleSide });


      const geo = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight, window.innerWidth / 2, window.innerHeight / 2);
      const plane = new THREE.Mesh(geo, mat);
      planeRef.current = plane;

      scene.add(plane);
      plane.rotation.x = -Math.PI / 2 - 0.2;
      plane.position.y = -25;
      cameraRef.current!.position.z = 60;
    };

    const initLights = () => {
      const lightDistance = 500;

      // Adding Ambient Light
      const ambientLight = new THREE.AmbientLight(0x404040, 1); // soft white light
      sceneRef.current!.add(ambientLight);

      const light1 = new THREE.PointLight(conf.light1Color, conf.lightIntensity, lightDistance);
      const light2 = new THREE.PointLight(conf.light2Color, conf.lightIntensity, lightDistance);
      const light3 = new THREE.PointLight(conf.light3Color, conf.lightIntensity, lightDistance);
      const light4 = new THREE.PointLight(conf.light4Color, conf.lightIntensity, lightDistance);

      light1.position.set(0, 50, 30);
      light2.position.set(0, -50, -30);
      light3.position.set(50, 50, 0);
      light4.position.set(-50, 50, 0);

      lightRefs.current = [light1, light2, light3, light4];

      sceneRef.current!.add(light1);
      sceneRef.current!.add(light2);
      sceneRef.current!.add(light3);
      sceneRef.current!.add(light4);
    };


    const animate = () => {
      setTimeout(() => {

      }, 500);
      requestAnimationFrame(animate);

      animatePlane();
      animateLights();

      rendererRef.current!.render(sceneRef.current!, cameraRef.current!);
    };

    const animatePlane = () => {
      const gArray = planeRef.current!.geometry.attributes.position.array;
      const time = Date.now() * 0.0001;
      const noise4D = createNoise4D();

      for (let i = 0; i < gArray.length; i += 3) {
        gArray[i + 2] = noise4D(gArray[i] / conf.xyCoef, gArray[i + 1] / conf.xyCoef, time, mouseRef.current.x + mouseRef.current.y) * conf.zCoef;
      }

      planeRef.current!.geometry.attributes.position.needsUpdate = true;
    };

    const animateLights = () => {
      const time = Date.now() * 0.001;
      const d = 50;
      lightRefs.current.forEach((light, i) => {
        light.position.x = Math.sin(time * 0.1 * (i + 1)) * d;
        light.position.z = Math.cos(time * 0.2 * (i + 1)) * d;
      });
    };

    const updateLightsColors = () => {
      conf.light1Color = random().hex();
      conf.light2Color = random().hex();
      conf.light3Color = random().hex();
      conf.light4Color = random().hex();

      lightRefs.current[0].color = new THREE.Color(conf.light1Color);
      lightRefs.current[1].color = new THREE.Color(conf.light2Color);
      lightRefs.current[2].color = new THREE.Color(conf.light3Color);
      lightRefs.current[3].color = new THREE.Color(conf.light4Color);
    };

    const updateSize = () => {
      const width = window.innerWidth;
      const height = window.innerHeight;

      rendererRef.current!.setSize(width, height);
      cameraRef.current!.aspect = width / height;
      cameraRef.current!.updateProjectionMatrix();
    };

    init();

    return () => {
      window.removeEventListener('resize', updateSize);
    };
  }, [conf]);

  return {
    noiseInputRef,
    heightInputRef,
  };
};

export default useBackgroundAnimation;

and this is the component where I use it

import useBackgroundAnimation from './useBackgroundAnimation';

const BackgroundComponent: React.FC = () => {
  const { noiseInputRef,
    heightInputRef,
  } = useBackgroundAnimation({
    el: 'backgroundCanvas',
  });

  return (
    <div className="text-center min-h-screen flex flex-col justify-center items-center bg-white">
      <div id="page" className="max-w-3xl mx-auto">
        <header className="mb-6">
          <div className="inner">
            <h3 className="masthead-brand">Background #1</h3>
            <nav className="nav nav-masthead justify-content-center">
              <a className="nav-link" href="https://codepen.io/soju22/" target="_blank" rel="noopener noreferrer">Codepen Profile</a>
              <a className="nav-link" href="https://codepen.io/collection/AGZywR" target="_blank" rel="noopener noreferrer">ThreeJS Collection</a>
            </nav>
          </div>
        </header>

        <main role="main" className="inner cover">
          <h2>Interactive Background</h2>
          <p className="lead">
            This simple interactive background is made with
            <a href="https://threejs.org" target="_blank" rel="noopener noreferrer"> ThreeJS</a>
            and a
            <a href="https://threejs.org/docs/#api/en/geometries/PlaneBufferGeometry" target="_blank" rel="noopener noreferrer"> PlaneBufferGeometry</a>
            animated with Simplex noise.
          </p>

          <form className="w-4/5 mx-auto">
            <div className="form-row">
              <div className="form-group col-sm-6">
                <label htmlFor="noiseInput" className="form-label">Noise Coef</label>
                <input ref={noiseInputRef} type="range" min="1" max="100" className="custom-range" id="noiseInput" />
              </div>
              <div className="form-group col-sm-6">
                <label htmlFor="heightInput" className="form-label">Height Coef</label>
                <input ref={heightInputRef} type="range" min="1" max="100" className="custom-range" id="heightInput" />
              </div>
            </div>
            <div className="form-group col-md-12">
              <button type="button" id="trigger" className="btn btn-sm btn-secondary">Random Colors</button>
            </div>
          </form>
        </main>

        <footer className="mastfoot mt-auto"></footer>
      </div>
      <canvas id="backgroundCanvas"></canvas>
    </div>
  );
};

export default BackgroundComponent;

And this is what I got:

enter image description here

The problem: I can't replicate the color and the smooth movement.

Upvotes: 1

Views: 45

Answers (0)

Related Questions