mlabonne's picture
upload demo files (#1)
01488bc
import { createPortal, useFrame, useThree } from "@react-three/fiber";
import { useCallback, useMemo, useRef, useState } from "react";
import { Camera, Color, Mesh, Scene, Texture, Vector2, Vector3 } from "three";
import { ShaderPass } from "three/examples/jsm/Addons.js";
import { Effect as FluidEffect } from "./effect/Fluid";
import { useFBOs } from "./hooks/useFBOs";
import { useMaterials } from "./hooks/useMaterials";
import { DEFAULT_CONFIG } from "./constant";
import { usePointer } from "./hooks/usePointer";
import { normalizeScreenHz } from "./utils";
import { type FluidProps } from "./types";
type MaterialName = keyof ReturnType<typeof useMaterials>;
type FBONames = keyof ReturnType<typeof useFBOs>;
type Uniforms = {
uColor: Vector3 | Color;
uPointer: Vector2;
uTarget: Texture | null;
uVelocity: Texture;
uCurl: Texture;
uTexture: Texture;
uPressure: Texture;
uDivergence: Texture;
uSource: Texture;
uRadius: number;
uClearValue: number;
uCurlValue: number;
uDissipation: number;
};
export const Fluid = ({
blend = DEFAULT_CONFIG.blend,
force = DEFAULT_CONFIG.force,
radius = DEFAULT_CONFIG.radius,
curl = DEFAULT_CONFIG.curl,
swirl = DEFAULT_CONFIG.swirl,
intensity = DEFAULT_CONFIG.intensity,
distortion = DEFAULT_CONFIG.distortion,
fluidColor = DEFAULT_CONFIG.fluidColor,
backgroundColor = DEFAULT_CONFIG.backgroundColor,
showBackground = DEFAULT_CONFIG.showBackground,
rainbow = DEFAULT_CONFIG.rainbow,
pressure = DEFAULT_CONFIG.pressure,
densityDissipation = DEFAULT_CONFIG.densityDissipation,
velocityDissipation = DEFAULT_CONFIG.velocityDissipation,
blendFunction = DEFAULT_CONFIG.blendFunction,
}: FluidProps) => {
const size = useThree((three) => three.size);
const gl = useThree((three) => three.gl);
const [bufferScene] = useState(() => new Scene());
const bufferCamera = useMemo(() => new Camera(), []);
const meshRef = useRef<Mesh>(null);
const postRef = useRef<ShaderPass>(null);
const pointerRef = useRef(new Vector2());
const colorRef = useRef(new Vector3());
const FBOs = useFBOs();
const materials = useMaterials();
const splatStack = usePointer({ force });
const setShaderMaterial = useCallback(
(name: MaterialName) => {
if (!meshRef.current) return;
meshRef.current.material = materials[name];
meshRef.current.material.needsUpdate = true;
},
[materials],
);
const setRenderTarget = useCallback(
(name: FBONames) => {
const target = FBOs[name];
if ("write" in target) {
gl.setRenderTarget(target.write);
gl.clear();
gl.render(bufferScene, bufferCamera);
target.swap();
} else {
gl.setRenderTarget(target);
gl.clear();
gl.render(bufferScene, bufferCamera);
}
},
[bufferCamera, bufferScene, FBOs, gl],
);
const setUniforms = useCallback(
<K extends keyof Uniforms>(
material: MaterialName,
uniform: K,
value: Uniforms[K],
) => {
const mat = materials[material];
if (mat && mat.uniforms[uniform]) {
mat.uniforms[uniform].value = value;
}
},
[materials],
);
useFrame((_, delta) => {
if (!meshRef.current || !postRef.current) return;
for (let i = splatStack.length - 1; i >= 0; i--) {
const { mouseX, mouseY, velocityX, velocityY } = splatStack[i];
pointerRef.current.set(mouseX, mouseY);
colorRef.current.set(velocityX, velocityY, 10.0);
setShaderMaterial("splat");
setUniforms("splat", "uTarget", FBOs.velocity.read.texture);
setUniforms("splat", "uPointer", pointerRef.current);
setUniforms("splat", "uColor", colorRef.current);
setUniforms("splat", "uRadius", radius / 100.0);
setRenderTarget("velocity");
setUniforms("splat", "uTarget", FBOs.density.read.texture);
setRenderTarget("density");
splatStack.pop();
}
setShaderMaterial("curl");
setUniforms("curl", "uVelocity", FBOs.velocity.read.texture);
setRenderTarget("curl");
setShaderMaterial("vorticity");
setUniforms("vorticity", "uVelocity", FBOs.velocity.read.texture);
setUniforms("vorticity", "uCurl", FBOs.curl.texture);
setUniforms("vorticity", "uCurlValue", curl);
setRenderTarget("velocity");
setShaderMaterial("divergence");
setUniforms("divergence", "uVelocity", FBOs.velocity.read.texture);
setRenderTarget("divergence");
setShaderMaterial("clear");
setUniforms("clear", "uTexture", FBOs.pressure.read.texture);
setUniforms("clear", "uClearValue", normalizeScreenHz(pressure, delta));
setRenderTarget("pressure");
setShaderMaterial("pressure");
setUniforms("pressure", "uDivergence", FBOs.divergence.texture);
for (let i = 0; i < swirl; i++) {
setUniforms("pressure", "uPressure", FBOs.pressure.read.texture);
setRenderTarget("pressure");
}
setShaderMaterial("gradientSubstract");
setUniforms("gradientSubstract", "uPressure", FBOs.pressure.read.texture);
setUniforms("gradientSubstract", "uVelocity", FBOs.velocity.read.texture);
setRenderTarget("velocity");
setShaderMaterial("advection");
setUniforms("advection", "uVelocity", FBOs.velocity.read.texture);
setUniforms("advection", "uSource", FBOs.velocity.read.texture);
setUniforms(
"advection",
"uDissipation",
normalizeScreenHz(velocityDissipation, delta),
);
setRenderTarget("velocity");
setUniforms("advection", "uVelocity", FBOs.velocity.read.texture);
setUniforms("advection", "uSource", FBOs.density.read.texture);
setUniforms(
"advection",
"uDissipation",
normalizeScreenHz(densityDissipation, delta),
);
setRenderTarget("density");
});
return (
<>
{createPortal(
<mesh ref={meshRef} scale={[size.width, size.height, 1]}>
<planeGeometry args={[2, 2]} />
</mesh>,
bufferScene,
)}
<FluidEffect
blendFunction={blendFunction}
intensity={intensity}
rainbow={rainbow}
distortion={distortion}
backgroundColor={backgroundColor}
blend={blend}
fluidColor={fluidColor}
showBackground={showBackground}
ref={postRef}
tFluid={FBOs.density.read.texture}
/>
</>
);
};