npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

r3f-points-fx

v1.0.5

Published

React three fiber component for easily creating fbo particles meshes. Also for making the points to transition from one geometry to other.

Downloads

71

Readme

R3f Points FX

r3f-points-fx - npm

https://github.com/VedantSG123/R3FPointsFX/assets/103552663/b70cee56-86d6-48d0-8c15-0b38c05eb9f0

The R3FPointsFX component is a customizable particle system for 3D graphics built using the three.js library and integrated with React via the @react-three/fiber package. It allows you to create visually appealing particle effects in your 3D scenes.

Just pass the array of meshes (you can load meshes from gltf/glb files) and see the particles arrange in its shape. Transitions can be created from one model to another.

[!IMPORTANT]
Starting from version 1.0.4, changing the model index to transition from one arrangement to another has been added as a method of the ref of the comnponent. To prevent animation jerk caused at the end of transition due to re-render in react, it is recommended to use the methods exposed by the ref to the points mesh.

Table of Contents

Features

  • Create a particle system with customizable properties.
  • Use shaders to control particle behavior and appearance.
  • Incorporate data textures for particle positions.
  • Exposes methods to manipulate particle animations dynamically.
  • Highly performant as all the computation is done by shaders.

Requirements

  • @react-three/fiber
  • @react-three/drei
  • three
  • Your react three fiber scene 🤩

Installation

To use the R3FPointsFX component in your React three fiber application, you need to follow these installation steps:

  1. Make sure you have Node.js and npm (Node Package Manager) installed on your machine.

  2. Install the packages:

npm i r3f-points-fx

Examples

R3FPointsFX-basic - CodeSandbox

R3FPointsFx-Advance - CodeSandbox

Usage

Basic:

import React, { useRef, useEffect } from "react";
import { useFrame } from "@react-three/fiber";
import { R3FPointsFX } from "r3f-points-fx";

const MyComponent = ({ currentIndex }) => {
  // Use useRef to store previous index, start time, progress, and PointsFX reference
  const previousIndex = useRef(null);
  const startTime = useRef(0);
  const progress = useRef(0);
  const pointsRef = useRef(null);
  const duration = 2;

  /***
	Load your gltf meshes / define three.js defaut meshses and create an array of meshes
	currentIndex should be set as per the index of model we want to transition to from the current model
  ***/

  // Function to change the 3D model
  const changeModel = () => {
    if( !pointsRef.current ) return
    startTime.current = 0;
    pointsRef.current.setModelB(currentIndex);
  };

  // Use useEffect to trigger model change when currentIndex changes
  useEffect(() => {
    changeModel();
  }, [currentIndex]);

  // Use useFrame to handle animation and model transition
  useFrame((state) => {
    if (startTime.current === 0) {
      startTime.current = state.clock.elapsedTime;
    }

    const elapsed = state.clock.elapsedTime - startTime.current;

    progress.current = Math.min(elapsed / duration, 1);
    if (progress.current >= 1 && pointsRef.current) {
      pointsRef.current.setModelA(currentIndex);
      previousIndex.current = currentIndex;
    }

    if(pointsRef.current){
      pointsRef.current.updateProgress(progress.current);
      pointsRef.current.updateTime(state.clock.elapsedTime);
    }
    
  });

  return (
    <>
      {/* Other 3D components */}
      <R3FPointsFX
        modelsArray={/* Provide an array of models */}
        // Pass other props as needed
        ref={pointsRef}
        modelA={previousIndex.current}
        modelB={currentIndex}
      />
    </>
  );
};

export default MyComponent

For typescript:

import React, { useRef, useEffect } from "react";
import { useFrame } from "@react-three/fiber";
import { R3FPointsFX, R3FPointsFXRefType } from "r3f-points-fx";

interface MyComponentProps {
  currentModelIndex: number;
}

