Spaces:
Running
Running
| 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} | |
| /> | |
| </> | |
| ); | |
| }; | |