Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
1 lines (1 loc) · 34.5 KB
/
Copy pathindex.html
File metadata and controls
1 lines (1 loc) · 34.5 KB
1
<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><title>Ocean4 Live</title><style>:root{--bg:#0a1a22;--card:rgba(243,247,248,.95);--ink:#10232c;--ink-soft:#4f646e;--rule:rgba(16,35,44,.14);--signal:#d4461a;--on-ink:#f3f7f8;--sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--card:rgba(8,21,28,.93);--ink:#dde8ec;--ink-soft:#8ca1ab;--rule:rgba(221,232,236,.15);--signal:#ff6a33;--on-ink:#08151c;color-scheme:dark}}:root[data-theme=dark]{--card:rgba(8,21,28,.93);--ink:#dde8ec;--ink-soft:#8ca1ab;--rule:rgba(221,232,236,.15);--signal:#ff6a33;--on-ink:#08151c;color-scheme:dark}body,html{height:100%;margin:0;background:var(--bg);overflow:hidden}body{font:13px/1.4 var(--sans);color:var(--ink)}#c{position:fixed;inset:0;width:100%;height:100%;display:block;touch-action:none;cursor:grab}#c:active{cursor:grabbing}#panel{position:fixed;top:calc(12px + env(safe-area-inset-top,0px));left:max(12px,env(safe-area-inset-left,0px));width:min(292px,calc(100% - 24px));max-height:calc(100% - 24px - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px));overflow:auto;background:var(--card);border:1px solid var(--rule);border-radius:10px;box-shadow:0 8px 28px rgba(0,10,16,.28)}summary{display:flex;align-items:baseline;gap:8px;padding:10px 12px;cursor:pointer;list-style:none;user-select:none}summary::-webkit-details-marker{display:none}summary b{font:700 15px/1 var(--sans);letter-spacing:.01em}summary b i{font-style:normal;color:var(--signal)}#clock{margin-left:auto;font:12px/1 var(--mono);color:var(--ink-soft);font-variant-numeric:tabular-nums}summary::after{content:"";width:7px;height:7px;border:solid var(--ink-soft);border-width:0 1.5px 1.5px 0;transform:translateY(-2px) rotate(45deg);transition:transform .2s}details:not([open]) summary::after{transform:translateY(1px) rotate(-135deg)}.body{display:grid;gap:12px;padding:0 12px 12px}.label{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-soft)}#scenes{display:grid;gap:2px;margin:0;padding:0;border:0}#scenes legend{padding:0 0 4px}.scene{display:grid;gap:1px;text-align:left;padding:7px 9px;border:0;border-radius:6px;background:0 0;color:inherit;font:inherit;cursor:pointer}.scene span{font:11px/1.3 var(--mono);color:var(--ink-soft)}.scene:hover{background:var(--rule)}.scene[aria-pressed=true]{background:var(--ink);color:var(--on-ink)}.scene[aria-pressed=true] span{color:inherit;opacity:.75}.ctl{display:grid;grid-template-columns:1fr auto;gap:2px 8px;align-items:baseline}.ctl output{font:12px var(--mono);font-variant-numeric:tabular-nums}.ctl input{grid-column:1/-1;width:100%;margin:2px 0 0;accent-color:var(--signal)}.ctl input:disabled{opacity:.35}#read{display:grid;grid-template-columns:auto 1fr;gap:3px 10px;margin:0;padding-top:10px;border-top:1px solid var(--rule)}#read dt{color:var(--ink-soft)}#read dd{margin:0;font:12px var(--mono);font-variant-numeric:tabular-nums;text-align:right}.row{display:flex;gap:8px}.row button{flex:1;padding:7px 0;border:1px solid var(--rule);border-radius:6px;background:0 0;color:inherit;font:600 12px var(--sans);cursor:pointer}.row button:hover{border-color:var(--ink-soft)}button:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--signal);outline-offset:2px}#hint{position:fixed;right:max(12px,env(safe-area-inset-right,0px));bottom:calc(10px + env(safe-area-inset-bottom,0px));margin:0;font:11px var(--mono);color:#dfe9ed;opacity:.75;text-shadow:0 1px 3px #000;pointer-events:none}#err{position:fixed;inset:auto 12px 50% 12px;max-width:440px;margin:0 auto;padding:14px 16px;border-radius:8px;background:var(--card);color:var(--ink);font-size:14px}@media (prefers-reduced-motion:reduce){summary::after{transition:none}}</style><canvas id="c" aria-label="Live 3D ocean. Drag to orbit, scroll or pinch to zoom."></canvas><aside id="panel" aria-label="Simulation controls"><details id="card" open><summary><b>ocean<i>4</i> live</b><span id="clock">t 0.0 s</span></summary><div class="body"><fieldset id="scenes"><legend class="label">Scene</legend></fieldset><div class="ctl"><label for="wind">Wind at 10 m</label><output id="windO" for="wind"></output><input type="range" id="wind" min="3" max="25" step="0.5"></div><div class="ctl"><label for="chop">Choppiness λ</label><output id="chopO" for="chop"></output><input type="range" id="chop" min="0" max="2.5" step="0.05"></div><div class="ctl"><label for="sun">Sun elevation</label><output id="sunO" for="sun"></output><input type="range" id="sun" min="3" max="75" step="1" value="24"></div><div class="ctl"><label for="speed">Time</label><output id="speedO" for="speed"></output><input type="range" id="speed" min="0" max="3" step="0.05" value="1"></div><dl id="read"></dl><div class="row"><button id="play" type="button">Pause</button><button id="restart" type="button">Restart</button></div></div></details></aside><p id="hint">drag to orbit · scroll or pinch to zoom</p><p id="err" role="alert" hidden></p><script>"use strict";(()=>{const e=e=>document.getElementById(e),t=e("c"),o=t=>{const o=e("err");throw o.textContent=t,o.hidden=!1,new Error(t)},a=t.getContext("webgl2",{antialias:!1,alpha:!1});a||o("This page needs WebGL2. Open it in a current Chrome, Edge, Firefox or Safari."),a.getExtension("EXT_color_buffer_float")||o("This GPU cannot render to float textures (EXT_color_buffer_float), which the FFT needs.");const n=!!a.getExtension("OES_texture_float_linear"),u=a.getExtension("EXT_texture_filter_anisotropic"),c=n?a.RGBA32F:a.RGBA16F,r=9.80665,i=.00275,s=2*Math.PI,f=e=>e*Math.PI/180,l=[.9999999999998099,676.5203681218851,-1259.1392167224028,771.3234287776531,-176.6150291621406,12.507343278686905,-.13857109526572012,9984369578019572e-21,1.5056327351493116e-7];function m(e){if(e<.5)return Math.log(Math.PI/Math.abs(Math.sin(Math.PI*e)))-m(1-e);let t=l[0];const o=7.5+(e-=1);for(let o=1;o<9;o++)t+=l[o]/(e+o);return.5*Math.log(s)+(e+.5)*Math.log(o)-o+Math.log(t)}const x=(e,t)=>Math.sqrt(((e,t)=>e.depth>0?t*Math.tanh(t*e.depth):t)(e,t)*((e,t)=>r*(e.capillary?1+t*t*i*i:1))(e,t));function v(e,t){let o=x(e,t);if(e.Tloop>0){const t=s/e.Tloop;o=Math.floor(o/t)*t}return o}function h(e,t){const o=Math.cos(.5*t);return o<=0?0:Math.exp((2*e-1)*Math.LN2-Math.log(Math.PI)+2*m(e+1)-m(2*e+1)+2*e*Math.log(o))}function p(e,t,o){const a=t.wp,n=o<=a?.07:.09;return t.alpha*r*r/o**5*Math.exp(-1.25*(a/o)**4)*e.gamma**Math.exp(-(o-a)*(o-a)/(2*n*n*a*a))}function d(e,t,o){const a=e.wp;return h(t<=a?6.97*(t/a)**4.06:9.77*(t/a)**(-2.33-1.45*(e.U/e.cp-1.17)),o)}function w(e){const t=function(e){const t=22*(r*r/(e.V*e.fetch))**(1/3);return{U:e.V,wp:t,cp:r/t,alpha:.076*(e.V*e.V/(e.fetch*r))**.22}}(e),o=[...e.L].sort((e,t)=>t-e),a="none"===e.spectrum&&!(e.swell&&e.swell[0]>0);let n=0,u=0,c=0;const i=o.map((r,i)=>{if(a)return{N:e.N,L:r,dk:s/r};const l=i===o.length-1,m=r/e.N,w=l?1e30:.5*Math.PI/m,E=function(e,t,o,a,n,u){const c=e.N,r=c*c,i=s/o,l=new Float64Array(r),m=new Float64Array(r),w=new Float32Array(r),E=Math.cos(f(e.wind)),y=Math.sin(f(e.wind)),M=e.swell,b=M?Math.cos(f(M[2])):0,F=M?Math.sin(f(M[2])):0,_=M&&M[1]>0?s/M[1]:0;let A=0,T=0;for(let o=0;o<c;o++){const r=o<c/2?o:o-c;for(let s=0;s<c;s++){const f=s<c/2?s:s-c,g=o*c+s,R=i*f,k=i*r,z=Math.hypot(R,k);if(w[g]=v(e,z),z<1e-12||f===-c/2||r===-c/2||z<a||z>=n)continue;const D=Math.exp(-z*z*u*u)*i*i,S=Math.atan2(E*k-y*R,E*R+y*k),N=x(e,z),L=1e-4*z,P=(x(e,z+L)-x(e,z-L))/(2*L);if("none"!==e.spectrum&&(l[g]=p(e,t,N)*d(t,N,S)*P/z*D),M&&M[0]>0){const e=Math.atan2(b*k-F*R,b*R+F*k);m[g]=Math.exp(-(N-_)*(N-_)/(.0128*_*_))*h(25,e)*P/z*D}A+=l[g],T+=m[g]}}return{N:c,L:o,dk:i,Pw:l,Ps:m,omega:w,varw:A,vars:T}}(e,t,r,n,w,l?e.cutoff<0?.5*m:e.cutoff:0);return n=w,u+=E.varw,c+=E.vars,E});if(e.Hs>0&&u<=0)throw new Error("The wind sea has no energy on these grids.");const l=e.Hs>0?(e.Hs/4)**2/u:1,m=e.swell&&e.swell[0]>0&&c>0?(e.swell[0]/4)**2/c:1;a||i.forEach((t,o)=>{t.h0=function(e,t,o,a){const n=function(e){let t=0,o=0,a=0,n=!1;function u(){const e=t,a=o,n=65535&a,u=a>>>16,c=32557*n,r=19605*n,i=32557*u,s=(c>>>16)+(65535&r)+(65535&i),f=((65535&s)<<16|65535&c)>>>0,l=19605*u+(r>>>16)+(i>>>16)+(s>>>16)+Math.imul(e,1284865837)+Math.imul(a,1481765933),m=f+695383991;o=m>>>0,t=l+3028054935+(m>4294967295?1:0)>>>0;const x=(((a>>>18|e<<14)^a)>>>27|(e>>>18^e)<<5)>>>0,v=e>>>27;return(x>>>v|x<<(32-v&31))>>>0}u();const c=o+e;o=c>>>0,t=t+Math.floor(c/4294967296)>>>0,u();const r=()=>(u()+.5)/4294967296;return()=>{if(n)return n=!1,a;let e,t,o;do{e=2*r()-1,t=2*r()-1,o=e*e+t*t}while(o>=1||0===o);const u=Math.sqrt(-2*Math.log(o)/o);return a=t*u,n=!0,e*u}}(a),u=e.N*e.N,c=new Float32Array(4*u);for(let a=0;a<u;a++){const u=.5*Math.sqrt(t*e.Pw[a]+o*e.Ps[a]),r=n(),i=n();c[4*a]=r*u,c[4*a+1]=i*u,c[4*a+2]=e.omega[a]}return c}(t,l,m,e.seed+o),t.Pw=t.Ps=t.omega=null});const w=e.E>0?e.E:o[0],E=e.M>0?e.M:Math.max(e.N,Math.ceil(w/(o[o.length-1]/e.N)));let y=8;for(;y<E;)y<<=1;return{cas:i,sc:t,E:w,M:Math.min(y,2048),Hs:4*Math.sqrt(l*u+m*c),still:a}}const E="#version 300 es\nprecision highp float;precision highp int;precision highp sampler2D;precision highp sampler2DArray; vec2 cm(vec2 a,vec2 b){return vec2(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}\n",y=E+"void main(){vec2 p=vec2((gl_VertexID<<1)&2,gl_VertexID&2);gl_Position=vec4(p*2.-1.,0.,1.);}",M=y.replace("0.,1.)","1.,1.)"),b="uniform sampler2DArray uD,uS,uX;uniform int uNC,uN;uniform float uL[4]; vec4 bil(sampler2DArray t,vec2 x,int c){vec2 u=x/uL[c]*float(uN),f=fract(u);ivec2 a=ivec2(floor(u))&(uN-1),b=(a+1)&(uN-1); return mix(mix(texelFetch(t,ivec3(a,c),0),texelFetch(t,ivec3(b.x,a.y,c),0),f.x),mix(texelFetch(t,ivec3(a.x,b.y,c),0),texelFetch(t,ivec3(b,c),0),f.x),f.y);}\n",F={spec:"uniform sampler2D uH0;uniform int uN;uniform float uDk,uT;uniform vec2 uCur; layout(location=0)out vec4 o0;layout(location=1)out vec4 o1;layout(location=2)out vec4 o2; void main(){ivec2 q=ivec2(gl_FragCoord.xy);int N=uN;float kx=uDk*float(q.x<N/2?q.x:q.x-N),kz=uDk*float(q.y<N/2?q.y:q.y-N),k=length(vec2(kx,kz)); o0=vec4(0);o1=vec4(0);o2=vec4(0);if(k<1e-12)return; vec4 a0=texelFetch(uH0,q,0),b0=texelFetch(uH0,ivec2((N-q.x)%N,(N-q.y)%N),0); vec2 e=vec2(cos(a0.z*uT),sin(a0.z*uT)),a=cm(a0.xy,e),b=cm(vec2(b0.x,-b0.y),vec2(e.x,-e.y)),s=a+b; float kU=kx*uCur.x+kz*uCur.y;vec2 c=vec2(cos(kU*uT),-sin(kU*uT)),hk=cm(s,c),r=a0.z*(a-b)-kU*s,hd=cm(vec2(-r.y,r.x),c); float ux=kx/k,uz=kz/k; o0=vec4(hk*(1.+ux),cm(hk,vec2(-kx,-uz)));o1=vec4(cm(hk,vec2(0.,kz+kx*ux)),cm(hk,vec2(kz*uz,kx*uz)));o2=vec4(cm(hd,vec2(uz,-ux)),0.,0.);}",fft:"uniform sampler2D uA,uTw;\n#if CNT==3\nuniform sampler2D uB,uC;layout(location=1)out vec4 oB;layout(location=2)out vec4 oC;\n#endif\nlayout(location=0)out vec4 oA;uniform int uNs,uQ,uStride,uR;uniform bool uHz;uniform float uSign; ivec2 q;int j,k,r; ivec2 at(int i){return uHz?ivec2(i,q.y):ivec2(q.x,i);} vec2 tw(int i){vec2 w=texelFetch(uTw,ivec2(i,0),0).xy;return vec2(w.x,uSign*w.y);} vec4 rot(vec4 v,vec2 w){return vec4(cm(v.xy,w),cm(v.zw,w));} vec4 st(sampler2D s){vec4 x0=texelFetch(s,at(j),0),x1=rot(texelFetch(s,at(j+uQ),0),tw(k*uStride)); if(uR==2)return r==0?x0+x1:x0-x1; vec4 x2=rot(texelFetch(s,at(j+2*uQ),0),tw(2*k*uStride)),x3=rot(texelFetch(s,at(j+3*uQ),0),tw(3*k*uStride)); vec4 a=x0+x2,b=x0-x2,c=x1+x3,d=x1-x3;d=uSign*vec4(-d.y,d.x,-d.w,d.z); return r==0?a+c:r==1?b+d:r==2?a-c:b-d;} void main(){q=ivec2(gl_FragCoord.xy);int o=uHz?q.x:q.y;k=o&(uNs-1);r=(o/uNs)&(uR-1);j=(o/(uR*uNs))*uNs+k; oA=st(uA);\n#if CNT==3\noB=st(uB);oC=st(uC);\n#endif\n}",asm:"uniform sampler2D uA,uB,uC;layout(location=0)out vec4 oD;layout(location=1)out vec4 oS;layout(location=2)out vec4 oX; void main(){ivec2 q=ivec2(gl_FragCoord.xy);vec4 a=texelFetch(uA,q,0),b=texelFetch(uB,q,0),c=texelFetch(uC,q,0); oD=vec4(a.y,a.x,a.z,0.);oS=vec4(a.w,b.x,b.y,b.z);oX=vec4(b.w,c.x,c.y,0.);}",prep:b+"uniform sampler2D uState;uniform vec2 uWO;uniform float uDxw,uS0,uMargin,uDecay,uGain,uTrimL,uTrimA;uniform int uM,uNO;uniform ivec4 uOK;uniform vec4 uOP[4]; layout(location=0)out vec4 oE;layout(location=1)out vec4 oAux; float sdb(vec2 p,vec2 b){vec2 d=abs(p)-b;return length(max(d,0.))+min(max(d.x,d.y),0.);} float occ(vec2 x,float g){float O=1.;for(int k=0;k<4;k++){if(k>=uNO)break;vec4 o=uOP[k]; float d=uOK[k]==0?sdb(x-o.xy,vec2(g))-o.z:sdb(x-o.xy-.5*o.zw,.5*o.zw+g);O*=clamp(d/uDxw+.5,0.,1.);}return O;} void main(){ivec2 q=ivec2(gl_FragCoord.xy);vec2 x=vec2(q)*uDxw;float O=occ(x,0.),src=clamp(O-occ(x,max(1.,ceil(uMargin/uDxw))*uDxw),0.,1.); float dx=float(min(q.x,uM-1-q.x))*uDxw,dz=float(min(q.y,uM-1-q.y))*uDxw; float T=(dx<uTrimL?pow(dx/uTrimL,uTrimA):1.)*(dz<uTrimL?pow(dz/uTrimL,uTrimA):1.); float A=0.;for(int c=0;c<4;c++){if(c>=uNC)break;A+=bil(uD,uWO+x,c).y;} vec2 s=texelFetch(uState,q,0).xy;float h=(O*s.x-(1.-O)*A)*T,phi=O*s.y*T; oE=vec4(h*uDecay+uGain*uS0*src,phi*uDecay,0.,0.);oAux=vec4(O,T,src,0.);}",prop:"uniform sampler2D uE;uniform int uM;uniform float uDk,uDt,uDepth,uCap;uniform vec2 uCur; layout(location=0)out vec4 o0;layout(location=1)out vec4 o1;layout(location=2)out vec4 o2; void main(){ivec2 q=ivec2(gl_FragCoord.xy);int M=uM;vec2 F=texelFetch(uE,q,0).xy,Fp=texelFetch(uE,ivec2((M-q.x)%M,(M-q.y)%M),0).xy;Fp.y=-Fp.y; vec2 Hk=.5*(F+Fp),d=F-Fp,Pk=.5*vec2(d.y,-d.x); float kx=uDk*float(q.x<M/2?q.x:q.x-M),kz=uDk*float(q.y<M/2?q.y:q.y-M),k=length(vec2(kx,kz)); float K=uDepth>0.?k*tanh(min(k*uDepth,20.)):k,Gk=9.80665*(1.+uCap*k*k*7.5625e-6),w=sqrt(K*Gk); vec2 Hn=vec2(0),Pn=vec2(0);if(w>1e-9){float c=cos(w*uDt),s=sin(w*uDt);Hn=c*Hk+(K/w)*s*Pk;Pn=c*Pk-(Gk/w)*s*Hk;} float a=-(kx*uCur.x+kz*uCur.y)*uDt;vec2 ph=vec2(cos(a),sin(a));Hn=cm(Hn,ph);Pn=cm(Pn,ph); vec2 E2=vec2(0),E3=vec2(0),E4=vec2(0),E5=vec2(0); if(k>1e-12){float ux=kx/k,uz=kz/k;E2=cm(Hn,vec2(uz,-ux));E3=cm(Hn,vec2(-kz,kx));E4=cm(Hn,vec2(kx*ux,kz*uz));E5=Hn*(kx*uz);} o0=vec4(Hn+vec2(-Pn.y,Pn.x),E2);o1=vec4(E3,E4);o2=vec4(E5,0.,0.);}",fin:"uniform sampler2D uA,uB,uC,uAux;uniform float uScale; layout(location=0)out vec4 oState;layout(location=1)out vec4 oD;layout(location=2)out vec4 oS;layout(location=3)out vec4 oX; void main(){ivec2 q=ivec2(gl_FragCoord.xy);vec4 a=texelFetch(uA,q,0),b=texelFetch(uB,q,0),c=texelFetch(uC,q,0);vec2 ot=texelFetch(uAux,q,0).xy;float m=ot.x*ot.y*uScale; oState=vec4(a.xy*m,0.,0.);oD=vec4(a.z*m,a.x*m,a.w*m,ot.x);oS=b*m;oX=vec4(c.x*m,0.,0.,0.);}",foam:b+"uniform sampler2D uFoam,uWS,uWX,uWD;uniform bool uEw;uniform vec2 uFO,uShift;uniform float uFdx,uLam,uDt,uDecay,uThr;uniform int uFM; out vec4 o; float rd(vec2 p){float m=float(uFM-1);if(p.x<0.||p.y<0.||p.x>m||p.y>m)return 0.;ivec2 a=ivec2(p),b=min(a+1,uFM-1);vec2 f=p-vec2(a); return mix(mix(texelFetch(uFoam,a,0).x,texelFetch(uFoam,ivec2(b.x,a.y),0).x,f.x),mix(texelFetch(uFoam,ivec2(a.x,b.y),0).x,texelFetch(uFoam,b,0).x,f.x),f.y);} void main(){ivec2 q=ivec2(gl_FragCoord.xy);vec2 x=uFO+vec2(q)*uFdx;vec4 s=vec4(0);vec3 v=vec3(0); for(int c=0;c<4;c++){if(c>=uNC)break;s+=bil(uS,x,c);v+=bil(uX,x,c).xyz;} float O=1.;if(uEw){s+=texelFetch(uWS,q,0);v.x+=texelFetch(uWX,q,0).x;O=texelFetch(uWD,q,0).w;}s*=O;v*=O; float old=uDt>0.?rd(vec2(q)-uLam*v.yz*uDt/uFdx+uShift):0.; float Jxx=1.+uLam*s.z,Jzz=1.+uLam*s.w,Jxz=uLam*v.x,jm=.5*(Jxx+Jzz)-.5*sqrt((Jxx-Jzz)*(Jxx-Jzz)+4.*Jxz*Jxz); o=vec4((jm<uThr?1.:old*uDecay)*O,0.,0.,0.);}"},_="uniform vec3 uSun;uniform float uExp,uFogD; const vec3 SUNC=vec3(1.,.9,.78)*10.; vec3 sky(vec3 d){float y=max(d.y,0.),s=max(dot(d,uSun),0.),lo=1.-smoothstep(0.,.5,uSun.y); vec3 c=mix(vec3(.62,.72,.82),vec3(.11,.27,.58),pow(y,.45)); c+=vec3(1.,.55,.28)*pow(s,6.)*(1.-y)*.7*lo+vec3(1.,.88,.72)*(pow(s,80.)*.45+pow(s,900.)*1.5); return c*(.25+.75*smoothstep(-.04,.25,uSun.y));} vec3 haze(vec3 d){return sky(normalize(vec3(d.x,0.,d.z)+1e-6));} vec3 tone(vec3 c){c*=uExp;c=(c*(2.51*c+.03))/(c*(2.43*c+.59)+.14);return pow(clamp(c,0.,1.),vec3(1./2.2));}\n",A="uniform mat4 uInv;uniform vec3 uCam;uniform vec2 uRes;out vec4 o;"+_+"void main(){vec4 w=uInv*vec4(gl_FragCoord.xy/uRes*2.-1.,1.,1.);vec3 d=normalize(w.xyz/w.w-uCam); vec3 c=d.y>0.?sky(d)+SUNC*smoothstep(.99985,.99993,dot(d,uSun)):haze(d);o=vec4(tone(c),1.);}",T="in vec3 vP;in vec2 vX;out vec4 o; uniform sampler2DArray uS,uX;uniform int uNC,uN;uniform float uL[4]; uniform bool uEw,uFw;uniform sampler2D uWS,uWX,uWD,uF;uniform vec2 uWO,uFO;uniform float uWE,uWM,uFE,uFM,uLam,uThr,uRg;uniform vec3 uCam;"+_+"void main(){vec4 s=vec4(0);float jxz=0.; for(int c=0;c<4;c++){if(c>=uNC)break;vec3 t=vec3(vX/uL[c]+.5/float(uN),float(c));s+=texture(uS,t);jxz+=texture(uX,t).x;} float O=1.;if(uEw){vec2 u=(vX-uWO)/uWE,t=u+.5/uWM;float in1=step(0.,min(u.x,u.y))*step(max(u.x,u.y),1.); vec4 ws=texture(uWS,t);float wx=texture(uWX,t).x,wo=texture(uWD,t).w;s+=ws*in1;jxz+=wx*in1;O=mix(1.,wo,in1);} s*=O;jxz*=O; float Jxx=1.+uLam*s.z,Jzz=1.+uLam*s.w,Jxz=uLam*jxz;vec3 n=normalize(vec3(s.y*Jxz-Jzz*s.x,Jxx*Jzz-Jxz*Jxz,Jxz*s.x-s.y*Jxx));if(n.y<0.)n=-n; float jm=.5*(Jxx+Jzz)-.5*sqrt((Jxx-Jzz)*(Jxx-Jzz)+4.*Jxz*Jxz),foam=smoothstep(uThr+.12,uThr-.04,jm); if(uFw){vec2 u=(vX-uFO)/uFE;float e=min(min(u.x,u.y),min(1.-u.x,1.-u.y)),f=texture(uF,u+.5/uFM).x;foam=mix(foam,f,smoothstep(0.,.05,e));} foam=clamp(foam*O,0.,1.); vec3 V=uCam-vP;float dist=length(V);V/=dist;float nv=max(dot(n,V),1e-3),nl=max(dot(n,uSun),0.); float F=.02+.98*pow(1.-nv,5.);vec3 R=reflect(-V,n);R.y=abs(R.y); float ro=clamp(.06+dist*uRg,.06,.32),a2=ro*ro*ro*ro;vec3 H=normalize(V+uSun);float nh=max(dot(n,H),0.),dd=nh*nh*(a2-1.)+1.; vec3 spec=SUNC*a2/(3.14159*dd*dd)*(.02+.98*pow(1.-max(dot(H,V),0.),5.))/(4.*max(nv,.2))*step(0.,dot(n,uSun)); float hg=clamp(vP.y*.22+.42,0.,1.),amb=.25+.75*smoothstep(-.04,.25,uSun.y); vec3 water=mix(vec3(.003,.028,.055),vec3(.02,.17,.16),hg*hg)*(.45+.55*(nl*.6+.4))*amb; vec3 col=mix(water,sky(R),F)+spec; col=mix(col,vec3(.82,.88,.9)*(.35+.65*nl)*amb,foam*.9); col=mix(col,haze(-V),1.-exp(-dist/uFogD));o=vec4(tone(col),1.);}",g="in vec3 vP,vN;out vec4 o;uniform vec3 uCam,uCol;uniform bool uBands;"+_+"void main(){vec3 n=normalize(vN),V=normalize(uCam-vP);vec3 b=uCol;if(uBands&&fract(vP.y/1.5)<.5)b=vec3(.85,.86,.84); float amb=.25+.75*smoothstep(-.04,.25,uSun.y);vec3 c=b*(max(dot(n,uSun),0.)*1.6*amb+sky(normalize(n+vec3(0.,.6,0.)))*.5); c+=SUNC*.04*pow(max(dot(reflect(-V,n),uSun),0.),40.);c=mix(c,haze(-V),1.-exp(-length(uCam-vP)/uFogD));o=vec4(tone(c),1.);}";function R(e,t){const o=a.createProgram(),n=[[a.VERTEX_SHADER,e],[a.FRAGMENT_SHADER,t]].map(([e,t])=>{const n=a.createShader(e);return a.shaderSource(n,t),a.compileShader(n),a.attachShader(o,n),n});a.bindAttribLocation(o,0,"aG"),a.bindAttribLocation(o,0,"aP"),a.bindAttribLocation(o,1,"aN"),a.linkProgram(o);const u={};return{p:o,sh:n,u:e=>e in u?u[e]:u[e]=a.getUniformLocation(o,e)}}a.getExtension("KHR_parallel_shader_compile");const k=e=>R(y,E+e),z={spec:k(F.spec),fft1:k("#define CNT 1\n"+F.fft),fft3:k("#define CNT 3\n"+F.fft),asm:k(F.asm),prep:k(F.prep),prop:k(F.prop),fin:k(F.fin),foam:k(F.foam),bg:R(M,E+A),ocean:R(E+"in vec2 aG;uniform mat4 uVP;uniform vec3 uCam;uniform float uR0,uR1,uRings,uStep,uLam; uniform sampler2DArray uD;uniform int uNC,uN;uniform float uL[4];uniform bool uEw;uniform sampler2D uWD;uniform vec2 uWO;uniform float uWE,uWM; out vec3 vP;out vec2 vX; void main(){float r=aG.x<.5?0.:uR0*pow(uR1/uR0,(aG.x-1.)/(uRings-2.));vec2 x=uCam.xz+r*vec2(cos(aG.y),sin(aG.y));float sp=max(r,uR0)*uStep; vec3 d=vec3(0);for(int c=0;c<4;c++){if(c>=uNC)break;d+=textureLod(uD,vec3(x/uL[c]+.5/float(uN),float(c)),max(0.,log2(sp*float(uN)/uL[c]))).xyz;} float O=1.;if(uEw){vec2 u=(x-uWO)/uWE;if(min(u.x,u.y)>=0.&&max(u.x,u.y)<=1.){vec4 w=textureLod(uWD,u+.5/uWM,max(0.,log2(sp*uWM/uWE)));d+=w.xyz;O=w.w;}} d*=O;vP=vec3(x.x+uLam*d.x,d.y,x.y+uLam*d.z);vX=x;gl_Position=uVP*vec4(vP,1.);}",E+T),obj:R(E+"in vec3 aP;in vec3 aN;uniform mat4 uVP;uniform vec3 uOff,uScale;out vec3 vP,vN; void main(){vP=aP*uScale+uOff;vN=normalize(aN/uScale);gl_Position=uVP*vec4(vP,1.);}",E+g)};Object.values(z).forEach(function(e){if(!a.getProgramParameter(e.p,a.LINK_STATUS))throw new Error(e.sh.map(e=>a.getShaderInfoLog(e)).join("\n")+a.getProgramInfoLog(e.p));return e});let D=null;function S(e,...t){D!==e&&(a.useProgram(e.p),D=e);let o=0;for(const n of t)for(const t in n){const u=n[t],c=e.u(t.slice(2));switch(t[0]){case"i":a.uniform1i(c,u);break;case"f":a.uniform1f(c,u);break;case"2":a.uniform2fv(c,u);break;case"3":a.uniform3fv(c,u);break;case"a":a.uniform1fv(c,u);break;case"q":a.uniform4fv(c,u);break;case"k":a.uniform4iv(c,u);break;case"m":a.uniformMatrix4fv(c,!1,u);break;case"t":case"A":a.activeTexture(a.TEXTURE0+o),a.bindTexture("A"===t[0]?a.TEXTURE_2D_ARRAY:a.TEXTURE_2D,u),a.uniform1i(c,o++)}}}function N(e,t,o,n={}){const c=a.createTexture(),r=n.layers?a.TEXTURE_2D_ARRAY:a.TEXTURE_2D,i=n.mip?Math.log2(Math.max(e,t))+1:1;a.bindTexture(r,c),n.layers?a.texStorage3D(r,i,o,e,t,n.layers):a.texStorage2D(r,i,o,e,t);const s=n.mip?a.LINEAR_MIPMAP_LINEAR:n.linear?a.LINEAR:a.NEAREST;return a.texParameteri(r,a.TEXTURE_MIN_FILTER,s),a.texParameteri(r,a.TEXTURE_MAG_FILTER,n.mip||n.linear?a.LINEAR:a.NEAREST),a.texParameteri(r,a.TEXTURE_WRAP_S,n.repeat?a.REPEAT:a.CLAMP_TO_EDGE),a.texParameteri(r,a.TEXTURE_WRAP_T,n.repeat?a.REPEAT:a.CLAMP_TO_EDGE),n.mip&&u&&a.texParameterf(r,u.TEXTURE_MAX_ANISOTROPY_EXT,4),n.data&&a.texSubImage2D(r,0,0,0,e,t,n.rg?a.RG:a.RGBA,a.FLOAT,n.data),c}function L(e){const t=a.createFramebuffer();if(a.bindFramebuffer(a.FRAMEBUFFER,t),e.forEach((e,t)=>Array.isArray(e)?a.framebufferTextureLayer(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0+t,e[0],0,e[1]):a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0+t,a.TEXTURE_2D,e,0)),a.drawBuffers(e.map((e,t)=>a.COLOR_ATTACHMENT0+t)),a.checkFramebufferStatus(a.FRAMEBUFFER)!==a.FRAMEBUFFER_COMPLETE)throw new Error("A framebuffer is incomplete on this GPU.");return t}const P=a.createVertexArray();function C(e,t,o){a.bindFramebuffer(a.FRAMEBUFFER,e),a.viewport(0,0,t,o),a.bindVertexArray(P),a.drawArrays(a.TRIANGLES,0,3)}const O={};function q(e){if(!O[e]){const t=new Float32Array(2*e);for(let o=0;o<e;o++)t[2*o]=Math.cos(s*o/e),t[2*o+1]=Math.sin(s*o/e);O[e]=N(e,1,a.RG32F,{data:t,rg:!0})}return O[e]}function U(e,t,o,n){const u=3===o?z.fft3:z.fft1;S(u,{t_uTw:q(e),f_uSign:n});const c=u.U||(u.U=["uA","uB","uC","uNs","uQ","uStride","uR","uHz"].map(u.u));for(let e=0;e<o;e++)a.uniform1i(c[e],1+e);let r=0;for(let n=1;n>=0;n--)for(let u=1;u<e;){const i=4*u<=e?4:2,s=t[r].t;for(let e=0;e<o;e++)a.activeTexture(a.TEXTURE1+e),a.bindTexture(a.TEXTURE_2D,s[e]);a.uniform1i(c[3],u),a.uniform1i(c[4],e/i),a.uniform1i(c[5],e/(i*u)),a.uniform1i(c[6],i),a.uniform1i(c[7],n),C(t[1-r].fb,e,e),r=1-r,u*=i}}const W=N(1,1,c,{linear:!0}),X=(e,t)=>{const o=Array.from({length:t},()=>N(e,e,a.RGBA32F));return{t:o,fb:L(o)}};function B(e){if(!e.every(e=>Number.isInteger(e)))return 0;const t=(e,o)=>o?t(o,e%o):e;return e.reduce((e,o)=>e/t(e,o)*o)}const H={N:256,L:[500],V:15,wind:0,spectrum:"jonswap",fetch:1e5,gamma:3.3,Hs:0,cutoff:-1,lambda:1,depth:0,capillary:!1,swell:null,cur:[0,0],Tloop:0,seed:1,E:0,M:0,source:0,srcMargin:1,friction:0,trimL:0,trimA:.05,wcThr:.3,wcT:4,obs:[],dt:.1,warmup:0,follow:-1},I={kind:"disc",x:30,z:100,a:3,vx:6,vz:0},V=[{name:"Open sea",note:"3 cascades · swell 1 m, 12 s from 30°",p:{L:[1e3,250,60],E:300,M:1024,V:12,swell:[1,12,30]},cam:{yaw:2.4,pitch:.12,dist:42},focus:[0,0],sunAz:140},{name:"Wake on a wind sea",note:"3 m post at 6 m/s · 8 m/s wind, 20 km fetch",p:{L:[400,100],V:8,fetch:2e4,E:200,M:512,obs:[{...I,x:150}],source:.5,warmup:22,follow:0},cam:{yaw:-2.25,pitch:.34,dist:78},sunAz:60},{name:"Kelvin wake",note:"3 m post at 6 m/s through flat water",p:{spectrum:"none",L:[200],N:512,obs:[{...I,x:150}],source:.5,warmup:22,follow:0},cam:{yaw:-2.25,pitch:.42,dist:82},sunAz:60},{name:"Box scattering",note:"40 m box · 8 m/s wind · 300 m window",p:{L:[300],V:8,obs:[{kind:"box",x:130,z:130,a:40,b:40}],warmup:30},cam:{yaw:-2.5,pitch:.5,dist:175},focus:[150,150],sunAz:70}],G={wind:e("wind"),chop:e("chop"),sun:e("sun"),speed:e("speed")};let J=null,j=0,$=0,Y=1,K=!matchMedia("(prefers-reduced-motion: reduce)").matches,Q=!0;const Z={yaw:0,pitch:.2,dist:60,target:[0,0,0]},ee=["calm (glassy)","calm (rippled)","smooth","slight","moderate","rough","very rough","high","very high","phenomenal"];function te(e){const t=V[j];if(t.p.follow>=0&&J){const o=J.window(e),a=J.obstacle(J.p.obs[t.p.follow],e);return[o[0]+a[0],o[1]+a[1]]}return t.focus}function oe(e,t){const o=V[e];t&&e===j||(G.wind.value=o.p.V||12,G.chop.value=o.p.lambda||1,Object.assign(Z,o.cam)),j=e,J&&J.dispose();const n="none"===o.p.spectrum;G.wind.disabled=n,J=function(e,t=!1){const o=Object.assign({},H,e,{obs:(e.obs||[]).map(e=>({b:0,vx:0,vz:0,...e}))}),n=w(o),u=o.N,r=n.cas.length,i=n.cas.map(e=>e.L),f=n.still,l=t?a.RGBA16F:c,m=[],x=e=>(m.push(e),e),v=f?[]:n.cas.map(e=>x(N(u,u,a.RGBA32F,{data:e.h0}))),h=f?null:X(u,3),p=f?null:X(u,3);f||m.push(...h.t,...p.t);const d=()=>x(N(u,u,l,{layers:r,mip:!0,repeat:!0})),E=d(),y=d(),M=d(),b=i.map((e,t)=>L([[E,t],[y,t],[M,t]])),F=new Float32Array(4);i.forEach((e,t)=>{F[t]=e});const _={A_uD:E,A_uS:y,A_uX:M,i_uNC:r,i_uN:u,a_uL:F},A=n.E,T=n.M,g=A/T,R=o.obs.length>0,k=o.follow>=0?o.obs[o.follow]:null,D=k?[k.vx,k.vz]:[0,0],P=Math.ceil(o.warmup/o.dt),O={p:o,sea:n,N:u,nc:r,L:F,E:A,M:T,dxw:g,ew:R,wv:D,casU:_,D:E,S:y,X:M,tFirst:-P*o.dt,wrap:[0,0],wrapP:B(i)};let q,I,V,G,J,j,$,Y,K,Q,Z;R&&(q=X(T,3),I=X(T,3),m.push(...q.t,...I.t),V={t:[q.t[0]],fb:L([q.t[0]])},G={t:[I.t[0]],fb:L([I.t[0]])},J=x(N(T,T,a.RGBA32F)),j=x(N(T,T,a.RGBA32F)),$=x(N(T,T,l,{mip:!0})),Y=x(N(T,T,l,{mip:!0})),K=x(N(T,T,l,{mip:!0})),Q=L([q.t[0],J]),Z=L([j,$,Y,K]),Object.assign(O,{WD:$,WS:Y,WX:K}));const ee=R||!t?T:Math.min(T,512),te=A/ee;O.Mf=ee;const oe=[0,1].map(()=>x(N(ee,ee,l,{linear:!0}))),ae=oe.map(e=>L([e]));let ne=0,ue=null;return O.foam=()=>oe[ne],O.foamO=[0,0],O.window=e=>[D[0]*e-O.wrap[0],D[1]*e-O.wrap[1]],O.obstacle=(e,t)=>[e.x+(e.vx-D[0])*t,e.z+(e.vz-D[1])*t],O.ewU=R?{t_uWS:Y,t_uWX:K,t_uWD:$}:{t_uWS:W,t_uWX:W,t_uWD:W},O.step=(e,t,c,i,l=!0)=>{if(a.disable(a.DEPTH_TEST),!f){for(let t=0;t<r;t++)S(z.spec,{t_uH0:v[t],i_uN:u,f_uDk:n.cas[t].dk,f_uT:e,"2_uCur":o.cur}),C(h.fb,u,u),U(u,[h,p],3,1),S(z.asm,{t_uA:h.t[0],t_uB:h.t[1],t_uC:h.t[2]}),C(b[t],u,u);if(l)for(const e of[E,y,M])a.bindTexture(a.TEXTURE_2D_ARRAY,e),a.generateMipmap(a.TEXTURE_2D_ARRAY)}const m=e-O.tFirst;let x;if(R){x=O.window(m);const e=[0,0,0,0],n=new Float32Array(16);o.obs.forEach((t,o)=>{const[a,u]=O.obstacle(t,m);e[o]="disc"===t.kind?0:1,n.set([a,u,t.a,t.b||0],4*o)});const u=o.friction>0?Math.exp(-t/o.friction):1,c=o.friction>0?o.friction*(1-u):t;if(S(z.prep,{t_uState:j,"2_uWO":x,f_uDxw:g,f_uS0:o.source,f_uMargin:o.srcMargin,f_uDecay:u,f_uGain:c,f_uTrimL:o.trimL>0?o.trimL:A/8,f_uTrimA:o.trimA,i_uM:T,i_uNO:o.obs.length,k_uOK:e,q_uOP:n},_),C(Q,T,T),U(T,[V,G],1,-1),S(z.prop,{t_uE:q.t[0],i_uM:T,f_uDk:s/A,f_uDt:t,f_uDepth:o.depth,f_uCap:o.capillary?1:0,"2_uCur":[o.cur[0]-D[0],o.cur[1]-D[1]]}),C(I.fb,T,T),U(T,[I,q],3,1),S(z.fin,{t_uA:I.t[0],t_uB:I.t[1],t_uC:I.t[2],t_uAux:J,f_uScale:1/(T*T)}),C(Z,T,T),l)for(const e of[$,Y,K])a.bindTexture(a.TEXTURE_2D,e),a.generateMipmap(a.TEXTURE_2D)}else x=[Math.round((i[0]-A/2)/te)*te,Math.round((i[1]-A/2)/te)*te];const d=ue&&!c?R?[D[0]*t/te,D[1]*t/te]:[(x[0]-ue[0])/te,(x[1]-ue[1])/te]:[0,0];ue=x,O.foamO=x,S(z.foam,{t_uFoam:oe[ne],i_uEw:R,"2_uFO":x,"2_uShift":d,f_uFdx:te,i_uFM:ee,f_uLam:o.lambda,f_uDt:c?0:t,f_uDecay:t>0&&!c?Math.exp(-t/o.wcT):0,f_uThr:o.wcThr},_,O.ewU),C(ae[1-ne],ee,ee),ne=1-ne},O.dispose=()=>{m.forEach(e=>a.deleteTexture(e)),[...f?[]:[h.fb,p.fb],...b,...ae,...R?[q.fb,I.fb,V.fb,G.fb,Q,Z]:[]].forEach(e=>a.deleteFramebuffer(e))},O}({...V[j].p,V:+G.wind.value,lambda:+G.chop.value},!0),$=0,function(e,t){const o=e.p,a=Math.round(-e.tFirst/o.dt);for(let n=-a;n<=0;n++)e.step(n*o.dt,n===-a?0:o.dt,n===-a,t,0===n)}(J,o.focus||[0,0]),Q=!0,document.querySelectorAll(".scene").forEach((t,o)=>t.setAttribute("aria-pressed",o===e)),ae(!0)}function ae(t){const o=J.p,a="none"===o.spectrum,n=[];var u;e("windO").textContent=a?"calm":`${(+G.wind.value).toFixed(1)} m/s · Bft ${u=+G.wind.value,Math.min(12,Math.round(Math.pow(u/.836,2/3)))}`,e("chopO").textContent=(+G.chop.value).toFixed(2),e("sunO").textContent=`${G.sun.value}°`,e("speedO").textContent=K?`${Y.toFixed(2)}×`:"paused",e("play").textContent=K?"Pause":"Play";const c=J.sea.Hs,r=(e=>e<=0?0:1+[.1,.5,1.25,2.5,4,6,9,14].filter(t=>e>t).length)(c);if(a||(n.push(["Hs expected",`${c.toFixed(2)} m`],["Sea state",`${r} · ${ee[r]}`]),n.push(["Peak period",`${(s/J.sea.sc.wp).toFixed(1)} s`])),n.push(["Cascades","none"===o.spectrum?"none":J.L.slice(0,J.nc).join(" / ")+" m"]),J.ew){const e=o.obs[0],t=Math.hypot(e.vx,e.vz);n.push(["eWave grid",`${J.M}² · ${J.dxw.toFixed(2)} m`]),t>0&&n.push(["Post speed",`${t.toFixed(1)} m/s · ${(1.943844*t).toFixed(1)} kn`],["Kelvin angle","19.47°"])}else n.push(["Whitecap grid",`${J.Mf}² · ${(J.E/J.Mf).toFixed(2)} m`]);!t&&e("read").childElementCount||(e("read").innerHTML=n.map(([e,t])=>`<dt>${e}</dt><dd>${t}</dd>`).join("")),Q=!0}V.forEach((t,o)=>{const a=document.createElement("button");a.type="button",a.className="scene",a.innerHTML=`${t.name}<span>${t.note}</span>`,a.onclick=()=>oe(o),e("scenes").append(a)}),G.wind.addEventListener("change",()=>oe(j,!0)),G.wind.addEventListener("input",()=>ae(!0)),G.chop.addEventListener("input",()=>{J.p.lambda=+G.chop.value,ae(!0)}),G.sun.addEventListener("input",()=>ae(!0)),G.speed.addEventListener("input",()=>{Y=+G.speed.value,ae(!0)}),e("play").onclick=()=>{K=!K,ae(!0)},e("restart").onclick=()=>oe(j,!0),innerWidth<640&&(e("card").open=!1);const ne=320,ue=a.createVertexArray();let ce;{const e=new Float32Array(205440),t=new Uint32Array(612480);for(let t=0,o=0;t<320;t++)for(let a=0;a<=ne;a++)e[o++]=t,e[o++]=s*a/ne;let o=0;for(let e=0;e<319;e++)for(let a=0;a<ne;a++){const n=321*e+a,u=n+ne+1;t.set([n,u,n+1,n+1,u,u+1],o),o+=6}a.bindVertexArray(ue),a.bindBuffer(a.ARRAY_BUFFER,a.createBuffer()),a.bufferData(a.ARRAY_BUFFER,e,a.STATIC_DRAW),a.enableVertexAttribArray(0),a.vertexAttribPointer(0,2,a.FLOAT,!1,0,0),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,a.createBuffer()),a.bufferData(a.ELEMENT_ARRAY_BUFFER,t,a.STATIC_DRAW),ce=t.length}function re(e,t){const o=a.createVertexArray();return a.bindVertexArray(o),[e,t].forEach((e,t)=>{a.bindBuffer(a.ARRAY_BUFFER,a.createBuffer()),a.bufferData(a.ARRAY_BUFFER,new Float32Array(e),a.STATIC_DRAW),a.enableVertexAttribArray(t),a.vertexAttribPointer(t,3,a.FLOAT,!1,0,0)}),{vao:o,n:e.length/3}}const ie=(()=>{const e=[],t=[];for(let o=0;o<48;o++){const a=s*o/48,n=s*(o+1)/48,u=Math.cos(a),c=Math.sin(a),r=Math.cos(n),i=Math.sin(n);e.push(u,0,c,r,1,i,u,1,c,u,0,c,r,0,i,r,1,i),t.push(u,0,c,r,0,i,u,0,c,u,0,c,r,0,i,r,0,i),e.push(0,1,0,u,1,c,r,1,i),t.push(0,1,0,0,1,0,0,1,0)}return re(e,t)})(),se=(()=>{const e=[],t=[],o=(o,a,n,u)=>{const c=[o,o.map((e,t)=>e+a[t]),o.map((e,t)=>e+a[t]+n[t]),o.map((e,t)=>e+n[t])];[0,1,2,0,2,3].forEach(o=>{e.push(...c[o]),t.push(...u)})};return o([0,0,0],[0,1,0],[1,0,0],[0,0,-1]),o([0,0,1],[1,0,0],[0,1,0],[0,0,1]),o([0,0,0],[0,0,1],[0,1,0],[-1,0,0]),o([1,0,0],[0,1,0],[0,0,1],[1,0,0]),o([0,1,0],[0,0,1],[1,0,0],[0,1,0]),re(e,t)})();function fe(e){const[t,o,a,n,u,c,r,i,s,f,l,m,x,v,h,p]=e,d=t*c-o*u,w=t*r-a*u,E=t*i-n*u,y=o*r-a*c,M=o*i-n*c,b=a*i-n*r,F=s*v-f*x,_=s*h-l*x,A=s*p-m*x,T=f*h-l*v,g=f*p-m*v,R=l*p-m*h,k=1/(d*R-w*g+E*T+y*A-M*_+b*F);return[(c*R-r*g+i*T)*k,(a*g-o*R-n*T)*k,(v*b-h*M+p*y)*k,(l*M-f*b-m*y)*k,(r*A-u*R-i*_)*k,(t*R-a*A+n*_)*k,(h*E-x*b-p*w)*k,(s*b-l*E+m*w)*k,(u*g-c*A+i*F)*k,(o*A-t*g-n*F)*k,(x*M-v*E+p*d)*k,(f*E-s*M-m*d)*k,(c*_-u*T-r*F)*k,(t*T-o*_+a*F)*k,(v*w-x*y-h*d)*k,(s*y-f*w+l*d)*k]}const le=new Map;let me=0;t.addEventListener("pointerdown",e=>{t.setPointerCapture(e.pointerId),le.set(e.pointerId,[e.clientX,e.clientY])}),t.addEventListener("pointermove",e=>{const t=le.get(e.pointerId);if(t&&(Q=!0,1===le.size&&(Z.yaw-=.005*(e.clientX-t[0]),Z.pitch=Math.min(1.45,Math.max(-.2,Z.pitch+.004*(e.clientY-t[1])))),le.set(e.pointerId,[e.clientX,e.clientY]),2===le.size)){const[e,t]=[...le.values()],o=Math.hypot(e[0]-t[0],e[1]-t[1]);me&&(Z.dist=Math.min(2500,Math.max(6,Z.dist*me/o))),me=o}});const xe=e=>{le.delete(e.pointerId),me=0};t.addEventListener("pointerup",xe),t.addEventListener("pointercancel",xe),t.addEventListener("wheel",e=>{e.preventDefault(),Q=!0,Z.dist=Math.min(2500,Math.max(6,Z.dist*Math.exp(.0012*e.deltaY)))},{passive:!1}),addEventListener("resize",()=>{Q=!0});let ve=0,he=1,pe=1/60,de=0;try{oe(Math.min(V.length-1,+(/^#scene=(\d)/.exec(location.hash)||[0,0])[1]))}catch(e){o(`The simulation could not start: ${e.message}`)}requestAnimationFrame(function o(n){if(requestAnimationFrame(o),n-ve<14.5)return;const u=Math.min(.1,(n-ve)/1e3);if(ve=n,!K&&!Q)return;Q=!1,K&&(pe=.95*pe+.05*u,pe>.024&&he>.55?(he*=.88,pe=.017):pe<.018&&he<1&&(he=Math.min(1,1.02*he)));const c=K?u*Y:0;if(c>0){$+=c;const e=$-J.tFirst,t=J.window(e);J.wrapP&&Math.abs(t[0])>J.wrapP&&(J.wrap[0]+=Math.sign(t[0])*J.wrapP),J.wrapP&&Math.abs(t[1])>J.wrapP&&(J.wrap[1]+=Math.sign(t[1])*J.wrapP),J.step($,c,!1,te(e))}!function(e){const o=Math.min(devicePixelRatio||1,2),n=t.clientWidth*o,u=t.clientHeight*o,c=Math.min(1,Math.sqrt(37e5/(n*u)))*he,r=Math.round(n*c),i=Math.round(u*c);t.width===r&&t.height===i||(t.width=r,t.height=i);const s=te(e),l=Math.cos(Z.pitch);Z.target=[s[0],0,s[1]];const m=[s[0]+Z.dist*l*Math.sin(Z.yaw),Z.dist*Math.sin(Z.pitch),s[1]+Z.dist*l*Math.cos(Z.yaw)];m[1]=Math.max(m[1],1.6);const x=function(e,t){const o=new Array(16);for(let a=0;a<4;a++)for(let n=0;n<4;n++){let u=0;for(let o=0;o<4;o++)u+=e[4*o+n]*t[4*a+o];o[4*a+n]=u}return o}(function(e,t,o,a){const n=1/Math.tan(.41);return[n/t,0,0,0,0,n,0,0,0,0,(a+o)/(o-a),-1,0,0,2*a*o/(o-a),0]}(0,r/i,Math.min(1,Math.max(.05,.04*m[1])),6e4),function(e,t){const o=e=>{const t=Math.hypot(...e);return e.map(e=>e/t)},a=(e,t)=>[e[1]*t[2]-e[2]*t[1],e[2]*t[0]-e[0]*t[2],e[0]*t[1]-e[1]*t[0]],n=o((i=t,e.map((e,t)=>e-i[t]))),u=o(a([0,1,0],n)),c=a(n,u),r=t=>-(t[0]*e[0]+t[1]*e[1]+t[2]*e[2]);var i;return[u[0],c[0],n[0],0,u[1],c[1],n[1],0,u[2],c[2],n[2],0,r(u),r(c),r(n),1]}(m,Z.target)),v=f(+G.sun.value),h=f(V[j].sunAz),p=[Math.cos(v)*Math.cos(h),Math.sin(v),Math.cos(v)*Math.sin(h)],d=J.p,w={"3_uSun":p,f_uExp:1,f_uFogD:4200,"3_uCam":m};a.bindFramebuffer(a.FRAMEBUFFER,null),a.viewport(0,0,r,i),a.enable(a.DEPTH_TEST),a.clear(a.DEPTH_BUFFER_BIT);const E=Math.min(2,Math.max(.05,.03*m[1])),y=J.ew?J.window(e):[0,0];S(z.ocean,{m_uVP:x,f_uR0:E,f_uR1:2e4,f_uRings:320,f_uStep:Math.log(2e4/E)/318,f_uLam:d.lambda,i_uEw:J.ew,"2_uWO":y,f_uWE:J.E,f_uWM:J.M,i_uFw:1,t_uF:J.foam(),"2_uFO":J.foamO,f_uFE:J.E,f_uFM:J.Mf,f_uThr:d.wcThr,f_uRg:1e-4*Math.min(2.5,J.sea.Hs)},w,J.casU,J.ewU),a.bindVertexArray(ue),a.drawElements(a.TRIANGLES,ce,a.UNSIGNED_INT,0),d.obs.forEach(t=>{const[o,n]=J.obstacle(t,e),u="disc"===t.kind,c=u?2.2*t.a:7;S(z.obj,{m_uVP:x,"3_uOff":[y[0]+o,-12,y[1]+n],"3_uScale":[t.a,c+12,u?t.a:t.b],"3_uCol":u?[.85,.22,.05]:[.34,.33,.31],i_uBands:u},w);const r=u?ie:se;a.bindVertexArray(r.vao),a.drawArrays(a.TRIANGLES,0,r.n)}),a.depthFunc(a.LEQUAL),a.depthMask(!1),S(z.bg,{m_uInv:fe(x),"2_uRes":[r,i]},w),a.bindVertexArray(P),a.drawArrays(a.TRIANGLES,0,3),a.depthMask(!0),a.depthFunc(a.LESS)}($-J.tFirst),n-de>250&&(e("clock").textContent=`t ${$.toFixed(1)} s`,de=n)})})()</script>