const MyComponent: React.FC<MyComponentProps> = ({ currentModelIndex }) => {
  const previousIndex = useRef<number | null>(null);
  const startTime = useRef(0);
  const progress = useRef(0);
  const pointsRef = useRef<R3FPointsFXRefType>(null);
  const duration = 2;

  /***
	Load your gltf meshes / define three.js defaut meshses and create an array of meshes
	currentIndex should be set as per the index of model we want to transition to from the current model
  ***/
  
  const changeModel = () => {
    startTime.current = 0;
    pointsRef.current?.setModelB(currentModelIndex);
  };

  useEffect(() => {
    changeModel();
  }, [currentModelIndex]);

  useFrame((state) => {
    if (startTime.current === 0) {
      startTime.current = state.clock.elapsedTime;
    }

    const elapsed = state.clock.elapsedTime - startTime.current;

    progress.current = Math.min(elapsed / duration, 1);
    if (progress.current >= 1) {
      pointsRef.current?.setModelA(previousRef.current);
      previousIndex.current = currentModelIndex;
    }

    pointsRef.current?.updateProgress(progress.current);
    pointsRef.current?.updateTime(state.clock.elapsedTime);
  });

  return (
    <>
      {/* Other 3D components */}
      <R3FPointsFX
        modelsArray={/* Provide an array of meshes */}
        // Pass other props as needed
        ref={pointsRef}
        modelA={previousIndex.current}
        modelB={currentModelIndex}
      />
    </>
  );
};

export default MyComponent;

Updating progress:

Always update the progress using pointsRef.

[!Warning] Do not use state variables for progress as it will cause serious performance issues.

The usage of pointsRef.udateProgress and pointsRef.updateTime is demonstrated in the sandbox 😊

Ref methods

| Method | Usage | Return | | ------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | getSimulationMesh | const mesh = pointsRef.curret.getSimulationMesh() | the ref to the simulation mesh | | getPointsMesh | const points = pointsRef.current.getPointsMesh() | the ref to points mesh (this can be used to modify the position, rotation, scaling of the points mesh ) | | updateProgress | pointsRef.current.updateProgress(progress:number) | - | | updateTime | pointsRef.current.updateTime(progress:number) | - | | setModelA | pointsRef.current.setModelA(index:number \| null) | - | | setModelB | pointsRef.current.setModelB(index:number \| null) | - |

[!IMPORTANT]
Updating the time for the shader using updateTime is mandatory and must be done inside useFrame or any other similar way.

Props

The R3FPointsFX component accepts the following props:

  • modelsArray: An array of 3D models used for generating particles.
  • pointsCount: The number of particles in the system (default = 128).
  • pointsVertFunctions: Custom vertex shader functions.
  • pointsFragFunctions: Custom fragment shader functions.
  • modelA: Index of the first model in modelsArray.
  • modelB: Index of the second model in modelsArray.
  • uniforms: Custom uniforms for shaders.
  • baseColor: The base color for the particles.
  • pointSize: The size of individual particles (default = 1.0).
  • alpha: The alpha (transparency) value for particles.
  • attributes: Additional attributes for the particle system.
  • blending: Blending mode for particles (default = THREE.AdditiveBlending).
  • Attributes of <points>...</points> (in react three fiber) such as position, rotation, scale can also be passed to control the respective parameters of the mesh.

Customization

You can pass your own uniforms and provided shader functions to give a more customized look to your particles 🤩. Use the provided shader function templates to control the color, size, shape, position of the particles. Please check the advance-example for better understanding about how customization works 😉.

Untitled-2023-12-15-1811

[!WARNING]
Do not modify the names of the function, you are supposed to use the global variables / input variables to the function to achieve the effect, even if you want your custom functions to run, just place them in these template functions.

Finally pass these templates (with whatever changes you have done) as props to the component. If your are not using any function just let it remain same as the template, changing the name or input params of these function will cause shader to crash.

GLSL Function templates:

Fragment Functions
//Uniforms which you have passed must be declared here
//For example:
//uniform float uColor1;


vec3 model_color(int model_index, vec3 position, float time){
  //model index is provided to color a specific mesh in the described way
  //color the model with index 0 with uColor1
  //For example:
  //if(model_index == 0){
  //   return uColor1;
  //}
  vec3 color = uColor;
  return color;
}

