react-particle-physics 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BEST_PRACTICES.md +45 -0
- package/README.md +42 -0
- package/dist/LineSystem.d.ts +29 -0
- package/dist/ParticleSystem.d.ts +32 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1035 -0
- package/dist/index.mjs +994 -0
- package/dist/lineShaders.d.ts +2 -0
- package/dist/particleShaders.d.ts +4 -0
- package/dist/performance.d.ts +14 -0
- package/dist/sampleLogoToParticles.d.ts +19 -0
- package/package.json +51 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Particle Engine Architecture & Best Practices
|
|
2
|
+
|
|
3
|
+
This library achieves 60fps rendering of 100k+ particles on mobile hardware by heavily offloading work to the GPU and entirely bypassing React's render lifecycle during active animation.
|
|
4
|
+
|
|
5
|
+
Here are the core architectural best practices and shader logic we extracted from the project:
|
|
6
|
+
|
|
7
|
+
## 1. Zero React Overhead (The Uniform Mutation Pattern)
|
|
8
|
+
React Three Fiber's `useFrame` hook gives us direct access to the render loop. However, updating React state inside this loop causes constant re-rendering, completely destroying performance.
|
|
9
|
+
|
|
10
|
+
**Practice**: We never store animation frames or pointer coordinates in React `useState`. Instead, we hold a `useMemo` reference to the `THREE.ShaderMaterial` and mutate `material.uniforms` directly inside `useFrame`.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// INSIDE useFrame (Runs 60x per second)
|
|
14
|
+
// Do NOT call setX() or setState()
|
|
15
|
+
particleMaterial.uniforms.uTime.value = clock.elapsedTime;
|
|
16
|
+
particleMaterial.uniforms.uPointer.value.copy(pointerCurrent.current);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 2. Ping-Pong FBO Physics (GPU Trail Buffer)
|
|
20
|
+
To calculate fluid-like displacement, particles need to know not just where the cursor *is*, but where it *was*. Running this physics simulation on the CPU for 100k particles is too slow.
|
|
21
|
+
|
|
22
|
+
**Practice**: We use a **Ping-Pong Framebuffer Object (FBO)**.
|
|
23
|
+
1. We set up an invisible offscreen scene containing a 2D quad and a custom shader (`trailFragmentShader.glsl`).
|
|
24
|
+
2. We maintain two render targets (`write` and `read`).
|
|
25
|
+
3. Every frame, we render the current pointer position onto the `write` target, blending it over the `read` target (which holds the previous frame).
|
|
26
|
+
4. We multiply the color value by a `uDecay` value to slowly fade out old pointer coordinates over time.
|
|
27
|
+
5. We swap the `read` and `write` buffers, and pass the `read` texture into the main particle shader.
|
|
28
|
+
|
|
29
|
+
The particle vertex shader then looks up its own UV coordinate inside this trail texture to see if the fluid has been disturbed nearby, displacing its `gl_Position` on the GPU.
|
|
30
|
+
|
|
31
|
+
## 3. BufferGeometry & Typed Arrays
|
|
32
|
+
Instancing individual `THREE.Mesh` objects is too expensive.
|
|
33
|
+
|
|
34
|
+
**Practice**: We use a single `THREE.Points` or `THREE.LineSegments` draw call.
|
|
35
|
+
During the one-time `sampleImageToParticles` pass, we parse the 2D image canvas and construct flat `Float32Array` objects for positions, colors, UVs, and seeds.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const positions = new Float32Array(particleCount * 3);
|
|
39
|
+
```
|
|
40
|
+
We inject these directly into a `THREE.BufferGeometry`. This maps the data directly into GPU memory, allowing the WebGL instance to render the entire scene in one extremely efficient draw call.
|
|
41
|
+
|
|
42
|
+
## 4. Hardware Scaling
|
|
43
|
+
**Practice**: We use dynamic pixel ratios.
|
|
44
|
+
`Math.min(window.devicePixelRatio || 1, 2)`
|
|
45
|
+
This caps rendering to a maximum pixel ratio of 2, preventing high-density screens (like iPhone Pro Max 3x screens) from crashing due to over-rendering 100k particles at 3x resolution. We also keep `frustumCulled={false}` because the displacement shaders move vertices outside their original bounding box.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# React Particle Physics
|
|
2
|
+
|
|
3
|
+
A high-performance WebGL physics engine for React Three Fiber that converts images into interactive particle clouds and topographic lines.
|
|
4
|
+
|
|
5
|
+
Features 60fps performance on mobile, GPU-accelerated interaction, ping-pong fluid trails, and zero React overhead on the main render loop.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install react-particle-physics three @react-three/fiber
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Canvas } from '@react-three/fiber';
|
|
17
|
+
import { ParticleSystem } from 'react-particle-physics';
|
|
18
|
+
|
|
19
|
+
export default function App() {
|
|
20
|
+
return (
|
|
21
|
+
<Canvas camera={{ position: [0, 0, 2.5] }}>
|
|
22
|
+
<ParticleSystem
|
|
23
|
+
image="/path/to/logo.png"
|
|
24
|
+
particleCount={50000}
|
|
25
|
+
interactionRadius={0.25}
|
|
26
|
+
sampleMode="alpha"
|
|
27
|
+
/>
|
|
28
|
+
</Canvas>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Available Components
|
|
34
|
+
|
|
35
|
+
### `<ParticleSystem />`
|
|
36
|
+
Generates a `THREE.Points` particle cloud mapped to the brightness/alpha data of an input image.
|
|
37
|
+
|
|
38
|
+
### `<LineSystem />`
|
|
39
|
+
Generates a `THREE.LineSegments` topographic heightmap based on the luminance of the input image.
|
|
40
|
+
|
|
41
|
+
### `sampleLogoToParticles`
|
|
42
|
+
Headless utility to parse an image via 2D Canvas context and map it to `Float32Arrays` for injection into WebGL BufferGeometries.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParticleSampleMode } from './sampleLogoToParticles';
|
|
2
|
+
export type LineSystemProps = {
|
|
3
|
+
/** The URL or data URI of the image to sample */
|
|
4
|
+
image: string;
|
|
5
|
+
/** Approximate number of horizontal lines to generate */
|
|
6
|
+
lineCount?: number;
|
|
7
|
+
/** Rendering width of the lines */
|
|
8
|
+
lineWidth?: number;
|
|
9
|
+
/** Vertical displacement intensity from luminance */
|
|
10
|
+
lineAmplitude?: number;
|
|
11
|
+
/** How pixels are sampled */
|
|
12
|
+
sampleMode?: ParticleSampleMode;
|
|
13
|
+
/** Threshold for edge detection */
|
|
14
|
+
edgeThreshold?: number;
|
|
15
|
+
/** Line colors */
|
|
16
|
+
particleColor?: string;
|
|
17
|
+
colorMode?: 'base' | 'random' | 'original';
|
|
18
|
+
glowIntensity?: number;
|
|
19
|
+
opacityVariation?: number;
|
|
20
|
+
/** Physics and interaction */
|
|
21
|
+
interactionRadius?: number;
|
|
22
|
+
displacementStrength?: number;
|
|
23
|
+
/** Destruct sequence */
|
|
24
|
+
isDestructing?: boolean;
|
|
25
|
+
onDestructComplete?: () => void;
|
|
26
|
+
/** Callback when loading state changes */
|
|
27
|
+
onLoadStatus?: (isLoading: boolean, error: string | null) => void;
|
|
28
|
+
};
|
|
29
|
+
export declare function LineSystem({ image, lineCount, lineWidth, lineAmplitude, interactionRadius, displacementStrength, sampleMode, edgeThreshold, particleColor, colorMode, glowIntensity, opacityVariation, isDestructing, onDestructComplete, onLoadStatus, }: LineSystemProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { type ParticleSampleMode } from './sampleLogoToParticles';
|
|
3
|
+
export type ParticleSystemProps = {
|
|
4
|
+
/** The URL or data URI of the image to sample */
|
|
5
|
+
image: string;
|
|
6
|
+
/** Number of particles to sample (default: 50000) */
|
|
7
|
+
particleCount?: number;
|
|
8
|
+
/** How pixels are sampled from the image */
|
|
9
|
+
sampleMode?: ParticleSampleMode;
|
|
10
|
+
/** Thresholds for sampling */
|
|
11
|
+
alphaThreshold?: number;
|
|
12
|
+
lumaThreshold?: number;
|
|
13
|
+
edgeThreshold?: number;
|
|
14
|
+
/** Particle appearance */
|
|
15
|
+
particleSize?: number;
|
|
16
|
+
particleColor?: string;
|
|
17
|
+
colorMode?: 'base' | 'random' | 'original';
|
|
18
|
+
glowIntensity?: number;
|
|
19
|
+
opacityVariation?: number;
|
|
20
|
+
/** Physics and interaction */
|
|
21
|
+
interactionRadius?: number;
|
|
22
|
+
displacementStrength?: number;
|
|
23
|
+
trailStrength?: number;
|
|
24
|
+
/** Destruct sequence */
|
|
25
|
+
isDestructing?: boolean;
|
|
26
|
+
onDestructComplete?: () => void;
|
|
27
|
+
/** Callback ref for the underlying THREE.Points mesh */
|
|
28
|
+
onPointsRef?: (ref: THREE.Points | null) => void;
|
|
29
|
+
/** Callback when loading state changes */
|
|
30
|
+
onLoadStatus?: (isLoading: boolean, error: string | null) => void;
|
|
31
|
+
};
|
|
32
|
+
export declare function ParticleSystem({ image, particleCount, interactionRadius, displacementStrength, particleSize, trailStrength, sampleMode, alphaThreshold, lumaThreshold, edgeThreshold, particleColor, colorMode, glowIntensity, opacityVariation, isDestructing, onDestructComplete, onPointsRef, onLoadStatus, }: ParticleSystemProps): import("react").JSX.Element | null;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ParticleSystem, type ParticleSystemProps } from './ParticleSystem';
|
|
2
|
+
export { LineSystem, type LineSystemProps } from './LineSystem';
|
|
3
|
+
export { sampleLogoToParticles, type ParticleSample, type ParticleSampleMode } from './sampleLogoToParticles';
|
|
4
|
+
export { detectPerformanceTier, getPresetSettings, type PerformanceTier } from './performance';
|