@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,539 @@
|
|
|
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
|
+
* A starling murmuration: thousands of boids with topological neighbours swirl around a drifting roost, folding into dense, wave-like sheets. Use for dusk, wildlife, flocking, atmospheric scenes.
|
|
9
|
+
*
|
|
10
|
+
* Reynolds boids on the 7 nearest neighbours (spatial hash): separation + alignment + cohesion, Lissajous roost, |v| clamped, bank = atan(a⊥/g)
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Reynolds boids with a topological neighbourhood (Ballerini et al. showed
|
|
14
|
+
// starlings interact with a fixed number of nearest neighbours, ~7, not with
|
|
15
|
+
// everything inside a fixed radius). Each bird steers by
|
|
16
|
+
// a = wSep * sum (p_i - p_j) (1 - d^2/dSep^2)/d + wAli * (mean v_j - v_i)
|
|
17
|
+
// + wCoh * (mean p_j - p_i) + roost + flock centring + boundary,
|
|
18
|
+
// with |a| capped (a real bird's turn radius) and |v| held near cruise speed.
|
|
19
|
+
// The nearest seven are found with a uniform spatial hash, so the cost is O(N).
|
|
20
|
+
// A roost point drifts along a Lissajous path and the flock orbits it, which
|
|
21
|
+
// keeps the shape turning over; because alignment is topological, a turn
|
|
22
|
+
// started by a few birds propagates as an agitation wave, and the sheets fold
|
|
23
|
+
// on themselves - the dense, dark folds of a real murmuration. Banking is
|
|
24
|
+
// taken from the lateral acceleration, so birds flash brighter and darker as
|
|
25
|
+
// the wave passes, exactly as a real flock does.
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
const vert = /* glsl */ `
|
|
28
|
+
// position holds the chevron template: (along, across, wing-tip flag)
|
|
29
|
+
attribute vec3 aPos;
|
|
30
|
+
attribute vec4 aVel; // velocity xyz, bank angle w
|
|
31
|
+
attribute float aPhase;
|
|
32
|
+
uniform float uTime;
|
|
33
|
+
uniform float uBirdSize;
|
|
34
|
+
uniform float uBound;
|
|
35
|
+
uniform float uViewH;
|
|
36
|
+
uniform float uLead;
|
|
37
|
+
uniform float uOpacity;
|
|
38
|
+
varying float vShade;
|
|
39
|
+
varying float vAlpha;
|
|
40
|
+
varying float vEdge;
|
|
41
|
+
|
|
42
|
+
void main(){
|
|
43
|
+
vec3 p = aPos + aVel.xyz * uLead;
|
|
44
|
+
vec3 f = aVel.xyz;
|
|
45
|
+
float sp = length(f);
|
|
46
|
+
f = sp > 1e-5 ? f / sp : vec3(0.0, 0.0, 1.0);
|
|
47
|
+
vec3 side = cross(f, vec3(0.0, 1.0, 0.0));
|
|
48
|
+
float sl = length(side);
|
|
49
|
+
side = sl > 1e-4 ? side / sl : vec3(1.0, 0.0, 0.0);
|
|
50
|
+
vec3 up = cross(side, f);
|
|
51
|
+
float b = aVel.w;
|
|
52
|
+
vec3 sb = side * cos(b) + up * sin(b);
|
|
53
|
+
vec3 ub = up * cos(b) - side * sin(b);
|
|
54
|
+
|
|
55
|
+
vec4 mvC = modelViewMatrix * vec4(p, 1.0);
|
|
56
|
+
float pxPerUnit = projectionMatrix[1][1] * uViewH / max(-mvC.z, 0.05);
|
|
57
|
+
float L = uBirdSize * 0.075 * uBound;
|
|
58
|
+
// never let a bird fall under ~2 px: grow it and give back the energy in alpha
|
|
59
|
+
float grow = max(1.0, 2.2 / max(L * pxPerUnit, 1e-4));
|
|
60
|
+
L *= grow;
|
|
61
|
+
vAlpha = uOpacity / grow;
|
|
62
|
+
|
|
63
|
+
// wings beat with a per-bird phase, in bursts between short glides
|
|
64
|
+
float beat = uTime * 7.4 + aPhase * 30.0;
|
|
65
|
+
float glide = smoothstep(0.25, 0.6, sin(uTime * 0.5 + aPhase * 12.0) * 0.5 + 0.6);
|
|
66
|
+
float flap = sin(beat) * 0.42 * glide * abs(position.y) * position.z;
|
|
67
|
+
vec3 world = p + f * (position.x * L) + sb * (position.y * L) + ub * (flap * L);
|
|
68
|
+
|
|
69
|
+
// wing plane catches the light from above: banking birds flash, as in a real flock
|
|
70
|
+
vec3 lightDir = normalize(vec3(0.25, 1.0, 0.4));
|
|
71
|
+
vShade = 0.42 + 0.58 * pow(abs(dot(ub, lightDir)), 0.7);
|
|
72
|
+
vEdge = position.z;
|
|
73
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(world, 1.0);
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
const frag = /* glsl */ `
|
|
77
|
+
uniform vec3 uColor;
|
|
78
|
+
uniform float uIntensity;
|
|
79
|
+
varying float vShade;
|
|
80
|
+
varying float vAlpha;
|
|
81
|
+
varying float vEdge;
|
|
82
|
+
void main(){
|
|
83
|
+
float a = vAlpha * mix(0.85, 1.0, vEdge);
|
|
84
|
+
if (a < 0.004) discard;
|
|
85
|
+
vec3 col = uColor * vShade * uIntensity;
|
|
86
|
+
// premultiplied: birds are small opaque silhouettes, density does the rest
|
|
87
|
+
gl_FragColor = vec4(col * a, a);
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
function mulberry32(seed) {
|
|
91
|
+
return () => {
|
|
92
|
+
seed |= 0;
|
|
93
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
94
|
+
let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
95
|
+
r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
|
|
96
|
+
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function createUniforms() {
|
|
100
|
+
return {
|
|
101
|
+
uTime: { value: 0 },
|
|
102
|
+
uSpeed: { value: 1 },
|
|
103
|
+
uIntensity: { value: 1 },
|
|
104
|
+
uOpacity: { value: 0.92 },
|
|
105
|
+
uColor: { value: new THREE.Color() },
|
|
106
|
+
uFlockRadius: { value: 2.6 },
|
|
107
|
+
uCohesion: { value: 1 },
|
|
108
|
+
uAlignment: { value: 1 },
|
|
109
|
+
uSeparation: { value: 1 },
|
|
110
|
+
uBirdSize: { value: 1 },
|
|
111
|
+
uBound: { value: 1 },
|
|
112
|
+
uViewH: { value: 800 },
|
|
113
|
+
uLead: { value: 0 },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function applyConfig(u, c, bound, env) {
|
|
117
|
+
u.uSpeed.value = c.speed;
|
|
118
|
+
u.uIntensity.value = c.intensity;
|
|
119
|
+
u.uOpacity.value = c.opacity;
|
|
120
|
+
u.uColor.value.set(c.color);
|
|
121
|
+
u.uFlockRadius.value = c.flockRadius;
|
|
122
|
+
u.uCohesion.value = c.cohesion;
|
|
123
|
+
u.uAlignment.value = c.alignment;
|
|
124
|
+
u.uSeparation.value = c.separation;
|
|
125
|
+
u.uBirdSize.value = c.birdSize;
|
|
126
|
+
u.uBound.value = bound;
|
|
127
|
+
}
|
|
128
|
+
function build(u, c, bound, env) {
|
|
129
|
+
const n = Math.round(Math.min(Math.max(c.count, 100), 4000));
|
|
130
|
+
const hasHost = !!(env && env.hasHost);
|
|
131
|
+
const b = bound;
|
|
132
|
+
const K = 7; // topological neighbourhood size
|
|
133
|
+
const SIM_DT = 1 / 25; // fixed sim rate; the shader extrapolates the rest of the frame
|
|
134
|
+
const TABLE = 4096; // spatial-hash buckets (power of two)
|
|
135
|
+
const MASK = TABLE - 1;
|
|
136
|
+
const rand = mulberry32(5501);
|
|
137
|
+
const pos = new Float32Array(n * 3);
|
|
138
|
+
const vel = new Float32Array(n * 4); // vx, vy, vz, bank
|
|
139
|
+
const phase = new Float32Array(n);
|
|
140
|
+
const nb = new Int32Array(n * K);
|
|
141
|
+
const nbCount = new Int32Array(n);
|
|
142
|
+
const bucketCount = new Int32Array(TABLE + 1);
|
|
143
|
+
const bucketStart = new Int32Array(TABLE + 1);
|
|
144
|
+
const cursor = new Int32Array(TABLE);
|
|
145
|
+
const hashOf = new Int32Array(n);
|
|
146
|
+
const sorted = new Int32Array(n);
|
|
147
|
+
const seen = new Int32Array(27);
|
|
148
|
+
const dBest = new Float32Array(K);
|
|
149
|
+
const cy = 0.95 * b; // centre of the airspace, above the host
|
|
150
|
+
for (let i = 0; i < n; i++) {
|
|
151
|
+
// start in a loose ball, already moving roughly around the roost
|
|
152
|
+
const uz = rand() * 2 - 1, ph = rand() * Math.PI * 2;
|
|
153
|
+
const rr = Math.cbrt(rand()) * c.flockRadius * 0.5 * b;
|
|
154
|
+
const sxy = Math.sqrt(1 - uz * uz);
|
|
155
|
+
const x = rr * sxy * Math.cos(ph), y = rr * uz * 0.5, z = rr * sxy * Math.sin(ph);
|
|
156
|
+
pos[i * 3] = x;
|
|
157
|
+
pos[i * 3 + 1] = y + cy;
|
|
158
|
+
pos[i * 3 + 2] = z;
|
|
159
|
+
const sp = 0.8 * b;
|
|
160
|
+
vel[i * 4] = -z * 0.6 + (rand() - 0.5) * sp;
|
|
161
|
+
vel[i * 4 + 1] = (rand() - 0.5) * sp * 0.3;
|
|
162
|
+
vel[i * 4 + 2] = x * 0.6 + (rand() - 0.5) * sp;
|
|
163
|
+
const m = Math.hypot(vel[i * 4], vel[i * 4 + 1], vel[i * 4 + 2]) + 1e-6;
|
|
164
|
+
vel[i * 4] *= sp / m;
|
|
165
|
+
vel[i * 4 + 1] *= sp / m;
|
|
166
|
+
vel[i * 4 + 2] *= sp / m;
|
|
167
|
+
vel[i * 4 + 3] = 0;
|
|
168
|
+
phase[i] = rand();
|
|
169
|
+
}
|
|
170
|
+
// one chevron: two triangles, nose forward, wing tips swept back
|
|
171
|
+
const local = new Float32Array([
|
|
172
|
+
0.55, 0, 0, -0.3, -0.5, 1, -0.12, 0, 0,
|
|
173
|
+
0.55, 0, 0, -0.12, 0, 0, -0.3, 0.5, 1,
|
|
174
|
+
]);
|
|
175
|
+
const geo = new THREE.InstancedBufferGeometry();
|
|
176
|
+
geo.setAttribute('position', new THREE.BufferAttribute(local, 3));
|
|
177
|
+
const posAttr = new THREE.InstancedBufferAttribute(pos, 3);
|
|
178
|
+
const velAttr = new THREE.InstancedBufferAttribute(vel, 4);
|
|
179
|
+
posAttr.setUsage(THREE.DynamicDrawUsage);
|
|
180
|
+
velAttr.setUsage(THREE.DynamicDrawUsage);
|
|
181
|
+
geo.setAttribute('aPos', posAttr);
|
|
182
|
+
geo.setAttribute('aVel', velAttr);
|
|
183
|
+
geo.setAttribute('aPhase', new THREE.InstancedBufferAttribute(phase, 1));
|
|
184
|
+
geo.instanceCount = n;
|
|
185
|
+
const mat = new THREE.ShaderMaterial({
|
|
186
|
+
vertexShader: vert, fragmentShader: frag, uniforms: u,
|
|
187
|
+
transparent: true, depthWrite: false, blending: THREE.NormalBlending,
|
|
188
|
+
premultipliedAlpha: true, side: THREE.DoubleSide,
|
|
189
|
+
});
|
|
190
|
+
const mesh = new THREE.Mesh(geo, mat);
|
|
191
|
+
mesh.frustumCulled = false;
|
|
192
|
+
const vp = new THREE.Vector2();
|
|
193
|
+
mesh.onBeforeRender = (renderer) => {
|
|
194
|
+
renderer.getDrawingBufferSize(vp);
|
|
195
|
+
u.uViewH.value = vp.y * 0.5;
|
|
196
|
+
};
|
|
197
|
+
const group = new THREE.Group();
|
|
198
|
+
group.add(mesh);
|
|
199
|
+
let simTime = 0;
|
|
200
|
+
let acc = 0;
|
|
201
|
+
let steps = 0;
|
|
202
|
+
let cell = 0.12 * Math.max(c.flockRadius, 1) * b; // hash cell, self-tuned below
|
|
203
|
+
function hashCell(ix, iy, iz) {
|
|
204
|
+
return ((ix * 73856093) ^ (iy * 19349663) ^ (iz * 83492791)) & MASK;
|
|
205
|
+
}
|
|
206
|
+
function buildGrid() {
|
|
207
|
+
for (let h = 0; h <= TABLE; h++)
|
|
208
|
+
bucketCount[h] = 0;
|
|
209
|
+
const inv = 1 / cell;
|
|
210
|
+
for (let i = 0; i < n; i++) {
|
|
211
|
+
const h = hashCell(Math.floor(pos[i * 3] * inv), Math.floor(pos[i * 3 + 1] * inv), Math.floor(pos[i * 3 + 2] * inv));
|
|
212
|
+
hashOf[i] = h;
|
|
213
|
+
bucketCount[h]++;
|
|
214
|
+
}
|
|
215
|
+
let s = 0;
|
|
216
|
+
for (let h = 0; h < TABLE; h++) {
|
|
217
|
+
bucketStart[h] = s;
|
|
218
|
+
cursor[h] = s;
|
|
219
|
+
s += bucketCount[h];
|
|
220
|
+
}
|
|
221
|
+
bucketStart[TABLE] = s;
|
|
222
|
+
for (let i = 0; i < n; i++) {
|
|
223
|
+
sorted[cursor[hashOf[i]]++] = i;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** the K nearest neighbours of every bird, from the 27 cells around it.
|
|
227
|
+
* The cell size tunes itself so each bird examines ~26 candidates: the
|
|
228
|
+
* flock's density changes by an order of magnitude as it folds. */
|
|
229
|
+
function findNeighbours() {
|
|
230
|
+
let cand = 0;
|
|
231
|
+
let got = 0;
|
|
232
|
+
const inv = 1 / cell;
|
|
233
|
+
const rMax = cell * 2;
|
|
234
|
+
const r2Max = rMax * rMax;
|
|
235
|
+
for (let i = 0; i < n; i++) {
|
|
236
|
+
const i3 = i * 3;
|
|
237
|
+
const x = pos[i3], y = pos[i3 + 1], z = pos[i3 + 2];
|
|
238
|
+
const cx = Math.floor(x * inv), cyi = Math.floor(y * inv), cz = Math.floor(z * inv);
|
|
239
|
+
let found = 0;
|
|
240
|
+
let worst = r2Max;
|
|
241
|
+
let nSeen = 0;
|
|
242
|
+
let budget = 96; // hard cap, so a collapsed flock cannot get slow
|
|
243
|
+
const base = i * K;
|
|
244
|
+
for (let dx = -1; dx <= 1 && budget > 0; dx++) {
|
|
245
|
+
for (let dy = -1; dy <= 1 && budget > 0; dy++) {
|
|
246
|
+
for (let dz = -1; dz <= 1 && budget > 0; dz++) {
|
|
247
|
+
const h = hashCell(cx + dx, cyi + dy, cz + dz);
|
|
248
|
+
let dup = 0;
|
|
249
|
+
for (let q = 0; q < nSeen; q++)
|
|
250
|
+
if (seen[q] === h)
|
|
251
|
+
dup = 1;
|
|
252
|
+
if (dup === 1)
|
|
253
|
+
continue;
|
|
254
|
+
seen[nSeen++] = h;
|
|
255
|
+
const end = bucketStart[h] + bucketCount[h];
|
|
256
|
+
for (let s = bucketStart[h]; s < end; s++) {
|
|
257
|
+
if (budget <= 0)
|
|
258
|
+
break;
|
|
259
|
+
budget--;
|
|
260
|
+
const j = sorted[s];
|
|
261
|
+
if (j === i)
|
|
262
|
+
continue;
|
|
263
|
+
const j3 = j * 3;
|
|
264
|
+
const ax = pos[j3] - x, ay = pos[j3 + 1] - y, az = pos[j3 + 2] - z;
|
|
265
|
+
cand++;
|
|
266
|
+
const d2 = ax * ax + ay * ay + az * az;
|
|
267
|
+
if (d2 >= worst && found >= K)
|
|
268
|
+
continue;
|
|
269
|
+
if (d2 >= r2Max)
|
|
270
|
+
continue;
|
|
271
|
+
// insertion sort into the top-K list
|
|
272
|
+
let k = found < K ? found : K - 1;
|
|
273
|
+
while (k > 0 && dBest[k - 1] > d2) {
|
|
274
|
+
dBest[k] = dBest[k - 1];
|
|
275
|
+
nb[base + k] = nb[base + k - 1];
|
|
276
|
+
k--;
|
|
277
|
+
}
|
|
278
|
+
dBest[k] = d2;
|
|
279
|
+
nb[base + k] = j;
|
|
280
|
+
if (found < K)
|
|
281
|
+
found++;
|
|
282
|
+
worst = dBest[found - 1];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
nbCount[i] = found;
|
|
288
|
+
got += found;
|
|
289
|
+
}
|
|
290
|
+
const R = Math.max(u.uFlockRadius.value, 1) * b;
|
|
291
|
+
let f = Math.cbrt(16 / Math.max(cand / n, 1));
|
|
292
|
+
if (got < n * 6.4)
|
|
293
|
+
f = Math.max(f, 1.12);
|
|
294
|
+
cell = Math.min(Math.max(cell * Math.min(Math.max(f, 0.88), 1.12), 0.02 * R), 0.5 * R);
|
|
295
|
+
}
|
|
296
|
+
const roost = new Float32Array(3);
|
|
297
|
+
const hawk = new Float32Array(3);
|
|
298
|
+
// A raptor cruising a long, slow path through the airspace. Birds near it
|
|
299
|
+
// pull away; because alignment is topological, that escape spreads outward
|
|
300
|
+
// as an agitation wave and tears the dark folds a real murmuration shows.
|
|
301
|
+
function moveHawk(t) {
|
|
302
|
+
const R = Math.max(u.uFlockRadius.value, 1) * b;
|
|
303
|
+
hawk[0] = 1.35 * R * Math.sin(0.038 * t + 0.4);
|
|
304
|
+
hawk[1] = cy + 0.5 * R * Math.sin(0.107 * t + 1.9);
|
|
305
|
+
hawk[2] = 1.35 * R * Math.sin(0.0271 * t * 2 + 2.7) * Math.cos(0.019 * t);
|
|
306
|
+
}
|
|
307
|
+
function moveRoost(t) {
|
|
308
|
+
// Lissajous drift: the roost never repeats its path exactly
|
|
309
|
+
const R = Math.max(u.uFlockRadius.value, 1) * b;
|
|
310
|
+
roost[0] = 0.22 * R * Math.sin(0.085 * t) + 0.1 * R * Math.sin(0.211 * t + 1.1);
|
|
311
|
+
roost[1] = cy + 0.12 * R * Math.sin(0.061 * t + 2.3);
|
|
312
|
+
roost[2] = 0.22 * R * Math.cos(0.073 * t + 0.7) + 0.1 * R * Math.sin(0.187 * t + 3.4);
|
|
313
|
+
}
|
|
314
|
+
function step(h) {
|
|
315
|
+
const R = Math.max(u.uFlockRadius.value, 1) * b;
|
|
316
|
+
const cruise = 0.32 * R;
|
|
317
|
+
const maxAcc = 3.2 * cruise;
|
|
318
|
+
const wSep = 3.6 * Math.max(u.uSeparation.value, 0) * cruise;
|
|
319
|
+
const wAli = 3.6 * Math.max(u.uAlignment.value, 0);
|
|
320
|
+
const wCoh = 2.4 * Math.max(u.uCohesion.value, 0);
|
|
321
|
+
const dSep = 0.055 * R;
|
|
322
|
+
const dSep2 = dSep * dSep;
|
|
323
|
+
const hostR = hasHost ? 1.3 * b : 0;
|
|
324
|
+
// flock centring: every bird keeps a weak bearing on the whole flock, the
|
|
325
|
+
// term that keeps a murmuration one body even after a raptor splits it
|
|
326
|
+
let mcx = 0, mcy = 0, mcz = 0;
|
|
327
|
+
for (let i = 0; i < n; i++) {
|
|
328
|
+
mcx += pos[i * 3];
|
|
329
|
+
mcy += pos[i * 3 + 1];
|
|
330
|
+
mcz += pos[i * 3 + 2];
|
|
331
|
+
}
|
|
332
|
+
mcx /= n;
|
|
333
|
+
mcy /= n;
|
|
334
|
+
mcz /= n;
|
|
335
|
+
moveRoost(simTime);
|
|
336
|
+
moveHawk(simTime);
|
|
337
|
+
const hx = hawk[0], hy = hawk[1], hz = hawk[2];
|
|
338
|
+
const rEsc = 0.42 * R;
|
|
339
|
+
const rEsc2 = rEsc * rEsc;
|
|
340
|
+
const rx = roost[0], ry = roost[1], rz = roost[2];
|
|
341
|
+
// the swirl axis leans and turns slowly, so the flock is never a flat disc
|
|
342
|
+
const sA = 0.95 * Math.sin(0.043 * simTime), sB = 0.95 * Math.cos(0.031 * simTime + 1.2);
|
|
343
|
+
const sN = 1 / Math.sqrt(1 + sA * sA + sB * sB);
|
|
344
|
+
const spinX = sA * sN, spinY = sN, spinZ = sB * sN;
|
|
345
|
+
for (let i = 0; i < n; i++) {
|
|
346
|
+
const i3 = i * 3, i4 = i * 4;
|
|
347
|
+
const x = pos[i3], y = pos[i3 + 1], z = pos[i3 + 2];
|
|
348
|
+
let vx = vel[i4], vy = vel[i4 + 1], vz = vel[i4 + 2];
|
|
349
|
+
let ax = 0, ay = 0, az = 0;
|
|
350
|
+
const cnt = nbCount[i];
|
|
351
|
+
if (cnt > 0) {
|
|
352
|
+
let mx = 0, my = 0, mz = 0, ux = 0, uy = 0, uz = 0;
|
|
353
|
+
const base = i * K;
|
|
354
|
+
for (let k = 0; k < cnt; k++) {
|
|
355
|
+
const j = nb[base + k];
|
|
356
|
+
const j3 = j * 3, j4 = j * 4;
|
|
357
|
+
const dx = pos[j3] - x, dy = pos[j3 + 1] - y, dz = pos[j3 + 2] - z;
|
|
358
|
+
mx += dx;
|
|
359
|
+
my += dy;
|
|
360
|
+
mz += dz;
|
|
361
|
+
ux += vel[j4];
|
|
362
|
+
uy += vel[j4 + 1];
|
|
363
|
+
uz += vel[j4 + 2];
|
|
364
|
+
const d2 = dx * dx + dy * dy + dz * dz + 1e-6;
|
|
365
|
+
if (d2 < dSep2) {
|
|
366
|
+
// soft-core repulsion, strongest right up against a neighbour
|
|
367
|
+
const f = wSep * (1 - d2 / dSep2) / Math.sqrt(d2);
|
|
368
|
+
ax -= dx * f;
|
|
369
|
+
ay -= dy * f;
|
|
370
|
+
az -= dz * f;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const inv = 1 / cnt;
|
|
374
|
+
ax += (mx * inv) * wCoh;
|
|
375
|
+
ay += (my * inv) * wCoh;
|
|
376
|
+
az += (mz * inv) * wCoh;
|
|
377
|
+
ax += (ux * inv - vx) * wAli;
|
|
378
|
+
ay += (uy * inv - vy) * wAli;
|
|
379
|
+
az += (uz * inv - vz) * wAli;
|
|
380
|
+
}
|
|
381
|
+
// roost: a soft spring in, plus a swirl about a slowly tilting axis,
|
|
382
|
+
// which is why a murmuration keeps circling and folding over its roost
|
|
383
|
+
const tx = rx - x, ty = ry - y, tz = rz - z;
|
|
384
|
+
const td = Math.sqrt(tx * tx + ty * ty + tz * tz) + 1e-5;
|
|
385
|
+
// a loose leash: no roost force at all near it, so the flock is free
|
|
386
|
+
// to organise itself into sheets instead of filling the airspace
|
|
387
|
+
const pull = 2.2 * cruise * Math.min(Math.max(td / (0.75 * R) - 0.25, 0), 1.2);
|
|
388
|
+
ax += (tx / td) * pull;
|
|
389
|
+
ay += (ty / td) * pull;
|
|
390
|
+
az += (tz / td) * pull;
|
|
391
|
+
const swirl = 0.45 * cruise / Math.max(td, 0.35 * R);
|
|
392
|
+
ax += (spinY * -tz - spinZ * -ty) * swirl;
|
|
393
|
+
ay += (spinZ * -tx - spinX * -tz) * swirl;
|
|
394
|
+
az += (spinX * -ty - spinY * -tx) * swirl;
|
|
395
|
+
// soft, flattened boundary of the airspace
|
|
396
|
+
const ey = (y - cy) / 0.78;
|
|
397
|
+
const e = Math.sqrt(x * x + ey * ey + z * z);
|
|
398
|
+
if (e > R) {
|
|
399
|
+
const f = -(e - R) * 4 * cruise / (R * e);
|
|
400
|
+
ax += x * f;
|
|
401
|
+
ay += ey * f * 0.78;
|
|
402
|
+
az += z * f;
|
|
403
|
+
}
|
|
404
|
+
if (y < -0.55 * b)
|
|
405
|
+
ay += (-0.55 * b - y) * 6 * cruise;
|
|
406
|
+
// never fly through the host
|
|
407
|
+
if (hostR > 0) {
|
|
408
|
+
const d = Math.sqrt(x * x + y * y + z * z) + 1e-5;
|
|
409
|
+
if (d < hostR) {
|
|
410
|
+
const f = (hostR - d) * 6 * cruise / (hostR * d);
|
|
411
|
+
ax += x * f;
|
|
412
|
+
ay += y * f;
|
|
413
|
+
az += z * f;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// weak bearing on the flock centre
|
|
417
|
+
const fx0 = mcx - x, fy0 = mcy - y, fz0 = mcz - z;
|
|
418
|
+
const fd = Math.sqrt(fx0 * fx0 + fy0 * fy0 + fz0 * fz0) + 1e-5;
|
|
419
|
+
const fc = 0.75 * cruise * Math.min(fd / (0.5 * R), 1) / fd;
|
|
420
|
+
ax += fx0 * fc;
|
|
421
|
+
ay += fy0 * fc;
|
|
422
|
+
az += fz0 * fc;
|
|
423
|
+
// escape from the raptor: a strong, short-range push away
|
|
424
|
+
const gx = x - hx, gy = y - hy, gz = z - hz;
|
|
425
|
+
const g2 = gx * gx + gy * gy + gz * gz;
|
|
426
|
+
if (g2 < rEsc2) {
|
|
427
|
+
const gd = Math.sqrt(g2) + 1e-4;
|
|
428
|
+
const f = 2.8 * cruise * (1 - gd / rEsc) / gd;
|
|
429
|
+
ax += gx * f;
|
|
430
|
+
ay += gy * f * 0.8;
|
|
431
|
+
az += gz * f;
|
|
432
|
+
}
|
|
433
|
+
// cap the turn a bird can pull
|
|
434
|
+
const am = Math.sqrt(ax * ax + ay * ay + az * az);
|
|
435
|
+
if (am > maxAcc) {
|
|
436
|
+
const f = maxAcc / am;
|
|
437
|
+
ax *= f;
|
|
438
|
+
ay *= f;
|
|
439
|
+
az *= f;
|
|
440
|
+
}
|
|
441
|
+
vx += ax * h;
|
|
442
|
+
vy += ay * h;
|
|
443
|
+
vz += az * h;
|
|
444
|
+
// hold the airspeed near cruise (birds do not hover or dive away)
|
|
445
|
+
let sp = Math.sqrt(vx * vx + vy * vy + vz * vz) + 1e-6;
|
|
446
|
+
const target = sp + (cruise - sp) * Math.min(h * 1.5, 1);
|
|
447
|
+
const f2 = Math.min(Math.max(target, 0.55 * cruise), 1.5 * cruise) / sp;
|
|
448
|
+
vx *= f2;
|
|
449
|
+
vy *= f2;
|
|
450
|
+
vz *= f2;
|
|
451
|
+
sp = sp * f2;
|
|
452
|
+
vel[i4] = vx;
|
|
453
|
+
vel[i4 + 1] = vy;
|
|
454
|
+
vel[i4 + 2] = vz;
|
|
455
|
+
pos[i3] = x + vx * h;
|
|
456
|
+
pos[i3 + 1] = y + vy * h;
|
|
457
|
+
pos[i3 + 2] = z + vz * h;
|
|
458
|
+
// bank: a coordinated turn tilts the wings by atan(lateral accel / g)
|
|
459
|
+
const fx = vx / sp, fy = vy / sp, fz = vz / sp;
|
|
460
|
+
let sx = fz, sy = 0, sz = -fx; // cross(f, up)
|
|
461
|
+
const sl = Math.sqrt(sx * sx + sz * sz) + 1e-5;
|
|
462
|
+
sx /= sl;
|
|
463
|
+
sz /= sl;
|
|
464
|
+
const lat = ax * sx + az * sz;
|
|
465
|
+
const bankT = Math.atan(lat / (1.6 * cruise));
|
|
466
|
+
vel[i4 + 3] += (bankT - vel[i4 + 3]) * Math.min(h * 4, 1);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
buildGrid();
|
|
470
|
+
findNeighbours();
|
|
471
|
+
return {
|
|
472
|
+
object: group,
|
|
473
|
+
materials: [mat],
|
|
474
|
+
apply(c2) { },
|
|
475
|
+
update(dt, camera) {
|
|
476
|
+
const h = Math.min(Math.max(dt, 0), 0.1) * Math.max(u.uSpeed.value, 0);
|
|
477
|
+
u.uTime.value += h;
|
|
478
|
+
acc += h;
|
|
479
|
+
let guard = 0;
|
|
480
|
+
while (acc >= SIM_DT && guard < 3) {
|
|
481
|
+
// a bird's nearest seven change slowly: refresh them every 3rd step
|
|
482
|
+
if (steps % 3 === 0) {
|
|
483
|
+
buildGrid();
|
|
484
|
+
findNeighbours();
|
|
485
|
+
}
|
|
486
|
+
step(SIM_DT);
|
|
487
|
+
simTime += SIM_DT;
|
|
488
|
+
acc -= SIM_DT;
|
|
489
|
+
steps++;
|
|
490
|
+
guard++;
|
|
491
|
+
}
|
|
492
|
+
if (guard >= 3)
|
|
493
|
+
acc = 0;
|
|
494
|
+
// the shader carries the birds forward over the part of the frame not stepped
|
|
495
|
+
u.uLead.value = acc;
|
|
496
|
+
posAttr.needsUpdate = true;
|
|
497
|
+
velAttr.needsUpdate = true;
|
|
498
|
+
},
|
|
499
|
+
dispose() { geo.dispose(); mat.dispose(); },
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/** Defaults for every knob, so an older saved config still renders. */
|
|
503
|
+
const DEFAULTS = {
|
|
504
|
+
intensity: 1,
|
|
505
|
+
speed: 1,
|
|
506
|
+
color: '#b9c8dc',
|
|
507
|
+
opacity: 0.92,
|
|
508
|
+
count: 2200,
|
|
509
|
+
flockRadius: 2.6,
|
|
510
|
+
cohesion: 1,
|
|
511
|
+
alignment: 1,
|
|
512
|
+
separation: 1,
|
|
513
|
+
birdSize: 1,
|
|
514
|
+
};
|
|
515
|
+
const MurmurationEffect = ({ boundingRadius, config, animations, hasHost = false, onObjectReady, onObjectDispose }) => {
|
|
516
|
+
const rootRef = useRef(null);
|
|
517
|
+
const animCache = useRef({});
|
|
518
|
+
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
519
|
+
// a standalone effect gets boundingRadius 1
|
|
520
|
+
const b = boundingRadius > 0 ? boundingRadius : 1;
|
|
521
|
+
const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
|
|
522
|
+
const env = useMemo(() => ({ hasHost: hasHost }), [hasHost]);
|
|
523
|
+
// uniforms outlive the objects, so keyframe animations survive a rebuild
|
|
524
|
+
const uniforms = useMemo(() => createUniforms(), []);
|
|
525
|
+
// rebuilt only when the host size or a structural knob changes
|
|
526
|
+
const instance = useMemo(() => build(uniforms, { ...DEFAULTS, ...config }, b, env), [uniforms, b, env, cfg.count]);
|
|
527
|
+
useEffect(() => () => instance.dispose(), [instance]);
|
|
528
|
+
useLayoutEffect(() => {
|
|
529
|
+
applyConfig(uniforms, cfg, b, env);
|
|
530
|
+
instance.apply(cfg);
|
|
531
|
+
}, [instance, uniforms, cfg, b, env]);
|
|
532
|
+
useFrame((state, delta) => {
|
|
533
|
+
instance.update(Math.min(delta, 0.1), state.camera);
|
|
534
|
+
if (animations?.length)
|
|
535
|
+
applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
|
|
536
|
+
});
|
|
537
|
+
return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
|
|
538
|
+
};
|
|
539
|
+
export default MurmurationEffect;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AnimationOptions, PlanetaryRingsConfig } from '@3driseai/3drise-core';
|
|
3
|
+
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
|
+
export interface PlanetaryRingsEffectProps extends ObjectReadyProps {
|
|
5
|
+
boundingRadius: number;
|
|
6
|
+
config: PlanetaryRingsConfig;
|
|
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 PlanetaryRingsEffect: React.FC<PlanetaryRingsEffectProps>;
|
|
12
|
+
export default PlanetaryRingsEffect;
|
|
13
|
+
//# sourceMappingURL=PlanetaryRingsEffect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanetaryRingsEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/PlanetaryRingsEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAGpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAqbD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA+B7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|