@3driseai/3drise-viewer 0.1.16 → 0.1.18
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/README.md +4 -3
- package/dist/components/ChladniGrid.d.ts +15 -0
- package/dist/components/ChladniGrid.d.ts.map +1 -0
- package/dist/components/ChladniGrid.js +62 -0
- package/dist/components/CustomPrimitive.d.ts +2 -0
- package/dist/components/CustomPrimitive.d.ts.map +1 -1
- package/dist/components/CustomPrimitive.js +2 -2
- package/dist/components/EffectsGenerator.d.ts.map +1 -1
- package/dist/components/EffectsGenerator.js +26 -2
- package/dist/components/FlowNetGrid.d.ts +15 -0
- package/dist/components/FlowNetGrid.d.ts.map +1 -0
- package/dist/components/FlowNetGrid.js +61 -0
- package/dist/components/Galaxy.d.ts.map +1 -1
- package/dist/components/Galaxy.js +2 -0
- package/dist/components/GirihGrid.d.ts +15 -0
- package/dist/components/GirihGrid.d.ts.map +1 -0
- package/dist/components/GirihGrid.js +62 -0
- package/dist/components/GravWaveGrid.d.ts +15 -0
- package/dist/components/GravWaveGrid.d.ts.map +1 -0
- package/dist/components/GravWaveGrid.js +62 -0
- package/dist/components/GridsGenerator.d.ts +2 -2
- package/dist/components/GridsGenerator.d.ts.map +1 -1
- package/dist/components/GridsGenerator.js +21 -0
- package/dist/components/HyperbolicGrid.d.ts +15 -0
- package/dist/components/HyperbolicGrid.d.ts.map +1 -0
- package/dist/components/HyperbolicGrid.js +61 -0
- package/dist/components/LayoutInstances.d.ts +13 -0
- package/dist/components/LayoutInstances.d.ts.map +1 -0
- package/dist/components/LayoutInstances.js +39 -0
- package/dist/components/ModelPrimitive.d.ts +2 -0
- package/dist/components/ModelPrimitive.d.ts.map +1 -1
- package/dist/components/ModelPrimitive.js +3 -3
- package/dist/components/ObjectNode.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +44 -9
- package/dist/components/ParticlesGenerator.d.ts +10 -0
- package/dist/components/ParticlesGenerator.d.ts.map +1 -0
- package/dist/components/ParticlesGenerator.js +23 -0
- package/dist/components/ReactionDiffusionGrid.d.ts +15 -0
- package/dist/components/ReactionDiffusionGrid.d.ts.map +1 -0
- package/dist/components/ReactionDiffusionGrid.js +376 -0
- package/dist/components/WallpaperGrid.d.ts +15 -0
- package/dist/components/WallpaperGrid.d.ts.map +1 -0
- package/dist/components/WallpaperGrid.js +555 -0
- package/dist/components/cursor/CursorController.d.ts.map +1 -1
- package/dist/components/effects/BlackHoleEffect.d.ts +3 -3
- package/dist/components/effects/BlackHoleEffect.d.ts.map +1 -1
- package/dist/components/effects/BlackHoleEffect.js +293 -163
- package/dist/components/effects/DandelionSeedsEffect.d.ts +13 -0
- package/dist/components/effects/DandelionSeedsEffect.d.ts.map +1 -0
- package/dist/components/effects/DandelionSeedsEffect.js +510 -0
- package/dist/components/effects/FallingLeavesEffect.d.ts +13 -0
- package/dist/components/effects/FallingLeavesEffect.d.ts.map +1 -0
- package/dist/components/effects/FallingLeavesEffect.js +446 -0
- package/dist/components/effects/FieldLinesEffect.d.ts +11 -0
- package/dist/components/effects/FieldLinesEffect.d.ts.map +1 -0
- package/dist/components/effects/FieldLinesEffect.js +260 -0
- package/dist/components/effects/FireflySwarmEffect.d.ts +13 -0
- package/dist/components/effects/FireflySwarmEffect.d.ts.map +1 -0
- package/dist/components/effects/FireflySwarmEffect.js +408 -0
- package/dist/components/effects/GalaxyEffect.d.ts +13 -0
- package/dist/components/effects/GalaxyEffect.d.ts.map +1 -0
- package/dist/components/effects/GalaxyEffect.js +374 -0
- package/dist/components/effects/HarmonicShellEffect.d.ts +11 -0
- package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -0
- package/dist/components/effects/HarmonicShellEffect.js +209 -0
- package/dist/components/effects/IceCrystalsEffect.d.ts +3 -3
- package/dist/components/effects/IceCrystalsEffect.d.ts.map +1 -1
- package/dist/components/effects/IceCrystalsEffect.js +286 -166
- package/dist/components/effects/IceHaloEffect.d.ts +13 -0
- package/dist/components/effects/IceHaloEffect.d.ts.map +1 -0
- package/dist/components/effects/IceHaloEffect.js +306 -0
- package/dist/components/effects/LichtenbergEffect.d.ts +13 -0
- package/dist/components/effects/LichtenbergEffect.d.ts.map +1 -0
- package/dist/components/effects/LichtenbergEffect.js +517 -0
- package/dist/components/effects/MoleculesEffect.d.ts +3 -3
- package/dist/components/effects/MoleculesEffect.d.ts.map +1 -1
- package/dist/components/effects/MoleculesEffect.js +322 -164
- package/dist/components/effects/MurmurationEffect.d.ts +13 -0
- package/dist/components/effects/MurmurationEffect.d.ts.map +1 -0
- package/dist/components/effects/MurmurationEffect.js +539 -0
- package/dist/components/effects/PlanetaryRingsEffect.d.ts +13 -0
- package/dist/components/effects/PlanetaryRingsEffect.d.ts.map +1 -0
- package/dist/components/effects/PlanetaryRingsEffect.js +458 -0
- package/dist/components/effects/PortalEffect.d.ts +3 -3
- package/dist/components/effects/PortalEffect.d.ts.map +1 -1
- package/dist/components/effects/PortalEffect.js +240 -163
- package/dist/components/effects/ShockwaveEffect.d.ts +5 -3
- package/dist/components/effects/ShockwaveEffect.d.ts.map +1 -1
- package/dist/components/effects/ShockwaveEffect.js +538 -142
- package/dist/components/effects/SmokePlumeEffect.d.ts +3 -3
- package/dist/components/effects/SmokePlumeEffect.d.ts.map +1 -1
- package/dist/components/effects/SmokePlumeEffect.js +180 -116
- package/dist/components/effects/SmokeRingEffect.d.ts +3 -3
- package/dist/components/effects/SmokeRingEffect.d.ts.map +1 -1
- package/dist/components/effects/SmokeRingEffect.js +260 -135
- package/dist/components/effects/StrangeAttractorEffect.d.ts +11 -0
- package/dist/components/effects/StrangeAttractorEffect.d.ts.map +1 -0
- package/dist/components/effects/StrangeAttractorEffect.js +229 -0
- package/dist/components/effects/VolumetricFogEffect.d.ts +5 -3
- package/dist/components/effects/VolumetricFogEffect.d.ts.map +1 -1
- package/dist/components/effects/VolumetricFogEffect.js +211 -93
- package/dist/components/effects/WaterCausticsEffect.d.ts +13 -0
- package/dist/components/effects/WaterCausticsEffect.d.ts.map +1 -0
- package/dist/components/effects/WaterCausticsEffect.js +308 -0
- package/dist/components/effects/index.d.ts +9 -0
- package/dist/components/effects/index.d.ts.map +1 -1
- package/dist/components/effects/index.js +9 -0
- package/dist/components/index.d.ts +19 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +10 -0
- package/dist/components/particles/BioluminescentWakeParticles.d.ts +12 -0
- package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -0
- package/dist/components/particles/BioluminescentWakeParticles.js +1123 -0
- package/dist/components/particles/BrownianDustParticles.d.ts +12 -0
- package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -0
- package/dist/components/particles/BrownianDustParticles.js +757 -0
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts +12 -0
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -0
- package/dist/components/particles/ElectronOrbitalsParticles.js +944 -0
- package/dist/components/particles/GrinderSparksParticles.d.ts +12 -0
- package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -0
- package/dist/components/particles/GrinderSparksParticles.js +744 -0
- package/dist/components/particles/TidalStreamParticles.d.ts +12 -0
- package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -0
- package/dist/components/particles/TidalStreamParticles.js +788 -0
- package/dist/components/particles/index.d.ts +11 -0
- package/dist/components/particles/index.d.ts.map +1 -0
- package/dist/components/particles/index.js +6 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/hooks/useModelObject.d.ts +6 -1
- package/dist/hooks/useModelObject.d.ts.map +1 -1
- package/dist/hooks/useModelObject.js +19 -6
- package/dist/types/gridConfigs.d.ts +98 -0
- package/dist/types/gridConfigs.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/particleTrails.d.ts +70 -0
- package/dist/utils/particleTrails.d.ts.map +1 -0
- package/dist/utils/particleTrails.js +369 -0
- package/dist/utils/pointerField.d.ts +28 -0
- package/dist/utils/pointerField.d.ts.map +1 -0
- package/dist/utils/pointerField.js +103 -0
- package/package.json +2 -2
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
|
+
import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
|
|
6
|
+
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
7
|
+
/**
|
|
8
|
+
* Dandelion seeds let go of the head one by one and drift away: a high-drag pappus gives them a slow terminal fall, and curl-noise wind with updrafts carries them. Use for meadow, summer, wish, gentle scenes.
|
|
9
|
+
*
|
|
10
|
+
* dv/dt = (u_wind + u_turb + lift·ŷ − v_settle·ŷ − v)/τ · u = ∇×A (divergence-free) · potential flow around the head · pappus aligns with −v_rel
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// A dandelion seed is a tiny achene under a pappus - an umbrella of ~100 fine
|
|
14
|
+
// filaments. The pappus has an enormous drag-to-mass ratio (it even holds a
|
|
15
|
+
// separated vortex ring above itself), so the seed reaches its terminal speed
|
|
16
|
+
// in a fraction of a second and then simply goes where the air goes:
|
|
17
|
+
// dv/dt = (u_air - v_settle * yhat - v) / tau, tau ~ 0.3 s
|
|
18
|
+
// The air is a mean wind plus a divergence-free swirl (the analytic curl of a
|
|
19
|
+
// trig vector potential, so gusts rotate and never compress) plus a slow
|
|
20
|
+
// updraft. Around the head the mean wind follows the potential flow past a
|
|
21
|
+
// sphere, u = U(1 + a^3/2r^3) - 3a^3/2r^5 (U.r) r, which is exactly tangent at
|
|
22
|
+
// the surface - so seeds slide around the head instead of into it.
|
|
23
|
+
// Drag also aligns the pappus with the relative airflow, so each seed hangs
|
|
24
|
+
// its achene downwind-down and tips as gusts hit it. Seeds are pulled loose
|
|
25
|
+
// one at a time (sooner in stronger wind), fade out far downwind, and are
|
|
26
|
+
// replaced on the same spot on the head.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const vert = /* glsl */ `
|
|
29
|
+
// position = (s along the part, side of the ribbon, filament angle)
|
|
30
|
+
attribute float aPart; // 0 pappus filament, 1 beak, 2 achene
|
|
31
|
+
attribute vec4 aSeedA; // hub position xyz, alpha w
|
|
32
|
+
attribute vec4 aSeedB; // pappus axis xyz, roll w
|
|
33
|
+
attribute vec2 aVar; // per-seed size factor, per-seed shape hash
|
|
34
|
+
uniform float uSeedSize;
|
|
35
|
+
uniform float uBound;
|
|
36
|
+
uniform float uViewH;
|
|
37
|
+
varying float vAcross;
|
|
38
|
+
varying float vFade;
|
|
39
|
+
varying float vPart;
|
|
40
|
+
varying float vAlpha;
|
|
41
|
+
|
|
42
|
+
void main(){
|
|
43
|
+
vec3 C = aSeedA.xyz;
|
|
44
|
+
vec3 A = normalize(aSeedB.xyz + vec3(0.0, 1e-5, 0.0));
|
|
45
|
+
vec3 ref = abs(A.y) < 0.9 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
|
46
|
+
vec3 e1 = normalize(cross(A, ref));
|
|
47
|
+
vec3 e2 = cross(A, e1);
|
|
48
|
+
float phi = position.z + aSeedB.w;
|
|
49
|
+
vec3 d = cos(phi) * e1 + sin(phi) * e2;
|
|
50
|
+
|
|
51
|
+
float R = 0.16 * uSeedSize * uBound * aVar.x; // pappus radius
|
|
52
|
+
float s = position.x;
|
|
53
|
+
vec3 P;
|
|
54
|
+
vec3 T;
|
|
55
|
+
float hw;
|
|
56
|
+
float fade;
|
|
57
|
+
if (aPart < 0.5){
|
|
58
|
+
// one bristle: a nearly straight ray from the hub on a cone of half-angle
|
|
59
|
+
// th, with a little gravity sag, so the pappus reads as a starburst whose
|
|
60
|
+
// spread of angles gives the shallow dome
|
|
61
|
+
float h = fract(sin((position.z + aVar.y * 7.3) * 31.7182) * 43758.5453);
|
|
62
|
+
float th = 0.72 + 0.62 * h;
|
|
63
|
+
float len = R / max(sin(th), 0.45) * (0.82 + 0.3 * h);
|
|
64
|
+
float sag = 0.14 * s * s;
|
|
65
|
+
P = C + len * (s * sin(th) * d + (s * cos(th) - sag) * A);
|
|
66
|
+
T = normalize(sin(th) * d + (cos(th) - 0.28 * s) * A);
|
|
67
|
+
hw = R * (0.013 + 0.013 * s); // feathery toward the tip
|
|
68
|
+
fade = (1.0 - 0.35 * s) * (1.0 - smoothstep(0.7, 1.0, s) * 0.8);
|
|
69
|
+
} else if (aPart < 1.5){
|
|
70
|
+
// the beak: a thin stalk hanging from the hub down the axis
|
|
71
|
+
P = C - A * (1.45 * R * s);
|
|
72
|
+
T = A;
|
|
73
|
+
hw = R * 0.011;
|
|
74
|
+
fade = 0.55;
|
|
75
|
+
} else {
|
|
76
|
+
// the achene: a small spindle at the bottom of the beak
|
|
77
|
+
float t0 = 1.45 * R;
|
|
78
|
+
P = C - A * (t0 + 0.55 * R * s);
|
|
79
|
+
T = A;
|
|
80
|
+
hw = R * 0.05 * sin(3.14159 * (0.12 + 0.82 * s));
|
|
81
|
+
fade = 1.0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
vec4 mv = modelViewMatrix * vec4(P, 1.0);
|
|
85
|
+
float pxPerUnit = projectionMatrix[1][1] * uViewH / max(-mv.z, 0.05);
|
|
86
|
+
float minHW = 0.8 / max(pxPerUnit, 1e-4); // keep every ribbon ~1.6 px wide
|
|
87
|
+
float w = max(hw, minHW);
|
|
88
|
+
// give back in alpha what a widened ribbon gains in area
|
|
89
|
+
vAlpha = aSeedA.w * clamp(hw / max(minHW, 1e-6), 0.4, 1.0);
|
|
90
|
+
vec3 vd = normalize(cameraPosition - P);
|
|
91
|
+
vec3 off = cross(T, vd);
|
|
92
|
+
float ol = length(off);
|
|
93
|
+
off = ol > 1e-5 ? off / ol : e1;
|
|
94
|
+
P += off * (position.y * w);
|
|
95
|
+
|
|
96
|
+
vAcross = position.y;
|
|
97
|
+
vFade = fade;
|
|
98
|
+
vPart = aPart;
|
|
99
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(P, 1.0);
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
const frag = /* glsl */ `
|
|
103
|
+
uniform vec3 uColor;
|
|
104
|
+
uniform vec3 uStemColor;
|
|
105
|
+
uniform float uIntensity;
|
|
106
|
+
uniform float uOpacity;
|
|
107
|
+
varying float vAcross;
|
|
108
|
+
varying float vFade;
|
|
109
|
+
varying float vPart;
|
|
110
|
+
varying float vAlpha;
|
|
111
|
+
|
|
112
|
+
void main(){
|
|
113
|
+
// soft across the ribbon: a parabolic profile, so no hard edges anywhere
|
|
114
|
+
float a = vAlpha * vFade * (1.0 - vAcross * vAcross) * uOpacity;
|
|
115
|
+
if (a < 0.003) discard;
|
|
116
|
+
vec3 col = vPart < 0.5 ? uColor : uStemColor * (vPart > 1.5 ? 1.0 : 0.62);
|
|
117
|
+
gl_FragColor = vec4(col * uIntensity, a * (vPart < 0.5 ? 0.45 : 0.9));
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
function mulberry32(seed) {
|
|
121
|
+
return () => {
|
|
122
|
+
seed |= 0;
|
|
123
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
124
|
+
let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
125
|
+
r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
|
|
126
|
+
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** deterministic 0..1 value for seed i on its cycle c */
|
|
130
|
+
function hash2(i, c) {
|
|
131
|
+
let h = Math.imul(i + 1, 374761393) ^ Math.imul(c + 1, 668265263);
|
|
132
|
+
h = Math.imul(h ^ (h >>> 13), 1274126177);
|
|
133
|
+
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
|
|
134
|
+
}
|
|
135
|
+
/** the ribbon template: 32 pappus filaments, the beak and the achene */
|
|
136
|
+
function buildTemplate() {
|
|
137
|
+
const F = 32;
|
|
138
|
+
const NODE = 4;
|
|
139
|
+
const data = [];
|
|
140
|
+
const part = [];
|
|
141
|
+
const index = [];
|
|
142
|
+
let base = 0;
|
|
143
|
+
const strip = (nodes, phi, p) => {
|
|
144
|
+
for (let k = 0; k < nodes; k++) {
|
|
145
|
+
const s = k / (nodes - 1);
|
|
146
|
+
data.push(s, -1, phi);
|
|
147
|
+
part.push(p);
|
|
148
|
+
data.push(s, 1, phi);
|
|
149
|
+
part.push(p);
|
|
150
|
+
}
|
|
151
|
+
for (let k = 0; k < nodes - 1; k++) {
|
|
152
|
+
const a = base + k * 2;
|
|
153
|
+
index.push(a, a + 1, a + 2, a + 1, a + 3, a + 2);
|
|
154
|
+
}
|
|
155
|
+
base += nodes * 2;
|
|
156
|
+
};
|
|
157
|
+
for (let f = 0; f < F; f++) {
|
|
158
|
+
const j = (((f * 2654435761) >>> 0) / 4294967296 - 0.5) * 0.55;
|
|
159
|
+
strip(NODE, ((f + j) / F) * Math.PI * 2, 0);
|
|
160
|
+
}
|
|
161
|
+
strip(3, 0, 1);
|
|
162
|
+
strip(4, 0, 2);
|
|
163
|
+
const geo = new THREE.InstancedBufferGeometry();
|
|
164
|
+
geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(data), 3));
|
|
165
|
+
geo.setAttribute('aPart', new THREE.BufferAttribute(new Float32Array(part), 1));
|
|
166
|
+
geo.setIndex(index);
|
|
167
|
+
return geo;
|
|
168
|
+
}
|
|
169
|
+
function createUniforms() {
|
|
170
|
+
return {
|
|
171
|
+
uTime: { value: 0 },
|
|
172
|
+
uSpeed: { value: 1 },
|
|
173
|
+
uIntensity: { value: 1 },
|
|
174
|
+
uOpacity: { value: 1 },
|
|
175
|
+
uColor: { value: new THREE.Color() },
|
|
176
|
+
uStemColor: { value: new THREE.Color() },
|
|
177
|
+
uWindSpeed: { value: 0.55 },
|
|
178
|
+
uWindDirection: { value: 20 },
|
|
179
|
+
uTurbulence: { value: 0.8 },
|
|
180
|
+
uSeedSize: { value: 1 },
|
|
181
|
+
uLift: { value: 1 },
|
|
182
|
+
uBound: { value: 1 },
|
|
183
|
+
uViewH: { value: 800 },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function applyConfig(u, c, bound, env) {
|
|
187
|
+
u.uSpeed.value = c.speed;
|
|
188
|
+
u.uIntensity.value = c.intensity;
|
|
189
|
+
u.uOpacity.value = c.opacity;
|
|
190
|
+
u.uColor.value.set(c.color);
|
|
191
|
+
u.uStemColor.value.set(c.stemColor);
|
|
192
|
+
u.uWindSpeed.value = c.windSpeed;
|
|
193
|
+
u.uWindDirection.value = c.windDirection;
|
|
194
|
+
u.uTurbulence.value = c.turbulence;
|
|
195
|
+
u.uSeedSize.value = c.seedSize;
|
|
196
|
+
u.uLift.value = c.lift;
|
|
197
|
+
u.uBound.value = bound;
|
|
198
|
+
}
|
|
199
|
+
function build(u, c, bound, env) {
|
|
200
|
+
const n = Math.round(Math.min(Math.max(c.seedCount, 10), 260));
|
|
201
|
+
const hasHost = !!(env && env.hasHost);
|
|
202
|
+
const b = bound;
|
|
203
|
+
const SIM_DT = 1 / 30;
|
|
204
|
+
const TAU_V = 0.32; // velocity relaxation time of a pappus, seconds
|
|
205
|
+
const SETTLE = 0.12; // terminal settling speed, in bound units per second
|
|
206
|
+
const FADE_R = 6.5; // seeds fade out beyond this radius
|
|
207
|
+
const LIFE = 22; // ... or after this long in the air
|
|
208
|
+
const rand = mulberry32(90210);
|
|
209
|
+
const slot = new Float32Array(n * 3); // attachment direction on the head
|
|
210
|
+
const pos = new Float32Array(n * 3);
|
|
211
|
+
const vel = new Float32Array(n * 3);
|
|
212
|
+
const axis = new Float32Array(n * 3);
|
|
213
|
+
const timer = new Float32Array(n);
|
|
214
|
+
const wait = new Float32Array(n);
|
|
215
|
+
const cycle = new Int32Array(n);
|
|
216
|
+
const mode = new Int32Array(n); // 0 attached, 1 flying, 2 fading
|
|
217
|
+
const roll = new Float32Array(n);
|
|
218
|
+
const spin = new Float32Array(n);
|
|
219
|
+
const varr = new Float32Array(n * 2); // size factor, shape hash
|
|
220
|
+
const settleF = new Float32Array(n); // per-seed terminal fall speed
|
|
221
|
+
const tilt = new Float32Array(n * 3); // small permanent tilt of the pappus
|
|
222
|
+
const seedA = new Float32Array(n * 4);
|
|
223
|
+
const seedB = new Float32Array(n * 4);
|
|
224
|
+
const air = new Float32Array(3);
|
|
225
|
+
// attachment slots: a Fibonacci spiral over the upper hemisphere of the head
|
|
226
|
+
const GOLD = Math.PI * (3 - Math.sqrt(5));
|
|
227
|
+
for (let i = 0; i < n; i++) {
|
|
228
|
+
const t = (i + 0.5) / n;
|
|
229
|
+
const y = -0.12 + 1.1 * t; // upper hemisphere, a little below the equator
|
|
230
|
+
const r = Math.sqrt(Math.max(1 - y * y, 0.02));
|
|
231
|
+
const a = i * GOLD;
|
|
232
|
+
slot[i * 3] = r * Math.cos(a);
|
|
233
|
+
slot[i * 3 + 1] = y;
|
|
234
|
+
slot[i * 3 + 2] = r * Math.sin(a);
|
|
235
|
+
axis[i * 3] = slot[i * 3];
|
|
236
|
+
axis[i * 3 + 1] = slot[i * 3 + 1];
|
|
237
|
+
axis[i * 3 + 2] = slot[i * 3 + 2];
|
|
238
|
+
cycle[i] = 0;
|
|
239
|
+
mode[i] = 0;
|
|
240
|
+
// staggered: every seed is already part way through its wait
|
|
241
|
+
wait[i] = 4 + 11 * hash2(i, 0);
|
|
242
|
+
timer[i] = wait[i] * rand();
|
|
243
|
+
roll[i] = rand() * Math.PI * 2;
|
|
244
|
+
varr[i * 2] = 0.78 + 0.44 * rand();
|
|
245
|
+
varr[i * 2 + 1] = rand();
|
|
246
|
+
// real pappi differ in drag, so seeds sink at slightly different rates
|
|
247
|
+
settleF[i] = SETTLE * b * (0.72 + 0.6 * rand());
|
|
248
|
+
// and none is perfectly symmetric: each flies with a small fixed lean
|
|
249
|
+
const ta = rand() * Math.PI * 2, tm = 0.07 + 0.2 * rand();
|
|
250
|
+
tilt[i * 3] = Math.cos(ta) * tm;
|
|
251
|
+
tilt[i * 3 + 1] = 0;
|
|
252
|
+
tilt[i * 3 + 2] = Math.sin(ta) * tm;
|
|
253
|
+
spin[i] = (0.1 + 0.25 * rand()) * (rand() < 0.5 ? -1 : 1);
|
|
254
|
+
seedA[i * 4 + 3] = 0;
|
|
255
|
+
}
|
|
256
|
+
const geo = buildTemplate();
|
|
257
|
+
const aAttr = new THREE.InstancedBufferAttribute(seedA, 4);
|
|
258
|
+
const bAttr = new THREE.InstancedBufferAttribute(seedB, 4);
|
|
259
|
+
aAttr.setUsage(THREE.DynamicDrawUsage);
|
|
260
|
+
bAttr.setUsage(THREE.DynamicDrawUsage);
|
|
261
|
+
geo.setAttribute('aSeedA', aAttr);
|
|
262
|
+
geo.setAttribute('aSeedB', bAttr);
|
|
263
|
+
geo.setAttribute('aVar', new THREE.InstancedBufferAttribute(varr, 2));
|
|
264
|
+
geo.instanceCount = n;
|
|
265
|
+
const mat = new THREE.ShaderMaterial({
|
|
266
|
+
vertexShader: vert, fragmentShader: frag, uniforms: u,
|
|
267
|
+
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
|
|
268
|
+
side: THREE.DoubleSide,
|
|
269
|
+
});
|
|
270
|
+
const mesh = new THREE.Mesh(geo, mat);
|
|
271
|
+
mesh.frustumCulled = false;
|
|
272
|
+
const vp = new THREE.Vector2();
|
|
273
|
+
mesh.onBeforeRender = (renderer) => {
|
|
274
|
+
renderer.getDrawingBufferSize(vp);
|
|
275
|
+
u.uViewH.value = vp.y * 0.5;
|
|
276
|
+
};
|
|
277
|
+
const group = new THREE.Group();
|
|
278
|
+
group.add(mesh);
|
|
279
|
+
let simTime = 0;
|
|
280
|
+
let acc = 0;
|
|
281
|
+
/** air velocity at (x,y,z): mean wind around the head + curl gusts + updraft */
|
|
282
|
+
function airAt(x, y, z, t) {
|
|
283
|
+
const ws = Math.max(u.uWindSpeed.value, 0) * b;
|
|
284
|
+
const ang = (u.uWindDirection.value * Math.PI) / 180;
|
|
285
|
+
let wx = Math.cos(ang) * ws;
|
|
286
|
+
let wz = Math.sin(ang) * ws;
|
|
287
|
+
let wy = 0;
|
|
288
|
+
if (hasHost) {
|
|
289
|
+
// potential flow past a sphere: tangent at the surface, uniform far away
|
|
290
|
+
const aH = 1.12 * b;
|
|
291
|
+
const r2 = x * x + y * y + z * z;
|
|
292
|
+
const r = Math.sqrt(r2) + 1e-5;
|
|
293
|
+
if (r < 8 * aH) {
|
|
294
|
+
const rr = Math.max(r, aH);
|
|
295
|
+
const k = (aH * aH * aH) / (2 * rr * rr * rr);
|
|
296
|
+
const dotp = (wx * x + wz * z) / (rr * rr);
|
|
297
|
+
const px = wx * (1 + k) - 3 * k * dotp * x;
|
|
298
|
+
const py = -3 * k * dotp * y;
|
|
299
|
+
const pz = wz * (1 + k) - 3 * k * dotp * z;
|
|
300
|
+
wx = px;
|
|
301
|
+
wy = py;
|
|
302
|
+
wz = pz;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Divergence-free gusts: the analytic curl of a trig vector potential,
|
|
306
|
+
// sampled in coordinates carried along by the mean wind (Taylor's frozen
|
|
307
|
+
// turbulence). Advecting the pattern matters: a gust field pinned to
|
|
308
|
+
// space would have fixed stagnation points that collect seeds.
|
|
309
|
+
const ax0 = x - Math.cos(ang) * ws * t;
|
|
310
|
+
const az0 = z - Math.sin(ang) * ws * t;
|
|
311
|
+
const g = Math.max(u.uTurbulence.value, 0) * (0.4 * ws + 0.05 * b);
|
|
312
|
+
const k1 = 0.62 / b;
|
|
313
|
+
const p1 = 0.21 * t, p2 = 0.17 * t + 2.1, p3 = 0.13 * t + 4.7;
|
|
314
|
+
wx += g * (Math.sin(k1 * az0 + p1) + 0.6 * Math.cos(k1 * y * 1.3 + p3));
|
|
315
|
+
wy += g * (0.7 * Math.sin(k1 * ax0 + p2) + 0.7 * Math.cos(k1 * az0 * 1.2 + p1));
|
|
316
|
+
wz += g * (0.6 * Math.sin(k1 * y + p3) + Math.cos(k1 * ax0 * 1.1 + p2));
|
|
317
|
+
// slow, wide thermals, also carried along with the wind
|
|
318
|
+
const lift = Math.max(u.uLift.value, 0) * SETTLE * b;
|
|
319
|
+
wy += lift * (1.0 + 0.9 * Math.sin(0.42 * ax0 / b + 0.15 * t) * Math.cos(0.36 * az0 / b - 0.11 * t));
|
|
320
|
+
air[0] = wx;
|
|
321
|
+
air[1] = wy;
|
|
322
|
+
air[2] = wz;
|
|
323
|
+
}
|
|
324
|
+
function hubRadius() {
|
|
325
|
+
return b + 1.45 * 0.16 * Math.max(u.uSeedSize.value, 0.1) * b;
|
|
326
|
+
}
|
|
327
|
+
function respawn(i) {
|
|
328
|
+
cycle[i]++;
|
|
329
|
+
mode[i] = 0;
|
|
330
|
+
timer[i] = 0;
|
|
331
|
+
wait[i] = 4 + 11 * hash2(i, cycle[i]);
|
|
332
|
+
roll[i] = hash2(i, cycle[i] + 7919) * Math.PI * 2;
|
|
333
|
+
}
|
|
334
|
+
function step(h) {
|
|
335
|
+
const t = simTime;
|
|
336
|
+
const hubR = hubRadius();
|
|
337
|
+
const ws = Math.max(u.uWindSpeed.value, 0);
|
|
338
|
+
const fadeR = FADE_R * b;
|
|
339
|
+
for (let i = 0; i < n; i++) {
|
|
340
|
+
const i3 = i * 3, i4 = i * 4;
|
|
341
|
+
roll[i] += h * spin[i];
|
|
342
|
+
timer[i] += h;
|
|
343
|
+
if (mode[i] === 0) {
|
|
344
|
+
// still on the head: sit on the slot, fade in, and wait for a gust
|
|
345
|
+
const sx = slot[i3], sy = slot[i3 + 1], sz = slot[i3 + 2];
|
|
346
|
+
pos[i3] = sx * hubR;
|
|
347
|
+
pos[i3 + 1] = sy * hubR;
|
|
348
|
+
pos[i3 + 2] = sz * hubR;
|
|
349
|
+
axis[i3] = sx;
|
|
350
|
+
axis[i3 + 1] = sy;
|
|
351
|
+
axis[i3 + 2] = sz;
|
|
352
|
+
seedA[i4 + 3] = Math.min(timer[i] / 1.6, 1);
|
|
353
|
+
// stronger wind pulls seeds loose sooner
|
|
354
|
+
if (timer[i] > wait[i] * (0.45 / Math.max(ws, 0.06))) {
|
|
355
|
+
mode[i] = 1;
|
|
356
|
+
timer[i] = 0;
|
|
357
|
+
airAt(pos[i3], pos[i3 + 1], pos[i3 + 2], t);
|
|
358
|
+
vel[i3] = air[0] * 0.2;
|
|
359
|
+
vel[i3 + 1] = air[1] * 0.2;
|
|
360
|
+
vel[i3 + 2] = air[2] * 0.2;
|
|
361
|
+
}
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
let x = pos[i3], y = pos[i3 + 1], z = pos[i3 + 2];
|
|
365
|
+
airAt(x, y, z, t);
|
|
366
|
+
// relax toward the air's velocity minus the slow terminal fall
|
|
367
|
+
const tx = air[0], ty = air[1] - settleF[i], tz = air[2];
|
|
368
|
+
const k = Math.min(h / TAU_V, 1);
|
|
369
|
+
let vx = vel[i3] + (tx - vel[i3]) * k;
|
|
370
|
+
let vy = vel[i3 + 1] + (ty - vel[i3 + 1]) * k;
|
|
371
|
+
let vz = vel[i3 + 2] + (tz - vel[i3 + 2]) * k;
|
|
372
|
+
x += vx * h;
|
|
373
|
+
y += vy * h;
|
|
374
|
+
z += vz * h;
|
|
375
|
+
// the head is solid: slide around it
|
|
376
|
+
if (hasHost) {
|
|
377
|
+
const d = Math.sqrt(x * x + y * y + z * z);
|
|
378
|
+
const lim = hubR * 0.99;
|
|
379
|
+
if (d < lim && d > 1e-5) {
|
|
380
|
+
const f = lim / d;
|
|
381
|
+
x *= f;
|
|
382
|
+
y *= f;
|
|
383
|
+
z *= f;
|
|
384
|
+
const vr = (vx * x + vy * y + vz * z) / (lim * lim);
|
|
385
|
+
vx -= vr * x;
|
|
386
|
+
vy -= vr * y;
|
|
387
|
+
vz -= vr * z;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
vel[i3] = vx;
|
|
391
|
+
vel[i3 + 1] = vy;
|
|
392
|
+
vel[i3 + 2] = vz;
|
|
393
|
+
pos[i3] = x;
|
|
394
|
+
pos[i3 + 1] = y;
|
|
395
|
+
pos[i3 + 2] = z;
|
|
396
|
+
// drag turns the pappus into the relative airflow (achene downwind-down)
|
|
397
|
+
const rx = air[0] - vx, ry = air[1] - vy, rz = air[2] - vz;
|
|
398
|
+
let ax = rx + tilt[i3] * settleF[i] * 4, ay = ry + settleF[i] * 0.6, az = rz + tilt[i3 + 2] * settleF[i] * 4;
|
|
399
|
+
const am = Math.sqrt(ax * ax + ay * ay + az * az);
|
|
400
|
+
if (am < 1e-4) {
|
|
401
|
+
ax = 0;
|
|
402
|
+
ay = 1;
|
|
403
|
+
az = 0;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
ax /= am;
|
|
407
|
+
ay /= am;
|
|
408
|
+
az /= am;
|
|
409
|
+
}
|
|
410
|
+
const ka = Math.min(h / 0.35, 1);
|
|
411
|
+
let nx = axis[i3] + (ax - axis[i3]) * ka;
|
|
412
|
+
let ny = axis[i3 + 1] + (ay - axis[i3 + 1]) * ka;
|
|
413
|
+
let nz = axis[i3 + 2] + (az - axis[i3 + 2]) * ka;
|
|
414
|
+
const nm = Math.sqrt(nx * nx + ny * ny + nz * nz) + 1e-6;
|
|
415
|
+
axis[i3] = nx / nm;
|
|
416
|
+
axis[i3 + 1] = ny / nm;
|
|
417
|
+
axis[i3 + 2] = nz / nm;
|
|
418
|
+
const dist = Math.sqrt(x * x + y * y + z * z);
|
|
419
|
+
if (mode[i] === 1) {
|
|
420
|
+
seedA[i4 + 3] = 1;
|
|
421
|
+
if (dist > fadeR || timer[i] > LIFE) {
|
|
422
|
+
mode[i] = 2;
|
|
423
|
+
timer[i] = 0;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const f = 1 - timer[i] / 1.4;
|
|
428
|
+
seedA[i4 + 3] = Math.max(f, 0);
|
|
429
|
+
if (f <= 0)
|
|
430
|
+
respawn(i);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function writeInstances() {
|
|
435
|
+
for (let i = 0; i < n; i++) {
|
|
436
|
+
const i3 = i * 3, i4 = i * 4;
|
|
437
|
+
seedA[i4] = pos[i3];
|
|
438
|
+
seedA[i4 + 1] = pos[i3 + 1];
|
|
439
|
+
seedA[i4 + 2] = pos[i3 + 2];
|
|
440
|
+
seedB[i4] = axis[i3];
|
|
441
|
+
seedB[i4 + 1] = axis[i3 + 1];
|
|
442
|
+
seedB[i4 + 2] = axis[i3 + 2];
|
|
443
|
+
seedB[i4 + 3] = roll[i];
|
|
444
|
+
}
|
|
445
|
+
aAttr.needsUpdate = true;
|
|
446
|
+
bAttr.needsUpdate = true;
|
|
447
|
+
}
|
|
448
|
+
step(0);
|
|
449
|
+
writeInstances();
|
|
450
|
+
return {
|
|
451
|
+
object: group,
|
|
452
|
+
materials: [mat],
|
|
453
|
+
apply(c2) { },
|
|
454
|
+
update(dt, camera) {
|
|
455
|
+
const h = Math.min(Math.max(dt, 0), 0.1) * Math.max(u.uSpeed.value, 0);
|
|
456
|
+
u.uTime.value += h;
|
|
457
|
+
acc += h;
|
|
458
|
+
let guard = 0;
|
|
459
|
+
while (acc >= SIM_DT && guard < 6) {
|
|
460
|
+
step(SIM_DT);
|
|
461
|
+
simTime += SIM_DT;
|
|
462
|
+
acc -= SIM_DT;
|
|
463
|
+
guard++;
|
|
464
|
+
}
|
|
465
|
+
if (guard >= 6)
|
|
466
|
+
acc = 0;
|
|
467
|
+
writeInstances();
|
|
468
|
+
},
|
|
469
|
+
dispose() { geo.dispose(); mat.dispose(); },
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/** Defaults for every knob, so an older saved config still renders. */
|
|
473
|
+
const DEFAULTS = {
|
|
474
|
+
intensity: 1,
|
|
475
|
+
speed: 1,
|
|
476
|
+
color: '#ffeccf',
|
|
477
|
+
opacity: 1,
|
|
478
|
+
seedCount: 90,
|
|
479
|
+
windSpeed: 0.55,
|
|
480
|
+
windDirection: 20,
|
|
481
|
+
turbulence: 0.8,
|
|
482
|
+
seedSize: 1,
|
|
483
|
+
lift: 1,
|
|
484
|
+
stemColor: '#d8b183',
|
|
485
|
+
};
|
|
486
|
+
const DandelionSeedsEffect = ({ boundingRadius, config, animations, hasHost = false, onObjectReady, onObjectDispose }) => {
|
|
487
|
+
const rootRef = useRef(null);
|
|
488
|
+
const animCache = useRef({});
|
|
489
|
+
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
490
|
+
// a standalone effect gets boundingRadius 1
|
|
491
|
+
const b = boundingRadius > 0 ? boundingRadius : 1;
|
|
492
|
+
const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
|
|
493
|
+
const env = useMemo(() => ({ hasHost: hasHost }), [hasHost]);
|
|
494
|
+
// uniforms outlive the objects, so keyframe animations survive a rebuild
|
|
495
|
+
const uniforms = useMemo(() => createUniforms(), []);
|
|
496
|
+
// rebuilt only when the host size or a structural knob changes
|
|
497
|
+
const instance = useMemo(() => build(uniforms, { ...DEFAULTS, ...config }, b, env), [uniforms, b, env, cfg.seedCount]);
|
|
498
|
+
useEffect(() => () => instance.dispose(), [instance]);
|
|
499
|
+
useLayoutEffect(() => {
|
|
500
|
+
applyConfig(uniforms, cfg, b, env);
|
|
501
|
+
instance.apply(cfg);
|
|
502
|
+
}, [instance, uniforms, cfg, b, env]);
|
|
503
|
+
useFrame((state, delta) => {
|
|
504
|
+
instance.update(Math.min(delta, 0.1), state.camera);
|
|
505
|
+
if (animations?.length)
|
|
506
|
+
applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
|
|
507
|
+
});
|
|
508
|
+
return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
|
|
509
|
+
};
|
|
510
|
+
export default DandelionSeedsEffect;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AnimationOptions, FallingLeavesConfig } from '@3driseai/3drise-core';
|
|
3
|
+
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
|
+
export interface FallingLeavesEffectProps extends ObjectReadyProps {
|
|
5
|
+
boundingRadius: number;
|
|
6
|
+
config: FallingLeavesConfig;
|
|
7
|
+
animations?: AnimationOptions[];
|
|
8
|
+
/** True when the effect is attached to an object, so it can react to the host. */
|
|
9
|
+
hasHost?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare const FallingLeavesEffect: React.FC<FallingLeavesEffectProps>;
|
|
12
|
+
export default FallingLeavesEffect;
|
|
13
|
+
//# sourceMappingURL=FallingLeavesEffect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FallingLeavesEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/FallingLeavesEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAwZD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA+B3D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|