layershift 0.2.2 → 0.3.0

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.
Files changed (36) hide show
  1. package/README.md +14 -0
  2. package/dist/components/layershift.js +1783 -654
  3. package/dist/npm/layershift.es.js +5154 -2569
  4. package/dist/types/components/layershift/layershift-element.d.ts +2 -1
  5. package/dist/types/components/layershift/layershift-element.d.ts.map +1 -1
  6. package/dist/types/components/layershift/portal-element.d.ts +2 -1
  7. package/dist/types/components/layershift/portal-element.d.ts.map +1 -1
  8. package/dist/types/components/layershift/types.d.ts +8 -0
  9. package/dist/types/components/layershift/types.d.ts.map +1 -1
  10. package/dist/types/gpu-backend.d.ts +37 -0
  11. package/dist/types/gpu-backend.d.ts.map +1 -0
  12. package/dist/types/jfa-distance-field.d.ts +78 -0
  13. package/dist/types/jfa-distance-field.d.ts.map +1 -0
  14. package/dist/types/parallax-renderer-webgpu.d.ts +103 -0
  15. package/dist/types/parallax-renderer-webgpu.d.ts.map +1 -0
  16. package/dist/types/parallax-renderer.d.ts +54 -91
  17. package/dist/types/parallax-renderer.d.ts.map +1 -1
  18. package/dist/types/portal-renderer-webgpu.d.ts +199 -0
  19. package/dist/types/portal-renderer-webgpu.d.ts.map +1 -0
  20. package/dist/types/portal-renderer.d.ts +63 -65
  21. package/dist/types/portal-renderer.d.ts.map +1 -1
  22. package/dist/types/precomputed-depth.d.ts +9 -51
  23. package/dist/types/precomputed-depth.d.ts.map +1 -1
  24. package/dist/types/quality.d.ts +95 -0
  25. package/dist/types/quality.d.ts.map +1 -0
  26. package/dist/types/render-pass-webgpu.d.ts +76 -0
  27. package/dist/types/render-pass-webgpu.d.ts.map +1 -0
  28. package/dist/types/render-pass.d.ts +171 -0
  29. package/dist/types/render-pass.d.ts.map +1 -0
  30. package/dist/types/renderer-base.d.ts +124 -0
  31. package/dist/types/renderer-base.d.ts.map +1 -0
  32. package/dist/types/webgl-utils.d.ts +29 -0
  33. package/dist/types/webgl-utils.d.ts.map +1 -0
  34. package/dist/types/webgpu-utils.d.ts +42 -0
  35. package/dist/types/webgpu-utils.d.ts.map +1 -0
  36. package/package.json +24 -4
@@ -1,196 +1,456 @@
1
- var Layershift=(function(et){"use strict";var ut=typeof document<"u"?document.currentScript:null;class it{worker;currentBuffer;pendingTimeSec=null;workerBusy=!1;disposed=!1;constructor(t,e){this.worker=t,this.currentBuffer=new Uint8Array(e)}static async create(t,e,i){const o=new Worker(new URL("/assets/depth-worker-CMcEa805.js",ut&&ut.tagName.toUpperCase()==="SCRIPT"&&ut.src||new URL("layershift.js",document.baseURI).href),{type:"module"}),n=e*i,s=new it(o,n),h=t.frames.map(a=>{const l=new Uint8Array(a.length);return l.set(a),l.buffer});return await new Promise((a,l)=>{const u=setTimeout(()=>l(new Error("Worker init timeout")),1e4);o.onmessage=f=>{f.data.type==="ready"&&(clearTimeout(u),a())},o.onerror=f=>{clearTimeout(u),l(f)},o.postMessage({type:"init",frames:h,meta:{frameCount:t.meta.frameCount,fps:t.meta.fps,width:t.meta.width,height:t.meta.height},targetWidth:e,targetHeight:i},h)}),o.onmessage=a=>{if(a.data.type==="result"&&(s.currentBuffer=a.data.data,s.workerBusy=!1,s.pendingTimeSec!==null)){const l=s.pendingTimeSec;s.pendingTimeSec=null,s.requestSample(l)}},s}sample(t){return this.requestSample(t),this.currentBuffer}requestSample(t){if(!this.disposed){if(this.workerBusy){this.pendingTimeSec=t;return}this.workerBusy=!0,this.worker.postMessage({type:"sample",timeSec:t})}}dispose(){this.disposed=!0,this.worker.terminate()}}class gt{constructor(t,e,i){this.depthData=t,this.targetWidth=e,this.targetHeight=i;const o=t.meta.width*t.meta.height,n=e*i;this.interpolatedDepth=new Float32Array(o),this.bilateralOutput=new Float32Array(o),this.resizedDepth=new Float32Array(n),this.uint8Output=new Uint8Array(n)}interpolatedDepth;resizedDepth;bilateralOutput;uint8Output;lastFrameIndex=-1;lastNextFrameIndex=-1;lastLerpFactor=-1;sample(t){const e=j(t*this.depthData.meta.fps,0,this.depthData.meta.frameCount-1),i=Math.floor(e),o=Math.min(i+1,this.depthData.meta.frameCount-1),n=e-i,s=i!==this.lastFrameIndex||o!==this.lastNextFrameIndex,h=Math.abs(n-this.lastLerpFactor)>.001;if(!s&&!h)return this.uint8Output;this.lastFrameIndex=i,this.lastNextFrameIndex=o,this.lastLerpFactor=n;const a=1-n,l=this.depthData.frames[i],u=this.depthData.frames[o];for(let g=0;g<this.interpolatedDepth.length;g+=1)this.interpolatedDepth[g]=(l[g]*a+u[g]*n)/255;kt(this.interpolatedDepth,this.depthData.meta.width,this.depthData.meta.height,this.bilateralOutput);const f=this.targetWidth!==this.depthData.meta.width||this.targetHeight!==this.depthData.meta.height;f&&Ot(this.bilateralOutput,this.depthData.meta.width,this.depthData.meta.height,this.targetWidth,this.targetHeight,this.resizedDepth);const c=f?this.resizedDepth:this.bilateralOutput;for(let g=0;g<this.uint8Output.length;g+=1)this.uint8Output[g]=c[g]*255+.5|0;return this.uint8Output}}async function vt(r,t,e){const[i,o]=await Promise.all([Mt(t),_t(r)]);return It(o,i)}async function Mt(r){const t=await fetch(r);if(!t.ok)throw new Error(`Failed to fetch depth metadata (${t.status} ${t.statusText}).`);const e=await t.json();return Vt(e),{frameCount:e.frameCount,fps:e.fps,width:e.width,height:e.height,sourceFps:e.sourceFps}}async function _t(r,t){const e=await fetch(r);if(!e.ok)throw new Error(`Failed to fetch depth data (${e.status} ${e.statusText}).`);e.headers.get("content-length");const i=e.body;if(!i)return new Uint8Array(await e.arrayBuffer());const o=[];let n=0;const s=i.getReader();for(;;){const{done:l,value:u}=await s.read();if(l)break;u&&(o.push(u),n+=u.byteLength)}const h=new Uint8Array(n);let a=0;for(const l of o)h.set(l,a),a+=l.byteLength;return h}function It(r,t){if(r.byteLength<4)throw new Error("Depth data binary is missing the frame-count header.");const i=new DataView(r.buffer,r.byteOffset,r.byteLength).getUint32(0,!0),o=t.width*t.height,n=4+i*o;if(r.byteLength!==n)throw new Error(`Depth data byte length mismatch. Expected ${n} bytes, received ${r.byteLength}.`);if(i!==t.frameCount)throw new Error(`Depth frame count mismatch between metadata (${t.frameCount}) and binary header (${i}).`);const s=r.subarray(4),h=new Array(i);for(let a=0;a<i;a+=1){const l=a*o;h[a]=s.subarray(l,l+o)}return{meta:t,frames:h}}function Vt(r){if(!r||typeof r.frameCount!="number"||typeof r.fps!="number"||typeof r.width!="number"||typeof r.height!="number"||typeof r.sourceFps!="number")throw new Error("Depth metadata is malformed.");if(!Number.isFinite(r.frameCount)||!Number.isFinite(r.fps)||!Number.isFinite(r.width)||!Number.isFinite(r.height)||!Number.isFinite(r.sourceFps)||r.frameCount<=0||r.fps<=0||r.width<=0||r.height<=0||r.sourceFps<=0)throw new Error("Depth metadata contains invalid numeric values.")}function Ot(r,t,e,i,o,n){const s=t/i,h=e/o;for(let a=0;a<o;a+=1){const l=(a+.5)*h-.5,u=j(Math.floor(l),0,e-1),f=j(u+1,0,e-1),c=l-u;for(let g=0;g<i;g+=1){const d=(g+.5)*s-.5,m=j(Math.floor(d),0,t-1),U=j(m+1,0,t-1),y=d-m,E=r[u*t+m],p=r[u*t+U],v=r[f*t+m],T=r[f*t+U],x=E+(p-E)*y,A=v+(T-v)*y;n[a*i+g]=x+(A-x)*c}}}function kt(r,t,e,i){for(let s=0;s<e;s+=1)for(let h=0;h<t;h+=1){const a=s*t+h,l=r[a];let u=1,f=l;for(let c=-2;c<=2;c+=1){const g=s+c;if(!(g<0||g>=e))for(let d=-2;d<=2;d+=1){if(d===0&&c===0)continue;const m=h+d;if(m<0||m>=t)continue;const U=r[g*t+m],y=d*d+c*c,E=U-l,p=Math.exp(-y/2.25-E*E/.01);u+=p,f+=U*p}}i[a]=f/u}}function j(r,t,e){return Math.min(e,Math.max(t,r))}const Bt={parallaxStrength:.05,contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4,pomSteps:16,overscanPadding:.08};function Ht(r,t,e){const i=new Float32Array(256);if(r.length===0||t<=0||e<=0)return xt(i);const o=Nt(r.length),n=t*e;let s=0;const h=new Uint32Array(256);for(const T of o){const x=r[T],A=Math.min(x.length,n);for(let S=0;S<A;S+=1)h[x[S]]+=1;s+=A}if(s===0)return xt(i);const a=1/s;for(let T=0;T<256;T+=1)i[T]=h[T]*a;const l=new Float32Array(256);l[0]=i[0];for(let T=1;T<256;T+=1)l[T]=l[T-1]+i[T];const u=Y(l,.05),f=Y(l,.25),c=Y(l,.5),g=Y(l,.75),d=Y(l,.95);let m=0;for(let T=0;T<256;T+=1)m+=T/255*i[T];let U=0;for(let T=0;T<256;T+=1){const x=T/255-m;U+=i[T]*x*x}const y=Math.sqrt(U),E=d-u,p=g-f,v=zt(i);return{mean:m,stdDev:y,p5:u,p25:f,median:c,p75:g,p95:d,effectiveRange:E,iqr:p,bimodality:v,histogram:i}}function Xt(r){if(r.effectiveRange<.05||r.stdDev<.02)return{...Bt};const t=r.effectiveRange-.5,e=r.bimodality-.4,i=V(.05-t*.03+e*.01,.035,.065),o=V(r.p5-.03,0,.25),n=V(r.p95+.03,.75,1),s=V((i-.03)/.05,0,1),h=V(.6-s*.25,.35,.6),a=V(.6-t*.2,.5,.7),l=V(.4+t*.2,.25,.5),u=16,f=V(i+.03,.06,.1);return{parallaxStrength:i,contrastLow:o,contrastHigh:n,verticalReduction:h,dofStart:a,dofStrength:l,pomSteps:u,overscanPadding:f}}function Nt(r){if(r<=0)return[];if(r===1)return[0];const t=r-1,e=[0,Math.floor(r/4),Math.floor(r/2),Math.floor(3*r/4),t],i=new Set,o=[];for(const n of e)i.has(n)||(i.add(n),o.push(n));return o}function Y(r,t){for(let e=0;e<256;e+=1)if(r[e]>=t)return e/255;return 1}function zt(r){const t=new Float32Array(256);for(let c=0;c<256;c+=1){let g=0,d=0;for(let m=c-2;m<=c+2;m+=1)m>=0&&m<256&&(g+=r[m],d+=1);t[c]=g/d}let e=0;for(let c=0;c<256;c+=1)e+=t[c];e/=256;const i=e*2,o=25,n=[];for(let c=1;c<255;c+=1)t[c]>t[c-1]&&t[c]>t[c+1]&&t[c]>=i&&n.push({bin:c,height:t[c]});if(t[0]>t[1]&&t[0]>=i&&n.push({bin:0,height:t[0]}),t[255]>t[254]&&t[255]>=i&&n.push({bin:255,height:t[255]}),n.sort((c,g)=>g.height-c.height),n.length<2)return 0;const s=n[0];let h=null;for(let c=1;c<n.length;c+=1)if(Math.abs(n[c].bin-s.bin)>=o){h=n[c];break}if(!h)return 0;const a=Math.min(s.bin,h.bin),l=Math.max(s.bin,h.bin);let u=1/0;for(let c=a;c<=l;c+=1)t[c]<u&&(u=t[c]);const f=Math.min(s.height,h.height);return f<=0?0:V(1-u/f,0,1)}function xt(r){return{mean:0,stdDev:0,p5:0,p25:0,median:0,p75:0,p95:0,effectiveRange:0,iqr:0,bimodality:0,histogram:r}}function V(r,t,e){return Math.min(e,Math.max(t,r))}const Wt=`#version 300 es
2
- in vec2 aPosition;
3
-
4
- // UV coordinates for cover-fit + overscan.
5
- // Computed on the CPU and passed as a uniform to avoid
6
- // recreating geometry on every resize.
7
- uniform vec2 uUvOffset;
8
- uniform vec2 uUvScale;
9
-
10
- out vec2 vUv;
11
- out vec2 vScreenUv;
12
-
13
- void main() {
14
- // Map from clip space [-1,1] to [0,1], then apply cover-fit transform
15
- vec2 baseUv = aPosition * 0.5 + 0.5;
16
- vUv = baseUv * uUvScale + uUvOffset;
17
- // Screen-space UV always [0,1] used for vignette and edge fade
18
- // which should operate on screen position, not texture coordinates.
19
- vScreenUv = baseUv;
20
- gl_Position = vec4(aPosition, 0.0, 1.0);
1
+ var Layershift=(function(ne){"use strict";class be{constructor(e){this.depthData=e;const t=e.meta.width*e.meta.height;this.uint8Output=new Uint8Array(t)}uint8Output;lastFrameIndex=-1;lastNextFrameIndex=-1;lastLerpFactor=-1;sample(e){const t=nt(e*this.depthData.meta.fps,0,this.depthData.meta.frameCount-1),i=Math.floor(t),n=Math.min(i+1,this.depthData.meta.frameCount-1),r=t-i,s=i!==this.lastFrameIndex||n!==this.lastNextFrameIndex,a=Math.abs(r-this.lastLerpFactor)>.001;if(!s&&!a)return this.uint8Output;this.lastFrameIndex=i,this.lastNextFrameIndex=n,this.lastLerpFactor=r;const l=1-r,u=this.depthData.frames[i],h=this.depthData.frames[n];for(let f=0;f<this.uint8Output.length;f+=1)this.uint8Output[f]=u[f]*l+h[f]*r+.5|0;return this.uint8Output}}async function Se(o,e,t){const[i,n]=await Promise.all([Qe(e),et(o)]);return tt(n,i)}async function Qe(o){const e=await fetch(o);if(!e.ok)throw new Error(`Failed to fetch depth metadata (${e.status} ${e.statusText}).`);const t=await e.json();return it(t),{frameCount:t.frameCount,fps:t.fps,width:t.width,height:t.height,sourceFps:t.sourceFps}}async function et(o,e){const t=await fetch(o);if(!t.ok)throw new Error(`Failed to fetch depth data (${t.status} ${t.statusText}).`);t.headers.get("content-length");const i=t.body;if(!i)return new Uint8Array(await t.arrayBuffer());const n=[];let r=0;const s=i.getReader();for(;;){const{done:u,value:h}=await s.read();if(u)break;h&&(n.push(h),r+=h.byteLength)}const a=new Uint8Array(r);let l=0;for(const u of n)a.set(u,l),l+=u.byteLength;return a}function tt(o,e){if(o.byteLength<4)throw new Error("Depth data binary is missing the frame-count header.");const i=new DataView(o.buffer,o.byteOffset,o.byteLength).getUint32(0,!0),n=e.width*e.height,r=4+i*n;if(o.byteLength!==r)throw new Error(`Depth data byte length mismatch. Expected ${r} bytes, received ${o.byteLength}.`);if(i!==e.frameCount)throw new Error(`Depth frame count mismatch between metadata (${e.frameCount}) and binary header (${i}).`);const s=o.subarray(4),a=new Array(i);for(let l=0;l<i;l+=1){const u=l*n;a[l]=s.subarray(u,u+n)}return{meta:e,frames:a}}function it(o){if(!o||typeof o.frameCount!="number"||typeof o.fps!="number"||typeof o.width!="number"||typeof o.height!="number"||typeof o.sourceFps!="number")throw new Error("Depth metadata is malformed.");if(!Number.isFinite(o.frameCount)||!Number.isFinite(o.fps)||!Number.isFinite(o.width)||!Number.isFinite(o.height)||!Number.isFinite(o.sourceFps)||o.frameCount<=0||o.fps<=0||o.width<=0||o.height<=0||o.sourceFps<=0)throw new Error("Depth metadata contains invalid numeric values.")}function nt(o,e,t){return Math.min(t,Math.max(e,o))}const rt={parallaxStrength:.05,contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4,pomSteps:16,overscanPadding:.08};function ot(o,e,t){const i=new Float32Array(256);if(o.length===0||e<=0||t<=0)return Te(i);const n=at(o.length),r=e*t;let s=0;const a=new Uint32Array(256);for(const S of n){const x=o[S],E=Math.min(x.length,r);for(let P=0;P<E;P+=1)a[x[P]]+=1;s+=E}if(s===0)return Te(i);const l=1/s;for(let S=0;S<256;S+=1)i[S]=a[S]*l;const u=new Float32Array(256);u[0]=i[0];for(let S=1;S<256;S+=1)u[S]=u[S-1]+i[S];const h=Z(u,.05),f=Z(u,.25),c=Z(u,.5),g=Z(u,.75),v=Z(u,.95);let d=0;for(let S=0;S<256;S+=1)d+=S/255*i[S];let A=0;for(let S=0;S<256;S+=1){const x=S/255-d;A+=i[S]*x*x}const b=Math.sqrt(A),y=v-h,p=g-f,m=lt(i);return{mean:d,stdDev:b,p5:h,p25:f,median:c,p75:g,p95:v,effectiveRange:y,iqr:p,bimodality:m,histogram:i}}function st(o){if(o.effectiveRange<.05||o.stdDev<.02)return{...rt};const e=o.effectiveRange-.5,t=o.bimodality-.4,i=V(.05-e*.03+t*.01,.035,.065),n=V(o.p5-.03,0,.25),r=V(o.p95+.03,.75,1),s=V((i-.03)/.05,0,1),a=V(.6-s*.25,.35,.6),l=V(.6-e*.2,.5,.7),u=V(.4+e*.2,.25,.5),h=16,f=V(i+.03,.06,.1);return{parallaxStrength:i,contrastLow:n,contrastHigh:r,verticalReduction:a,dofStart:l,dofStrength:u,pomSteps:h,overscanPadding:f}}function at(o){if(o<=0)return[];if(o===1)return[0];const e=o-1,t=[0,Math.floor(o/4),Math.floor(o/2),Math.floor(3*o/4),e],i=new Set,n=[];for(const r of t)i.has(r)||(i.add(r),n.push(r));return n}function Z(o,e){for(let t=0;t<256;t+=1)if(o[t]>=e)return t/255;return 1}function lt(o){const e=new Float32Array(256);for(let c=0;c<256;c+=1){let g=0,v=0;for(let d=c-2;d<=c+2;d+=1)d>=0&&d<256&&(g+=o[d],v+=1);e[c]=g/v}let t=0;for(let c=0;c<256;c+=1)t+=e[c];t/=256;const i=t*2,n=25,r=[];for(let c=1;c<255;c+=1)e[c]>e[c-1]&&e[c]>e[c+1]&&e[c]>=i&&r.push({bin:c,height:e[c]});if(e[0]>e[1]&&e[0]>=i&&r.push({bin:0,height:e[0]}),e[255]>e[254]&&e[255]>=i&&r.push({bin:255,height:e[255]}),r.sort((c,g)=>g.height-c.height),r.length<2)return 0;const s=r[0];let a=null;for(let c=1;c<r.length;c+=1)if(Math.abs(r[c].bin-s.bin)>=n){a=r[c];break}if(!a)return 0;const l=Math.min(s.bin,a.bin),u=Math.max(s.bin,a.bin);let h=1/0;for(let c=l;c<=u;c+=1)e[c]<h&&(h=e[c]);const f=Math.min(s.height,a.height);return f<=0?0:V(1-h/f,0,1)}function Te(o){return{mean:0,stdDev:0,p5:0,p25:0,median:0,p75:0,p95:0,effectiveRange:0,iqr:0,bimodality:0,histogram:o}}function V(o,e,t){return Math.min(t,Math.max(e,o))}function W(o,e,t){const i=o.createShader(e);if(!i)throw new Error("Failed to create shader.");if(o.shaderSource(i,t),o.compileShader(i),!o.getShaderParameter(i,o.COMPILE_STATUS)){const n=o.getShaderInfoLog(i)??"";throw o.deleteShader(i),new Error(`Shader compilation failed:
2
+ ${n}`)}return i}function de(o,e,t){const i=o.createProgram();if(!i)throw new Error("Failed to create program.");if(o.attachShader(i,e),o.attachShader(i,t),o.linkProgram(i),!o.getProgramParameter(i,o.LINK_STATUS)){const n=o.getProgramInfoLog(i)??"";throw o.deleteProgram(i),new Error(`Program linking failed:
3
+ ${n}`)}return o.detachShader(i,e),o.detachShader(i,t),o.deleteShader(e),o.deleteShader(t),i}function pe(o,e,t){const i={};for(const n of t)i[n]=o.getUniformLocation(e,n);return i}const ut=new Float32Array([-1,-1,1,-1,-1,1,1,1]);function ye(o,e){const t=o.createVertexArray();if(!t)throw new Error("Failed to create VAO.");o.bindVertexArray(t);const i=o.createBuffer();o.bindBuffer(o.ARRAY_BUFFER,i),o.bufferData(o.ARRAY_BUFFER,ut,o.STATIC_DRAW);const n=o.getAttribLocation(e,"aPosition");return o.enableVertexAttribArray(n),o.vertexAttribPointer(n,2,o.FLOAT,!1,0,0),o.bindVertexArray(null),t}class Ee{slots=new Map;nextUnit=0;register(e){if(this.slots.has(e))throw new Error(`TextureRegistry: slot '${e}' already registered.`);const t={name:e,unit:this.nextUnit++,texture:null};return this.slots.set(e,t),t}get(e){const t=this.slots.get(e);if(!t)throw new Error(`TextureRegistry: slot '${e}' not found.`);return t}disposeAll(e){for(const t of this.slots.values())t.texture&&(e.deleteTexture(t.texture),t.texture=null)}get size(){return this.slots.size}}function G(o,e,t,i,n){const r=W(o,o.VERTEX_SHADER,t),s=W(o,o.FRAGMENT_SHADER,i),a=de(o,r,s),l=pe(o,a,n);return{name:e,program:a,uniforms:l,dispose(u){u.deleteProgram(a)}}}const Pe={high:{dprCap:2,depthMaxDim:512,pomSteps:16,bilateralRadius:2,jfaDivisor:2},medium:{dprCap:1.5,depthMaxDim:512,pomSteps:16,bilateralRadius:2,jfaDivisor:2},low:{dprCap:1,depthMaxDim:256,pomSteps:8,bilateralRadius:1,jfaDivisor:4}};function ht(o){let e="unknown";const t=o.getExtension("WEBGL_debug_renderer_info");t&&(e=o.getParameter(t.UNMASKED_RENDERER_WEBGL)||"unknown");const i=o.getParameter(o.MAX_TEXTURE_SIZE),n=typeof navigator<"u"&&navigator.hardwareConcurrency||0,r=typeof navigator<"u"&&navigator.deviceMemory||0,s=typeof window<"u"&&window.devicePixelRatio||1,a=typeof screen<"u"?(screen.width||0)*(screen.height||0):0,l=typeof navigator<"u"&&("ontouchstart"in window||navigator.maxTouchPoints>0),u=a>0&&a<1920*1080;return{gpuRenderer:e,maxTextureSize:i,hardwareConcurrency:n,deviceMemory:r,devicePixelRatio:s,screenPixels:a,isMobile:l&&u}}const ct=["mali-4","mali-t","adreno 3","adreno 4","adreno 5","powervr sgx","intel hd graphics","intel uhd graphics","intel iris","llvmpipe","swiftshader","software"],ft=["nvidia","geforce","radeon rx","radeon pro","apple m","apple gpu","adreno 7","adreno 6","mali-g7","mali-g6"];function Ae(o){let e=0;const t=o.gpuRenderer.toLowerCase(),i=ct.some(r=>t.includes(r)),n=ft.some(r=>t.includes(r));return i&&(e-=30),n&&(e+=20),o.maxTextureSize>=16384?e+=10:o.maxTextureSize>=8192?e+=5:o.maxTextureSize<=4096&&(e-=15),o.hardwareConcurrency>=8?e+=5:o.hardwareConcurrency>=4?e+=0:o.hardwareConcurrency>0&&o.hardwareConcurrency<4&&(e-=10),o.deviceMemory>=8?e+=5:o.deviceMemory>=4?e+=0:o.deviceMemory>0&&o.deviceMemory<4&&(e-=15),o.isMobile&&(e-=10),e>=0?"high":e>=-25?"medium":"low"}function we(o,e){const t=e&&e!=="auto"?e:Ae(ht(o));return{tier:t,...Pe[t]}}function dt(o){const e=o.description||o.device||"unknown",t=8192,i=typeof navigator<"u"&&navigator.hardwareConcurrency||0,n=typeof navigator<"u"&&navigator.deviceMemory||0,r=typeof window<"u"&&window.devicePixelRatio||1,s=typeof screen<"u"?(screen.width||0)*(screen.height||0):0,a=typeof navigator<"u"&&("ontouchstart"in window||navigator.maxTouchPoints>0),l=s>0&&s<1920*1080;return{gpuRenderer:e,maxTextureSize:t,hardwareConcurrency:i,deviceMemory:n,devicePixelRatio:r,screenPixels:s,isMobile:a&&l}}function De(o,e){const t=e&&e!=="auto"?e:Ae(dt(o));return{tier:t,...Pe[t]}}class k{static RESIZE_DEBOUNCE_MS=100;canvas;container;depthWidth=0;depthHeight=0;sourceDepthWidth=0;sourceDepthHeight=0;depthSubsampleBuffer=null;videoAspect=1.7777777777777777;uvOffset=[0,0];uvScale=[1,1];readDepth=null;readInput=null;playbackVideo=null;onVideoFrame=null;animationFrameHandle=0;rvfcHandle=0;rvfcSupported=!1;resizeObserver=null;resizeTimer=null;qualityParams;constructor(e){this.container=e,this.canvas=document.createElement("canvas"),this.container.appendChild(this.canvas),this.canvas.addEventListener("webglcontextlost",this._handleContextLost),this.canvas.addEventListener("webglcontextrestored",this._handleContextRestored)}start(e,t,i,n){this.stop(),this.playbackVideo=e,this.readDepth=t,this.readInput=i,this.onVideoFrame=n??null,this.rvfcSupported=k.isRVFCSupported(),this.rvfcSupported&&(this.rvfcHandle=e.requestVideoFrameCallback(this._videoFrameLoop)),this.animationFrameHandle=window.requestAnimationFrame(this._rafLoop)}stop(){this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0),this.rvfcHandle&&this.playbackVideo&&(this.playbackVideo.cancelVideoFrameCallback(this.rvfcHandle),this.rvfcHandle=0),this.playbackVideo=null,this.readDepth=null,this.readInput=null,this.onVideoFrame=null,this.rvfcSupported=!1}dispose(){this.stop(),this.disposeRenderer(),this.canvas.removeEventListener("webglcontextlost",this._handleContextLost),this.canvas.removeEventListener("webglcontextrestored",this._handleContextRestored),this.canvas.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),window.removeEventListener("resize",this.scheduleResizeRecalculate),this.resizeTimer!==null&&(window.clearTimeout(this.resizeTimer),this.resizeTimer=null)}static isRVFCSupported(){return"requestVideoFrameCallback"in HTMLVideoElement.prototype}_rafLoop=()=>{this.animationFrameHandle=window.requestAnimationFrame(this._rafLoop),this.onRenderFrame()};_videoFrameLoop=(e,t)=>{const i=this.playbackVideo;if(!i)return;this.rvfcHandle=i.requestVideoFrameCallback(this._videoFrameLoop);const n=t.mediaTime??i.currentTime;this.onDepthUpdate(n),this.onVideoFrame&&this.onVideoFrame(n,t.presentedFrames??0)};_handleContextLost=e=>{e.preventDefault(),this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0)};_handleContextRestored=()=>{this.onContextRestored()};setupResizeHandling(){typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.scheduleResizeRecalculate()}),this.resizeObserver.observe(this.container)),window.addEventListener("resize",this.scheduleResizeRecalculate),this.recalculateViewportLayout()}scheduleResizeRecalculate=()=>{this.resizeTimer!==null&&window.clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.resizeTimer=null,this.recalculateViewportLayout()},k.RESIZE_DEBOUNCE_MS)};getViewportSize(){const e=Math.max(1,Math.round(this.container.clientWidth||window.innerWidth)),t=Math.max(1,Math.round(this.container.clientHeight||window.innerHeight));return{width:e,height:t}}clampDepthDimensions(e,t,i){this.sourceDepthWidth=e,this.sourceDepthHeight=t;let n=e,r=t;if(n>i||r>i){const s=i/Math.max(n,r);n=Math.max(1,Math.round(n*s)),r=Math.max(1,Math.round(r*s))}this.depthWidth=n,this.depthHeight=r,n!==e||r!==t?this.depthSubsampleBuffer=new Uint8Array(n*r):this.depthSubsampleBuffer=null}subsampleDepth(e){if(!this.depthSubsampleBuffer)return e;const t=this.depthSubsampleBuffer,i=this.sourceDepthWidth,n=this.depthWidth,r=this.depthHeight;for(let s=0;s<r;s++){const l=Math.min(Math.round(s*i/n),this.sourceDepthHeight-1)*i,u=s*n;for(let h=0;h<n;h++){const f=Math.min(Math.round(h*i/n),i-1);t[u+h]=e[l+f]}}return t}computeCoverFitUV(e,t){const{width:i,height:n}=this.getViewportSize(),r=i/n,s=e+t;let a=1,l=1;r>this.videoAspect?l=this.videoAspect/r:a=r/this.videoAspect;const u=1+s*2;a/=u,l/=u,this.uvOffset=[(1-a)/2,(1-l)/2],this.uvScale=[a,l]}}const pt=`#version 300 es
4
+ in vec2 aPosition;
5
+
6
+ // UV coordinates for cover-fit + overscan.
7
+ // Computed on the CPU and passed as a uniform to avoid
8
+ // recreating geometry on every resize.
9
+ uniform vec2 uUvOffset;
10
+ uniform vec2 uUvScale;
11
+
12
+ out vec2 vUv;
13
+ out vec2 vScreenUv;
14
+
15
+ void main() {
16
+ // Map from clip space [-1,1] to [0,1], then apply cover-fit transform
17
+ vec2 baseUv = aPosition * 0.5 + 0.5;
18
+ vUv = baseUv * uUvScale + uUvOffset;
19
+ // Screen-space UV always [0,1] -- used for vignette and edge fade
20
+ // which should operate on screen position, not texture coordinates.
21
+ vScreenUv = baseUv;
22
+ gl_Position = vec4(aPosition, 0.0, 1.0);
23
+ }
24
+ `,mt=`#version 300 es
25
+ in vec2 aPosition;
26
+ out vec2 vUv;
27
+
28
+ void main() {
29
+ vUv = aPosition * 0.5 + 0.5;
30
+ gl_Position = vec4(aPosition, 0.0, 1.0);
31
+ }
32
+ `,gt=`#version 300 es
33
+ precision highp float;
34
+
35
+ // BILATERAL_RADIUS is injected as a #define at compile time.
36
+ // Radius 2 -> 5x5 kernel (high/medium), radius 1 -> 3x3 kernel (low).
37
+
38
+ uniform sampler2D uRawDepth;
39
+ uniform vec2 uTexelSize;
40
+ uniform float uSpatialSigma2;
41
+
42
+ in vec2 vUv;
43
+ out vec4 fragColor;
44
+
45
+ void main() {
46
+ const float depthSigma2 = 0.01; // 0.1^2
47
+
48
+ float center = texture(uRawDepth, vUv).r;
49
+ float totalWeight = 1.0;
50
+ float totalDepth = center;
51
+
52
+ for (int dy = -BILATERAL_RADIUS; dy <= BILATERAL_RADIUS; dy++) {
53
+ for (int dx = -BILATERAL_RADIUS; dx <= BILATERAL_RADIUS; dx++) {
54
+ if (dx == 0 && dy == 0) continue;
55
+
56
+ vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
57
+ float neighbor = texture(uRawDepth, vUv + offset).r;
58
+
59
+ float spatialDist2 = float(dx * dx + dy * dy);
60
+ float depthDiff = neighbor - center;
61
+ float w = exp(-spatialDist2 / uSpatialSigma2 - (depthDiff * depthDiff) / depthSigma2);
62
+
63
+ totalWeight += w;
64
+ totalDepth += neighbor * w;
65
+ }
21
66
  }
22
- `,Gt=`#version 300 es
23
- precision highp float;
24
-
25
- // ---- Uniforms ----
26
-
27
- /** Color video frame, uploaded from HTMLVideoElement. */
28
- uniform sampler2D uImage;
29
-
30
- /**
31
- * Single-channel depth map (R channel, 0=near, 1=far).
32
- * Pre-filtered with a bilateral filter on the CPU before upload,
33
- * so a single texture() read gives smooth, edge-preserving depth.
34
- * Uploaded as R8 format (auto-normalized to [0,1]).
35
- */
36
- uniform sampler2D uDepth;
37
-
38
- /**
39
- * Current parallax input from mouse or gyroscope.
40
- * Range [-1, 1] for both x (horizontal) and y (vertical).
41
- */
42
- uniform vec2 uOffset;
43
-
44
- /** Parallax displacement magnitude in UV space (e.g. 0.05 = 5%). */
45
- uniform float uStrength;
46
-
47
- /** Whether to use POM ray-marching instead of basic displacement. */
48
- uniform bool uPomEnabled;
49
-
50
- /** Number of ray-march steps for POM (runtime-adjustable). */
51
- uniform int uPomSteps;
52
-
53
- /** Smoothstep lower bound for depth contrast curve (depth-adaptive). */
54
- uniform float uContrastLow;
55
-
56
- /** Smoothstep upper bound for depth contrast curve (depth-adaptive). */
57
- uniform float uContrastHigh;
58
-
59
- /** Y-axis displacement multiplier (depth-adaptive). */
60
- uniform float uVerticalReduction;
61
-
62
- /** Depth threshold where DOF blur ramp begins (depth-adaptive). */
63
- uniform float uDofStart;
64
67
 
65
- /** Maximum DOF blur blend factor (depth-adaptive). */
66
- uniform float uDofStrength;
68
+ fragColor = vec4(totalDepth / totalWeight, 0.0, 0.0, 1.0);
69
+ }
70
+ `,vt=`#version 300 es
71
+ precision highp float;
72
+
73
+ // ---- Uniforms ----
74
+
75
+ /** Color video frame, uploaded from HTMLVideoElement. */
76
+ uniform sampler2D uImage;
77
+
78
+ /**
79
+ * Single-channel depth map (R channel, 0=near, 1=far).
80
+ * Bilateral-filtered on the GPU via a dedicated render pass,
81
+ * so a single texture() read gives smooth, edge-preserving depth.
82
+ */
83
+ uniform sampler2D uDepth;
67
84
 
68
- /**
69
- * Texel size for video/image texture (1.0 / videoResolution).
70
- * Used by the depth-of-field effect to sample neighboring pixels.
71
- */
72
- uniform vec2 uImageTexelSize;
85
+ /**
86
+ * Current parallax input from mouse or gyroscope.
87
+ * Range [-1, 1] for both x (horizontal) and y (vertical).
88
+ */
89
+ uniform vec2 uOffset;
73
90
 
74
- // ---- Varyings ----
91
+ /** Parallax displacement magnitude in UV space (e.g. 0.05 = 5%). */
92
+ uniform float uStrength;
75
93
 
76
- /** Interpolated texture coordinates from vertex shader (cover-fit transformed). */
77
- in vec2 vUv;
94
+ /** Whether to use POM ray-marching instead of basic displacement. */
95
+ uniform bool uPomEnabled;
96
+
97
+ /** Number of ray-march steps for POM (runtime-adjustable). */
98
+ uniform int uPomSteps;
99
+
100
+ /** Smoothstep lower bound for depth contrast curve (depth-adaptive). */
101
+ uniform float uContrastLow;
102
+
103
+ /** Smoothstep upper bound for depth contrast curve (depth-adaptive). */
104
+ uniform float uContrastHigh;
105
+
106
+ /** Y-axis displacement multiplier (depth-adaptive). */
107
+ uniform float uVerticalReduction;
108
+
109
+ /** Depth threshold where DOF blur ramp begins (depth-adaptive). */
110
+ uniform float uDofStart;
111
+
112
+ /** Maximum DOF blur blend factor (depth-adaptive). */
113
+ uniform float uDofStrength;
114
+
115
+ /**
116
+ * Texel size for video/image texture (1.0 / videoResolution).
117
+ * Used by the depth-of-field effect to sample neighboring pixels.
118
+ */
119
+ uniform vec2 uImageTexelSize;
120
+
121
+ // ---- Varyings ----
122
+
123
+ /** Interpolated texture coordinates from vertex shader (cover-fit transformed). */
124
+ in vec2 vUv;
125
+
126
+ /** Screen-space UV [0,1] -- always covers the full viewport. */
127
+ in vec2 vScreenUv;
128
+
129
+ /** Fragment output color. */
130
+ out vec4 fragColor;
131
+
132
+ // ---- Helper functions ----
133
+
134
+ /**
135
+ * Compute an edge fade factor that reduces displacement near UV
136
+ * boundaries.
137
+ */
138
+ float edgeFade(vec2 uv) {
139
+ float margin = uStrength * 1.5;
140
+ float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
141
+ float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
142
+ return fadeX * fadeY;
143
+ }
144
+
145
+ /**
146
+ * Compute a subtle vignette darkening factor.
147
+ */
148
+ float vignette(vec2 uv) {
149
+ float dist = length(uv - 0.5) * 1.4;
150
+ return 1.0 - pow(dist, 2.5);
151
+ }
152
+
153
+ // ---- Displacement functions ----
154
+
155
+ /**
156
+ * Basic UV displacement with edge fade.
157
+ */
158
+ vec2 basicDisplace(vec2 uv) {
159
+ float depth = texture(uDepth, uv).r;
160
+ depth = smoothstep(uContrastLow, uContrastHigh, depth);
161
+ float displacement = (1.0 - depth) * uStrength;
162
+ displacement *= edgeFade(uv);
163
+ vec2 offset = uOffset * displacement;
164
+ offset.y *= uVerticalReduction;
165
+ return uv + offset;
166
+ }
167
+
168
+ /**
169
+ * Parallax Occlusion Mapping (POM) ray-marching displacement.
170
+ */
171
+ vec2 pomDisplace(vec2 uv) {
172
+ float layerDepth = 1.0 / float(uPomSteps);
173
+
174
+ vec2 scaledOffset = uOffset;
175
+ scaledOffset.y *= uVerticalReduction;
176
+
177
+ vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
178
+
179
+ float currentLayerDepth = 0.0;
180
+ vec2 currentUV = uv;
181
+
182
+ float fade = edgeFade(uv);
183
+
184
+ for (int i = 0; i < MAX_POM_STEPS; i++) {
185
+ if (i >= uPomSteps) break;
186
+
187
+ float rawDepth = texture(uDepth, currentUV).r;
188
+ rawDepth = smoothstep(uContrastLow, uContrastHigh, rawDepth);
189
+ float depthAtUV = 1.0 - rawDepth;
78
190
 
79
- /** Screen-space UV [0,1] — always covers the full viewport. */
80
- in vec2 vScreenUv;
81
-
82
- /** Fragment output color. */
83
- out vec4 fragColor;
84
-
85
- // ---- Helper functions ----
191
+ if (currentLayerDepth > depthAtUV) {
192
+ vec2 prevUV = currentUV - deltaUV;
193
+ float prevLayerDepth = currentLayerDepth - layerDepth;
194
+ float prevRaw = texture(uDepth, prevUV).r;
195
+ prevRaw = smoothstep(uContrastLow, uContrastHigh, prevRaw);
196
+ float prevDepthAtUV = 1.0 - prevRaw;
197
+
198
+ float afterDepth = depthAtUV - currentLayerDepth;
199
+ float beforeDepth = prevDepthAtUV - prevLayerDepth;
200
+ float t = afterDepth / (afterDepth - beforeDepth);
201
+
202
+ vec2 hitUV = mix(currentUV, prevUV, t);
203
+ return mix(uv, hitUV, fade);
204
+ }
86
205
 
87
- /**
88
- * Compute an edge fade factor that reduces displacement near UV
89
- * boundaries.
90
- */
91
- float edgeFade(vec2 uv) {
92
- float margin = uStrength * 1.5;
93
- float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
94
- float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
95
- return fadeX * fadeY;
206
+ currentUV += deltaUV;
207
+ currentLayerDepth += layerDepth;
96
208
  }
97
209
 
98
- /**
99
- * Compute a subtle vignette darkening factor.
100
- */
101
- float vignette(vec2 uv) {
102
- float dist = length(uv - 0.5) * 1.4;
103
- return 1.0 - pow(dist, 2.5);
104
- }
210
+ return mix(uv, currentUV, fade);
211
+ }
212
+
213
+ // ---- Main ----
214
+
215
+ void main() {
216
+ vec2 displaced = uPomEnabled ? pomDisplace(vUv) : basicDisplace(vUv);
217
+ displaced = clamp(displaced, vec2(0.0), vec2(1.0));
218
+
219
+ vec4 color = texture(uImage, displaced);
220
+
221
+ // Depth-of-field hint
222
+ float dofDepth = texture(uDepth, displaced).r;
223
+ float dof = smoothstep(uDofStart, 1.0, dofDepth) * uDofStrength;
224
+ vec4 blurred = (
225
+ texture(uImage, displaced + vec2( uImageTexelSize.x, 0.0)) +
226
+ texture(uImage, displaced + vec2(-uImageTexelSize.x, 0.0)) +
227
+ texture(uImage, displaced + vec2( 0.0, uImageTexelSize.y)) +
228
+ texture(uImage, displaced + vec2( 0.0, -uImageTexelSize.y))
229
+ ) * 0.25;
230
+ color = mix(color, blurred, dof);
231
+
232
+ // Vignette (screen-space, not texture-space)
233
+ color.rgb *= vignette(vScreenUv);
234
+
235
+ fragColor = color;
236
+ }
237
+ `,J={contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4},xt=["uRawDepth","uTexelSize","uSpatialSigma2"],bt={2:2.25,1:.5625};function St(o,e){const t=gt.replace("#version 300 es",`#version 300 es
238
+ #define BILATERAL_RADIUS ${e}`),i=W(o,o.VERTEX_SHADER,mt),n=W(o,o.FRAGMENT_SHADER,t),r=de(o,i,n),s=pe(o,r,xt),a=bt[e]??2.25;let l=null;const u={name:"bilateral-filter",program:r,uniforms:s,fbo:null,outputs:[],width:0,height:0,resize(h,f,c){},initFBO(h,f,c,g){l&&h.deleteFramebuffer(l),u.width=c,u.height=g,l=h.createFramebuffer(),u.fbo=l,h.bindFramebuffer(h.FRAMEBUFFER,l),h.framebufferTexture2D(h.FRAMEBUFFER,h.COLOR_ATTACHMENT0,h.TEXTURE_2D,f,0),h.bindFramebuffer(h.FRAMEBUFFER,null),h.useProgram(r),h.uniform1i(s.uRawDepth,2),h.uniform2f(s.uTexelSize,1/c,1/g),h.uniform1f(s.uSpatialSigma2,a)},execute(h,f,c,g,v,d,A,b){l&&(h.activeTexture(h.TEXTURE2),h.bindTexture(h.TEXTURE_2D,c),h.texSubImage2D(h.TEXTURE_2D,0,0,0,v,d,h.RED,h.UNSIGNED_BYTE,g),h.bindFramebuffer(h.FRAMEBUFFER,l),h.viewport(0,0,v,d),h.useProgram(r),h.bindVertexArray(f),h.drawArrays(h.TRIANGLE_STRIP,0,4),h.bindFramebuffer(h.FRAMEBUFFER,null),h.viewport(0,0,A,b))},dispose(h){l&&(h.deleteFramebuffer(l),l=null,u.fbo=null),h.deleteProgram(r)}};return u}const Tt=["uImage","uDepth","uOffset","uStrength","uPomEnabled","uPomSteps","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uUvOffset","uUvScale"],yt=64;function Et(o){const e=vt.replace("#version 300 es",`#version 300 es
239
+ #define MAX_POM_STEPS ${yt}`),t=W(o,o.VERTEX_SHADER,pt),i=W(o,o.FRAGMENT_SHADER,e),n=de(o,t,i),r=pe(o,n,Tt);return{name:"parallax",program:n,uniforms:r,setStaticUniforms(s,a,l,u){s.useProgram(n),s.uniform1i(r.uImage,0),s.uniform1i(r.uDepth,1),s.uniform1f(r.uStrength,a.parallaxStrength),s.uniform1i(r.uPomEnabled,a.pomEnabled?1:0),s.uniform1i(r.uPomSteps,a.pomSteps),s.uniform1f(r.uContrastLow,a.contrastLow),s.uniform1f(r.uContrastHigh,a.contrastHigh),s.uniform1f(r.uVerticalReduction,a.verticalReduction),s.uniform1f(r.uDofStart,a.dofStart),s.uniform1f(r.uDofStrength,a.dofStrength),s.uniform2f(r.uImageTexelSize,1/l,1/u)},updateUvTransform(s,a,l){s.useProgram(n),s.uniform2f(r.uUvOffset,a[0],a[1]),s.uniform2f(r.uUvScale,l[0],l[1])},dispose(s){s.deleteProgram(n)}}}class Pt extends k{gl=null;quadVao=null;bilateralPass=null;parallaxPass=null;textures=new Ee;videoSlot;filteredDepthSlot;rawDepthSlot;config;constructor(e,t){super(e),this.videoSlot=this.textures.register("video"),this.filteredDepthSlot=this.textures.register("filteredDepth"),this.rawDepthSlot=this.textures.register("rawDepth"),this.config={parallaxStrength:t.parallaxStrength,pomEnabled:t.pomEnabled,pomSteps:t.pomSteps,overscanPadding:t.overscanPadding,contrastLow:t.contrastLow??J.contrastLow,contrastHigh:t.contrastHigh??J.contrastHigh,verticalReduction:t.verticalReduction??J.verticalReduction,dofStart:t.dofStart??J.dofStart,dofStrength:t.dofStrength??J.dofStrength};const i=this.canvas.getContext("webgl2",{antialias:!1,alpha:!1,desynchronized:!0,powerPreference:"high-performance"});if(!i)throw new Error("WebGL 2 is not supported.");this.gl=i,this.qualityParams=we(i,t.quality),"drawingBufferColorSpace"in i&&(i.drawingBufferColorSpace="srgb"),i.clearColor(0,0,0,1),i.pixelStorei(i.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.setupResizeHandling()}initialize(e,t,i){const n=this.gl;n&&(this.disposeTextures(),this.videoAspect=e.videoWidth/e.videoHeight,this.clampDepthDimensions(t,i,this.qualityParams.depthMaxDim),this.videoSlot.texture=n.createTexture(),n.activeTexture(n.TEXTURE0+this.videoSlot.unit),n.bindTexture(n.TEXTURE_2D,this.videoSlot.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),this.rawDepthSlot.texture=n.createTexture(),n.activeTexture(n.TEXTURE0+this.rawDepthSlot.unit),n.bindTexture(n.TEXTURE_2D,this.rawDepthSlot.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texStorage2D(n.TEXTURE_2D,1,n.R8,t,i),this.filteredDepthSlot.texture=n.createTexture(),n.activeTexture(n.TEXTURE0+this.filteredDepthSlot.unit),n.bindTexture(n.TEXTURE_2D,this.filteredDepthSlot.texture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texStorage2D(n.TEXTURE_2D,1,n.R8,t,i),this.bilateralPass&&this.filteredDepthSlot.texture&&this.bilateralPass.initFBO(n,this.filteredDepthSlot.texture,t,i),this.parallaxPass&&this.parallaxPass.setStaticUniforms(n,this.config,e.videoWidth,e.videoHeight),this.recalculateViewportLayout())}initGPUResources(){const e=this.gl;e&&(this.bilateralPass=St(e,this.qualityParams.bilateralRadius),this.parallaxPass=Et(e),this.quadVao=ye(e,this.parallaxPass.program),e.disable(e.DEPTH_TEST))}onRenderFrame(){const e=this.gl,t=this.playbackVideo;if(!(!e||!this.parallaxPass||!this.quadVao)&&!(!t||t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)){if(e.useProgram(this.parallaxPass.program),e.activeTexture(e.TEXTURE0+this.videoSlot.unit),e.bindTexture(e.TEXTURE_2D,this.videoSlot.texture),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),this.rvfcSupported||this.onDepthUpdate(t.currentTime),this.readInput){const i=this.readInput();e.uniform2f(this.parallaxPass.uniforms.uOffset,-i.x,i.y)}e.bindVertexArray(this.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4)}}onDepthUpdate(e){const t=this.gl;if(!t||!this.readDepth||!this.rawDepthSlot.texture||!this.bilateralPass)return;const i=this.subsampleDepth(this.readDepth(e));this.bilateralPass.execute(t,this.quadVao,this.rawDepthSlot.texture,i,this.depthWidth,this.depthHeight,this.canvas.width,this.canvas.height)}recalculateViewportLayout(){const e=this.gl;if(!e)return;const{width:t,height:i}=this.getViewportSize(),n=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),r=Math.round(t*n),s=Math.round(i*n);(this.canvas.width!==r||this.canvas.height!==s)&&(this.canvas.width=r,this.canvas.height=s,e.viewport(0,0,r,s)),this.computeCoverFitUV(this.config.parallaxStrength,this.config.overscanPadding),this.parallaxPass&&this.parallaxPass.updateUvTransform(e,this.uvOffset,this.uvScale)}disposeRenderer(){this.disposeTextures(),this.disposeGPUResources(),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null)}onContextRestored(){const e=this.canvas.getContext("webgl2");e&&(this.gl=e,e.clearColor(0,0,0,1),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.playbackVideo&&this.depthWidth>0&&this.initialize(this.playbackVideo,this.depthWidth,this.depthHeight),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(()=>this.onRenderFrame())))}disposeTextures(){const e=this.gl;e&&this.textures.disposeAll(e)}disposeGPUResources(){const e=this.gl;e&&(this.bilateralPass&&(this.bilateralPass.dispose(e),this.bilateralPass=null),this.parallaxPass&&(this.parallaxPass.dispose(e),this.parallaxPass=null),this.quadVao&&(e.deleteVertexArray(this.quadVao),this.quadVao=null))}}const Ue=new Float32Array([-1,-1,1,-1,-1,1,1,1]);function Re(o){const e=o.createBuffer({size:Ue.byteLength,usage:GPUBufferUsage.VERTEX|GPUBufferUsage.COPY_DST,mappedAtCreation:!0});return new Float32Array(e.getMappedRange()).set(Ue),e.unmap(),e}function me(o,e){const t=o.createBuffer({size:e.byteLength,usage:GPUBufferUsage.VERTEX|GPUBufferUsage.COPY_DST,mappedAtCreation:!0});return new Float32Array(t.getMappedRange()).set(e),t.unmap(),t}function At(o,e){const t=o.createBuffer({size:e.byteLength,usage:GPUBufferUsage.INDEX|GPUBufferUsage.COPY_DST,mappedAtCreation:!0});return new Uint16Array(t.getMappedRange()).set(e),t.unmap(),t}function B(o,e){const t=Math.ceil(e/16)*16;return o.createBuffer({size:t,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST})}function Fe(o){return o.createSampler({magFilter:"linear",minFilter:"linear",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge"})}function wt(o){return o.createSampler({magFilter:"nearest",minFilter:"nearest",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge"})}function Le(o,e,t,i=!0){o.queue.copyExternalImageToTexture({source:t,flipY:i},{texture:e},[t.videoWidth,t.videoHeight])}const N={arrayStride:8,attributes:[{shaderLocation:0,offset:0,format:"float32x2"}]},Be={arrayStride:8,attributes:[{shaderLocation:0,offset:0,format:"float32x2"}]},Dt={arrayStride:16,attributes:[{shaderLocation:0,offset:0,format:"float32x2"},{shaderLocation:1,offset:8,format:"float32x2"}]},Ut={arrayStride:24,attributes:[{shaderLocation:0,offset:0,format:"float32x2"},{shaderLocation:1,offset:8,format:"float32x3"},{shaderLocation:2,offset:20,format:"float32"}]},Rt=`// Parallax vertex shader — fullscreen quad pass-through with cover-fit UV transform.
240
+
241
+ struct Uniforms {
242
+ uvOffset: vec2f,
243
+ uvScale: vec2f,
244
+ };
245
+
246
+ @group(0) @binding(0) var<uniform> u: Uniforms;
247
+
248
+ struct VertexOutput {
249
+ @builtin(position) position: vec4f,
250
+ @location(0) uv: vec2f,
251
+ @location(1) screenUv: vec2f,
252
+ };
253
+
254
+ @vertex
255
+ fn vs_main(@location(0) aPosition: vec2f) -> VertexOutput {
256
+ var out: VertexOutput;
257
+ let baseUv = aPosition * 0.5 + 0.5;
258
+ out.uv = baseUv * u.uvScale + u.uvOffset;
259
+ out.screenUv = baseUv;
260
+ out.position = vec4f(aPosition, 0.0, 1.0);
261
+ return out;
262
+ }
263
+ `,Ft=`// Parallax fragment shader — per-pixel depth-based displacement with POM, DOF, vignette.
264
+ // MAX_POM_STEPS is an override constant set at pipeline creation.
265
+ //
266
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
267
+ // because POM ray-marching requires texture reads in non-uniform control flow
268
+ // (loop iterations and conditional branches that depend on texture values).
269
+ // WGSL requires textureSample to be called only from uniform control flow.
270
+
271
+ override MAX_POM_STEPS: i32 = 64;
272
+
273
+ struct Uniforms {
274
+ offset: vec2f,
275
+ strength: f32,
276
+ pomEnabled: u32, // bool as u32 (0 = false, 1 = true)
277
+ pomSteps: i32,
278
+ contrastLow: f32,
279
+ contrastHigh: f32,
280
+ verticalReduction: f32,
281
+ dofStart: f32,
282
+ dofStrength: f32,
283
+ imageTexelSize: vec2f,
284
+ };
285
+
286
+ // Binding 0 is the vertex uniform buffer (uvOffset, uvScale).
287
+ @group(0) @binding(1) var<uniform> u: Uniforms;
288
+ @group(0) @binding(2) var imageTex: texture_2d<f32>;
289
+ @group(0) @binding(3) var imageSampler: sampler;
290
+ @group(0) @binding(4) var depthTex: texture_2d<f32>;
291
+ @group(0) @binding(5) var depthSampler: sampler;
292
+
293
+ // ---- Helper functions ----
294
+
295
+ fn edgeFade(uv: vec2f) -> f32 {
296
+ let margin = u.strength * 1.5;
297
+ let fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
298
+ let fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
299
+ return fadeX * fadeY;
300
+ }
301
+
302
+ fn vignette(uv: vec2f) -> f32 {
303
+ let dist = length(uv - 0.5) * 1.4;
304
+ return 1.0 - pow(dist, 2.5);
305
+ }
306
+
307
+ // ---- Displacement functions ----
308
+
309
+ fn basicDisplace(uv: vec2f) -> vec2f {
310
+ var depth = textureSampleLevel(depthTex, depthSampler, uv, 0.0).r;
311
+ depth = smoothstep(u.contrastLow, u.contrastHigh, depth);
312
+ var displacement = (1.0 - depth) * u.strength;
313
+ displacement *= edgeFade(uv);
314
+ var off = u.offset * displacement;
315
+ off.y *= u.verticalReduction;
316
+ return uv + off;
317
+ }
318
+
319
+ fn pomDisplace(uv: vec2f) -> vec2f {
320
+ let layerD = 1.0 / f32(u.pomSteps);
321
+
322
+ var scaledOffset = u.offset;
323
+ scaledOffset.y *= u.verticalReduction;
324
+
325
+ let deltaUV = scaledOffset * u.strength / f32(u.pomSteps);
326
+
327
+ var currentLayerDepth: f32 = 0.0;
328
+ var currentUV = uv;
329
+
330
+ let fade = edgeFade(uv);
331
+
332
+ for (var i: i32 = 0; i < MAX_POM_STEPS; i++) {
333
+ if (i >= u.pomSteps) { break; }
334
+
335
+ var rawDepth = textureSampleLevel(depthTex, depthSampler, currentUV, 0.0).r;
336
+ rawDepth = smoothstep(u.contrastLow, u.contrastHigh, rawDepth);
337
+ let depthAtUV = 1.0 - rawDepth;
338
+
339
+ if (currentLayerDepth > depthAtUV) {
340
+ let prevUV = currentUV - deltaUV;
341
+ let prevLayerDepth = currentLayerDepth - layerD;
342
+ var prevRaw = textureSampleLevel(depthTex, depthSampler, prevUV, 0.0).r;
343
+ prevRaw = smoothstep(u.contrastLow, u.contrastHigh, prevRaw);
344
+ let prevDepthAtUV = 1.0 - prevRaw;
345
+
346
+ let afterDepth = depthAtUV - currentLayerDepth;
347
+ let beforeDepth = prevDepthAtUV - prevLayerDepth;
348
+ let t = afterDepth / (afterDepth - beforeDepth);
349
+
350
+ let hitUV = mix(currentUV, prevUV, t);
351
+ return mix(uv, hitUV, fade);
352
+ }
105
353
 
106
- // ---- Displacement functions ----
107
-
108
- /**
109
- * Basic UV displacement with edge fade.
110
- */
111
- vec2 basicDisplace(vec2 uv) {
112
- float depth = texture(uDepth, uv).r;
113
- depth = smoothstep(uContrastLow, uContrastHigh, depth);
114
- float displacement = (1.0 - depth) * uStrength;
115
- displacement *= edgeFade(uv);
116
- vec2 offset = uOffset * displacement;
117
- offset.y *= uVerticalReduction;
118
- return uv + offset;
354
+ currentUV += deltaUV;
355
+ currentLayerDepth += layerD;
119
356
  }
120
357
 
121
- /**
122
- * Parallax Occlusion Mapping (POM) ray-marching displacement.
123
- */
124
- vec2 pomDisplace(vec2 uv) {
125
- float layerDepth = 1.0 / float(uPomSteps);
126
-
127
- vec2 scaledOffset = uOffset;
128
- scaledOffset.y *= uVerticalReduction;
129
-
130
- vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
131
-
132
- float currentLayerDepth = 0.0;
133
- vec2 currentUV = uv;
134
-
135
- float fade = edgeFade(uv);
136
-
137
- for (int i = 0; i < MAX_POM_STEPS; i++) {
138
- if (i >= uPomSteps) break;
139
-
140
- float rawDepth = texture(uDepth, currentUV).r;
141
- rawDepth = smoothstep(uContrastLow, uContrastHigh, rawDepth);
142
- float depthAtUV = 1.0 - rawDepth;
143
-
144
- if (currentLayerDepth > depthAtUV) {
145
- vec2 prevUV = currentUV - deltaUV;
146
- float prevLayerDepth = currentLayerDepth - layerDepth;
147
- float prevRaw = texture(uDepth, prevUV).r;
148
- prevRaw = smoothstep(uContrastLow, uContrastHigh, prevRaw);
149
- float prevDepthAtUV = 1.0 - prevRaw;
150
-
151
- float afterDepth = depthAtUV - currentLayerDepth;
152
- float beforeDepth = prevDepthAtUV - prevLayerDepth;
153
- float t = afterDepth / (afterDepth - beforeDepth);
154
-
155
- vec2 hitUV = mix(currentUV, prevUV, t);
156
- return mix(uv, hitUV, fade);
157
- }
158
-
159
- currentUV += deltaUV;
160
- currentLayerDepth += layerDepth;
358
+ return mix(uv, currentUV, fade);
359
+ }
360
+
361
+ // ---- Main ----
362
+
363
+ @fragment
364
+ fn fs_main(
365
+ @location(0) uv: vec2f,
366
+ @location(1) screenUv: vec2f
367
+ ) -> @location(0) vec4f {
368
+ var displaced: vec2f;
369
+ if (u.pomEnabled != 0u) {
370
+ displaced = pomDisplace(uv);
371
+ } else {
372
+ displaced = basicDisplace(uv);
373
+ }
374
+ displaced = clamp(displaced, vec2f(0.0), vec2f(1.0));
375
+
376
+ var color = textureSampleLevel(imageTex, imageSampler, displaced, 0.0);
377
+
378
+ // Depth-of-field hint
379
+ let dofDepth = textureSampleLevel(depthTex, depthSampler, displaced, 0.0).r;
380
+ let dof = smoothstep(u.dofStart, 1.0, dofDepth) * u.dofStrength;
381
+ let ts = u.imageTexelSize;
382
+ let blurred = (
383
+ textureSampleLevel(imageTex, imageSampler, displaced + vec2f( ts.x, 0.0), 0.0) +
384
+ textureSampleLevel(imageTex, imageSampler, displaced + vec2f(-ts.x, 0.0), 0.0) +
385
+ textureSampleLevel(imageTex, imageSampler, displaced + vec2f( 0.0, ts.y), 0.0) +
386
+ textureSampleLevel(imageTex, imageSampler, displaced + vec2f( 0.0, -ts.y), 0.0)
387
+ ) * 0.25;
388
+ color = mix(color, blurred, dof);
389
+
390
+ // Vignette (screen-space, not texture-space)
391
+ color = vec4f(color.rgb * vignette(screenUv), color.a);
392
+
393
+ return color;
394
+ }
395
+ `,Lt=`// Bilateral filter vertex shader — simple pass-through, no cover-fit transform.
396
+
397
+ struct VertexOutput {
398
+ @builtin(position) position: vec4f,
399
+ @location(0) uv: vec2f,
400
+ };
401
+
402
+ @vertex
403
+ fn vs_main(@location(0) aPosition: vec2f) -> VertexOutput {
404
+ var out: VertexOutput;
405
+ out.uv = aPosition * 0.5 + 0.5;
406
+ out.position = vec4f(aPosition, 0.0, 1.0);
407
+ return out;
408
+ }
409
+ `,Bt=`// Bilateral filter fragment shader — edge-preserving depth smoothing.
410
+ // BILATERAL_RADIUS is an override constant set at pipeline creation.
411
+ //
412
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
413
+ // for consistency with other WGSL shaders and to prevent non-uniform control
414
+ // flow issues if the shader is modified in the future.
415
+
416
+ override BILATERAL_RADIUS: i32 = 2;
417
+
418
+ struct Uniforms {
419
+ texelSize: vec2f,
420
+ spatialSigma2: f32,
421
+ };
422
+
423
+ @group(0) @binding(0) var<uniform> u: Uniforms;
424
+ @group(0) @binding(1) var rawDepthTex: texture_2d<f32>;
425
+ @group(0) @binding(2) var rawDepthSampler: sampler;
426
+
427
+ @fragment
428
+ fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
429
+ let depthSigma2: f32 = 0.01; // 0.1^2
430
+
431
+ let center = textureSampleLevel(rawDepthTex, rawDepthSampler, uv, 0.0).r;
432
+ var totalWeight: f32 = 1.0;
433
+ var totalDepth: f32 = center;
434
+
435
+ for (var dy: i32 = -BILATERAL_RADIUS; dy <= BILATERAL_RADIUS; dy++) {
436
+ for (var dx: i32 = -BILATERAL_RADIUS; dx <= BILATERAL_RADIUS; dx++) {
437
+ if (dx == 0 && dy == 0) { continue; }
438
+
439
+ let offset = vec2f(f32(dx), f32(dy)) * u.texelSize;
440
+ let neighbor = textureSampleLevel(rawDepthTex, rawDepthSampler, uv + offset, 0.0).r;
441
+
442
+ let spatialDist2 = f32(dx * dx + dy * dy);
443
+ let depthDiff = neighbor - center;
444
+ let w = exp(-spatialDist2 / u.spatialSigma2 - (depthDiff * depthDiff) / depthSigma2);
445
+
446
+ totalWeight += w;
447
+ totalDepth += neighbor * w;
161
448
  }
162
-
163
- return mix(uv, currentUV, fade);
164
449
  }
165
450
 
166
- // ---- Main ----
167
-
168
- void main() {
169
- vec2 displaced = uPomEnabled ? pomDisplace(vUv) : basicDisplace(vUv);
170
- displaced = clamp(displaced, vec2(0.0), vec2(1.0));
171
-
172
- vec4 color = texture(uImage, displaced);
173
-
174
- // Depth-of-field hint
175
- float dofDepth = texture(uDepth, displaced).r;
176
- float dof = smoothstep(uDofStart, 1.0, dofDepth) * uDofStrength;
177
- vec4 blurred = (
178
- texture(uImage, displaced + vec2( uImageTexelSize.x, 0.0)) +
179
- texture(uImage, displaced + vec2(-uImageTexelSize.x, 0.0)) +
180
- texture(uImage, displaced + vec2( 0.0, uImageTexelSize.y)) +
181
- texture(uImage, displaced + vec2( 0.0, -uImageTexelSize.y))
182
- ) * 0.25;
183
- color = mix(color, blurred, dof);
184
-
185
- // Vignette (screen-space, not texture-space)
186
- color.rgb *= vignette(vScreenUv);
187
-
188
- fragColor = color;
189
- }
190
- `,q={contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4};function Tt(r,t,e){const i=r.createShader(t);if(!i)throw new Error("Failed to create shader.");if(r.shaderSource(i,e),r.compileShader(i),!r.getShaderParameter(i,r.COMPILE_STATUS)){const o=r.getShaderInfoLog(i)??"";throw r.deleteShader(i),new Error(`Shader compilation failed:
191
- ${o}`)}return i}function jt(r,t,e){const i=r.createProgram();if(!i)throw new Error("Failed to create program.");if(r.attachShader(i,t),r.attachShader(i,e),r.linkProgram(i),!r.getProgramParameter(i,r.LINK_STATUS)){const o=r.getProgramInfoLog(i)??"";throw r.deleteProgram(i),new Error(`Program linking failed:
192
- ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.deleteShader(e),i}function Yt(r,t){return{uImage:r.getUniformLocation(t,"uImage"),uDepth:r.getUniformLocation(t,"uDepth"),uOffset:r.getUniformLocation(t,"uOffset"),uStrength:r.getUniformLocation(t,"uStrength"),uPomEnabled:r.getUniformLocation(t,"uPomEnabled"),uPomSteps:r.getUniformLocation(t,"uPomSteps"),uContrastLow:r.getUniformLocation(t,"uContrastLow"),uContrastHigh:r.getUniformLocation(t,"uContrastHigh"),uVerticalReduction:r.getUniformLocation(t,"uVerticalReduction"),uDofStart:r.getUniformLocation(t,"uDofStart"),uDofStrength:r.getUniformLocation(t,"uDofStrength"),uImageTexelSize:r.getUniformLocation(t,"uImageTexelSize"),uUvOffset:r.getUniformLocation(t,"uUvOffset"),uUvScale:r.getUniformLocation(t,"uUvScale")}}class Z{static RESIZE_DEBOUNCE_MS=100;static MAX_POM_STEPS=64;canvas;gl=null;program=null;uniforms=null;vao=null;videoTexture=null;depthTexture=null;container;depthWidth=0;depthHeight=0;videoAspect=1.7777777777777777;readDepth=null;readInput=null;playbackVideo=null;onVideoFrame=null;animationFrameHandle=0;rvfcHandle=0;rvfcSupported=!1;resizeObserver=null;resizeTimer=null;uvOffset=[0,0];uvScale=[1,1];config;constructor(t,e){this.container=t,this.config={parallaxStrength:e.parallaxStrength,pomEnabled:e.pomEnabled,pomSteps:e.pomSteps,overscanPadding:e.overscanPadding,contrastLow:e.contrastLow??q.contrastLow,contrastHigh:e.contrastHigh??q.contrastHigh,verticalReduction:e.verticalReduction??q.verticalReduction,dofStart:e.dofStart??q.dofStart,dofStrength:e.dofStrength??q.dofStrength},this.canvas=document.createElement("canvas");const i=this.canvas.getContext("webgl2",{antialias:!1,alpha:!1,desynchronized:!0,powerPreference:"high-performance"});if(!i)throw new Error("WebGL 2 is not supported.");this.gl=i,"drawingBufferColorSpace"in i&&(i.drawingBufferColorSpace="srgb"),i.clearColor(0,0,0,1),i.pixelStorei(i.UNPACK_FLIP_Y_WEBGL,!0),this.container.appendChild(this.canvas),this.initGPUResources(),this.setupResizeHandling(),this.canvas.addEventListener("webglcontextlost",this.handleContextLost),this.canvas.addEventListener("webglcontextrestored",this.handleContextRestored)}initialize(t,e,i){const o=this.gl;o&&(this.disposeTextures(),this.videoAspect=t.videoWidth/t.videoHeight,this.depthWidth=e,this.depthHeight=i,this.videoTexture=o.createTexture(),o.activeTexture(o.TEXTURE0),o.bindTexture(o.TEXTURE_2D,this.videoTexture),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_MIN_FILTER,o.LINEAR),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_MAG_FILTER,o.LINEAR),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_WRAP_S,o.CLAMP_TO_EDGE),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_WRAP_T,o.CLAMP_TO_EDGE),this.depthTexture=o.createTexture(),o.activeTexture(o.TEXTURE1),o.bindTexture(o.TEXTURE_2D,this.depthTexture),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_MIN_FILTER,o.LINEAR),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_MAG_FILTER,o.LINEAR),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_WRAP_S,o.CLAMP_TO_EDGE),o.texParameteri(o.TEXTURE_2D,o.TEXTURE_WRAP_T,o.CLAMP_TO_EDGE),o.texStorage2D(o.TEXTURE_2D,1,o.R8,e,i),this.program&&this.uniforms&&(o.useProgram(this.program),o.uniform1i(this.uniforms.uImage,0),o.uniform1i(this.uniforms.uDepth,1),o.uniform1f(this.uniforms.uStrength,this.config.parallaxStrength),o.uniform1i(this.uniforms.uPomEnabled,this.config.pomEnabled?1:0),o.uniform1i(this.uniforms.uPomSteps,this.config.pomSteps),o.uniform1f(this.uniforms.uContrastLow,this.config.contrastLow),o.uniform1f(this.uniforms.uContrastHigh,this.config.contrastHigh),o.uniform1f(this.uniforms.uVerticalReduction,this.config.verticalReduction),o.uniform1f(this.uniforms.uDofStart,this.config.dofStart),o.uniform1f(this.uniforms.uDofStrength,this.config.dofStrength),o.uniform2f(this.uniforms.uImageTexelSize,1/t.videoWidth,1/t.videoHeight)),this.recalculateViewportLayout())}start(t,e,i,o){this.stop(),this.playbackVideo=t,this.readDepth=e,this.readInput=i,this.onVideoFrame=o??null,this.rvfcSupported=Z.isRVFCSupported(),this.rvfcSupported&&(this.rvfcHandle=t.requestVideoFrameCallback(this.videoFrameLoop)),this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)}stop(){this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0),this.rvfcHandle&&this.playbackVideo&&(this.playbackVideo.cancelVideoFrameCallback(this.rvfcHandle),this.rvfcHandle=0),this.playbackVideo=null,this.readDepth=null,this.readInput=null,this.onVideoFrame=null,this.rvfcSupported=!1}dispose(){this.stop(),this.disposeTextures(),this.disposeGPUResources(),this.canvas.removeEventListener("webglcontextlost",this.handleContextLost),this.canvas.removeEventListener("webglcontextrestored",this.handleContextRestored),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null),this.canvas.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),window.removeEventListener("resize",this.scheduleResizeRecalculate),this.resizeTimer!==null&&(window.clearTimeout(this.resizeTimer),this.resizeTimer=null)}initGPUResources(){const t=this.gl;if(!t)return;const e=Gt.replace("#version 300 es",`#version 300 es
193
- #define MAX_POM_STEPS ${Z.MAX_POM_STEPS}`),i=Tt(t,t.VERTEX_SHADER,Wt),o=Tt(t,t.FRAGMENT_SHADER,e);this.program=jt(t,i,o),this.uniforms=Yt(t,this.program);const n=new Float32Array([-1,-1,1,-1,-1,1,1,1]);this.vao=t.createVertexArray(),t.bindVertexArray(this.vao);const s=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,s),t.bufferData(t.ARRAY_BUFFER,n,t.STATIC_DRAW);const h=t.getAttribLocation(this.program,"aPosition");t.enableVertexAttribArray(h),t.vertexAttribPointer(h,2,t.FLOAT,!1,0,0),t.bindVertexArray(null),t.disable(t.DEPTH_TEST)}static isRVFCSupported(){return"requestVideoFrameCallback"in HTMLVideoElement.prototype}videoFrameLoop=(t,e)=>{const i=this.playbackVideo;if(!i)return;this.rvfcHandle=i.requestVideoFrameCallback(this.videoFrameLoop);const o=e.mediaTime??i.currentTime;this.updateDepthTexture(o),this.onVideoFrame&&this.onVideoFrame(o,e.presentedFrames??0)};renderLoop=()=>{this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop);const t=this.gl,e=this.playbackVideo;if(!(!t||!this.program||!this.uniforms||!this.vao)&&!(!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)){if(t.useProgram(this.program),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),this.rvfcSupported||this.updateDepthTexture(e.currentTime),this.readInput){const i=this.readInput();t.uniform2f(this.uniforms.uOffset,-i.x,i.y)}t.bindVertexArray(this.vao),t.drawArrays(t.TRIANGLE_STRIP,0,4)}};updateDepthTexture(t){const e=this.gl;if(!e||!this.readDepth||!this.depthTexture)return;const i=this.readDepth(t);e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.depthTexture),e.texSubImage2D(e.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,e.RED,e.UNSIGNED_BYTE,i)}setupResizeHandling(){typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.scheduleResizeRecalculate()}),this.resizeObserver.observe(this.container)),window.addEventListener("resize",this.scheduleResizeRecalculate),this.recalculateViewportLayout()}scheduleResizeRecalculate=()=>{this.resizeTimer!==null&&window.clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.resizeTimer=null,this.recalculateViewportLayout()},Z.RESIZE_DEBOUNCE_MS)};recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:i}=this.getViewportSize(),o=Math.min(window.devicePixelRatio,2),n=Math.round(e*o),s=Math.round(i*o);(this.canvas.width!==n||this.canvas.height!==s)&&(this.canvas.width=n,this.canvas.height=s,t.viewport(0,0,n,s));const h=e/i,a=this.config.parallaxStrength+this.config.overscanPadding;let l=1,u=1;h>this.videoAspect?u=this.videoAspect/h:l=h/this.videoAspect;const f=1+a*2;l/=f,u/=f,this.uvOffset=[(1-l)/2,(1-u)/2],this.uvScale=[l,u],this.program&&this.uniforms&&(t.useProgram(this.program),t.uniform2f(this.uniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),t.uniform2f(this.uniforms.uUvScale,this.uvScale[0],this.uvScale[1]))}getViewportSize(){const t=Math.max(1,Math.round(this.container.clientWidth||window.innerWidth)),e=Math.max(1,Math.round(this.container.clientHeight||window.innerHeight));return{width:t,height:e}}handleContextLost=t=>{t.preventDefault(),this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0)};handleContextRestored=()=>{const t=this.canvas.getContext("webgl2");t&&(this.gl=t,t.clearColor(0,0,0,1),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.playbackVideo&&this.depthWidth>0&&this.initialize(this.playbackVideo,this.depthWidth,this.depthHeight),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)))};disposeTextures(){const t=this.gl;t&&(this.videoTexture&&(t.deleteTexture(this.videoTexture),this.videoTexture=null),this.depthTexture&&(t.deleteTexture(this.depthTexture),this.depthTexture=null))}disposeGPUResources(){const t=this.gl;t&&(this.program&&(t.deleteProgram(this.program),this.program=null),this.vao&&(t.deleteVertexArray(this.vao),this.vao=null),this.uniforms=null)}}class Et{abortController=null;initialized=!1;initializing=!1;element;constructor(t){this.element=t}onConnected(){this.element.setupShadowDOM(),this.tryInit()}onDisconnected(){this.cancelInit(),this.element.doDispose(),this.initialized=!1}onAttributeChanged(t,e,i){this.element.reinitAttributes.includes(t)&&e!==i&&(this.initialized?(this.cancelInit(),this.element.doDispose(),this.initialized=!1,this.element.setupShadowDOM(),this.tryInit()):this.initializing||this.tryInit())}get isInitialized(){return this.initialized}markInitialized(){this.initialized=!0,this.initializing=!1}async tryInit(){if(this.initializing)return;const t=this.element;if(!t.isConnected)return;for(const i of t.reinitAttributes)if(!t.getAttribute(i))return;this.cancelInit();const e=new AbortController;this.abortController=e,this.initializing=!0;try{if(await t.doInit(e.signal),e.signal.aborted){this.initializing=!1;return}}catch{this.initializing=!1}}cancelInit(){this.abortController?.abort(),this.abortController=null,this.initializing=!1}}const H={parallaxX:.4,parallaxY:1,parallaxMax:30,overscan:.05,autoplay:!0,loop:!0,muted:!0};let qt=class Ct{constructor(t,e=.08,i=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const t=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,e=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=ot(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=ot(this.smoothedOutput.y,t.y,e),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=t=>{const e=this.host.getBoundingClientRect(),i=(t.clientX-e.left)/e.width*2-1,o=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=N(i,-1,1),this.pointerTarget.y=N(o,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=t=>{const e=t.touches[0];e&&(this.touchActive=!0,this.touchAnchorX=e.clientX,this.touchAnchorY=e.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=t=>{const e=t.touches[0];if(!e)return;const i=e.clientX-this.touchAnchorX,o=e.clientY-this.touchAnchorY,n=Ct.TOUCH_DRAG_RANGE;this.pointerTarget.x=N(i/n,-1,1),this.pointerTarget.y=N(o/n,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const t=DeviceOrientationEvent;if(typeof t.requestPermission=="function")try{if(await t.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=t=>{const e=N((t.gamma??0)/45,-1,1),i=N((t.beta??0)/45,-1,1);this.motionTarget.x=ot(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=ot(this.motionTarget.y,i,this.motionLerpFactor)}};class rt extends HTMLElement{static TAG_NAME="layershift-parallax";static get observedAttributes(){return["src","depth-src","depth-meta","parallax-x","parallax-y","parallax-max","layers","overscan","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta"];shadow;container=null;renderer=null;inputHandler=null;depthWorker=null;video=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Et(this)}getAttrFloat(t,e){const i=this.getAttribute(t);if(i===null)return e;const o=parseFloat(i);return Number.isFinite(o)?o:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return!(i==="false"||i==="0")}get parallaxX(){return this.getAttrFloat("parallax-x",H.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",H.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",H.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",H.overscan)}get shouldAutoplay(){return this.getAttrBool("autoplay",H.autoplay)}get shouldLoop(){return this.getAttrBool("loop",H.loop)}get shouldMute(){return this.getAttrBool("muted",H.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachVideoEventListeners(t){t.addEventListener("play",()=>{this.emit("layershift-parallax:play",{currentTime:t.currentTime})}),t.addEventListener("pause",()=>{this.emit("layershift-parallax:pause",{currentTime:t.currentTime})}),t.addEventListener("ended",()=>{t.loop&&(this.loopCount+=1,this.emit("layershift-parallax:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,i){this.lifecycle.onAttributeChanged(t,e,i)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
451
+ return vec4f(totalDepth / totalWeight, 0.0, 0.0, 1.0);
452
+ }
453
+ `,$={contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4},Mt={2:2.25,1:.5625},Ct=64,_t=16,Vt=16,Me=48;class Gt extends k{device;context=null;canvasFormat;config;quadBuffer=null;linearSampler=null;bilateralPipeline=null;bilateralBindGroupLayout=null;bilateralUniformBuffer=null;bilateralBindGroup=null;rawDepthTexture=null;rawDepthView=null;filteredDepthTexture=null;filteredDepthView=null;parallaxPipeline=null;parallaxBindGroupLayout=null;vertexUniformBuffer=null;fragmentUniformBuffer=null;parallaxBindGroup=null;videoTexture=null;videoTextureView=null;offsetData=new Float32Array(2);depthFlipBuffer=null;constructor(e,t,i,n){super(e),this.device=i,this.config={parallaxStrength:t.parallaxStrength,pomEnabled:t.pomEnabled,pomSteps:t.pomSteps,overscanPadding:t.overscanPadding,contrastLow:t.contrastLow??$.contrastLow,contrastHigh:t.contrastHigh??$.contrastHigh,verticalReduction:t.verticalReduction??$.verticalReduction,dofStart:t.dofStart??$.dofStart,dofStrength:t.dofStrength??$.dofStrength},this.qualityParams=De(n,t.quality),this.context=this.canvas.getContext("webgpu"),this.canvasFormat=navigator.gpu.getPreferredCanvasFormat(),this.context.configure({device:i,format:this.canvasFormat,alphaMode:"opaque"}),this.quadBuffer=Re(i),this.linearSampler=Fe(i),this.createBilateralPipeline(),this.createParallaxPipeline(),this.bilateralUniformBuffer=B(i,_t),this.vertexUniformBuffer=B(i,Vt),this.fragmentUniformBuffer=B(i,Me),i.lost.then(r=>{console.error(`WebGPU device lost (${r.reason}): ${r.message}`),this.stop()}),this.setupResizeHandling()}initialize(e,t,i){this.disposeTextures(),this.videoAspect=e.videoWidth/e.videoHeight,this.clampDepthDimensions(t,i,this.qualityParams.depthMaxDim),this.videoTexture=this.device.createTexture({size:[e.videoWidth,e.videoHeight],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT}),this.videoTextureView=this.videoTexture.createView(),this.rawDepthTexture=this.device.createTexture({size:[this.depthWidth,this.depthHeight],format:"r8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST}),this.rawDepthView=this.rawDepthTexture.createView(),this.filteredDepthTexture=this.device.createTexture({size:[this.depthWidth,this.depthHeight],format:"r8unorm",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.filteredDepthView=this.filteredDepthTexture.createView(),this.depthFlipBuffer=new Uint8Array(this.depthWidth*this.depthHeight);const n=Mt[this.qualityParams.bilateralRadius]??2.25;this.device.queue.writeBuffer(this.bilateralUniformBuffer,0,new Float32Array([1/this.depthWidth,1/this.depthHeight,n,0])),this.writeStaticFragmentUniforms(e.videoWidth,e.videoHeight),this.rebuildBilateralBindGroup(),this.rebuildParallaxBindGroup(),this.recalculateViewportLayout()}createBilateralPipeline(){const e=this.device;this.bilateralBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createPipelineLayout({bindGroupLayouts:[this.bilateralBindGroupLayout]});this.bilateralPipeline=e.createRenderPipeline({layout:t,vertex:{module:e.createShaderModule({code:Lt}),entryPoint:"vs_main",buffers:[N]},fragment:{module:e.createShaderModule({code:Bt}),entryPoint:"fs_main",targets:[{format:"r8unorm"}],constants:{BILATERAL_RADIUS:this.qualityParams.bilateralRadius}},primitive:{topology:"triangle-strip"}})}createParallaxPipeline(){const e=this.device;this.parallaxBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:3,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:4,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:5,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createPipelineLayout({bindGroupLayouts:[this.parallaxBindGroupLayout]});this.parallaxPipeline=e.createRenderPipeline({layout:t,vertex:{module:e.createShaderModule({code:Rt}),entryPoint:"vs_main",buffers:[N]},fragment:{module:e.createShaderModule({code:Ft}),entryPoint:"fs_main",targets:[{format:this.canvasFormat}],constants:{MAX_POM_STEPS:Ct}},primitive:{topology:"triangle-strip"}})}rebuildBilateralBindGroup(){!this.bilateralBindGroupLayout||!this.bilateralUniformBuffer||!this.rawDepthView||!this.linearSampler||(this.bilateralBindGroup=this.device.createBindGroup({layout:this.bilateralBindGroupLayout,entries:[{binding:0,resource:{buffer:this.bilateralUniformBuffer}},{binding:1,resource:this.rawDepthView},{binding:2,resource:this.linearSampler}]}))}rebuildParallaxBindGroup(){!this.parallaxBindGroupLayout||!this.vertexUniformBuffer||!this.fragmentUniformBuffer||!this.videoTextureView||!this.filteredDepthView||!this.linearSampler||(this.parallaxBindGroup=this.device.createBindGroup({layout:this.parallaxBindGroupLayout,entries:[{binding:0,resource:{buffer:this.vertexUniformBuffer}},{binding:1,resource:{buffer:this.fragmentUniformBuffer}},{binding:2,resource:this.videoTextureView},{binding:3,resource:this.linearSampler},{binding:4,resource:this.filteredDepthView},{binding:5,resource:this.linearSampler}]}))}writeStaticFragmentUniforms(e,t){const i=new ArrayBuffer(Me),n=new Float32Array(i),r=new Uint32Array(i),s=new Int32Array(i);n[0]=0,n[1]=0,n[2]=this.config.parallaxStrength,r[3]=this.config.pomEnabled?1:0,s[4]=this.config.pomSteps,n[5]=this.config.contrastLow,n[6]=this.config.contrastHigh,n[7]=this.config.verticalReduction,n[8]=this.config.dofStart,n[9]=this.config.dofStrength,n[10]=1/e,n[11]=1/t,this.device.queue.writeBuffer(this.fragmentUniformBuffer,0,i)}flipDepthY(e){const t=this.depthFlipBuffer,i=this.depthWidth,n=this.depthHeight;for(let r=0;r<n;r++){const s=r*i,a=(n-1-r)*i;t.set(e.subarray(s,s+i),a)}return t}onRenderFrame(){const e=this.playbackVideo;if(!this.context||!this.parallaxPipeline||!this.parallaxBindGroup||!this.quadBuffer||!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)return;if(this.videoTexture&&Le(this.device,this.videoTexture,e),this.rvfcSupported||this.onDepthUpdate(e.currentTime),this.readInput){const n=this.readInput();this.offsetData[0]=-n.x,this.offsetData[1]=n.y,this.device.queue.writeBuffer(this.fragmentUniformBuffer,0,this.offsetData)}const t=this.device.createCommandEncoder(),i=t.beginRenderPass({colorAttachments:[{view:this.context.getCurrentTexture().createView(),clearValue:{r:0,g:0,b:0,a:1},loadOp:"clear",storeOp:"store"}]});i.setPipeline(this.parallaxPipeline),i.setBindGroup(0,this.parallaxBindGroup),i.setVertexBuffer(0,this.quadBuffer),i.draw(4),i.end(),this.device.queue.submit([t.finish()])}onDepthUpdate(e){if(!this.readDepth||!this.rawDepthTexture||!this.filteredDepthView||!this.bilateralPipeline||!this.bilateralBindGroup||!this.quadBuffer)return;const t=this.subsampleDepth(this.readDepth(e)),i=this.flipDepthY(t);this.device.queue.writeTexture({texture:this.rawDepthTexture},i,{bytesPerRow:this.depthWidth},{width:this.depthWidth,height:this.depthHeight});const n=this.device.createCommandEncoder(),r=n.beginRenderPass({colorAttachments:[{view:this.filteredDepthView,loadOp:"clear",clearValue:{r:0,g:0,b:0,a:1},storeOp:"store"}]});r.setPipeline(this.bilateralPipeline),r.setBindGroup(0,this.bilateralBindGroup),r.setVertexBuffer(0,this.quadBuffer),r.draw(4),r.end(),this.device.queue.submit([n.finish()])}recalculateViewportLayout(){if(!this.context)return;const{width:e,height:t}=this.getViewportSize(),i=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),n=Math.round(e*i),r=Math.round(t*i);(this.canvas.width!==n||this.canvas.height!==r)&&(this.canvas.width=n,this.canvas.height=r),this.computeCoverFitUV(this.config.parallaxStrength,this.config.overscanPadding),this.vertexUniformBuffer&&this.device.queue.writeBuffer(this.vertexUniformBuffer,0,new Float32Array([this.uvOffset[0],this.uvOffset[1],this.uvScale[0],this.uvScale[1]]))}disposeRenderer(){this.disposeTextures(),this.bilateralUniformBuffer?.destroy(),this.bilateralUniformBuffer=null,this.vertexUniformBuffer?.destroy(),this.vertexUniformBuffer=null,this.fragmentUniformBuffer?.destroy(),this.fragmentUniformBuffer=null,this.quadBuffer?.destroy(),this.quadBuffer=null,this.linearSampler=null,this.bilateralPipeline=null,this.bilateralBindGroupLayout=null,this.bilateralBindGroup=null,this.parallaxPipeline=null,this.parallaxBindGroupLayout=null,this.parallaxBindGroup=null,this.context&&(this.context.unconfigure(),this.context=null),this.depthFlipBuffer=null}onContextRestored(){}disposeTextures(){this.videoTexture?.destroy(),this.videoTexture=null,this.videoTextureView=null,this.rawDepthTexture?.destroy(),this.rawDepthTexture=null,this.rawDepthView=null,this.filteredDepthTexture?.destroy(),this.filteredDepthTexture=null,this.filteredDepthView=null,this.bilateralBindGroup=null,this.parallaxBindGroup=null}}const It=1500;function Ot(){return typeof navigator<"u"&&"gpu"in navigator}async function Ce(o="auto"){if(o==="webgl2")return{type:"webgl2"};if(!Ot()){if(o==="webgpu")throw new Error("WebGPU not available: navigator.gpu is undefined");return{type:"webgl2"}}try{const e=await kt();if(!e){if(o==="webgpu")throw new Error("WebGPU adapter request returned null");return{type:"webgl2"}}const t=await e.requestDevice();return{type:"webgpu",adapter:e,device:t}}catch(e){if(o==="webgpu")throw e;return{type:"webgl2"}}}async function kt(){const o=navigator.gpu.requestAdapter({powerPreference:"high-performance"}),e=new Promise(t=>{setTimeout(()=>t(null),It)});return Promise.race([o,e])}class _e{abortController=null;initialized=!1;initializing=!1;element;constructor(e){this.element=e}onConnected(){this.element.setupShadowDOM(),this.tryInit()}onDisconnected(){this.cancelInit(),this.element.doDispose(),this.initialized=!1}onAttributeChanged(e,t,i){this.element.reinitAttributes.includes(e)&&t!==i&&(this.initialized?(this.cancelInit(),this.element.doDispose(),this.initialized=!1,this.element.setupShadowDOM(),this.tryInit()):this.initializing||this.tryInit())}get isInitialized(){return this.initialized}markInitialized(){this.initialized=!0,this.initializing=!1}async tryInit(){if(this.initializing)return;const e=this.element;if(!e.isConnected)return;for(const i of e.reinitAttributes)if(!e.getAttribute(i))return;this.cancelInit();const t=new AbortController;this.abortController=t,this.initializing=!0;try{if(await e.doInit(t.signal),t.signal.aborted){this.initializing=!1;return}}catch{this.initializing=!1}}cancelInit(){this.abortController?.abort(),this.abortController=null,this.initializing=!1}}const X={parallaxX:.4,parallaxY:1,parallaxMax:30,overscan:.05,autoplay:!0,loop:!0,muted:!0};let Nt=class Ke{constructor(e,t=.08,i=.06){this.host=e,this.lerpFactor=t,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const e=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,t=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=oe(this.smoothedOutput.x,e.x,t),this.smoothedOutput.y=oe(this.smoothedOutput.y,e.y,t),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=e=>{const t=this.host.getBoundingClientRect(),i=(e.clientX-t.left)/t.width*2-1,n=(e.clientY-t.top)/t.height*2-1;this.pointerTarget.x=j(i,-1,1),this.pointerTarget.y=j(n,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=e=>{const t=e.touches[0];t&&(this.touchActive=!0,this.touchAnchorX=t.clientX,this.touchAnchorY=t.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=e=>{const t=e.touches[0];if(!t)return;const i=t.clientX-this.touchAnchorX,n=t.clientY-this.touchAnchorY,r=Ke.TOUCH_DRAG_RANGE;this.pointerTarget.x=j(i/r,-1,1),this.pointerTarget.y=j(n/r,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const e=DeviceOrientationEvent;if(typeof e.requestPermission=="function")try{if(await e.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=e=>{const t=j((e.gamma??0)/45,-1,1),i=j((e.beta??0)/45,-1,1);this.motionTarget.x=oe(this.motionTarget.x,t,this.motionLerpFactor),this.motionTarget.y=oe(this.motionTarget.y,i,this.motionLerpFactor)}};class re extends HTMLElement{static TAG_NAME="layershift-parallax";static get observedAttributes(){return["src","depth-src","depth-meta","parallax-x","parallax-y","parallax-max","layers","overscan","quality","gpu-backend","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta"];shadow;container=null;renderer=null;inputHandler=null;video=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new _e(this)}getAttrFloat(e,t){const i=this.getAttribute(e);if(i===null)return t;const n=parseFloat(i);return Number.isFinite(n)?n:t}getAttrBool(e,t){if(!this.hasAttribute(e))return t;const i=this.getAttribute(e);return!(i==="false"||i==="0")}get parallaxX(){return this.getAttrFloat("parallax-x",X.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",X.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",X.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",X.overscan)}get quality(){const e=this.getAttribute("quality");if(e==="auto"||e==="high"||e==="medium"||e==="low")return e}get gpuBackend(){const e=this.getAttribute("gpu-backend");return e==="webgpu"||e==="webgl2"?e:"auto"}get shouldAutoplay(){return this.getAttrBool("autoplay",X.autoplay)}get shouldLoop(){return this.getAttrBool("loop",X.loop)}get shouldMute(){return this.getAttrBool("muted",X.muted)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}attachVideoEventListeners(e){e.addEventListener("play",()=>{this.emit("layershift-parallax:play",{currentTime:e.currentTime})}),e.addEventListener("pause",()=>{this.emit("layershift-parallax:pause",{currentTime:e.currentTime})}),e.addEventListener("ended",()=>{e.loop&&(this.loopCount+=1,this.emit("layershift-parallax:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(e,t,i){this.lifecycle.onAttributeChanged(e,t,i)}setupShadowDOM(){this.shadow.innerHTML="";const e=document.createElement("style");e.textContent=`
194
454
  :host {
195
455
  display: block;
196
456
  width: 100%;
@@ -210,517 +470,1386 @@ ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.delete
210
470
  width: 100%;
211
471
  height: 100%;
212
472
  }
213
- `,this.shadow.appendChild(t),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async doInit(t){const e=this.getAttribute("src"),i=this.getAttribute("depth-src"),o=this.getAttribute("depth-meta");if(this.container)try{const[n,s]=await Promise.all([this.createVideoElement(e),vt(i,o)]);if(t.aborted){n.remove();return}this.video=n,this.loopCount=0,this.attachVideoEventListeners(n);const h=Ht(s.frames,s.meta.width,s.meta.height),a=Xt(h),l=this.hasAttribute("parallax-max")?this.parallaxMax/Math.max(n.videoWidth,1):a.parallaxStrength,u=this.hasAttribute("overscan")?this.overscan:a.overscanPadding;let f;try{const d=await it.create(s,s.meta.width,s.meta.height);this.depthWorker=d,f=m=>d.sample(m)}catch{const d=new gt(s,s.meta.width,s.meta.height);f=m=>d.sample(m)}if(t.aborted){n.remove(),this.depthWorker?.dispose(),this.depthWorker=null;return}this.renderer=new Z(this.container,{parallaxStrength:l,pomEnabled:!0,pomSteps:a.pomSteps,overscanPadding:u,contrastLow:a.contrastLow,contrastHigh:a.contrastHigh,verticalReduction:a.verticalReduction,dofStart:a.dofStart,dofStrength:a.dofStrength}),this.renderer.initialize(n,s.meta.width,s.meta.height),this.inputHandler=new qt(this);const c=this.parallaxX,g=this.parallaxY;if(this.renderer.start(n,f,()=>{if(!this.inputHandler)return{x:0,y:0};const d=this.inputHandler.update();return{x:d.x*c,y:d.y*g}},(d,m)=>{this.emit("layershift-parallax:frame",{currentTime:d,frameNumber:m})}),this.shouldAutoplay){n.currentTime=0;try{await n.play()}catch{}}if(t.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-parallax:ready",{videoWidth:n.videoWidth,videoHeight:n.videoHeight,duration:n.duration,depthProfile:h,derivedParams:a})}catch(n){const s=n instanceof Error?n.message:"Failed to initialize.";console.error("<layershift-parallax>: Failed to initialize.",n),this.emit("layershift-parallax:error",{message:s})}}async createVideoElement(t){const e=document.createElement("video");return e.crossOrigin="anonymous",e.setAttribute("crossorigin","anonymous"),e.playsInline=!0,e.setAttribute("playsinline",""),e.setAttribute("webkit-playsinline","true"),e.muted=this.shouldMute,e.defaultMuted=this.shouldMute,this.shouldMute&&e.setAttribute("muted",""),e.loop=this.shouldLoop,e.preload="auto",e.style.display="none",e.src=t,this.shadow.appendChild(e),await new Promise((i,o)=>{if(e.readyState>=HTMLMediaElement.HAVE_METADATA){i();return}const n=()=>{h(),i()},s=()=>{h(),o(new Error("Failed to load video metadata."))},h=()=>{e.removeEventListener("loadedmetadata",n),e.removeEventListener("error",s)};e.addEventListener("loadedmetadata",n),e.addEventListener("error",s),e.load()}),e}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.depthWorker?.dispose(),this.depthWorker=null,this.video&&(this.video.pause(),this.video.removeAttribute("src"),this.video.load(),this.video.remove(),this.video=null),this.loopCount=0,this.container=null}}function N(r,t,e){return Math.min(e,Math.max(t,r))}function ot(r,t,e){return r+(t-r)*e}const Zt=`#version 300 es
214
- in vec2 aPosition;
215
- uniform vec2 uMeshScale;
216
- void main() {
217
- gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
473
+ `,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async doInit(e){const t=this.getAttribute("src"),i=this.getAttribute("depth-src"),n=this.getAttribute("depth-meta");if(this.container)try{const[r,s]=await Promise.all([this.createVideoElement(t),Se(i,n)]);if(e.aborted){r.remove();return}this.video=r,this.loopCount=0,this.attachVideoEventListeners(r);const a=ot(s.frames,s.meta.width,s.meta.height),l=st(a),u=this.hasAttribute("parallax-max")?this.parallaxMax/Math.max(r.videoWidth,1):l.parallaxStrength,h=this.hasAttribute("overscan")?this.overscan:l.overscanPadding,f=new be(s),c=b=>f.sample(b),g=await Ce(this.gpuBackend);if(e.aborted)return;const v={parallaxStrength:u,pomEnabled:!0,pomSteps:l.pomSteps,overscanPadding:h,quality:this.quality,contrastLow:l.contrastLow,contrastHigh:l.contrastHigh,verticalReduction:l.verticalReduction,dofStart:l.dofStart,dofStrength:l.dofStrength};g.type==="webgpu"&&g.device&&g.adapter?this.renderer=new Gt(this.container,v,g.device,g.adapter.info):this.renderer=new Pt(this.container,v),this.renderer.initialize(r,s.meta.width,s.meta.height),this.inputHandler=new Nt(this);const d=this.parallaxX,A=this.parallaxY;if(this.renderer.start(r,c,()=>{if(!this.inputHandler)return{x:0,y:0};const b=this.inputHandler.update();return{x:b.x*d,y:b.y*A}},(b,y)=>{this.emit("layershift-parallax:frame",{currentTime:b,frameNumber:y})}),this.shouldAutoplay){r.currentTime=0;try{await r.play()}catch{}}if(e.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-parallax:ready",{videoWidth:r.videoWidth,videoHeight:r.videoHeight,duration:r.duration,depthProfile:a,derivedParams:l})}catch(r){const s=r instanceof Error?r.message:"Failed to initialize.";console.error("<layershift-parallax>: Failed to initialize.",r),this.emit("layershift-parallax:error",{message:s})}}async createVideoElement(e){const t=document.createElement("video");return t.crossOrigin="anonymous",t.setAttribute("crossorigin","anonymous"),t.playsInline=!0,t.setAttribute("playsinline",""),t.setAttribute("webkit-playsinline","true"),t.muted=this.shouldMute,t.defaultMuted=this.shouldMute,this.shouldMute&&t.setAttribute("muted",""),t.loop=this.shouldLoop,t.preload="auto",t.style.display="none",t.src=e,this.shadow.appendChild(t),await new Promise((i,n)=>{if(t.readyState>=HTMLMediaElement.HAVE_METADATA){i();return}const r=()=>{a(),i()},s=()=>{a(),n(new Error("Failed to load video metadata."))},a=()=>{t.removeEventListener("loadedmetadata",r),t.removeEventListener("error",s)};t.addEventListener("loadedmetadata",r),t.addEventListener("error",s),t.load()}),t}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.video&&(this.video.pause(),this.video.removeAttribute("src"),this.video.load(),this.video.remove(),this.video=null),this.loopCount=0,this.container=null}}function j(o,e,t){return Math.min(t,Math.max(e,o))}function oe(o,e,t){return o+(e-o)*t}class se{gl;hasColorBufferFloat;maskFbo=null;maskTex=null;pingFbo=null;pingTex=null;pongFbo=null;pongTex=null;distFbo=null;distTex=null;_width=0;_height=0;_dirty=!0;constructor(e,t){this.gl=e,this.hasColorBufferFloat=t}get width(){return this._width}get height(){return this._height}get isDirty(){return this._dirty}get distanceTexture(){return this.distTex}get maskTexture(){return this.maskTex}markDirty(){this._dirty=!0}createResources(e,t,i){const n=this.gl;this.dispose();const r=Math.max(1,Math.round(e/i)),s=Math.max(1,Math.round(t/i));this._width=r,this._height=s;const a=(u,h,f,c)=>{const g=n.createFramebuffer();return n.bindFramebuffer(n.FRAMEBUFFER,g),n.bindTexture(n.TEXTURE_2D,u),n.texStorage2D(n.TEXTURE_2D,1,h,f,c),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.framebufferTexture2D(n.FRAMEBUFFER,n.COLOR_ATTACHMENT0,n.TEXTURE_2D,u,0),n.bindFramebuffer(n.FRAMEBUFFER,null),g};this.maskTex=n.createTexture(),this.maskFbo=a(this.maskTex,n.R8,r,s);const l=this.hasColorBufferFloat?n.RG16F:n.RGBA8;this.pingTex=n.createTexture(),this.pingFbo=a(this.pingTex,l,r,s),this.pongTex=n.createTexture(),this.pongFbo=a(this.pongTex,l,r,s),this.distTex=n.createTexture(),this.distFbo=a(this.distTex,n.RGBA8,r,s),this._dirty=!0}compute(e){const t=this.gl;if(!this.maskFbo||!this.pingFbo||!this.pongFbo||!this.distFbo)return;const i=this._width,n=this._height;if(i===0||n===0)return;t.viewport(0,0,i,n),t.disable(t.STENCIL_TEST),t.disable(t.BLEND),t.bindFramebuffer(t.FRAMEBUFFER,this.maskFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(e.maskPass.program),t.uniform2f(e.maskPass.uniforms.uMeshScale,e.meshScaleX,e.meshScaleY),t.bindVertexArray(e.maskVao),t.drawElements(t.TRIANGLES,e.stencilIndexCount,t.UNSIGNED_SHORT,0),t.bindFramebuffer(t.FRAMEBUFFER,this.pingFbo),t.clearColor(-1,-1,0,0),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(e.seedPass.program),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(e.seedPass.uniforms.uMask,5),t.uniform2f(e.seedPass.uniforms.uTexelSize,1/i,1/n),t.bindVertexArray(e.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const r=se.computeFloodIterations(i,n);t.useProgram(e.floodPass.program);let s=this.pingTex,a=this.pongFbo,l=this.pongTex;for(let u=0;u<r.length;u++){const h=r[u]/Math.max(i,n);t.bindFramebuffer(t.FRAMEBUFFER,a),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,s),t.uniform1i(e.floodPass.uniforms.uSeedTex,5),t.uniform1f(e.floodPass.uniforms.uStepSize,h),t.bindVertexArray(e.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const f=s,c=a;s=l,a=c===this.pongFbo?this.pingFbo:this.pongFbo,l=f}t.bindFramebuffer(t.FRAMEBUFFER,this.distFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(e.distPass.program),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,s),t.uniform1i(e.distPass.uniforms.uSeedTex,5),t.activeTexture(t.TEXTURE6),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(e.distPass.uniforms.uMask,6),t.uniform1f(e.distPass.uniforms.uBevelWidth,e.distRange),t.bindVertexArray(e.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.activeTexture(t.TEXTURE4),t.bindTexture(t.TEXTURE_2D,this.distTex),t.bindFramebuffer(t.FRAMEBUFFER,null),this._dirty=!1}static computeFloodIterations(e,t){const i=Math.max(e,t),n=[];let r=Math.ceil(i/2);for(;r>=1;)n.push(r),r=Math.floor(r/2);return n}dispose(){const e=this.gl;this.maskTex&&(e.deleteTexture(this.maskTex),this.maskTex=null),this.maskFbo&&(e.deleteFramebuffer(this.maskFbo),this.maskFbo=null),this.pingTex&&(e.deleteTexture(this.pingTex),this.pingTex=null),this.pingFbo&&(e.deleteFramebuffer(this.pingFbo),this.pingFbo=null),this.pongTex&&(e.deleteTexture(this.pongTex),this.pongTex=null),this.pongFbo&&(e.deleteFramebuffer(this.pongFbo),this.pongFbo=null),this.distTex&&(e.deleteTexture(this.distTex),this.distTex=null),this.distFbo&&(e.deleteFramebuffer(this.distFbo),this.distFbo=null),this._width=0,this._height=0,this._dirty=!0}}const Xt=`#version 300 es
474
+ in vec2 aPosition;
475
+ uniform vec2 uMeshScale;
476
+ void main() {
477
+ gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
478
+ }
479
+ `,Ht=`#version 300 es
480
+ precision lowp float;
481
+ out vec4 fragColor;
482
+ void main() { fragColor = vec4(0.0); }
483
+ `,Wt=`#version 300 es
484
+ in vec2 aPosition;
485
+ uniform vec2 uMeshScale;
486
+ void main() {
487
+ gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
488
+ }
489
+ `,jt=`#version 300 es
490
+ precision lowp float;
491
+ out vec4 fragColor;
492
+ void main() { fragColor = vec4(1.0); }
493
+ `,zt=`#version 300 es
494
+ in vec2 aPosition;
495
+ out vec2 vUv;
496
+ void main() {
497
+ vUv = aPosition * 0.5 + 0.5;
498
+ gl_Position = vec4(aPosition, 0.0, 1.0);
499
+ }
500
+ `,qt=`#version 300 es
501
+ precision highp float;
502
+ uniform sampler2D uMask;
503
+ uniform vec2 uTexelSize;
504
+ in vec2 vUv;
505
+ out vec2 fragSeed;
506
+
507
+ void main() {
508
+ float center = texture(uMask, vUv).r;
509
+ float left = texture(uMask, vUv + vec2(-uTexelSize.x, 0.0)).r;
510
+ float right = texture(uMask, vUv + vec2( uTexelSize.x, 0.0)).r;
511
+ float up = texture(uMask, vUv + vec2(0.0, uTexelSize.y)).r;
512
+ float down = texture(uMask, vUv + vec2(0.0, -uTexelSize.y)).r;
513
+
514
+ bool isEdge = (step(0.5, center) != step(0.5, left)) ||
515
+ (step(0.5, center) != step(0.5, right)) ||
516
+ (step(0.5, center) != step(0.5, up)) ||
517
+ (step(0.5, center) != step(0.5, down));
518
+
519
+ if (isEdge) {
520
+ fragSeed = vUv;
521
+ } else {
522
+ fragSeed = vec2(-1.0);
218
523
  }
219
- `,$t=`#version 300 es
220
- precision lowp float;
221
- out vec4 fragColor;
222
- void main() { fragColor = vec4(0.0); }
524
+ }
525
+ `,Yt=`#version 300 es
526
+ in vec2 aPosition;
527
+ out vec2 vUv;
528
+ void main() {
529
+ vUv = aPosition * 0.5 + 0.5;
530
+ gl_Position = vec4(aPosition, 0.0, 1.0);
531
+ }
532
+ `,Zt=`#version 300 es
533
+ precision highp float;
534
+ uniform sampler2D uSeedTex;
535
+ uniform float uStepSize;
536
+ in vec2 vUv;
537
+ out vec2 fragSeed;
538
+
539
+ void main() {
540
+ vec2 bestSeed = texture(uSeedTex, vUv).rg;
541
+ float bestDist = (bestSeed.x < 0.0) ? 1.0e10 : distance(vUv, bestSeed);
542
+
543
+ for (int dy = -1; dy <= 1; dy++) {
544
+ for (int dx = -1; dx <= 1; dx++) {
545
+ if (dx == 0 && dy == 0) continue;
546
+ vec2 offset = vec2(float(dx), float(dy)) * uStepSize;
547
+ vec2 sampleUv = vUv + offset;
548
+ if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
549
+ vec2 neighborSeed = texture(uSeedTex, sampleUv).rg;
550
+ if (neighborSeed.x < 0.0) continue;
551
+ float d = distance(vUv, neighborSeed);
552
+ if (d < bestDist) {
553
+ bestDist = d;
554
+ bestSeed = neighborSeed;
555
+ }
556
+ }
557
+ }
558
+
559
+ fragSeed = bestSeed;
560
+ }
223
561
  `,Jt=`#version 300 es
224
- in vec2 aPosition;
225
- uniform vec2 uMeshScale;
226
- void main() {
227
- gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
562
+ in vec2 aPosition;
563
+ out vec2 vUv;
564
+ void main() {
565
+ vUv = aPosition * 0.5 + 0.5;
566
+ gl_Position = vec4(aPosition, 0.0, 1.0);
567
+ }
568
+ `,$t=`#version 300 es
569
+ precision highp float;
570
+ uniform sampler2D uSeedTex;
571
+ uniform sampler2D uMask;
572
+ uniform float uBevelWidth;
573
+ in vec2 vUv;
574
+ out vec4 fragDist;
575
+
576
+ void main() {
577
+ float mask = texture(uMask, vUv).r;
578
+ if (mask < 0.5) {
579
+ fragDist = vec4(0.0);
580
+ return;
228
581
  }
582
+
583
+ vec2 seed = texture(uSeedTex, vUv).rg;
584
+ if (seed.x < 0.0) {
585
+ fragDist = vec4(1.0);
586
+ return;
587
+ }
588
+
589
+ float d = distance(vUv, seed);
590
+ float normalized = clamp(d / max(uBevelWidth, 0.001), 0.0, 1.0);
591
+ fragDist = vec4(normalized, 0.0, 0.0, 1.0);
592
+ }
229
593
  `,Kt=`#version 300 es
230
- precision lowp float;
231
- out vec4 fragColor;
232
- void main() { fragColor = vec4(1.0); }
594
+ in vec2 aPosition;
595
+ uniform vec2 uUvOffset;
596
+ uniform vec2 uUvScale;
597
+ out vec2 vUv;
598
+ out vec2 vScreenUv;
599
+ void main() {
600
+ vec2 baseUv = aPosition * 0.5 + 0.5;
601
+ vUv = baseUv * uUvScale + uUvOffset;
602
+ vScreenUv = baseUv;
603
+ gl_Position = vec4(aPosition, 0.0, 1.0);
604
+ }
233
605
  `,Qt=`#version 300 es
234
- in vec2 aPosition;
235
- out vec2 vUv;
236
- void main() {
237
- vUv = aPosition * 0.5 + 0.5;
238
- gl_Position = vec4(aPosition, 0.0, 1.0);
239
- }
240
- `,te=`#version 300 es
241
- precision highp float;
242
- uniform sampler2D uMask;
243
- uniform vec2 uTexelSize;
244
- in vec2 vUv;
245
- out vec2 fragSeed;
246
-
247
- void main() {
248
- float center = texture(uMask, vUv).r;
249
- float left = texture(uMask, vUv + vec2(-uTexelSize.x, 0.0)).r;
250
- float right = texture(uMask, vUv + vec2( uTexelSize.x, 0.0)).r;
251
- float up = texture(uMask, vUv + vec2(0.0, uTexelSize.y)).r;
252
- float down = texture(uMask, vUv + vec2(0.0, -uTexelSize.y)).r;
253
-
254
- bool isEdge = (step(0.5, center) != step(0.5, left)) ||
255
- (step(0.5, center) != step(0.5, right)) ||
256
- (step(0.5, center) != step(0.5, up)) ||
257
- (step(0.5, center) != step(0.5, down));
258
-
259
- if (isEdge) {
260
- fragSeed = vUv;
261
- } else {
262
- fragSeed = vec2(-1.0);
606
+ precision highp float;
607
+
608
+ #define MAX_POM_STEPS 32
609
+
610
+ uniform sampler2D uImage;
611
+ uniform sampler2D uDepth;
612
+ uniform vec2 uOffset;
613
+ uniform float uStrength;
614
+ uniform int uPomSteps;
615
+
616
+ // Lens transform: remap depth curve for exaggerated/compressed depth feel
617
+ uniform float uDepthPower; // >1 = telephoto, <1 = wide-angle
618
+ uniform float uDepthScale; // multiplier on depth range
619
+ uniform float uDepthBias; // shift depth origin
620
+
621
+ // Depth-adaptive contrast
622
+ uniform float uContrastLow;
623
+ uniform float uContrastHigh;
624
+ uniform float uVerticalReduction;
625
+
626
+ // DOF
627
+ uniform float uDofStart;
628
+ uniform float uDofStrength;
629
+ uniform vec2 uImageTexelSize;
630
+
631
+ // Interior mood
632
+ uniform float uFogDensity; // volumetric fog bias (0 = none, 0.3 = subtle)
633
+ uniform vec3 uFogColor; // fog tint color
634
+ uniform float uColorShift; // warm/cool grading shift
635
+ uniform float uBrightnessBias; // overall brightness adjustment
636
+
637
+ in vec2 vUv;
638
+ in vec2 vScreenUv;
639
+
640
+ layout(location = 0) out vec4 fragColor;
641
+ layout(location = 1) out vec4 fragDepth;
642
+
643
+ // Apply lens transform to raw depth
644
+ float lensDepth(float raw) {
645
+ float d = smoothstep(uContrastLow, uContrastHigh, raw);
646
+ d = pow(d, uDepthPower) * uDepthScale + uDepthBias;
647
+ return clamp(d, 0.0, 1.0);
648
+ }
649
+
650
+ float edgeFade(vec2 uv) {
651
+ float margin = uStrength * 1.5;
652
+ float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
653
+ float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
654
+ return fadeX * fadeY;
655
+ }
656
+
657
+ // POM ray-march with lens-transformed depth
658
+ vec2 pomDisplace(vec2 uv, out float hitDepth) {
659
+ float layerD = 1.0 / float(uPomSteps);
660
+ vec2 scaledOffset = uOffset;
661
+ scaledOffset.y *= uVerticalReduction;
662
+ vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
663
+ float currentLayerDepth = 0.0;
664
+ vec2 currentUV = uv;
665
+ float fade = edgeFade(uv);
666
+
667
+ for (int i = 0; i < MAX_POM_STEPS; i++) {
668
+ if (i >= uPomSteps) break;
669
+ float raw = texture(uDepth, currentUV).r;
670
+ float depthAtUV = 1.0 - lensDepth(raw);
671
+ if (currentLayerDepth > depthAtUV) {
672
+ vec2 prevUV = currentUV - deltaUV;
673
+ float prevLayerD = currentLayerDepth - layerD;
674
+ float prevRaw = texture(uDepth, prevUV).r;
675
+ float prevDepthAtUV = 1.0 - lensDepth(prevRaw);
676
+ float afterD = depthAtUV - currentLayerDepth;
677
+ float beforeD = prevDepthAtUV - prevLayerD;
678
+ float t = afterD / (afterD - beforeD);
679
+ vec2 hitUV = mix(currentUV, prevUV, t);
680
+ hitDepth = mix(depthAtUV, prevDepthAtUV, t);
681
+ return mix(uv, hitUV, fade);
263
682
  }
683
+ currentUV += deltaUV;
684
+ currentLayerDepth += layerD;
264
685
  }
265
- `,ee=`#version 300 es
266
- in vec2 aPosition;
267
- out vec2 vUv;
268
- void main() {
269
- vUv = aPosition * 0.5 + 0.5;
270
- gl_Position = vec4(aPosition, 0.0, 1.0);
686
+ hitDepth = 1.0 - lensDepth(texture(uDepth, currentUV).r);
687
+ return mix(uv, currentUV, fade);
688
+ }
689
+
690
+ void main() {
691
+ float hitDepth;
692
+ vec2 displaced = pomDisplace(vUv, hitDepth);
693
+ displaced = clamp(displaced, vec2(0.0), vec2(1.0));
694
+
695
+ vec4 color = texture(uImage, displaced);
696
+
697
+ // DOF: blur far objects
698
+ float rawDepthAtHit = texture(uDepth, displaced).r;
699
+ float lensD = lensDepth(rawDepthAtHit);
700
+ float dof = smoothstep(uDofStart, 1.0, lensD) * uDofStrength;
701
+ if (dof > 0.01) {
702
+ vec2 ts = uImageTexelSize;
703
+ vec4 blurred = (
704
+ texture(uImage, displaced + vec2( ts.x, 0.0)) +
705
+ texture(uImage, displaced + vec2(-ts.x, 0.0)) +
706
+ texture(uImage, displaced + vec2( 0.0, ts.y)) +
707
+ texture(uImage, displaced + vec2( 0.0, -ts.y)) +
708
+ texture(uImage, displaced + vec2( ts.x, ts.y)) +
709
+ texture(uImage, displaced + vec2(-ts.x, -ts.y)) +
710
+ texture(uImage, displaced + vec2( ts.x, -ts.y)) +
711
+ texture(uImage, displaced + vec2(-ts.x, ts.y))
712
+ ) * 0.125;
713
+ color = mix(color, blurred, dof);
271
714
  }
272
- `,ie=`#version 300 es
273
- precision highp float;
274
- uniform sampler2D uSeedTex;
275
- uniform float uStepSize;
276
- in vec2 vUv;
277
- out vec2 fragSeed;
278
-
279
- void main() {
280
- vec2 bestSeed = texture(uSeedTex, vUv).rg;
281
- float bestDist = (bestSeed.x < 0.0) ? 1.0e10 : distance(vUv, bestSeed);
282
-
283
- for (int dy = -1; dy <= 1; dy++) {
284
- for (int dx = -1; dx <= 1; dx++) {
285
- if (dx == 0 && dy == 0) continue;
286
- vec2 offset = vec2(float(dx), float(dy)) * uStepSize;
287
- vec2 sampleUv = vUv + offset;
288
- if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
289
- vec2 neighborSeed = texture(uSeedTex, sampleUv).rg;
290
- if (neighborSeed.x < 0.0) continue;
291
- float d = distance(vUv, neighborSeed);
292
- if (d < bestDist) {
293
- bestDist = d;
294
- bestSeed = neighborSeed;
295
- }
296
- }
297
- }
298
715
 
299
- fragSeed = bestSeed;
300
- }
301
- `,re=`#version 300 es
302
- in vec2 aPosition;
303
- out vec2 vUv;
304
- void main() {
305
- vUv = aPosition * 0.5 + 0.5;
306
- gl_Position = vec4(aPosition, 0.0, 1.0);
716
+ // Volumetric fog bias: far objects fade into fog color
717
+ float fogFactor = smoothstep(0.3, 1.0, lensD) * uFogDensity;
718
+ color.rgb = mix(color.rgb, uFogColor, fogFactor);
719
+
720
+ // Color grading shift: warm near, cool far (or vice versa)
721
+ float gradeAmount = (lensD - 0.5) * uColorShift;
722
+ color.r += gradeAmount * 0.08;
723
+ color.b -= gradeAmount * 0.08;
724
+
725
+ // Brightness bias
726
+ color.rgb *= (1.0 + uBrightnessBias);
727
+
728
+ // Subtle vignette inside portal
729
+ float dist = length(vScreenUv - 0.5) * 1.4;
730
+ color.rgb *= 1.0 - pow(dist, 3.0) * 0.3;
731
+
732
+ fragColor = color;
733
+ // Write lens-transformed depth to second attachment for boundary effects
734
+ fragDepth = vec4(lensD, 0.0, 0.0, 1.0);
735
+ }
736
+ `,ei=`#version 300 es
737
+ in vec2 aPosition;
738
+ out vec2 vUv;
739
+ void main() {
740
+ vUv = aPosition * 0.5 + 0.5;
741
+ gl_Position = vec4(aPosition, 0.0, 1.0);
742
+ }
743
+ `,ti=`#version 300 es
744
+ precision highp float;
745
+ uniform sampler2D uInteriorColor;
746
+ uniform sampler2D uDistField;
747
+ uniform float uEdgeOcclusionWidth; // how far edge darkening extends
748
+ uniform float uEdgeOcclusionStrength; // how strong (0=none, 1=full black)
749
+
750
+ in vec2 vUv;
751
+ out vec4 fragColor;
752
+
753
+ // sRGB <-> linear conversions for correct lighting math
754
+ vec3 toLinear(vec3 s) {
755
+ return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
756
+ }
757
+ vec3 toSRGB(vec3 l) {
758
+ return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
759
+ }
760
+
761
+ void main() {
762
+ vec4 color = texture(uInteriorColor, vUv);
763
+ float dist = texture(uDistField, vUv).r; // 0=edge, 1=deep interior
764
+
765
+ // Emissive passthrough: preserve original video luminance.
766
+ // Only apply a subtle edge occlusion ramp to sell chamfer->interior depth.
767
+ vec3 linear = toLinear(color.rgb);
768
+ float occ = smoothstep(0.0, uEdgeOcclusionWidth, dist);
769
+ linear *= mix(1.0 - uEdgeOcclusionStrength, 1.0, occ);
770
+
771
+ fragColor = vec4(toSRGB(linear), color.a);
772
+ }
773
+ `,ii=`#version 300 es
774
+ in vec2 aPosition;
775
+ in vec2 aNormal;
776
+ uniform float uRimWidth;
777
+ uniform vec2 uMeshScale;
778
+ out vec2 vNormal;
779
+ out vec2 vEdgeUv; // screen-space UV for sampling FBO textures
780
+ out float vEdgeDist; // 0 at edge, 1 at outer extent
781
+
782
+ void main() {
783
+ vec2 scaledPos = aPosition * uMeshScale;
784
+ vec2 scaledNormal = normalize(aNormal * uMeshScale);
785
+ vec2 pos = scaledPos + scaledNormal * uRimWidth;
786
+
787
+ // Pass screen-space UV of this fragment for FBO sampling
788
+ vEdgeUv = pos * 0.5 + 0.5;
789
+ vNormal = scaledNormal;
790
+
791
+ // Distance from the actual edge (0) to the outer rim extent (1)
792
+ vEdgeDist = length(pos - scaledPos) / max(uRimWidth, 0.001);
793
+
794
+ gl_Position = vec4(pos, 0.0, 1.0);
795
+ }
796
+ `,ni=`#version 300 es
797
+ precision highp float;
798
+
799
+ uniform sampler2D uInteriorColor;
800
+ uniform sampler2D uInteriorDepth;
801
+ uniform sampler2D uDistField;
802
+ uniform float uRimIntensity;
803
+ uniform vec3 uRimColor;
804
+ uniform float uRefractionStrength;
805
+ uniform float uChromaticStrength;
806
+ uniform float uOcclusionIntensity;
807
+ uniform vec2 uTexelSize; // 1.0 / viewport resolution
808
+
809
+ // Volumetric edge wall
810
+ uniform float uEdgeThickness;
811
+ uniform float uEdgeSpecular;
812
+ uniform vec3 uEdgeColor;
813
+ uniform vec2 uLightDir;
814
+ uniform float uBevelIntensity;
815
+
816
+ in vec2 vNormal;
817
+ in vec2 vEdgeUv;
818
+ in float vEdgeDist;
819
+ out vec4 fragColor;
820
+
821
+ void main() {
822
+ // Clamp UV to valid range for texture sampling
823
+ vec2 sampleUv = clamp(vEdgeUv, vec2(0.001), vec2(0.999));
824
+
825
+ // Sample interior depth at this boundary location
826
+ float interiorDepth = texture(uInteriorDepth, sampleUv).r;
827
+
828
+ // === DEPTH-REACTIVE RIM (structural seam) ===
829
+ float depthReactivity = 1.0 - interiorDepth; // 1=near, 0=far
830
+ float rimProfile = 1.0 - smoothstep(0.0, 1.0, vEdgeDist);
831
+ rimProfile = pow(rimProfile, 1.5); // sharper falloff = more structural
832
+
833
+ float depthPressure = mix(0.2, 1.0, depthReactivity * depthReactivity);
834
+ float rim = rimProfile * depthPressure * uRimIntensity;
835
+
836
+ vec3 rimCol = uRimColor;
837
+ rimCol.r += depthReactivity * 0.15;
838
+ rimCol.g += depthReactivity * 0.05;
839
+
840
+ // === REFRACTION DISTORTION ===
841
+ vec2 ts = uTexelSize * 3.0;
842
+ float dLeft = texture(uInteriorDepth, sampleUv + vec2(-ts.x, 0.0)).r;
843
+ float dRight = texture(uInteriorDepth, sampleUv + vec2( ts.x, 0.0)).r;
844
+ float dUp = texture(uInteriorDepth, sampleUv + vec2(0.0, ts.y)).r;
845
+ float dDown = texture(uInteriorDepth, sampleUv + vec2(0.0, -ts.y)).r;
846
+ vec2 depthGradient = vec2(dRight - dLeft, dUp - dDown);
847
+ vec2 refractUv = sampleUv + depthGradient * uRefractionStrength * rimProfile;
848
+ refractUv = clamp(refractUv, vec2(0.001), vec2(0.999));
849
+
850
+ vec4 refractedColor = texture(uInteriorColor, refractUv);
851
+
852
+ // === CHROMATIC FRINGE ===
853
+ float chromaticAmount = uChromaticStrength * depthReactivity * rimProfile;
854
+ vec2 chromaticDir = vNormal * chromaticAmount;
855
+ float cr = texture(uInteriorColor, refractUv + chromaticDir).r;
856
+ float cg = refractedColor.g;
857
+ float cb = texture(uInteriorColor, refractUv - chromaticDir).b;
858
+ vec3 chromaticColor = vec3(cr, cg, cb);
859
+
860
+ // === OCCLUSION CONTACT SHADOW ===
861
+ float occlusionAmount = smoothstep(0.4, 0.0, interiorDepth) * uOcclusionIntensity * rimProfile;
862
+
863
+ // === VOLUMETRIC EDGE WALL ===
864
+ // Sample distance field to get the inner-side distance at this boundary location
865
+ float edgeDist = texture(uDistField, sampleUv).r;
866
+ float wallZone = smoothstep(uEdgeThickness, 0.0, edgeDist) * rimProfile;
867
+
868
+ // Wall lighting from distance field gradient
869
+ vec2 dtx = vec2(1.0) / vec2(textureSize(uDistField, 0));
870
+ float wdL = texture(uDistField, sampleUv + vec2(-dtx.x, 0.0)).r;
871
+ float wdR = texture(uDistField, sampleUv + vec2( dtx.x, 0.0)).r;
872
+ float wdU = texture(uDistField, sampleUv + vec2(0.0, dtx.y)).r;
873
+ float wdD = texture(uDistField, sampleUv + vec2(0.0, -dtx.y)).r;
874
+ vec2 wallNormal = vec2(wdR - wdL, wdU - wdD);
875
+ float wnLen = length(wallNormal);
876
+ if (wnLen > 0.001) wallNormal /= wnLen;
877
+
878
+ float wallSpec = pow(max(dot(wallNormal, uLightDir), 0.0), 16.0) * uEdgeSpecular;
879
+ vec3 wallColor = mix(refractedColor.rgb * 0.4, uEdgeColor, 0.3);
880
+ wallColor += vec3(wallSpec);
881
+
882
+ // === COMPOSITE ===
883
+ vec3 color = mix(refractedColor.rgb, chromaticColor, min(chromaticAmount * 10.0, 1.0));
884
+ color *= (1.0 - occlusionAmount * 0.4);
885
+
886
+ // Blend in volumetric wall
887
+ color = mix(color, wallColor, wallZone * uBevelIntensity);
888
+
889
+ // Add rim energy on top
890
+ color += rimCol * rim;
891
+
892
+ // Alpha: rim edge fades out
893
+ float alpha = rimProfile * max(rim, occlusionAmount + chromaticAmount * 5.0 + wallZone * 0.5);
894
+ alpha = clamp(alpha, 0.0, 1.0);
895
+
896
+ fragColor = vec4(color * alpha, alpha);
897
+ }
898
+ `,ri=`#version 300 es
899
+ in vec2 aPosition;
900
+ in vec3 aNormal3;
901
+ in float aLerpT; // 0 = inner (at silhouette), 1 = outer edge
902
+ uniform vec2 uMeshScale;
903
+ out vec3 vNormal;
904
+ out vec2 vScreenUv;
905
+ out float vLerpT;
906
+
907
+ void main() {
908
+ vec2 sp = aPosition * uMeshScale;
909
+ vNormal = aNormal3;
910
+ vScreenUv = sp * 0.5 + 0.5;
911
+ vLerpT = aLerpT;
912
+ gl_Position = vec4(sp, 0.0, 1.0);
913
+ }
914
+ `,oi=`#version 300 es
915
+ precision highp float;
916
+ uniform vec3 uLightDir3;
917
+ uniform vec3 uChamferColor;
918
+ uniform float uChamferAmbient;
919
+ uniform float uChamferSpecular;
920
+ uniform float uChamferShininess;
921
+ uniform sampler2D uInteriorColor;
922
+ uniform vec2 uTexelSize; // 1 / viewport resolution
923
+
924
+ in vec3 vNormal;
925
+ in vec2 vScreenUv;
926
+ in float vLerpT;
927
+ out vec4 fragColor;
928
+
929
+ vec3 toLinear(vec3 s) {
930
+ return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
931
+ }
932
+ vec3 toSRGB(vec3 l) {
933
+ return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
934
+ }
935
+
936
+ // Approximate gaussian blur via 13-tap poisson disc, radius scaled by vLerpT.
937
+ vec3 blurSample(vec2 center, float radius) {
938
+ // Poisson disc offsets (normalized to unit circle)
939
+ const vec2 offsets[12] = vec2[12](
940
+ vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457),
941
+ vec2(-0.203, 0.621), vec2( 0.962, -0.195), vec2( 0.473, -0.480),
942
+ vec2( 0.519, 0.767), vec2( 0.185, -0.893), vec2( 0.507, 0.064),
943
+ vec2(-0.321, -0.860), vec2(-0.791, 0.557), vec2( 0.330, 0.418)
944
+ );
945
+ vec3 sum = texture(uInteriorColor, center).rgb;
946
+ for (int i = 0; i < 12; i++) {
947
+ vec2 uv = center + offsets[i] * radius;
948
+ uv = clamp(uv, vec2(0.001), vec2(0.999));
949
+ sum += texture(uInteriorColor, uv).rgb;
307
950
  }
308
- `,oe=`#version 300 es
309
- precision highp float;
310
- uniform sampler2D uSeedTex;
311
- uniform sampler2D uMask;
312
- uniform float uBevelWidth;
313
- in vec2 vUv;
314
- out vec4 fragDist;
315
-
316
- void main() {
317
- float mask = texture(uMask, vUv).r;
318
- if (mask < 0.5) {
319
- fragDist = vec4(0.0);
320
- return;
321
- }
322
-
323
- vec2 seed = texture(uSeedTex, vUv).rg;
324
- if (seed.x < 0.0) {
325
- fragDist = vec4(1.0);
326
- return;
327
- }
328
-
329
- float d = distance(vUv, seed);
330
- float normalized = clamp(d / max(uBevelWidth, 0.001), 0.0, 1.0);
331
- fragDist = vec4(normalized, 0.0, 0.0, 1.0);
951
+ return sum / 13.0;
952
+ }
953
+
954
+ void main() {
955
+ vec3 N = normalize(vNormal);
956
+ vec3 L = normalize(uLightDir3);
957
+ vec3 V = vec3(0.0, 0.0, -1.0); // orthographic view direction
958
+
959
+ // Blinn-Phong lighting in linear space
960
+ float diff = max(dot(N, L), 0.0);
961
+ vec3 H = normalize(L + V);
962
+ float spec = pow(max(dot(N, H), 0.0), uChamferShininess) * uChamferSpecular;
963
+
964
+ // Sample interior video with progressive blur (sharper at inner edge)
965
+ vec2 uv = clamp(vScreenUv, vec2(0.001), vec2(0.999));
966
+ float blurRadius = vLerpT * 12.0 * length(uTexelSize);
967
+ vec3 videoSample = blurRadius > 0.0001
968
+ ? blurSample(uv, blurRadius)
969
+ : texture(uInteriorColor, uv).rgb;
970
+
971
+ // Base color: video tinted through chamfer color (like frosted glass)
972
+ vec3 video = toLinear(videoSample);
973
+ vec3 tint = toLinear(uChamferColor);
974
+ // Blend: mostly video near inner edge, more tinted at outer edge
975
+ vec3 base = mix(video, video * tint * 3.0, vLerpT * 0.5);
976
+
977
+ // Apply Blinn-Phong
978
+ vec3 lit = base * (uChamferAmbient + (1.0 - uChamferAmbient) * diff) + vec3(spec);
979
+ fragColor = vec4(toSRGB(lit), 1.0);
980
+ }
981
+ `;function Ve(o){const e=[];let t=0;for(let i=0;i<o.length-2;i+=2){const n=o[i],r=o[i+1],s=o[i+2],a=o[i+3],l=s-n,u=a-r,h=Math.sqrt(l*l+u*u);if(h<1e-6)continue;const f=-u/h,c=l/h;e.push(n,r,f,c,n,r,-f,-c,s,a,f,c,s,a,f,c,n,r,-f,-c,s,a,-f,-c),t+=6}return{vertices:new Float32Array(e),count:t}}function Ge(o,e,t,i,n){if(i<=0)return{vertices:new Float32Array(0),count:0};const r=n*Math.PI/180,s=-Math.cos(r),a=Math.sin(r),l=[];let u=0;for(let h=0;h<e.length;h++){const f=e[h],v=((h+1<e.length?e[h+1]:o.length)-f)/2;if(v<3)continue;const d=v-1;let A=0;for(let x=0;x<d;x++){const E=f+x*2,P=o[E],U=o[E+1],R=o[E+2],C=o[E+3];A+=P*C-R*U}const b=A>=0?1:-1,y=[],p=[];for(let x=0;x<d;x++){const E=f+x*2,P=o[E+2]-o[E],U=o[E+3]-o[E+1],R=Math.sqrt(P*P+U*U);R<1e-8?(y.push(x>0?y[x-1]:0),p.push(x>0?p[x-1]:0)):(y.push(-U/R*b),p.push(P/R*b))}const m=[],S=[];for(let x=0;x<d;x++){const E=(x-1+d)%d;let P=y[E]+y[x],U=p[E]+p[x];const R=Math.sqrt(P*P+U*U);R>1e-8?(P/=R,U/=R):(P=y[x],U=p[x]),m.push(P),S.push(U)}for(let x=0;x<d;x++){const E=x,P=(x+1)%d,U=f+x*2,R=f+(x+1)%d*2,C=o[U],I=o[U+1],D=o[R],w=o[R+1],L=m[E]*a,M=S[E]*a,O=s,_=m[P]*a,Y=S[P]*a,ie=s,fe=C+m[E]*i,$e=I+S[E]*i,Ki=D+m[P]*i,Qi=w+S[P]*i;l.push(C,I,L,M,O,0),l.push(fe,$e,L,M,O,1),l.push(D,w,_,Y,ie,0),l.push(D,w,_,Y,ie,0),l.push(fe,$e,L,M,O,1),l.push(Ki,Qi,_,Y,ie,1),u+=6}}return{vertices:new Float32Array(l),count:u}}class si extends k{gl=null;stencilPass=null;maskPass=null;jfaSeedPass=null;jfaFloodPass=null;jfaDistPass=null;interiorPass=null;compositePass=null;boundaryPass=null;chamferPass=null;quadVao=null;stencilVao=null;stencilIndexCount=0;maskVao=null;boundaryVao=null;boundaryVertexCount=0;chamferVao=null;chamferVertexCount=0;textures=new Ee;videoSlot;depthSlot;interiorFbo=null;interiorColorTex=null;interiorDepthTex=null;fboWidth=0;fboHeight=0;jfa=null;hasColorBufferFloat=!1;meshAspect=1;meshScaleX=.65;meshScaleY=.65;lightDirX=-.707;lightDirY=.707;lightDir3=[-.5,.7,-.3];config;constructor(e,t){super(e),this.config={...t},this.videoSlot=this.textures.register("video"),this.depthSlot=this.textures.register("depth");const i=this.config.bevelLightAngle*Math.PI/180;this.lightDirX=Math.cos(i),this.lightDirY=Math.sin(i);const n=this.config.lightDirection,r=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);r>1e-6&&(this.lightDir3=[n[0]/r,n[1]/r,n[2]/r]);const s=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0,premultipliedAlpha:!0,stencil:!0,desynchronized:!0,powerPreference:"high-performance"});if(!s)throw new Error("WebGL 2 is not supported.");this.gl=s,this.qualityParams=we(s,t.quality),"drawingBufferColorSpace"in s&&(s.drawingBufferColorSpace="srgb"),this.hasColorBufferFloat=!!s.getExtension("EXT_color_buffer_float"),s.clearColor(0,0,0,0),s.pixelStorei(s.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.setupResizeHandling()}initialize(e,t,i,n){const r=this.gl;r&&(this.disposeTextures(),this.disposeFBO(),this.jfa&&(this.jfa.dispose(),this.jfa=null),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.videoAspect=e.videoWidth/e.videoHeight,this.meshAspect=n.aspect,this.clampDepthDimensions(t,i,this.qualityParams.depthMaxDim),this.videoSlot.texture=r.createTexture(),r.activeTexture(r.TEXTURE0+this.videoSlot.unit),r.bindTexture(r.TEXTURE_2D,this.videoSlot.texture),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MIN_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MAG_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_S,r.CLAMP_TO_EDGE),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_T,r.CLAMP_TO_EDGE),this.depthSlot.texture=r.createTexture(),r.activeTexture(r.TEXTURE0+this.depthSlot.unit),r.bindTexture(r.TEXTURE_2D,this.depthSlot.texture),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MIN_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MAG_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_S,r.CLAMP_TO_EDGE),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_T,r.CLAMP_TO_EDGE),r.texStorage2D(r.TEXTURE_2D,1,r.R8,this.depthWidth,this.depthHeight),this.uploadStencilMesh(n),this.uploadMaskMesh(n),this.uploadBoundaryMesh(n),this.uploadChamferMesh(n),this.interiorPass&&(r.useProgram(this.interiorPass.program),r.uniform1i(this.interiorPass.uniforms.uImage,0),r.uniform1i(this.interiorPass.uniforms.uDepth,1),r.uniform1f(this.interiorPass.uniforms.uStrength,this.config.parallaxStrength),r.uniform1i(this.interiorPass.uniforms.uPomSteps,this.config.pomSteps),r.uniform1f(this.interiorPass.uniforms.uDepthPower,this.config.depthPower),r.uniform1f(this.interiorPass.uniforms.uDepthScale,this.config.depthScale),r.uniform1f(this.interiorPass.uniforms.uDepthBias,this.config.depthBias),r.uniform1f(this.interiorPass.uniforms.uContrastLow,this.config.contrastLow),r.uniform1f(this.interiorPass.uniforms.uContrastHigh,this.config.contrastHigh),r.uniform1f(this.interiorPass.uniforms.uVerticalReduction,this.config.verticalReduction),r.uniform1f(this.interiorPass.uniforms.uDofStart,this.config.dofStart),r.uniform1f(this.interiorPass.uniforms.uDofStrength,this.config.dofStrength),r.uniform2f(this.interiorPass.uniforms.uImageTexelSize,1/e.videoWidth,1/e.videoHeight),r.uniform1f(this.interiorPass.uniforms.uFogDensity,this.config.fogDensity),r.uniform3f(this.interiorPass.uniforms.uFogColor,...this.config.fogColor),r.uniform1f(this.interiorPass.uniforms.uColorShift,this.config.colorShift),r.uniform1f(this.interiorPass.uniforms.uBrightnessBias,this.config.brightnessBias)),this.compositePass&&(r.useProgram(this.compositePass.program),r.uniform1i(this.compositePass.uniforms.uInteriorColor,2),r.uniform1i(this.compositePass.uniforms.uDistField,4),r.uniform1f(this.compositePass.uniforms.uEdgeOcclusionWidth,this.config.edgeOcclusionWidth),r.uniform1f(this.compositePass.uniforms.uEdgeOcclusionStrength,this.config.edgeOcclusionStrength)),this.chamferPass&&(r.useProgram(this.chamferPass.program),r.uniform3f(this.chamferPass.uniforms.uLightDir3,...this.lightDir3),r.uniform3f(this.chamferPass.uniforms.uChamferColor,...this.config.chamferColor),r.uniform1f(this.chamferPass.uniforms.uChamferAmbient,this.config.chamferAmbient),r.uniform1f(this.chamferPass.uniforms.uChamferSpecular,this.config.chamferSpecular),r.uniform1f(this.chamferPass.uniforms.uChamferShininess,this.config.chamferShininess),r.uniform1i(this.chamferPass.uniforms.uInteriorColor,2)),this.boundaryPass&&(r.useProgram(this.boundaryPass.program),r.uniform1i(this.boundaryPass.uniforms.uInteriorColor,2),r.uniform1i(this.boundaryPass.uniforms.uInteriorDepth,3),r.uniform1i(this.boundaryPass.uniforms.uDistField,4),r.uniform1f(this.boundaryPass.uniforms.uRimIntensity,this.config.rimLightIntensity),r.uniform3f(this.boundaryPass.uniforms.uRimColor,...this.config.rimLightColor),r.uniform1f(this.boundaryPass.uniforms.uRefractionStrength,this.config.refractionStrength),r.uniform1f(this.boundaryPass.uniforms.uChromaticStrength,this.config.chromaticStrength),r.uniform1f(this.boundaryPass.uniforms.uOcclusionIntensity,this.config.occlusionIntensity),r.uniform1f(this.boundaryPass.uniforms.uEdgeThickness,this.config.edgeThickness),r.uniform1f(this.boundaryPass.uniforms.uEdgeSpecular,this.config.edgeSpecular),r.uniform3f(this.boundaryPass.uniforms.uEdgeColor,...this.config.edgeColor),r.uniform2f(this.boundaryPass.uniforms.uLightDir,this.lightDirX,this.lightDirY),r.uniform1f(this.boundaryPass.uniforms.uBevelIntensity,this.config.bevelIntensity)),this.recalculateViewportLayout())}uploadStencilMesh(e){const t=this.gl;if(!t||!this.stencilPass)return;this.stencilVao=t.createVertexArray(),t.bindVertexArray(this.stencilVao);const i=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,i),t.bufferData(t.ARRAY_BUFFER,e.vertices,t.STATIC_DRAW);const n=t.getAttribLocation(this.stencilPass.program,"aPosition");t.enableVertexAttribArray(n),t.vertexAttribPointer(n,2,t.FLOAT,!1,0,0);const r=t.createBuffer();t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,r),t.bufferData(t.ELEMENT_ARRAY_BUFFER,e.indices,t.STATIC_DRAW),this.stencilIndexCount=e.indices.length,t.bindVertexArray(null)}uploadMaskMesh(e){const t=this.gl;if(!t||!this.maskPass)return;this.maskVao=t.createVertexArray(),t.bindVertexArray(this.maskVao);const i=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,i),t.bufferData(t.ARRAY_BUFFER,e.vertices,t.STATIC_DRAW);const n=t.getAttribLocation(this.maskPass.program,"aPosition");t.enableVertexAttribArray(n),t.vertexAttribPointer(n,2,t.FLOAT,!1,0,0);const r=t.createBuffer();t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,r),t.bufferData(t.ELEMENT_ARRAY_BUFFER,e.indices,t.STATIC_DRAW),t.bindVertexArray(null)}uploadBoundaryMesh(e){const t=this.gl;if(!t||!this.boundaryPass)return;const i=Ve(e.edgeVertices);if(i.count===0)return;this.boundaryVao=t.createVertexArray(),t.bindVertexArray(this.boundaryVao);const n=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,n),t.bufferData(t.ARRAY_BUFFER,i.vertices,t.STATIC_DRAW);const r=16,s=t.getAttribLocation(this.boundaryPass.program,"aPosition");t.enableVertexAttribArray(s),t.vertexAttribPointer(s,2,t.FLOAT,!1,r,0);const a=t.getAttribLocation(this.boundaryPass.program,"aNormal");a>=0&&(t.enableVertexAttribArray(a),t.vertexAttribPointer(a,2,t.FLOAT,!1,r,8)),this.boundaryVertexCount=i.count,t.bindVertexArray(null)}uploadChamferMesh(e){const t=this.gl;if(!t||!this.chamferPass||this.config.chamferWidth<=0)return;const i=Ge(e.edgeVertices,e.contourOffsets,e.contourIsHole,this.config.chamferWidth,this.config.chamferAngle);if(i.count===0)return;this.chamferVao=t.createVertexArray(),t.bindVertexArray(this.chamferVao);const n=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,n),t.bufferData(t.ARRAY_BUFFER,i.vertices,t.STATIC_DRAW);const r=24,s=t.getAttribLocation(this.chamferPass.program,"aPosition");t.enableVertexAttribArray(s),t.vertexAttribPointer(s,2,t.FLOAT,!1,r,0);const a=t.getAttribLocation(this.chamferPass.program,"aNormal3");a>=0&&(t.enableVertexAttribArray(a),t.vertexAttribPointer(a,3,t.FLOAT,!1,r,8));const l=t.getAttribLocation(this.chamferPass.program,"aLerpT");l>=0&&(t.enableVertexAttribArray(l),t.vertexAttribPointer(l,1,t.FLOAT,!1,r,20)),this.chamferVertexCount=i.count,t.bindVertexArray(null)}disposeChamferGeometry(){const e=this.gl;e&&(this.chamferVao&&(e.deleteVertexArray(this.chamferVao),this.chamferVao=null),this.chamferVertexCount=0)}createFBO(e,t){const i=this.gl;if(!i)return;this.disposeFBO(),this.fboWidth=e,this.fboHeight=t,this.interiorFbo=i.createFramebuffer(),i.bindFramebuffer(i.FRAMEBUFFER,this.interiorFbo),this.interiorColorTex=i.createTexture(),i.activeTexture(i.TEXTURE2),i.bindTexture(i.TEXTURE_2D,this.interiorColorTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,e,t),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,this.interiorColorTex,0),this.interiorDepthTex=i.createTexture(),i.activeTexture(i.TEXTURE3),i.bindTexture(i.TEXTURE_2D,this.interiorDepthTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,e,t),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT1,i.TEXTURE_2D,this.interiorDepthTex,0),i.drawBuffers([i.COLOR_ATTACHMENT0,i.COLOR_ATTACHMENT1]);const n=i.checkFramebufferStatus(i.FRAMEBUFFER);n!==i.FRAMEBUFFER_COMPLETE&&console.error("Interior FBO incomplete:",n),i.bindFramebuffer(i.FRAMEBUFFER,null)}createJFAResources(e,t){const i=this.gl;i&&(this.jfa||(this.jfa=new se(i,this.hasColorBufferFloat)),this.jfa.createResources(e,t,this.qualityParams.jfaDivisor))}computeDistanceField(){!this.jfa||!this.maskPass||!this.jfaSeedPass||!this.jfaFloodPass||!this.jfaDistPass||!this.maskVao||!this.quadVao||this.jfa.compute({maskPass:this.maskPass,seedPass:this.jfaSeedPass,floodPass:this.jfaFloodPass,distPass:this.jfaDistPass,maskVao:this.maskVao,quadVao:this.quadVao,meshScaleX:this.meshScaleX,meshScaleY:this.meshScaleY,stencilIndexCount:this.stencilIndexCount,distRange:Math.max(this.config.bevelWidth,this.config.edgeOcclusionWidth)})}initGPUResources(){const e=this.gl;e&&(this.stencilPass=G(e,"stencil",Xt,Ht,["uMeshScale"]),this.maskPass=G(e,"mask",Wt,jt,["uMeshScale"]),this.jfaSeedPass=G(e,"jfa-seed",zt,qt,["uMask","uTexelSize"]),this.jfaFloodPass=G(e,"jfa-flood",Yt,Zt,["uSeedTex","uStepSize"]),this.jfaDistPass=G(e,"jfa-dist",Jt,$t,["uSeedTex","uMask","uBevelWidth"]),this.interiorPass=G(e,"interior",Kt,Qt,["uImage","uDepth","uOffset","uStrength","uPomSteps","uDepthPower","uDepthScale","uDepthBias","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uFogDensity","uFogColor","uColorShift","uBrightnessBias","uUvOffset","uUvScale"]),this.compositePass=G(e,"composite",ei,ti,["uInteriorColor","uDistField","uEdgeOcclusionWidth","uEdgeOcclusionStrength"]),this.boundaryPass=G(e,"boundary",ii,ni,["uInteriorColor","uInteriorDepth","uDistField","uRimIntensity","uRimColor","uRimWidth","uMeshScale","uRefractionStrength","uChromaticStrength","uOcclusionIntensity","uTexelSize","uEdgeThickness","uEdgeSpecular","uEdgeColor","uLightDir","uBevelIntensity"]),this.chamferPass=G(e,"chamfer",ri,oi,["uMeshScale","uLightDir3","uChamferColor","uChamferAmbient","uChamferSpecular","uChamferShininess","uInteriorColor","uTexelSize"]),this.quadVao=ye(e,this.interiorPass.program),e.disable(e.DEPTH_TEST))}onRenderFrame(){const e=this.gl,t=this.playbackVideo;if(!e||!this.interiorPass||!this.quadVao||!t||t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA||!this.interiorFbo||!this.interiorColorTex||!this.interiorDepthTex)return;this.jfa?.isDirty&&this.maskVao&&(this.computeDistanceField(),e.viewport(0,0,this.canvas.width,this.canvas.height)),e.activeTexture(e.TEXTURE0+this.videoSlot.unit),e.bindTexture(e.TEXTURE_2D,this.videoSlot.texture),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),this.rvfcSupported||this.onDepthUpdate(t.currentTime);let i=0,n=0;if(this.readInput){const r=this.readInput();i=-r.x,n=r.y}if(e.bindFramebuffer(e.FRAMEBUFFER,this.interiorFbo),e.checkFramebufferStatus(e.FRAMEBUFFER)!==e.FRAMEBUFFER_COMPLETE){e.bindFramebuffer(e.FRAMEBUFFER,null);return}e.viewport(0,0,this.fboWidth,this.fboHeight),e.clearColor(0,0,0,1),e.clear(e.COLOR_BUFFER_BIT),e.useProgram(this.interiorPass.program),e.uniform2f(this.interiorPass.uniforms.uOffset,i,n),e.activeTexture(e.TEXTURE0+this.videoSlot.unit),e.bindTexture(e.TEXTURE_2D,this.videoSlot.texture),e.activeTexture(e.TEXTURE0+this.depthSlot.unit),e.bindTexture(e.TEXTURE_2D,this.depthSlot.texture),e.bindVertexArray(this.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4),e.bindFramebuffer(e.FRAMEBUFFER,null),e.clearColor(0,0,0,0),e.viewport(0,0,this.canvas.width,this.canvas.height),e.clear(e.COLOR_BUFFER_BIT|e.STENCIL_BUFFER_BIT),this.stencilVao&&this.stencilPass&&this.stencilIndexCount>0&&(e.enable(e.STENCIL_TEST),e.stencilFunc(e.ALWAYS,1,255),e.stencilOp(e.KEEP,e.KEEP,e.REPLACE),e.stencilMask(255),e.colorMask(!1,!1,!1,!1),e.useProgram(this.stencilPass.program),e.bindVertexArray(this.stencilVao),e.drawElements(e.TRIANGLES,this.stencilIndexCount,e.UNSIGNED_SHORT,0),e.colorMask(!0,!0,!0,!0)),e.stencilFunc(e.EQUAL,1,255),e.stencilMask(0),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,this.interiorColorTex),e.activeTexture(e.TEXTURE3),e.bindTexture(e.TEXTURE_2D,this.interiorDepthTex),e.activeTexture(e.TEXTURE4),e.bindTexture(e.TEXTURE_2D,this.jfa?.distanceTexture??null),e.useProgram(this.compositePass.program),e.bindVertexArray(this.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4),e.disable(e.STENCIL_TEST),this.chamferVao&&this.chamferPass&&this.chamferVertexCount>0&&(e.useProgram(this.chamferPass.program),e.uniform2f(this.chamferPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY),e.uniform2f(this.chamferPass.uniforms.uTexelSize,1/this.canvas.width,1/this.canvas.height),e.bindVertexArray(this.chamferVao),e.drawArrays(e.TRIANGLES,0,this.chamferVertexCount)),this.boundaryVao&&this.boundaryPass&&this.boundaryVertexCount>0&&this.config.rimLightIntensity>0&&(e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),e.useProgram(this.boundaryPass.program),e.bindVertexArray(this.boundaryVao),e.drawArrays(e.TRIANGLES,0,this.boundaryVertexCount),e.disable(e.BLEND))}onDepthUpdate(e){const t=this.gl;if(!t||!this.readDepth||!this.depthSlot.texture)return;const i=this.subsampleDepth(this.readDepth(e));t.activeTexture(t.TEXTURE0+this.depthSlot.unit),t.bindTexture(t.TEXTURE_2D,this.depthSlot.texture),t.texSubImage2D(t.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,t.RED,t.UNSIGNED_BYTE,i)}recalculateViewportLayout(){const e=this.gl;if(!e)return;const{width:t,height:i}=this.getViewportSize(),n=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),r=Math.round(t*n),s=Math.round(i*n);(this.canvas.width!==r||this.canvas.height!==s)&&(this.canvas.width=r,this.canvas.height=s,e.viewport(0,0,r,s)),(this.fboWidth!==r||this.fboHeight!==s)&&this.createFBO(r,s);const a=this.qualityParams.jfaDivisor,l=Math.max(1,Math.round(r/a)),u=Math.max(1,Math.round(s/a));(!this.jfa||this.jfa.width!==l||this.jfa.height!==u)&&this.createJFAResources(r,s),this.computeCoverFitUV(this.config.parallaxStrength,this.config.overscanPadding),this.interiorPass&&(e.useProgram(this.interiorPass.program),e.uniform2f(this.interiorPass.uniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),e.uniform2f(this.interiorPass.uniforms.uUvScale,this.uvScale[0],this.uvScale[1]));const h=t/i,f=.65;this.meshScaleX=f,this.meshScaleY=f,h>this.meshAspect?this.meshScaleX=f*(this.meshAspect/h):this.meshScaleY=f*(h/this.meshAspect),this.stencilPass&&(e.useProgram(this.stencilPass.program),e.uniform2f(this.stencilPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.boundaryPass&&(e.useProgram(this.boundaryPass.program),e.uniform2f(this.boundaryPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY),e.uniform1f(this.boundaryPass.uniforms.uRimWidth,this.config.rimLightWidth),e.uniform2f(this.boundaryPass.uniforms.uTexelSize,1/r,1/s)),this.chamferPass&&(e.useProgram(this.chamferPass.program),e.uniform2f(this.chamferPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.jfa&&this.jfa.markDirty()}onContextRestored(){const e=this.canvas.getContext("webgl2",{alpha:!0,premultipliedAlpha:!0,stencil:!0});e&&(this.gl=e,this.hasColorBufferFloat=!!e.getExtension("EXT_color_buffer_float"),e.clearColor(0,0,0,0),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.recalculateViewportLayout(),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(()=>this.onRenderFrame())))}disposeTextures(){const e=this.gl;e&&this.textures.disposeAll(e)}disposeFBO(){const e=this.gl;e&&(this.interiorColorTex&&(e.deleteTexture(this.interiorColorTex),this.interiorColorTex=null),this.interiorDepthTex&&(e.deleteTexture(this.interiorDepthTex),this.interiorDepthTex=null),this.interiorFbo&&(e.deleteFramebuffer(this.interiorFbo),this.interiorFbo=null),this.fboWidth=0,this.fboHeight=0)}disposeStencilGeometry(){const e=this.gl;e&&(this.stencilVao&&(e.deleteVertexArray(this.stencilVao),this.stencilVao=null),this.maskVao&&(e.deleteVertexArray(this.maskVao),this.maskVao=null),this.stencilIndexCount=0)}disposeBoundaryGeometry(){const e=this.gl;e&&(this.boundaryVao&&(e.deleteVertexArray(this.boundaryVao),this.boundaryVao=null),this.boundaryVertexCount=0)}disposeRenderer(){this.disposeTextures(),this.disposeFBO(),this.jfa&&(this.jfa.dispose(),this.jfa=null),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.disposeGPUResources(),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null)}disposeGPUResources(){const e=this.gl;if(!e)return;const t=[this.stencilPass,this.maskPass,this.jfaSeedPass,this.jfaFloodPass,this.jfaDistPass,this.interiorPass,this.compositePass,this.boundaryPass,this.chamferPass];for(const i of t)i&&i.dispose(e);this.stencilPass=null,this.maskPass=null,this.jfaSeedPass=null,this.jfaFloodPass=null,this.jfaDistPass=null,this.interiorPass=null,this.compositePass=null,this.boundaryPass=null,this.chamferPass=null,this.quadVao&&(e.deleteVertexArray(this.quadVao),this.quadVao=null)}}const ai=`// Stencil mark pass — renders the triangulated SVG mesh into the stencil buffer.
982
+ // Color output is zeroed; only the stencil write matters.
983
+ //
984
+ // Bind group 0, binding 0: uniform buffer with mesh NDC scale factor.
985
+ // Vertex input: triangle list from earcut triangulation (vec2f positions in [-1,1]).
986
+ // Topology: triangle-list (not strip) — mesh comes from earcut.
987
+
988
+ struct Uniforms {
989
+ meshScale: vec2f,
990
+ }
991
+
992
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
993
+
994
+ struct VsOutput {
995
+ @builtin(position) position: vec4f,
996
+ }
997
+
998
+ @vertex
999
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1000
+ var out: VsOutput;
1001
+ out.position = vec4f(aPosition * uniforms.meshScale, 0.0, 1.0);
1002
+ return out;
1003
+ }
1004
+
1005
+ // Fragment outputs black — the stencil buffer write is what matters,
1006
+ // not the color. Matches GLSL: fragColor = vec4(0.0).
1007
+ @fragment
1008
+ fn fs_main() -> @location(0) vec4f {
1009
+ return vec4f(0.0, 0.0, 0.0, 0.0);
1010
+ }
1011
+ `,li=`// Mask pass — renders the triangulated SVG mesh as a solid white fill.
1012
+ // The output texture is consumed by the JFA seed pass for edge detection.
1013
+ //
1014
+ // Bind group 0, binding 0: uniform buffer with mesh NDC scale factor.
1015
+ // Vertex input: triangle list from earcut triangulation (vec2f positions in [-1,1]).
1016
+ // Topology: triangle-list (not strip) — mesh comes from earcut.
1017
+
1018
+ struct Uniforms {
1019
+ meshScale: vec2f,
1020
+ }
1021
+
1022
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1023
+
1024
+ struct VsOutput {
1025
+ @builtin(position) position: vec4f,
1026
+ }
1027
+
1028
+ @vertex
1029
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1030
+ var out: VsOutput;
1031
+ out.position = vec4f(aPosition * uniforms.meshScale, 0.0, 1.0);
1032
+ return out;
1033
+ }
1034
+
1035
+ // White fill — marks interior of the portal shape in the mask texture.
1036
+ // Matches GLSL: fragColor = vec4(1.0).
1037
+ @fragment
1038
+ fn fs_main() -> @location(0) vec4f {
1039
+ return vec4f(1.0, 1.0, 1.0, 1.0);
1040
+ }
1041
+ `,ui=`// JFA seed pass — detects edges in the binary mask via 4-neighbor comparison.
1042
+ // Edge pixels store their own UV as the seed coordinate (RG channels).
1043
+ // Non-edge pixels store (-1, -1) to mark "no seed here."
1044
+ //
1045
+ // This is the initialization step for Jump Flood Algorithm distance field generation.
1046
+ //
1047
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1048
+ // for consistency with other portal WGSL shaders and to prevent non-uniform
1049
+ // control flow issues if the shader is modified in the future.
1050
+ //
1051
+ // Bind group 0:
1052
+ // binding 0 = mask texture (r8unorm or rgba8unorm, only .r is read)
1053
+ // binding 1 = sampler for the mask texture
1054
+ // binding 2 = uniform buffer { texelSize: vec2f } for neighbor offsets
1055
+ //
1056
+ // Topology: triangle-strip fullscreen quad (4 vertices, no index buffer).
1057
+
1058
+ struct Uniforms {
1059
+ texelSize: vec2f,
1060
+ }
1061
+
1062
+ @group(0) @binding(0) var maskTexture: texture_2d<f32>;
1063
+ @group(0) @binding(1) var maskSampler: sampler;
1064
+ @group(0) @binding(2) var<uniform> uniforms: Uniforms;
1065
+
1066
+ struct VsOutput {
1067
+ @builtin(position) position: vec4f,
1068
+ @location(0) uv: vec2f,
1069
+ }
1070
+
1071
+ // Fullscreen quad: input positions are clip-space corners {-1,-1}, {1,-1}, {-1,1}, {1,1}.
1072
+ // UV is derived as position * 0.5 + 0.5.
1073
+ @vertex
1074
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1075
+ var out: VsOutput;
1076
+ out.uv = aPosition * 0.5 + 0.5;
1077
+ out.position = vec4f(aPosition, 0.0, 1.0);
1078
+ return out;
1079
+ }
1080
+
1081
+ // Edge detection: a pixel is an edge if its mask classification (above/below 0.5)
1082
+ // differs from any of its 4 cardinal neighbors. This matches the GLSL step()-based
1083
+ // comparison exactly — step(0.5, x) returns 0.0 if x < 0.5, else 1.0, so
1084
+ // inequality of step values means the two pixels straddle the mask boundary.
1085
+ @fragment
1086
+ fn fs_main(in: VsOutput) -> @location(0) vec2f {
1087
+ let center = textureSampleLevel(maskTexture, maskSampler, in.uv, 0.0).r;
1088
+ let left = textureSampleLevel(maskTexture, maskSampler, in.uv + vec2f(-uniforms.texelSize.x, 0.0), 0.0).r;
1089
+ let right = textureSampleLevel(maskTexture, maskSampler, in.uv + vec2f( uniforms.texelSize.x, 0.0), 0.0).r;
1090
+ let up = textureSampleLevel(maskTexture, maskSampler, in.uv + vec2f(0.0, uniforms.texelSize.y), 0.0).r;
1091
+ let down = textureSampleLevel(maskTexture, maskSampler, in.uv + vec2f(0.0, -uniforms.texelSize.y), 0.0).r;
1092
+
1093
+ let centerStep = step(0.5, center);
1094
+ let isEdge = (centerStep != step(0.5, left)) ||
1095
+ (centerStep != step(0.5, right)) ||
1096
+ (centerStep != step(0.5, up)) ||
1097
+ (centerStep != step(0.5, down));
1098
+
1099
+ if (isEdge) {
1100
+ return in.uv;
1101
+ } else {
1102
+ return vec2f(-1.0, -1.0);
332
1103
  }
333
- `,ne=`#version 300 es
334
- in vec2 aPosition;
335
- uniform vec2 uUvOffset;
336
- uniform vec2 uUvScale;
337
- out vec2 vUv;
338
- out vec2 vScreenUv;
339
- void main() {
340
- vec2 baseUv = aPosition * 0.5 + 0.5;
341
- vUv = baseUv * uUvScale + uUvOffset;
342
- vScreenUv = baseUv;
343
- gl_Position = vec4(aPosition, 0.0, 1.0);
1104
+ }
1105
+ `,hi=`// JFA flood fill pass — propagates nearest-seed information across the texture.
1106
+ // Each pixel examines its 9 neighbors (including self) at the current step size
1107
+ // and keeps the seed coordinate that is closest in UV space.
1108
+ //
1109
+ // Run iteratively with halving step sizes (N/2, N/4, ... 1) to build
1110
+ // an approximate Voronoi diagram / distance field from the edge seeds.
1111
+ //
1112
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1113
+ // because the loop body has per-fragment \`continue\` statements (UV bounds check)
1114
+ // that create non-uniform control flow. WGSL requires textureSample to be
1115
+ // called only from uniform control flow.
1116
+ //
1117
+ // Bind group 0:
1118
+ // binding 0 = uniform buffer { texelSize: vec2f, stepSize: f32 }
1119
+ // binding 1 = input seed texture (rg16float or rg32float, from previous pass)
1120
+ // binding 2 = sampler for the input texture
1121
+ //
1122
+ // Topology: triangle-strip fullscreen quad (4 vertices, no index buffer).
1123
+
1124
+ struct Uniforms {
1125
+ texelSize: vec2f,
1126
+ stepSize: f32,
1127
+ }
1128
+
1129
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1130
+ @group(0) @binding(1) var inputTexture: texture_2d<f32>;
1131
+ @group(0) @binding(2) var inputSampler: sampler;
1132
+
1133
+ struct VsOutput {
1134
+ @builtin(position) position: vec4f,
1135
+ @location(0) uv: vec2f,
1136
+ }
1137
+
1138
+ @vertex
1139
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1140
+ var out: VsOutput;
1141
+ out.uv = aPosition * 0.5 + 0.5;
1142
+ out.position = vec4f(aPosition, 0.0, 1.0);
1143
+ return out;
1144
+ }
1145
+
1146
+ // JFA flood step: for each of 9 neighbors at stepSize offset, read the stored
1147
+ // seed coordinate. If valid (seed.x >= 0), compute distance to that seed.
1148
+ // Keep the closest seed found across all neighbors.
1149
+ //
1150
+ // Seeds with x < 0 are uninitialized (no edge seed has reached that texel yet).
1151
+ // Out-of-bounds sample UVs are skipped to avoid wrapping artifacts.
1152
+ @fragment
1153
+ fn fs_main(in: VsOutput) -> @location(0) vec2f {
1154
+ var bestSeed = textureSampleLevel(inputTexture, inputSampler, in.uv, 0.0).rg;
1155
+ var bestDist: f32;
1156
+ if (bestSeed.x < 0.0) {
1157
+ bestDist = 1.0e10;
1158
+ } else {
1159
+ bestDist = distance(in.uv, bestSeed);
344
1160
  }
345
- `,se=`#version 300 es
346
- precision highp float;
347
1161
 
348
- #define MAX_POM_STEPS 32
1162
+ // Iterate over the 3x3 neighbor grid at current step size.
1163
+ // The loop is unrolled as two nested loops over {-1, 0, 1}.
1164
+ for (var dy: i32 = -1; dy <= 1; dy = dy + 1) {
1165
+ for (var dx: i32 = -1; dx <= 1; dx = dx + 1) {
1166
+ // Skip center — already read above as the initial best.
1167
+ if (dx == 0 && dy == 0) {
1168
+ continue;
1169
+ }
349
1170
 
350
- uniform sampler2D uImage;
351
- uniform sampler2D uDepth;
352
- uniform vec2 uOffset;
353
- uniform float uStrength;
354
- uniform int uPomSteps;
1171
+ let offset = vec2f(f32(dx), f32(dy)) * uniforms.stepSize;
1172
+ let sampleUv = in.uv + offset;
355
1173
 
356
- // Lens transform: remap depth curve for exaggerated/compressed depth feel
357
- uniform float uDepthPower; // >1 = telephoto, <1 = wide-angle
358
- uniform float uDepthScale; // multiplier on depth range
359
- uniform float uDepthBias; // shift depth origin
1174
+ // Clamp to [0,1] skip samples that would read outside the texture.
1175
+ if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) {
1176
+ continue;
1177
+ }
360
1178
 
361
- // Depth-adaptive contrast
362
- uniform float uContrastLow;
363
- uniform float uContrastHigh;
364
- uniform float uVerticalReduction;
1179
+ let neighborSeed = textureSampleLevel(inputTexture, inputSampler, sampleUv, 0.0).rg;
365
1180
 
366
- // DOF
367
- uniform float uDofStart;
368
- uniform float uDofStrength;
369
- uniform vec2 uImageTexelSize;
1181
+ // Skip uninitialized seeds.
1182
+ if (neighborSeed.x < 0.0) {
1183
+ continue;
1184
+ }
370
1185
 
371
- // Interior mood
372
- uniform float uFogDensity; // volumetric fog bias (0 = none, 0.3 = subtle)
373
- uniform vec3 uFogColor; // fog tint color
374
- uniform float uColorShift; // warm/cool grading shift
375
- uniform float uBrightnessBias; // overall brightness adjustment
376
-
377
- in vec2 vUv;
378
- in vec2 vScreenUv;
379
-
380
- layout(location = 0) out vec4 fragColor;
381
- layout(location = 1) out vec4 fragDepth;
382
-
383
- // Apply lens transform to raw depth
384
- float lensDepth(float raw) {
385
- float d = smoothstep(uContrastLow, uContrastHigh, raw);
386
- d = pow(d, uDepthPower) * uDepthScale + uDepthBias;
387
- return clamp(d, 0.0, 1.0);
1186
+ let d = distance(in.uv, neighborSeed);
1187
+ if (d < bestDist) {
1188
+ bestDist = d;
1189
+ bestSeed = neighborSeed;
1190
+ }
1191
+ }
388
1192
  }
389
1193
 
390
- float edgeFade(vec2 uv) {
391
- float margin = uStrength * 1.5;
392
- float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
393
- float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
394
- return fadeX * fadeY;
1194
+ return bestSeed;
1195
+ }
1196
+ `,ci=`// JFA distance pass converts nearest-seed UV from flood fill into a normalized
1197
+ // distance value. Pixels outside the mask are zeroed; pixels with no valid seed
1198
+ // are set to 1.0 (maximum distance). Otherwise the Euclidean UV-space distance
1199
+ // to the seed is divided by uBevelWidth to produce a 0..1 ramp.
1200
+ //
1201
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1202
+ // because early returns based on texture data create non-uniform control flow.
1203
+ // WGSL requires textureSample to be called only from uniform control flow.
1204
+ //
1205
+ // Bind group 0:
1206
+ // binding 0 = uniform buffer { texelSize: vec2f, bevelWidth: f32 }
1207
+ // binding 1 = seed texture (rg16float/rg32float from JFA flood output)
1208
+ // binding 2 = sampler for seed texture
1209
+ // binding 3 = mask texture (r8unorm, only .r is read)
1210
+ // binding 4 = sampler for mask texture
1211
+ //
1212
+ // Topology: triangle-strip fullscreen quad (4 vertices, no index buffer).
1213
+
1214
+ struct Uniforms {
1215
+ texelSize: vec2f,
1216
+ bevelWidth: f32,
1217
+ }
1218
+
1219
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1220
+ @group(0) @binding(1) var seedTexture: texture_2d<f32>;
1221
+ @group(0) @binding(2) var seedSampler: sampler;
1222
+ @group(0) @binding(3) var maskTexture: texture_2d<f32>;
1223
+ @group(0) @binding(4) var maskSampler: sampler;
1224
+
1225
+ struct VsOutput {
1226
+ @builtin(position) position: vec4f,
1227
+ @location(0) uv: vec2f,
1228
+ }
1229
+
1230
+ @vertex
1231
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1232
+ var out: VsOutput;
1233
+ out.uv = aPosition * 0.5 + 0.5;
1234
+ out.position = vec4f(aPosition, 0.0, 1.0);
1235
+ return out;
1236
+ }
1237
+
1238
+ // Distance computation: mirrors the GLSL exactly.
1239
+ // - Pixels outside mask (mask < 0.5) -> black (zero distance / not part of shape).
1240
+ // - Pixels with no valid seed (seed.x < 0) -> maximum distance (1.0 everywhere).
1241
+ // - Otherwise: Euclidean distance from UV to seed, normalized by bevelWidth.
1242
+ @fragment
1243
+ fn fs_main(in: VsOutput) -> @location(0) vec4f {
1244
+ let mask = textureSampleLevel(maskTexture, maskSampler, in.uv, 0.0).r;
1245
+ if (mask < 0.5) {
1246
+ return vec4f(0.0, 0.0, 0.0, 0.0);
395
1247
  }
396
1248
 
397
- // POM ray-march with lens-transformed depth
398
- vec2 pomDisplace(vec2 uv, out float hitDepth) {
399
- float layerD = 1.0 / float(uPomSteps);
400
- vec2 scaledOffset = uOffset;
401
- scaledOffset.y *= uVerticalReduction;
402
- vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
403
- float currentLayerDepth = 0.0;
404
- vec2 currentUV = uv;
405
- float fade = edgeFade(uv);
406
-
407
- for (int i = 0; i < MAX_POM_STEPS; i++) {
408
- if (i >= uPomSteps) break;
409
- float raw = texture(uDepth, currentUV).r;
410
- float depthAtUV = 1.0 - lensDepth(raw);
411
- if (currentLayerDepth > depthAtUV) {
412
- vec2 prevUV = currentUV - deltaUV;
413
- float prevLayerD = currentLayerDepth - layerD;
414
- float prevRaw = texture(uDepth, prevUV).r;
415
- float prevDepthAtUV = 1.0 - lensDepth(prevRaw);
416
- float afterD = depthAtUV - currentLayerDepth;
417
- float beforeD = prevDepthAtUV - prevLayerD;
418
- float t = afterD / (afterD - beforeD);
419
- vec2 hitUV = mix(currentUV, prevUV, t);
420
- hitDepth = mix(depthAtUV, prevDepthAtUV, t);
421
- return mix(uv, hitUV, fade);
422
- }
423
- currentUV += deltaUV;
424
- currentLayerDepth += layerD;
425
- }
426
- hitDepth = 1.0 - lensDepth(texture(uDepth, currentUV).r);
427
- return mix(uv, currentUV, fade);
1249
+ let seed = textureSampleLevel(seedTexture, seedSampler, in.uv, 0.0).rg;
1250
+ if (seed.x < 0.0) {
1251
+ return vec4f(1.0, 1.0, 1.0, 1.0);
428
1252
  }
429
1253
 
430
- void main() {
431
- float hitDepth;
432
- vec2 displaced = pomDisplace(vUv, hitDepth);
433
- displaced = clamp(displaced, vec2(0.0), vec2(1.0));
434
-
435
- vec4 color = texture(uImage, displaced);
436
-
437
- // DOF: blur far objects
438
- float rawDepthAtHit = texture(uDepth, displaced).r;
439
- float lensD = lensDepth(rawDepthAtHit);
440
- float dof = smoothstep(uDofStart, 1.0, lensD) * uDofStrength;
441
- if (dof > 0.01) {
442
- vec2 ts = uImageTexelSize;
443
- vec4 blurred = (
444
- texture(uImage, displaced + vec2( ts.x, 0.0)) +
445
- texture(uImage, displaced + vec2(-ts.x, 0.0)) +
446
- texture(uImage, displaced + vec2( 0.0, ts.y)) +
447
- texture(uImage, displaced + vec2( 0.0, -ts.y)) +
448
- texture(uImage, displaced + vec2( ts.x, ts.y)) +
449
- texture(uImage, displaced + vec2(-ts.x, -ts.y)) +
450
- texture(uImage, displaced + vec2( ts.x, -ts.y)) +
451
- texture(uImage, displaced + vec2(-ts.x, ts.y))
452
- ) * 0.125;
453
- color = mix(color, blurred, dof);
454
- }
455
-
456
- // Volumetric fog bias: far objects fade into fog color
457
- float fogFactor = smoothstep(0.3, 1.0, lensD) * uFogDensity;
458
- color.rgb = mix(color.rgb, uFogColor, fogFactor);
1254
+ let d = distance(in.uv, seed);
1255
+ let normalized = clamp(d / max(uniforms.bevelWidth, 0.001), 0.0, 1.0);
1256
+ return vec4f(normalized, 0.0, 0.0, 1.0);
1257
+ }
1258
+ `,fi=`// Interior pass — the most complex portal shader. Renders the parallax-displaced
1259
+ // video scene visible through the portal opening.
1260
+ //
1261
+ // Pipeline: POM ray-march -> barrel lens distortion -> DOF blur -> volumetric fog
1262
+ // -> color grading -> vignette -> MRT output (color + depth).
1263
+ //
1264
+ // The vertex shader applies a cover-fit UV transform so the video fills the
1265
+ // viewport regardless of aspect ratio.
1266
+ //
1267
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1268
+ // because POM ray-marching requires texture reads in non-uniform control flow
1269
+ // (loop iterations and conditional branches that depend on texture values).
1270
+ // WGSL requires textureSample to be called only from uniform control flow.
1271
+ //
1272
+ // Bind group 0:
1273
+ // binding 0 = vertex uniform buffer (uvOffset, uvScale)
1274
+ // binding 1 = fragment uniform buffer (all effect parameters)
1275
+ // binding 2 = video (image) texture
1276
+ // binding 3 = video sampler
1277
+ // binding 4 = depth texture
1278
+ // binding 5 = depth sampler
1279
+ //
1280
+ // Topology: triangle-strip fullscreen quad (4 vertices, no index buffer).
1281
+ // Render targets: location 0 = color (rgba8unorm), location 1 = depth (r8unorm).
1282
+
1283
+ // Pipeline-overridable POM loop bound. The actual step count is controlled by
1284
+ // the uniform uPomSteps, but WGSL requires a compile-time loop bound.
1285
+ override MAX_POM_STEPS: i32 = 64;
1286
+
1287
+ struct VertexUniforms {
1288
+ uvOffset: vec2f,
1289
+ uvScale: vec2f,
1290
+ }
1291
+
1292
+ struct FragmentUniforms {
1293
+ // Input / parallax control
1294
+ offset: vec2f, // mouse/gyro input
1295
+ strength: f32, // parallax displacement magnitude
1296
+ pomSteps: i32, // active POM step count (<= MAX_POM_STEPS)
459
1297
 
460
- // Color grading shift: warm near, cool far (or vice versa)
461
- float gradeAmount = (lensD - 0.5) * uColorShift;
462
- color.r += gradeAmount * 0.08;
463
- color.b -= gradeAmount * 0.08;
1298
+ // Lens transform: remap depth curve for exaggerated/compressed depth feel
1299
+ depthPower: f32, // >1 = telephoto, <1 = wide-angle
1300
+ depthScale: f32, // multiplier on depth range
1301
+ depthBias: f32, // shift depth origin
464
1302
 
465
- // Brightness bias
466
- color.rgb *= (1.0 + uBrightnessBias);
1303
+ // Depth-adaptive contrast
1304
+ contrastLow: f32,
1305
+ contrastHigh: f32,
1306
+ verticalReduction: f32,
467
1307
 
468
- // Subtle vignette inside portal
469
- float dist = length(vScreenUv - 0.5) * 1.4;
470
- color.rgb *= 1.0 - pow(dist, 3.0) * 0.3;
1308
+ // DOF
1309
+ dofStart: f32,
1310
+ dofStrength: f32,
1311
+ imageTexelSize: vec2f,
471
1312
 
472
- fragColor = color;
473
- // Write lens-transformed depth to second attachment for boundary effects
474
- fragDepth = vec4(lensD, 0.0, 0.0, 1.0);
475
- }
476
- `,ae=`#version 300 es
477
- in vec2 aPosition;
478
- out vec2 vUv;
479
- void main() {
480
- vUv = aPosition * 0.5 + 0.5;
481
- gl_Position = vec4(aPosition, 0.0, 1.0);
482
- }
483
- `,he=`#version 300 es
484
- precision highp float;
485
- uniform sampler2D uInteriorColor;
486
- uniform sampler2D uDistField;
487
- uniform float uEdgeOcclusionWidth; // how far edge darkening extends
488
- uniform float uEdgeOcclusionStrength; // how strong (0=none, 1=full black)
489
-
490
- in vec2 vUv;
491
- out vec4 fragColor;
492
-
493
- // sRGB linear conversions for correct lighting math
494
- vec3 toLinear(vec3 s) {
495
- return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
496
- }
497
- vec3 toSRGB(vec3 l) {
498
- return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
1313
+ // Interior mood
1314
+ fogDensity: f32, // volumetric fog bias (0 = none, 0.3 = subtle)
1315
+ fogColor: vec3f, // fog tint color
1316
+ colorShift: f32, // warm/cool grading shift
1317
+ brightnessBias: f32, // overall brightness adjustment
1318
+ }
1319
+
1320
+ @group(0) @binding(0) var<uniform> vertUniforms: VertexUniforms;
1321
+ @group(0) @binding(1) var<uniform> fragUniforms: FragmentUniforms;
1322
+ @group(0) @binding(2) var imageTexture: texture_2d<f32>;
1323
+ @group(0) @binding(3) var imageSampler: sampler;
1324
+ @group(0) @binding(4) var depthTexture: texture_2d<f32>;
1325
+ @group(0) @binding(5) var depthSampler: sampler;
1326
+
1327
+ struct VsOutput {
1328
+ @builtin(position) position: vec4f,
1329
+ @location(0) uv: vec2f, // cover-fit UV for video/depth sampling
1330
+ @location(1) screenUv: vec2f, // raw 0..1 UV for vignette etc.
1331
+ }
1332
+
1333
+ @vertex
1334
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1335
+ var out: VsOutput;
1336
+ let baseUv = aPosition * 0.5 + 0.5;
1337
+ out.uv = baseUv * vertUniforms.uvScale + vertUniforms.uvOffset;
1338
+ out.screenUv = baseUv;
1339
+ out.position = vec4f(aPosition, 0.0, 1.0);
1340
+ return out;
1341
+ }
1342
+
1343
+ // --- Fragment helpers ---
1344
+
1345
+ // Apply lens transform to raw 0..1 depth value.
1346
+ // Contrast remap -> power curve -> scale + bias -> clamp.
1347
+ fn lensDepth(raw: f32) -> f32 {
1348
+ var d = smoothstep(fragUniforms.contrastLow, fragUniforms.contrastHigh, raw);
1349
+ d = pow(d, fragUniforms.depthPower) * fragUniforms.depthScale + fragUniforms.depthBias;
1350
+ return clamp(d, 0.0, 1.0);
1351
+ }
1352
+
1353
+ // Fade displacement near UV edges to avoid boundary artifacts.
1354
+ fn edgeFade(uv: vec2f) -> f32 {
1355
+ let margin = fragUniforms.strength * 1.5;
1356
+ let fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
1357
+ let fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
1358
+ return fadeX * fadeY;
1359
+ }
1360
+
1361
+ // MRT output: color at location 0, depth at location 1.
1362
+ struct FragOutput {
1363
+ @location(0) color: vec4f,
1364
+ @location(1) depth: vec4f,
1365
+ }
1366
+
1367
+ @fragment
1368
+ fn fs_main(in: VsOutput) -> FragOutput {
1369
+ // --- POM ray-march with lens-transformed depth ---
1370
+ let layerD = 1.0 / f32(fragUniforms.pomSteps);
1371
+ var scaledOffset = fragUniforms.offset;
1372
+ scaledOffset.y *= fragUniforms.verticalReduction;
1373
+ let deltaUV = scaledOffset * fragUniforms.strength / f32(fragUniforms.pomSteps);
1374
+ var currentLayerDepth: f32 = 0.0;
1375
+ var currentUV = in.uv;
1376
+ let fade = edgeFade(in.uv);
1377
+
1378
+ // hitDepth tracks the interpolated depth at the POM intersection point.
1379
+ var hitDepth: f32 = 0.0;
1380
+ var pomHit = false;
1381
+
1382
+ for (var i: i32 = 0; i < MAX_POM_STEPS; i = i + 1) {
1383
+ if (i >= fragUniforms.pomSteps) {
1384
+ break;
1385
+ }
1386
+ let raw = textureSampleLevel(depthTexture, depthSampler, currentUV, 0.0).r;
1387
+ let depthAtUV = 1.0 - lensDepth(raw);
1388
+ if (currentLayerDepth > depthAtUV) {
1389
+ // Intersection found — refine with linear interpolation between
1390
+ // current and previous layer.
1391
+ let prevUV = currentUV - deltaUV;
1392
+ let prevLayerD = currentLayerDepth - layerD;
1393
+ let prevRaw = textureSampleLevel(depthTexture, depthSampler, prevUV, 0.0).r;
1394
+ let prevDepthAtUV = 1.0 - lensDepth(prevRaw);
1395
+ let afterD = depthAtUV - currentLayerDepth;
1396
+ let beforeD = prevDepthAtUV - prevLayerD;
1397
+ let t = afterD / (afterD - beforeD);
1398
+ let hitUV = mix(currentUV, prevUV, t);
1399
+ hitDepth = mix(depthAtUV, prevDepthAtUV, t);
1400
+ currentUV = mix(in.uv, hitUV, fade);
1401
+ pomHit = true;
1402
+ break;
1403
+ }
1404
+ currentUV = currentUV + deltaUV;
1405
+ currentLayerDepth = currentLayerDepth + layerD;
499
1406
  }
500
1407
 
501
- void main() {
502
- vec4 color = texture(uInteriorColor, vUv);
503
- float dist = texture(uDistField, vUv).r; // 0=edge, 1=deep interior
504
-
505
- // Emissive passthrough: preserve original video luminance.
506
- // Only apply a subtle edge occlusion ramp to sell chamfer→interior depth.
507
- vec3 linear = toLinear(color.rgb);
508
- float occ = smoothstep(0.0, uEdgeOcclusionWidth, dist);
509
- linear *= mix(1.0 - uEdgeOcclusionStrength, 1.0, occ);
510
-
511
- fragColor = vec4(toSRGB(linear), color.a);
512
- }
513
- `,le=`#version 300 es
514
- in vec2 aPosition;
515
- in vec2 aNormal;
516
- uniform float uRimWidth;
517
- uniform vec2 uMeshScale;
518
- out vec2 vNormal;
519
- out vec2 vEdgeUv; // screen-space UV for sampling FBO textures
520
- out float vEdgeDist; // 0 at edge, 1 at outer extent
521
-
522
- void main() {
523
- vec2 scaledPos = aPosition * uMeshScale;
524
- vec2 scaledNormal = normalize(aNormal * uMeshScale);
525
- vec2 pos = scaledPos + scaledNormal * uRimWidth;
526
-
527
- // Pass screen-space UV of this fragment for FBO sampling
528
- vEdgeUv = pos * 0.5 + 0.5;
529
- vNormal = scaledNormal;
530
-
531
- // Distance from the actual edge (0) to the outer rim extent (1)
532
- vEdgeDist = length(pos - scaledPos) / max(uRimWidth, 0.001);
533
-
534
- gl_Position = vec4(pos, 0.0, 1.0);
535
- }
536
- `,ce=`#version 300 es
537
- precision highp float;
538
-
539
- uniform sampler2D uInteriorColor;
540
- uniform sampler2D uInteriorDepth;
541
- uniform sampler2D uDistField;
542
- uniform float uRimIntensity;
543
- uniform vec3 uRimColor;
544
- uniform float uRefractionStrength;
545
- uniform float uChromaticStrength;
546
- uniform float uOcclusionIntensity;
547
- uniform vec2 uTexelSize; // 1.0 / viewport resolution
548
-
549
- // Volumetric edge wall
550
- uniform float uEdgeThickness;
551
- uniform float uEdgeSpecular;
552
- uniform vec3 uEdgeColor;
553
- uniform vec2 uLightDir;
554
- uniform float uBevelIntensity;
555
-
556
- in vec2 vNormal;
557
- in vec2 vEdgeUv;
558
- in float vEdgeDist;
559
- out vec4 fragColor;
560
-
561
- void main() {
562
- // Clamp UV to valid range for texture sampling
563
- vec2 sampleUv = clamp(vEdgeUv, vec2(0.001), vec2(0.999));
564
-
565
- // Sample interior depth at this boundary location
566
- float interiorDepth = texture(uInteriorDepth, sampleUv).r;
567
-
568
- // === DEPTH-REACTIVE RIM (structural seam) ===
569
- float depthReactivity = 1.0 - interiorDepth; // 1=near, 0=far
570
- float rimProfile = 1.0 - smoothstep(0.0, 1.0, vEdgeDist);
571
- rimProfile = pow(rimProfile, 1.5); // sharper falloff = more structural
572
-
573
- float depthPressure = mix(0.2, 1.0, depthReactivity * depthReactivity);
574
- float rim = rimProfile * depthPressure * uRimIntensity;
575
-
576
- vec3 rimCol = uRimColor;
577
- rimCol.r += depthReactivity * 0.15;
578
- rimCol.g += depthReactivity * 0.05;
579
-
580
- // === REFRACTION DISTORTION ===
581
- vec2 ts = uTexelSize * 3.0;
582
- float dLeft = texture(uInteriorDepth, sampleUv + vec2(-ts.x, 0.0)).r;
583
- float dRight = texture(uInteriorDepth, sampleUv + vec2( ts.x, 0.0)).r;
584
- float dUp = texture(uInteriorDepth, sampleUv + vec2(0.0, ts.y)).r;
585
- float dDown = texture(uInteriorDepth, sampleUv + vec2(0.0, -ts.y)).r;
586
- vec2 depthGradient = vec2(dRight - dLeft, dUp - dDown);
587
- vec2 refractUv = sampleUv + depthGradient * uRefractionStrength * rimProfile;
588
- refractUv = clamp(refractUv, vec2(0.001), vec2(0.999));
589
-
590
- vec4 refractedColor = texture(uInteriorColor, refractUv);
591
-
592
- // === CHROMATIC FRINGE ===
593
- float chromaticAmount = uChromaticStrength * depthReactivity * rimProfile;
594
- vec2 chromaticDir = vNormal * chromaticAmount;
595
- float cr = texture(uInteriorColor, refractUv + chromaticDir).r;
596
- float cg = refractedColor.g;
597
- float cb = texture(uInteriorColor, refractUv - chromaticDir).b;
598
- vec3 chromaticColor = vec3(cr, cg, cb);
599
-
600
- // === OCCLUSION CONTACT SHADOW ===
601
- float occlusionAmount = smoothstep(0.4, 0.0, interiorDepth) * uOcclusionIntensity * rimProfile;
602
-
603
- // === VOLUMETRIC EDGE WALL ===
604
- // Sample distance field to get the inner-side distance at this boundary location
605
- float edgeDist = texture(uDistField, sampleUv).r;
606
- float wallZone = smoothstep(uEdgeThickness, 0.0, edgeDist) * rimProfile;
607
-
608
- // Wall lighting from distance field gradient
609
- vec2 dtx = vec2(1.0) / vec2(textureSize(uDistField, 0));
610
- float wdL = texture(uDistField, sampleUv + vec2(-dtx.x, 0.0)).r;
611
- float wdR = texture(uDistField, sampleUv + vec2( dtx.x, 0.0)).r;
612
- float wdU = texture(uDistField, sampleUv + vec2(0.0, dtx.y)).r;
613
- float wdD = texture(uDistField, sampleUv + vec2(0.0, -dtx.y)).r;
614
- vec2 wallNormal = vec2(wdR - wdL, wdU - wdD);
615
- float wnLen = length(wallNormal);
616
- if (wnLen > 0.001) wallNormal /= wnLen;
617
-
618
- float wallSpec = pow(max(dot(wallNormal, uLightDir), 0.0), 16.0) * uEdgeSpecular;
619
- vec3 wallColor = mix(refractedColor.rgb * 0.4, uEdgeColor, 0.3);
620
- wallColor += vec3(wallSpec);
621
-
622
- // === COMPOSITE ===
623
- vec3 color = mix(refractedColor.rgb, chromaticColor, min(chromaticAmount * 10.0, 1.0));
624
- color *= (1.0 - occlusionAmount * 0.4);
625
-
626
- // Blend in volumetric wall
627
- color = mix(color, wallColor, wallZone * uBevelIntensity);
628
-
629
- // Add rim energy on top
630
- color += rimCol * rim;
631
-
632
- // Alpha: rim edge fades out
633
- float alpha = rimProfile * max(rim, occlusionAmount + chromaticAmount * 5.0 + wallZone * 0.5);
634
- alpha = clamp(alpha, 0.0, 1.0);
635
-
636
- fragColor = vec4(color * alpha, alpha);
637
- }
638
- `,ue=`#version 300 es
639
- in vec2 aPosition;
640
- in vec3 aNormal3;
641
- in float aLerpT; // 0 = inner (at silhouette), 1 = outer edge
642
- uniform vec2 uMeshScale;
643
- out vec3 vNormal;
644
- out vec2 vScreenUv;
645
- out float vLerpT;
646
-
647
- void main() {
648
- vec2 sp = aPosition * uMeshScale;
649
- vNormal = aNormal3;
650
- vScreenUv = sp * 0.5 + 0.5;
651
- vLerpT = aLerpT;
652
- gl_Position = vec4(sp, 0.0, 1.0);
1408
+ // No intersection — use the last marched position.
1409
+ if (!pomHit) {
1410
+ hitDepth = 1.0 - lensDepth(textureSampleLevel(depthTexture, depthSampler, currentUV, 0.0).r);
1411
+ currentUV = mix(in.uv, currentUV, fade);
653
1412
  }
654
- `,fe=`#version 300 es
655
- precision highp float;
656
- uniform vec3 uLightDir3;
657
- uniform vec3 uChamferColor;
658
- uniform float uChamferAmbient;
659
- uniform float uChamferSpecular;
660
- uniform float uChamferShininess;
661
- uniform sampler2D uInteriorColor;
662
- uniform vec2 uTexelSize; // 1 / viewport resolution
663
-
664
- in vec3 vNormal;
665
- in vec2 vScreenUv;
666
- in float vLerpT;
667
- out vec4 fragColor;
668
-
669
- vec3 toLinear(vec3 s) {
670
- return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
671
- }
672
- vec3 toSRGB(vec3 l) {
673
- return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
1413
+
1414
+ let displaced = clamp(currentUV, vec2f(0.0), vec2f(1.0));
1415
+ var color = textureSampleLevel(imageTexture, imageSampler, displaced, 0.0);
1416
+
1417
+ // --- DOF: blur far objects ---
1418
+ let rawDepthAtHit = textureSampleLevel(depthTexture, depthSampler, displaced, 0.0).r;
1419
+ let lensD = lensDepth(rawDepthAtHit);
1420
+ let dof = smoothstep(fragUniforms.dofStart, 1.0, lensD) * fragUniforms.dofStrength;
1421
+ if (dof > 0.01) {
1422
+ let ts = fragUniforms.imageTexelSize;
1423
+ let blurred = (
1424
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f( ts.x, 0.0), 0.0) +
1425
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f(-ts.x, 0.0), 0.0) +
1426
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f( 0.0, ts.y), 0.0) +
1427
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f( 0.0, -ts.y), 0.0) +
1428
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f( ts.x, ts.y), 0.0) +
1429
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f(-ts.x, -ts.y), 0.0) +
1430
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f( ts.x, -ts.y), 0.0) +
1431
+ textureSampleLevel(imageTexture, imageSampler, displaced + vec2f(-ts.x, ts.y), 0.0)
1432
+ ) * 0.125;
1433
+ color = mix(color, blurred, dof);
674
1434
  }
675
1435
 
676
- // Approximate gaussian blur via 13-tap poisson disc, radius scaled by vLerpT.
677
- vec3 blurSample(vec2 center, float radius) {
678
- // Poisson disc offsets (normalized to unit circle)
679
- const vec2 offsets[12] = vec2[12](
680
- vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457),
681
- vec2(-0.203, 0.621), vec2( 0.962, -0.195), vec2( 0.473, -0.480),
682
- vec2( 0.519, 0.767), vec2( 0.185, -0.893), vec2( 0.507, 0.064),
683
- vec2(-0.321, -0.860), vec2(-0.791, 0.557), vec2( 0.330, 0.418)
684
- );
685
- vec3 sum = texture(uInteriorColor, center).rgb;
686
- for (int i = 0; i < 12; i++) {
687
- vec2 uv = center + offsets[i] * radius;
688
- uv = clamp(uv, vec2(0.001), vec2(0.999));
689
- sum += texture(uInteriorColor, uv).rgb;
690
- }
691
- return sum / 13.0;
1436
+ // --- Volumetric fog: far objects fade into fog color ---
1437
+ let fogFactor = smoothstep(0.3, 1.0, lensD) * fragUniforms.fogDensity;
1438
+ color = vec4f(mix(color.rgb, fragUniforms.fogColor, fogFactor), color.a);
1439
+
1440
+ // --- Color grading: warm near, cool far (or vice versa) ---
1441
+ let gradeAmount = (lensD - 0.5) * fragUniforms.colorShift;
1442
+ color = vec4f(
1443
+ color.r + gradeAmount * 0.08,
1444
+ color.g,
1445
+ color.b - gradeAmount * 0.08,
1446
+ color.a,
1447
+ );
1448
+
1449
+ // --- Brightness bias ---
1450
+ color = vec4f(color.rgb * (1.0 + fragUniforms.brightnessBias), color.a);
1451
+
1452
+ // --- Subtle vignette inside portal ---
1453
+ let dist = length(in.screenUv - 0.5) * 1.4;
1454
+ color = vec4f(color.rgb * (1.0 - pow(dist, 3.0) * 0.3), color.a);
1455
+
1456
+ var out: FragOutput;
1457
+ out.color = color;
1458
+ // Write lens-transformed depth to second attachment for boundary effects.
1459
+ out.depth = vec4f(lensD, 0.0, 0.0, 1.0);
1460
+ return out;
1461
+ }
1462
+ `,di=`// Composite pass — composites the interior FBO color over the background,
1463
+ // applying a subtle edge occlusion ramp driven by the JFA distance field.
1464
+ //
1465
+ // The interior video luminance is preserved (emissive passthrough). The only
1466
+ // modification is a smooth darkening near the portal edge to sell the
1467
+ // chamfer-to-interior depth transition.
1468
+ //
1469
+ // sRGB conversions ensure occlusion math happens in linear space.
1470
+ //
1471
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1472
+ // for consistency with other portal WGSL shaders and to prevent non-uniform
1473
+ // control flow issues if the shader is modified in the future.
1474
+ //
1475
+ // Bind group 0:
1476
+ // binding 0 = uniform buffer { edgeOcclusionWidth: f32, edgeOcclusionStrength: f32 }
1477
+ // binding 1 = interior color texture (rgba8unorm from interior pass)
1478
+ // binding 2 = interior color sampler
1479
+ // binding 3 = distance field texture (r8unorm from JFA distance pass)
1480
+ // binding 4 = distance field sampler
1481
+ //
1482
+ // Topology: triangle-strip fullscreen quad (4 vertices, no index buffer).
1483
+
1484
+ struct Uniforms {
1485
+ edgeOcclusionWidth: f32, // how far edge darkening extends (in dist-field units)
1486
+ edgeOcclusionStrength: f32, // how strong (0 = none, 1 = full black at edge)
1487
+ }
1488
+
1489
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1490
+ @group(0) @binding(1) var interiorColorTexture: texture_2d<f32>;
1491
+ @group(0) @binding(2) var interiorColorSampler: sampler;
1492
+ @group(0) @binding(3) var distFieldTexture: texture_2d<f32>;
1493
+ @group(0) @binding(4) var distFieldSampler: sampler;
1494
+
1495
+ struct VsOutput {
1496
+ @builtin(position) position: vec4f,
1497
+ @location(0) uv: vec2f,
1498
+ }
1499
+
1500
+ @vertex
1501
+ fn vs_main(@location(0) aPosition: vec2f) -> VsOutput {
1502
+ var out: VsOutput;
1503
+ out.uv = aPosition * 0.5 + 0.5;
1504
+ out.position = vec4f(aPosition, 0.0, 1.0);
1505
+ return out;
1506
+ }
1507
+
1508
+ // --- sRGB <-> linear conversions for correct lighting math ---
1509
+
1510
+ fn toLinear(s: vec3f) -> vec3f {
1511
+ // Per-channel: if s <= 0.04045, linear = s/12.92
1512
+ // else linear = ((s + 0.055) / 1.055)^2.4
1513
+ return mix(
1514
+ s / 12.92,
1515
+ pow((s + 0.055) / 1.055, vec3f(2.4)),
1516
+ step(vec3f(0.04045), s),
1517
+ );
1518
+ }
1519
+
1520
+ fn toSRGB(l: vec3f) -> vec3f {
1521
+ // Per-channel: if l <= 0.0031308, srgb = l * 12.92
1522
+ // else srgb = 1.055 * l^(1/2.4) - 0.055
1523
+ return mix(
1524
+ l * 12.92,
1525
+ 1.055 * pow(l, vec3f(1.0 / 2.4)) - 0.055,
1526
+ step(vec3f(0.0031308), l),
1527
+ );
1528
+ }
1529
+
1530
+ @fragment
1531
+ fn fs_main(in: VsOutput) -> @location(0) vec4f {
1532
+ let color = textureSampleLevel(interiorColorTexture, interiorColorSampler, in.uv, 0.0);
1533
+ let dist = textureSampleLevel(distFieldTexture, distFieldSampler, in.uv, 0.0).r; // 0=edge, 1=deep interior
1534
+
1535
+ // Emissive passthrough: preserve original video luminance.
1536
+ // Only apply a subtle edge occlusion ramp to sell chamfer->interior depth.
1537
+ var linear = toLinear(color.rgb);
1538
+ let occ = smoothstep(0.0, uniforms.edgeOcclusionWidth, dist);
1539
+ linear *= mix(1.0 - uniforms.edgeOcclusionStrength, 1.0, occ);
1540
+
1541
+ return vec4f(toSRGB(linear), color.a);
1542
+ }
1543
+ `,pi=`// Boundary effects pass — renders the visual boundary between the portal interior
1544
+ // and the surrounding scene. Produces depth-reactive rim lighting, refraction
1545
+ // distortion, chromatic aberration fringe, contact occlusion shadows, and a
1546
+ // volumetric edge wall effect.
1547
+ //
1548
+ // The vertex shader takes the extruded boundary mesh (position + 2D normal) and
1549
+ // pushes vertices outward along their normals by uRimWidth to create the
1550
+ // boundary ribbon geometry.
1551
+ //
1552
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1553
+ // for consistency with other portal WGSL shaders and to prevent non-uniform
1554
+ // control flow issues if the shader is modified in the future.
1555
+ //
1556
+ // Bind group 0:
1557
+ // binding 0 = uniform buffer (all effect parameters)
1558
+ // binding 1 = interior color texture (rgba8unorm from interior pass)
1559
+ // binding 2 = interior color sampler
1560
+ // binding 3 = interior depth texture (r8unorm from interior pass MRT)
1561
+ // binding 4 = interior depth sampler
1562
+ // binding 5 = distance field texture (r8unorm from JFA distance pass)
1563
+ // binding 6 = distance field sampler
1564
+ //
1565
+ // Vertex input: position (vec2f) + normal (vec2f) per boundary vertex.
1566
+ // Topology: triangle-strip or triangle-list (boundary ribbon mesh).
1567
+
1568
+ struct Uniforms {
1569
+ // Vertex uniforms
1570
+ rimWidth: f32,
1571
+ meshScale: vec2f,
1572
+
1573
+ // Fragment uniforms — rim
1574
+ rimIntensity: f32,
1575
+ rimColor: vec3f,
1576
+
1577
+ // Fragment uniforms — refraction / chromatic
1578
+ refractionStrength: f32,
1579
+ chromaticStrength: f32,
1580
+ occlusionIntensity: f32,
1581
+ texelSize: vec2f, // 1.0 / viewport resolution
1582
+
1583
+ // Fragment uniforms — volumetric edge wall
1584
+ edgeThickness: f32,
1585
+ edgeSpecular: f32,
1586
+ edgeColor: vec3f,
1587
+ lightDir: vec2f,
1588
+ bevelIntensity: f32,
1589
+ }
1590
+
1591
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1592
+ @group(0) @binding(1) var interiorColorTexture: texture_2d<f32>;
1593
+ @group(0) @binding(2) var interiorColorSampler: sampler;
1594
+ @group(0) @binding(3) var interiorDepthTexture: texture_2d<f32>;
1595
+ @group(0) @binding(4) var interiorDepthSampler: sampler;
1596
+ @group(0) @binding(5) var distFieldTexture: texture_2d<f32>;
1597
+ @group(0) @binding(6) var distFieldSampler: sampler;
1598
+
1599
+ struct VsOutput {
1600
+ @builtin(position) position: vec4f,
1601
+ @location(0) normal: vec2f,
1602
+ @location(1) edgeUv: vec2f, // screen-space UV for sampling FBO textures
1603
+ @location(2) edgeDist: f32, // 0 at edge, 1 at outer extent
1604
+ }
1605
+
1606
+ @vertex
1607
+ fn vs_main(
1608
+ @location(0) aPosition: vec2f,
1609
+ @location(1) aNormal: vec2f,
1610
+ ) -> VsOutput {
1611
+ let scaledPos = aPosition * uniforms.meshScale;
1612
+ let scaledNormal = normalize(aNormal * uniforms.meshScale);
1613
+ let pos = scaledPos + scaledNormal * uniforms.rimWidth;
1614
+
1615
+ var out: VsOutput;
1616
+ // Pass screen-space UV of this fragment for FBO sampling.
1617
+ out.edgeUv = pos * 0.5 + 0.5;
1618
+ out.normal = scaledNormal;
1619
+ // Distance from the actual edge (0) to the outer rim extent (1).
1620
+ out.edgeDist = length(pos - scaledPos) / max(uniforms.rimWidth, 0.001);
1621
+ out.position = vec4f(pos, 0.0, 1.0);
1622
+ return out;
1623
+ }
1624
+
1625
+ @fragment
1626
+ fn fs_main(in: VsOutput) -> @location(0) vec4f {
1627
+ // Clamp UV to valid range for texture sampling.
1628
+ let sampleUv = clamp(in.edgeUv, vec2f(0.001), vec2f(0.999));
1629
+
1630
+ // Sample interior depth at this boundary location.
1631
+ let interiorDepth = textureSampleLevel(interiorDepthTexture, interiorDepthSampler, sampleUv, 0.0).r;
1632
+
1633
+ // === DEPTH-REACTIVE RIM (structural seam) ===
1634
+ let depthReactivity = 1.0 - interiorDepth; // 1=near, 0=far
1635
+ var rimProfile = 1.0 - smoothstep(0.0, 1.0, in.edgeDist);
1636
+ rimProfile = pow(rimProfile, 1.5); // sharper falloff = more structural
1637
+
1638
+ let depthPressure = mix(0.2, 1.0, depthReactivity * depthReactivity);
1639
+ let rim = rimProfile * depthPressure * uniforms.rimIntensity;
1640
+
1641
+ var rimCol = uniforms.rimColor;
1642
+ rimCol.r += depthReactivity * 0.15;
1643
+ rimCol.g += depthReactivity * 0.05;
1644
+
1645
+ // === REFRACTION DISTORTION ===
1646
+ let ts = uniforms.texelSize * 3.0;
1647
+ let dLeft = textureSampleLevel(interiorDepthTexture, interiorDepthSampler, sampleUv + vec2f(-ts.x, 0.0), 0.0).r;
1648
+ let dRight = textureSampleLevel(interiorDepthTexture, interiorDepthSampler, sampleUv + vec2f( ts.x, 0.0), 0.0).r;
1649
+ let dUp = textureSampleLevel(interiorDepthTexture, interiorDepthSampler, sampleUv + vec2f(0.0, ts.y), 0.0).r;
1650
+ let dDown = textureSampleLevel(interiorDepthTexture, interiorDepthSampler, sampleUv + vec2f(0.0, -ts.y), 0.0).r;
1651
+ let depthGradient = vec2f(dRight - dLeft, dUp - dDown);
1652
+ let refractUv = clamp(
1653
+ sampleUv + depthGradient * uniforms.refractionStrength * rimProfile,
1654
+ vec2f(0.001),
1655
+ vec2f(0.999),
1656
+ );
1657
+
1658
+ let refractedColor = textureSampleLevel(interiorColorTexture, interiorColorSampler, refractUv, 0.0);
1659
+
1660
+ // === CHROMATIC FRINGE ===
1661
+ let chromaticAmount = uniforms.chromaticStrength * depthReactivity * rimProfile;
1662
+ let chromaticDir = in.normal * chromaticAmount;
1663
+ let cr = textureSampleLevel(interiorColorTexture, interiorColorSampler, refractUv + chromaticDir, 0.0).r;
1664
+ let cg = refractedColor.g;
1665
+ let cb = textureSampleLevel(interiorColorTexture, interiorColorSampler, refractUv - chromaticDir, 0.0).b;
1666
+ let chromaticColor = vec3f(cr, cg, cb);
1667
+
1668
+ // === OCCLUSION CONTACT SHADOW ===
1669
+ let occlusionAmount = smoothstep(0.4, 0.0, interiorDepth) * uniforms.occlusionIntensity * rimProfile;
1670
+
1671
+ // === VOLUMETRIC EDGE WALL ===
1672
+ // Sample distance field to get the inner-side distance at this boundary location.
1673
+ let edgeDist = textureSampleLevel(distFieldTexture, distFieldSampler, sampleUv, 0.0).r;
1674
+ let wallZone = smoothstep(uniforms.edgeThickness, 0.0, edgeDist) * rimProfile;
1675
+
1676
+ // Wall lighting from distance field gradient.
1677
+ let distDims = vec2f(textureDimensions(distFieldTexture, 0));
1678
+ let dtx = vec2f(1.0) / distDims;
1679
+ let wdL = textureSampleLevel(distFieldTexture, distFieldSampler, sampleUv + vec2f(-dtx.x, 0.0), 0.0).r;
1680
+ let wdR = textureSampleLevel(distFieldTexture, distFieldSampler, sampleUv + vec2f( dtx.x, 0.0), 0.0).r;
1681
+ let wdU = textureSampleLevel(distFieldTexture, distFieldSampler, sampleUv + vec2f(0.0, dtx.y), 0.0).r;
1682
+ let wdD = textureSampleLevel(distFieldTexture, distFieldSampler, sampleUv + vec2f(0.0, -dtx.y), 0.0).r;
1683
+ var wallNormal = vec2f(wdR - wdL, wdU - wdD);
1684
+ let wnLen = length(wallNormal);
1685
+ if (wnLen > 0.001) {
1686
+ wallNormal = wallNormal / wnLen;
692
1687
  }
693
1688
 
694
- void main() {
695
- vec3 N = normalize(vNormal);
696
- vec3 L = normalize(uLightDir3);
697
- vec3 V = vec3(0.0, 0.0, -1.0); // orthographic view direction
698
-
699
- // Blinn-Phong lighting in linear space
700
- float diff = max(dot(N, L), 0.0);
701
- vec3 H = normalize(L + V);
702
- float spec = pow(max(dot(N, H), 0.0), uChamferShininess) * uChamferSpecular;
703
-
704
- // Sample interior video with progressive blur (sharper at inner edge)
705
- vec2 uv = clamp(vScreenUv, vec2(0.001), vec2(0.999));
706
- float blurRadius = vLerpT * 12.0 * length(uTexelSize);
707
- vec3 videoSample = blurRadius > 0.0001
708
- ? blurSample(uv, blurRadius)
709
- : texture(uInteriorColor, uv).rgb;
710
-
711
- // Base color: video tinted through chamfer color (like frosted glass)
712
- vec3 video = toLinear(videoSample);
713
- vec3 tint = toLinear(uChamferColor);
714
- // Blend: mostly video near inner edge, more tinted at outer edge
715
- vec3 base = mix(video, video * tint * 3.0, vLerpT * 0.5);
716
-
717
- // Apply Blinn-Phong
718
- vec3 lit = base * (uChamferAmbient + (1.0 - uChamferAmbient) * diff) + vec3(spec);
719
- fragColor = vec4(toSRGB(lit), 1.0);
1689
+ let wallSpec = pow(max(dot(wallNormal, uniforms.lightDir), 0.0), 16.0) * uniforms.edgeSpecular;
1690
+ var wallColor = mix(refractedColor.rgb * 0.4, uniforms.edgeColor, 0.3);
1691
+ wallColor += vec3f(wallSpec);
1692
+
1693
+ // === COMPOSITE ===
1694
+ var color = mix(refractedColor.rgb, chromaticColor, min(chromaticAmount * 10.0, 1.0));
1695
+ color *= (1.0 - occlusionAmount * 0.4);
1696
+
1697
+ // Blend in volumetric wall.
1698
+ color = mix(color, wallColor, wallZone * uniforms.bevelIntensity);
1699
+
1700
+ // Add rim energy on top.
1701
+ color += rimCol * rim;
1702
+
1703
+ // Alpha: rim edge fades out.
1704
+ var alpha = rimProfile * max(rim, occlusionAmount + chromaticAmount * 5.0 + wallZone * 0.5);
1705
+ alpha = clamp(alpha, 0.0, 1.0);
1706
+
1707
+ return vec4f(color * alpha, alpha);
1708
+ }
1709
+ `,mi=`// Chamfer pass renders the beveled ring around the portal opening with
1710
+ // Blinn-Phong lighting and a frosted glass effect. The interior video is
1711
+ // sampled through the chamfer with a progressive poisson disc blur (sharper
1712
+ // at the inner edge, more blurred toward the outer edge), then tinted through
1713
+ // the chamfer color to simulate frosted glass.
1714
+ //
1715
+ // Uses textureSampleLevel (explicit LOD 0) instead of textureSample throughout
1716
+ // because the blur function is called from a per-fragment conditional branch
1717
+ // (blurRadius varies per vertex). WGSL requires textureSample to be called
1718
+ // only from uniform control flow.
1719
+ //
1720
+ // Bind group 0:
1721
+ // binding 0 = uniform buffer (lighting + chamfer parameters)
1722
+ // binding 1 = interior color texture (rgba8unorm from interior pass)
1723
+ // binding 2 = interior color sampler
1724
+ //
1725
+ // Vertex input: position (vec2f) + normal3 (vec3f) + lerpT (f32).
1726
+ // Topology: triangle-strip or triangle-list (chamfer ring mesh).
1727
+
1728
+ struct Uniforms {
1729
+ lightDir3: vec3f,
1730
+ chamferColor: vec3f,
1731
+ chamferAmbient: f32,
1732
+ chamferSpecular: f32,
1733
+ chamferShininess: f32,
1734
+ meshScale: vec2f,
1735
+ texelSize: vec2f, // 1 / viewport resolution
1736
+ }
1737
+
1738
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1739
+ @group(0) @binding(1) var interiorColorTexture: texture_2d<f32>;
1740
+ @group(0) @binding(2) var interiorColorSampler: sampler;
1741
+
1742
+ struct VsOutput {
1743
+ @builtin(position) position: vec4f,
1744
+ @location(0) normal: vec3f,
1745
+ @location(1) screenUv: vec2f,
1746
+ @location(2) lerpT: f32,
1747
+ }
1748
+
1749
+ @vertex
1750
+ fn vs_main(
1751
+ @location(0) aPosition: vec2f,
1752
+ @location(1) aNormal3: vec3f,
1753
+ @location(2) aLerpT: f32,
1754
+ ) -> VsOutput {
1755
+ let sp = aPosition * uniforms.meshScale;
1756
+
1757
+ var out: VsOutput;
1758
+ out.normal = aNormal3;
1759
+ out.screenUv = sp * 0.5 + 0.5;
1760
+ out.lerpT = aLerpT;
1761
+ out.position = vec4f(sp, 0.0, 1.0);
1762
+ return out;
1763
+ }
1764
+
1765
+ // --- sRGB <-> linear conversions for correct lighting math ---
1766
+
1767
+ fn toLinear(s: vec3f) -> vec3f {
1768
+ return mix(
1769
+ s / 12.92,
1770
+ pow((s + 0.055) / 1.055, vec3f(2.4)),
1771
+ step(vec3f(0.04045), s),
1772
+ );
1773
+ }
1774
+
1775
+ fn toSRGB(l: vec3f) -> vec3f {
1776
+ return mix(
1777
+ l * 12.92,
1778
+ 1.055 * pow(l, vec3f(1.0 / 2.4)) - 0.055,
1779
+ step(vec3f(0.0031308), l),
1780
+ );
1781
+ }
1782
+
1783
+ // Approximate gaussian blur via 13-tap poisson disc, radius scaled by lerpT.
1784
+ // Poisson disc offsets are normalized to the unit circle.
1785
+ fn blurSample(center: vec2f, radius: f32) -> vec3f {
1786
+ // 12 poisson disc offsets — same values as the GLSL const array.
1787
+ let o0 = vec2f(-0.326, -0.406);
1788
+ let o1 = vec2f(-0.840, -0.074);
1789
+ let o2 = vec2f(-0.696, 0.457);
1790
+ let o3 = vec2f(-0.203, 0.621);
1791
+ let o4 = vec2f( 0.962, -0.195);
1792
+ let o5 = vec2f( 0.473, -0.480);
1793
+ let o6 = vec2f( 0.519, 0.767);
1794
+ let o7 = vec2f( 0.185, -0.893);
1795
+ let o8 = vec2f( 0.507, 0.064);
1796
+ let o9 = vec2f(-0.321, -0.860);
1797
+ let o10 = vec2f(-0.791, 0.557);
1798
+ let o11 = vec2f( 0.330, 0.418);
1799
+
1800
+ // Center tap.
1801
+ var sum = textureSampleLevel(interiorColorTexture, interiorColorSampler, center, 0.0).rgb;
1802
+
1803
+ // 12 offset taps. Each UV is clamped to avoid edge sampling artifacts.
1804
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o0 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1805
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o1 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1806
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o2 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1807
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o3 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1808
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o4 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1809
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o5 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1810
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o6 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1811
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o7 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1812
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o8 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1813
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o9 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1814
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o10 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1815
+ sum += textureSampleLevel(interiorColorTexture, interiorColorSampler, clamp(center + o11 * radius, vec2f(0.001), vec2f(0.999)), 0.0).rgb;
1816
+
1817
+ return sum / 13.0;
1818
+ }
1819
+
1820
+ @fragment
1821
+ fn fs_main(in: VsOutput) -> @location(0) vec4f {
1822
+ let N = normalize(in.normal);
1823
+ let L = normalize(uniforms.lightDir3);
1824
+ let V = vec3f(0.0, 0.0, -1.0); // orthographic view direction
1825
+
1826
+ // Blinn-Phong lighting in linear space.
1827
+ let diff = max(dot(N, L), 0.0);
1828
+ let H = normalize(L + V);
1829
+ let spec = pow(max(dot(N, H), 0.0), uniforms.chamferShininess) * uniforms.chamferSpecular;
1830
+
1831
+ // Sample interior video with progressive blur (sharper at inner edge).
1832
+ let uv = clamp(in.screenUv, vec2f(0.001), vec2f(0.999));
1833
+ let blurRadius = in.lerpT * 12.0 * length(uniforms.texelSize);
1834
+
1835
+ var videoSample: vec3f;
1836
+ if (blurRadius > 0.0001) {
1837
+ videoSample = blurSample(uv, blurRadius);
1838
+ } else {
1839
+ videoSample = textureSampleLevel(interiorColorTexture, interiorColorSampler, uv, 0.0).rgb;
720
1840
  }
721
- `;function L(r,t,e){const i=r.createShader(t);if(!i)throw new Error("Failed to create shader.");if(r.shaderSource(i,e),r.compileShader(i),!r.getShaderParameter(i,r.COMPILE_STATUS)){const o=r.getShaderInfoLog(i)??"";throw r.deleteShader(i),new Error(`Shader compilation failed:
722
- ${o}`)}return i}function O(r,t,e){const i=r.createProgram();if(!i)throw new Error("Failed to create program.");if(r.attachShader(i,t),r.attachShader(i,e),r.linkProgram(i),!r.getProgramParameter(i,r.LINK_STATUS)){const o=r.getProgramInfoLog(i)??"";throw r.deleteProgram(i),new Error(`Program linking failed:
723
- ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.deleteShader(e),i}function de(r){const t=[];let e=0;for(let i=0;i<r.length-2;i+=2){const o=r[i],n=r[i+1],s=r[i+2],h=r[i+3],a=s-o,l=h-n,u=Math.sqrt(a*a+l*l);if(u<1e-6)continue;const f=-l/u,c=a/u;t.push(o,n,f,c,o,n,-f,-c,s,h,f,c,s,h,f,c,o,n,-f,-c,s,h,-f,-c),e+=6}return{vertices:new Float32Array(t),count:e}}function me(r,t,e,i,o){if(i<=0)return{vertices:new Float32Array(0),count:0};const n=o*Math.PI/180,s=-Math.cos(n),h=Math.sin(n),a=[];let l=0;for(let u=0;u<t.length;u++){const f=t[u],d=((u+1<t.length?t[u+1]:r.length)-f)/2;if(d<3)continue;const m=d-1;let U=0;for(let x=0;x<m;x++){const A=f+x*2,S=r[A],D=r[A+1],P=r[A+2],_=r[A+3];U+=S*_-P*D}const y=U>=0?1:-1,E=[],p=[];for(let x=0;x<m;x++){const A=f+x*2,S=r[A+2]-r[A],D=r[A+3]-r[A+1],P=Math.sqrt(S*S+D*D);P<1e-8?(E.push(x>0?E[x-1]:0),p.push(x>0?p[x-1]:0)):(E.push(-D/P*y),p.push(S/P*y))}const v=[],T=[];for(let x=0;x<m;x++){const A=(x-1+m)%m;let S=E[A]+E[x],D=p[A]+p[x];const P=Math.sqrt(S*S+D*D);P>1e-8?(S/=P,D/=P):(S=E[x],D=p[x]),v.push(S),T.push(D)}for(let x=0;x<m;x++){const A=x,S=(x+1)%m,D=f+x*2,P=f+(x+1)%m*2,_=r[D],k=r[D+1],F=r[P],R=r[P+1],C=v[A]*h,M=T[A]*h,B=s,I=v[S]*h,G=T[S]*h,tt=s,ct=_+v[A]*i,Lt=k+T[A]*i,ze=F+v[S]*i,We=R+T[S]*i;a.push(_,k,C,M,B,0),a.push(ct,Lt,C,M,B,1),a.push(F,R,I,G,tt,0),a.push(F,R,I,G,tt,0),a.push(ct,Lt,C,M,B,1),a.push(ze,We,I,G,tt,1),l+=6}}return{vertices:new Float32Array(a),count:l}}class ft{static RESIZE_DEBOUNCE_MS=100;canvas;gl=null;container;stencilProgram=null;maskProgram=null;jfaSeedProgram=null;jfaFloodProgram=null;jfaDistProgram=null;interiorProgram=null;compositeProgram=null;boundaryProgram=null;chamferProgram=null;stencilUniforms={};maskUniforms={};jfaSeedUniforms={};jfaFloodUniforms={};jfaDistUniforms={};interiorUniforms={};compositeUniforms={};boundaryUniforms={};chamferUniforms={};quadVao=null;stencilVao=null;stencilIndexCount=0;maskVao=null;boundaryVao=null;boundaryVertexCount=0;chamferVao=null;chamferVertexCount=0;videoTexture=null;depthTexture=null;interiorFbo=null;interiorColorTex=null;interiorDepthTex=null;fboWidth=0;fboHeight=0;maskFbo=null;maskTex=null;jfaPingFbo=null;jfaPingTex=null;jfaPongFbo=null;jfaPongTex=null;distFbo=null;distTex=null;jfaWidth=0;jfaHeight=0;distFieldDirty=!0;hasColorBufferFloat=!1;depthWidth=0;depthHeight=0;videoAspect=1.7777777777777777;meshAspect=1;meshScaleX=.65;meshScaleY=.65;readDepth=null;readInput=null;playbackVideo=null;onVideoFrame=null;animationFrameHandle=0;rvfcHandle=0;rvfcSupported=!1;resizeObserver=null;resizeTimer=null;uvOffset=[0,0];uvScale=[1,1];lightDirX=-.707;lightDirY=.707;lightDir3=[-.5,.7,-.3];config;constructor(t,e){this.container=t,this.config={...e};const i=this.config.bevelLightAngle*Math.PI/180;this.lightDirX=Math.cos(i),this.lightDirY=Math.sin(i);const o=this.config.lightDirection,n=Math.sqrt(o[0]*o[0]+o[1]*o[1]+o[2]*o[2]);n>1e-6&&(this.lightDir3=[o[0]/n,o[1]/n,o[2]/n]),this.canvas=document.createElement("canvas");const s=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0,premultipliedAlpha:!0,stencil:!0,desynchronized:!0,powerPreference:"high-performance"});if(!s)throw new Error("WebGL 2 is not supported.");this.gl=s,"drawingBufferColorSpace"in s&&(s.drawingBufferColorSpace="srgb"),this.hasColorBufferFloat=!!s.getExtension("EXT_color_buffer_float"),s.clearColor(0,0,0,0),s.pixelStorei(s.UNPACK_FLIP_Y_WEBGL,!0),this.container.appendChild(this.canvas),this.initGPUResources(),this.setupResizeHandling(),this.canvas.addEventListener("webglcontextlost",this.handleContextLost),this.canvas.addEventListener("webglcontextrestored",this.handleContextRestored)}initialize(t,e,i,o){const n=this.gl;n&&(this.disposeTextures(),this.disposeFBO(),this.disposeJFA(),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.videoAspect=t.videoWidth/t.videoHeight,this.meshAspect=o.aspect,this.depthWidth=e,this.depthHeight=i,this.videoTexture=n.createTexture(),n.activeTexture(n.TEXTURE0),n.bindTexture(n.TEXTURE_2D,this.videoTexture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),this.depthTexture=n.createTexture(),n.activeTexture(n.TEXTURE1),n.bindTexture(n.TEXTURE_2D,this.depthTexture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texStorage2D(n.TEXTURE_2D,1,n.R8,e,i),this.uploadStencilMesh(o),this.uploadMaskMesh(o),this.uploadBoundaryMesh(o),this.uploadChamferMesh(o),this.interiorProgram&&(n.useProgram(this.interiorProgram),n.uniform1i(this.interiorUniforms.uImage,0),n.uniform1i(this.interiorUniforms.uDepth,1),n.uniform1f(this.interiorUniforms.uStrength,this.config.parallaxStrength),n.uniform1i(this.interiorUniforms.uPomSteps,this.config.pomSteps),n.uniform1f(this.interiorUniforms.uDepthPower,this.config.depthPower),n.uniform1f(this.interiorUniforms.uDepthScale,this.config.depthScale),n.uniform1f(this.interiorUniforms.uDepthBias,this.config.depthBias),n.uniform1f(this.interiorUniforms.uContrastLow,this.config.contrastLow),n.uniform1f(this.interiorUniforms.uContrastHigh,this.config.contrastHigh),n.uniform1f(this.interiorUniforms.uVerticalReduction,this.config.verticalReduction),n.uniform1f(this.interiorUniforms.uDofStart,this.config.dofStart),n.uniform1f(this.interiorUniforms.uDofStrength,this.config.dofStrength),n.uniform2f(this.interiorUniforms.uImageTexelSize,1/t.videoWidth,1/t.videoHeight),n.uniform1f(this.interiorUniforms.uFogDensity,this.config.fogDensity),n.uniform3f(this.interiorUniforms.uFogColor,...this.config.fogColor),n.uniform1f(this.interiorUniforms.uColorShift,this.config.colorShift),n.uniform1f(this.interiorUniforms.uBrightnessBias,this.config.brightnessBias)),this.compositeProgram&&(n.useProgram(this.compositeProgram),n.uniform1i(this.compositeUniforms.uInteriorColor,2),n.uniform1i(this.compositeUniforms.uDistField,4),n.uniform1f(this.compositeUniforms.uEdgeOcclusionWidth,this.config.edgeOcclusionWidth),n.uniform1f(this.compositeUniforms.uEdgeOcclusionStrength,this.config.edgeOcclusionStrength)),this.chamferProgram&&(n.useProgram(this.chamferProgram),n.uniform3f(this.chamferUniforms.uLightDir3,...this.lightDir3),n.uniform3f(this.chamferUniforms.uChamferColor,...this.config.chamferColor),n.uniform1f(this.chamferUniforms.uChamferAmbient,this.config.chamferAmbient),n.uniform1f(this.chamferUniforms.uChamferSpecular,this.config.chamferSpecular),n.uniform1f(this.chamferUniforms.uChamferShininess,this.config.chamferShininess),n.uniform1i(this.chamferUniforms.uInteriorColor,2)),this.boundaryProgram&&(n.useProgram(this.boundaryProgram),n.uniform1i(this.boundaryUniforms.uInteriorColor,2),n.uniform1i(this.boundaryUniforms.uInteriorDepth,3),n.uniform1i(this.boundaryUniforms.uDistField,4),n.uniform1f(this.boundaryUniforms.uRimIntensity,this.config.rimLightIntensity),n.uniform3f(this.boundaryUniforms.uRimColor,...this.config.rimLightColor),n.uniform1f(this.boundaryUniforms.uRefractionStrength,this.config.refractionStrength),n.uniform1f(this.boundaryUniforms.uChromaticStrength,this.config.chromaticStrength),n.uniform1f(this.boundaryUniforms.uOcclusionIntensity,this.config.occlusionIntensity),n.uniform1f(this.boundaryUniforms.uEdgeThickness,this.config.edgeThickness),n.uniform1f(this.boundaryUniforms.uEdgeSpecular,this.config.edgeSpecular),n.uniform3f(this.boundaryUniforms.uEdgeColor,...this.config.edgeColor),n.uniform2f(this.boundaryUniforms.uLightDir,this.lightDirX,this.lightDirY),n.uniform1f(this.boundaryUniforms.uBevelIntensity,this.config.bevelIntensity)),this.recalculateViewportLayout())}uploadStencilMesh(t){const e=this.gl;if(!e||!this.stencilProgram)return;this.stencilVao=e.createVertexArray(),e.bindVertexArray(this.stencilVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const o=e.getAttribLocation(this.stencilProgram,"aPosition");e.enableVertexAttribArray(o),e.vertexAttribPointer(o,2,e.FLOAT,!1,0,0);const n=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,n),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),this.stencilIndexCount=t.indices.length,e.bindVertexArray(null)}uploadMaskMesh(t){const e=this.gl;if(!e||!this.maskProgram)return;this.maskVao=e.createVertexArray(),e.bindVertexArray(this.maskVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const o=e.getAttribLocation(this.maskProgram,"aPosition");e.enableVertexAttribArray(o),e.vertexAttribPointer(o,2,e.FLOAT,!1,0,0);const n=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,n),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),e.bindVertexArray(null)}uploadBoundaryMesh(t){const e=this.gl;if(!e||!this.boundaryProgram)return;const i=de(t.edgeVertices);if(i.count===0)return;this.boundaryVao=e.createVertexArray(),e.bindVertexArray(this.boundaryVao);const o=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,o),e.bufferData(e.ARRAY_BUFFER,i.vertices,e.STATIC_DRAW);const n=16,s=e.getAttribLocation(this.boundaryProgram,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,n,0);const h=e.getAttribLocation(this.boundaryProgram,"aNormal");h>=0&&(e.enableVertexAttribArray(h),e.vertexAttribPointer(h,2,e.FLOAT,!1,n,8)),this.boundaryVertexCount=i.count,e.bindVertexArray(null)}uploadChamferMesh(t){const e=this.gl;if(!e||!this.chamferProgram||this.config.chamferWidth<=0)return;const i=me(t.edgeVertices,t.contourOffsets,t.contourIsHole,this.config.chamferWidth,this.config.chamferAngle);if(i.count===0)return;this.chamferVao=e.createVertexArray(),e.bindVertexArray(this.chamferVao);const o=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,o),e.bufferData(e.ARRAY_BUFFER,i.vertices,e.STATIC_DRAW);const n=24,s=e.getAttribLocation(this.chamferProgram,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,n,0);const h=e.getAttribLocation(this.chamferProgram,"aNormal3");h>=0&&(e.enableVertexAttribArray(h),e.vertexAttribPointer(h,3,e.FLOAT,!1,n,8));const a=e.getAttribLocation(this.chamferProgram,"aLerpT");a>=0&&(e.enableVertexAttribArray(a),e.vertexAttribPointer(a,1,e.FLOAT,!1,n,20)),this.chamferVertexCount=i.count,e.bindVertexArray(null)}disposeChamferGeometry(){const t=this.gl;t&&(this.chamferVao&&(t.deleteVertexArray(this.chamferVao),this.chamferVao=null),this.chamferVertexCount=0)}createFBO(t,e){const i=this.gl;if(!i)return;this.disposeFBO(),this.fboWidth=t,this.fboHeight=e,this.interiorFbo=i.createFramebuffer(),i.bindFramebuffer(i.FRAMEBUFFER,this.interiorFbo),this.interiorColorTex=i.createTexture(),i.activeTexture(i.TEXTURE2),i.bindTexture(i.TEXTURE_2D,this.interiorColorTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,t,e),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,this.interiorColorTex,0),this.interiorDepthTex=i.createTexture(),i.activeTexture(i.TEXTURE3),i.bindTexture(i.TEXTURE_2D,this.interiorDepthTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,t,e),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT1,i.TEXTURE_2D,this.interiorDepthTex,0),i.drawBuffers([i.COLOR_ATTACHMENT0,i.COLOR_ATTACHMENT1]);const o=i.checkFramebufferStatus(i.FRAMEBUFFER);o!==i.FRAMEBUFFER_COMPLETE&&console.error("Interior FBO incomplete:",o),i.bindFramebuffer(i.FRAMEBUFFER,null)}createJFAResources(t,e){const i=this.gl;if(!i)return;this.disposeJFA();const o=Math.max(1,Math.round(t/2)),n=Math.max(1,Math.round(e/2));this.jfaWidth=o,this.jfaHeight=n;const s=(a,l,u,f)=>{const c=i.createFramebuffer();return i.bindFramebuffer(i.FRAMEBUFFER,c),i.bindTexture(i.TEXTURE_2D,a),i.texStorage2D(i.TEXTURE_2D,1,l,u,f),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,a,0),i.bindFramebuffer(i.FRAMEBUFFER,null),c};this.maskTex=i.createTexture(),this.maskFbo=s(this.maskTex,i.R8,o,n);const h=this.hasColorBufferFloat?i.RG16F:i.RGBA8;this.jfaPingTex=i.createTexture(),this.jfaPingFbo=s(this.jfaPingTex,h,o,n),this.jfaPongTex=i.createTexture(),this.jfaPongFbo=s(this.jfaPongTex,h,o,n),this.distTex=i.createTexture(),this.distFbo=s(this.distTex,i.RGBA8,o,n),this.distFieldDirty=!0}computeDistanceField(){const t=this.gl;if(!t||!this.maskFbo||!this.maskVao||!this.quadVao||!this.jfaPingFbo||!this.jfaPongFbo||!this.distFbo)return;const e=this.jfaWidth,i=this.jfaHeight;if(e===0||i===0)return;t.viewport(0,0,e,i),t.disable(t.STENCIL_TEST),t.disable(t.BLEND),t.bindFramebuffer(t.FRAMEBUFFER,this.maskFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.maskProgram),t.uniform2f(this.maskUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.bindVertexArray(this.maskVao),t.drawElements(t.TRIANGLES,this.stencilIndexCount,t.UNSIGNED_SHORT,0),t.bindFramebuffer(t.FRAMEBUFFER,this.jfaPingFbo),t.clearColor(-1,-1,0,0),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.jfaSeedProgram),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(this.jfaSeedUniforms.uMask,5),t.uniform2f(this.jfaSeedUniforms.uTexelSize,1/e,1/i),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const o=Math.max(e,i),n=[];let s=Math.ceil(o/2);for(;s>=1;)n.push(s),s=Math.floor(s/2);t.useProgram(this.jfaFloodProgram);let h=this.jfaPingTex,a=this.jfaPongFbo,l=this.jfaPongTex;for(let f=0;f<n.length;f++){const c=n[f]/Math.max(e,i);t.bindFramebuffer(t.FRAMEBUFFER,a),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,h),t.uniform1i(this.jfaFloodUniforms.uSeedTex,5),t.uniform1f(this.jfaFloodUniforms.uStepSize,c),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const g=h,d=a;h=l,a=d===this.jfaPongFbo?this.jfaPingFbo:this.jfaPongFbo,l=g}t.bindFramebuffer(t.FRAMEBUFFER,this.distFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.jfaDistProgram),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,h),t.uniform1i(this.jfaDistUniforms.uSeedTex,5),t.activeTexture(t.TEXTURE6),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(this.jfaDistUniforms.uMask,6);const u=Math.max(this.config.bevelWidth,this.config.edgeOcclusionWidth);t.uniform1f(this.jfaDistUniforms.uBevelWidth,u),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.activeTexture(t.TEXTURE4),t.bindTexture(t.TEXTURE_2D,this.distTex),t.bindFramebuffer(t.FRAMEBUFFER,null),this.distFieldDirty=!1}start(t,e,i,o){this.stop(),this.playbackVideo=t,this.readDepth=e,this.readInput=i,this.onVideoFrame=o??null,this.rvfcSupported="requestVideoFrameCallback"in HTMLVideoElement.prototype,this.rvfcSupported&&(this.rvfcHandle=t.requestVideoFrameCallback(this.videoFrameLoop)),this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)}stop(){this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0),this.rvfcHandle&&this.playbackVideo&&(this.playbackVideo.cancelVideoFrameCallback(this.rvfcHandle),this.rvfcHandle=0),this.playbackVideo=null,this.readDepth=null,this.readInput=null,this.onVideoFrame=null,this.rvfcSupported=!1}dispose(){this.stop(),this.disposeTextures(),this.disposeFBO(),this.disposeJFA(),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.disposeGPUResources(),this.canvas.removeEventListener("webglcontextlost",this.handleContextLost),this.canvas.removeEventListener("webglcontextrestored",this.handleContextRestored),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null),this.canvas.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),window.removeEventListener("resize",this.scheduleResizeRecalculate),this.resizeTimer!==null&&(window.clearTimeout(this.resizeTimer),this.resizeTimer=null)}initGPUResources(){const t=this.gl;if(!t)return;this.stencilProgram=O(t,L(t,t.VERTEX_SHADER,Zt),L(t,t.FRAGMENT_SHADER,$t)),this.stencilUniforms={uMeshScale:t.getUniformLocation(this.stencilProgram,"uMeshScale")},this.maskProgram=O(t,L(t,t.VERTEX_SHADER,Jt),L(t,t.FRAGMENT_SHADER,Kt)),this.maskUniforms={uMeshScale:t.getUniformLocation(this.maskProgram,"uMeshScale")},this.jfaSeedProgram=O(t,L(t,t.VERTEX_SHADER,Qt),L(t,t.FRAGMENT_SHADER,te)),this.jfaSeedUniforms=this.getUniforms(this.jfaSeedProgram,["uMask","uTexelSize"]),this.jfaFloodProgram=O(t,L(t,t.VERTEX_SHADER,ee),L(t,t.FRAGMENT_SHADER,ie)),this.jfaFloodUniforms=this.getUniforms(this.jfaFloodProgram,["uSeedTex","uStepSize"]),this.jfaDistProgram=O(t,L(t,t.VERTEX_SHADER,re),L(t,t.FRAGMENT_SHADER,oe)),this.jfaDistUniforms=this.getUniforms(this.jfaDistProgram,["uSeedTex","uMask","uBevelWidth"]),this.interiorProgram=O(t,L(t,t.VERTEX_SHADER,ne),L(t,t.FRAGMENT_SHADER,se)),this.interiorUniforms=this.getUniforms(this.interiorProgram,["uImage","uDepth","uOffset","uStrength","uPomSteps","uDepthPower","uDepthScale","uDepthBias","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uFogDensity","uFogColor","uColorShift","uBrightnessBias","uUvOffset","uUvScale"]),this.compositeProgram=O(t,L(t,t.VERTEX_SHADER,ae),L(t,t.FRAGMENT_SHADER,he)),this.compositeUniforms=this.getUniforms(this.compositeProgram,["uInteriorColor","uDistField","uEdgeOcclusionWidth","uEdgeOcclusionStrength"]),this.boundaryProgram=O(t,L(t,t.VERTEX_SHADER,le),L(t,t.FRAGMENT_SHADER,ce)),this.boundaryUniforms=this.getUniforms(this.boundaryProgram,["uInteriorColor","uInteriorDepth","uDistField","uRimIntensity","uRimColor","uRimWidth","uMeshScale","uRefractionStrength","uChromaticStrength","uOcclusionIntensity","uTexelSize","uEdgeThickness","uEdgeSpecular","uEdgeColor","uLightDir","uBevelIntensity"]),this.chamferProgram=O(t,L(t,t.VERTEX_SHADER,ue),L(t,t.FRAGMENT_SHADER,fe)),this.chamferUniforms=this.getUniforms(this.chamferProgram,["uMeshScale","uLightDir3","uChamferColor","uChamferAmbient","uChamferSpecular","uChamferShininess","uInteriorColor","uTexelSize"]);const e=new Float32Array([-1,-1,1,-1,-1,1,1,1]);this.quadVao=t.createVertexArray(),t.bindVertexArray(this.quadVao);const i=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,i),t.bufferData(t.ARRAY_BUFFER,e,t.STATIC_DRAW);const o=t.getAttribLocation(this.interiorProgram,"aPosition");t.enableVertexAttribArray(o),t.vertexAttribPointer(o,2,t.FLOAT,!1,0,0),t.bindVertexArray(null),t.disable(t.DEPTH_TEST)}getUniforms(t,e){const i=this.gl,o={};for(const n of e)o[n]=i.getUniformLocation(t,n);return o}videoFrameLoop=(t,e)=>{const i=this.playbackVideo;if(!i)return;this.rvfcHandle=i.requestVideoFrameCallback(this.videoFrameLoop);const o=e.mediaTime??i.currentTime;this.updateDepthTexture(o),this.onVideoFrame&&this.onVideoFrame(o,e.presentedFrames??0)};renderLoop=()=>{this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop);const t=this.gl,e=this.playbackVideo;if(!t||!this.interiorProgram||!this.quadVao||!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA||!this.interiorFbo||!this.interiorColorTex||!this.interiorDepthTex)return;this.distFieldDirty&&this.maskVao&&this.distFbo&&(this.computeDistanceField(),t.viewport(0,0,this.canvas.width,this.canvas.height)),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),this.rvfcSupported||this.updateDepthTexture(e.currentTime);let i=0,o=0;if(this.readInput){const n=this.readInput();i=-n.x,o=n.y}if(t.bindFramebuffer(t.FRAMEBUFFER,this.interiorFbo),t.checkFramebufferStatus(t.FRAMEBUFFER)!==t.FRAMEBUFFER_COMPLETE){t.bindFramebuffer(t.FRAMEBUFFER,null);return}t.viewport(0,0,this.fboWidth,this.fboHeight),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.interiorProgram),t.uniform2f(this.interiorUniforms.uOffset,i,o),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.activeTexture(t.TEXTURE1),t.bindTexture(t.TEXTURE_2D,this.depthTexture),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.bindFramebuffer(t.FRAMEBUFFER,null),t.clearColor(0,0,0,0),t.viewport(0,0,this.canvas.width,this.canvas.height),t.clear(t.COLOR_BUFFER_BIT|t.STENCIL_BUFFER_BIT),this.stencilVao&&this.stencilProgram&&this.stencilIndexCount>0&&(t.enable(t.STENCIL_TEST),t.stencilFunc(t.ALWAYS,1,255),t.stencilOp(t.KEEP,t.KEEP,t.REPLACE),t.stencilMask(255),t.colorMask(!1,!1,!1,!1),t.useProgram(this.stencilProgram),t.bindVertexArray(this.stencilVao),t.drawElements(t.TRIANGLES,this.stencilIndexCount,t.UNSIGNED_SHORT,0),t.colorMask(!0,!0,!0,!0)),t.stencilFunc(t.EQUAL,1,255),t.stencilMask(0),t.activeTexture(t.TEXTURE2),t.bindTexture(t.TEXTURE_2D,this.interiorColorTex),t.activeTexture(t.TEXTURE3),t.bindTexture(t.TEXTURE_2D,this.interiorDepthTex),t.activeTexture(t.TEXTURE4),t.bindTexture(t.TEXTURE_2D,this.distTex),t.useProgram(this.compositeProgram),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.disable(t.STENCIL_TEST),this.chamferVao&&this.chamferProgram&&this.chamferVertexCount>0&&(t.useProgram(this.chamferProgram),t.uniform2f(this.chamferUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform2f(this.chamferUniforms.uTexelSize,1/this.canvas.width,1/this.canvas.height),t.bindVertexArray(this.chamferVao),t.drawArrays(t.TRIANGLES,0,this.chamferVertexCount)),this.boundaryVao&&this.boundaryProgram&&this.boundaryVertexCount>0&&this.config.rimLightIntensity>0&&(t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA),t.useProgram(this.boundaryProgram),t.bindVertexArray(this.boundaryVao),t.drawArrays(t.TRIANGLES,0,this.boundaryVertexCount),t.disable(t.BLEND))};updateDepthTexture(t){const e=this.gl;if(!e||!this.readDepth||!this.depthTexture)return;const i=this.readDepth(t);e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.depthTexture),e.texSubImage2D(e.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,e.RED,e.UNSIGNED_BYTE,i)}setupResizeHandling(){typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.scheduleResizeRecalculate()}),this.resizeObserver.observe(this.container)),window.addEventListener("resize",this.scheduleResizeRecalculate),this.recalculateViewportLayout()}scheduleResizeRecalculate=()=>{this.resizeTimer!==null&&window.clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.resizeTimer=null,this.recalculateViewportLayout()},ft.RESIZE_DEBOUNCE_MS)};recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:i}=this.getViewportSize(),o=Math.min(window.devicePixelRatio,2),n=Math.round(e*o),s=Math.round(i*o);(this.canvas.width!==n||this.canvas.height!==s)&&(this.canvas.width=n,this.canvas.height=s,t.viewport(0,0,n,s)),(this.fboWidth!==n||this.fboHeight!==s)&&this.createFBO(n,s);const h=Math.max(1,Math.round(n/2)),a=Math.max(1,Math.round(s/2));(this.jfaWidth!==h||this.jfaHeight!==a)&&this.createJFAResources(n,s);const l=e/i,u=this.config.parallaxStrength+this.config.overscanPadding;let f=1,c=1;l>this.videoAspect?c=this.videoAspect/l:f=l/this.videoAspect;const g=1+u*2;f/=g,c/=g,this.uvOffset=[(1-f)/2,(1-c)/2],this.uvScale=[f,c],this.interiorProgram&&(t.useProgram(this.interiorProgram),t.uniform2f(this.interiorUniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),t.uniform2f(this.interiorUniforms.uUvScale,this.uvScale[0],this.uvScale[1]));const d=.65;this.meshScaleX=d,this.meshScaleY=d,l>this.meshAspect?this.meshScaleX=d*(this.meshAspect/l):this.meshScaleY=d*(l/this.meshAspect),this.stencilProgram&&(t.useProgram(this.stencilProgram),t.uniform2f(this.stencilUniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.boundaryProgram&&(t.useProgram(this.boundaryProgram),t.uniform2f(this.boundaryUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform1f(this.boundaryUniforms.uRimWidth,this.config.rimLightWidth),t.uniform2f(this.boundaryUniforms.uTexelSize,1/n,1/s)),this.chamferProgram&&(t.useProgram(this.chamferProgram),t.uniform2f(this.chamferUniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.distFieldDirty=!0}getViewportSize(){const t=Math.max(1,Math.round(this.container.clientWidth||window.innerWidth)),e=Math.max(1,Math.round(this.container.clientHeight||window.innerHeight));return{width:t,height:e}}handleContextLost=t=>{t.preventDefault(),this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0)};handleContextRestored=()=>{const t=this.canvas.getContext("webgl2",{alpha:!0,premultipliedAlpha:!0,stencil:!0});t&&(this.gl=t,this.hasColorBufferFloat=!!t.getExtension("EXT_color_buffer_float"),t.clearColor(0,0,0,0),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.recalculateViewportLayout(),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)))};disposeTextures(){const t=this.gl;t&&(this.videoTexture&&(t.deleteTexture(this.videoTexture),this.videoTexture=null),this.depthTexture&&(t.deleteTexture(this.depthTexture),this.depthTexture=null))}disposeFBO(){const t=this.gl;t&&(this.interiorColorTex&&(t.deleteTexture(this.interiorColorTex),this.interiorColorTex=null),this.interiorDepthTex&&(t.deleteTexture(this.interiorDepthTex),this.interiorDepthTex=null),this.interiorFbo&&(t.deleteFramebuffer(this.interiorFbo),this.interiorFbo=null),this.fboWidth=0,this.fboHeight=0)}disposeJFA(){const t=this.gl;t&&(this.maskTex&&(t.deleteTexture(this.maskTex),this.maskTex=null),this.maskFbo&&(t.deleteFramebuffer(this.maskFbo),this.maskFbo=null),this.jfaPingTex&&(t.deleteTexture(this.jfaPingTex),this.jfaPingTex=null),this.jfaPingFbo&&(t.deleteFramebuffer(this.jfaPingFbo),this.jfaPingFbo=null),this.jfaPongTex&&(t.deleteTexture(this.jfaPongTex),this.jfaPongTex=null),this.jfaPongFbo&&(t.deleteFramebuffer(this.jfaPongFbo),this.jfaPongFbo=null),this.distTex&&(t.deleteTexture(this.distTex),this.distTex=null),this.distFbo&&(t.deleteFramebuffer(this.distFbo),this.distFbo=null),this.jfaWidth=0,this.jfaHeight=0,this.distFieldDirty=!0)}disposeStencilGeometry(){const t=this.gl;t&&(this.stencilVao&&(t.deleteVertexArray(this.stencilVao),this.stencilVao=null),this.maskVao&&(t.deleteVertexArray(this.maskVao),this.maskVao=null),this.stencilIndexCount=0)}disposeBoundaryGeometry(){const t=this.gl;t&&(this.boundaryVao&&(t.deleteVertexArray(this.boundaryVao),this.boundaryVao=null),this.boundaryVertexCount=0)}disposeGPUResources(){const t=this.gl;t&&(this.stencilProgram&&(t.deleteProgram(this.stencilProgram),this.stencilProgram=null),this.maskProgram&&(t.deleteProgram(this.maskProgram),this.maskProgram=null),this.jfaSeedProgram&&(t.deleteProgram(this.jfaSeedProgram),this.jfaSeedProgram=null),this.jfaFloodProgram&&(t.deleteProgram(this.jfaFloodProgram),this.jfaFloodProgram=null),this.jfaDistProgram&&(t.deleteProgram(this.jfaDistProgram),this.jfaDistProgram=null),this.interiorProgram&&(t.deleteProgram(this.interiorProgram),this.interiorProgram=null),this.compositeProgram&&(t.deleteProgram(this.compositeProgram),this.compositeProgram=null),this.boundaryProgram&&(t.deleteProgram(this.boundaryProgram),this.boundaryProgram=null),this.chamferProgram&&(t.deleteProgram(this.chamferProgram),this.chamferProgram=null),this.quadVao&&(t.deleteVertexArray(this.quadVao),this.quadVao=null),this.stencilUniforms={},this.maskUniforms={},this.jfaSeedUniforms={},this.jfaFloodUniforms={},this.jfaDistUniforms={},this.interiorUniforms={},this.compositeUniforms={},this.boundaryUniforms={},this.chamferUniforms={})}}async function pe(r){const t=await fetch(r);if(!t.ok)throw new Error(`Failed to fetch SVG: ${t.status} ${t.statusText}`);const e=await t.text();return ge(e)}function ge(r){const i=new DOMParser().parseFromString(r,"image/svg+xml").querySelector("svg");if(!i)throw new Error("No <svg> element found in document.");const o=ve(i);if(o.length===0)throw new Error("No path data found in SVG.");let n=1/0,s=1/0,h=-1/0,a=-1/0;for(const F of o)for(let R=0;R<F.length;R+=2)n=Math.min(n,F[R]),h=Math.max(h,F[R]),s=Math.min(s,F[R+1]),a=Math.max(a,F[R+1]);const l=h-n,u=a-s,f=(n+h)/2,c=(s+a)/2,g=2/Math.max(l,u),d=l/u,m=o.map(F=>{const R=[];for(let C=0;C<F.length;C+=2)R.push((F[C]-f)*g),R.push(-((F[C+1]-c)*g));return R}),U=Re(m),y=[],E=[];for(const F of U){const{flatCoords:R,holeIndices:C}=Se(F),M=Fe(R,C),B=y.length/2;for(const I of M)E.push(I+B);for(const I of R)y.push(I)}const p=y,v=E,T=[],x=[],A=[],S=St(m);for(let F=0;F<m.length;F++){const R=m[F];x.push(T.length),A.push(S[F]);for(let C=0;C<R.length;C++)T.push(R[C]);R.length>=2&&T.push(R[0],R[1])}let D=1/0,P=1/0,_=-1/0,k=-1/0;for(let F=0;F<p.length;F+=2)D=Math.min(D,p[F]),_=Math.max(_,p[F]),P=Math.min(P,p[F+1]),k=Math.max(k,p[F+1]);return{vertices:new Float32Array(p),indices:new Uint16Array(v),edgeVertices:new Float32Array(T),contourOffsets:x,contourIsHole:A,bounds:{minX:D,maxX:_,minY:P,maxY:k},aspect:d}}function ve(r){const t=[];return r.querySelectorAll("path").forEach(a=>{const l=a.getAttribute("d");if(!l)return;const u=Ee(l);t.push(...u)}),r.querySelectorAll("polygon").forEach(a=>{const l=a.getAttribute("points");if(!l)return;const u=bt(l);u.length>=6&&t.push(u)}),r.querySelectorAll("polyline").forEach(a=>{const l=a.getAttribute("points");if(!l)return;const u=bt(l);u.length>=6&&t.push(u)}),r.querySelectorAll("rect").forEach(a=>{const l=parseFloat(a.getAttribute("x")||"0"),u=parseFloat(a.getAttribute("y")||"0"),f=parseFloat(a.getAttribute("width")||"0"),c=parseFloat(a.getAttribute("height")||"0");f>0&&c>0&&t.push([l,u,l+f,u,l+f,u+c,l,u+c])}),r.querySelectorAll("circle").forEach(a=>{const l=parseFloat(a.getAttribute("cx")||"0"),u=parseFloat(a.getAttribute("cy")||"0"),f=parseFloat(a.getAttribute("r")||"0");f>0&&t.push(xe(l,u,f))}),r.querySelectorAll("ellipse").forEach(a=>{const l=parseFloat(a.getAttribute("cx")||"0"),u=parseFloat(a.getAttribute("cy")||"0"),f=parseFloat(a.getAttribute("rx")||"0"),c=parseFloat(a.getAttribute("ry")||"0");f>0&&c>0&&t.push(Te(l,u,f,c))}),t}function bt(r){const t=[],e=r.trim().split(/[\s,]+/);for(let i=0;i<e.length-1;i+=2){const o=parseFloat(e[i]),n=parseFloat(e[i+1]);Number.isFinite(o)&&Number.isFinite(n)&&t.push(o,n)}return t}function xe(r,t,e,i=64){const o=[];for(let n=0;n<i;n++){const s=2*Math.PI*n/i;o.push(r+e*Math.cos(s),t+e*Math.sin(s))}return o}function Te(r,t,e,i,o=64){const n=[];for(let s=0;s<o;s++){const h=2*Math.PI*s/o;n.push(r+e*Math.cos(h),t+i*Math.sin(h))}return n}function Ee(r){const t=[];let e=[],i=0,o=0,n=0,s=0,h=0,a=0,l="";const u=be(r);let f=0;function c(){return f>=u.length?0:parseFloat(u[f++])}for(;f<u.length;){const g=u[f];let d;/^[a-zA-Z]$/.test(g)?(d=g,f++):d=l==="M"?"L":l==="m"?"l":l;const m=d===d.toLowerCase();switch(d.toUpperCase()){case"M":{e.length>0&&t.push(e),e=[];const y=c()+(m?i:0),E=c()+(m?o:0);i=y,o=E,n=y,s=E,e.push(i,o),h=i,a=o;break}case"L":{i=c()+(m?i:0),o=c()+(m?o:0),e.push(i,o),h=i,a=o;break}case"H":{i=c()+(m?i:0),e.push(i,o),h=i,a=o;break}case"V":{o=c()+(m?o:0),e.push(i,o),h=i,a=o;break}case"C":{const y=c()+(m?i:0),E=c()+(m?o:0),p=c()+(m?i:0),v=c()+(m?o:0),T=c()+(m?i:0),x=c()+(m?o:0);$(e,i,o,y,E,p,v,T,x),i=T,o=x,h=p,a=v;break}case"S":{const y=2*i-h,E=2*o-a,p=c()+(m?i:0),v=c()+(m?o:0),T=c()+(m?i:0),x=c()+(m?o:0);$(e,i,o,y,E,p,v,T,x),i=T,o=x,h=p,a=v;break}case"Q":{const y=c()+(m?i:0),E=c()+(m?o:0),p=c()+(m?i:0),v=c()+(m?o:0);yt(e,i,o,y,E,p,v),i=p,o=v,h=y,a=E;break}case"T":{const y=2*i-h,E=2*o-a,p=c()+(m?i:0),v=c()+(m?o:0);yt(e,i,o,y,E,p,v),i=p,o=v,h=y,a=E;break}case"A":{const y=c(),E=c(),p=c(),v=c(),T=c(),x=c()+(m?i:0),A=c()+(m?o:0);Ae(e,i,o,y,E,p,!!v,!!T,x,A),i=x,o=A,h=i,a=o;break}case"Z":{i=n,o=s,e.length>0&&t.push(e),e=[],h=i,a=o;break}default:f++;break}l=d}return e.length>=6&&t.push(e),t}function be(r){const t=[],e=/([a-zA-Z])|([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/g;let i;for(;(i=e.exec(r))!==null;)t.push(i[0]);return t}const ye=.5;function $(r,t,e,i,o,n,s,h,a,l=0){if(l>12){r.push(h,a);return}const u=h-t,f=a-e,c=Math.sqrt(u*u+f*f);if(c<1e-6){r.push(h,a);return}const g=Math.abs((i-h)*f-(o-a)*u)/c,d=Math.abs((n-h)*f-(s-a)*u)/c;if(g+d<ye){r.push(h,a);return}const m=(t+i)/2,U=(e+o)/2,y=(i+n)/2,E=(o+s)/2,p=(n+h)/2,v=(s+a)/2,T=(m+y)/2,x=(U+E)/2,A=(y+p)/2,S=(E+v)/2,D=(T+A)/2,P=(x+S)/2;$(r,t,e,m,U,T,x,D,P,l+1),$(r,D,P,A,S,p,v,h,a,l+1)}function yt(r,t,e,i,o,n,s){const h=t+.6666666666666666*(i-t),a=e+2/3*(o-e),l=n+2/3*(i-n),u=s+2/3*(o-s);$(r,t,e,h,a,l,u,n,s)}function Ae(r,t,e,i,o,n,s,h,a,l){if(i===0||o===0){r.push(a,l);return}let u=Math.abs(i),f=Math.abs(o);const c=n*Math.PI/180,g=Math.cos(c),d=Math.sin(c),m=(t-a)/2,U=(e-l)/2,y=g*m+d*U,E=-d*m+g*U;let p=y*y/(u*u)+E*E/(f*f);if(p>1){const M=Math.sqrt(p);u*=M,f*=M,p=1}const v=u*u,T=f*f,x=y*y,A=E*E;let S=Math.max(0,(v*T-v*A-T*x)/(v*A+T*x));S=Math.sqrt(S),s===h&&(S=-S);const D=S*(u*E)/f,P=S*-(f*y)/u,_=g*D-d*P+(t+a)/2,k=d*D+g*P+(e+l)/2,F=At(1,0,(y-D)/u,(E-P)/f);let R=At((y-D)/u,(E-P)/f,(-y-D)/u,(-E-P)/f);!h&&R>0&&(R-=2*Math.PI),h&&R<0&&(R+=2*Math.PI);const C=Math.max(4,Math.ceil(Math.abs(R)/(Math.PI/16)));for(let M=1;M<=C;M++){const B=F+M/C*R,I=Math.cos(B),G=Math.sin(B),tt=g*u*I-d*f*G+_,ct=d*u*I+g*f*G+k;r.push(tt,ct)}}function At(r,t,e,i){const o=r*i-t*e<0?-1:1,n=r*e+t*i,s=Math.sqrt(r*r+t*t),h=Math.sqrt(e*e+i*i),a=n/(s*h);return o*Math.acos(Math.max(-1,Math.min(1,a)))}function Se(r){const t=[],e=[];for(let i=0;i<r.length;i++){i>0&&e.push(t.length/2);for(const o of r[i])t.push(o)}return{flatCoords:t,holeIndices:e}}function St(r){const t=r.length,e=r.map(o=>Math.abs(Rt(o))),i=new Array(t).fill(!1);for(let o=0;o<t;o++){let n=0;const s=r[o][0],h=r[o][1];for(let a=0;a<t;a++)o!==a&&e[a]>e[o]&&Ft(s,h,r[a])&&n++;i[o]=n%2===1}return i}function Re(r){if(r.length<=1)return[r];const t=St(r),e=r.map((s,h)=>{const a=Rt(s);return{index:h,contour:s,area:a,isOuter:!t[h]}}),i=e.filter(s=>s.isOuter),o=e.filter(s=>!s.isOuter);if(i.length===0)return r.map(s=>[s]);const n=i.map(s=>({outer:s.contour,holes:[]}));for(const s of o){const h=s.contour[0],a=s.contour[1];let l=-1,u=1/0;for(let f=0;f<i.length;f++)if(Ft(h,a,i[f].contour)){const c=Math.abs(i[f].area);c<u&&(u=c,l=f)}l>=0?n[l].holes.push(s.contour):n.push({outer:s.contour,holes:[]})}return n.map(s=>[s.outer,...s.holes])}function Rt(r){let t=0;const e=r.length;for(let i=0;i<e;i+=2){const o=r[i],n=r[i+1],s=r[(i+2)%e],h=r[(i+3)%e];t+=o*h-s*n}return t/2}function Ft(r,t,e){let i=!1;const o=e.length;for(let n=0,s=o-2;n<o;s=n,n+=2){const h=e[n],a=e[n+1],l=e[s],u=e[s+1];a>t!=u>t&&r<(l-h)*(t-a)/(u-a)+h&&(i=!i)}return i}function Fe(r,t,e=2){const i=t&&t.length>0,o=i?t[0]*e:r.length;let n=Dt(r,0,o,e,!0);const s=[];if(!n||n.next===n.prev)return s;i&&(n=Le(r,t,n,e));let h=1/0,a=1/0,l=-1/0,u=-1/0,f=0;if(r.length>80*e){for(let c=0;c<o;c+=e){const g=r[c],d=r[c+1];g<h&&(h=g),d<a&&(a=d),g>l&&(l=g),d>u&&(u=d)}f=Math.max(l-h,u-a),f=f!==0?32767/f:0}return J(n,s,e,h,a,f,0),s}function Dt(r,t,e,i,o){let n=null;if(o===Xe(r,t,e,i)>0)for(let s=t;s<e;s+=i)n=wt(s,r[s],r[s+1],n);else for(let s=e-i;s>=t;s-=i)n=wt(s,r[s],r[s+1],n);return n&&nt(n,n.next)&&(Q(n),n=n.next),n?(n.next.prev=n,n.prev.next=n,n.next):null}function X(r,t){t||(t=r);let e=r,i;do if(i=!1,!e.steiner&&(nt(e,e.next)||w(e.prev,e,e.next)===0)){if(Q(e),e=t=e.prev,e===e.next)break;i=!0}else e=e.next;while(i||e!==t);return t}function J(r,t,e,i,o,n,s){if(!r)return;!s&&n&&Ie(r,i,o,n);let h=r,a,l;for(;r.prev!==r.next;){if(a=r.prev,l=r.next,n?Ue(r,i,o,n):De(r)){t.push(a.i/e,r.i/e,l.i/e),Q(r),r=l.next,h=l.next;continue}if(r=l,r===h){s?s===1?(r=Pe(X(r),t,e),J(r,t,e,i,o,n,2)):s===2&&we(r,t,e,i,o,n):J(X(r),t,e,i,o,n,1);break}}}function De(r){const t=r.prev,e=r,i=r.next;if(w(t,e,i)>=0)return!1;const o=t.x,n=e.x,s=i.x,h=t.y,a=e.y,l=i.y,u=o<n?o<s?o:s:n<s?n:s,f=h<a?h<l?h:l:a<l?a:l,c=o>n?o>s?o:s:n>s?n:s,g=h>a?h>l?h:l:a>l?a:l;let d=i.next;for(;d!==t;){if(d.x>=u&&d.x<=c&&d.y>=f&&d.y<=g&&z(o,h,n,a,s,l,d.x,d.y)&&w(d.prev,d,d.next)>=0)return!1;d=d.next}return!0}function Ue(r,t,e,i){const o=r.prev,n=r,s=r.next;if(w(o,n,s)>=0)return!1;const h=o.x,a=n.x,l=s.x,u=o.y,f=n.y,c=s.y,g=h<a?h<l?h:l:a<l?a:l,d=u<f?u<c?u:c:f<c?f:c,m=h>a?h>l?h:l:a>l?a:l,U=u>f?u>c?u:c:f>c?f:c,y=dt(g,d,t,e,i),E=dt(m,U,t,e,i);let p=r.prevZ,v=r.nextZ;for(;p&&p.z>=y&&v&&v.z<=E;){if(p.x>=g&&p.x<=m&&p.y>=d&&p.y<=U&&p!==o&&p!==s&&z(h,u,a,f,l,c,p.x,p.y)&&w(p.prev,p,p.next)>=0||(p=p.prevZ,v.x>=g&&v.x<=m&&v.y>=d&&v.y<=U&&v!==o&&v!==s&&z(h,u,a,f,l,c,v.x,v.y)&&w(v.prev,v,v.next)>=0))return!1;v=v.nextZ}for(;p&&p.z>=y;){if(p.x>=g&&p.x<=m&&p.y>=d&&p.y<=U&&p!==o&&p!==s&&z(h,u,a,f,l,c,p.x,p.y)&&w(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;v&&v.z<=E;){if(v.x>=g&&v.x<=m&&v.y>=d&&v.y<=U&&v!==o&&v!==s&&z(h,u,a,f,l,c,v.x,v.y)&&w(v.prev,v,v.next)>=0)return!1;v=v.nextZ}return!0}function Pe(r,t,e){let i=r;do{const o=i.prev,n=i.next.next;!nt(o,n)&&Ut(o,i,i.next,n)&&K(o,n)&&K(n,o)&&(t.push(o.i/e,i.i/e,n.i/e),Q(i),Q(i.next),i=r=n),i=i.next}while(i!==r);return X(i)}function we(r,t,e,i,o,n){let s=r;do{let h=s.next.next;for(;h!==s.prev;){if(s.i!==h.i&&ke(s,h)){let a=Pt(s,h);s=X(s,s.next),a=X(a,a.next),J(s,t,e,i,o,n,0),J(a,t,e,i,o,n,0);return}h=h.next}s=s.next}while(s!==r)}function Le(r,t,e,i){const o=[];for(let n=0;n<t.length;n++){const s=t[n]*i,h=n<t.length-1?t[n+1]*i:r.length,a=Dt(r,s,h,i,!1);a&&(a===a.next&&(a.steiner=!0),o.push(Oe(a)))}o.sort((n,s)=>n.x-s.x);for(const n of o)e=Ce(n,e);return e}function Ce(r,t){const e=Me(r,t);if(!e)return t;const i=Pt(e,r);return X(i,i.next),X(e,e.next)}function Me(r,t){let e=t;const i=r.x,o=r.y;let n=-1/0,s=null;do{if(o<=e.y&&o>=e.next.y&&e.next.y!==e.y){const f=e.x+(o-e.y)/(e.next.y-e.y)*(e.next.x-e.x);if(f<=i&&f>n&&(n=f,s=e.x<e.next.x?e:e.next,f===i))return s}e=e.next}while(e!==t);if(!s)return null;const h=s,a=s.x,l=s.y;let u=1/0;e=s;do{if(i>=e.x&&e.x>=a&&i!==e.x&&z(o<l?i:n,o,a,l,o<l?n:i,o,e.x,e.y)){const f=Math.abs(o-e.y)/(i-e.x);K(e,r)&&(f<u||f===u&&(e.x>s.x||_e(s,e)))&&(s=e,u=f)}e=e.next}while(e!==h);return s}function _e(r,t){return w(r.prev,r,t.prev)<0&&w(t.next,r,r.next)<0}function Ie(r,t,e,i){let o=r;do o.z===0&&(o.z=dt(o.x,o.y,t,e,i)),o.prevZ=o.prev,o.nextZ=o.next,o=o.next;while(o!==r);o.prevZ.nextZ=null,o.prevZ=null,Ve(o)}function Ve(r){let t=1,e;do{let i=r;r=null;let o=null;for(e=0;i;){e++;let n=i,s=0;for(let a=0;a<t&&(s++,n=n.nextZ,!!n);a++);let h=t;for(;s>0||h>0&&n;){let a;s!==0&&(h===0||!n||i.z<=n.z)?(a=i,i=i.nextZ,s--):(a=n,n=n.nextZ,h--),o?o.nextZ=a:r=a,a.prevZ=o,o=a}i=n}o.nextZ=null,t*=2}while(e>1);return r}function dt(r,t,e,i,o){let n=(r-e)*o|0,s=(t-i)*o|0;return n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,s=(s|s<<8)&16711935,s=(s|s<<4)&252645135,s=(s|s<<2)&858993459,s=(s|s<<1)&1431655765,n|s<<1}function Oe(r){let t=r,e=r;do(t.x<e.x||t.x===e.x&&t.y<e.y)&&(e=t),t=t.next;while(t!==r);return e}function z(r,t,e,i,o,n,s,h){return(o-s)*(t-h)-(r-s)*(n-h)>=0&&(r-s)*(i-h)-(e-s)*(t-h)>=0&&(e-s)*(n-h)-(o-s)*(i-h)>=0}function ke(r,t){return r.next.i!==t.i&&r.prev.i!==t.i&&!Be(r,t)&&(K(r,t)&&K(t,r)&&He(r,t)&&(w(r.prev,r,t.prev)!==0||w(r,t.prev,t)!==0)||nt(r,t)&&w(r.prev,r,r.next)>0&&w(t.prev,t,t.next)>0)}function w(r,t,e){return(t.y-r.y)*(e.x-t.x)-(t.x-r.x)*(e.y-t.y)}function nt(r,t){return r.x===t.x&&r.y===t.y}function Ut(r,t,e,i){const o=at(w(r,t,e)),n=at(w(r,t,i)),s=at(w(e,i,r)),h=at(w(e,i,t));return!!(o!==n&&s!==h||o===0&&st(r,e,t)||n===0&&st(r,i,t)||s===0&&st(e,r,i)||h===0&&st(e,t,i))}function st(r,t,e){return t.x<=Math.max(r.x,e.x)&&t.x>=Math.min(r.x,e.x)&&t.y<=Math.max(r.y,e.y)&&t.y>=Math.min(r.y,e.y)}function at(r){return r>0?1:r<0?-1:0}function Be(r,t){let e=r;do{if(e.i!==r.i&&e.next.i!==r.i&&e.i!==t.i&&e.next.i!==t.i&&Ut(e,e.next,r,t))return!0;e=e.next}while(e!==r);return!1}function K(r,t){return w(r.prev,r,r.next)<0?w(r,t,r.next)>=0&&w(r,r.prev,t)>=0:w(r,t,r.prev)<0||w(r,r.next,t)<0}function He(r,t){let e=r,i=!1;const o=(r.x+t.x)/2,n=(r.y+t.y)/2;do e.y>n!=e.next.y>n&&e.next.y!==e.y&&o<(e.next.x-e.x)*(n-e.y)/(e.next.y-e.y)+e.x&&(i=!i),e=e.next;while(e!==r);return i}function Pt(r,t){const e=mt(r.i,r.x,r.y),i=mt(t.i,t.x,t.y),o=r.next,n=t.prev;return r.next=t,t.prev=r,e.next=o,o.prev=e,i.next=e,e.prev=i,n.next=i,i.prev=n,i}function wt(r,t,e,i){const o=mt(r,t,e);return i?(o.next=i.next,o.prev=i,i.next.prev=o,i.next=o):(o.prev=o,o.next=o),o}function Q(r){r.next.prev=r.prev,r.prev.next=r.next,r.prevZ&&(r.prevZ.nextZ=r.nextZ),r.nextZ&&(r.nextZ.prevZ=r.prevZ)}function mt(r,t,e){return{i:r,x:t,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function Xe(r,t,e,i){let o=0;for(let n=t,s=e-i;n<e;n+=i)o+=(r[s]-r[n])*(r[n+1]+r[s+1]),s=n;return o}const b={parallaxX:.4,parallaxY:.8,parallaxMax:30,overscan:.06,pomSteps:16,rimIntensity:.6,rimColor:"#ffffff",rimWidth:.025,refractionStrength:.015,chromaticStrength:.008,occlusionIntensity:.4,depthPower:.7,depthScale:1.2,depthBias:-.05,fogDensity:.15,fogColor:"#1a1a2e",colorShift:.6,brightnessBias:.05,contrastLow:.02,contrastHigh:.98,verticalReduction:.5,dofStart:.5,dofStrength:.5,bevelIntensity:.5,bevelWidth:.04,bevelDarkening:.2,bevelDesaturation:.12,bevelLightAngle:135,edgeThickness:.01,edgeSpecular:.35,edgeColor:"#a0a0a0",chamferWidth:.025,chamferAngle:45,chamferColor:"#262630",chamferAmbient:.12,chamferSpecular:.3,chamferShininess:24,edgeOcclusionWidth:.03,edgeOcclusionStrength:.2,lightDirection:"-0.5,0.7,-0.3",autoplay:!0,loop:!0,muted:!0};class pt{constructor(t,e=.08,i=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const t=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,e=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=lt(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=lt(this.smoothedOutput.y,t.y,e),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=t=>{const e=this.host.getBoundingClientRect(),i=(t.clientX-e.left)/e.width*2-1,o=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=W(i,-1,1),this.pointerTarget.y=W(o,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=t=>{const e=t.touches[0];e&&(this.touchActive=!0,this.touchAnchorX=e.clientX,this.touchAnchorY=e.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=t=>{const e=t.touches[0];if(!e)return;const i=e.clientX-this.touchAnchorX,o=e.clientY-this.touchAnchorY,n=pt.TOUCH_DRAG_RANGE;this.pointerTarget.x=W(i/n,-1,1),this.pointerTarget.y=W(o/n,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const t=DeviceOrientationEvent;if(typeof t.requestPermission=="function")try{if(await t.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=t=>{const e=W((t.gamma??0)/45,-1,1),i=W((t.beta??0)/45,-1,1);this.motionTarget.x=lt(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=lt(this.motionTarget.y,i,this.motionLerpFactor)}}class ht extends HTMLElement{static TAG_NAME="layershift-portal";static get observedAttributes(){return["src","depth-src","depth-meta","logo-src","parallax-x","parallax-y","parallax-max","overscan","pom-steps","rim-intensity","rim-color","rim-width","refraction-strength","chromatic-strength","occlusion-intensity","depth-power","depth-scale","depth-bias","fog-density","fog-color","color-shift","brightness-bias","contrast-low","contrast-high","vertical-reduction","dof-start","dof-strength","bevel-intensity","bevel-width","bevel-darkening","bevel-desaturation","bevel-light-angle","edge-thickness","edge-specular","edge-color","chamfer-width","chamfer-angle","chamfer-color","chamfer-ambient","chamfer-specular","chamfer-shininess","edge-occlusion-width","edge-occlusion-strength","light-direction","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta","logo-src"];shadow;container=null;renderer=null;inputHandler=null;depthWorker=null;video=null;mesh=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Et(this)}getAttrFloat(t,e){const i=this.getAttribute(t);if(i===null)return e;const o=parseFloat(i);return Number.isFinite(o)?o:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return!(i==="false"||i==="0")}getAttrColor(t,e){const i=this.getAttribute(t)??e;return Ne(i)}getAttrVec3(t,e){const o=(this.getAttribute(t)??e).split(",").map(s=>parseFloat(s.trim()));if(o.length>=3&&o.every(Number.isFinite))return[o[0],o[1],o[2]];const n=e.split(",").map(s=>parseFloat(s.trim()));return[n[0],n[1],n[2]]}get parallaxX(){return this.getAttrFloat("parallax-x",b.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",b.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",b.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",b.overscan)}get pomSteps(){return this.getAttrFloat("pom-steps",b.pomSteps)}get rimIntensity(){return this.getAttrFloat("rim-intensity",b.rimIntensity)}get rimWidth(){return this.getAttrFloat("rim-width",b.rimWidth)}get rimColor(){return this.getAttrColor("rim-color",b.rimColor)}get refractionStrength(){return this.getAttrFloat("refraction-strength",b.refractionStrength)}get chromaticStrength(){return this.getAttrFloat("chromatic-strength",b.chromaticStrength)}get occlusionIntensity(){return this.getAttrFloat("occlusion-intensity",b.occlusionIntensity)}get depthPower(){return this.getAttrFloat("depth-power",b.depthPower)}get depthScale(){return this.getAttrFloat("depth-scale",b.depthScale)}get depthBias(){return this.getAttrFloat("depth-bias",b.depthBias)}get fogDensity(){return this.getAttrFloat("fog-density",b.fogDensity)}get fogColor(){return this.getAttrColor("fog-color",b.fogColor)}get colorShift(){return this.getAttrFloat("color-shift",b.colorShift)}get brightnessBias(){return this.getAttrFloat("brightness-bias",b.brightnessBias)}get contrastLow(){return this.getAttrFloat("contrast-low",b.contrastLow)}get contrastHigh(){return this.getAttrFloat("contrast-high",b.contrastHigh)}get verticalReduction(){return this.getAttrFloat("vertical-reduction",b.verticalReduction)}get dofStart(){return this.getAttrFloat("dof-start",b.dofStart)}get dofStrength(){return this.getAttrFloat("dof-strength",b.dofStrength)}get bevelIntensity(){return this.getAttrFloat("bevel-intensity",b.bevelIntensity)}get bevelWidth(){return this.getAttrFloat("bevel-width",b.bevelWidth)}get bevelDarkening(){return this.getAttrFloat("bevel-darkening",b.bevelDarkening)}get bevelDesaturation(){return this.getAttrFloat("bevel-desaturation",b.bevelDesaturation)}get bevelLightAngle(){return this.getAttrFloat("bevel-light-angle",b.bevelLightAngle)}get edgeThickness(){return this.getAttrFloat("edge-thickness",b.edgeThickness)}get edgeSpecular(){return this.getAttrFloat("edge-specular",b.edgeSpecular)}get edgeColor(){return this.getAttrColor("edge-color",b.edgeColor)}get chamferWidth(){return this.getAttrFloat("chamfer-width",b.chamferWidth)}get chamferAngle(){return this.getAttrFloat("chamfer-angle",b.chamferAngle)}get chamferColor(){return this.getAttrColor("chamfer-color",b.chamferColor)}get chamferAmbient(){return this.getAttrFloat("chamfer-ambient",b.chamferAmbient)}get chamferSpecular(){return this.getAttrFloat("chamfer-specular",b.chamferSpecular)}get chamferShininess(){return this.getAttrFloat("chamfer-shininess",b.chamferShininess)}get edgeOcclusionWidth(){return this.getAttrFloat("edge-occlusion-width",b.edgeOcclusionWidth)}get edgeOcclusionStrength(){return this.getAttrFloat("edge-occlusion-strength",b.edgeOcclusionStrength)}get lightDirection3(){return this.getAttrVec3("light-direction",b.lightDirection)}get shouldAutoplay(){return this.getAttrBool("autoplay",b.autoplay)}get shouldLoop(){return this.getAttrBool("loop",b.loop)}get shouldMute(){return this.getAttrBool("muted",b.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachVideoEventListeners(t){t.addEventListener("play",()=>{this.emit("layershift-portal:play",{currentTime:t.currentTime})}),t.addEventListener("pause",()=>{this.emit("layershift-portal:pause",{currentTime:t.currentTime})}),t.addEventListener("ended",()=>{t.loop&&(this.loopCount+=1,this.emit("layershift-portal:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,i){this.lifecycle.onAttributeChanged(t,e,i)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
1841
+
1842
+ // Base color: video tinted through chamfer color (like frosted glass).
1843
+ let video = toLinear(videoSample);
1844
+ let tint = toLinear(uniforms.chamferColor);
1845
+ // Blend: mostly video near inner edge, more tinted at outer edge.
1846
+ let base = mix(video, video * tint * 3.0, in.lerpT * 0.5);
1847
+
1848
+ // Apply Blinn-Phong.
1849
+ let lit = base * (uniforms.chamferAmbient + (1.0 - uniforms.chamferAmbient) * diff) + vec3f(spec);
1850
+ return vec4f(toSRGB(lit), 1.0);
1851
+ }
1852
+ `,gi=64,vi=16,xi=16,Ie=96,bi=16,Oe=112,ke=64,Si=16,Ti=16,yi=16;class Ei extends k{device;context=null;canvasFormat;config;quadBuffer=null;linearSampler=null;nearestSampler=null;interiorPipeline=null;interiorBindGroupLayout=null;stencilMarkPipeline=null;stencilMarkBindGroupLayout=null;compositePipeline=null;compositeBindGroupLayout=null;chamferPipeline=null;chamferBindGroupLayout=null;boundaryPipeline=null;boundaryBindGroupLayout=null;maskPipeline=null;maskBindGroupLayout=null;jfaSeedPipeline=null;jfaSeedBindGroupLayout=null;jfaFloodPipeline=null;jfaFloodBindGroupLayout=null;jfaDistPipeline=null;jfaDistBindGroupLayout=null;meshScaleUniformBuffer=null;interiorVertexUniformBuffer=null;interiorFragmentUniformBuffer=null;compositeUniformBuffer=null;boundaryUniformBuffer=null;chamferUniformBuffer=null;jfaSeedUniformBuffer=null;jfaFloodUniformBuffer=null;jfaDistUniformBuffer=null;interiorBindGroup=null;stencilMarkBindGroup=null;compositeBindGroup=null;chamferBindGroup=null;boundaryBindGroup=null;maskBindGroup=null;jfaSeedBindGroup=null;jfaDistBindGroup=null;videoTexture=null;videoTextureView=null;depthTexture=null;depthTextureView=null;interiorColorTexture=null;interiorColorView=null;interiorDepthTexture=null;interiorDepthView=null;fboWidth=0;fboHeight=0;depthStencilTexture=null;depthStencilView=null;dsWidth=0;dsHeight=0;jfaMaskTexture=null;jfaMaskView=null;jfaPingTexture=null;jfaPingView=null;jfaPongTexture=null;jfaPongView=null;jfaDistTexture=null;jfaDistView=null;jfaWidth=0;jfaHeight=0;jfaDirty=!0;stencilVertexBuffer=null;stencilIndexBuffer=null;stencilIndexCount=0;boundaryVertexBuffer=null;boundaryVertexCount=0;chamferVertexBuffer=null;chamferVertexCount=0;meshAspect=1;meshScaleX=.65;meshScaleY=.65;lightDirX=-.707;lightDirY=.707;lightDir3=[-.5,.7,-.3];depthFlipBuffer=null;offsetData=new Float32Array(2);constructor(e,t,i,n){super(e),this.device=i,this.config={...t},this.qualityParams=De(n,t.quality);const r=this.config.bevelLightAngle*Math.PI/180;this.lightDirX=Math.cos(r),this.lightDirY=Math.sin(r);const s=this.config.lightDirection,a=Math.sqrt(s[0]*s[0]+s[1]*s[1]+s[2]*s[2]);a>1e-6&&(this.lightDir3=[s[0]/a,s[1]/a,s[2]/a]),this.context=this.canvas.getContext("webgpu"),this.canvasFormat=navigator.gpu.getPreferredCanvasFormat(),this.context.configure({device:i,format:this.canvasFormat,alphaMode:"premultiplied"}),this.quadBuffer=Re(i),this.linearSampler=Fe(i),this.nearestSampler=wt(i),this.createInteriorPipeline(),this.createStencilMarkPipeline(),this.createCompositePipeline(),this.createChamferPipeline(),this.createBoundaryPipeline(),this.createMaskPipeline(),this.createJFASeedPipeline(),this.createJFAFloodPipeline(),this.createJFADistPipeline(),this.meshScaleUniformBuffer=B(i,vi),this.interiorVertexUniformBuffer=B(i,xi),this.interiorFragmentUniformBuffer=B(i,Ie),this.compositeUniformBuffer=B(i,bi),this.boundaryUniformBuffer=B(i,Oe),this.chamferUniformBuffer=B(i,ke),this.jfaSeedUniformBuffer=B(i,Si),this.jfaFloodUniformBuffer=B(i,Ti),this.jfaDistUniformBuffer=B(i,yi),i.lost.then(l=>{console.error(`WebGPU device lost (${l.reason}): ${l.message}`),this.stop()}),this.setupResizeHandling()}initialize(e,t,i,n){this.disposeTextures(),this.disposeGeometryBuffers(),this.videoAspect=e.videoWidth/e.videoHeight,this.meshAspect=n.aspect,this.clampDepthDimensions(t,i,this.qualityParams.depthMaxDim),this.videoTexture=this.device.createTexture({size:[e.videoWidth,e.videoHeight],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT}),this.videoTextureView=this.videoTexture.createView(),this.depthTexture=this.device.createTexture({size:[this.depthWidth,this.depthHeight],format:"r8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST}),this.depthTextureView=this.depthTexture.createView(),this.depthFlipBuffer=new Uint8Array(this.depthWidth*this.depthHeight),this.uploadStencilMesh(n),this.uploadBoundaryMesh(n),this.uploadChamferMesh(n),this.writeStaticInteriorUniforms(e.videoWidth,e.videoHeight),this.writeStaticCompositeUniforms(),this.writeStaticChamferUniforms(),this.writeStaticBoundaryUniforms(),this.recalculateViewportLayout()}createInteriorPipeline(){const e=this.device;this.interiorBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:3,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:4,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:5,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:fi});this.interiorPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.interiorBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[N]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:"rgba8unorm"},{format:"r8unorm"}],constants:{MAX_POM_STEPS:gi}},primitive:{topology:"triangle-strip"}})}createStencilMarkPipeline(){const e=this.device;this.stencilMarkBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX,buffer:{type:"uniform"}}]});const t=e.createShaderModule({code:ai});this.stencilMarkPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.stencilMarkBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[Be]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:this.canvasFormat,writeMask:0}]},primitive:{topology:"triangle-list"},depthStencil:{format:"depth24plus-stencil8",depthWriteEnabled:!1,depthCompare:"always",stencilFront:{compare:"always",passOp:"replace",failOp:"keep",depthFailOp:"keep"},stencilBack:{compare:"always",passOp:"replace",failOp:"keep",depthFailOp:"keep"},stencilReadMask:255,stencilWriteMask:255}})}createCompositePipeline(){const e=this.device;this.compositeBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:3,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:4,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:di});this.compositePipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.compositeBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[N]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:this.canvasFormat}]},primitive:{topology:"triangle-strip"},depthStencil:{format:"depth24plus-stencil8",depthWriteEnabled:!1,depthCompare:"always",stencilFront:{compare:"equal",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilBack:{compare:"equal",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilReadMask:255,stencilWriteMask:0}})}createChamferPipeline(){const e=this.device;this.chamferBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:mi});this.chamferPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.chamferBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[Ut]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:this.canvasFormat}]},primitive:{topology:"triangle-list"},depthStencil:{format:"depth24plus-stencil8",depthWriteEnabled:!1,depthCompare:"always",stencilFront:{compare:"always",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilBack:{compare:"always",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilReadMask:0,stencilWriteMask:0}})}createBoundaryPipeline(){const e=this.device;this.boundaryBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:3,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:4,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:5,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:6,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:pi});this.boundaryPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.boundaryBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[Dt]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:this.canvasFormat,blend:{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha",operation:"add"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha",operation:"add"}}}]},primitive:{topology:"triangle-list"},depthStencil:{format:"depth24plus-stencil8",depthWriteEnabled:!1,depthCompare:"always",stencilFront:{compare:"always",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilBack:{compare:"always",passOp:"keep",failOp:"keep",depthFailOp:"keep"},stencilReadMask:0,stencilWriteMask:0}})}createMaskPipeline(){const e=this.device;this.maskBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX,buffer:{type:"uniform"}}]});const t=e.createShaderModule({code:li});this.maskPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.maskBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[Be]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:"r8unorm"}]},primitive:{topology:"triangle-list"}})}createJFASeedPipeline(){const e=this.device;this.jfaSeedBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:2,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}}]});const t=e.createShaderModule({code:ui});this.jfaSeedPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.jfaSeedBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[N]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:"rg16float"}]},primitive:{topology:"triangle-strip"}})}createJFAFloodPipeline(){const e=this.device;this.jfaFloodBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:hi});this.jfaFloodPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.jfaFloodBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[N]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:"rg16float"}]},primitive:{topology:"triangle-strip"}})}createJFADistPipeline(){const e=this.device;this.jfaDistBindGroupLayout=e.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}},{binding:3,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:4,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});const t=e.createShaderModule({code:ci});this.jfaDistPipeline=e.createRenderPipeline({layout:e.createPipelineLayout({bindGroupLayouts:[this.jfaDistBindGroupLayout]}),vertex:{module:t,entryPoint:"vs_main",buffers:[N]},fragment:{module:t,entryPoint:"fs_main",targets:[{format:"rgba8unorm"}]},primitive:{topology:"triangle-strip"}})}uploadStencilMesh(e){this.stencilVertexBuffer=me(this.device,e.vertices),this.stencilIndexBuffer=At(this.device,e.indices),this.stencilIndexCount=e.indices.length}uploadBoundaryMesh(e){const t=Ve(e.edgeVertices);t.count!==0&&(this.boundaryVertexBuffer=me(this.device,t.vertices),this.boundaryVertexCount=t.count)}uploadChamferMesh(e){if(this.config.chamferWidth<=0)return;const t=Ge(e.edgeVertices,e.contourOffsets,e.contourIsHole,this.config.chamferWidth,this.config.chamferAngle);t.count!==0&&(this.chamferVertexBuffer=me(this.device,t.vertices),this.chamferVertexCount=t.count)}writeStaticInteriorUniforms(e,t){const i=new ArrayBuffer(Ie),n=new Float32Array(i),r=new Int32Array(i);n[0]=0,n[1]=0,n[2]=this.config.parallaxStrength,r[3]=this.config.pomSteps,n[4]=this.config.depthPower,n[5]=this.config.depthScale,n[6]=this.config.depthBias,n[7]=this.config.contrastLow,n[8]=this.config.contrastHigh,n[9]=this.config.verticalReduction,n[10]=this.config.dofStart,n[11]=this.config.dofStrength,n[12]=1/e,n[13]=1/t,n[14]=this.config.fogDensity,n[16]=this.config.fogColor[0],n[17]=this.config.fogColor[1],n[18]=this.config.fogColor[2],n[19]=this.config.colorShift,n[20]=this.config.brightnessBias,this.device.queue.writeBuffer(this.interiorFragmentUniformBuffer,0,i)}writeStaticCompositeUniforms(){this.device.queue.writeBuffer(this.compositeUniformBuffer,0,new Float32Array([this.config.edgeOcclusionWidth,this.config.edgeOcclusionStrength,0,0]))}writeStaticChamferUniforms(){const e=new ArrayBuffer(ke),t=new Float32Array(e);t[0]=this.lightDir3[0],t[1]=this.lightDir3[1],t[2]=this.lightDir3[2],t[4]=this.config.chamferColor[0],t[5]=this.config.chamferColor[1],t[6]=this.config.chamferColor[2],t[7]=this.config.chamferAmbient,t[8]=this.config.chamferSpecular,t[9]=this.config.chamferShininess,t[10]=this.meshScaleX,t[11]=this.meshScaleY,t[12]=0,t[13]=0,this.device.queue.writeBuffer(this.chamferUniformBuffer,0,e)}writeStaticBoundaryUniforms(){const e=new ArrayBuffer(Oe),t=new Float32Array(e);t[0]=this.config.rimLightWidth,t[2]=this.meshScaleX,t[3]=this.meshScaleY,t[4]=this.config.rimLightIntensity,t[8]=this.config.rimLightColor[0],t[9]=this.config.rimLightColor[1],t[10]=this.config.rimLightColor[2],t[11]=this.config.refractionStrength,t[12]=this.config.chromaticStrength,t[13]=this.config.occlusionIntensity,t[14]=0,t[15]=0,t[16]=this.config.edgeThickness,t[17]=this.config.edgeSpecular,t[20]=this.config.edgeColor[0],t[21]=this.config.edgeColor[1],t[22]=this.config.edgeColor[2],t[24]=this.lightDirX,t[25]=this.lightDirY,t[26]=this.config.bevelIntensity,this.device.queue.writeBuffer(this.boundaryUniformBuffer,0,e)}rebuildInteriorBindGroup(){!this.interiorBindGroupLayout||!this.interiorVertexUniformBuffer||!this.interiorFragmentUniformBuffer||!this.videoTextureView||!this.depthTextureView||!this.linearSampler||(this.interiorBindGroup=this.device.createBindGroup({layout:this.interiorBindGroupLayout,entries:[{binding:0,resource:{buffer:this.interiorVertexUniformBuffer}},{binding:1,resource:{buffer:this.interiorFragmentUniformBuffer}},{binding:2,resource:this.videoTextureView},{binding:3,resource:this.linearSampler},{binding:4,resource:this.depthTextureView},{binding:5,resource:this.linearSampler}]}))}rebuildStencilMarkBindGroup(){!this.stencilMarkBindGroupLayout||!this.meshScaleUniformBuffer||(this.stencilMarkBindGroup=this.device.createBindGroup({layout:this.stencilMarkBindGroupLayout,entries:[{binding:0,resource:{buffer:this.meshScaleUniformBuffer}}]}))}rebuildCompositeBindGroup(){if(!this.compositeBindGroupLayout||!this.compositeUniformBuffer||!this.interiorColorView||!this.linearSampler)return;const e=this.jfaDistView??this.createFallbackTextureView("rgba8unorm");this.compositeBindGroup=this.device.createBindGroup({layout:this.compositeBindGroupLayout,entries:[{binding:0,resource:{buffer:this.compositeUniformBuffer}},{binding:1,resource:this.interiorColorView},{binding:2,resource:this.linearSampler},{binding:3,resource:e},{binding:4,resource:this.linearSampler}]})}rebuildChamferBindGroup(){!this.chamferBindGroupLayout||!this.chamferUniformBuffer||!this.interiorColorView||!this.linearSampler||(this.chamferBindGroup=this.device.createBindGroup({layout:this.chamferBindGroupLayout,entries:[{binding:0,resource:{buffer:this.chamferUniformBuffer}},{binding:1,resource:this.interiorColorView},{binding:2,resource:this.linearSampler}]}))}rebuildBoundaryBindGroup(){if(!this.boundaryBindGroupLayout||!this.boundaryUniformBuffer||!this.interiorColorView||!this.interiorDepthView||!this.linearSampler)return;const e=this.jfaDistView??this.createFallbackTextureView("rgba8unorm");this.boundaryBindGroup=this.device.createBindGroup({layout:this.boundaryBindGroupLayout,entries:[{binding:0,resource:{buffer:this.boundaryUniformBuffer}},{binding:1,resource:this.interiorColorView},{binding:2,resource:this.linearSampler},{binding:3,resource:this.interiorDepthView},{binding:4,resource:this.linearSampler},{binding:5,resource:e},{binding:6,resource:this.linearSampler}]})}rebuildMaskBindGroup(){!this.maskBindGroupLayout||!this.meshScaleUniformBuffer||(this.maskBindGroup=this.device.createBindGroup({layout:this.maskBindGroupLayout,entries:[{binding:0,resource:{buffer:this.meshScaleUniformBuffer}}]}))}rebuildJFASeedBindGroup(){!this.jfaSeedBindGroupLayout||!this.jfaMaskView||!this.linearSampler||!this.jfaSeedUniformBuffer||(this.jfaSeedBindGroup=this.device.createBindGroup({layout:this.jfaSeedBindGroupLayout,entries:[{binding:0,resource:this.jfaMaskView},{binding:1,resource:this.linearSampler},{binding:2,resource:{buffer:this.jfaSeedUniformBuffer}}]}))}rebuildJFADistBindGroup(){if(!this.jfaDistBindGroupLayout||!this.jfaDistUniformBuffer||!this.jfaMaskView||!this.linearSampler)return;const e=this.jfaPingView??this.jfaPongView;e&&(this.jfaDistBindGroup=this.device.createBindGroup({layout:this.jfaDistBindGroupLayout,entries:[{binding:0,resource:{buffer:this.jfaDistUniformBuffer}},{binding:1,resource:e},{binding:2,resource:this.nearestSampler},{binding:3,resource:this.jfaMaskView},{binding:4,resource:this.linearSampler}]}))}rebuildBindGroups(){this.rebuildInteriorBindGroup(),this.rebuildStencilMarkBindGroup(),this.rebuildCompositeBindGroup(),this.rebuildChamferBindGroup(),this.rebuildBoundaryBindGroup(),this.rebuildMaskBindGroup(),this.rebuildJFASeedBindGroup(),this.rebuildJFADistBindGroup()}createInteriorFBO(e,t){this.disposeInteriorFBO(),this.fboWidth=e,this.fboHeight=t,this.interiorColorTexture=this.device.createTexture({size:[e,t],format:"rgba8unorm",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.interiorColorView=this.interiorColorTexture.createView(),this.interiorDepthTexture=this.device.createTexture({size:[e,t],format:"r8unorm",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.interiorDepthView=this.interiorDepthTexture.createView()}createDepthStencilTexture(e,t){this.disposeDepthStencilTexture(),this.dsWidth=e,this.dsHeight=t,this.depthStencilTexture=this.device.createTexture({size:[e,t],format:"depth24plus-stencil8",usage:GPUTextureUsage.RENDER_ATTACHMENT}),this.depthStencilView=this.depthStencilTexture.createView()}createJFAResources(e,t){this.disposeJFAResources();const i=this.qualityParams.jfaDivisor,n=Math.max(1,Math.round(e/i)),r=Math.max(1,Math.round(t/i));this.jfaWidth=n,this.jfaHeight=r,this.jfaMaskTexture=this.device.createTexture({size:[n,r],format:"r8unorm",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.jfaMaskView=this.jfaMaskTexture.createView(),this.jfaPingTexture=this.device.createTexture({size:[n,r],format:"rg16float",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.jfaPingView=this.jfaPingTexture.createView(),this.jfaPongTexture=this.device.createTexture({size:[n,r],format:"rg16float",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.jfaPongView=this.jfaPongTexture.createView(),this.jfaDistTexture=this.device.createTexture({size:[n,r],format:"rgba8unorm",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),this.jfaDistView=this.jfaDistTexture.createView(),this.jfaDirty=!0}computeDistanceField(e){if(!this.maskPipeline||!this.jfaSeedPipeline||!this.jfaFloodPipeline||!this.jfaDistPipeline||!this.stencilVertexBuffer||!this.stencilIndexBuffer||!this.quadBuffer||!this.jfaMaskView||!this.jfaPingView||!this.jfaPongView||!this.jfaDistView||!this.maskBindGroup||!this.jfaSeedBindGroup||!this.jfaFloodBindGroupLayout||!this.jfaDistBindGroupLayout||!this.nearestSampler||!this.linearSampler||!this.jfaMaskView)return;const t=this.jfaWidth,i=this.jfaHeight;if(t===0||i===0)return;{const u=e.beginRenderPass({colorAttachments:[{view:this.jfaMaskView,clearValue:{r:0,g:0,b:0,a:1},loadOp:"clear",storeOp:"store"}]});u.setPipeline(this.maskPipeline),u.setBindGroup(0,this.maskBindGroup),u.setVertexBuffer(0,this.stencilVertexBuffer),u.setIndexBuffer(this.stencilIndexBuffer,"uint16"),u.drawIndexed(this.stencilIndexCount),u.end()}this.device.queue.writeBuffer(this.jfaSeedUniformBuffer,0,new Float32Array([1/t,1/i,0,0])),this.rebuildJFASeedBindGroup();{const u=e.beginRenderPass({colorAttachments:[{view:this.jfaPingView,clearValue:{r:-1,g:-1,b:0,a:0},loadOp:"clear",storeOp:"store"}]});u.setPipeline(this.jfaSeedPipeline),u.setBindGroup(0,this.jfaSeedBindGroup),u.setVertexBuffer(0,this.quadBuffer),u.draw(4),u.end()}const n=se.computeFloodIterations(t,i);let r=this.jfaPingView,s=this.jfaPongView;for(let u=0;u<n.length;u++){const h=n[u]/Math.max(t,i);this.device.queue.writeBuffer(this.jfaFloodUniformBuffer,0,new Float32Array([1/t,1/i,h,0]));const f=this.device.createBindGroup({layout:this.jfaFloodBindGroupLayout,entries:[{binding:0,resource:{buffer:this.jfaFloodUniformBuffer}},{binding:1,resource:r},{binding:2,resource:this.nearestSampler}]}),c=e.beginRenderPass({colorAttachments:[{view:s,loadOp:"clear",clearValue:{r:-1,g:-1,b:0,a:0},storeOp:"store"}]});c.setPipeline(this.jfaFloodPipeline),c.setBindGroup(0,f),c.setVertexBuffer(0,this.quadBuffer),c.draw(4),c.end();const g=r;r=s,s=g}const a=Math.max(this.config.bevelWidth,this.config.edgeOcclusionWidth);this.device.queue.writeBuffer(this.jfaDistUniformBuffer,0,new Float32Array([1/t,1/i,a,0]));const l=this.device.createBindGroup({layout:this.jfaDistBindGroupLayout,entries:[{binding:0,resource:{buffer:this.jfaDistUniformBuffer}},{binding:1,resource:r},{binding:2,resource:this.nearestSampler},{binding:3,resource:this.jfaMaskView},{binding:4,resource:this.linearSampler}]});{const u=e.beginRenderPass({colorAttachments:[{view:this.jfaDistView,clearValue:{r:0,g:0,b:0,a:1},loadOp:"clear",storeOp:"store"}]});u.setPipeline(this.jfaDistPipeline),u.setBindGroup(0,l),u.setVertexBuffer(0,this.quadBuffer),u.draw(4),u.end()}this.jfaDirty=!1,this.rebuildCompositeBindGroup(),this.rebuildBoundaryBindGroup()}flipDepthY(e){const t=this.depthFlipBuffer,i=this.depthWidth,n=this.depthHeight;for(let r=0;r<n;r++){const s=r*i,a=(n-1-r)*i;t.set(e.subarray(s,s+i),a)}return t}fallbackTexture=null;createFallbackTextureView(e){return this.fallbackTexture||(this.fallbackTexture=this.device.createTexture({size:[1,1],format:e,usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST}),this.device.queue.writeTexture({texture:this.fallbackTexture},new Uint8Array([0,0,0,0]),{bytesPerRow:4},{width:1,height:1})),this.fallbackTexture.createView()}onRenderFrame(){const e=this.playbackVideo;if(!this.context||!this.interiorPipeline||!this.interiorBindGroup||!this.quadBuffer||!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA||!this.interiorColorView||!this.interiorDepthView)return;if(this.videoTexture&&Le(this.device,this.videoTexture,e),this.rvfcSupported||this.onDepthUpdate(e.currentTime),this.readInput){const n=this.readInput();this.offsetData[0]=-n.x,this.offsetData[1]=n.y,this.device.queue.writeBuffer(this.interiorFragmentUniformBuffer,0,this.offsetData)}const t=this.device.createCommandEncoder();this.jfaDirty&&this.stencilVertexBuffer&&this.stencilIndexBuffer&&this.computeDistanceField(t);{const n=t.beginRenderPass({colorAttachments:[{view:this.interiorColorView,clearValue:{r:0,g:0,b:0,a:1},loadOp:"clear",storeOp:"store"},{view:this.interiorDepthView,clearValue:{r:0,g:0,b:0,a:1},loadOp:"clear",storeOp:"store"}]});n.setPipeline(this.interiorPipeline),n.setBindGroup(0,this.interiorBindGroup),n.setVertexBuffer(0,this.quadBuffer),n.draw(4),n.end()}const i=this.context.getCurrentTexture().createView();if(this.stencilMarkPipeline&&this.stencilMarkBindGroup&&this.stencilVertexBuffer&&this.stencilIndexBuffer&&this.stencilIndexCount>0&&this.depthStencilView){const n=t.beginRenderPass({colorAttachments:[{view:i,clearValue:{r:0,g:0,b:0,a:0},loadOp:"clear",storeOp:"store"}],depthStencilAttachment:{view:this.depthStencilView,stencilClearValue:0,stencilLoadOp:"clear",stencilStoreOp:"store",depthClearValue:1,depthLoadOp:"clear",depthStoreOp:"discard"}});n.setStencilReference(1),n.setPipeline(this.stencilMarkPipeline),n.setBindGroup(0,this.stencilMarkBindGroup),n.setVertexBuffer(0,this.stencilVertexBuffer),n.setIndexBuffer(this.stencilIndexBuffer,"uint16"),n.drawIndexed(this.stencilIndexCount),n.end()}if(this.compositePipeline&&this.compositeBindGroup&&this.depthStencilView){const n=t.beginRenderPass({colorAttachments:[{view:i,loadOp:"load",storeOp:"store"}],depthStencilAttachment:{view:this.depthStencilView,stencilLoadOp:"load",stencilStoreOp:"store",depthLoadOp:"load",depthStoreOp:"discard"}});n.setStencilReference(1),n.setPipeline(this.compositePipeline),n.setBindGroup(0,this.compositeBindGroup),n.setVertexBuffer(0,this.quadBuffer),n.draw(4),n.end()}if(this.chamferPipeline&&this.chamferBindGroup&&this.chamferVertexBuffer&&this.chamferVertexCount>0&&this.depthStencilView){const n=t.beginRenderPass({colorAttachments:[{view:i,loadOp:"load",storeOp:"store"}],depthStencilAttachment:{view:this.depthStencilView,stencilLoadOp:"load",stencilStoreOp:"store",depthLoadOp:"load",depthStoreOp:"discard"}});n.setPipeline(this.chamferPipeline),n.setBindGroup(0,this.chamferBindGroup),n.setVertexBuffer(0,this.chamferVertexBuffer),n.draw(this.chamferVertexCount),n.end()}if(this.boundaryPipeline&&this.boundaryBindGroup&&this.boundaryVertexBuffer&&this.boundaryVertexCount>0&&this.config.rimLightIntensity>0&&this.depthStencilView){const n=t.beginRenderPass({colorAttachments:[{view:i,loadOp:"load",storeOp:"store"}],depthStencilAttachment:{view:this.depthStencilView,stencilLoadOp:"load",stencilStoreOp:"store",depthLoadOp:"load",depthStoreOp:"discard"}});n.setPipeline(this.boundaryPipeline),n.setBindGroup(0,this.boundaryBindGroup),n.setVertexBuffer(0,this.boundaryVertexBuffer),n.draw(this.boundaryVertexCount),n.end()}this.device.queue.submit([t.finish()])}onDepthUpdate(e){if(!this.readDepth||!this.depthTexture)return;const t=this.subsampleDepth(this.readDepth(e)),i=this.flipDepthY(t);this.device.queue.writeTexture({texture:this.depthTexture},i,{bytesPerRow:this.depthWidth},{width:this.depthWidth,height:this.depthHeight})}recalculateViewportLayout(){if(!this.context)return;const{width:e,height:t}=this.getViewportSize(),i=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),n=Math.round(e*i),r=Math.round(t*i);(this.canvas.width!==n||this.canvas.height!==r)&&(this.canvas.width=n,this.canvas.height=r),(this.fboWidth!==n||this.fboHeight!==r)&&this.createInteriorFBO(n,r),(this.dsWidth!==n||this.dsHeight!==r)&&this.createDepthStencilTexture(n,r);const s=this.qualityParams.jfaDivisor,a=Math.max(1,Math.round(n/s)),l=Math.max(1,Math.round(r/s));(this.jfaWidth!==a||this.jfaHeight!==l)&&this.createJFAResources(n,r),this.computeCoverFitUV(this.config.parallaxStrength,this.config.overscanPadding),this.interiorVertexUniformBuffer&&this.device.queue.writeBuffer(this.interiorVertexUniformBuffer,0,new Float32Array([this.uvOffset[0],this.uvOffset[1],this.uvScale[0],this.uvScale[1]]));const u=e/t,h=.65;this.meshScaleX=h,this.meshScaleY=h,u>this.meshAspect?this.meshScaleX=h*(this.meshAspect/u):this.meshScaleY=h*(u/this.meshAspect),this.meshScaleUniformBuffer&&this.device.queue.writeBuffer(this.meshScaleUniformBuffer,0,new Float32Array([this.meshScaleX,this.meshScaleY,0,0])),this.boundaryUniformBuffer&&(this.device.queue.writeBuffer(this.boundaryUniformBuffer,8,new Float32Array([this.meshScaleX,this.meshScaleY])),this.device.queue.writeBuffer(this.boundaryUniformBuffer,56,new Float32Array([1/n,1/r]))),this.chamferUniformBuffer&&(this.device.queue.writeBuffer(this.chamferUniformBuffer,40,new Float32Array([this.meshScaleX,this.meshScaleY])),this.device.queue.writeBuffer(this.chamferUniformBuffer,48,new Float32Array([1/n,1/r]))),this.jfaDirty=!0,this.rebuildBindGroups()}onContextRestored(){}disposeRenderer(){this.disposeTextures(),this.disposeInteriorFBO(),this.disposeDepthStencilTexture(),this.disposeJFAResources(),this.disposeGeometryBuffers(),this.meshScaleUniformBuffer?.destroy(),this.meshScaleUniformBuffer=null,this.interiorVertexUniformBuffer?.destroy(),this.interiorVertexUniformBuffer=null,this.interiorFragmentUniformBuffer?.destroy(),this.interiorFragmentUniformBuffer=null,this.compositeUniformBuffer?.destroy(),this.compositeUniformBuffer=null,this.boundaryUniformBuffer?.destroy(),this.boundaryUniformBuffer=null,this.chamferUniformBuffer?.destroy(),this.chamferUniformBuffer=null,this.jfaSeedUniformBuffer?.destroy(),this.jfaSeedUniformBuffer=null,this.jfaFloodUniformBuffer?.destroy(),this.jfaFloodUniformBuffer=null,this.jfaDistUniformBuffer?.destroy(),this.jfaDistUniformBuffer=null,this.quadBuffer?.destroy(),this.quadBuffer=null,this.linearSampler=null,this.nearestSampler=null,this.fallbackTexture?.destroy(),this.fallbackTexture=null,this.interiorPipeline=null,this.interiorBindGroupLayout=null,this.interiorBindGroup=null,this.stencilMarkPipeline=null,this.stencilMarkBindGroupLayout=null,this.stencilMarkBindGroup=null,this.compositePipeline=null,this.compositeBindGroupLayout=null,this.compositeBindGroup=null,this.chamferPipeline=null,this.chamferBindGroupLayout=null,this.chamferBindGroup=null,this.boundaryPipeline=null,this.boundaryBindGroupLayout=null,this.boundaryBindGroup=null,this.maskPipeline=null,this.maskBindGroupLayout=null,this.maskBindGroup=null,this.jfaSeedPipeline=null,this.jfaSeedBindGroupLayout=null,this.jfaSeedBindGroup=null,this.jfaFloodPipeline=null,this.jfaFloodBindGroupLayout=null,this.jfaDistPipeline=null,this.jfaDistBindGroupLayout=null,this.jfaDistBindGroup=null,this.context&&(this.context.unconfigure(),this.context=null),this.depthFlipBuffer=null}disposeTextures(){this.videoTexture?.destroy(),this.videoTexture=null,this.videoTextureView=null,this.depthTexture?.destroy(),this.depthTexture=null,this.depthTextureView=null,this.interiorBindGroup=null}disposeInteriorFBO(){this.interiorColorTexture?.destroy(),this.interiorColorTexture=null,this.interiorColorView=null,this.interiorDepthTexture?.destroy(),this.interiorDepthTexture=null,this.interiorDepthView=null,this.fboWidth=0,this.fboHeight=0,this.compositeBindGroup=null,this.chamferBindGroup=null,this.boundaryBindGroup=null}disposeDepthStencilTexture(){this.depthStencilTexture?.destroy(),this.depthStencilTexture=null,this.depthStencilView=null,this.dsWidth=0,this.dsHeight=0}disposeJFAResources(){this.jfaMaskTexture?.destroy(),this.jfaMaskTexture=null,this.jfaMaskView=null,this.jfaPingTexture?.destroy(),this.jfaPingTexture=null,this.jfaPingView=null,this.jfaPongTexture?.destroy(),this.jfaPongTexture=null,this.jfaPongView=null,this.jfaDistTexture?.destroy(),this.jfaDistTexture=null,this.jfaDistView=null,this.jfaWidth=0,this.jfaHeight=0,this.jfaDirty=!0,this.jfaSeedBindGroup=null,this.jfaDistBindGroup=null}disposeGeometryBuffers(){this.stencilVertexBuffer?.destroy(),this.stencilVertexBuffer=null,this.stencilIndexBuffer?.destroy(),this.stencilIndexBuffer=null,this.stencilIndexCount=0,this.boundaryVertexBuffer?.destroy(),this.boundaryVertexBuffer=null,this.boundaryVertexCount=0,this.chamferVertexBuffer?.destroy(),this.chamferVertexBuffer=null,this.chamferVertexCount=0,this.stencilMarkBindGroup=null,this.maskBindGroup=null}}async function Pi(o){const e=await fetch(o);if(!e.ok)throw new Error(`Failed to fetch SVG: ${e.status} ${e.statusText}`);const t=await e.text();return Ai(t)}function Ai(o){const i=new DOMParser().parseFromString(o,"image/svg+xml").querySelector("svg");if(!i)throw new Error("No <svg> element found in document.");const n=wi(i);if(n.length===0)throw new Error("No path data found in SVG.");let r=1/0,s=1/0,a=-1/0,l=-1/0;for(const D of n)for(let w=0;w<D.length;w+=2)r=Math.min(r,D[w]),a=Math.max(a,D[w]),s=Math.min(s,D[w+1]),l=Math.max(l,D[w+1]);const u=a-r,h=l-s,f=(r+a)/2,c=(s+l)/2,g=2/Math.max(u,h),v=u/h,d=n.map(D=>{const w=[];for(let L=0;L<D.length;L+=2)w.push((D[L]-f)*g),w.push(-((D[L+1]-c)*g));return w}),A=Ci(d),b=[],y=[];for(const D of A){const{flatCoords:w,holeIndices:L}=Mi(D),M=_i(w,L),O=b.length/2;for(const _ of M)y.push(_+O);for(const _ of w)b.push(_)}const p=b,m=y,S=[],x=[],E=[],P=We(d);for(let D=0;D<d.length;D++){const w=d[D];x.push(S.length),E.push(P[D]);for(let L=0;L<w.length;L++)S.push(w[L]);w.length>=2&&S.push(w[0],w[1])}let U=1/0,R=1/0,C=-1/0,I=-1/0;for(let D=0;D<p.length;D+=2)U=Math.min(U,p[D]),C=Math.max(C,p[D]),R=Math.min(R,p[D+1]),I=Math.max(I,p[D+1]);return{vertices:new Float32Array(p),indices:new Uint16Array(m),edgeVertices:new Float32Array(S),contourOffsets:x,contourIsHole:E,bounds:{minX:U,maxX:C,minY:R,maxY:I},aspect:v}}function wi(o){const e=[];return o.querySelectorAll("path").forEach(l=>{const u=l.getAttribute("d");if(!u)return;const h=Ri(u);e.push(...h)}),o.querySelectorAll("polygon").forEach(l=>{const u=l.getAttribute("points");if(!u)return;const h=Ne(u);h.length>=6&&e.push(h)}),o.querySelectorAll("polyline").forEach(l=>{const u=l.getAttribute("points");if(!u)return;const h=Ne(u);h.length>=6&&e.push(h)}),o.querySelectorAll("rect").forEach(l=>{const u=parseFloat(l.getAttribute("x")||"0"),h=parseFloat(l.getAttribute("y")||"0"),f=parseFloat(l.getAttribute("width")||"0"),c=parseFloat(l.getAttribute("height")||"0");f>0&&c>0&&e.push([u,h,u+f,h,u+f,h+c,u,h+c])}),o.querySelectorAll("circle").forEach(l=>{const u=parseFloat(l.getAttribute("cx")||"0"),h=parseFloat(l.getAttribute("cy")||"0"),f=parseFloat(l.getAttribute("r")||"0");f>0&&e.push(Di(u,h,f))}),o.querySelectorAll("ellipse").forEach(l=>{const u=parseFloat(l.getAttribute("cx")||"0"),h=parseFloat(l.getAttribute("cy")||"0"),f=parseFloat(l.getAttribute("rx")||"0"),c=parseFloat(l.getAttribute("ry")||"0");f>0&&c>0&&e.push(Ui(u,h,f,c))}),e}function Ne(o){const e=[],t=o.trim().split(/[\s,]+/);for(let i=0;i<t.length-1;i+=2){const n=parseFloat(t[i]),r=parseFloat(t[i+1]);Number.isFinite(n)&&Number.isFinite(r)&&e.push(n,r)}return e}function Di(o,e,t,i=64){const n=[];for(let r=0;r<i;r++){const s=2*Math.PI*r/i;n.push(o+t*Math.cos(s),e+t*Math.sin(s))}return n}function Ui(o,e,t,i,n=64){const r=[];for(let s=0;s<n;s++){const a=2*Math.PI*s/n;r.push(o+t*Math.cos(a),e+i*Math.sin(a))}return r}function Ri(o){const e=[];let t=[],i=0,n=0,r=0,s=0,a=0,l=0,u="";const h=Fi(o);let f=0;function c(){return f>=h.length?0:parseFloat(h[f++])}for(;f<h.length;){const g=h[f];let v;/^[a-zA-Z]$/.test(g)?(v=g,f++):v=u==="M"?"L":u==="m"?"l":u;const d=v===v.toLowerCase();switch(v.toUpperCase()){case"M":{t.length>0&&e.push(t),t=[];const b=c()+(d?i:0),y=c()+(d?n:0);i=b,n=y,r=b,s=y,t.push(i,n),a=i,l=n;break}case"L":{i=c()+(d?i:0),n=c()+(d?n:0),t.push(i,n),a=i,l=n;break}case"H":{i=c()+(d?i:0),t.push(i,n),a=i,l=n;break}case"V":{n=c()+(d?n:0),t.push(i,n),a=i,l=n;break}case"C":{const b=c()+(d?i:0),y=c()+(d?n:0),p=c()+(d?i:0),m=c()+(d?n:0),S=c()+(d?i:0),x=c()+(d?n:0);K(t,i,n,b,y,p,m,S,x),i=S,n=x,a=p,l=m;break}case"S":{const b=2*i-a,y=2*n-l,p=c()+(d?i:0),m=c()+(d?n:0),S=c()+(d?i:0),x=c()+(d?n:0);K(t,i,n,b,y,p,m,S,x),i=S,n=x,a=p,l=m;break}case"Q":{const b=c()+(d?i:0),y=c()+(d?n:0),p=c()+(d?i:0),m=c()+(d?n:0);Xe(t,i,n,b,y,p,m),i=p,n=m,a=b,l=y;break}case"T":{const b=2*i-a,y=2*n-l,p=c()+(d?i:0),m=c()+(d?n:0);Xe(t,i,n,b,y,p,m),i=p,n=m,a=b,l=y;break}case"A":{const b=c(),y=c(),p=c(),m=c(),S=c(),x=c()+(d?i:0),E=c()+(d?n:0);Bi(t,i,n,b,y,p,!!m,!!S,x,E),i=x,n=E,a=i,l=n;break}case"Z":{i=r,n=s,t.length>0&&e.push(t),t=[],a=i,l=n;break}default:f++;break}u=v}return t.length>=6&&e.push(t),e}function Fi(o){const e=[],t=/([a-zA-Z])|([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/g;let i;for(;(i=t.exec(o))!==null;)e.push(i[0]);return e}const Li=.5;function K(o,e,t,i,n,r,s,a,l,u=0){if(u>12){o.push(a,l);return}const h=a-e,f=l-t,c=Math.sqrt(h*h+f*f);if(c<1e-6){o.push(a,l);return}const g=Math.abs((i-a)*f-(n-l)*h)/c,v=Math.abs((r-a)*f-(s-l)*h)/c;if(g+v<Li){o.push(a,l);return}const d=(e+i)/2,A=(t+n)/2,b=(i+r)/2,y=(n+s)/2,p=(r+a)/2,m=(s+l)/2,S=(d+b)/2,x=(A+y)/2,E=(b+p)/2,P=(y+m)/2,U=(S+E)/2,R=(x+P)/2;K(o,e,t,d,A,S,x,U,R,u+1),K(o,U,R,E,P,p,m,a,l,u+1)}function Xe(o,e,t,i,n,r,s){const a=e+.6666666666666666*(i-e),l=t+2/3*(n-t),u=r+2/3*(i-r),h=s+2/3*(n-s);K(o,e,t,a,l,u,h,r,s)}function Bi(o,e,t,i,n,r,s,a,l,u){if(i===0||n===0){o.push(l,u);return}let h=Math.abs(i),f=Math.abs(n);const c=r*Math.PI/180,g=Math.cos(c),v=Math.sin(c),d=(e-l)/2,A=(t-u)/2,b=g*d+v*A,y=-v*d+g*A;let p=b*b/(h*h)+y*y/(f*f);if(p>1){const M=Math.sqrt(p);h*=M,f*=M,p=1}const m=h*h,S=f*f,x=b*b,E=y*y;let P=Math.max(0,(m*S-m*E-S*x)/(m*E+S*x));P=Math.sqrt(P),s===a&&(P=-P);const U=P*(h*y)/f,R=P*-(f*b)/h,C=g*U-v*R+(e+l)/2,I=v*U+g*R+(t+u)/2,D=He(1,0,(b-U)/h,(y-R)/f);let w=He((b-U)/h,(y-R)/f,(-b-U)/h,(-y-R)/f);!a&&w>0&&(w-=2*Math.PI),a&&w<0&&(w+=2*Math.PI);const L=Math.max(4,Math.ceil(Math.abs(w)/(Math.PI/16)));for(let M=1;M<=L;M++){const O=D+M/L*w,_=Math.cos(O),Y=Math.sin(O),ie=g*h*_-v*f*Y+C,fe=v*h*_+g*f*Y+I;o.push(ie,fe)}}function He(o,e,t,i){const n=o*i-e*t<0?-1:1,r=o*t+e*i,s=Math.sqrt(o*o+e*e),a=Math.sqrt(t*t+i*i),l=r/(s*a);return n*Math.acos(Math.max(-1,Math.min(1,l)))}function Mi(o){const e=[],t=[];for(let i=0;i<o.length;i++){i>0&&t.push(e.length/2);for(const n of o[i])e.push(n)}return{flatCoords:e,holeIndices:t}}function We(o){const e=o.length,t=o.map(n=>Math.abs(je(n))),i=new Array(e).fill(!1);for(let n=0;n<e;n++){let r=0;const s=o[n][0],a=o[n][1];for(let l=0;l<e;l++)n!==l&&t[l]>t[n]&&ze(s,a,o[l])&&r++;i[n]=r%2===1}return i}function Ci(o){if(o.length<=1)return[o];const e=We(o),t=o.map((s,a)=>{const l=je(s);return{index:a,contour:s,area:l,isOuter:!e[a]}}),i=t.filter(s=>s.isOuter),n=t.filter(s=>!s.isOuter);if(i.length===0)return o.map(s=>[s]);const r=i.map(s=>({outer:s.contour,holes:[]}));for(const s of n){const a=s.contour[0],l=s.contour[1];let u=-1,h=1/0;for(let f=0;f<i.length;f++)if(ze(a,l,i[f].contour)){const c=Math.abs(i[f].area);c<h&&(h=c,u=f)}u>=0?r[u].holes.push(s.contour):r.push({outer:s.contour,holes:[]})}return r.map(s=>[s.outer,...s.holes])}function je(o){let e=0;const t=o.length;for(let i=0;i<t;i+=2){const n=o[i],r=o[i+1],s=o[(i+2)%t],a=o[(i+3)%t];e+=n*a-s*r}return e/2}function ze(o,e,t){let i=!1;const n=t.length;for(let r=0,s=n-2;r<n;s=r,r+=2){const a=t[r],l=t[r+1],u=t[s],h=t[s+1];l>e!=h>e&&o<(u-a)*(e-l)/(h-l)+a&&(i=!i)}return i}function _i(o,e,t=2){const i=e&&e.length>0,n=i?e[0]*t:o.length;let r=qe(o,0,n,t,!0);const s=[];if(!r||r.next===r.prev)return s;i&&(r=ki(o,e,r,t));let a=1/0,l=1/0,u=-1/0,h=-1/0,f=0;if(o.length>80*t){for(let c=0;c<n;c+=t){const g=o[c],v=o[c+1];g<a&&(a=g),v<l&&(l=v),g>u&&(u=g),v>h&&(h=v)}f=Math.max(u-a,h-l),f=f!==0?32767/f:0}return Q(r,s,t,a,l,f,0),s}function qe(o,e,t,i,n){let r=null;if(n===Ji(o,e,t,i)>0)for(let s=e;s<t;s+=i)r=Je(s,o[s],o[s+1],r);else for(let s=t-i;s>=e;s-=i)r=Je(s,o[s],o[s+1],r);return r&&ae(r,r.next)&&(te(r),r=r.next),r?(r.next.prev=r,r.prev.next=r,r.next):null}function H(o,e){e||(e=o);let t=o,i;do if(i=!1,!t.steiner&&(ae(t,t.next)||F(t.prev,t,t.next)===0)){if(te(t),t=e=t.prev,t===t.next)break;i=!0}else t=t.next;while(i||t!==e);return e}function Q(o,e,t,i,n,r,s){if(!o)return;!s&&r&&Wi(o,i,n,r);let a=o,l,u;for(;o.prev!==o.next;){if(l=o.prev,u=o.next,r?Gi(o,i,n,r):Vi(o)){e.push(l.i/t,o.i/t,u.i/t),te(o),o=u.next,a=u.next;continue}if(o=u,o===a){s?s===1?(o=Ii(H(o),e,t),Q(o,e,t,i,n,r,2)):s===2&&Oi(o,e,t,i,n,r):Q(H(o),e,t,i,n,r,1);break}}}function Vi(o){const e=o.prev,t=o,i=o.next;if(F(e,t,i)>=0)return!1;const n=e.x,r=t.x,s=i.x,a=e.y,l=t.y,u=i.y,h=n<r?n<s?n:s:r<s?r:s,f=a<l?a<u?a:u:l<u?l:u,c=n>r?n>s?n:s:r>s?r:s,g=a>l?a>u?a:u:l>u?l:u;let v=i.next;for(;v!==e;){if(v.x>=h&&v.x<=c&&v.y>=f&&v.y<=g&&z(n,a,r,l,s,u,v.x,v.y)&&F(v.prev,v,v.next)>=0)return!1;v=v.next}return!0}function Gi(o,e,t,i){const n=o.prev,r=o,s=o.next;if(F(n,r,s)>=0)return!1;const a=n.x,l=r.x,u=s.x,h=n.y,f=r.y,c=s.y,g=a<l?a<u?a:u:l<u?l:u,v=h<f?h<c?h:c:f<c?f:c,d=a>l?a>u?a:u:l>u?l:u,A=h>f?h>c?h:c:f>c?f:c,b=ge(g,v,e,t,i),y=ge(d,A,e,t,i);let p=o.prevZ,m=o.nextZ;for(;p&&p.z>=b&&m&&m.z<=y;){if(p.x>=g&&p.x<=d&&p.y>=v&&p.y<=A&&p!==n&&p!==s&&z(a,h,l,f,u,c,p.x,p.y)&&F(p.prev,p,p.next)>=0||(p=p.prevZ,m.x>=g&&m.x<=d&&m.y>=v&&m.y<=A&&m!==n&&m!==s&&z(a,h,l,f,u,c,m.x,m.y)&&F(m.prev,m,m.next)>=0))return!1;m=m.nextZ}for(;p&&p.z>=b;){if(p.x>=g&&p.x<=d&&p.y>=v&&p.y<=A&&p!==n&&p!==s&&z(a,h,l,f,u,c,p.x,p.y)&&F(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;m&&m.z<=y;){if(m.x>=g&&m.x<=d&&m.y>=v&&m.y<=A&&m!==n&&m!==s&&z(a,h,l,f,u,c,m.x,m.y)&&F(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function Ii(o,e,t){let i=o;do{const n=i.prev,r=i.next.next;!ae(n,r)&&Ye(n,i,i.next,r)&&ee(n,r)&&ee(r,n)&&(e.push(n.i/t,i.i/t,r.i/t),te(i),te(i.next),i=o=r),i=i.next}while(i!==o);return H(i)}function Oi(o,e,t,i,n,r){let s=o;do{let a=s.next.next;for(;a!==s.prev;){if(s.i!==a.i&&qi(s,a)){let l=Ze(s,a);s=H(s,s.next),l=H(l,l.next),Q(s,e,t,i,n,r,0),Q(l,e,t,i,n,r,0);return}a=a.next}s=s.next}while(s!==o)}function ki(o,e,t,i){const n=[];for(let r=0;r<e.length;r++){const s=e[r]*i,a=r<e.length-1?e[r+1]*i:o.length,l=qe(o,s,a,i,!1);l&&(l===l.next&&(l.steiner=!0),n.push(zi(l)))}n.sort((r,s)=>r.x-s.x);for(const r of n)t=Ni(r,t);return t}function Ni(o,e){const t=Xi(o,e);if(!t)return e;const i=Ze(t,o);return H(i,i.next),H(t,t.next)}function Xi(o,e){let t=e;const i=o.x,n=o.y;let r=-1/0,s=null;do{if(n<=t.y&&n>=t.next.y&&t.next.y!==t.y){const f=t.x+(n-t.y)/(t.next.y-t.y)*(t.next.x-t.x);if(f<=i&&f>r&&(r=f,s=t.x<t.next.x?t:t.next,f===i))return s}t=t.next}while(t!==e);if(!s)return null;const a=s,l=s.x,u=s.y;let h=1/0;t=s;do{if(i>=t.x&&t.x>=l&&i!==t.x&&z(n<u?i:r,n,l,u,n<u?r:i,n,t.x,t.y)){const f=Math.abs(n-t.y)/(i-t.x);ee(t,o)&&(f<h||f===h&&(t.x>s.x||Hi(s,t)))&&(s=t,h=f)}t=t.next}while(t!==a);return s}function Hi(o,e){return F(o.prev,o,e.prev)<0&&F(e.next,o,o.next)<0}function Wi(o,e,t,i){let n=o;do n.z===0&&(n.z=ge(n.x,n.y,e,t,i)),n.prevZ=n.prev,n.nextZ=n.next,n=n.next;while(n!==o);n.prevZ.nextZ=null,n.prevZ=null,ji(n)}function ji(o){let e=1,t;do{let i=o;o=null;let n=null;for(t=0;i;){t++;let r=i,s=0;for(let l=0;l<e&&(s++,r=r.nextZ,!!r);l++);let a=e;for(;s>0||a>0&&r;){let l;s!==0&&(a===0||!r||i.z<=r.z)?(l=i,i=i.nextZ,s--):(l=r,r=r.nextZ,a--),n?n.nextZ=l:o=l,l.prevZ=n,n=l}i=r}n.nextZ=null,e*=2}while(t>1);return o}function ge(o,e,t,i,n){let r=(o-t)*n|0,s=(e-i)*n|0;return r=(r|r<<8)&16711935,r=(r|r<<4)&252645135,r=(r|r<<2)&858993459,r=(r|r<<1)&1431655765,s=(s|s<<8)&16711935,s=(s|s<<4)&252645135,s=(s|s<<2)&858993459,s=(s|s<<1)&1431655765,r|s<<1}function zi(o){let e=o,t=o;do(e.x<t.x||e.x===t.x&&e.y<t.y)&&(t=e),e=e.next;while(e!==o);return t}function z(o,e,t,i,n,r,s,a){return(n-s)*(e-a)-(o-s)*(r-a)>=0&&(o-s)*(i-a)-(t-s)*(e-a)>=0&&(t-s)*(r-a)-(n-s)*(i-a)>=0}function qi(o,e){return o.next.i!==e.i&&o.prev.i!==e.i&&!Yi(o,e)&&(ee(o,e)&&ee(e,o)&&Zi(o,e)&&(F(o.prev,o,e.prev)!==0||F(o,e.prev,e)!==0)||ae(o,e)&&F(o.prev,o,o.next)>0&&F(e.prev,e,e.next)>0)}function F(o,e,t){return(e.y-o.y)*(t.x-e.x)-(e.x-o.x)*(t.y-e.y)}function ae(o,e){return o.x===e.x&&o.y===e.y}function Ye(o,e,t,i){const n=ue(F(o,e,t)),r=ue(F(o,e,i)),s=ue(F(t,i,o)),a=ue(F(t,i,e));return!!(n!==r&&s!==a||n===0&&le(o,t,e)||r===0&&le(o,i,e)||s===0&&le(t,o,i)||a===0&&le(t,e,i))}function le(o,e,t){return e.x<=Math.max(o.x,t.x)&&e.x>=Math.min(o.x,t.x)&&e.y<=Math.max(o.y,t.y)&&e.y>=Math.min(o.y,t.y)}function ue(o){return o>0?1:o<0?-1:0}function Yi(o,e){let t=o;do{if(t.i!==o.i&&t.next.i!==o.i&&t.i!==e.i&&t.next.i!==e.i&&Ye(t,t.next,o,e))return!0;t=t.next}while(t!==o);return!1}function ee(o,e){return F(o.prev,o,o.next)<0?F(o,e,o.next)>=0&&F(o,o.prev,e)>=0:F(o,e,o.prev)<0||F(o,o.next,e)<0}function Zi(o,e){let t=o,i=!1;const n=(o.x+e.x)/2,r=(o.y+e.y)/2;do t.y>r!=t.next.y>r&&t.next.y!==t.y&&n<(t.next.x-t.x)*(r-t.y)/(t.next.y-t.y)+t.x&&(i=!i),t=t.next;while(t!==o);return i}function Ze(o,e){const t=ve(o.i,o.x,o.y),i=ve(e.i,e.x,e.y),n=o.next,r=e.prev;return o.next=e,e.prev=o,t.next=n,n.prev=t,i.next=t,t.prev=i,r.next=i,i.prev=r,i}function Je(o,e,t,i){const n=ve(o,e,t);return i?(n.next=i.next,n.prev=i,i.next.prev=n,i.next=n):(n.prev=n,n.next=n),n}function te(o){o.next.prev=o.prev,o.prev.next=o.next,o.prevZ&&(o.prevZ.nextZ=o.nextZ),o.nextZ&&(o.nextZ.prevZ=o.prevZ)}function ve(o,e,t){return{i:o,x:e,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function Ji(o,e,t,i){let n=0;for(let r=e,s=t-i;r<t;r+=i)n+=(o[s]-o[r])*(o[r+1]+o[s+1]),s=r;return n}const T={parallaxX:.4,parallaxY:.8,parallaxMax:30,overscan:.06,pomSteps:16,rimIntensity:.6,rimColor:"#ffffff",rimWidth:.025,refractionStrength:.015,chromaticStrength:.008,occlusionIntensity:.4,depthPower:.7,depthScale:1.2,depthBias:-.05,fogDensity:.15,fogColor:"#1a1a2e",colorShift:.6,brightnessBias:.05,contrastLow:.02,contrastHigh:.98,verticalReduction:.5,dofStart:.5,dofStrength:.5,bevelIntensity:.5,bevelWidth:.04,bevelDarkening:.2,bevelDesaturation:.12,bevelLightAngle:135,edgeThickness:.01,edgeSpecular:.35,edgeColor:"#a0a0a0",chamferWidth:.025,chamferAngle:45,chamferColor:"#262630",chamferAmbient:.12,chamferSpecular:.3,chamferShininess:24,edgeOcclusionWidth:.03,edgeOcclusionStrength:.2,lightDirection:"-0.5,0.7,-0.3",autoplay:!0,loop:!0,muted:!0};class xe{constructor(e,t=.08,i=.06){this.host=e,this.lerpFactor=t,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const e=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,t=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=ce(this.smoothedOutput.x,e.x,t),this.smoothedOutput.y=ce(this.smoothedOutput.y,e.y,t),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=e=>{const t=this.host.getBoundingClientRect(),i=(e.clientX-t.left)/t.width*2-1,n=(e.clientY-t.top)/t.height*2-1;this.pointerTarget.x=q(i,-1,1),this.pointerTarget.y=q(n,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=e=>{const t=e.touches[0];t&&(this.touchActive=!0,this.touchAnchorX=t.clientX,this.touchAnchorY=t.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=e=>{const t=e.touches[0];if(!t)return;const i=t.clientX-this.touchAnchorX,n=t.clientY-this.touchAnchorY,r=xe.TOUCH_DRAG_RANGE;this.pointerTarget.x=q(i/r,-1,1),this.pointerTarget.y=q(n/r,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const e=DeviceOrientationEvent;if(typeof e.requestPermission=="function")try{if(await e.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=e=>{const t=q((e.gamma??0)/45,-1,1),i=q((e.beta??0)/45,-1,1);this.motionTarget.x=ce(this.motionTarget.x,t,this.motionLerpFactor),this.motionTarget.y=ce(this.motionTarget.y,i,this.motionLerpFactor)}}class he extends HTMLElement{static TAG_NAME="layershift-portal";static get observedAttributes(){return["src","depth-src","depth-meta","logo-src","parallax-x","parallax-y","parallax-max","overscan","pom-steps","quality","gpu-backend","rim-intensity","rim-color","rim-width","refraction-strength","chromatic-strength","occlusion-intensity","depth-power","depth-scale","depth-bias","fog-density","fog-color","color-shift","brightness-bias","contrast-low","contrast-high","vertical-reduction","dof-start","dof-strength","bevel-intensity","bevel-width","bevel-darkening","bevel-desaturation","bevel-light-angle","edge-thickness","edge-specular","edge-color","chamfer-width","chamfer-angle","chamfer-color","chamfer-ambient","chamfer-specular","chamfer-shininess","edge-occlusion-width","edge-occlusion-strength","light-direction","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta","logo-src"];shadow;container=null;renderer=null;inputHandler=null;video=null;mesh=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new _e(this)}getAttrFloat(e,t){const i=this.getAttribute(e);if(i===null)return t;const n=parseFloat(i);return Number.isFinite(n)?n:t}getAttrBool(e,t){if(!this.hasAttribute(e))return t;const i=this.getAttribute(e);return!(i==="false"||i==="0")}getAttrColor(e,t){const i=this.getAttribute(e)??t;return $i(i)}getAttrVec3(e,t){const n=(this.getAttribute(e)??t).split(",").map(s=>parseFloat(s.trim()));if(n.length>=3&&n.every(Number.isFinite))return[n[0],n[1],n[2]];const r=t.split(",").map(s=>parseFloat(s.trim()));return[r[0],r[1],r[2]]}get parallaxX(){return this.getAttrFloat("parallax-x",T.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",T.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",T.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",T.overscan)}get pomSteps(){return this.getAttrFloat("pom-steps",T.pomSteps)}get quality(){const e=this.getAttribute("quality");if(e==="auto"||e==="high"||e==="medium"||e==="low")return e}get gpuBackend(){const e=this.getAttribute("gpu-backend");return e==="webgpu"||e==="webgl2"?e:"auto"}get rimIntensity(){return this.getAttrFloat("rim-intensity",T.rimIntensity)}get rimWidth(){return this.getAttrFloat("rim-width",T.rimWidth)}get rimColor(){return this.getAttrColor("rim-color",T.rimColor)}get refractionStrength(){return this.getAttrFloat("refraction-strength",T.refractionStrength)}get chromaticStrength(){return this.getAttrFloat("chromatic-strength",T.chromaticStrength)}get occlusionIntensity(){return this.getAttrFloat("occlusion-intensity",T.occlusionIntensity)}get depthPower(){return this.getAttrFloat("depth-power",T.depthPower)}get depthScale(){return this.getAttrFloat("depth-scale",T.depthScale)}get depthBias(){return this.getAttrFloat("depth-bias",T.depthBias)}get fogDensity(){return this.getAttrFloat("fog-density",T.fogDensity)}get fogColor(){return this.getAttrColor("fog-color",T.fogColor)}get colorShift(){return this.getAttrFloat("color-shift",T.colorShift)}get brightnessBias(){return this.getAttrFloat("brightness-bias",T.brightnessBias)}get contrastLow(){return this.getAttrFloat("contrast-low",T.contrastLow)}get contrastHigh(){return this.getAttrFloat("contrast-high",T.contrastHigh)}get verticalReduction(){return this.getAttrFloat("vertical-reduction",T.verticalReduction)}get dofStart(){return this.getAttrFloat("dof-start",T.dofStart)}get dofStrength(){return this.getAttrFloat("dof-strength",T.dofStrength)}get bevelIntensity(){return this.getAttrFloat("bevel-intensity",T.bevelIntensity)}get bevelWidth(){return this.getAttrFloat("bevel-width",T.bevelWidth)}get bevelDarkening(){return this.getAttrFloat("bevel-darkening",T.bevelDarkening)}get bevelDesaturation(){return this.getAttrFloat("bevel-desaturation",T.bevelDesaturation)}get bevelLightAngle(){return this.getAttrFloat("bevel-light-angle",T.bevelLightAngle)}get edgeThickness(){return this.getAttrFloat("edge-thickness",T.edgeThickness)}get edgeSpecular(){return this.getAttrFloat("edge-specular",T.edgeSpecular)}get edgeColor(){return this.getAttrColor("edge-color",T.edgeColor)}get chamferWidth(){return this.getAttrFloat("chamfer-width",T.chamferWidth)}get chamferAngle(){return this.getAttrFloat("chamfer-angle",T.chamferAngle)}get chamferColor(){return this.getAttrColor("chamfer-color",T.chamferColor)}get chamferAmbient(){return this.getAttrFloat("chamfer-ambient",T.chamferAmbient)}get chamferSpecular(){return this.getAttrFloat("chamfer-specular",T.chamferSpecular)}get chamferShininess(){return this.getAttrFloat("chamfer-shininess",T.chamferShininess)}get edgeOcclusionWidth(){return this.getAttrFloat("edge-occlusion-width",T.edgeOcclusionWidth)}get edgeOcclusionStrength(){return this.getAttrFloat("edge-occlusion-strength",T.edgeOcclusionStrength)}get lightDirection3(){return this.getAttrVec3("light-direction",T.lightDirection)}get shouldAutoplay(){return this.getAttrBool("autoplay",T.autoplay)}get shouldLoop(){return this.getAttrBool("loop",T.loop)}get shouldMute(){return this.getAttrBool("muted",T.muted)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}attachVideoEventListeners(e){e.addEventListener("play",()=>{this.emit("layershift-portal:play",{currentTime:e.currentTime})}),e.addEventListener("pause",()=>{this.emit("layershift-portal:pause",{currentTime:e.currentTime})}),e.addEventListener("ended",()=>{e.loop&&(this.loopCount+=1,this.emit("layershift-portal:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(e,t,i){this.lifecycle.onAttributeChanged(e,t,i)}setupShadowDOM(){this.shadow.innerHTML="";const e=document.createElement("style");e.textContent=`
724
1853
  :host {
725
1854
  display: block;
726
1855
  width: 100%;
@@ -740,4 +1869,4 @@ ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.delete
740
1869
  width: 100%;
741
1870
  height: 100%;
742
1871
  }
743
- `,this.shadow.appendChild(t),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async doInit(t){const e=this.getAttribute("src"),i=this.getAttribute("depth-src"),o=this.getAttribute("depth-meta"),n=this.getAttribute("logo-src");if(this.container)try{const[s,h,a]=await Promise.all([this.createVideoElement(e),vt(i,o),pe(n)]);if(t.aborted){s.remove();return}this.video=s,this.mesh=a,this.loopCount=0,this.attachVideoEventListeners(s);const l=this.parallaxMax/Math.max(s.videoWidth,1);let u;try{const d=await it.create(h,h.meta.width,h.meta.height);this.depthWorker=d,u=m=>d.sample(m)}catch{const d=new gt(h,h.meta.width,h.meta.height);u=m=>d.sample(m)}if(t.aborted){s.remove(),this.depthWorker?.dispose(),this.depthWorker=null;return}const f={parallaxStrength:l,overscanPadding:this.overscan,pomSteps:this.pomSteps,rimLightIntensity:this.rimIntensity,rimLightColor:this.rimColor,rimLightWidth:this.rimWidth,refractionStrength:this.refractionStrength,chromaticStrength:this.chromaticStrength,occlusionIntensity:this.occlusionIntensity,depthPower:this.depthPower,depthScale:this.depthScale,depthBias:this.depthBias,fogDensity:this.fogDensity,fogColor:this.fogColor,colorShift:this.colorShift,brightnessBias:this.brightnessBias,contrastLow:this.contrastLow,contrastHigh:this.contrastHigh,verticalReduction:this.verticalReduction,dofStart:this.dofStart,dofStrength:this.dofStrength,bevelIntensity:this.bevelIntensity,bevelWidth:this.bevelWidth,bevelDarkening:this.bevelDarkening,bevelDesaturation:this.bevelDesaturation,bevelLightAngle:this.bevelLightAngle,edgeThickness:this.edgeThickness,edgeSpecular:this.edgeSpecular,edgeColor:this.edgeColor,chamferWidth:this.chamferWidth,chamferAngle:this.chamferAngle,chamferColor:this.chamferColor,chamferAmbient:this.chamferAmbient,chamferSpecular:this.chamferSpecular,chamferShininess:this.chamferShininess,edgeOcclusionWidth:this.edgeOcclusionWidth,edgeOcclusionStrength:this.edgeOcclusionStrength,lightDirection:this.lightDirection3};this.renderer=new ft(this.container,f),this.renderer.initialize(s,h.meta.width,h.meta.height,a),this.inputHandler=new pt(this);const c=this.parallaxX,g=this.parallaxY;if(this.renderer.start(s,u,()=>{if(!this.inputHandler)return{x:0,y:0};const d=this.inputHandler.update();return{x:d.x*c,y:d.y*g}},(d,m)=>{this.emit("layershift-portal:frame",{currentTime:d,frameNumber:m})}),this.shouldAutoplay){s.currentTime=0;try{await s.play()}catch{}}if(t.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-portal:ready",{videoWidth:s.videoWidth,videoHeight:s.videoHeight,duration:s.duration})}catch(s){const h=s instanceof Error?s.message:"Failed to initialize.";console.error("<layershift-portal>: Failed to initialize.",s),this.emit("layershift-portal:error",{message:h})}}async createVideoElement(t){const e=document.createElement("video");return e.crossOrigin="anonymous",e.setAttribute("crossorigin","anonymous"),e.playsInline=!0,e.setAttribute("playsinline",""),e.setAttribute("webkit-playsinline","true"),e.muted=this.shouldMute,e.defaultMuted=this.shouldMute,this.shouldMute&&e.setAttribute("muted",""),e.loop=this.shouldLoop,e.preload="auto",e.style.display="none",e.src=t,this.shadow.appendChild(e),await new Promise((i,o)=>{if(e.readyState>=HTMLMediaElement.HAVE_METADATA){i();return}const n=()=>{h(),i()},s=()=>{h(),o(new Error("Failed to load video metadata."))},h=()=>{e.removeEventListener("loadedmetadata",n),e.removeEventListener("error",s)};e.addEventListener("loadedmetadata",n),e.addEventListener("error",s),e.load()}),e}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.depthWorker?.dispose(),this.depthWorker=null,this.video&&(this.video.pause(),this.video.removeAttribute("src"),this.video.load(),this.video.remove(),this.video=null),this.mesh=null,this.loopCount=0,this.container=null}}function W(r,t,e){return Math.min(e,Math.max(t,r))}function lt(r,t,e){return r+(t-r)*e}function Ne(r){const t=r.replace("#","");if(t.length===3){const e=parseInt(t[0]+t[0],16)/255,i=parseInt(t[1]+t[1],16)/255,o=parseInt(t[2]+t[2],16)/255;return[e,i,o]}if(t.length===6){const e=parseInt(t.substring(0,2),16)/255,i=parseInt(t.substring(2,4),16)/255,o=parseInt(t.substring(4,6),16)/255;return[e,i,o]}return[0,0,0]}return customElements.get(rt.TAG_NAME)||customElements.define(rt.TAG_NAME,rt),customElements.get(ht.TAG_NAME)||customElements.define(ht.TAG_NAME,ht),et.LayershiftElement=rt,et.LayershiftPortalElement=ht,Object.defineProperty(et,Symbol.toStringTag,{value:"Module"}),et})({});
1872
+ `,this.shadow.appendChild(e),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async doInit(e){const t=this.getAttribute("src"),i=this.getAttribute("depth-src"),n=this.getAttribute("depth-meta"),r=this.getAttribute("logo-src");if(this.container)try{const[s,a,l]=await Promise.all([this.createVideoElement(t),Se(i,n),Pi(r)]);if(e.aborted){s.remove();return}this.video=s,this.mesh=l,this.loopCount=0,this.attachVideoEventListeners(s);const u=this.parallaxMax/Math.max(s.videoWidth,1),h=new be(a),f=A=>h.sample(A),c={parallaxStrength:u,overscanPadding:this.overscan,pomSteps:this.pomSteps,quality:this.quality,rimLightIntensity:this.rimIntensity,rimLightColor:this.rimColor,rimLightWidth:this.rimWidth,refractionStrength:this.refractionStrength,chromaticStrength:this.chromaticStrength,occlusionIntensity:this.occlusionIntensity,depthPower:this.depthPower,depthScale:this.depthScale,depthBias:this.depthBias,fogDensity:this.fogDensity,fogColor:this.fogColor,colorShift:this.colorShift,brightnessBias:this.brightnessBias,contrastLow:this.contrastLow,contrastHigh:this.contrastHigh,verticalReduction:this.verticalReduction,dofStart:this.dofStart,dofStrength:this.dofStrength,bevelIntensity:this.bevelIntensity,bevelWidth:this.bevelWidth,bevelDarkening:this.bevelDarkening,bevelDesaturation:this.bevelDesaturation,bevelLightAngle:this.bevelLightAngle,edgeThickness:this.edgeThickness,edgeSpecular:this.edgeSpecular,edgeColor:this.edgeColor,chamferWidth:this.chamferWidth,chamferAngle:this.chamferAngle,chamferColor:this.chamferColor,chamferAmbient:this.chamferAmbient,chamferSpecular:this.chamferSpecular,chamferShininess:this.chamferShininess,edgeOcclusionWidth:this.edgeOcclusionWidth,edgeOcclusionStrength:this.edgeOcclusionStrength,lightDirection:this.lightDirection3},g=await Ce(this.gpuBackend);if(e.aborted)return;g.type==="webgpu"&&g.device&&g.adapter?this.renderer=new Ei(this.container,c,g.device,g.adapter.info):this.renderer=new si(this.container,c),this.renderer.initialize(s,a.meta.width,a.meta.height,l),this.inputHandler=new xe(this);const v=this.parallaxX,d=this.parallaxY;if(this.renderer.start(s,f,()=>{if(!this.inputHandler)return{x:0,y:0};const A=this.inputHandler.update();return{x:A.x*v,y:A.y*d}},(A,b)=>{this.emit("layershift-portal:frame",{currentTime:A,frameNumber:b})}),this.shouldAutoplay){s.currentTime=0;try{await s.play()}catch{}}if(e.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-portal:ready",{videoWidth:s.videoWidth,videoHeight:s.videoHeight,duration:s.duration})}catch(s){const a=s instanceof Error?s.message:"Failed to initialize.";console.error("<layershift-portal>: Failed to initialize.",s),this.emit("layershift-portal:error",{message:a})}}async createVideoElement(e){const t=document.createElement("video");return t.crossOrigin="anonymous",t.setAttribute("crossorigin","anonymous"),t.playsInline=!0,t.setAttribute("playsinline",""),t.setAttribute("webkit-playsinline","true"),t.muted=this.shouldMute,t.defaultMuted=this.shouldMute,this.shouldMute&&t.setAttribute("muted",""),t.loop=this.shouldLoop,t.preload="auto",t.style.display="none",t.src=e,this.shadow.appendChild(t),await new Promise((i,n)=>{if(t.readyState>=HTMLMediaElement.HAVE_METADATA){i();return}const r=()=>{a(),i()},s=()=>{a(),n(new Error("Failed to load video metadata."))},a=()=>{t.removeEventListener("loadedmetadata",r),t.removeEventListener("error",s)};t.addEventListener("loadedmetadata",r),t.addEventListener("error",s),t.load()}),t}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.video&&(this.video.pause(),this.video.removeAttribute("src"),this.video.load(),this.video.remove(),this.video=null),this.mesh=null,this.loopCount=0,this.container=null}}function q(o,e,t){return Math.min(t,Math.max(e,o))}function ce(o,e,t){return o+(e-o)*t}function $i(o){const e=o.replace("#","");if(e.length===3){const t=parseInt(e[0]+e[0],16)/255,i=parseInt(e[1]+e[1],16)/255,n=parseInt(e[2]+e[2],16)/255;return[t,i,n]}if(e.length===6){const t=parseInt(e.substring(0,2),16)/255,i=parseInt(e.substring(2,4),16)/255,n=parseInt(e.substring(4,6),16)/255;return[t,i,n]}return[0,0,0]}return customElements.get(re.TAG_NAME)||customElements.define(re.TAG_NAME,re),customElements.get(he.TAG_NAME)||customElements.define(he.TAG_NAME,he),ne.LayershiftElement=re,ne.LayershiftPortalElement=he,Object.defineProperty(ne,Symbol.toStringTag,{value:"Module"}),ne})({});