r3f-points-fx
Version:
React three fiber component for easily creating high performance particles meshes. Makes particles morphing from one arrangement to another a piece of cake.
434 lines (408 loc) • 14.8 kB
JavaScript
import { jsxs as B, Fragment as tt, jsx as y } from "react/jsx-runtime";
import { useFBO as et } from "@react-three/drei";
import { useThree as rt, useFrame as it, createPortal as ot } from "@react-three/fiber";
import * as p from "react";
import * as a from "three";
import { Triangle as nt, Vector3 as st, Vector2 as E } from "three";
const at = (s) => {
if (s < 0) return 0;
const t = Math.ceil(Math.sqrt(s));
return t * t;
}, d = new nt(), R = new st(), G = new E(), W = new E(), $ = new E();
class ut {
constructor(t) {
this.geometry = t.geometry, this.randomFunction = Math.random, this.indexAttribute = this.geometry.index, this.positionAttribute = this.geometry.getAttribute("position"), this.normalAttribute = this.geometry.getAttribute("normal"), this.colorAttribute = this.geometry.getAttribute("color"), this.uvAttribute = this.geometry.getAttribute("uv"), this.weightAttribute = null, this.distribution = null;
}
setWeightAttribute(t) {
return this.weightAttribute = t ? this.geometry.getAttribute(t) : null, this;
}
build() {
const t = this.indexAttribute, n = this.positionAttribute, e = this.weightAttribute, u = t ? t.count / 3 : n.count / 3, v = new Float32Array(u);
for (let l = 0; l < u; l++) {
let c = 1, f = 3 * l, A = 3 * l + 1, x = 3 * l + 2;
t && (f = t.getX(f), A = t.getX(A), x = t.getX(x)), e && (c = e.getX(f) + e.getX(A) + e.getX(x)), d.a.fromBufferAttribute(n, f), d.b.fromBufferAttribute(n, A), d.c.fromBufferAttribute(n, x), c *= d.getArea(), v[l] = c;
}
const r = new Float32Array(u);
let m = 0;
for (let l = 0; l < u; l++)
m += v[l], r[l] = m;
return this.distribution = r, this;
}
setRandomGenerator(t) {
return this.randomFunction = t, this;
}
sample(t, n, e, u) {
const v = this.sampleFaceIndex();
return this.sampleFace(v, t, n, e, u);
}
sampleFaceIndex() {
const t = this.distribution[this.distribution.length - 1];
return this.binarySearch(this.randomFunction() * t);
}
binarySearch(t) {
const n = this.distribution;
let e = 0, u = n.length - 1, v = -1;
for (; e <= u; ) {
const r = Math.ceil((e + u) / 2);
if (r === 0 || n[r - 1] <= t && n[r] > t) {
v = r;
break;
} else t < n[r] ? u = r - 1 : e = r + 1;
}
return v;
}
sampleFace(t, n, e, u, v) {
let r = this.randomFunction(), m = this.randomFunction();
r + m > 1 && (r = 1 - r, m = 1 - m);
const l = this.indexAttribute;
let c = t * 3, f = t * 3 + 1, A = t * 3 + 2;
return l && (c = l.getX(c), f = l.getX(f), A = l.getX(A)), d.a.fromBufferAttribute(this.positionAttribute, c), d.b.fromBufferAttribute(this.positionAttribute, f), d.c.fromBufferAttribute(this.positionAttribute, A), n.set(0, 0, 0).addScaledVector(d.a, r).addScaledVector(d.b, m).addScaledVector(d.c, 1 - (r + m)), e !== void 0 && (this.normalAttribute !== void 0 ? (d.a.fromBufferAttribute(this.normalAttribute, c), d.b.fromBufferAttribute(this.normalAttribute, f), d.c.fromBufferAttribute(this.normalAttribute, A), e.set(0, 0, 0).addScaledVector(d.a, r).addScaledVector(d.b, m).addScaledVector(d.c, 1 - (r + m)).normalize()) : d.getNormal(e)), u !== void 0 && this.colorAttribute !== void 0 && (d.a.fromBufferAttribute(this.colorAttribute, c), d.b.fromBufferAttribute(this.colorAttribute, f), d.c.fromBufferAttribute(this.colorAttribute, A), R.set(0, 0, 0).addScaledVector(d.a, r).addScaledVector(d.b, m).addScaledVector(d.c, 1 - (r + m)), u.r = R.x, u.g = R.y, u.b = R.z), v !== void 0 && this.uvAttribute !== void 0 && (G.fromBufferAttribute(this.uvAttribute, c), W.fromBufferAttribute(this.uvAttribute, f), $.fromBufferAttribute(this.uvAttribute, A), v.set(0, 0).addScaledVector(G, r).addScaledVector(W, m).addScaledVector($, 1 - (r + m))), this;
}
}
const lt = (s, t) => {
const n = s * 4, e = new Float32Array(n), u = new ut(t).build(), v = Math.sqrt(s), r = v;
for (let l = 0; l < s; l++) {
const c = l * 4, f = new a.Vector3();
u.sample(f), e[c] = f.x, e[c + 1] = f.y, e[c + 2] = f.z, e[c + 3] = 1;
}
const m = new a.DataTexture(
e,
v,
r,
a.RGBAFormat,
a.FloatType
);
return m.needsUpdate = !0, m;
}, ct = (s, t) => {
const n = t.geometry.attributes.position, e = new Float32Array(s * 4), u = Math.sqrt(s), v = u;
let r = 0;
for (let l = 0; l < s; l++) {
const c = l * 4;
if (r < n.count) {
const f = r * 3;
e[c] = n.array[f], e[c + 1] = n.array[f + 1], e[c + 2] = n.array[f + 2], e[c + 3] = 1, r++;
} else {
const f = Math.floor(Math.random() * n.count);
e[c] = n.array[f * 3], e[c + 1] = n.array[f * 3 + 1], e[c + 2] = n.array[f * 3 + 2], e[c + 3] = 1;
}
}
const m = new a.DataTexture(
e,
u,
v,
a.RGBAFormat,
a.FloatType
);
return m.needsUpdate = !0, m;
}, ft = (s, t, n = !1) => n ? ct(s, t) : lt(s, t), k = (s) => Object.entries(s).reduce((e, [u, v]) => {
const r = a.UniformsUtils.clone({
[u]: {
value: v
}
});
return {
...e,
...r
};
}, {}), mt = `
float progressModifier(vec3 origin, vec3 target, float progress){
return progress;
}
`, dt = (s) => `
uniform float uTime;
uniform float uTransitionProgress;
uniform sampler2D positionsA;
uniform sampler2D positionsB;
uniform int uModel1;
uniform int uModel2;
varying vec2 vUv;
${s || mt}
void main() {
vec3 model1 = texture2D(positionsA, vUv).rgb;
vec3 model2 = texture2D(positionsB, vUv).rgb;
float progress = progressModifier(model1, model2, uTransitionProgress);
vec3 pos = mix(model1, model2, progress);
gl_FragColor = vec4(pos, progress);
}
`, ht = `
varying vec2 vUv;
varying float vTransitionProgress;
void main() {
vUv = uv;
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
}
`, vt = `
vec4 modifier(int index){
// index is the current active model's index in model array passed
// use gl_PointCoord and alpha to control the shape
vec2 uv = gl_PointCoord;
float distanceFromCenter = length(uv - 0.5);
float alpha = ceil(max(0.5 - distanceFromCenter, 0.0));
vec3 color = uColor;
vec4 result = vec4(color, uAlpha * alpha);
return result;
}
`, pt = (s) => `
uniform vec3 uColor;
uniform float uTime;
uniform int uModel1;
uniform int uModel2;
uniform float uAlpha;
varying vec3 vPosition;
varying float vTransitionProgress;
${s || vt}
void main() {
vec4 color1 = modifier(uModel1);
vec4 color2 = modifier(uModel2);
gl_FragColor = mix(color1, color2, vTransitionProgress);
}
`, gt = `
VertexProperties modifier(vec3 pos, float progress){
VertexProperties result;
result.position = pos;
result.pointSize = uPointSize;
result.progress = progress;
return result;
}
`, bt = (s, t) => `
uniform sampler2D uPosition;
uniform float uTime;
uniform vec2 uViewPort;
uniform float uDpr;
uniform float uPointSize;
uniform int uModel1;
uniform int uModel2;
varying vec3 vPosition;
varying float vTransitionProgress;
struct VertexProperties {
vec3 position;
float pointSize;
float progress;
};
${s || gt}
void main(){
vec4 textureData = texture2D(uPosition, position.xy);
vec3 pos = textureData.xyz;
float progress = textureData.w;
VertexProperties res = modifier(pos, progress);
vec4 modelPosition = modelMatrix * vec4(res.position, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
gl_PointSize = res.pointSize * uViewPort.y * uDpr;
${t ? "gl_PointSize *= (1.0 / abs(viewPosition.z));" : ""}
vPosition = res.position;
vTransitionProgress = res.progress;
}
`, xt = p.forwardRef(
({
models: s,
pointsCount: t = 1e3,
modelA: n = null,
modelB: e = null,
uniforms: u = {},
baseColor: v = new a.Color(0, 0, 0),
pointSize: r = 0.1,
alpha: m = 1,
attributes: l = [],
blending: c = a.AdditiveBlending,
vertexModifier: f,
fragmentModifier: A,
progressModifier: x,
sizeAttenutation: H = !0,
organizedParticleIndexes: L = [],
...N
}, J) => {
const g = p.useRef(null), h = p.useRef(null), F = p.useRef(n), w = p.useRef(e), z = p.useRef(new a.Scene()), K = p.useRef(
new a.OrthographicCamera(-1, 1, 1, -1, 1 / Math.pow(2, 53), 1)
), {
gl: D,
size: { width: V, height: _ }
} = rt(), S = p.useMemo(() => at(t), [t]), P = p.useMemo(() => {
const i = [];
return s.forEach((o, b) => {
i.push(
ft(
S,
o,
L.includes(b)
)
);
}), i;
}, [s, S]), Q = p.useMemo(
() => k({
uTransitionProgress: 0,
uTime: 0,
positionsA: F.current !== null ? P[F.current] : null,
positionsB: w.current !== null ? P[w.current] : null,
uModel1: F.current,
uModel2: w.current
}),
[P]
), Y = p.useMemo(() => k({
uPosition: null,
uColor: v,
uTime: 0,
uModel1: F.current,
uModel2: w.current,
uPointSize: r / 10,
uAlpha: m,
uViewPort: new a.Vector2(V, _),
uDpr: D.getPixelRatio(),
...u
}), []), U = p.useCallback((i) => {
var o;
((o = g.current) == null ? void 0 : o.material) instanceof a.ShaderMaterial && (g.current.material.uniforms.uTransitionProgress.value = Math.min(
i,
1
));
}, []), j = p.useCallback(
(i) => {
var b;
let o = null;
i >= 0 && i < P.length && (o = i, F.current = o), ((b = g.current) == null ? void 0 : b.material) instanceof a.ShaderMaterial && (g.current.material.uniforms.positionsA.value = o !== null ? P[o] : null, g.current.material.uniforms.uModel1.value = o);
},
[P]
), C = p.useCallback(
(i) => {
var b;
let o = null;
i >= 0 && i < P.length && (o = i, w.current = o), ((b = g.current) == null ? void 0 : b.material) instanceof a.ShaderMaterial && (g.current.material.uniforms.positionsB.value = o !== null ? P[o] : null, g.current.material.uniforms.uModel2.value = o);
},
[P]
);
p.useEffect(() => {
var i;
((i = h.current) == null ? void 0 : i.material) instanceof a.ShaderMaterial && (h.current.material.uniforms.uColor.value = v, h.current.material.uniforms.uPointSize.value = r / 10, h.current.material.uniforms.uAlpha.value = m), Object.entries(u).forEach(([o, b]) => {
var M;
((M = h.current) == null ? void 0 : M.material) instanceof a.ShaderMaterial && h.current.material.uniforms[o] && (h.current.material.uniforms[o].value = b);
});
}, [v, r, m, u]), p.useEffect(() => {
var i;
((i = h.current) == null ? void 0 : i.material) instanceof a.ShaderMaterial && (h.current.material.uniforms.uViewPort.value.set(V, _), h.current.material.uniforms.uDpr.value = D.getPixelRatio());
}, [V, _, D]);
const O = p.useMemo(() => {
const i = new Float32Array([
-1,
-1,
0,
1,
-1,
0,
1,
1,
0,
-1,
-1,
0,
1,
1,
0,
-1,
1,
0
]), o = new Float32Array([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0]);
return { positions: i, uvs: o };
}, []), X = et(Math.sqrt(S), Math.sqrt(S), {
minFilter: a.NearestFilter,
magFilter: a.NearestFilter,
type: a.FloatType
}), Z = p.useMemo(() => {
const i = Math.sqrt(S), o = i, b = new Float32Array(S * 3);
for (let M = 0; M < S; M++) {
const T = M * 3;
b[T + 0] = M % o / o, b[T + 1] = M / o / i;
}
return b;
}, [S]);
return p.useImperativeHandle(
J,
() => ({
getSimulationMesh: () => g.current,
getPointsMesh: () => h.current,
updateProgress: U,
updateTime: (i) => {
g.current && g.current.material instanceof a.ShaderMaterial && (g.current.material.uniforms.uTime.value = i), h.current && h.current.material instanceof a.ShaderMaterial && (h.current.material.uniforms.uTime.value = i);
},
setModelA: j,
setModelB: C
}),
[U, j, C]
), it((i) => {
var b, M, T;
const { gl: o } = i;
if (o.setRenderTarget(X), o.clear(), o.render(z.current, K.current), o.setRenderTarget(null), ((b = h.current) == null ? void 0 : b.material) instanceof a.ShaderMaterial && (h.current.material.uniforms.uPosition.value = X.texture), ((M = g.current) == null ? void 0 : M.material) instanceof a.ShaderMaterial && ((T = h.current) == null ? void 0 : T.material) instanceof a.ShaderMaterial) {
const I = g.current.material.uniforms.uModel1.value, q = g.current.material.uniforms.uModel2.value;
h.current.material.uniforms.uModel1.value !== I && (h.current.material.uniforms.uModel1.value = I), h.current.material.uniforms.uModel2.value !== q && (h.current.material.uniforms.uModel2.value = q);
}
}), /* @__PURE__ */ B(tt, { children: [
ot(
/* @__PURE__ */ B("mesh", { ref: g, children: [
/* @__PURE__ */ B("bufferGeometry", { children: [
/* @__PURE__ */ y(
"bufferAttribute",
{
attach: "attributes-position",
args: [O.positions, 3]
}
),
/* @__PURE__ */ y(
"bufferAttribute",
{
attach: "attributes-uv",
args: [O.uvs, 2]
}
)
] }),
/* @__PURE__ */ y(
"shaderMaterial",
{
uniforms: Q,
vertexShader: ht,
fragmentShader: dt(x)
}
)
] }),
z.current
),
/* @__PURE__ */ B("points", { ...N, ref: h, children: [
/* @__PURE__ */ B("bufferGeometry", { children: [
/* @__PURE__ */ y(
"bufferAttribute",
{
attach: "attributes-position",
args: [Z, 3]
}
),
l.map((i, o) => /* @__PURE__ */ y(
"bufferAttribute",
{
attach: i.attach,
args: [i.array, i.itemSize]
},
o
))
] }),
/* @__PURE__ */ y(
"shaderMaterial",
{
uniforms: Y,
vertexShader: bt(f, H),
fragmentShader: pt(A),
depthWrite: !1,
blending: c,
transparent: !0,
vertexColors: !0
}
)
] })
] });
}
);
export {
xt as R3FPointsFX,
at as nextPerfectSquare
};