float alpha_shape(vec2 point_cord, float alpha){
  //particle center at (0.5, 0.5)
  //by default circular particles
  //for square particles remove the below code and just return alpha
  float dist = distance(point_cord, vec2(0.5));
  float final = dist > 0.5 ? 0.0 : alpha;
  return final;
}
Vertex Functions
//Uniforms or attributes which you have passed must be declared here
//For example:
//attribute vec3 aRandom;

vec3 position_calc(vec3 pos, float time){
  return pos;
}

float point_size(float distance_from_camera, float time){
  float size = uPointSize;
  return size;
}

Example:

function MyComponent() {
  //your other code
  const fragmentFunctions = `
	uniform vec3 uColor1;
	uniform vec3 uColor2;
	uniform vec3 uColor3;

	vec3 angleMix(vec3 position){
		float pi = asin(1.0) * 2.0;
		float angle = atan(position.z, position.x) + pi;
		float total = pi * 2.0;
		float scale = angle/total;
		float division = fract((scale + 1.0/6.0) * 3.0);
		division *= 6.0;
		division = clamp(division, 0.0, 1.0);
		vec3 color;
		if(angle <= total/6.0){
			color = mix(uColor3, uColor1, division);
		}else if(angle <= total/2.0){
			color = mix(uColor1, uColor2, division);
		}else if(angle <= (5.0*total)/6.0){
			color = mix(uColor2, uColor3, division);
		}else{
			color = mix(uColor3, uColor1, division);
		}
		return color;
	}

	vec3 model_color(int model_index, vec3 position, float time){
	  if(model_index == 1){
		  return angleMix(position);
	  }
	  vec3 color = uColor;
	  return color;
	}

	float alpha_shape(vec2 point_cord, float alpha){
	  //particle center at (0.5, 0.5)
	  //by default circular particles 
	  //for square particles remove the below code and just return alpha
	  float dist = distance(point_cord, vec2(0.5));
	  float final = dist > 0.5 ? 0.0 : alpha;
	  return final;
	}
  `
  const vertexFunctions = `
	attribute vec3 aRandom;
	vec3 position_calc(vec3 pos, float time){
	  //add some random motion to particles
	  pos.x += sin(time * aRandom.x) * 0.01;
      pos.y += cos(time * aRandom.y) * 0.01;
      pos.z += sin(time * aRandom.z) * 0.01;
	  return pos;
	}

	float point_size(float distance_from_camera, float time){
	  //Make particles closer to camera more bigger
      float size = uPointSize;
      float adjustedPointSize = 40.0 * pow(2.0, -distanceToCamera) * size*;
      return adjustedPointSize;
    }
  `
  const generateRandomnArray = (size: number) => {
    const length = size * size * 3
    const data = new Float32Array(length)

    for (let i = 0; i < length; i++) {
      const stride = i * 3

      data[stride] = Math.random() * 3 - 1
      data[stride + 1] = Math.random() * 3 - 1
      data[stride + 2] = Math.random() * 3 - 1
    }
    return data
  }

  const randomArray = useMemo(() => {
    return generateRandomnArray(128)
  }, [])

  return (
    <>
      <R3FPointsFX
        modelsArray={/* Provide an array of models */}
        // Customize other props as needed
        ref={pointsRef}
        modelA={modelA}
        modelB={modelB}
        uniforms={{
          uColor1: new THREE.Color("#D0BFFF"),
          uColor2: new THREE.Color("#DAFFFB"),
          uColor3: new THREE.Color("#FF6AC2"),
        }}
        attributes={[
          {
            name: "aRandom",
            array: randomArray,
            itemSize: 3,
          },
        ]}
        pointsVertFunctions={vertexFunctions}
        pointsFragFunctions={fragmentFunctions}
      />
    </>
  )
}

References

https://blog.maximeheckel.com/posts/the-magical-world-of-particles-with-react-three-fiber-and-shaders/

FBO particles – Youpi ! (barradeau.com)