motionsites中文站 / Particle Brain
Particle Brain
aadmin2026-11-05 发布未分类9 阅读

Particle Brain

类型: · 收录:2026-11-05 01:13:02 · 源码:会员可见

[hidecontent type="vip1"]
# Nously — Particle Brain Scroll Hero (exact recreation prompt)

Give me a single-file, three-section scroll hero called **Nously**. A brain made of 10,000 tiny wireframe pyramids glows in gold, violet, teal and white on a black, finely grained background. It builds itself when the page loads. Your cursor pushes nearby pyramids aside and makes them grow before springs pull them back. On scroll, the brain turns and slides left for the second section, then bursts into a field of floating pyramids behind large centred text.

HTML, CSS and one module script using three.js 0.149.0 from jsDelivr through an import map. The 3D scene uses four small data files: a baked brain position texture, a scale map, a colour map and a pyramid model. Reproduce every value, string and timing exactly, and use the complete code at the end as the source of truth.

Seven rules define it:
1. **Positions live on the GPU.** A 200 × 200 EXR holds four 100 × 100 shapes, one per quadrant. Only the first, the brain, is used here. Each texel is a point in 0–1 space, scaled by `(p-0.5)*2*radius`.
2. **Two ping-pong simulations.** A position pass and a spring pass run every frame on 200 × 200 float render targets with nearest filtering. Targets come from the EXR, velocity is `(old + (target-current)*spring)*friction` with spring `.006` plus a tiny random per cone, and friction `.892`.
3. **Cones are an `InstancedMesh`** of the pyramid model with 10,000 instances at scale .1. A `MeshBasicMaterial` is patched with `onBeforeCompile`. Its vertex shader reads each cone's position from the simulation texture at `a_id*2`, spins it with 3D simplex noise, scales it from the scale map, and colours it from the colour map times `1.3`.
4. **The cursor pushes cones.** Within `smoothstep(1.25+|delta|, 0, distance)` world units of the eased mouse, cones wobble on `sin/cos(time*random)`, grow by `0.75`, and shift toward grey `0.45`.
5. **Ordering makes it ripple.** On load, the EXR is drawn once into an 8-bit target and read back. Cones are ranked by height and by side, and those ranks delay the build-up and the explosion so they sweep across the brain.
6. **Scroll progress works like the original:** section index plus the fraction of that section scrolled, eased at 10% per frame. Rotation, offset, radius and explosion are all piecewise-linear functions of it (below), each eased at 10% per frame.
7. **The shaders run on a seconds clock.** The original drives them from GSAP's ticker, which counts in seconds, so pass `requestAnimationFrame` time ÷ 1000. Milliseconds make every cone flicker.

---

## 1. Page basics

- Title: `Nously`
- Meta description: `Plug into your team's shared brainpower. Nously turns insight and expertise into knowledge anyone can find.`
- Font: `https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500&display=swap`, with preconnects. The supplied standalone file embeds the latin woff2 as base64.
- Import map: `three` → `https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js`, `three/addons/` → `https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/`. Use `EXRLoader`, `GLTFLoader`, `EffectComposer`, `RenderPass`, `UnrealBloomPass`, `ShaderPass`, `Pass` / `FullScreenQuad` and `VignetteShader`.
- The canvas is fixed full screen behind the content. Content has `pointer-events:none`, except links.
- Colours: black page, white text, gold kicker `#f2b41b`, violet buttons `#8257f5`.
- Unit: `--u:min(calc(100vw/1600),calc(100vh/900))`, which becomes `1px` at 820px and below.

## 2. Data files

The four data files are embedded as base64 data URIs in the supplied HTML Code. Load them exactly as named in the code:

- `POSITIONS_EXR`: 200 × 200 EXR, four 100 × 100 position quadrants. Use nearest filtering and `flipY:true`.
- `SCALE_PNG`: 200 × 200 per-cone scale, red channel. Use nearest filtering and `flipY:false`.
- `COLOR_PNG`: 200 × 200 per-cone colour. Use nearest filtering and `flipY:false`.
- `PYRAMID_GLB`: a thin wireframe-style pyramid mesh, about 1.6 KB.

## 3. Content

- Header, fixed: logo (a three-colour triangle and `Nously`, 22u), links `Product`, `Pricing`, `Blog` (14u uppercase, tracking .04em) and the violet pill `Request access`. A black gradient behind it fades in over the first 200px of scroll.
- **Hero**, left at 134u: `Unlock` / `collective` / `wisdom.` at 132u, line height .98, tracking -.045em. Then the gold kicker `Stop managing knowledge. Start using it.` at 12.5u. Then the 20u paragraph: `Plug into your team’s shared brainpower. Ask Nously to find anything, or anyone, across every tool you use. Focus on doing your best work with context, conviction and clarity.` Then a `Request access` pill.
- **Section 2**, starting at 68vw: `Make decisions` / `with confidence` at 47u, then the 17u paragraph `Nously’s AI search reads across your whole organisation and pulls out the knowledge you need, so you can take the guesswork out of your work.`
- **Section 3**, centred, 1380u wide, 40u text, with each pair starting 62vh down:
  - `This is work today. Countless fragments of critical knowledge scattered across hundreds of disconnected systems.`
  - `Almost a third of your week goes on hunting for the information and the expertise you need to do your job.`
  - `The endless struggle to make sense of the chaos leaves your team overwhelmed and unproductive.`
  - `So they interrupt a busy colleague yet again, or try to connect the dots with half the picture.`

## 4. Camera and scene

- `PerspectiveCamera` at z 10, near .1, far 30. Apply `setFocalLength(35)`, then set `fov` back to 50.
- On every frame it tilts toward the mouse: `rotation.y += .1*(-.075*mx - rotation.y)`, `rotation.x += .1*(.05*my - rotation.x)`.
- The cones group sits at y -1.19. Its base rotation eases from -.25π to 0 over 3s on load.
- Build-up: `u_show` goes 0 → 1 linearly over 3s. Each cone starts at radius `factor + random*3` and eases in with a quintic in-out.
- Factor 4.35 on desktop and 2.5 on mobile. Cone scale 1.55 on desktop and 1.2 on mobile.

## 5. Scroll curves (desktop, `e` = progress)

```
rotY  = clamp(map(e,0,1,0,-π/2))
offX  = clamp(map(e,0,1,3,-4.5)) + clamp(map(e,1.25,1.5,.905,5)) - .905 ...   (full sum in code)
explode = clamp(map(e,1.1,2.2,0,1))
radius  = 4.35 + clamp(map(e,0,1,0,1)) - clamp(map(e,1.25,1.5,0,1))
progress2 (mouse field blend) = clamp(map(e,0,1,0,1))
```

The explosion multiplies each cone's position by `1 + 5*random`, staggered by rank (`d3 = .00015`). Cones fade with view depth: `smoothstep(-4.5, 4.0, viewZ+10)`.

## 6. Front pyramids

250 extra pyramids float through the frame in four colours: `rgb(93,57,154)`, `rgb(186,136,43)`, `rgb(40,116,100)` and `rgb(164,148,175)`, each with a random alpha. They drift on sine paths, tumble slowly, and shift against the mouse (eased at .25×).

## 7. Post-processing

1. `RenderPass`.
2. `UnrealBloomPass`: strength .4, radius 1, threshold .159.
3. Bokeh depth-of-field (desktop only): focal depth .125, focal length 27, f-stop 2509, max blur 10, 4 rings × 6 samples, fringe .7. It uses a depth pass where each mesh writes `1 - smoothstep(.1, 13, viewZ)`.
4. Vignette: offset .3, darkness 4.

A grain layer sits 0.4 in front of the camera: a rotated `sin·sin` dot pattern at alpha .149 and brightness .252 (.138 and .185 on retina).

---

## Complete code

