foundry-component-library 0.2.8 → 0.2.10
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/lib/components/Awards/index.tsx +37 -32
- package/lib/components/Awards/styles.module.scss +26 -1
- package/lib/components/ServiceHubsTeaserEffects/TileBalls.tsx +35 -0
- package/lib/components/ServiceHubsTeaserEffects/TileFluid.tsx +29 -0
- package/lib/components/ServiceHubsTeaserEffects/TileGlass.tsx +23 -0
- package/lib/components/ServiceHubsTeaserEffects/TileRays.tsx +94 -0
- package/lib/components/ServiceHubsTeaserEffects/bubbles/Bubble.tsx +35 -0
- package/lib/components/ServiceHubsTeaserEffects/bubbles/Bubbles.tsx +44 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/Fluid.tsx +192 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/constant.ts +23 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/effect/Fluid.tsx +22 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/effect/FluidEffect.tsx +58 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/advection.frag +16 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/base.vert +26 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/clear.frag +7 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/composite.frag +41 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/curl.frag +22 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/divergence.frag +41 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/gradientSubstract.frag +26 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/pressure.frag +28 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/splat.frag +19 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl/vorticity.frag +36 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/glsl.d.ts +14 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/hooks/useDoubleFBO.tsx +37 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/hooks/useFBOs.tsx +71 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/hooks/useMaterials.tsx +242 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/hooks/usePointer.tsx +54 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/index.ts +5 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/types.ts +27 -0
- package/lib/components/ServiceHubsTeaserEffects/fluid/utils.ts +12 -0
- package/lib/components/ServiceHubsTeaserEffects/glass/Lens.tsx +66 -0
- package/lib/components/ServiceHubsTeaserEffects/index.tsx +67 -0
- package/lib/components/ServiceHubsTeaserEffects/rays/LightSource.tsx +64 -0
- package/lib/components/ServiceHubsTeaserEffects/rays/noise.js +97 -0
- package/lib/components/ServiceHubsTeaserEffects/styles.module.scss +135 -0
- package/lib/index.ts +2 -0
- package/lib/queries/getHomePage.ts +10 -0
- package/lib/queries/getPageBySlug.ts +39 -4
- package/package.json +6 -2
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useRef } from "react";
|
|
3
2
|
import styles from "./styles.module.scss";
|
|
4
3
|
import Container from "../Container";
|
|
5
4
|
import { NextImage } from "../../types";
|
|
6
|
-
import useDrag from "../../hooks/useDrag";
|
|
7
5
|
import WavyText from "../TextAnimations/WavyText";
|
|
8
6
|
import { motion } from "framer-motion";
|
|
9
7
|
|
|
8
|
+
// Infinite marquee animation settings
|
|
9
|
+
const marqueeVariants = {
|
|
10
|
+
animate: {
|
|
11
|
+
x: [0, -"100%"],
|
|
12
|
+
transition: {
|
|
13
|
+
x: {
|
|
14
|
+
repeat: Infinity,
|
|
15
|
+
repeatType: "loop",
|
|
16
|
+
duration: 20,
|
|
17
|
+
ease: "linear",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
10
23
|
const Awards = ({
|
|
11
24
|
heading,
|
|
12
25
|
awards,
|
|
@@ -22,49 +35,41 @@ const Awards = ({
|
|
|
22
35
|
}[];
|
|
23
36
|
Image: NextImage;
|
|
24
37
|
}) => {
|
|
25
|
-
|
|
26
|
-
const { handleMouseDown, handleMouseMove, handleMouseUp, dragStyle } =
|
|
27
|
-
useDrag(sectionRef);
|
|
38
|
+
if (!awards) return null;
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
// Duplicate awards to create seamless looping
|
|
41
|
+
const marqueeItems = [...awards, ...awards];
|
|
30
42
|
|
|
31
43
|
return (
|
|
32
44
|
<motion.div
|
|
33
45
|
className={styles.awards}
|
|
34
46
|
initial={{ opacity: 0, y: -5 }}
|
|
35
47
|
animate={{ opacity: 1, y: 0 }}
|
|
36
|
-
transition={{
|
|
37
|
-
duration: 1,
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
48
|
+
transition={{ duration: 1 }}>
|
|
40
49
|
<Container>
|
|
41
50
|
{heading && <WavyText className={styles.heading} text={heading} />}
|
|
42
51
|
</Container>
|
|
43
52
|
<Container noMobilePadding>
|
|
44
|
-
<div
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
>
|
|
53
|
-
{awards.map((award, i) => {
|
|
54
|
-
const { image, heading, text } = award;
|
|
55
|
-
|
|
56
|
-
if (!image) return;
|
|
53
|
+
<div className={styles.marqueeWrapper}>
|
|
54
|
+
<motion.div
|
|
55
|
+
className={styles.marquee}
|
|
56
|
+
variants={marqueeVariants}
|
|
57
|
+
animate="animate">
|
|
58
|
+
{marqueeItems.map((award, i) => {
|
|
59
|
+
const { image, heading, text } = award;
|
|
60
|
+
if (!image) return null;
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
return (
|
|
63
|
+
<div key={image.sourceUrl + i} className={styles.award}>
|
|
64
|
+
<div className={styles.image}>
|
|
65
|
+
{image && <Image src={image.sourceUrl} alt="" fill />}
|
|
66
|
+
</div>
|
|
67
|
+
{heading && <div className={styles.text}>{heading}</div>}
|
|
68
|
+
{text && <div className={styles.client}>{text}</div>}
|
|
62
69
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
})}
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</motion.div>
|
|
68
73
|
</div>
|
|
69
74
|
</Container>
|
|
70
75
|
</motion.div>
|
|
@@ -21,14 +21,29 @@
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/* NEW — wrapper that clips the scrolling track */
|
|
25
|
+
.marqueeWrapper {
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
width: 100%;
|
|
28
|
+
position: relative;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* NEW — marquee track */
|
|
32
|
+
.marquee {
|
|
25
33
|
display: flex;
|
|
34
|
+
gap: 48px;
|
|
35
|
+
width: max-content;
|
|
36
|
+
animation: marquee 20s linear infinite;
|
|
26
37
|
|
|
27
38
|
@media #{$QUERY-sm} {
|
|
28
39
|
gap: 24px;
|
|
40
|
+
animation-duration: 16s;
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
|
|
44
|
+
/* Remove if using marquee (track should scroll) */
|
|
45
|
+
/* .items { display: flex; } */
|
|
46
|
+
|
|
32
47
|
.award {
|
|
33
48
|
width: 20%;
|
|
34
49
|
text-align: center;
|
|
@@ -85,3 +100,13 @@
|
|
|
85
100
|
font-size: 16px;
|
|
86
101
|
}
|
|
87
102
|
}
|
|
103
|
+
|
|
104
|
+
/* NEW — marquee animation: scroll half the track (because items are duplicated) */
|
|
105
|
+
@keyframes marquee {
|
|
106
|
+
0% {
|
|
107
|
+
transform: translateX(0);
|
|
108
|
+
}
|
|
109
|
+
100% {
|
|
110
|
+
transform: translateX(-50%);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Canvas } from "@react-three/fiber";
|
|
2
|
+
import { Environment, Text } from "@react-three/drei";
|
|
3
|
+
import { EffectComposer, N8AO, TiltShift2 } from "@react-three/postprocessing";
|
|
4
|
+
import Bubbles from "./bubbles/Bubbles";
|
|
5
|
+
|
|
6
|
+
const TileBalls = () => {
|
|
7
|
+
return (
|
|
8
|
+
<Canvas
|
|
9
|
+
shadows
|
|
10
|
+
dpr={[1, 2]}
|
|
11
|
+
gl={{ antialias: false }}
|
|
12
|
+
camera={{ fov: 15, position: [0, 0, 20] }}>
|
|
13
|
+
<color attach="background" args={["#380de8"]} />
|
|
14
|
+
<fog attach="fog" args={["red", 20, -5]} />
|
|
15
|
+
<ambientLight intensity={1.5} />
|
|
16
|
+
<pointLight position={[10, 10, 10]} intensity={1} castShadow />
|
|
17
|
+
<Bubbles />
|
|
18
|
+
<EffectComposer>
|
|
19
|
+
<N8AO aoRadius={6} intensity={2} color="red" />
|
|
20
|
+
<TiltShift2 blur={0.1} />
|
|
21
|
+
</EffectComposer>
|
|
22
|
+
<Environment preset="city" />
|
|
23
|
+
<Text
|
|
24
|
+
position={[0, 0, 0]}
|
|
25
|
+
fontSize={0.4}
|
|
26
|
+
textAlign="center"
|
|
27
|
+
fontWeight={600}
|
|
28
|
+
color="#fff">
|
|
29
|
+
Content and{"\n"}Campaigning
|
|
30
|
+
</Text>
|
|
31
|
+
</Canvas>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default TileBalls;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Text } from "@react-three/drei";
|
|
2
|
+
import { Canvas } from "@react-three/fiber";
|
|
3
|
+
import { EffectComposer } from "@react-three/postprocessing";
|
|
4
|
+
import { Fluid } from "./fluid/Fluid";
|
|
5
|
+
|
|
6
|
+
const TileFluid = () => {
|
|
7
|
+
return (
|
|
8
|
+
<Canvas
|
|
9
|
+
style={{
|
|
10
|
+
height: "100%",
|
|
11
|
+
width: "100%",
|
|
12
|
+
}}
|
|
13
|
+
camera={{ position: [0, 0, 20], fov: 15 }}>
|
|
14
|
+
<Text
|
|
15
|
+
position={[0, 0, 0]}
|
|
16
|
+
fontSize={0.4}
|
|
17
|
+
textAlign="center"
|
|
18
|
+
fontWeight={600}
|
|
19
|
+
color="#380de8">
|
|
20
|
+
Branding &{"\n"}Corporate ID
|
|
21
|
+
</Text>
|
|
22
|
+
<EffectComposer>
|
|
23
|
+
<Fluid backgroundColor="#fbff00" fluidColor="#380de8" rainbow={true} />
|
|
24
|
+
</EffectComposer>
|
|
25
|
+
</Canvas>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default TileFluid;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Canvas } from "@react-three/fiber";
|
|
2
|
+
import { Text } from "@react-three/drei";
|
|
3
|
+
import Lens from "./glass/Lens";
|
|
4
|
+
|
|
5
|
+
const TileGlass = () => {
|
|
6
|
+
return (
|
|
7
|
+
<Canvas camera={{ position: [0, 0, 20], fov: 15 }}>
|
|
8
|
+
<Lens>
|
|
9
|
+
<Text
|
|
10
|
+
position={[0, 0, 0]}
|
|
11
|
+
fontSize={0.4}
|
|
12
|
+
textAlign="center"
|
|
13
|
+
fontWeight={600}
|
|
14
|
+
color="#491b11">
|
|
15
|
+
Strategy &{"\n"}
|
|
16
|
+
Positioning
|
|
17
|
+
</Text>
|
|
18
|
+
</Lens>
|
|
19
|
+
</Canvas>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default TileGlass;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useRef } from "react";
|
|
4
|
+
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
|
5
|
+
import { OrbitControls, useGLTF } from "@react-three/drei";
|
|
6
|
+
import { Vector2, MeshBasicMaterial, DoubleSide } from "three";
|
|
7
|
+
import { LightSource } from "./rays/LightSource";
|
|
8
|
+
import {
|
|
9
|
+
EffectComposer,
|
|
10
|
+
EffectPass,
|
|
11
|
+
GodRaysEffect,
|
|
12
|
+
RenderPass,
|
|
13
|
+
} from "postprocessing";
|
|
14
|
+
|
|
15
|
+
function Slab({ url }) {
|
|
16
|
+
const gltf = useGLTF(url);
|
|
17
|
+
const slabRef = useRef();
|
|
18
|
+
const { camera, scene, gl } = useThree();
|
|
19
|
+
|
|
20
|
+
// GodRays light
|
|
21
|
+
const lightRef = useRef();
|
|
22
|
+
useFrame(({ pointer }) => {
|
|
23
|
+
if (lightRef.current) {
|
|
24
|
+
const LIGHT_RANGE = 5;
|
|
25
|
+
lightRef.current.position.x = pointer.x * LIGHT_RANGE;
|
|
26
|
+
lightRef.current.position.y = pointer.y * LIGHT_RANGE;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (!gltf) return;
|
|
32
|
+
|
|
33
|
+
const slab = gltf.scene;
|
|
34
|
+
slab.rotation.x = Math.PI / 2;
|
|
35
|
+
|
|
36
|
+
slab.traverse((obj) => {
|
|
37
|
+
if (obj.isMesh) {
|
|
38
|
+
obj.material = new MeshBasicMaterial({
|
|
39
|
+
color: 0x491b11,
|
|
40
|
+
side: DoubleSide,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
slabRef.current.add(slab);
|
|
46
|
+
|
|
47
|
+
// Setup postprocessing
|
|
48
|
+
const composer = new EffectComposer(gl);
|
|
49
|
+
composer.addPass(new RenderPass(scene, camera));
|
|
50
|
+
|
|
51
|
+
const gre = new GodRaysEffect(camera, lightRef.current, {
|
|
52
|
+
height: 480,
|
|
53
|
+
kernelSize: 2,
|
|
54
|
+
density: 1,
|
|
55
|
+
decay: 0.8,
|
|
56
|
+
weight: 0.5,
|
|
57
|
+
exposure: 0.3,
|
|
58
|
+
samples: 20,
|
|
59
|
+
clampMax: 0.95,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
composer.addPass(new EffectPass(camera, gre));
|
|
63
|
+
|
|
64
|
+
// Render loop override
|
|
65
|
+
const originalSetAnimationLoop = gl.setAnimationLoop;
|
|
66
|
+
gl.setAnimationLoop(() => composer.render());
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
// Cleanup
|
|
70
|
+
gl.setAnimationLoop(originalSetAnimationLoop);
|
|
71
|
+
composer.dispose();
|
|
72
|
+
};
|
|
73
|
+
}, [gltf, gl, scene, camera]);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
{/* <mesh ref={lightRef} position={[0, 0, -10]} /> */}
|
|
78
|
+
<group ref={slabRef} />
|
|
79
|
+
<LightSource ref={lightRef} position={[0, 0, -20]} />
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default function SlabScene() {
|
|
85
|
+
return (
|
|
86
|
+
<Canvas
|
|
87
|
+
camera={{ fov: 60, position: [0, 0, 10], near: 1, far: 100 }}
|
|
88
|
+
gl={{ antialias: true, powerPreference: "high-performance" }}>
|
|
89
|
+
{/* <ambientLight intensity={0.2} /> */}
|
|
90
|
+
{/* <OrbitControls /> */}
|
|
91
|
+
<Slab url="/slab-with-text.glb" />
|
|
92
|
+
</Canvas>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { Instance } from "@react-three/drei";
|
|
4
|
+
|
|
5
|
+
const Bubble = ({ factor, speed, xFactor, yFactor, zFactor }) => {
|
|
6
|
+
const ref = useRef();
|
|
7
|
+
|
|
8
|
+
useFrame((state) => {
|
|
9
|
+
const t = factor + state.clock.elapsedTime * (speed / 2);
|
|
10
|
+
|
|
11
|
+
ref.current!.scale.setScalar(Math.max(1.5, Math.cos(t) * 5));
|
|
12
|
+
|
|
13
|
+
ref.current!.position.set(
|
|
14
|
+
Math.cos(t) +
|
|
15
|
+
Math.sin(t * 1) / 10 +
|
|
16
|
+
xFactor +
|
|
17
|
+
Math.cos((t / 10) * factor) +
|
|
18
|
+
(Math.sin(t * 1) * factor) / 10,
|
|
19
|
+
Math.sin(t) +
|
|
20
|
+
Math.cos(t * 2) / 10 +
|
|
21
|
+
yFactor +
|
|
22
|
+
Math.sin((t / 10) * factor) +
|
|
23
|
+
(Math.cos(t * 2) * factor) / 10,
|
|
24
|
+
Math.sin(t) +
|
|
25
|
+
Math.cos(t * 2) / 10 +
|
|
26
|
+
zFactor +
|
|
27
|
+
Math.cos((t / 10) * factor) +
|
|
28
|
+
(Math.sin(t * 3) * factor) / 4
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return <Instance ref={ref} />;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default Bubble;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { MathUtils } from "three";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { Instances } from "@react-three/drei";
|
|
5
|
+
import Bubble from "./Bubble";
|
|
6
|
+
|
|
7
|
+
const particles = Array.from({ length: 20 }, () => ({
|
|
8
|
+
factor: MathUtils.randInt(20, 100),
|
|
9
|
+
speed: MathUtils.randFloat(0.01, 0.75),
|
|
10
|
+
xFactor: MathUtils.randFloatSpread(3),
|
|
11
|
+
yFactor: MathUtils.randFloatSpread(3),
|
|
12
|
+
zFactor: MathUtils.randFloatSpread(5),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const Bubbles = () => {
|
|
16
|
+
const ref = useRef(null);
|
|
17
|
+
|
|
18
|
+
useFrame(
|
|
19
|
+
(state, delta) =>
|
|
20
|
+
void (ref.current.rotation.y = MathUtils.damp(
|
|
21
|
+
ref.current.rotation.y,
|
|
22
|
+
(-state.pointer.x * Math.PI) / 6,
|
|
23
|
+
2.75,
|
|
24
|
+
delta
|
|
25
|
+
))
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Instances
|
|
30
|
+
limit={particles.length}
|
|
31
|
+
ref={ref}
|
|
32
|
+
castShadow
|
|
33
|
+
receiveShadow
|
|
34
|
+
position={[0, 2.5, 0]}>
|
|
35
|
+
<sphereGeometry args={[0.45, 64, 64]} />
|
|
36
|
+
<meshStandardMaterial roughness={1} color="#f0f0f0" />
|
|
37
|
+
{particles.map((data, i) => (
|
|
38
|
+
<Bubble key={i} {...data} />
|
|
39
|
+
))}
|
|
40
|
+
</Instances>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default Bubbles;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createPortal, useFrame, useThree } from '@react-three/fiber';
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Camera, Color, Mesh, Scene, Texture, Vector2, Vector3 } from 'three';
|
|
4
|
+
import { ShaderPass } from 'three/examples/jsm/Addons.js';
|
|
5
|
+
import { Effect as FluidEffect } from './effect/Fluid';
|
|
6
|
+
import { useFBOs } from './hooks/useFBOs';
|
|
7
|
+
import { useMaterials } from './hooks/useMaterials';
|
|
8
|
+
import { type FluidProps } from './types';
|
|
9
|
+
import { DEFAULT_CONFIG } from './constant';
|
|
10
|
+
import { usePointer } from './hooks/usePointer';
|
|
11
|
+
import { normalizeScreenHz } from './utils';
|
|
12
|
+
|
|
13
|
+
type MaterialName = keyof ReturnType<typeof useMaterials>;
|
|
14
|
+
type FBONames = keyof ReturnType<typeof useFBOs>;
|
|
15
|
+
|
|
16
|
+
type Uniforms = {
|
|
17
|
+
uColor: Vector3 | Color;
|
|
18
|
+
uPointer: Vector2;
|
|
19
|
+
uTarget: Texture | null;
|
|
20
|
+
uVelocity: Texture;
|
|
21
|
+
uCurl: Texture;
|
|
22
|
+
uTexture: Texture;
|
|
23
|
+
uPressure: Texture;
|
|
24
|
+
uDivergence: Texture;
|
|
25
|
+
uSource: Texture;
|
|
26
|
+
uRadius: number;
|
|
27
|
+
uClearValue: number;
|
|
28
|
+
uCurlValue: number;
|
|
29
|
+
uDissipation: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Fluid = ({
|
|
33
|
+
blend = DEFAULT_CONFIG.blend,
|
|
34
|
+
force = DEFAULT_CONFIG.force,
|
|
35
|
+
radius = DEFAULT_CONFIG.radius,
|
|
36
|
+
curl = DEFAULT_CONFIG.curl,
|
|
37
|
+
swirl = DEFAULT_CONFIG.swirl,
|
|
38
|
+
intensity = DEFAULT_CONFIG.intensity,
|
|
39
|
+
distortion = DEFAULT_CONFIG.distortion,
|
|
40
|
+
fluidColor = DEFAULT_CONFIG.fluidColor,
|
|
41
|
+
backgroundColor = DEFAULT_CONFIG.backgroundColor,
|
|
42
|
+
showBackground = DEFAULT_CONFIG.showBackground,
|
|
43
|
+
rainbow = DEFAULT_CONFIG.rainbow,
|
|
44
|
+
pressure = DEFAULT_CONFIG.pressure,
|
|
45
|
+
densityDissipation = DEFAULT_CONFIG.densityDissipation,
|
|
46
|
+
velocityDissipation = DEFAULT_CONFIG.velocityDissipation,
|
|
47
|
+
blendFunction = DEFAULT_CONFIG.blendFunction,
|
|
48
|
+
}: FluidProps) => {
|
|
49
|
+
const size = useThree((three) => three.size);
|
|
50
|
+
const gl = useThree((three) => three.gl);
|
|
51
|
+
|
|
52
|
+
const [bufferScene] = useState(() => new Scene());
|
|
53
|
+
const bufferCamera = useMemo(() => new Camera(), []);
|
|
54
|
+
|
|
55
|
+
const meshRef = useRef<Mesh>(null);
|
|
56
|
+
const postRef = useRef<ShaderPass>(null);
|
|
57
|
+
const pointerRef = useRef(new Vector2());
|
|
58
|
+
const colorRef = useRef(new Vector3());
|
|
59
|
+
|
|
60
|
+
const FBOs = useFBOs();
|
|
61
|
+
const materials = useMaterials();
|
|
62
|
+
const splatStack = usePointer({ force });
|
|
63
|
+
|
|
64
|
+
const setShaderMaterial = useCallback(
|
|
65
|
+
(name: MaterialName) => {
|
|
66
|
+
if (!meshRef.current) return;
|
|
67
|
+
|
|
68
|
+
meshRef.current.material = materials[name];
|
|
69
|
+
meshRef.current.material.needsUpdate = true;
|
|
70
|
+
},
|
|
71
|
+
[materials],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const setRenderTarget = useCallback(
|
|
75
|
+
(name: FBONames) => {
|
|
76
|
+
const target = FBOs[name];
|
|
77
|
+
|
|
78
|
+
if ('write' in target) {
|
|
79
|
+
gl.setRenderTarget(target.write);
|
|
80
|
+
gl.clear();
|
|
81
|
+
gl.render(bufferScene, bufferCamera);
|
|
82
|
+
target.swap();
|
|
83
|
+
} else {
|
|
84
|
+
gl.setRenderTarget(target);
|
|
85
|
+
gl.clear();
|
|
86
|
+
gl.render(bufferScene, bufferCamera);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[bufferCamera, bufferScene, FBOs, gl],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const setUniforms = useCallback(
|
|
93
|
+
<K extends keyof Uniforms>(material: MaterialName, uniform: K, value: Uniforms[K]) => {
|
|
94
|
+
const mat = materials[material];
|
|
95
|
+
|
|
96
|
+
if (mat && mat.uniforms[uniform]) {
|
|
97
|
+
mat.uniforms[uniform].value = value;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[materials],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
useFrame((_, delta) => {
|
|
104
|
+
if (!meshRef.current || !postRef.current) return;
|
|
105
|
+
|
|
106
|
+
for (let i = splatStack.length - 1; i >= 0; i--) {
|
|
107
|
+
const { mouseX, mouseY, velocityX, velocityY } = splatStack[i];
|
|
108
|
+
|
|
109
|
+
pointerRef.current.set(mouseX, mouseY);
|
|
110
|
+
colorRef.current.set(velocityX, velocityY, 10.0);
|
|
111
|
+
|
|
112
|
+
setShaderMaterial('splat');
|
|
113
|
+
setUniforms('splat', 'uTarget', FBOs.velocity.read.texture);
|
|
114
|
+
setUniforms('splat', 'uPointer', pointerRef.current);
|
|
115
|
+
setUniforms('splat', 'uColor', colorRef.current);
|
|
116
|
+
setUniforms('splat', 'uRadius', radius / 100.0);
|
|
117
|
+
setRenderTarget('velocity');
|
|
118
|
+
setUniforms('splat', 'uTarget', FBOs.density.read.texture);
|
|
119
|
+
setRenderTarget('density');
|
|
120
|
+
|
|
121
|
+
splatStack.pop();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setShaderMaterial('curl');
|
|
125
|
+
setUniforms('curl', 'uVelocity', FBOs.velocity.read.texture);
|
|
126
|
+
setRenderTarget('curl');
|
|
127
|
+
|
|
128
|
+
setShaderMaterial('vorticity');
|
|
129
|
+
setUniforms('vorticity', 'uVelocity', FBOs.velocity.read.texture);
|
|
130
|
+
setUniforms('vorticity', 'uCurl', FBOs.curl.texture);
|
|
131
|
+
setUniforms('vorticity', 'uCurlValue', curl);
|
|
132
|
+
setRenderTarget('velocity');
|
|
133
|
+
|
|
134
|
+
setShaderMaterial('divergence');
|
|
135
|
+
setUniforms('divergence', 'uVelocity', FBOs.velocity.read.texture);
|
|
136
|
+
setRenderTarget('divergence');
|
|
137
|
+
|
|
138
|
+
setShaderMaterial('clear');
|
|
139
|
+
setUniforms('clear', 'uTexture', FBOs.pressure.read.texture);
|
|
140
|
+
setUniforms('clear', 'uClearValue', normalizeScreenHz(pressure, delta));
|
|
141
|
+
setRenderTarget('pressure');
|
|
142
|
+
|
|
143
|
+
setShaderMaterial('pressure');
|
|
144
|
+
setUniforms('pressure', 'uDivergence', FBOs.divergence.texture);
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < swirl; i++) {
|
|
147
|
+
setUniforms('pressure', 'uPressure', FBOs.pressure.read.texture);
|
|
148
|
+
setRenderTarget('pressure');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setShaderMaterial('gradientSubstract');
|
|
152
|
+
setUniforms('gradientSubstract', 'uPressure', FBOs.pressure.read.texture);
|
|
153
|
+
setUniforms('gradientSubstract', 'uVelocity', FBOs.velocity.read.texture);
|
|
154
|
+
setRenderTarget('velocity');
|
|
155
|
+
|
|
156
|
+
setShaderMaterial('advection');
|
|
157
|
+
setUniforms('advection', 'uVelocity', FBOs.velocity.read.texture);
|
|
158
|
+
setUniforms('advection', 'uSource', FBOs.velocity.read.texture);
|
|
159
|
+
setUniforms('advection', 'uDissipation', normalizeScreenHz(velocityDissipation, delta));
|
|
160
|
+
|
|
161
|
+
setRenderTarget('velocity');
|
|
162
|
+
setUniforms('advection', 'uVelocity', FBOs.velocity.read.texture);
|
|
163
|
+
setUniforms('advection', 'uSource', FBOs.density.read.texture);
|
|
164
|
+
setUniforms('advection', 'uDissipation', normalizeScreenHz(densityDissipation, delta));
|
|
165
|
+
|
|
166
|
+
setRenderTarget('density');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<>
|
|
171
|
+
{createPortal(
|
|
172
|
+
<mesh ref={meshRef} scale={[size.width, size.height, 1]}>
|
|
173
|
+
<planeGeometry args={[2, 2]} />
|
|
174
|
+
</mesh>,
|
|
175
|
+
bufferScene,
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
<FluidEffect
|
|
179
|
+
blendFunction={blendFunction}
|
|
180
|
+
intensity={intensity}
|
|
181
|
+
rainbow={rainbow}
|
|
182
|
+
distortion={distortion}
|
|
183
|
+
backgroundColor={backgroundColor}
|
|
184
|
+
blend={blend}
|
|
185
|
+
fluidColor={fluidColor}
|
|
186
|
+
showBackground={showBackground}
|
|
187
|
+
ref={postRef}
|
|
188
|
+
tFluid={FBOs.density.read.texture}
|
|
189
|
+
/>
|
|
190
|
+
</>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BlendFunction } from 'postprocessing';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONFIG = {
|
|
4
|
+
blend: 5,
|
|
5
|
+
intensity: 2,
|
|
6
|
+
force: 1.1,
|
|
7
|
+
distortion: 0.4,
|
|
8
|
+
curl: 1.9,
|
|
9
|
+
radius: 0.3,
|
|
10
|
+
swirl: 4,
|
|
11
|
+
pressure: 0.8,
|
|
12
|
+
densityDissipation: 0.96,
|
|
13
|
+
velocityDissipation: 1.0,
|
|
14
|
+
fluidColor: '#3300ff',
|
|
15
|
+
backgroundColor: '#070410',
|
|
16
|
+
showBackground: true,
|
|
17
|
+
rainbow: false,
|
|
18
|
+
dyeRes: 512,
|
|
19
|
+
simRes: 128,
|
|
20
|
+
blendFunction: BlendFunction.SET,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export const REFRESH_RATE = 60;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useMemo } from 'react';
|
|
2
|
+
import { EffectProps } from '../types';
|
|
3
|
+
import { FluidEffect } from './FluidEffect';
|
|
4
|
+
|
|
5
|
+
export const Effect = forwardRef(function Fluid(props: EffectProps, ref) {
|
|
6
|
+
// prevent re-creating the effect on every render
|
|
7
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
8
|
+
const effect = useMemo(() => new FluidEffect(props), []);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
effect.state = { ...props };
|
|
12
|
+
effect.update();
|
|
13
|
+
}, [effect, props]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
return () => {
|
|
17
|
+
effect.dispose?.();
|
|
18
|
+
};
|
|
19
|
+
}, [effect]);
|
|
20
|
+
|
|
21
|
+
return <primitive ref={ref} object={effect} dispose={null} />;
|
|
22
|
+
});
|