This is the finished page with the four data URIs replaced by their names. The supplied HTML Code section contains the same page with the data embedded.

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Nously</title>
<meta name="description" content="Plug into your team's shared brainpower. Nously turns insight and expertise into knowledge anyone can find.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{--u:min(calc(100vw/1600),calc(100vh/900));--gold:#f2b41b;--violet:#8257f5}
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:#000}
body{color:#fff;font-family:"Inter Tight",system-ui,sans-serif;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
#gl{position:fixed;inset:0;width:100%;height:100%;display:block}
header{position:fixed;left:0;right:0;top:0;z-index:5;background:linear-gradient(rgba(0,0,0,var(--bar,0)),rgba(0,0,0,0));padding-bottom:calc(30*var(--u))!important;display:flex;align-items:center;justify-content:space-between;padding:calc(34*var(--u)) calc(38*var(--u)) 0 calc(42*var(--u))}
.logo{display:flex;align-items:center;gap:calc(7*var(--u));font-size:calc(22*var(--u));font-weight:500;letter-spacing:-.03em}
.logo svg{width:calc(26*var(--u));height:calc(26*var(--u))}
nav{display:flex;align-items:center;gap:calc(40*var(--u));font-size:calc(14*var(--u));font-weight:500;letter-spacing:.04em;text-transform:uppercase}
nav a:not(.pill){color:#d8d8de;transition:color .2s}nav a:not(.pill):hover{color:#fff}
.pill{display:inline-flex;align-items:center;height:calc(42*var(--u));padding:0 calc(20*var(--u));border-radius:99px;background:var(--violet);font-size:calc(14*var(--u));font-weight:500;letter-spacing:.04em;text-transform:uppercase;transition:background .2s}
.pill:hover{background:#946ef8}
main{position:relative;z-index:2;pointer-events:none}
main a{pointer-events:auto}
section{position:relative;min-height:100svh}
.hero{display:flex;flex-direction:column;justify-content:center;padding:calc(20*var(--u)) 0 0 calc(134*var(--u));width:calc(760*var(--u))}
h1{font-size:calc(132*var(--u));line-height:.98;font-weight:400;letter-spacing:-.045em}
.kick{margin-top:calc(42*var(--u));font-size:calc(12.5*var(--u));font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--gold)}
.lead{margin-top:calc(13*var(--u));width:calc(470*var(--u));font-size:calc(20*var(--u));line-height:1.42;color:#e4e4ea}
.hero .pill{align-self:flex-start;margin-top:calc(26*var(--u));height:calc(40*var(--u))}
.second{display:flex;align-items:center;padding-left:68vw}
.second div{width:min(30vw,calc(430*var(--u)))}
h2{font-size:calc(47*var(--u));line-height:1.12;font-weight:400;letter-spacing:-.03em}
.second p{margin-top:calc(22*var(--u));font-size:calc(17*var(--u));line-height:1.48;color:#d0d0d8}
.third{min-height:auto;padding:calc(40*var(--u)) 0 calc(200*var(--u))}
.third p{width:calc(1380*var(--u));margin:0 auto calc(44*var(--u));text-align:center;font-size:calc(40*var(--u));line-height:1.26;letter-spacing:-.018em}
.third p.gap{margin-top:calc(62vh)}
@media (max-width:820px){
  :root{--u:1px}
  header{padding:16px 16px 0}
  nav a:not(.pill){display:none}
  .pill{height:38px;padding:0 16px;font-size:12px}
  .hero{width:auto;padding:0 20px 7vh;justify-content:flex-end}
  h1{font-size:clamp(48px,13.5vw,66px)}
  .lead{width:auto;font-size:17px}
  .second{padding:0 20px 12vh;align-items:flex-end}
  .second div{width:auto}
  h2{font-size:34px}
  .third p{width:auto;padding:0 20px;font-size:22px}
}
</style>
</head>
<body>
<canvas id="gl" aria-hidden="true"></canvas>
<header>
  <a class="logo" href="#" aria-label="Nously"><svg viewBox="0 0 26 26" aria-hidden="true"><path d="M13 2 24 22H2Z" fill="#8257f5"/><path d="M13 2 24 22 13 15Z" fill="#2fd1c1"/><path d="M2 22 13 15 24 22Z" fill="#f2b41b"/></svg>Nously</a>
  <nav aria-label="Main"><a href="#">Product</a><a href="#">Pricing</a><a href="#">Blog</a><a class="pill" href="#">Request access</a></nav>
</header>
<main>
  <section class="hero">
    <h1>Unlock<br>collective<br>wisdom.</h1>
    <p class="kick">Stop managing knowledge. Start using it.</p>
    <p class="lead">Plug into your team’s shared brainpower. Ask Nously to find anything, or anyone, across every tool you use. Focus on doing your best work with context, conviction and clarity.</p>
    <a class="pill" href="#">Request access</a>
  </section>
  <section class="second">
    <div><h2>Make decisions<br>with confidence</h2><p>Nously’s AI search reads across your whole organisation and pulls out the knowledge you need, so you can take the guesswork out of your work.</p></div>
  </section>
  <section class="third">
    <p class="gap">This is work today. Countless fragments of critical knowledge scattered across hundreds of disconnected systems.</p>
    <p>Almost a third of your week goes on hunting for the information and the expertise you need to do your job.</p>
    <p class="gap">The endless struggle to make sense of the chaos leaves your team overwhelmed and unproductive.</p>
    <p>So they interrupt a busy colleague yet again, or try to connect the dots with half the picture.</p>
  </section>
</main>
<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/"}}</script>
<script type="module">
import * as THREE from 'three';
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { Pass, FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';

const ASSETS={pos:'POSITIONS_EXR',scale:'SCALE_PNG',color:'COLOR_PNG',pyramid:'PYRAMID_GLB'};
const isMobile=innerWidth<=820;
const clamp=(e,t,n)=>Math.min(Math.max(e,t),n),map=(e,t,n,i,r)=>(e-t)/(n-t)*(r-i)+i;
const visibleAt=(cam,depth)=>{const h=2*Math.tan(cam.fov*Math.PI/360)*Math.abs(depth);return{width:h*cam.aspect,height:h}};

// shader chunks
const SNOISE=`#define GLSLIFY 1
//
// Description : Array and textureless GLSL 2D/3D/4D simplex 
//               noise functions.
//      Author : Ian McEwan, Ashima Arts.
//  Maintainer : stegu
//     Lastmod : 20201014 (stegu)
//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
//               Distributed under the MIT License. See LICENSE file.
//               https://github.com/ashima/webgl-noise
//               https://github.com/stegu/webgl-noise
// 

vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289(vec4 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x) {
     return mod289(((x*34.0)+10.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

float snoise(vec3 v)
  { 
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);

// First corner
  vec3 i  = floor(v + dot(v, C.yyy) );
  vec3 x0 =   v - i + dot(i, C.xxx) ;

// Other corners
  vec3 g = step(x0.yzx, x0.xyz);
  vec3 l = 1.0 - g;
  vec3 i1 = min( g.xyz, l.zxy );
  vec3 i2 = max( g.xyz, l.zxy );

  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
  //   x1 = x0 - i1  + 1.0 * C.xxx;
  //   x2 = x0 - i2  + 2.0 * C.xxx;
  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
  vec3 x1 = x0 - i1 + C.xxx;
  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y

// Permutations
  i = mod289(i); 
  vec4 p = permute( permute( permute( 
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  float n_ = 0.142857142857; // 1.0/7.0
  vec3  ns = n_ * D.wyz - D.xzx;

  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

  vec4 x_ = floor(j * ns.z);
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  vec4 x = x_ *ns.x + ns.yyyy;
  vec4 y = y_ *ns.x + ns.yyyy;
  vec4 h = 1.0 - abs(x) - abs(y);

  vec4 b0 = vec4( x.xy, y.xy );
  vec4 b1 = vec4( x.zw, y.zw );

  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
  vec4 s0 = floor(b0)*2.0 + 1.0;
  vec4 s1 = floor(b1)*2.0 + 1.0;
  vec4 sh = -step(h, vec4(0.0));

  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  vec3 p0 = vec3(a0.xy,h.x);
  vec3 p1 = vec3(a0.zw,h.y);
  vec3 p2 = vec3(a1.xy,h.z);
  vec3 p3 = vec3(a1.zw,h.w);

//Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;

// Mix final noise value
  vec4 m = max(0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 105.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
                                dot(p2,x2), dot(p3,x3) ) );
  }`;
const QINTIC_IN_OUT=`float qinticInOut(float x){float inValue=16.0*x*x*x*x*x;float outValue=1.0-pow(-2.0*x+2.0,5.0)/2.0;return step(inValue,0.5)*inValue+step(0.5,outValue)*outValue;}
`;
const QINTIC_OUT=`float qinticOut(float t){return 1.0-pow(1.0-t,5.0);}
`;
const ROTATION=`mat4 rotationMatrix(vec3 axis,float angle){axis=normalize(axis);float s=sin(angle);float c=cos(angle);float oc=1.0-c;
return mat4(oc*axis.x*axis.x+c,oc*axis.x*axis.y-axis.z*s,oc*axis.z*axis.x+axis.y*s,0.0,
oc*axis.x*axis.y+axis.z*s,oc*axis.y*axis.y+c,oc*axis.y*axis.z-axis.x*s,0.0,
oc*axis.z*axis.x-axis.y*s,oc*axis.y*axis.z+axis.x*s,oc*axis.z*axis.z+c,0.0,
0.0,0.0,0.0,1.0);}
mat3 calcLookAtMatrix(vec3 origin,vec3 target,float roll){vec3 rr=vec3(sin(roll),cos(roll),0.0);vec3 ww=normalize(target-origin);vec3 uu=normalize(cross(ww,rr));vec3 vv=normalize(cross(uu,ww));return mat3(uu,vv,ww);}
`;
const MAP=`float map(float value,float min1,float max1,float min2,float max2){return min2+(value-min1)*(max2-min2)/(max1-min1);}
`;

// brain cones: position comes from the spring simulation, the cursor pushes and grows nearby cones
const CONE_VERTEX=`vec4 mvPosition=vec4(transformed,1.0);
vec3 pos=texture2D(t_simulation,vec2(a_id.x*2.,a_id.y*2.)).xyz;
vec4 mPos=vec4(pos,1.);
mPos*=rotationMatrix(vec3(1.,0.,0.),u_rotation.x*-1.);
mPos*=rotationMatrix(vec3(0.,1.,0.),u_rotation.y*-1.);
mPos*=rotationMatrix(vec3(0.,0.,1.),u_rotation.z*-1.);
mPos.y-=1.;
mPos.x+=u_offset.x;
mPos.y+=u_offset.y;
vec2 resolution=mix(u_resolution-u_resolution*0.5,u_resolution2-u_resolution2*0.5,min(1.,max(0.,u_progress2)));
vec2 m=u_mouse*vec2(1.,1.);
float dist=smoothstep(1.25+abs(max(u_delta.x,u_delta.y)),0.,distance(mPos.xy,m*resolution));
pos.x+=dist*sin(u_time*a_random.y)*(a_random.z*0.35+u_delta.x)*abs(u_explode-1.);
pos.y+=dist*cos(u_time*a_random.y)*(a_random.z*0.35+u_delta.y)*abs(u_explode-1.);
float n=snoise(pos*u_amplitude);
mat4 rotMat2=rotationMatrix(vec3(0.,1.,1.),mod(n+u_time,3.1416*2.));
float scale=texture2D(t_scale,a_id).x*u_scale;
float scale2=texture2D(t_scale,vec2(a_id.x+0.5,a_id.y)).x*u_scale;
float scale3=texture2D(t_scale,vec2(a_id.x,a_id.y+0.5)).x*u_scale;
float scale4=texture2D(t_scale,vec2(a_id.x+0.5,a_id.y+0.5)).x*u_scale;
scale=mix(scale,scale2,u_progress);
scale=mix(scale,scale3,max(0.,u_progress-1.));
scale=mix(scale,scale4,max(0.,u_progress-2.));
scale+=dist*0.75*abs(u_explode-1.);
#ifdef USE_INSTANCING
  mat4 instanceMat=instanceMatrix;
  instanceMat[3][0]=instanceMat[3][0]+pos.x;
  instanceMat[3][1]=instanceMat[3][1]+pos.y;
  instanceMat[3][2]=instanceMat[3][2]+pos.z;
  mat4 rotMat3=mat4(calcLookAtMatrix(vec3(instanceMat[3][0]+u_offset.x,instanceMat[3][1]+u_offset.y,instanceMat[3][2]),cameraPosition,0.));
  instanceMat[0][0]=instanceMat[0][0]*scale;
  instanceMat[1][1]=instanceMat[1][1]*scale;
  instanceMat[2][2]=instanceMat[2][2]*scale;
  if(u_mobileRotation>0.){instanceMat*=rotMat3;}else{instanceMat*=rotMat2;}
  mvPosition=instanceMat*mvPosition;
#endif
mat4 modViewMatrix=modelViewMatrix;
modViewMatrix[3][0]+=u_offset.x;
modViewMatrix[3][1]+=u_offset.y;
modViewMatrix*=rotationMatrix(vec3(1.,0.,0.),u_rotation.x);
modViewMatrix*=rotationMatrix(vec3(0.,1.,0.),u_rotation.y);
modViewMatrix*=rotationMatrix(vec3(0.,0.,1.),u_rotation.z);
mvPosition=modViewMatrix*mvPosition;
v_pos=mvPosition.xyz;
v_pos.z+=10.;
gl_Position=projectionMatrix*mvPosition;
v_id=a_id;
v_hover=dist;`;
const CONE_FRAGMENT=`vec3 col=texture2D(t_color,v_id).xyz;
vec3 col2=texture2D(t_color,vec2(v_id.x+0.5,v_id.y)).xyz;
vec3 col3=texture2D(t_color,vec2(v_id.x,v_id.y+0.5)).xyz;
vec3 col4=texture2D(t_color,vec2(v_id.x+0.5,v_id.y+0.5)).xyz;
col=mix(col,col2,u_progress);
col=mix(col,col3,max(0.,u_progress-1.));
col=mix(col,col4,max(0.,u_progress-2.));
col=mix(col,vec3(0.45),max(0.,v_hover-u_explode)*abs(u_explode-1.));
outgoingLight.rgb=col.rgb*u_colorFactor;
float alpha=diffuseColor.a*smoothstep(-4.5,4.,v_pos.z);
gl_FragColor=vec4(outgoingLight,alpha);`;
const WORLDPOS=`#if defined(USE_ENVMAP)||defined(DISTANCE)||defined(USE_SHADOWMAP)||defined(USE_TRANSMISSION)
vec4 worldPosition=vec4(transformed,1.0);
#ifdef USE_INSTANCING
worldPosition=instanceMat*worldPosition;
#endif
worldPosition=modelMatrix*worldPosition;
#endif`;
const DEPTH_FRAGMENT=`float color=1.0-smoothstep(u_near,u_far,vViewZDepth);
gl_FragColor=vec4(vec3(color),1.0);`;

const SIM_VERTEX=`precision highp float;
uniform mat4 projectionMatrix;uniform mat4 modelViewMatrix;
attribute vec2 position;attribute vec2 uv;varying vec2 vUv;
void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,0.,1.);}`;
const SIM_POS=`precision highp float;
uniform sampler2D t_position;uniform sampler2D t_oPos;uniform sampler2D t_velocity;uniform sampler2D t_params;uniform sampler2D t_params2;
uniform float u_factor;uniform float u_length;uniform float u_show;uniform float u_explode;uniform float u_progress;uniform float u_rendered;
varying vec2 vUv;
${QINTIC_IN_OUT}
void main(){
  vec4 tOpos=texture2D(t_oPos,vUv);vec3 pos=tOpos.xyz;
  if(u_rendered>0.){vec4 tVelocity=texture2D(t_velocity,vUv);pos+=tVelocity.xyz;}
  else{
    vec4 tParams=texture2D(t_params,vUv);vec4 tParams2=texture2D(t_params2,vUv);
    float d=0.00005;float d2=0.0005;
    float showVal=min(1.,max(0.,(u_show*(1.+d*(u_length-1.)))-d*tParams.z));showVal=qinticInOut(showVal);
    float radius=mix(u_factor+tParams.x*3.,u_factor,showVal)+min(1.,max(0.,(u_explode*(u_length-1.))-d2*tParams2.w));
    pos=texture2D(t_position,vec2(vUv.x*0.5,vUv.y*0.5)).xyz;
    pos=(pos-0.5)*2.*radius;
    vec3 pos2=(texture2D(t_position,vec2(vUv.x*0.5+0.5,vUv.y*0.5)).xyz-0.5)*2.*radius;
    vec3 pos3=(texture2D(t_position,vec2(vUv.x*0.5,vUv.y*0.5+0.5)).xyz-0.5)*2.*radius;
    vec3 pos4=(texture2D(t_position,vec2(vUv.x*0.5+0.5,vUv.y*0.5+0.5)).xyz-0.5)*2.*radius;
    pos=mix(pos,pos2,u_progress);pos=mix(pos,pos3,max(0.,u_progress-1.));pos=mix(pos,pos4,max(0.,u_progress-2.));
  }
  gl_FragColor=vec4(pos,1.);
}`;
const SIM_SPRING=`precision highp float;
uniform sampler2D t_oTarget;uniform sampler2D t_position;uniform sampler2D t_oVelocity;uniform sampler2D t_params;uniform sampler2D t_params2;uniform sampler2D t_params3;
uniform vec2 u_resolution;uniform float u_factor;uniform float u_length;uniform float u_show;uniform float u_explode;uniform float u_progress;uniform float u_spring;uniform float u_friction;uniform float u_rendered;uniform float u_delay;
varying vec2 vUv;
${QINTIC_IN_OUT}
void main(){
  float vx=0.,vy=0.,vz=0.;
  if(u_rendered>0.){
    vec4 tParams=texture2D(t_params,vUv);vec4 tParams2=texture2D(t_params2,vUv);vec4 tParams3=texture2D(t_params3,vUv);
    float d=0.00005;float d3=0.00015;
    float showVal=min(1.,max(0.,(u_show*(1.+d*(u_length-1.)))-d*tParams.z));showVal=qinticInOut(showVal);
    float radius=mix(u_factor+tParams.x*3.,u_factor,showVal);
    vec3 pos=(texture2D(t_position,vec2(vUv.x*0.5,vUv.y*0.5)).xyz-0.5)*2.*radius;
    vec3 pos2=(texture2D(t_position,vec2(vUv.x*0.5+0.5,vUv.y*0.5)).xyz-0.5)*2.*radius;
    vec3 pos3=(texture2D(t_position,vec2(vUv.x*0.5,vUv.y*0.5+0.5)).xyz-0.5)*2.*radius;
    vec3 pos4=(texture2D(t_position,vec2(vUv.x*0.5+0.5,vUv.y*0.5+0.5)).xyz-0.5)*2.*radius;
    float d2=0.0005;float d4=u_delay;
    pos=mix(pos,pos2,min(1.,max(0.,((u_progress*(1.+d4*(u_length-1.)))-d4*tParams2.x))));
    pos=mix(pos,pos3,min(1.,max(0.,((max(0.,u_progress-1.)*(1.+d2*(u_length-1.)))-d2*tParams2.y))));
    pos=mix(pos,pos4,min(1.,max(0.,((max(0.,u_progress-2.)*(1.+d4*(u_length-1.)))-d4*tParams2.z))));
    pos=mix(pos,pos*tParams3.x,min(1.,max(0.,u_explode*(1.+d3*(u_length-1.))-d3*tParams2.w)));
    vec4 tOtarget=texture2D(t_oTarget,vUv);vec4 tOVelocity=texture2D(t_oVelocity,vUv);
    float spring=u_spring+tParams.w;float friction=u_friction;
    vx=(tOVelocity.x+(pos.x-tOtarget.x)*spring)*friction;
    vy=(tOVelocity.y+(pos.y-tOtarget.y)*spring)*friction;
    vz=(tOVelocity.z+(pos.z-tOtarget.z)*spring)*friction;
  }
  gl_FragColor=vec4(vec3(vx,vy,vz),1.);
}`;
const COPY_FRAGMENT=`precision highp float;uniform sampler2D t_diffuse;varying vec2 vUv;void main(){gl_FragColor=texture2D(t_diffuse,vUv);}`;
const GRAIN_FRAGMENT=`precision highp float;
uniform float u_show;uniform float u_bright;uniform float u_alpha;uniform float u_scale;uniform float u_time;
varying vec2 vUv;
float random(vec2 st){return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);}
float pattern(){float s=sin(0.5),c=cos(0.5);vec2 center=vec2(random(vUv+u_time));vec2 tex=vUv*vec2(4096,4096)*u_scale-center;vec2 point=vec2(c*tex.x-s*tex.y,s*tex.x+c*tex.y);return(sin(point.x)*sin(point.y))*4.0;}
void main(){
  float dist=distance(vec2(0.5),vUv)*2.;
  vec4 black=vec4(0.,0.,0.,1.);
  float pat=pattern();
  vec4 grain=vec4(pat,pat,pat,u_alpha);grain.rgb*=u_bright;
  gl_FragColor=mix(grain,black,smoothstep(u_show-0.1,u_show+0.1,dist));
}`;
const FRONT_VERTEX=`mat4 rotMat=rotationMatrix(vec3(a_angle.x,a_angle.y,a_angle.z),mod(a_angle.w*u_time*0.15,3.1416*2.));
vec4 mvPosition=vec4(transformed,1.0);
v_color=a_color;
#ifdef USE_INSTANCING
  mat4 instanceMat=instanceMatrix;
  float zFactor=map(instanceMat[3][2],0.,9.,0.5,0.2);
  instanceMat[3][0]=instanceMat[3][0]*u_resolution.x*zFactor-u_mouse.x*a_param.x+sin(u_time*a_param.w*0.5)*a_param.y*0.15;
  instanceMat[3][1]=instanceMat[3][1]*u_resolution.y*zFactor-u_mouse.y*a_param.x+cos(u_time*a_param.w*0.5)*a_param.z*0.15;
  instanceMat[0][0]=instanceMat[0][0]*u_scale;
  instanceMat[1][1]=instanceMat[1][1]*u_scale;
  instanceMat[2][2]=instanceMat[2][2]*u_scale;
  instanceMat*=rotMat;
  mvPosition=instanceMat*mvPosition;
#endif
mvPosition=modelViewMatrix*mvPosition;
gl_Position=projectionMatrix*mvPosition;`;
const BOKEH={defines:{RINGS:4,SAMPLES:6},vertexShader:`varying vec2 vUv;void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`,fragmentShader:`#include <common>
varying vec2 vUv;
uniform sampler2D tColor;uniform sampler2D tDepth;uniform float textureWidth;uniform float textureHeight;
uniform float focalDepth;uniform float focalLength;uniform float fstop;uniform float znear;uniform float zfar;uniform float maxblur;uniform float threshold;uniform float gain;uniform float bias;uniform float fringe;uniform float dithering;
const int samples=SAMPLES;const int rings=RINGS;const int maxringsamples=rings*samples;
float CoC=0.03;
vec3 color(vec2 coords,float blur){vec3 col=vec3(0.0);vec2 texel=vec2(1.0/textureWidth,1.0/textureHeight);
  col.r=texture2D(tColor,coords+vec2(0.0,1.0)*texel*fringe*blur).r;col.g=texture2D(tColor,coords+vec2(-0.866,-0.5)*texel*fringe*blur).g;col.b=texture2D(tColor,coords+vec2(0.866,-0.5)*texel*fringe*blur).b;
  vec3 lumcoeff=vec3(0.299,0.587,0.114);float lum=dot(col.rgb,lumcoeff);float thresh=max((lum-threshold)*gain,0.0);return col+mix(vec3(0.0),col,thresh*blur);}
float linearize(float depth){return -zfar*znear/(depth*(zfar-znear)-zfar);}
float gather(float i,float j,int ringsamples,inout vec3 col,float w,float h,float blur){float rings2=float(rings);float step=PI*2.0/float(ringsamples);float pw=cos(j*step)*i;float ph=sin(j*step)*i;col+=color(vUv.xy+vec2(pw*w,ph*h),blur)*mix(1.0,i/rings2,bias);return mix(1.0,i/rings2,bias);}
void main(){
  float depth=linearize(texture2D(tDepth,vUv.xy).x);
  float f=focalLength;float d=focalDepth*1000.0;float o=depth*1000.0;
  float a=(o*f)/(o-f);float b=(d*f)/(d-f);float c=(d-f)/(d*fstop*CoC);
  float blur=clamp(abs(a-b)*c,0.0,1.0);
  vec2 noise=vec2(rand(vUv.xy),rand(vUv.xy+vec2(0.4,0.6)))*dithering*blur;
  float w=(1.0/textureWidth)*blur*maxblur+noise.x;float h=(1.0/textureHeight)*blur*maxblur+noise.y;
  vec3 col=texture2D(tColor,vUv.xy).rgb;
  if(blur>=0.05){float s=1.0;int ringsamples;
    for(int i=1;i<=rings;i++){ringsamples=i*samples;for(int j=0;j<maxringsamples;j++){if(j>=ringsamples)break;s+=gather(float(i),float(j),ringsamples,col,w,h,blur);}}
    col/=s;}
  gl_FragColor.rgb=col;gl_FragColor.a=1.0;
}`};

// a big triangle with its own orthographic camera, used for simulation passes
function fullscreen(material){
  const g=new THREE.BufferGeometry();
  g.setAttribute('position',new THREE.BufferAttribute(new Float32Array([-.5,-.5,1.5,-.5,-.5,1.5]),2));
  g.setAttribute('uv',new THREE.BufferAttribute(new Float32Array([0,0,2,0,0,2]),2));
  const mesh=new THREE.Mesh(g,material),scene=new THREE.Scene(),camera=new THREE.OrthographicCamera(-.5,.5,.5,-.5,0,1);
  mesh.frustumCulled=false;scene.add(mesh);return{mesh,scene,camera};
}
function pingPong(w,h,type){const o={wrapS:THREE.ClampToEdgeWrapping,wrapT:THREE.ClampToEdgeWrapping,minFilter:THREE.NearestFilter,magFilter:THREE.NearestFilter,format:THREE.RGBAFormat,type,depthBuffer:false,stencilBuffer:false,generateMipmaps:false};
  const p={read:new THREE.WebGLRenderTarget(w,h,o),write:new THREE.WebGLRenderTarget(w,h,o)};p.swap=()=>{const r=p.read;p.read=p.write;p.write=r};return p}
function inject(shader,uniforms){for(const u of uniforms){shader.uniforms[u.id]=u;shader.vertexShader=`uniform ${u.type} ${u.id};n`+shader.vertexShader;shader.fragmentShader=`uniform ${u.type} ${u.id};n`+shader.fragmentShader}}

class BokehPass extends Pass{
  constructor(scene,camera,o){
    super();this.scene=scene;this.camera=camera;
    this.renderTargetDepth=new THREE.WebGLRenderTarget(o.width,o.height,{minFilter:THREE.NearestFilter,magFilter:THREE.NearestFilter});
    this.materialDepth=new THREE.MeshDepthMaterial({depthPacking:THREE.RGBADepthPacking,blending:THREE.NoBlending});
    const u={tColor:{value:null},tDepth:{value:this.renderTargetDepth.texture},textureWidth:{value:o.width},textureHeight:{value:o.height},focalDepth:{value:o.focalDepth},focalLength:{value:o.focalLength},fstop:{value:o.fstop},maxblur:{value:o.maxblur},threshold:{value:.5},gain:{value:o.gain},bias:{value:o.bias},fringe:{value:.7},znear:{value:camera.near},zfar:{value:camera.far},dithering:{value:1e-4}};
    this.uniforms=u;this.materialBokeh=new THREE.ShaderMaterial({defines:BOKEH.defines,uniforms:u,vertexShader:BOKEH.vertexShader,fragmentShader:BOKEH.fragmentShader});
    this.needsSwap=true;this.fsQuad=new FullScreenQuad(this.materialBokeh);this._old=new THREE.Color();
  }
  setSize(w,h){this.uniforms.textureWidth.value=w;this.uniforms.textureHeight.value=h;this.renderTargetDepth.setSize(w,h)}
  render(renderer,writeBuffer,readBuffer){
    this.scene.traverse(e=>{if(e.isMesh){e._material=e.material;e.material=e.customDepthMaterial!==undefined?e.customDepthMaterial:this.materialDepth}});
    renderer.getClearColor(this._old);const a=renderer.getClearAlpha(),ac=renderer.autoClear;
    renderer.autoClear=false;renderer.setClearColor(0);renderer.setClearAlpha(1);renderer.setRenderTarget(this.renderTargetDepth);renderer.clear();renderer.render(this.scene,this.camera);
    this.uniforms.tColor.value=readBuffer.texture;this.uniforms.znear.value=this.camera.near;this.uniforms.zfar.value=this.camera.far;
    if(this.renderToScreen){renderer.setRenderTarget(null);this.fsQuad.render(renderer)}else{renderer.setRenderTarget(writeBuffer);renderer.clear();this.fsQuad.render(renderer)}
    this.scene.traverse(e=>{if(e.isMesh){e.material=e._material;e._material=null}});
    renderer.setClearColor(this._old);renderer.setClearAlpha(a);renderer.autoClear=ac;
  }
}
// depth material for the bokeh pass, sharing the vertex transform of the visible material
function depthMaterial(uniforms,patch){
  let vs=`varying vec2 vUv;nvarying float vViewZDepth;nvoid main(){n  #include <begin_vertex>n  #include <project_vertex>n  vViewZDepth=-mvPosition.z;n  vUv=uv;n}`,fs=`varying float vViewZDepth;nvarying vec2 vUv;nvoid main(){n${DEPTH_FRAGMENT}n}`;
  uniforms.u_near={type:'float',value:.1};uniforms.u_far={type:'float',value:13};
  const all={};for(const k in uniforms){all[k]=uniforms[k];vs=`uniform ${uniforms[k].type} ${k};n`+vs;fs=`uniform ${uniforms[k].type} ${k};n`+fs}
  const m=new THREE.ShaderMaterial({transparent:true,uniforms:all,vertexShader:vs,fragmentShader:fs});m.onBeforeCompile=patch;return m;
}

// the floating pyramids in front of everything
class FrontCones extends THREE.Object3D{
  constructor(){super();this._mouseTg={x:0,y:0};this._easing=.1;this._nb=250}
  build(geometry,camera){
    this._geometry=geometry.clone();
    const U=[{id:'u_time',type:'float',value:0},{id:'u_scale',type:'float',value:1},{id:'u_resolution',type:'vec2',value:new THREE.Vector2()},{id:'u_mouse',type:'vec2',value:new THREE.Vector2()}];
    this._uniforms=U;
    const patchVertex=s=>{s.vertexShader='varying vec4 v_color;nattribute vec4 a_color;nattribute vec4 a_angle;nattribute vec4 a_param;n'+MAP+ROTATION+s.vertexShader;s.vertexShader=s.vertexShader.replace('#include <project_vertex>',FRONT_VERTEX).replace('#include <worldpos_vertex>',WORLDPOS)};
    this._material=new THREE.MeshBasicMaterial({color:new THREE.Color('white'),vertexColors:true,transparent:true});
    this._material.onBeforeCompile=s=>{inject(s,U);patchVertex(s);s.fragmentShader='varying vec4 v_color;n'+s.fragmentShader;
      s.fragmentShader=s.fragmentShader.replace('#include <output_fragment>','vec3 col=v_color.rgb;n#ifdef OPAQUEndiffuseColor.a=1.0;n#endifn#ifdef USE_TRANSMISSIONndiffuseColor.a*=transmissionAlpha+0.1;n#endifnoutgoingLight.rgb=col.rgb;ndiffuseColor.a*=v_color.a;ngl_FragColor=vec4(outgoingLight,diffuseColor.a);')};
    this._mesh=new THREE.InstancedMesh(this._geometry,this._material,this._nb);this._mesh.frustumCulled=false;
    const mat=new THREE.Matrix4(),p=new THREE.Vector3(),q=new THREE.Quaternion(),sc=new THREE.Vector3();
    const col=new Float32Array(4*this._nb),ang=new Float32Array(4*this._nb),par=new Float32Array(4*this._nb),pal=[{r:93,g:57,b:154},{r:186,g:136,b:43},{r:40,g:116,b:100},{r:164,g:148,b:175}];
    for(let i=0;i<this._nb;i++){
      const f=isMobile?.075:.05;p.set(2*Math.random()-1,2*Math.random()-1,9*Math.random());sc.set(f,f,f);mat.compose(p,q,sc);this._mesh.setMatrixAt(i,mat);
      col.set([pal[i%4].r/255,pal[i%4].g/255,pal[i%4].b/255,Math.random()],4*i);
      ang.set([2*Math.random()-1,2*Math.random()-1,2*Math.random()-1,2*Math.random()-1*Math.PI],4*i);
      par.set([Math.random(),Math.random(),Math.random(),Math.random()],4*i);
    }
    this._geometry.setAttribute('a_param',new THREE.InstancedBufferAttribute(par,4));
    this._geometry.setAttribute('a_color',new THREE.InstancedBufferAttribute(col,4));
    this._geometry.setAttribute('a_angle',new THREE.InstancedBufferAttribute(ang,4));
    const DU={u_time:{type:'float',value:0},u_scale:{type:'float',value:1},u_resolution:{type:'vec2',value:new THREE.Vector2()},u_mouse:{type:'vec2',value:new THREE.Vector2()}};
    this._depthUniforms=DU;
    this._mesh.customDepthMaterial=depthMaterial(DU,s=>{s.vertexShader='varying vec4 v_color;nattribute vec4 a_color;nattribute vec4 a_angle;nattribute vec4 a_param;n'+MAP+ROTATION+s.vertexShader;s.vertexShader=s.vertexShader.replace('#include <project_vertex>',FRONT_VERTEX)});
    this.add(this._mesh);this._loaded=true;this.resize(camera);
  }
  onMousemove(m){this._mouseTg.x=m.x;this._mouseTg.y=m.y}
  resize(camera){if(!this._loaded)return;const s=visibleAt(camera,9.9);this._uniforms[2].value.set(s.width,s.height);this._depthUniforms.u_resolution.value.set(s.width,s.height)}
  update(t,progress){
    if(!this._loaded)return;this._uniforms[0].value=t;this._depthUniforms.u_time.value=t;
    for(const m of [this._uniforms[3].value,this._depthUniforms.u_mouse.value]){m.x+=(.25*this._mouseTg.x-m.x)*this._easing;m.y+=(.25*this._mouseTg.y-m.y)*this._easing}
    if(isMobile){const tx=clamp(map(progress,0,1,2,-5),-5,1)+clamp(map(progress,1.25,1.5,0,5),0,5)-clamp(map(progress,2.7,3,0,5),0,5)+clamp(map(progress,3.3,3.5,0,5),0,9)-clamp(map(progress,4.5,5,0,5),0,4);this.position.x+=(.25*tx-this.position.x)*this._easing}
  }
}

// the brain: 10,000 cones whose positions are simulated on the GPU
class Cones extends THREE.Object3D{
  constructor(){
    super();this._nb=isMobile?7e3:1e4;this._loaded=false;this.active=false;this._hasRendered=false;this._easing=.1;
    this._factor=isMobile?2.5:4.35;this._scale=isMobile?1.2:1.55;this._mTg={x:0,y:0};this._mPr={x:0,y:0};this._mDelta={x:0,y:0};
    this._params={baseRotation:new THREE.Vector3(0,-.25*Math.PI,0),basePosition:new THREE.Vector3()};this.position.set(0,-1.19,0);
  }
  build(renderer,posTexture,scaleTexture,colorTexture,geometry,posRT,camera){
    this.renderer=renderer;
    // order cones by height and side so the build-up and explosion ripple across the brain
    const read=(x,y)=>{const b=new Uint8Array(4e4);renderer.readRenderTargetPixels(posRT,x,y,100,100,b);return b};
    const e=read(0,0),t=read(100,0),n=read(0,100),r=read(100,100),o=read(0,0);
    const rank=(buf,k,dir)=>{const a=[];for(let i=0,j=0;i<buf.length;i+=4,j++)a.push({v:buf[i+k],index:j,order:0});a.sort((x,y)=>dir*(x.v-y.v));a.forEach((x,i)=>x.order=i);a.sort((x,y)=>x.index-y.index);return a};
    const s=rank(e,1,-1),a=rank(t,0,1),l=rank(n,0,-1),c=rank(r,1,-1),h=rank(o,1,1);
    const tex=()=>{const d=new THREE.DataTexture(new Float32Array(4e4),100,100,THREE.RGBAFormat,THREE.FloatType,THREE.UVMapping,THREE.ClampToEdgeWrapping,THREE.ClampToEdgeWrapping,THREE.NearestFilter,THREE.NearestFilter,0);d.generateMipmaps=false;return d};
    this._tParams=tex();this._tParams2=tex();this._tParams3=tex();
    for(let i=0;i<this._nb;i++){
      this._tParams.image.data.set([i%2==0?Math.random():-1*Math.random(),Math.floor(i/100)/200+.0025,s[i].order,2e-4*Math.random()-1e-4],4*i);
      this._tParams2.image.data.set([a[i].order,l[i].order,c[i].order,h[i].order],4*i);
      this._tParams3.image.data.set([1+5*Math.random(),2*Math.random()-1,map(e[4*i],0,256,-1,1),0],4*i);
    }
    this._tParams.needsUpdate=this._tParams2.needsUpdate=this._tParams3.needsUpdate=true;
    const ftype=isMobile?THREE.HalfFloatType:THREE.FloatType;
    this._fboPos=pingPong(200,200,ftype);this._fboSpring=pingPong(200,200,ftype);
    const common={t_params:{value:this._tParams},t_params2:{value:this._tParams2},u_show:{value:0},u_length:{value:this._nb},u_explode:{value:0},u_factor:{value:this._factor},u_progress:{value:0},u_rendered:{value:0}};
    this._simPos=fullscreen(new THREE.RawShaderMaterial({uniforms:{...THREE.UniformsUtils.clone(common),t_params:{value:this._tParams},t_params2:{value:this._tParams2},t_position:{value:posTexture},t_velocity:{value:null},t_pos:{value:null},t_oPos:{value:null}},vertexShader:SIM_VERTEX,fragmentShader:SIM_POS}));
    this._simSpring=fullscreen(new THREE.RawShaderMaterial({uniforms:{...THREE.UniformsUtils.clone(common),t_params:{value:this._tParams},t_params2:{value:this._tParams2},t_params3:{value:this._tParams3},t_position:{value:posTexture},t_oTarget:{value:null},t_velocity:{value:null},t_oVelocity:{value:null},u_spring:{value:.006},u_friction:{value:.892},u_resolution:{value:new THREE.Vector2()},u_delay:{value:isMobile?25e-6:5e-4}},vertexShader:SIM_VERTEX,fragmentShader:SIM_SPRING}));
    this._geometry=geometry.clone();
    const U=[{id:'t_position',type:'sampler2D',value:posTexture},{id:'t_scale',type:'sampler2D',value:scaleTexture},{id:'t_color',type:'sampler2D',value:colorTexture},{id:'u_factor',type:'float',value:this._factor},{id:'u_time',type:'float',value:0},{id:'u_scale',type:'float',value:this._scale},{id:'u_amplitude',type:'float',value:.619},{id:'u_colorFactor',type:'float',value:1.3},{id:'u_show',type:'float',value:0},{id:'u_length',type:'float',value:this._nb},{id:'u_mouse',type:'vec2',value:new THREE.Vector2()},{id:'u_resolution',type:'vec2',value:new THREE.Vector2(innerWidth,innerHeight)},{id:'u_offset',type:'vec3',value:new THREE.Vector3()},{id:'u_explode',type:'float',value:0},{id:'u_progress',type:'float',value:0},{id:'u_rotation',type:'vec3',value:new THREE.Vector3()},{id:'u_amplitude2',type:'float',value:.5},{id:'t_simulation',type:'sampler2D',value:null},{id:'t_fluid',type:'sampler2D',value:null},{id:'u_resolution2',type:'vec2',value:new THREE.Vector2()},{id:'u_progress2',type:'float',value:0},{id:'u_delta',type:'vec2',value:new THREE.Vector2()},{id:'u_delay',type:'float',value:isMobile?25e-6:5e-4},{id:'u_mobileRotation',type:'float',value:isMobile?Math.PI:0}];
    this._u=U;
    const head='attribute float a_index;nattribute float a_order;nvarying float v_hover;nvarying vec3 v_pos;nvarying vec2 v_id;nattribute vec2 a_id;nattribute vec4 a_angle;nattribute vec4 a_random;n'+QINTIC_OUT+ROTATION+QINTIC_IN_OUT+SNOISE;
    this._material=new THREE.MeshBasicMaterial({transparent:true});
    this._material.onBeforeCompile=sh=>{inject(sh,U);sh.vertexShader=head+sh.vertexShader;
      sh.vertexShader=sh.vertexShader.replace('#include <project_vertex>',CONE_VERTEX).replace('#include <worldpos_vertex>',WORLDPOS);
      sh.fragmentShader='varying float v_hover;nvarying vec2 v_id;nvarying vec3 v_pos;n'+sh.fragmentShader;
      sh.fragmentShader=sh.fragmentShader.replace('#include <output_fragment>',CONE_FRAGMENT)};
    for(const t of [posTexture]){t.minFilter=t.magFilter=THREE.NearestFilter;t.flipY=true;t.needsUpdate=true}
    for(const t of [scaleTexture,colorTexture]){t.minFilter=THREE.NearestFilter;t.flipY=false;t.needsUpdate=true}
    this._mesh=new THREE.InstancedMesh(this._geometry,this._material,this._nb);this._mesh.frustumCulled=false;
    const mat=new THREE.Matrix4(),p=new THREE.Vector3(),q=new THREE.Quaternion(),sc=new THREE.Vector3(.1,.1,.1);
    const rnd=new Float32Array(4*this._nb),ang=new Float32Array(4*this._nb),id=new Float32Array(2*this._nb),idx=new Float32Array(this._nb),ord=new Float32Array(this._nb);
    for(let i=0;i<this._nb;i++){mat.compose(p,q,sc);this._mesh.setMatrixAt(i,mat);
      id[2*i]=i%100/200+.0025;id[2*i+1]=Math.floor(i/100)/200+.0025;ang.set([Math.random(),Math.random(),Math.random(),Math.random()],4*i);idx[i]=i;
      rnd.set([i%2==0?Math.random():-1*Math.random(),.8*Math.random()+.2,isMobile?.5*Math.random():.5*Math.random()+.5,0],4*i)}
    const G=this._geometry;G.setAttribute('a_index',new THREE.InstancedBufferAttribute(idx,1));G.setAttribute('a_order',new THREE.InstancedBufferAttribute(ord,1));
    G.setAttribute('a_id',new THREE.InstancedBufferAttribute(id,2));G.setAttribute('a_angle',new THREE.InstancedBufferAttribute(ang,4));G.setAttribute('a_random',new THREE.InstancedBufferAttribute(rnd,4));
    const DU={};for(const u of U)DU[u.id]={type:u.type,value:u.value};this._du=DU;
    this._mesh.customDepthMaterial=depthMaterial(DU,sh=>{sh.vertexShader=head+sh.vertexShader;sh.vertexShader=sh.vertexShader.replace('#include <project_vertex>',CONE_VERTEX)});
    this.add(this._mesh);this._loaded=true;this.resize(camera);
  }
  uni(id){return this._u.find(u=>u.id===id).value}
  set(id,v){const u=this._u.find(u=>u.id===id);u.value=v;this._du[id].value=v}
  ease(id,target,k){const u=this._u.find(u=>u.id===id);const v=u.value+(target-u.value)*k;u.value=v;this._du[id].value=v}
  activate(){
    if(this.active)return;this.active=true;const start=performance.now(),dur=3000,from=this._params.baseRotation.y;
    const step=()=>{const k=Math.min(1,(performance.now()-start)/dur),e=1-(1-k)*(1-k);
      this._simPos.mesh.material.uniforms.u_show.value=k;this._simSpring.mesh.material.uniforms.u_show.value=k;this.set('u_show',k);
      this._params.baseRotation.y=from*(1-e);if(k<1)requestAnimationFrame(step)};step();
  }
  onMousemove(m){this._mTg={x:m.x,y:m.y};const lim=isMobile?.1:2;this._mDelta.x=clamp(50*(this._mTg.x-this._mPr.x),-lim,lim);this._mDelta.y=clamp(50*(this._mTg.y-this._mPr.y),-lim,lim);this._mPr.x=this._mTg.x;this._mPr.y=this._mTg.y}
  onMouseleave(){this._mDelta.x=0;this._mDelta.y=0}
  resize(camera){if(!this._loaded)return;const a=visibleAt(camera,10),b=visibleAt(camera,6.5);
    this.uni('u_resolution').set(a.width,a.height);this._simSpring.mesh.material.uniforms.u_resolution.value.set(a.width,a.height);this.uni('u_resolution2').set(b.width,b.height)}
  update(t,e){
    if(!this._loaded)return;this.set('u_time',t);
    // rotation
    const ry=clamp(map(e,0,1,0,.5*-Math.PI),.5*-Math.PI,0)+clamp(map(e,2.7,3,0,.5*Math.PI),0,.5*Math.PI)+clamp(map(e,3.3,3.5,0,.25*Math.PI),0,.25*Math.PI)-clamp(map(e,4.5,5,0,1.25*Math.PI),0,1.25*Math.PI)+clamp(map(e,5.7,isMobile?5.8:6,0,Math.PI),0,Math.PI);
    const rz=clamp(map(e,2.7,3,0,-.489),-.489,0)+clamp(map(e,3.3,3.5,0,.6),0,.6),R=this.uni('u_rotation'),k=.75*this._easing;
    R.x+=(0-R.x)*k;R.y+=(ry-R.y)*k;R.z+=(rz-R.z)*k;this.rotation.y=this._params.baseRotation.y;
    // mouse
    this._mDelta.x+=(0-this._mDelta.x)*this._easing;this._mDelta.y+=(0-this._mDelta.y)*this._easing;
    const M=this.uni('u_mouse'),D=this.uni('u_delta');M.x+=(this._mTg.x-M.x)*k;M.y+=(this._mTg.y-M.y)*k;D.x+=(this._mDelta.x-D.x)*k;D.y+=(this._mDelta.y-D.y)*k;
    // position, explode, factor and progress follow the scroll
    let tx,ty,ex,fa,pr,p2;
    if(isMobile){
      tx=clamp(map(e,0,1,1.5,0),0,1.5)+.052*M.x;ty=2;
      ex=clamp(map(e,1.4,1.7,0,1),0,1)-clamp(map(e,2.7,3,0,1),0,1)+clamp(map(e,4.5,5,0,1),0,1)-clamp(map(e,5.7,5.8,0,1),0,1);
      pr=clamp(map(e,2.7,3,0,1),0,1)+clamp(map(e,3.3,3.5,0,1),0,1)+clamp(map(e,5.7,5.8,0,1),0,1);fa=null;p2=null;
    }else{
      tx=clamp(map(e,0,1,3,-4.5),-4.5,3)+clamp(map(e,1.25,1.5,.905,5),.905,5)-clamp(map(e,2.8,3,.905,3),.905,3)+clamp(map(e,3.3,3.5,.905,6),.905,6)-clamp(map(e,4.5,5,.905,5),.905,4);
      ty=clamp(map(e,2.7,3,0,.5),0,.5)-clamp(map(e,3.3,3.5,0,.5),0,.5)+clamp(map(e,5.7,6,0,1.75),0,1.75);
      ex=clamp(map(e,1.1,2.2,0,1),0,1)-clamp(map(e,2.8,3,0,1),0,1)+clamp(map(e,4.5,5,0,1),0,1)-clamp(map(e,5.7,6,0,1),0,1);
      fa=this._factor+clamp(map(e,0,1,0,1),0,1)-clamp(map(e,1.25,1.5,0,1),0,1)+clamp(map(e,3.3,3.5,0,.3),0,.3)-clamp(map(e,5.7,6,0,1),0,1);
      pr=clamp(map(e,2.7,3,0,1),0,1)+clamp(map(e,3.3,3.5,0,1),0,1)+clamp(map(e,5.7,6,0,1),0,1);
      p2=clamp(map(e,0,1,0,1),0,1)-clamp(map(e,2.7,3,0,1),0,1)+clamp(map(e,3,3.5,0,1),0,1)-clamp(map(e,5.5,5.5,0,1),0,1);
    }
    this._params.basePosition.set(tx,ty,0);
    const sp=this._simPos.mesh.material.uniforms,ss=this._simSpring.mesh.material.uniforms,E=this._easing;
    this.ease('u_explode',ex,E);sp.u_explode.value+=(ex-sp.u_explode.value)*E;ss.u_explode.value+=(ex-ss.u_explode.value)*E;
    if(fa!==null){this.ease('u_factor',fa,E);sp.u_factor.value+=(fa-sp.u_factor.value)*E;ss.u_factor.value+=(fa-sp.u_factor.value)*E}
    this.ease('u_progress',pr,E);sp.u_progress.value+=(pr-sp.u_progress.value)*E;ss.u_progress.value+=(pr-ss.u_progress.value)*E;
    if(p2!==null)this.ease('u_progress2',p2,E);
    const O=this.uni('u_offset');O.x+=(tx-O.x)*E;O.y+=(ty-O.y)*E;
    this._updateFBO();
  }
  _updateFBO(){
    const r=this.renderer,sp=this._simPos.mesh.material.uniforms,ss=this._simSpring.mesh.material.uniforms;
    if(this._hasRendered){
      ss.t_oTarget.value=this._fboPos.read.texture;r.setRenderTarget(this._fboSpring.write);r.render(this._simSpring.scene,this._simSpring.camera);this._fboSpring.swap();
      ss.t_oVelocity.value=this._fboSpring.read.texture;ss.t_velocity.value=this._fboSpring.write.texture;sp.t_velocity.value=this._fboSpring.write.texture;
      r.setRenderTarget(this._fboPos.write);r.render(this._simPos.scene,this._simPos.camera);this._fboPos.swap();
      sp.t_oPos.value=this._fboPos.read.texture;sp.t_pos.value=this._fboPos.write.texture;this.set('t_simulation',this._fboPos.write.texture);
    }else{
      r.setRenderTarget(this._fboPos.write);r.render(this._simPos.scene,this._simPos.camera);this._fboPos.swap();
      sp.t_oPos.value=this._fboPos.read.texture;sp.t_pos.value=this._fboPos.write.texture;this.set('t_simulation',this._fboPos.write.texture);
      r.setRenderTarget(this._fboSpring.write);r.render(this._simSpring.scene,this._simSpring.camera);this._fboSpring.swap();
      ss.t_oVelocity.value=this._fboSpring.read.texture;ss.t_velocity.value=this._fboSpring.write.texture;
      sp.u_rendered.value=1;ss.u_rendered.value=1;this._hasRendered=true;
    }
    r.setRenderTarget(null);
  }
}

// the dotted grain layer just in front of the camera
class Grain extends THREE.Object3D{
  constructor(){super();const dpr2=devicePixelRatio>=2;
    this.u={u_show:{value:1},u_alpha:{value:dpr2?.138:.149},u_bright:{value:dpr2?.185:.252},u_scale:{value:dpr2?1.072:1.366},u_time:{value:0}};
    const f=fullscreen(new THREE.RawShaderMaterial({uniforms:this.u,transparent:true,vertexShader:SIM_VERTEX,fragmentShader:GRAIN_FRAGMENT}));
    this.mesh=f.mesh;f.scene.remove(this.mesh);this.mesh.customDepthMaterial=new THREE.MeshBasicMaterial({opacity:0,transparent:true});this.add(this.mesh)}
  update(t){this.u.u_time.value=t}
}

async function start(canvas,getProgress){
  const renderer=new THREE.WebGLRenderer({alpha:false,antialias:false,canvas,powerPreference:'high-performance',stencil:false});
  renderer.setPixelRatio(isMobile?2:1);renderer.setSize(innerWidth,innerHeight);
  const camera=new THREE.PerspectiveCamera(50,innerWidth/innerHeight,.1,30);camera.position.set(0,0,10);camera.setFocalLength(35);camera.fov=50;camera.updateProjectionMatrix();
  const scene=new THREE.Scene();
  const [pos,sc,co,gltf]=await Promise.all([
    new EXRLoader().loadAsync(ASSETS.pos),
    new THREE.TextureLoader().loadAsync(ASSETS.scale),
    new THREE.TextureLoader().loadAsync(ASSETS.color),
    new GLTFLoader().loadAsync(ASSETS.pyramid)]);
  let geometry;gltf.scene.traverse(e=>{if(e.type==='Mesh')geometry=e.geometry});
  pos.minFilter=pos.magFilter=THREE.NearestFilter;pos.flipY=true;pos.needsUpdate=true;
  // read the baked positions once into an 8-bit target so cones can be ordered
  const posRT=new THREE.WebGLRenderTarget(200,200,{minFilter:THREE.NearestFilter,magFilter:THREE.NearestFilter,depthBuffer:false});
  const copy=fullscreen(new THREE.RawShaderMaterial({uniforms:{t_diffuse:{value:pos}},vertexShader:SIM_VERTEX,fragmentShader:COPY_FRAGMENT}));
  renderer.setRenderTarget(posRT);renderer.render(copy.scene,copy.camera);renderer.setRenderTarget(null);
  const grain=new Grain();grain.position.set(0,0,9.6);scene.add(grain);
  const cones=new Cones();scene.add(cones);cones.build(renderer,pos,sc,co,geometry,posRT,camera);
  const front=new FrontCones();front.position.set(0,0,.1);scene.add(front);front.build(geometry,camera);
  const composer=new EffectComposer(renderer);
  composer.addPass(new RenderPass(scene,camera));
  const bloom=new UnrealBloomPass(new THREE.Vector2(innerWidth,innerHeight),1.5,.4,.85);bloom.threshold=.159;bloom.strength=.4;bloom.radius=1;composer.addPass(bloom);
  const bokeh=new BokehPass(scene,camera,{focalDepth:.125,focalLength:27,fstop:2509,maxblur:10,gain:0,bias:0,width:innerWidth,height:innerHeight});
  if(!isMobile)composer.addPass(bokeh);
  const vignette=new ShaderPass(VignetteShader);vignette.uniforms.offset.value=.3;vignette.uniforms.darkness.value=4;composer.addPass(vignette);
  renderer.toneMappingExposure=1;
  const m={x:0,y:0};
  addEventListener('pointermove',ev=>{m.x=2*(ev.clientX/innerWidth-.5);m.y=-2*(ev.clientY/innerHeight-.5);cones.onMousemove(m);front.onMousemove(m)});
  addEventListener('pointerdown',ev=>{m.x=2*(ev.clientX/innerWidth-.5);m.y=-2*(ev.clientY/innerHeight-.5);cones._mTg={...m};cones._mPr={...m};cones._mDelta={x:0,y:0}});
  document.addEventListener('pointerleave',()=>cones.onMouseleave());
  addEventListener('resize',()=>{renderer.setSize(innerWidth,innerHeight);camera.fov=50;camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();cones.resize(camera);front.resize(camera);composer.setSize(innerWidth,innerHeight);bokeh.setSize(innerWidth,innerHeight)});
  cones.activate();
  function frame(t){
    camera.rotation.y+=.1*(-.075*m.x-camera.rotation.y);camera.rotation.x+=.1*(.05*m.y-camera.rotation.x);
    const p=getProgress();grain.update(t);cones.update(t,p);front.update(t,p);composer.render();
  }
  return{frame};
}

// scroll progress the way the original measures it: section index plus how far through that section you are
if('scrollRestoration' in history)history.scrollRestoration='manual';scrollTo(0,0);
const sections=[...document.querySelectorAll('main section')];
let progress=0,target=0;
function measure(){const y=scrollY;let p=0;for(let i=0;i<sections.length;i++){const r=sections[i].getBoundingClientRect(),top=r.top+y;if(y>=top-1)p=i+Math.min(1,Math.abs((y-top)/r.height))}target=p;document.querySelector('header').style.setProperty('--bar',Math.min(1,y/200)*.85)}
addEventListener('scroll',measure,{passive:true});addEventListener('resize',measure);measure();progress=target;
const app=await start(document.getElementById('gl'),()=>progress);
// the original drives its shaders from GSAP's ticker, which counts in seconds
function loop(t){progress+=(target-progress)*.1;app.frame(t/1000);requestAnimationFrame(loop)}
requestAnimationFrame(loop);
</script>
</body>
</html>
```
[/hidecontent]
← 返回首页