layershift 0.3.0 → 0.4.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 (47) hide show
  1. package/dist/components/layershift.js +264 -1119
  2. package/dist/npm/layershift.es.js +2277 -3774
  3. package/dist/types/components/layershift/global.d.ts +2 -2
  4. package/dist/types/components/layershift/index.d.ts +5 -1
  5. package/dist/types/components/layershift/index.d.ts.map +1 -1
  6. package/dist/types/components/layershift/layershift-element.d.ts +39 -9
  7. package/dist/types/components/layershift/layershift-element.d.ts.map +1 -1
  8. package/dist/types/components/layershift/lifecycle.d.ts +6 -0
  9. package/dist/types/components/layershift/lifecycle.d.ts.map +1 -1
  10. package/dist/types/components/layershift/portal-element.d.ts +11 -3
  11. package/dist/types/components/layershift/portal-element.d.ts.map +1 -1
  12. package/dist/types/components/layershift/types.d.ts +22 -9
  13. package/dist/types/components/layershift/types.d.ts.map +1 -1
  14. package/dist/types/depth-analysis.d.ts +10 -6
  15. package/dist/types/depth-analysis.d.ts.map +1 -1
  16. package/dist/types/{parallax-renderer.d.ts → depth-effect-renderer.d.ts} +63 -15
  17. package/dist/types/depth-effect-renderer.d.ts.map +1 -0
  18. package/dist/types/depth-estimator.d.ts +96 -0
  19. package/dist/types/depth-estimator.d.ts.map +1 -0
  20. package/dist/types/input-handler.d.ts +8 -2
  21. package/dist/types/input-handler.d.ts.map +1 -1
  22. package/dist/types/media-source.d.ts +76 -0
  23. package/dist/types/media-source.d.ts.map +1 -0
  24. package/dist/types/portal-renderer.d.ts +2 -1
  25. package/dist/types/portal-renderer.d.ts.map +1 -1
  26. package/dist/types/precomputed-depth.d.ts +5 -0
  27. package/dist/types/precomputed-depth.d.ts.map +1 -1
  28. package/dist/types/renderer-base.d.ts +13 -8
  29. package/dist/types/renderer-base.d.ts.map +1 -1
  30. package/dist/types/shared/channel-to-renderer.d.ts +72 -0
  31. package/dist/types/shared/channel-to-renderer.d.ts.map +1 -0
  32. package/dist/types/shared/filter-config.d.ts +184 -0
  33. package/dist/types/shared/filter-config.d.ts.map +1 -0
  34. package/dist/types/video-source.d.ts +0 -1
  35. package/dist/types/video-source.d.ts.map +1 -1
  36. package/package.json +9 -3
  37. package/dist/types/gpu-backend.d.ts +0 -37
  38. package/dist/types/gpu-backend.d.ts.map +0 -1
  39. package/dist/types/parallax-renderer-webgpu.d.ts +0 -103
  40. package/dist/types/parallax-renderer-webgpu.d.ts.map +0 -1
  41. package/dist/types/parallax-renderer.d.ts.map +0 -1
  42. package/dist/types/portal-renderer-webgpu.d.ts +0 -199
  43. package/dist/types/portal-renderer-webgpu.d.ts.map +0 -1
  44. package/dist/types/render-pass-webgpu.d.ts +0 -76
  45. package/dist/types/render-pass-webgpu.d.ts.map +0 -1
  46. package/dist/types/webgpu-utils.d.ts +0 -42
  47. package/dist/types/webgpu-utils.d.ts.map +0 -1
@@ -1,6 +1,6 @@
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
1
+ var Layershift=(function(st){"use strict";class Ct{constructor(t){this.depthData=t;const e=t.meta.width*t.meta.height;this.uint8Output=new Uint8Array(e)}uint8Output;lastFrameIndex=-1;lastNextFrameIndex=-1;lastLerpFactor=-1;sample(t){const e=re(t*this.depthData.meta.fps,0,this.depthData.meta.frameCount-1),n=Math.floor(e),i=Math.min(n+1,this.depthData.meta.frameCount-1),r=e-n,s=n!==this.lastFrameIndex||i!==this.lastNextFrameIndex,a=Math.abs(r-this.lastLerpFactor)>.001;if(!s&&!a)return this.uint8Output;this.lastFrameIndex=n,this.lastNextFrameIndex=i,this.lastLerpFactor=r;const l=1-r,u=this.depthData.frames[n],h=this.depthData.frames[i];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 Lt(o,t,e){const[n,i]=await Promise.all([te(t),ee(o)]);return ne(i,n)}async function te(o){const t=await fetch(o);if(!t.ok)throw new Error(`Failed to fetch depth metadata (${t.status} ${t.statusText}).`);const e=await t.json();return ie(e),{frameCount:e.frameCount,fps:e.fps,width:e.width,height:e.height,sourceFps:e.sourceFps}}async function ee(o,t){const e=await fetch(o);if(!e.ok)throw new Error(`Failed to fetch depth data (${e.status} ${e.statusText}).`);e.headers.get("content-length");const n=e.body;if(!n)return new Uint8Array(await e.arrayBuffer());const i=[];let r=0;const s=n.getReader();for(;;){const{done:u,value:h}=await s.read();if(u)break;h&&(i.push(h),r+=h.byteLength)}const a=new Uint8Array(r);let l=0;for(const u of i)a.set(u,l),l+=u.byteLength;return a}function ne(o,t){if(o.byteLength<4)throw new Error("Depth data binary is missing the frame-count header.");const n=new DataView(o.buffer,o.byteOffset,o.byteLength).getUint32(0,!0),i=t.width*t.height,r=4+n*i;if(o.byteLength!==r)throw new Error(`Depth data byte length mismatch. Expected ${r} bytes, received ${o.byteLength}.`);if(n!==t.frameCount)throw new Error(`Depth frame count mismatch between metadata (${t.frameCount}) and binary header (${n}).`);const s=o.subarray(4),a=new Array(n);for(let l=0;l<n;l+=1){const u=l*i;a[l]=s.subarray(u,u+i)}return{meta:t,frames:a}}function ie(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 N(o,t){const e=new Uint8Array(o*t);return e.fill(128),{meta:{frameCount:1,fps:1,width:o,height:t,sourceFps:1},frames:[e]}}function re(o,t,e){return Math.min(e,Math.max(t,o))}const oe={parallaxStrength:.05,contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4,pomSteps:16,overscanPadding:.08};function se(o,t,e){const n=new Float32Array(256);if(o.length===0||t<=0||e<=0)return Ut(n);const i=le(o.length),r=t*e;let s=0;const a=new Uint32Array(256);for(const b of i){const T=o[b],m=Math.min(T.length,r);for(let S=0;S<m;S+=1)a[T[S]]+=1;s+=m}if(s===0)return Ut(n);const l=1/s;for(let b=0;b<256;b+=1)n[b]=a[b]*l;const u=new Float32Array(256);u[0]=n[0];for(let b=1;b<256;b+=1)u[b]=u[b-1]+n[b];const h=tt(u,.05),f=tt(u,.25),c=tt(u,.5),E=tt(u,.75),v=tt(u,.95);let p=0;for(let b=0;b<256;b+=1)p+=b/255*n[b];let P=0;for(let b=0;b<256;b+=1){const T=b/255-p;P+=n[b]*T*T}const x=Math.sqrt(P),y=v-h,d=E-f,g=he(n);return{mean:p,stdDev:x,p5:h,p25:f,median:c,p75:E,p95:v,effectiveRange:y,iqr:d,bimodality:g,histogram:n}}function ae(o){if(o.effectiveRange<.05||o.stdDev<.02)return{...oe};const t=o.effectiveRange-.5,e=o.bimodality-.4,n=O(.05-t*.03+e*.01,.035,.065),i=O(o.p5-.03,0,.25),r=O(o.p95+.03,.75,1),s=O((n-.03)/.05,0,1),a=O(.6-s*.25,.35,.6),l=O(.6-t*.2,.5,.7),u=O(.4+t*.2,.25,.5),h=16,f=O(n+.03,.06,.1);return{parallaxStrength:n,contrastLow:i,contrastHigh:r,verticalReduction:a,dofStart:l,dofStrength:u,pomSteps:h,overscanPadding:f}}function le(o){if(o<=0)return[];if(o===1)return[0];const t=o-1,e=[0,Math.floor(o/4),Math.floor(o/2),Math.floor(3*o/4),t],n=new Set,i=[];for(const r of e)n.has(r)||(n.add(r),i.push(r));return i}function tt(o,t){for(let e=0;e<256;e+=1)if(o[e]>=t)return e/255;return 1}function he(o){const t=new Float32Array(256);for(let c=0;c<256;c+=1){let E=0,v=0;for(let p=c-2;p<=c+2;p+=1)p>=0&&p<256&&(E+=o[p],v+=1);t[c]=E/v}let e=0;for(let c=0;c<256;c+=1)e+=t[c];e/=256;const n=e*2,i=25,r=[];for(let c=1;c<255;c+=1)t[c]>t[c-1]&&t[c]>t[c+1]&&t[c]>=n&&r.push({bin:c,height:t[c]});if(t[0]>t[1]&&t[0]>=n&&r.push({bin:0,height:t[0]}),t[255]>t[254]&&t[255]>=n&&r.push({bin:255,height:t[255]}),r.sort((c,E)=>E.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)>=i){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)t[c]<h&&(h=t[c]);const f=Math.min(s.height,a.height);return f<=0?0:O(1-h/f,0,1)}function Ut(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 O(o,t,e){return Math.min(e,Math.max(t,o))}function z(o,t,e){const n=o.createShader(t);if(!n)throw new Error("Failed to create shader.");if(o.shaderSource(n,e),o.compileShader(n),!o.getShaderParameter(n,o.COMPILE_STATUS)){const i=o.getShaderInfoLog(n)??"";throw o.deleteShader(n),new Error(`Shader compilation failed:
2
+ ${i}`)}return n}function Et(o,t,e){const n=o.createProgram();if(!n)throw new Error("Failed to create program.");if(o.attachShader(n,t),o.attachShader(n,e),o.linkProgram(n),!o.getProgramParameter(n,o.LINK_STATUS)){const i=o.getProgramInfoLog(n)??"";throw o.deleteProgram(n),new Error(`Program linking failed:
3
+ ${i}`)}return o.detachShader(n,t),o.detachShader(n,e),o.deleteShader(t),o.deleteShader(e),n}function bt(o,t,e){const n={};for(const i of e)n[i]=o.getUniformLocation(t,i);return n}const ue=new Float32Array([-1,-1,1,-1,-1,1,1,1]);function _t(o,t){const e=o.createVertexArray();if(!e)throw new Error("Failed to create VAO.");o.bindVertexArray(e);const n=o.createBuffer();o.bindBuffer(o.ARRAY_BUFFER,n),o.bufferData(o.ARRAY_BUFFER,ue,o.STATIC_DRAW);const i=o.getAttribLocation(t,"aPosition");return o.enableVertexAttribArray(i),o.vertexAttribPointer(i,2,o.FLOAT,!1,0,0),o.bindVertexArray(null),e}class Mt{slots=new Map;nextUnit=0;register(t){if(this.slots.has(t))throw new Error(`TextureRegistry: slot '${t}' already registered.`);const e={name:t,unit:this.nextUnit++,texture:null};return this.slots.set(t,e),e}get(t){const e=this.slots.get(t);if(!e)throw new Error(`TextureRegistry: slot '${t}' not found.`);return e}disposeAll(t){for(const e of this.slots.values())e.texture&&(t.deleteTexture(e.texture),e.texture=null)}get size(){return this.slots.size}}function k(o,t,e,n,i){const r=z(o,o.VERTEX_SHADER,e),s=z(o,o.FRAGMENT_SHADER,n),a=Et(o,r,s),l=bt(o,a,i);return{name:t,program:a,uniforms:l,dispose(u){u.deleteProgram(a)}}}const ce={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 fe(o){let t="unknown";const e=o.getExtension("WEBGL_debug_renderer_info");e&&(t=o.getParameter(e.UNMASKED_RENDERER_WEBGL)||"unknown");const n=o.getParameter(o.MAX_TEXTURE_SIZE),i=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:t,maxTextureSize:n,hardwareConcurrency:i,deviceMemory:r,devicePixelRatio:s,screenPixels:a,isMobile:l&&u}}const de=["mali-4","mali-t","adreno 3","adreno 4","adreno 5","powervr sgx","intel hd graphics","intel uhd graphics","intel iris","llvmpipe","swiftshader","software"],me=["nvidia","geforce","radeon rx","radeon pro","apple m","apple gpu","adreno 7","adreno 6","mali-g7","mali-g6"];function pe(o){let t=0;const e=o.gpuRenderer.toLowerCase(),n=de.some(r=>e.includes(r)),i=me.some(r=>e.includes(r));return n&&(t-=30),i&&(t+=20),o.maxTextureSize>=16384?t+=10:o.maxTextureSize>=8192?t+=5:o.maxTextureSize<=4096&&(t-=15),o.hardwareConcurrency>=8?t+=5:o.hardwareConcurrency>=4?t+=0:o.hardwareConcurrency>0&&o.hardwareConcurrency<4&&(t-=10),o.deviceMemory>=8?t+=5:o.deviceMemory>=4?t+=0:o.deviceMemory>0&&o.deviceMemory<4&&(t-=15),o.isMobile&&(t-=10),t>=0?"high":t>=-25?"medium":"low"}function It(o,t){const e=t&&t!=="auto"?t:pe(fe(o));return{tier:e,...ce[e]}}class at{static RESIZE_DEBOUNCE_MS=100;canvas;container;depthWidth=0;depthHeight=0;sourceDepthWidth=0;sourceDepthHeight=0;depthSubsampleBuffer=null;videoAspect=1.7777777777777777;isCameraSource=!1;uvOffset=[0,0];uvScale=[1,1];readDepth=null;readInput=null;mediaSource=null;onVideoFrame=null;animationFrameHandle=0;rvfcHandle=0;rvfcSupported=!1;resizeObserver=null;resizeTimer=null;qualityParams;constructor(t){this.container=t,this.canvas=document.createElement("canvas"),this.container.appendChild(this.canvas),this.canvas.addEventListener("webglcontextlost",this._handleContextLost),this.canvas.addEventListener("webglcontextrestored",this._handleContextRestored)}get canvasElement(){return this.canvas}start(t,e,n,i){this.stop(),this.mediaSource=t,this.readDepth=e,this.readInput=n,this.onVideoFrame=i??null,this.rvfcSupported=t.isLive&&typeof t.requestVideoFrameCallback=="function",this.rvfcSupported?this.rvfcHandle=t.requestVideoFrameCallback(this._videoFrameLoop):t.isLive||this.onDepthUpdate(t.currentTime),this.animationFrameHandle=window.requestAnimationFrame(this._rafLoop)}stop(){this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0),this.rvfcHandle&&this.mediaSource?.cancelVideoFrameCallback&&(this.mediaSource.cancelVideoFrameCallback(this.rvfcHandle),this.rvfcHandle=0),this.mediaSource=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=(t,e)=>{const n=this.mediaSource;if(!n||!n.requestVideoFrameCallback)return;this.rvfcHandle=n.requestVideoFrameCallback(this._videoFrameLoop);const i=e.mediaTime??n.currentTime;this.onDepthUpdate(i),this.onVideoFrame&&this.onVideoFrame(i,e.presentedFrames??0)};_handleContextLost=t=>{t.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()},at.RESIZE_DEBOUNCE_MS)};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}}clampDepthDimensions(t,e,n){this.sourceDepthWidth=t,this.sourceDepthHeight=e;let i=t,r=e;if(i>n||r>n){const s=n/Math.max(i,r);i=Math.max(1,Math.round(i*s)),r=Math.max(1,Math.round(r*s))}this.depthWidth=i,this.depthHeight=r,i!==t||r!==e?this.depthSubsampleBuffer=new Uint8Array(i*r):this.depthSubsampleBuffer=null}subsampleDepth(t){if(!this.depthSubsampleBuffer)return t;const e=this.depthSubsampleBuffer,n=this.sourceDepthWidth,i=this.depthWidth,r=this.depthHeight;for(let s=0;s<r;s++){const l=Math.min(Math.round(s*n/i),this.sourceDepthHeight-1)*n,u=s*i;for(let h=0;h<i;h++){const f=Math.min(Math.round(h*n/i),n-1);e[u+h]=t[l+f]}}return e}computeCoverFitUV(t,e){const{width:n,height:i}=this.getViewportSize(),r=n/i,s=t+e;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],this.isCameraSource&&(this.uvOffset[0]+=this.uvScale[0],this.uvScale[0]=-this.uvScale[0])}}const ge=`#version 300 es
4
4
  in vec2 aPosition;
5
5
 
6
6
  // UV coordinates for cover-fit + overscan.
@@ -16,12 +16,12 @@ void main() {
16
16
  // Map from clip space [-1,1] to [0,1], then apply cover-fit transform
17
17
  vec2 baseUv = aPosition * 0.5 + 0.5;
18
18
  vUv = baseUv * uUvScale + uUvOffset;
19
- // Screen-space UV always [0,1] -- used for vignette and edge fade
19
+ // Screen-space UV always [0,1] -- used for tilted focal plane and edge fade
20
20
  // which should operate on screen position, not texture coordinates.
21
21
  vScreenUv = baseUv;
22
22
  gl_Position = vec4(aPosition, 0.0, 1.0);
23
23
  }
24
- `,mt=`#version 300 es
24
+ `,ve=`#version 300 es
25
25
  in vec2 aPosition;
26
26
  out vec2 vUv;
27
27
 
@@ -29,7 +29,7 @@ void main() {
29
29
  vUv = aPosition * 0.5 + 0.5;
30
30
  gl_Position = vec4(aPosition, 0.0, 1.0);
31
31
  }
32
- `,gt=`#version 300 es
32
+ `,xe=`#version 300 es
33
33
  precision highp float;
34
34
 
35
35
  // BILATERAL_RADIUS is injected as a #define at compile time.
@@ -67,7 +67,7 @@ void main() {
67
67
 
68
68
  fragColor = vec4(totalDepth / totalWeight, 0.0, 0.0, 1.0);
69
69
  }
70
- `,vt=`#version 300 es
70
+ `,Te=`#version 300 es
71
71
  precision highp float;
72
72
 
73
73
  // ---- Uniforms ----
@@ -76,9 +76,13 @@ precision highp float;
76
76
  uniform sampler2D uImage;
77
77
 
78
78
  /**
79
- * Single-channel depth map (R channel, 0=near, 1=far).
79
+ * Single-channel depth map (R channel, 0=far, 1=near).
80
+ * Depth Anything v2 outputs higher=closer; no inversion is applied.
80
81
  * Bilateral-filtered on the GPU via a dedicated render pass,
81
82
  * so a single texture() read gives smooth, edge-preserving depth.
83
+ *
84
+ * DEPTH CONVENTION: 0.0 = farthest, 1.0 = nearest.
85
+ * See DEPTH_CONVENTION.md for the canonical reference.
82
86
  */
83
87
  uniform sampler2D uDepth;
84
88
 
@@ -118,6 +122,92 @@ uniform float uDofStrength;
118
122
  */
119
123
  uniform vec2 uImageTexelSize;
120
124
 
125
+ /**
126
+ * Displacement curve LUT — 256×1 R8 texture.
127
+ * Maps depth (0=far, 1=near) → displacement intensity [0,1].
128
+ * Replaces the hardcoded (1.0 - depth) linear ramp.
129
+ * When not provided, falls back to the legacy linear ramp.
130
+ */
131
+ uniform sampler2D uDisplacementCurve;
132
+
133
+ /**
134
+ * Blur curve LUT — 256×1 R8 texture.
135
+ * Maps depth → blur intensity [0,1].
136
+ * Replaces the hardcoded smoothstep(dofStart, 1.0, depth) * dofStrength ramp.
137
+ */
138
+ uniform sampler2D uBlurCurve;
139
+
140
+ /** Whether curve LUT textures are bound (0 = use legacy scalar math). */
141
+ uniform bool uCurvesEnabled;
142
+
143
+ /** Maximum blur radius in UV space, from blur channel params. */
144
+ uniform float uBlurRadius;
145
+
146
+ /** Per-frame depth offset for focal band shifting (0 = no shift). */
147
+ uniform float uFocalBandOffset;
148
+
149
+ /** Whether tilted focal plane blur is enabled (Scheimpflug simulation). */
150
+ uniform bool uTiltEnabled;
151
+
152
+ /** Precomputed tan(fov/2) for pseudo-world reconstruction. */
153
+ uniform float uTiltHalfTanFov;
154
+
155
+ /** Blur ramp width in pseudo-world-space units. */
156
+ uniform float uTiltTransitionWidth;
157
+
158
+ /** Maximum blur intensity for tilted plane DOF (0-1). */
159
+ uniform float uTiltPeakIntensity;
160
+
161
+ /** Tilted focal plane normal vector (computed from pitch/yaw on CPU). */
162
+ uniform vec3 uTiltPlaneNormal;
163
+
164
+ /** Tilted focal plane distance constant: dot(normal, focalPoint). */
165
+ uniform float uTiltPlaneD;
166
+
167
+ /**
168
+ * Glow curve LUT — 256×1 R8 texture.
169
+ * Maps depth → glow intensity [0,1].
170
+ * Near-camera objects typically have high glow intensity.
171
+ */
172
+ uniform sampler2D uGlowCurve;
173
+
174
+ /** Whether glow is enabled (has a glow channel with a non-zero curve). */
175
+ uniform bool uGlowEnabled;
176
+
177
+ /** Glow tint color [r, g, b]. */
178
+ uniform vec3 uGlowColor;
179
+
180
+ /** Glow spread radius in UV space. */
181
+ uniform float uGlowRadius;
182
+
183
+ /** Glow edge softness — controls blending of the glow mask. */
184
+ uniform float uGlowSoftness;
185
+
186
+ /**
187
+ * Color-shift curve LUT — 256×1 R8 texture.
188
+ * Maps depth → color-shift intensity [0,1].
189
+ * At 0: no color adjustment. At 1: full hue/sat/brightness/tint shift.
190
+ */
191
+ uniform sampler2D uColorShiftCurve;
192
+
193
+ /** Whether color-shift is enabled (has a color-shift channel). */
194
+ uniform bool uColorShiftEnabled;
195
+
196
+ /** Hue rotation in radians (converted from degrees on CPU). */
197
+ uniform float uColorShiftHue;
198
+
199
+ /** Saturation multiplier at full intensity. */
200
+ uniform float uColorShiftSaturation;
201
+
202
+ /** Brightness multiplier at full intensity. */
203
+ uniform float uColorShiftBrightness;
204
+
205
+ /** Tint blend strength at full intensity (0-1). */
206
+ uniform float uColorShiftTintStrength;
207
+
208
+ /** Tint target color [r, g, b]. */
209
+ uniform vec3 uColorShiftTintColor;
210
+
121
211
  // ---- Varyings ----
122
212
 
123
213
  /** Interpolated texture coordinates from vertex shader (cover-fit transformed). */
@@ -142,12 +232,29 @@ float edgeFade(vec2 uv) {
142
232
  return fadeX * fadeY;
143
233
  }
144
234
 
235
+ // ---- Color space helpers ----
236
+
237
+ /**
238
+ * RGB to HSV conversion.
239
+ * Returns vec3(hue [0-1], saturation [0-1], value [0-1]).
240
+ */
241
+ vec3 rgb2hsv(vec3 c) {
242
+ vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
243
+ vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
244
+ vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
245
+ float d = q.x - min(q.w, q.y);
246
+ float e = 1.0e-10;
247
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
248
+ }
249
+
145
250
  /**
146
- * Compute a subtle vignette darkening factor.
251
+ * HSV to RGB conversion.
252
+ * Input: vec3(hue [0-1], saturation [0-1], value [0-1]).
147
253
  */
148
- float vignette(vec2 uv) {
149
- float dist = length(uv - 0.5) * 1.4;
150
- return 1.0 - pow(dist, 2.5);
254
+ vec3 hsv2rgb(vec3 c) {
255
+ vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
256
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
257
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
151
258
  }
152
259
 
153
260
  // ---- Displacement functions ----
@@ -158,7 +265,14 @@ float vignette(vec2 uv) {
158
265
  vec2 basicDisplace(vec2 uv) {
159
266
  float depth = texture(uDepth, uv).r;
160
267
  depth = smoothstep(uContrastLow, uContrastHigh, depth);
161
- float displacement = (1.0 - depth) * uStrength;
268
+
269
+ float displacement;
270
+ if (uCurvesEnabled) {
271
+ displacement = texture(uDisplacementCurve, vec2(depth, 0.5)).r * uStrength;
272
+ } else {
273
+ displacement = (1.0 - depth) * uStrength;
274
+ }
275
+
162
276
  displacement *= edgeFade(uv);
163
277
  vec2 offset = uOffset * displacement;
164
278
  offset.y *= uVerticalReduction;
@@ -186,14 +300,28 @@ vec2 pomDisplace(vec2 uv) {
186
300
 
187
301
  float rawDepth = texture(uDepth, currentUV).r;
188
302
  rawDepth = smoothstep(uContrastLow, uContrastHigh, rawDepth);
189
- float depthAtUV = 1.0 - rawDepth;
303
+ float depthAtUV;
304
+ if (uCurvesEnabled) {
305
+ // Curve outputs displacement intensity (far=high, near=low with descending curve).
306
+ // POM needs a height value where 0=surface, 1=deep. The displacement intensity
307
+ // IS the height: far objects are "deep" (high displacement), near are "surface".
308
+ depthAtUV = texture(uDisplacementCurve, vec2(rawDepth, 0.5)).r;
309
+ } else {
310
+ depthAtUV = 1.0 - rawDepth;
311
+ }
190
312
 
191
313
  if (currentLayerDepth > depthAtUV) {
192
314
  vec2 prevUV = currentUV - deltaUV;
193
315
  float prevLayerDepth = currentLayerDepth - layerDepth;
316
+
194
317
  float prevRaw = texture(uDepth, prevUV).r;
195
318
  prevRaw = smoothstep(uContrastLow, uContrastHigh, prevRaw);
196
- float prevDepthAtUV = 1.0 - prevRaw;
319
+ float prevDepthAtUV;
320
+ if (uCurvesEnabled) {
321
+ prevDepthAtUV = texture(uDisplacementCurve, vec2(prevRaw, 0.5)).r;
322
+ } else {
323
+ prevDepthAtUV = 1.0 - prevRaw;
324
+ }
197
325
 
198
326
  float afterDepth = depthAtUV - currentLayerDepth;
199
327
  float beforeDepth = prevDepthAtUV - prevLayerDepth;
@@ -218,239 +346,127 @@ void main() {
218
346
 
219
347
  vec4 color = texture(uImage, displaced);
220
348
 
221
- // Depth-of-field hint
349
+ // Depth-of-field blur — three modes:
350
+ // 1. Tilted focal plane (Scheimpflug): 3D plane distance → CoC
351
+ // 2. Curve LUT: depth → LUT lookup → blur intensity
352
+ // 3. Legacy scalar: smoothstep ramp from dofStart
222
353
  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);
354
+ float blurIntensity;
355
+ if (uTiltEnabled) {
356
+ // Reconstruct pseudo-world position from depth + screen UV.
357
+ const float TILT_NEAR_Z = 0.5;
358
+ const float TILT_FAR_Z = 5.0;
359
+ float linearZ = mix(TILT_NEAR_Z, TILT_FAR_Z, dofDepth);
360
+ float aspect = uImageTexelSize.y / uImageTexelSize.x;
361
+ vec3 worldPos = vec3(
362
+ (vScreenUv.x - 0.5) * 2.0 * uTiltHalfTanFov * aspect * linearZ,
363
+ (vScreenUv.y - 0.5) * 2.0 * uTiltHalfTanFov * linearZ,
364
+ linearZ
365
+ );
366
+ float signedDist = dot(uTiltPlaneNormal, worldPos) - uTiltPlaneD;
367
+ blurIntensity = smoothstep(0.0, uTiltTransitionWidth, abs(signedDist)) * uTiltPeakIntensity;
368
+ } else if (uCurvesEnabled) {
369
+ float shiftedDepth = clamp(dofDepth + uFocalBandOffset, 0.0, 1.0);
370
+ blurIntensity = texture(uBlurCurve, vec2(shiftedDepth, 0.5)).r;
371
+ } else {
372
+ blurIntensity = smoothstep(uDofStart, 1.0, dofDepth) * uDofStrength;
373
+ }
349
374
 
350
- let hitUV = mix(currentUV, prevUV, t);
351
- return mix(uv, hitUV, fade);
375
+ if (blurIntensity > 0.001) {
376
+ float radius = blurIntensity * uBlurRadius;
377
+
378
+ // 13-tap Poisson disk — perceptually uniform distribution.
379
+ // Offsets are pre-normalized to unit disk; scaled by radius at runtime.
380
+ const vec2 poissonDisk[12] = vec2[12](
381
+ vec2(-0.326, -0.406),
382
+ vec2(-0.840, -0.074),
383
+ vec2(-0.696, 0.457),
384
+ vec2(-0.203, 0.621),
385
+ vec2( 0.962, -0.195),
386
+ vec2( 0.473, -0.480),
387
+ vec2( 0.519, 0.767),
388
+ vec2( 0.185, -0.893),
389
+ vec2( 0.507, 0.064),
390
+ vec2( 0.896, 0.412),
391
+ vec2(-0.322, -0.933),
392
+ vec2(-0.792, -0.598)
393
+ );
394
+
395
+ vec4 blurAccum = color; // center tap (weight 1)
396
+ for (int i = 0; i < 12; i++) {
397
+ vec2 sampleUV = displaced + poissonDisk[i] * radius;
398
+ sampleUV = clamp(sampleUV, vec2(0.0), vec2(1.0));
399
+ blurAccum += texture(uImage, sampleUV);
352
400
  }
401
+ blurAccum /= 13.0;
353
402
 
354
- currentUV += deltaUV;
355
- currentLayerDepth += layerD;
403
+ color = mix(color, blurAccum, blurIntensity);
356
404
  }
357
405
 
358
- return mix(uv, currentUV, fade);
359
- }
360
-
361
- // ---- Main ----
406
+ // ---- Glow pass ----
407
+ // Samples a glow curve LUT to determine per-pixel glow intensity,
408
+ // then spreads the glow to neighboring pixels via a small blur kernel.
409
+ // The glow is additive: it brightens pixels without replacing color.
410
+ if (uGlowEnabled) {
411
+ float glowDepth = texture(uDepth, displaced).r;
412
+ float glowMask = texture(uGlowCurve, vec2(glowDepth, 0.5)).r;
413
+
414
+ // Spread glow to neighbors for a soft halo effect
415
+ float spreadMask = 0.0;
416
+ float spreadSamples = 0.0;
417
+ for (int x = -3; x <= 3; x++) {
418
+ for (int y = -3; y <= 3; y++) {
419
+ vec2 sampleOffset = vec2(float(x), float(y)) * uImageTexelSize * uGlowRadius * 100.0;
420
+ float d = texture(uDepth, displaced + sampleOffset).r;
421
+ spreadMask += texture(uGlowCurve, vec2(d, 0.5)).r;
422
+ spreadSamples += 1.0;
423
+ }
424
+ }
425
+ spreadMask /= spreadSamples;
362
426
 
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);
427
+ // Combine direct glow mask with spread, apply softness
428
+ float finalGlow = mix(spreadMask * 0.5, glowMask, uGlowSoftness);
429
+ color.rgb += uGlowColor * finalGlow;
373
430
  }
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
431
 
418
- struct Uniforms {
419
- texelSize: vec2f,
420
- spatialSigma2: f32,
421
- };
432
+ // ---- Color-shift pass ----
433
+ // Applies depth-driven hue rotation, saturation/brightness adjustment,
434
+ // and optional tint blending. Intensity is driven by the color-shift
435
+ // curve LUT: 0 = no effect, 1 = full effect at that depth.
436
+ if (uColorShiftEnabled) {
437
+ float csDepth = texture(uDepth, displaced).r;
438
+ float csIntensity = texture(uColorShiftCurve, vec2(csDepth, 0.5)).r;
422
439
 
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;
440
+ if (csIntensity > 0.001) {
441
+ vec3 hsv = rgb2hsv(color.rgb);
426
442
 
427
- @fragment
428
- fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
429
- let depthSigma2: f32 = 0.01; // 0.1^2
443
+ // Hue rotation (uColorShiftHue is in radians, convert to 0-1 hue space)
444
+ float hueOffset = uColorShiftHue / 6.283185; // 2*PI
445
+ hsv.x = fract(hsv.x + hueOffset * csIntensity);
430
446
 
431
- let center = textureSampleLevel(rawDepthTex, rawDepthSampler, uv, 0.0).r;
432
- var totalWeight: f32 = 1.0;
433
- var totalDepth: f32 = center;
447
+ // Saturation: lerp multiplier from 1.0 toward target
448
+ float satMul = mix(1.0, uColorShiftSaturation, csIntensity);
449
+ hsv.y = clamp(hsv.y * satMul, 0.0, 1.0);
434
450
 
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; }
451
+ // Brightness: lerp multiplier from 1.0 toward target
452
+ float brightMul = mix(1.0, uColorShiftBrightness, csIntensity);
453
+ hsv.z = clamp(hsv.z * brightMul, 0.0, 1.0);
438
454
 
439
- let offset = vec2f(f32(dx), f32(dy)) * u.texelSize;
440
- let neighbor = textureSampleLevel(rawDepthTex, rawDepthSampler, uv + offset, 0.0).r;
455
+ vec3 shifted = hsv2rgb(hsv);
441
456
 
442
- let spatialDist2 = f32(dx * dx + dy * dy);
443
- let depthDiff = neighbor - center;
444
- let w = exp(-spatialDist2 / u.spatialSigma2 - (depthDiff * depthDiff) / depthSigma2);
457
+ // Tint: blend toward tint color
458
+ float tintAmount = uColorShiftTintStrength * csIntensity;
459
+ shifted = mix(shifted, uColorShiftTintColor, tintAmount);
445
460
 
446
- totalWeight += w;
447
- totalDepth += neighbor * w;
461
+ color.rgb = shifted;
448
462
  }
449
463
  }
450
464
 
451
- return vec4f(totalDepth / totalWeight, 0.0, 0.0, 1.0);
465
+ fragColor = color;
452
466
  }
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=`
467
+ `,I={contrastLow:.05,contrastHigh:.95,verticalReduction:.5,dofStart:.6,dofStrength:.4,blurRadius:.01,glowColor:[1,.95,.85],glowRadius:.02,glowSoftness:.6,tiltEnabled:!1,tiltHalfTanFov:Math.tan(50*Math.PI/360),tiltTransitionWidth:.3*4.5,tiltPeakIntensity:.8},Ee=["uRawDepth","uTexelSize","uSpatialSigma2"],be={2:2.25,1:.5625};function ye(o,t){const e=xe.replace("#version 300 es",`#version 300 es
468
+ #define BILATERAL_RADIUS ${t}`),n=z(o,o.VERTEX_SHADER,ve),i=z(o,o.FRAGMENT_SHADER,e),r=Et(o,n,i),s=bt(o,r,Ee),a=be[t]??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,E){l&&h.deleteFramebuffer(l),u.width=c,u.height=E,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/E),h.uniform1f(s.uSpatialSigma2,a)},execute(h,f,c,E,v,p,P,x){l&&(h.activeTexture(h.TEXTURE2),h.bindTexture(h.TEXTURE_2D,c),h.texSubImage2D(h.TEXTURE_2D,0,0,0,v,p,h.RED,h.UNSIGNED_BYTE,E),h.bindFramebuffer(h.FRAMEBUFFER,l),h.viewport(0,0,v,p),h.useProgram(r),h.bindVertexArray(f),h.drawArrays(h.TRIANGLE_STRIP,0,4),h.bindFramebuffer(h.FRAMEBUFFER,null),h.viewport(0,0,P,x))},dispose(h){l&&(h.deleteFramebuffer(l),l=null,u.fbo=null),h.deleteProgram(r)}};return u}const Se=["uImage","uDepth","uOffset","uStrength","uPomEnabled","uPomSteps","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uUvOffset","uUvScale","uDisplacementCurve","uBlurCurve","uCurvesEnabled","uBlurRadius","uFocalBandOffset","uGlowCurve","uGlowEnabled","uGlowColor","uGlowRadius","uGlowSoftness","uColorShiftCurve","uColorShiftEnabled","uColorShiftHue","uColorShiftSaturation","uColorShiftBrightness","uColorShiftTintStrength","uColorShiftTintColor","uTiltEnabled","uTiltHalfTanFov","uTiltTransitionWidth","uTiltPeakIntensity","uTiltPlaneNormal","uTiltPlaneD"],Ae=64;function we(o){const t=Te.replace("#version 300 es",`#version 300 es
469
+ #define MAX_POM_STEPS ${Ae}`),e=z(o,o.VERTEX_SHADER,ge),n=z(o,o.FRAGMENT_SHADER,t),i=Et(o,e,n),r=bt(o,i,Se);return{name:"depth-effect",program:i,uniforms:r,setStaticUniforms(s,a,l,u){s.useProgram(i),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),s.uniform1i(r.uDisplacementCurve,3),s.uniform1i(r.uBlurCurve,4),s.uniform1i(r.uCurvesEnabled,0),s.uniform1f(r.uBlurRadius,a.blurRadius),s.uniform1f(r.uFocalBandOffset,0),s.uniform1i(r.uGlowCurve,5),s.uniform1i(r.uGlowEnabled,0),s.uniform3f(r.uGlowColor,a.glowColor[0],a.glowColor[1],a.glowColor[2]),s.uniform1f(r.uGlowRadius,a.glowRadius),s.uniform1f(r.uGlowSoftness,a.glowSoftness),s.uniform1i(r.uColorShiftCurve,6),s.uniform1i(r.uColorShiftEnabled,0),s.uniform1f(r.uColorShiftHue,0),s.uniform1f(r.uColorShiftSaturation,1),s.uniform1f(r.uColorShiftBrightness,1),s.uniform1f(r.uColorShiftTintStrength,0),s.uniform3f(r.uColorShiftTintColor,.7,.8,.9),s.uniform1i(r.uTiltEnabled,a.tiltEnabled?1:0),s.uniform1f(r.uTiltHalfTanFov,a.tiltHalfTanFov),s.uniform1f(r.uTiltTransitionWidth,a.tiltTransitionWidth),s.uniform1f(r.uTiltPeakIntensity,a.tiltPeakIntensity),s.uniform3f(r.uTiltPlaneNormal,0,0,1),s.uniform1f(r.uTiltPlaneD,2.75)},updateUvTransform(s,a,l){s.useProgram(i),s.uniform2f(r.uUvOffset,a[0],a[1]),s.uniform2f(r.uUvScale,l[0],l[1])},dispose(s){s.deleteProgram(i)}}}class Re extends at{gl=null;quadVao=null;bilateralPass=null;effectPass=null;textures=new Mt;videoSlot;filteredDepthSlot;rawDepthSlot;displacementLutSlot;blurLutSlot;glowLutSlot;colorShiftLutSlot;config;constructor(t,e){super(t),this.videoSlot=this.textures.register("video"),this.filteredDepthSlot=this.textures.register("filteredDepth"),this.rawDepthSlot=this.textures.register("rawDepth"),this.displacementLutSlot=this.textures.register("displacementLut"),this.blurLutSlot=this.textures.register("blurLut"),this.glowLutSlot=this.textures.register("glowLut"),this.colorShiftLutSlot=this.textures.register("colorShiftLut"),this.config={parallaxStrength:e.parallaxStrength,pomEnabled:e.pomEnabled,pomSteps:e.pomSteps,overscanPadding:e.overscanPadding,contrastLow:e.contrastLow??I.contrastLow,contrastHigh:e.contrastHigh??I.contrastHigh,verticalReduction:e.verticalReduction??I.verticalReduction,dofStart:e.dofStart??I.dofStart,dofStrength:e.dofStrength??I.dofStrength,blurRadius:e.blurRadius??I.blurRadius,glowColor:e.glowColor??[...I.glowColor],glowRadius:e.glowRadius??I.glowRadius,glowSoftness:e.glowSoftness??I.glowSoftness,tiltEnabled:e.tiltEnabled??I.tiltEnabled,tiltHalfTanFov:e.tiltHalfTanFov??I.tiltHalfTanFov,tiltTransitionWidth:e.tiltTransitionWidth??I.tiltTransitionWidth,tiltPeakIntensity:e.tiltPeakIntensity??I.tiltPeakIntensity};const n=this.canvas.getContext("webgl2",{antialias:!1,alpha:!1,desynchronized:!0,powerPreference:"high-performance"});if(!n)throw new Error("WebGL 2 is not supported.");this.gl=n,this.qualityParams=It(n,e.quality),"drawingBufferColorSpace"in n&&(n.drawingBufferColorSpace="srgb"),n.clearColor(0,0,0,1),n.pixelStorei(n.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.setupResizeHandling()}initialize(t,e,n){const i=this.gl;i&&(this.disposeTextures(),this.isCameraSource=t.type==="camera",this.videoAspect=t.width/t.height,this.clampDepthDimensions(e,n,this.qualityParams.depthMaxDim),this.videoSlot.texture=i.createTexture(),i.activeTexture(i.TEXTURE0+this.videoSlot.unit),i.bindTexture(i.TEXTURE_2D,this.videoSlot.texture),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),this.rawDepthSlot.texture=i.createTexture(),i.activeTexture(i.TEXTURE0+this.rawDepthSlot.unit),i.bindTexture(i.TEXTURE_2D,this.rawDepthSlot.texture),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.texStorage2D(i.TEXTURE_2D,1,i.R8,e,n),this.filteredDepthSlot.texture=i.createTexture(),i.activeTexture(i.TEXTURE0+this.filteredDepthSlot.unit),i.bindTexture(i.TEXTURE_2D,this.filteredDepthSlot.texture),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.texStorage2D(i.TEXTURE_2D,1,i.R8,e,n),this.bilateralPass&&this.filteredDepthSlot.texture&&this.bilateralPass.initFBO(i,this.filteredDepthSlot.texture,e,n),this.effectPass&&this.effectPass.setStaticUniforms(i,this.config,t.width,t.height),this.recalculateViewportLayout())}updateConfig(t){const e=this.gl;if(!e||!this.effectPass)return;t.parallaxStrength!==void 0&&(this.config.parallaxStrength=t.parallaxStrength),t.pomEnabled!==void 0&&(this.config.pomEnabled=t.pomEnabled),t.pomSteps!==void 0&&(this.config.pomSteps=t.pomSteps),t.overscanPadding!==void 0&&(this.config.overscanPadding=t.overscanPadding),t.contrastLow!==void 0&&(this.config.contrastLow=t.contrastLow),t.contrastHigh!==void 0&&(this.config.contrastHigh=t.contrastHigh),t.verticalReduction!==void 0&&(this.config.verticalReduction=t.verticalReduction),t.dofStart!==void 0&&(this.config.dofStart=t.dofStart),t.dofStrength!==void 0&&(this.config.dofStrength=t.dofStrength),t.blurRadius!==void 0&&(this.config.blurRadius=t.blurRadius),t.glowColor!==void 0&&(this.config.glowColor=t.glowColor),t.glowRadius!==void 0&&(this.config.glowRadius=t.glowRadius),t.glowSoftness!==void 0&&(this.config.glowSoftness=t.glowSoftness),t.tiltEnabled!==void 0&&(this.config.tiltEnabled=t.tiltEnabled),t.tiltHalfTanFov!==void 0&&(this.config.tiltHalfTanFov=t.tiltHalfTanFov),t.tiltTransitionWidth!==void 0&&(this.config.tiltTransitionWidth=t.tiltTransitionWidth),t.tiltPeakIntensity!==void 0&&(this.config.tiltPeakIntensity=t.tiltPeakIntensity);const{uniforms:n,program:i}=this.effectPass;e.useProgram(i),e.uniform1f(n.uStrength,this.config.parallaxStrength),e.uniform1i(n.uPomEnabled,this.config.pomEnabled?1:0),e.uniform1i(n.uPomSteps,this.config.pomSteps),e.uniform1f(n.uContrastLow,this.config.contrastLow),e.uniform1f(n.uContrastHigh,this.config.contrastHigh),e.uniform1f(n.uVerticalReduction,this.config.verticalReduction),e.uniform1f(n.uDofStart,this.config.dofStart),e.uniform1f(n.uDofStrength,this.config.dofStrength),e.uniform1f(n.uBlurRadius,this.config.blurRadius),e.uniform3f(n.uGlowColor,this.config.glowColor[0],this.config.glowColor[1],this.config.glowColor[2]),e.uniform1f(n.uGlowRadius,this.config.glowRadius),e.uniform1f(n.uGlowSoftness,this.config.glowSoftness),e.uniform1i(n.uTiltEnabled,this.config.tiltEnabled?1:0),e.uniform1f(n.uTiltHalfTanFov,this.config.tiltHalfTanFov),e.uniform1f(n.uTiltTransitionWidth,this.config.tiltTransitionWidth),e.uniform1f(n.uTiltPeakIntensity,this.config.tiltPeakIntensity),(t.parallaxStrength!==void 0||t.overscanPadding!==void 0)&&this.recalculateViewportLayout()}updateCurveLUTs(t,e,n,i,r){const s=this.gl;if(!s||!this.effectPass)return;const a=!!(t||e),l=!!n,u=!!i;if(t&&this.uploadLUT(this.displacementLutSlot,t),e&&this.uploadLUT(this.blurLutSlot,e),n&&this.uploadLUT(this.glowLutSlot,n),i&&this.uploadLUT(this.colorShiftLutSlot,i),s.useProgram(this.effectPass.program),s.uniform1i(this.effectPass.uniforms.uCurvesEnabled,a?1:0),s.uniform1i(this.effectPass.uniforms.uGlowEnabled,l?1:0),s.uniform1i(this.effectPass.uniforms.uColorShiftEnabled,u?1:0),u&&r){const h=this.effectPass.uniforms;s.uniform1f(h.uColorShiftHue,r.hueShift*(Math.PI/180)),s.uniform1f(h.uColorShiftSaturation,r.saturation),s.uniform1f(h.uColorShiftBrightness,r.brightness),s.uniform1f(h.uColorShiftTintStrength,r.tintStrength),s.uniform3f(h.uColorShiftTintColor,r.tintColor[0],r.tintColor[1],r.tintColor[2])}}initGPUResources(){const t=this.gl;t&&(this.bilateralPass=ye(t,this.qualityParams.bilateralRadius),this.effectPass=we(t),this.quadVao=_t(t,this.effectPass.program),t.disable(t.DEPTH_TEST))}uploadLUT(t,e){const n=this.gl;n&&(n.activeTexture(n.TEXTURE0+t.unit),t.texture?n.bindTexture(n.TEXTURE_2D,t.texture):(t.texture=n.createTexture(),n.bindTexture(n.TEXTURE_2D,t.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,256,1)),n.texSubImage2D(n.TEXTURE_2D,0,0,0,256,1,n.RED,n.UNSIGNED_BYTE,e))}onRenderFrame(){const t=this.gl,e=this.mediaSource;if(!t||!this.effectPass||!this.quadVao)return;const n=e?.getImageSource();if(n){if(!this.rvfcSupported&&e.isLive&&this.onDepthUpdate(e.currentTime),t.useProgram(this.effectPass.program),t.activeTexture(t.TEXTURE0+this.videoSlot.unit),t.bindTexture(t.TEXTURE_2D,this.videoSlot.texture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,n),this.readInput){const i=this.readInput();t.uniform2f(this.effectPass.uniforms.uOffset,-i.x,i.y),i.focalBandOffset!==void 0&&t.uniform1f(this.effectPass.uniforms.uFocalBandOffset,i.focalBandOffset),i.tiltPlaneNormal!==void 0&&t.uniform3f(this.effectPass.uniforms.uTiltPlaneNormal,i.tiltPlaneNormal[0],i.tiltPlaneNormal[1],i.tiltPlaneNormal[2]),i.tiltPlaneD!==void 0&&t.uniform1f(this.effectPass.uniforms.uTiltPlaneD,i.tiltPlaneD)}t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4)}}onDepthUpdate(t){const e=this.gl;if(!e||!this.readDepth||!this.rawDepthSlot.texture||!this.bilateralPass)return;const n=this.subsampleDepth(this.readDepth(t));this.bilateralPass.execute(e,this.quadVao,this.rawDepthSlot.texture,n,this.depthWidth,this.depthHeight,this.canvas.width,this.canvas.height)}recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:n}=this.getViewportSize(),i=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),r=Math.round(e*i),s=Math.round(n*i);(this.canvas.width!==r||this.canvas.height!==s)&&(this.canvas.width=r,this.canvas.height=s,t.viewport(0,0,r,s)),this.computeCoverFitUV(this.config.parallaxStrength,this.config.overscanPadding),this.effectPass&&this.effectPass.updateUvTransform(t,this.uvOffset,this.uvScale)}disposeRenderer(){this.disposeTextures(),this.disposeGPUResources(),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null)}onContextRestored(){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.mediaSource&&this.depthWidth>0&&this.initialize(this.mediaSource,this.depthWidth,this.depthHeight),this.mediaSource&&(this.animationFrameHandle=window.requestAnimationFrame(()=>this.onRenderFrame())))}disposeTextures(){const t=this.gl;t&&this.textures.disposeAll(t)}disposeGPUResources(){const t=this.gl;t&&(this.bilateralPass&&(this.bilateralPass.dispose(t),this.bilateralPass=null),this.effectPass&&(this.effectPass.dispose(t),this.effectPass=null),this.quadVao&&(t.deleteVertexArray(this.quadVao),this.quadVao=null))}}async function lt(o,t={}){const{parent:e=document.body,loop:n=!0,muted:i=!0}=t,r=document.createElement("video");return r.crossOrigin="anonymous",r.setAttribute("crossorigin","anonymous"),r.playsInline=!0,r.setAttribute("playsinline",""),r.setAttribute("webkit-playsinline","true"),r.muted=i,r.defaultMuted=i,i&&r.setAttribute("muted",""),r.loop=n,r.preload="auto",r.style.display="none",r.src=o,e.appendChild(r),await Vt(r),new De(r)}class De{constructor(t){this.video=t}type="video";isLive=!0;get width(){return this.video.videoWidth}get height(){return this.video.videoHeight}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get paused(){return this.video.paused}getImageSource(){return this.video.readyState<HTMLMediaElement.HAVE_CURRENT_DATA?null:this.video}requestVideoFrameCallback(t){return this.video.requestVideoFrameCallback(t)}cancelVideoFrameCallback(t){this.video.cancelVideoFrameCallback(t)}play(){return this.video.play()}pause(){this.video.pause()}addEventListener(t,e){this.video.addEventListener(t,e)}removeEventListener(t,e){this.video.removeEventListener(t,e)}dispose(){this.video.pause(),this.video.removeAttribute("src"),this.video.load(),this.video.remove()}}async function ht(o,t={}){const e=new Image;return e.crossOrigin="anonymous",e.src=o,await new Promise((n,i)=>{if(e.complete&&e.naturalWidth>0){n();return}e.addEventListener("load",()=>n(),{once:!0}),e.addEventListener("error",()=>i(new Error(`Failed to load image: ${o}`)),{once:!0})}),new Pe(e)}class Pe{constructor(t){this.img=t}type="image";isLive=!1;currentTime=0;duration=0;paused=!1;get width(){return this.img.naturalWidth}get height(){return this.img.naturalHeight}getImageSource(){return this.img}dispose(){this.img.removeAttribute("src")}}async function Bt(o={video:!0},t={}){const{parent:e=document.body}=t,n=await navigator.mediaDevices.getUserMedia(o),i=document.createElement("video");return i.playsInline=!0,i.setAttribute("playsinline",""),i.muted=!0,i.defaultMuted=!0,i.style.display="none",i.srcObject=n,e.appendChild(i),await Vt(i),await i.play(),new Fe(i,n)}class Fe{constructor(t,e){this.video=t,this.stream=e}type="camera";isLive=!0;duration=1/0;get width(){return this.video.videoWidth}get height(){return this.video.videoHeight}get currentTime(){return this.video.currentTime}get paused(){return this.video.paused}getImageSource(){return this.video.readyState<HTMLMediaElement.HAVE_CURRENT_DATA?null:this.video}requestVideoFrameCallback(t){return this.video.requestVideoFrameCallback(t)}cancelVideoFrameCallback(t){this.video.cancelVideoFrameCallback(t)}play(){return this.video.play()}pause(){this.video.pause()}addEventListener(t,e){this.video.addEventListener(t,e)}removeEventListener(t,e){this.video.removeEventListener(t,e)}dispose(){this.video.pause(),this.video.srcObject=null,this.video.remove();for(const t of this.stream.getTracks())t.stop()}}async function Vt(o){o.readyState>=HTMLMediaElement.HAVE_METADATA||await new Promise((t,e)=>{const n=()=>{r(),t()},i=()=>{r(),e(new Error("Failed to load video metadata."))},r=()=>{o.removeEventListener("loadedmetadata",n),o.removeEventListener("error",i)};o.addEventListener("loadedmetadata",n),o.addEventListener("error",i),o.load()})}const yt=[.485,.456,.406],St=[.229,.224,.225],q=518;async function Ce(){return await import("onnxruntime-web/webgpu")}class Le{constructor(t,e){this.depthWidth=t,this.depthHeight=e;const n=t*e;this.frontBuffer=new Uint8Array(n),this.frontBuffer.fill(128),this.backBuffer=new Uint8Array(n),this.backBuffer.fill(128),this.readyPromise=new Promise(i=>{this.readyResolve=i})}ort=null;session=null;inputName="";outputName="";frontBuffer;backBuffer;inferenceInFlight=!1;readyResolve=null;readyPromise;captureCanvas=null;captureCtx=null;disposed=!1;async init(t,e){const n=await Ce();if(this.ort=n,this.captureCanvas=document.createElement("canvas"),this.captureCanvas.width=q,this.captureCanvas.height=q,this.captureCtx=this.captureCanvas.getContext("2d",{willReadFrequently:!0}),!this.captureCtx)throw new Error("[DepthEstimator] Failed to create 2D canvas context.");e?.({receivedBytes:0,totalBytes:null,fraction:0,label:"Downloading depth model…"});const i=await Ue(t,e);e?.({receivedBytes:i.byteLength,totalBytes:i.byteLength,fraction:1,label:"Initialising depth model…"});let r;try{r=await n.InferenceSession.create(i,{executionProviders:["webgpu"]}),console.log("[DepthEstimator] Using WebGPU execution provider")}catch(s){console.warn("[DepthEstimator] WebGPU EP unavailable, falling back to WASM:",s),n.env.wasm.proxy=!0,r=await n.InferenceSession.create(i,{executionProviders:["wasm"]}),console.log("[DepthEstimator] Using WASM execution provider (proxy worker)")}this.session=r,this.inputName=r.inputNames[0],this.outputName=r.outputNames[0],this.readyResolve?.(),this.readyResolve=null}waitUntilReady(){return this.readyPromise}submitFrame(t){this.inferenceInFlight||!this.session||this.disposed||(this.inferenceInFlight=!0,this.runInference(t))}async submitFrameAndWait(t){return!this.session||this.disposed?this.frontBuffer:(await this.runInference(t),this.frontBuffer)}getLatestDepth(){return this.frontBuffer}dispose(){this.disposed=!0,this.session?.release(),this.session=null,this.ort=null,this.captureCanvas=null,this.captureCtx=null}async runInference(t){try{if(!this.session||!this.captureCtx||!this.ort)return;this.captureCtx.drawImage(t,0,0,q,q);const e=this.captureCtx.getImageData(0,0,q,q),n=this.preprocess(e),r=(await this.session.run({[this.inputName]:n}))[this.outputName],s=r.data,a=r.dims,l=a.length===3?a[1]:a[2],u=a.length===3?a[2]:a[3];this.postProcess(s,u,l);const h=this.frontBuffer;this.frontBuffer=this.backBuffer,this.backBuffer=h}catch(e){console.error("[DepthEstimator] Inference failed:",e)}finally{this.inferenceInFlight=!1}}preprocess(t){const{data:e,width:n,height:i}=t,r=n*i,s=new Float32Array(3*r);for(let a=0;a<r;a++){const l=a*4;s[a]=(e[l]/255-yt[0])/St[0],s[r+a]=(e[l+1]/255-yt[1])/St[1],s[2*r+a]=(e[l+2]/255-yt[2])/St[2]}return new this.ort.Tensor("float32",s,[1,3,i,n])}postProcess(t,e,n){const{depthWidth:i,depthHeight:r}=this;let s=1/0,a=-1/0;for(let f=0;f<t.length;f++){const c=t[f];c<s&&(s=c),c>a&&(a=c)}const l=a-s||1,u=e/i,h=n/r;for(let f=0;f<r;f++)for(let c=0;c<i;c++){const E=c*u,v=f*h,p=Math.floor(E),P=Math.floor(v),x=Math.min(p+1,e-1),y=Math.min(P+1,n-1),d=E-p,g=v-P,b=t[P*e+p],T=t[P*e+x],m=t[y*e+p],S=t[y*e+x],F=(b*(1-d)*(1-g)+T*d*(1-g)+m*(1-d)*g+S*d*g-s)/l;this.backBuffer[f*i+c]=F*255+.5|0}}}async function ut(o,t,e,n){const i=new Le(t,e);return await i.init(o,n),i}async function Ue(o,t){const e=await fetch(o);if(!e.ok)throw new Error(`[DepthEstimator] Failed to fetch model (${e.status} ${e.statusText}).`);const n=e.headers.get("content-length"),i=n?Number(n):null,r=e.body;if(!r){const f=await e.arrayBuffer();return t?.({receivedBytes:f.byteLength,totalBytes:f.byteLength,fraction:1,label:"Downloading depth model…"}),f}const s=[];let a=0;const l=r.getReader();for(;;){const{done:f,value:c}=await l.read();if(f)break;c&&(s.push(c),a+=c.byteLength,t?.({receivedBytes:a,totalBytes:i,fraction:i?Math.min(a/i,1):0,label:"Downloading depth model…"}))}const u=new Uint8Array(a);let h=0;for(const f of s)u.set(f,h),h+=f.byteLength;return u.buffer}const Ot={sensitivityX:.4,sensitivityY:1,lerpFactor:.08};function At(o,t){const e=o.points;if(e.length===0)return 0;if(e.length===1||t<=e[0].x)return e[0].y;if(t>=e[e.length-1].x)return e[e.length-1].y;let n=0;for(;n<e.length-1&&e[n+1].x<t;)n++;const i=e[n],r=e[n+1],s=(t-i.x)/(r.x-i.x);switch(o.interpolation){case"step":return i.y;case"linear":return i.y+(r.y-i.y)*s;case"smooth":{const a=s*s*(3-2*s);return i.y+(r.y-i.y)*a}default:return i.y+(r.y-i.y)*s}}function ct(o,t=256){const e=new Uint8Array(t);for(let n=0;n<t;n++){const i=n/(t-1);e[n]=Math.round(At(o,i)*255)}return e}function X(o,t){return o.find(e=>e.channel===t&&e.enabled)}function kt(o,t){const e=X(o,"displacement"),n=X(o,"blur"),i=X(o,"glow"),r=e?.params;let s=.6,a=0;if(n){const l=n.curve;for(let u=0;u<=1;u+=.01)if(At(l,u)>.01){s=u;break}a=At(l,1)}return{parallaxStrength:r?.strength??.05,pomEnabled:r?.pomEnabled??!0,pomSteps:r?.pomSteps??16,contrastLow:t.contrastLow,contrastHigh:t.contrastHigh,verticalReduction:t.verticalReduction,dofStart:s,dofStrength:a,blurRadius:n?.params?.maxRadius??.01,glowColor:i?.params?.color??[1,.95,.85],glowRadius:i?.params?.radius??.02,glowSoftness:i?.params?.softness??.6,tiltEnabled:n?.params?.tiltEnabled??!1,tiltHalfTanFov:Math.tan((n?.params?.tiltFov??50)*Math.PI/360),tiltTransitionWidth:(n?.params?.focalWidth??.3)*4.5,tiltPeakIntensity:n?.params?.peakIntensity??.8}}function Nt(o){const t=X(o,"displacement"),e=X(o,"blur"),n=X(o,"glow"),i=X(o,"color-shift"),r=i?.params;return{displacementLUT:t?ct(t.curve):null,blurLUT:e?ct(e.curve):null,glowLUT:n?ct(n.curve):null,colorShiftLUT:i?ct(i.curve):null,colorShiftParams:r?{hueShift:r.hueShift??0,saturation:r.saturation??1,brightness:r.brightness??1,tintStrength:r.tintStrength??0,tintColor:r.tintColor??[.7,.8,.9]}:null}}class Ht{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,n){this.element.reinitAttributes.includes(t)&&e!==n&&(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;if(t.canInit){if(!t.canInit())return}else for(const n of t.reinitAttributes)if(!t.getAttribute(n))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 G={parallaxX:.4,parallaxY:1,parallaxMax:30,overscan:.05,autoplay:!0,loop:!0,muted:!0},Y=512,j=512;let _e=class Qt{constructor(t,e=.08,n=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=n,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=dt(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=dt(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(),n=(t.clientX-e.left)/e.width*2-1,i=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=Z(n,-1,1),this.pointerTarget.y=Z(i,-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 n=e.clientX-this.touchAnchorX,i=e.clientY-this.touchAnchorY,r=Qt.TOUCH_DRAG_RANGE;this.pointerTarget.x=Z(n/r,-1,1),this.pointerTarget.y=Z(i/r,-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=Z((t.gamma??0)/45,-1,1),n=Z((t.beta??0)/45,-1,1);this.motionTarget.x=dt(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=dt(this.motionTarget.y,n,this.motionLerpFactor)}};class ft extends HTMLElement{static TAG_NAME="layershift-effect";static get observedAttributes(){return["src","depth-src","depth-meta","depth-model","source-type","config","parallax-x","parallax-y","parallax-max","layers","overscan","quality","gpu-backend","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta","depth-model","source-type","config"];canInit(){if(this.sourceType==="camera")return!0;const t=!!this.getAttribute("src"),e=!!this.getAttribute("depth-src")&&!!this.getAttribute("depth-meta"),n=!!this.getAttribute("depth-model");return t&&(e||n)}shadow;container=null;renderer=null;inputHandler=null;source=null;depthEstimator=null;loopCount=0;lifecycle;depthFallback=null;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Ht(this)}getAttrFloat(t,e){const n=this.getAttribute(t);if(n===null)return e;const i=parseFloat(n);return Number.isFinite(i)?i:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const n=this.getAttribute(t);return!(n==="false"||n==="0")}get parallaxX(){return this.getAttrFloat("parallax-x",G.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",G.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",G.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",G.overscan)}get quality(){const t=this.getAttribute("quality");if(t==="auto"||t==="high"||t==="medium"||t==="low")return t}get gpuBackend(){return"webgl2"}get sourceType(){const t=this.getAttribute("source-type");return t==="camera"?"camera":t==="image"?"image":"video"}get depthModel(){return this.getAttribute("depth-model")}get shouldAutoplay(){return this.getAttrBool("autoplay",G.autoplay)}get shouldLoop(){return this.getAttrBool("loop",G.loop)}get shouldMute(){return this.getAttrBool("muted",G.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachSourceEventListeners(t){t.addEventListener&&(t.addEventListener("play",(()=>{this.emit("layershift-effect:play",{currentTime:t.currentTime})})),t.addEventListener("pause",(()=>{this.emit("layershift-effect:pause",{currentTime:t.currentTime})})),t.addEventListener("ended",(()=>{this.loopCount+=1,this.emit("layershift-effect:loop",{loopCount:this.loopCount})})))}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,n){this.lifecycle.onAttributeChanged(t,e,n)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
454
470
  :host {
455
471
  display: block;
456
472
  width: 100%;
@@ -470,34 +486,34 @@ fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
470
486
  width: 100%;
471
487
  height: 100%;
472
488
  }
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
489
+ `,this.shadow.appendChild(t),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async fetchFilterConfig(t){const e=this.getAttribute("config");if(!e)return null;try{const n=await fetch(e,{signal:t});if(!n.ok)return console.warn(`<layershift-effect>: Failed to fetch config from "${e}" (${n.status})`),null;const i=await n.json();return{channels:i.channels??[],motion:i.motion??Ot,overscanPadding:i.overscanPadding??.05,quality:i.quality??"auto"}}catch(n){return n.name==="AbortError"||console.warn("<layershift-effect>: Failed to parse config.",n),null}}buildLegacyConfig(t,e){const n=this.container?.clientWidth||e,i=this.hasAttribute("parallax-max")?this.parallaxMax/Math.max(n,1):t.parallaxStrength,r=this.hasAttribute("overscan")?this.overscan:t.overscanPadding;return{parallaxStrength:i,pomEnabled:!0,pomSteps:t.pomSteps,overscanPadding:r,quality:this.quality,contrastLow:t.contrastLow,contrastHigh:t.contrastHigh,verticalReduction:t.verticalReduction,dofStart:t.dofStart,dofStrength:t.dofStrength}}async doInit(t){if(!this.container)return;const e=this.sourceType==="camera",n=this.depthModel;try{let i,r,s=null;const a=m=>{this.emit("layershift-effect:model-progress",m)};if(e){if(i=await Bt({video:{facingMode:"user"}},{parent:this.shadow}),t.aborted){i.dispose();return}if(n){if(s=await ut(n,Y,j,a),t.aborted){s.dispose(),i.dispose();return}r=N(Y,j)}else r=N(i.width,i.height)}else{const m=this.getAttribute("src"),S=this.getAttribute("depth-src"),w=this.getAttribute("depth-meta"),F=!!S&&!!w,U=this.sourceType==="image"||/\.(jpe?g|png|webp|gif|avif|bmp)(\?|$)/i.test(m);if(F){const[_,R]=await Promise.all([U?ht(m):lt(m,{parent:this.shadow,loop:this.shouldLoop,muted:this.shouldMute}),Lt(S,w)]);if(t.aborted){_.dispose();return}i=_,r=R}else if(n){const[_,R]=await Promise.all([U?ht(m):lt(m,{parent:this.shadow,loop:this.shouldLoop,muted:this.shouldMute}),ut(n,Y,j,a)]);if(t.aborted){_.dispose(),R.dispose();return}if(i=_,s=R,U||!i.isLive){const D=i.getImageSource();if(D){const L=await s.submitFrameAndWait(D);r={meta:{frameCount:1,fps:1,width:Y,height:j,sourceFps:1},frames:[L]}}else r=N(Y,j)}else r=N(Y,j)}else throw new Error("Either depth-src/depth-meta or depth-model must be provided.")}this.source=i,this.depthEstimator=s,this.loopCount=0,this.attachSourceEventListeners(i);const l=se(r.frames,r.meta.width,r.meta.height),u=ae(l);this.depthFallback={contrastLow:u.contrastLow,contrastHigh:u.contrastHigh,verticalReduction:u.verticalReduction};let h;if(s)h=()=>s.getLatestDepth();else{const m=new Ct(r);h=S=>m.sample(S)}if(t.aborted)return;let f,c=null,E=Ot;const v=await this.fetchFilterConfig(t);if(t.aborted)return;if(v&&v.channels.length>0){const m=kt(v.channels,{contrastLow:u.contrastLow,contrastHigh:u.contrastHigh,verticalReduction:u.verticalReduction});f={parallaxStrength:m.parallaxStrength,pomEnabled:m.pomEnabled,pomSteps:m.pomSteps,overscanPadding:v.overscanPadding,quality:v.quality,contrastLow:m.contrastLow,contrastHigh:m.contrastHigh,verticalReduction:m.verticalReduction,dofStart:m.dofStart,dofStrength:m.dofStrength,blurRadius:m.blurRadius,glowColor:m.glowColor,glowRadius:m.glowRadius,glowSoftness:m.glowSoftness,tiltEnabled:m.tiltEnabled,tiltHalfTanFov:m.tiltHalfTanFov,tiltTransitionWidth:m.tiltTransitionWidth,tiltPeakIntensity:m.tiltPeakIntensity},c=Nt(v.channels),E=v.motion}else f=this.buildLegacyConfig(u,i.width);this.renderer=new Re(this.container,f),this.renderer.initialize(i,r.meta.width,r.meta.height),c&&this.renderer.updateCurveLUTs(c.displacementLUT,c.blurLUT,c.glowLUT,c.colorShiftLUT,c.colorShiftParams??void 0),this.inputHandler=new _e(this,E.lerpFactor);const p=E.sensitivityX,P=E.sensitivityY,x=E.tiltPlaneInput??!1,y=E.tiltPitchSensitivity??.35,d=E.tiltYawSensitivity??.15,b=v?.channels.find(m=>m.channel==="blur"&&m.enabled)?.params?.focalCenter??.5,T=s;if(this.renderer.start(i,h,()=>{if(!this.inputHandler)return{x:0,y:0};const m=this.inputHandler.update();if(x){const F=m.y*P*y,U=m.x*p*d,_=Math.cos(F),R=Math.sin(F),D=Math.cos(U),M=Math.sin(U)*_,V=-R,B=D*_,H=.5+b*(5-.5);return{x:m.x*p*.3,y:m.y*P*.3,tiltPlaneNormal:[M,V,B],tiltPlaneD:B*H}}return{x:m.x*p,y:m.y*P}},(m,S)=>{if(T){const w=i.getImageSource();w&&T.submitFrame(w)}this.emit("layershift-effect:frame",{currentTime:m,frameNumber:S})}),!e&&i.isLive&&this.shouldAutoplay&&i.play)try{await i.play()}catch{}if(t.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-effect:ready",{videoWidth:i.width,videoHeight:i.height,duration:i.duration,depthProfile:l,derivedParams:u})}catch(i){const r=i instanceof Error?i.message:"Failed to initialize.";console.error("<layershift-effect>: Failed to initialize.",i),this.emit("layershift-effect:error",{message:r})}}updateConfig(t){if(!this.renderer)return;const e=this.depthFallback??{contrastLow:.05,contrastHigh:.95,verticalReduction:.5},n=kt(t,e);this.renderer.updateConfig(n);const i=Nt(t);this.renderer.updateCurveLUTs(i.displacementLUT,i.blurLUT,i.glowLUT,i.colorShiftLUT,i.colorShiftParams??void 0)}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.depthEstimator?.dispose(),this.depthEstimator=null,this.source?.dispose(),this.source=null,this.depthFallback=null,this.loopCount=0,this.container=null}}function Z(o,t,e){return Math.min(e,Math.max(t,o))}function dt(o,t,e){return o+(t-o)*e}class wt{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(t,e){this.gl=t,this.hasColorBufferFloat=e}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(t,e,n){const i=this.gl;this.dispose();const r=Math.max(1,Math.round(t/n)),s=Math.max(1,Math.round(e/n));this._width=r,this._height=s;const a=(u,h,f,c)=>{const E=i.createFramebuffer();return i.bindFramebuffer(i.FRAMEBUFFER,E),i.bindTexture(i.TEXTURE_2D,u),i.texStorage2D(i.TEXTURE_2D,1,h,f,c),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,u,0),i.bindFramebuffer(i.FRAMEBUFFER,null),E};this.maskTex=i.createTexture(),this.maskFbo=a(this.maskTex,i.R8,r,s);const l=this.hasColorBufferFloat?i.RG16F:i.RGBA8;this.pingTex=i.createTexture(),this.pingFbo=a(this.pingTex,l,r,s),this.pongTex=i.createTexture(),this.pongFbo=a(this.pongTex,l,r,s),this.distTex=i.createTexture(),this.distFbo=a(this.distTex,i.RGBA8,r,s),this._dirty=!0}compute(t){const e=this.gl;if(!this.maskFbo||!this.pingFbo||!this.pongFbo||!this.distFbo)return;const n=this._width,i=this._height;if(n===0||i===0)return;e.viewport(0,0,n,i),e.disable(e.STENCIL_TEST),e.disable(e.BLEND),e.bindFramebuffer(e.FRAMEBUFFER,this.maskFbo),e.clearColor(0,0,0,1),e.clear(e.COLOR_BUFFER_BIT),e.useProgram(t.maskPass.program),e.uniform2f(t.maskPass.uniforms.uMeshScale,t.meshScaleX,t.meshScaleY),e.bindVertexArray(t.maskVao),e.drawElements(e.TRIANGLES,t.stencilIndexCount,e.UNSIGNED_SHORT,0),e.bindFramebuffer(e.FRAMEBUFFER,this.pingFbo),e.clearColor(-1,-1,0,0),e.clear(e.COLOR_BUFFER_BIT),e.useProgram(t.seedPass.program),e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,this.maskTex),e.uniform1i(t.seedPass.uniforms.uMask,5),e.uniform2f(t.seedPass.uniforms.uTexelSize,1/n,1/i),e.bindVertexArray(t.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4);const r=wt.computeFloodIterations(n,i);e.useProgram(t.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(n,i);e.bindFramebuffer(e.FRAMEBUFFER,a),e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,s),e.uniform1i(t.floodPass.uniforms.uSeedTex,5),e.uniform1f(t.floodPass.uniforms.uStepSize,h),e.bindVertexArray(t.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4);const f=s,c=a;s=l,a=c===this.pongFbo?this.pingFbo:this.pongFbo,l=f}e.bindFramebuffer(e.FRAMEBUFFER,this.distFbo),e.clearColor(0,0,0,1),e.clear(e.COLOR_BUFFER_BIT),e.useProgram(t.distPass.program),e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,s),e.uniform1i(t.distPass.uniforms.uSeedTex,5),e.activeTexture(e.TEXTURE6),e.bindTexture(e.TEXTURE_2D,this.maskTex),e.uniform1i(t.distPass.uniforms.uMask,6),e.uniform1f(t.distPass.uniforms.uBevelWidth,t.distRange),e.bindVertexArray(t.quadVao),e.drawArrays(e.TRIANGLE_STRIP,0,4),e.activeTexture(e.TEXTURE4),e.bindTexture(e.TEXTURE_2D,this.distTex),e.bindFramebuffer(e.FRAMEBUFFER,null),this._dirty=!1}static computeFloodIterations(t,e){const n=Math.max(t,e),i=[];let r=Math.ceil(n/2);for(;r>=1;)i.push(r),r=Math.floor(r/2);return i}dispose(){const t=this.gl;this.maskTex&&(t.deleteTexture(this.maskTex),this.maskTex=null),this.maskFbo&&(t.deleteFramebuffer(this.maskFbo),this.maskFbo=null),this.pingTex&&(t.deleteTexture(this.pingTex),this.pingTex=null),this.pingFbo&&(t.deleteFramebuffer(this.pingFbo),this.pingFbo=null),this.pongTex&&(t.deleteTexture(this.pongTex),this.pongTex=null),this.pongFbo&&(t.deleteFramebuffer(this.pongFbo),this.pongFbo=null),this.distTex&&(t.deleteTexture(this.distTex),this.distTex=null),this.distFbo&&(t.deleteFramebuffer(this.distFbo),this.distFbo=null),this._width=0,this._height=0,this._dirty=!0}}const Me=`#version 300 es
474
490
  in vec2 aPosition;
475
491
  uniform vec2 uMeshScale;
476
492
  void main() {
477
493
  gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
478
494
  }
479
- `,Ht=`#version 300 es
495
+ `,Ie=`#version 300 es
480
496
  precision lowp float;
481
497
  out vec4 fragColor;
482
498
  void main() { fragColor = vec4(0.0); }
483
- `,Wt=`#version 300 es
499
+ `,Be=`#version 300 es
484
500
  in vec2 aPosition;
485
501
  uniform vec2 uMeshScale;
486
502
  void main() {
487
503
  gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
488
504
  }
489
- `,jt=`#version 300 es
505
+ `,Ve=`#version 300 es
490
506
  precision lowp float;
491
507
  out vec4 fragColor;
492
508
  void main() { fragColor = vec4(1.0); }
493
- `,zt=`#version 300 es
509
+ `,Oe=`#version 300 es
494
510
  in vec2 aPosition;
495
511
  out vec2 vUv;
496
512
  void main() {
497
513
  vUv = aPosition * 0.5 + 0.5;
498
514
  gl_Position = vec4(aPosition, 0.0, 1.0);
499
515
  }
500
- `,qt=`#version 300 es
516
+ `,ke=`#version 300 es
501
517
  precision highp float;
502
518
  uniform sampler2D uMask;
503
519
  uniform vec2 uTexelSize;
@@ -522,14 +538,14 @@ void main() {
522
538
  fragSeed = vec2(-1.0);
523
539
  }
524
540
  }
525
- `,Yt=`#version 300 es
541
+ `,Ne=`#version 300 es
526
542
  in vec2 aPosition;
527
543
  out vec2 vUv;
528
544
  void main() {
529
545
  vUv = aPosition * 0.5 + 0.5;
530
546
  gl_Position = vec4(aPosition, 0.0, 1.0);
531
547
  }
532
- `,Zt=`#version 300 es
548
+ `,He=`#version 300 es
533
549
  precision highp float;
534
550
  uniform sampler2D uSeedTex;
535
551
  uniform float uStepSize;
@@ -558,14 +574,14 @@ void main() {
558
574
 
559
575
  fragSeed = bestSeed;
560
576
  }
561
- `,Jt=`#version 300 es
577
+ `,Xe=`#version 300 es
562
578
  in vec2 aPosition;
563
579
  out vec2 vUv;
564
580
  void main() {
565
581
  vUv = aPosition * 0.5 + 0.5;
566
582
  gl_Position = vec4(aPosition, 0.0, 1.0);
567
583
  }
568
- `,$t=`#version 300 es
584
+ `,Ge=`#version 300 es
569
585
  precision highp float;
570
586
  uniform sampler2D uSeedTex;
571
587
  uniform sampler2D uMask;
@@ -590,7 +606,7 @@ void main() {
590
606
  float normalized = clamp(d / max(uBevelWidth, 0.001), 0.0, 1.0);
591
607
  fragDist = vec4(normalized, 0.0, 0.0, 1.0);
592
608
  }
593
- `,Kt=`#version 300 es
609
+ `,We=`#version 300 es
594
610
  in vec2 aPosition;
595
611
  uniform vec2 uUvOffset;
596
612
  uniform vec2 uUvScale;
@@ -602,7 +618,7 @@ void main() {
602
618
  vScreenUv = baseUv;
603
619
  gl_Position = vec4(aPosition, 0.0, 1.0);
604
620
  }
605
- `,Qt=`#version 300 es
621
+ `,ze=`#version 300 es
606
622
  precision highp float;
607
623
 
608
624
  #define MAX_POM_STEPS 32
@@ -733,14 +749,14 @@ void main() {
733
749
  // Write lens-transformed depth to second attachment for boundary effects
734
750
  fragDepth = vec4(lensD, 0.0, 0.0, 1.0);
735
751
  }
736
- `,ei=`#version 300 es
752
+ `,qe=`#version 300 es
737
753
  in vec2 aPosition;
738
754
  out vec2 vUv;
739
755
  void main() {
740
756
  vUv = aPosition * 0.5 + 0.5;
741
757
  gl_Position = vec4(aPosition, 0.0, 1.0);
742
758
  }
743
- `,ti=`#version 300 es
759
+ `,Ye=`#version 300 es
744
760
  precision highp float;
745
761
  uniform sampler2D uInteriorColor;
746
762
  uniform sampler2D uDistField;
@@ -770,7 +786,7 @@ void main() {
770
786
 
771
787
  fragColor = vec4(toSRGB(linear), color.a);
772
788
  }
773
- `,ii=`#version 300 es
789
+ `,je=`#version 300 es
774
790
  in vec2 aPosition;
775
791
  in vec2 aNormal;
776
792
  uniform float uRimWidth;
@@ -793,7 +809,7 @@ void main() {
793
809
 
794
810
  gl_Position = vec4(pos, 0.0, 1.0);
795
811
  }
796
- `,ni=`#version 300 es
812
+ `,Ze=`#version 300 es
797
813
  precision highp float;
798
814
 
799
815
  uniform sampler2D uInteriorColor;
@@ -895,7 +911,7 @@ void main() {
895
911
 
896
912
  fragColor = vec4(color * alpha, alpha);
897
913
  }
898
- `,ri=`#version 300 es
914
+ `,$e=`#version 300 es
899
915
  in vec2 aPosition;
900
916
  in vec3 aNormal3;
901
917
  in float aLerpT; // 0 = inner (at silhouette), 1 = outer edge
@@ -911,7 +927,7 @@ void main() {
911
927
  vLerpT = aLerpT;
912
928
  gl_Position = vec4(sp, 0.0, 1.0);
913
929
  }
914
- `,oi=`#version 300 es
930
+ `,Ke=`#version 300 es
915
931
  precision highp float;
916
932
  uniform vec3 uLightDir3;
917
933
  uniform vec3 uChamferColor;
@@ -978,878 +994,7 @@ void main() {
978
994
  vec3 lit = base * (uChamferAmbient + (1.0 - uChamferAmbient) * diff) + vec3(spec);
979
995
  fragColor = vec4(toSRGB(lit), 1.0);
980
996
  }
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);
1103
- }
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);
1160
- }
1161
-
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
- }
1170
-
1171
- let offset = vec2f(f32(dx), f32(dy)) * uniforms.stepSize;
1172
- let sampleUv = in.uv + offset;
1173
-
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
- }
1178
-
1179
- let neighborSeed = textureSampleLevel(inputTexture, inputSampler, sampleUv, 0.0).rg;
1180
-
1181
- // Skip uninitialized seeds.
1182
- if (neighborSeed.x < 0.0) {
1183
- continue;
1184
- }
1185
-
1186
- let d = distance(in.uv, neighborSeed);
1187
- if (d < bestDist) {
1188
- bestDist = d;
1189
- bestSeed = neighborSeed;
1190
- }
1191
- }
1192
- }
1193
-
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);
1247
- }
1248
-
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);
1252
- }
1253
-
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)
1297
-
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
1302
-
1303
- // Depth-adaptive contrast
1304
- contrastLow: f32,
1305
- contrastHigh: f32,
1306
- verticalReduction: f32,
1307
-
1308
- // DOF
1309
- dofStart: f32,
1310
- dofStrength: f32,
1311
- imageTexelSize: vec2f,
1312
-
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;
1406
- }
1407
-
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);
1412
- }
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);
1434
- }
1435
-
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;
1687
- }
1688
-
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;
1840
- }
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=`
997
+ `;function Je(o){const t=[];let e=0;for(let n=0;n<o.length-2;n+=2){const i=o[n],r=o[n+1],s=o[n+2],a=o[n+3],l=s-i,u=a-r,h=Math.sqrt(l*l+u*u);if(h<1e-6)continue;const f=-u/h,c=l/h;t.push(i,r,f,c,i,r,-f,-c,s,a,f,c,s,a,f,c,i,r,-f,-c,s,a,-f,-c),e+=6}return{vertices:new Float32Array(t),count:e}}function Qe(o,t,e,n,i){if(n<=0)return{vertices:new Float32Array(0),count:0};const r=i*Math.PI/180,s=-Math.cos(r),a=Math.sin(r),l=[];let u=0;for(let h=0;h<t.length;h++){const f=t[h],v=((h+1<t.length?t[h+1]:o.length)-f)/2;if(v<3)continue;const p=v-1;let P=0;for(let T=0;T<p;T++){const m=f+T*2,S=o[m],w=o[m+1],F=o[m+2],U=o[m+3];P+=S*U-F*w}const x=P>=0?1:-1,y=[],d=[];for(let T=0;T<p;T++){const m=f+T*2,S=o[m+2]-o[m],w=o[m+3]-o[m+1],F=Math.sqrt(S*S+w*w);F<1e-8?(y.push(T>0?y[T-1]:0),d.push(T>0?d[T-1]:0)):(y.push(-w/F*x),d.push(S/F*x))}const g=[],b=[];for(let T=0;T<p;T++){const m=(T-1+p)%p;let S=y[m]+y[T],w=d[m]+d[T];const F=Math.sqrt(S*S+w*w);F>1e-8?(S/=F,w/=F):(S=y[T],w=d[T]),g.push(S),b.push(w)}for(let T=0;T<p;T++){const m=T,S=(T+1)%p,w=f+T*2,F=f+(T+1)%p*2,U=o[w],_=o[w+1],R=o[F],D=o[F+1],L=g[m]*a,M=b[m]*a,V=s,B=g[S]*a,H=b[S]*a,ot=s,Tt=U+g[m]*n,Jt=_+b[m]*n,Fn=R+g[S]*n,Cn=D+b[S]*n;l.push(U,_,L,M,V,0),l.push(Tt,Jt,L,M,V,1),l.push(R,D,B,H,ot,0),l.push(R,D,B,H,ot,0),l.push(Tt,Jt,L,M,V,1),l.push(Fn,Cn,B,H,ot,1),u+=6}}return{vertices:new Float32Array(l),count:u}}class tn extends at{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 Mt;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(t,e){super(t),this.config={...e},this.videoSlot=this.textures.register("video"),this.depthSlot=this.textures.register("depth");const n=this.config.bevelLightAngle*Math.PI/180;this.lightDirX=Math.cos(n),this.lightDirY=Math.sin(n);const i=this.config.lightDirection,r=Math.sqrt(i[0]*i[0]+i[1]*i[1]+i[2]*i[2]);r>1e-6&&(this.lightDir3=[i[0]/r,i[1]/r,i[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=It(s,e.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(t,e,n,i){const r=this.gl;r&&(this.disposeTextures(),this.disposeFBO(),this.jfa&&(this.jfa.dispose(),this.jfa=null),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.isCameraSource=t.type==="camera",this.videoAspect=t.width/t.height,this.meshAspect=i.aspect,this.clampDepthDimensions(e,n,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(i),this.uploadMaskMesh(i),this.uploadBoundaryMesh(i),this.uploadChamferMesh(i),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/t.width,1/t.height),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(t){const e=this.gl;if(!e||!this.stencilPass)return;this.stencilVao=e.createVertexArray(),e.bindVertexArray(this.stencilVao);const n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const i=e.getAttribLocation(this.stencilPass.program,"aPosition");e.enableVertexAttribArray(i),e.vertexAttribPointer(i,2,e.FLOAT,!1,0,0);const r=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,r),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.maskPass)return;this.maskVao=e.createVertexArray(),e.bindVertexArray(this.maskVao);const n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const i=e.getAttribLocation(this.maskPass.program,"aPosition");e.enableVertexAttribArray(i),e.vertexAttribPointer(i,2,e.FLOAT,!1,0,0);const r=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,r),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),e.bindVertexArray(null)}uploadBoundaryMesh(t){const e=this.gl;if(!e||!this.boundaryPass)return;const n=Je(t.edgeVertices);if(n.count===0)return;this.boundaryVao=e.createVertexArray(),e.bindVertexArray(this.boundaryVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,n.vertices,e.STATIC_DRAW);const r=16,s=e.getAttribLocation(this.boundaryPass.program,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,r,0);const a=e.getAttribLocation(this.boundaryPass.program,"aNormal");a>=0&&(e.enableVertexAttribArray(a),e.vertexAttribPointer(a,2,e.FLOAT,!1,r,8)),this.boundaryVertexCount=n.count,e.bindVertexArray(null)}uploadChamferMesh(t){const e=this.gl;if(!e||!this.chamferPass||this.config.chamferWidth<=0)return;const n=Qe(t.edgeVertices,t.contourOffsets,t.contourIsHole,this.config.chamferWidth,this.config.chamferAngle);if(n.count===0)return;this.chamferVao=e.createVertexArray(),e.bindVertexArray(this.chamferVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,n.vertices,e.STATIC_DRAW);const r=24,s=e.getAttribLocation(this.chamferPass.program,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,r,0);const a=e.getAttribLocation(this.chamferPass.program,"aNormal3");a>=0&&(e.enableVertexAttribArray(a),e.vertexAttribPointer(a,3,e.FLOAT,!1,r,8));const l=e.getAttribLocation(this.chamferPass.program,"aLerpT");l>=0&&(e.enableVertexAttribArray(l),e.vertexAttribPointer(l,1,e.FLOAT,!1,r,20)),this.chamferVertexCount=n.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 n=this.gl;if(!n)return;this.disposeFBO(),this.fboWidth=t,this.fboHeight=e,this.interiorFbo=n.createFramebuffer(),n.bindFramebuffer(n.FRAMEBUFFER,this.interiorFbo),this.interiorColorTex=n.createTexture(),n.activeTexture(n.TEXTURE2),n.bindTexture(n.TEXTURE_2D,this.interiorColorTex),n.texStorage2D(n.TEXTURE_2D,1,n.RGBA8,t,e),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,this.interiorColorTex,0),this.interiorDepthTex=n.createTexture(),n.activeTexture(n.TEXTURE3),n.bindTexture(n.TEXTURE_2D,this.interiorDepthTex),n.texStorage2D(n.TEXTURE_2D,1,n.RGBA8,t,e),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_ATTACHMENT1,n.TEXTURE_2D,this.interiorDepthTex,0),n.drawBuffers([n.COLOR_ATTACHMENT0,n.COLOR_ATTACHMENT1]);const i=n.checkFramebufferStatus(n.FRAMEBUFFER);i!==n.FRAMEBUFFER_COMPLETE&&console.error("Interior FBO incomplete:",i),n.bindFramebuffer(n.FRAMEBUFFER,null)}createJFAResources(t,e){const n=this.gl;n&&(this.jfa||(this.jfa=new wt(n,this.hasColorBufferFloat)),this.jfa.createResources(t,e,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 t=this.gl;t&&(this.stencilPass=k(t,"stencil",Me,Ie,["uMeshScale"]),this.maskPass=k(t,"mask",Be,Ve,["uMeshScale"]),this.jfaSeedPass=k(t,"jfa-seed",Oe,ke,["uMask","uTexelSize"]),this.jfaFloodPass=k(t,"jfa-flood",Ne,He,["uSeedTex","uStepSize"]),this.jfaDistPass=k(t,"jfa-dist",Xe,Ge,["uSeedTex","uMask","uBevelWidth"]),this.interiorPass=k(t,"interior",We,ze,["uImage","uDepth","uOffset","uStrength","uPomSteps","uDepthPower","uDepthScale","uDepthBias","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uFogDensity","uFogColor","uColorShift","uBrightnessBias","uUvOffset","uUvScale"]),this.compositePass=k(t,"composite",qe,Ye,["uInteriorColor","uDistField","uEdgeOcclusionWidth","uEdgeOcclusionStrength"]),this.boundaryPass=k(t,"boundary",je,Ze,["uInteriorColor","uInteriorDepth","uDistField","uRimIntensity","uRimColor","uRimWidth","uMeshScale","uRefractionStrength","uChromaticStrength","uOcclusionIntensity","uTexelSize","uEdgeThickness","uEdgeSpecular","uEdgeColor","uLightDir","uBevelIntensity"]),this.chamferPass=k(t,"chamfer",$e,Ke,["uMeshScale","uLightDir3","uChamferColor","uChamferAmbient","uChamferSpecular","uChamferShininess","uInteriorColor","uTexelSize"]),this.quadVao=_t(t,this.interiorPass.program),t.disable(t.DEPTH_TEST))}onRenderFrame(){const t=this.gl,e=this.mediaSource;if(!t||!this.interiorPass||!this.quadVao)return;const n=e?.getImageSource();if(!n||!this.interiorFbo||!this.interiorColorTex||!this.interiorDepthTex)return;this.jfa?.isDirty&&this.maskVao&&(this.computeDistanceField(),t.viewport(0,0,this.canvas.width,this.canvas.height)),t.activeTexture(t.TEXTURE0+this.videoSlot.unit),t.bindTexture(t.TEXTURE_2D,this.videoSlot.texture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,n),this.rvfcSupported||this.onDepthUpdate(e.currentTime);let i=0,r=0;if(this.readInput){const s=this.readInput();i=-s.x,r=s.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.interiorPass.program),t.uniform2f(this.interiorPass.uniforms.uOffset,i,r),t.activeTexture(t.TEXTURE0+this.videoSlot.unit),t.bindTexture(t.TEXTURE_2D,this.videoSlot.texture),t.activeTexture(t.TEXTURE0+this.depthSlot.unit),t.bindTexture(t.TEXTURE_2D,this.depthSlot.texture),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.stencilPass&&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.stencilPass.program),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.jfa?.distanceTexture??null),t.useProgram(this.compositePass.program),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.disable(t.STENCIL_TEST),this.chamferVao&&this.chamferPass&&this.chamferVertexCount>0&&(t.useProgram(this.chamferPass.program),t.uniform2f(this.chamferPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform2f(this.chamferPass.uniforms.uTexelSize,1/this.canvas.width,1/this.canvas.height),t.bindVertexArray(this.chamferVao),t.drawArrays(t.TRIANGLES,0,this.chamferVertexCount)),this.boundaryVao&&this.boundaryPass&&this.boundaryVertexCount>0&&this.config.rimLightIntensity>0&&(t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA),t.useProgram(this.boundaryPass.program),t.bindVertexArray(this.boundaryVao),t.drawArrays(t.TRIANGLES,0,this.boundaryVertexCount),t.disable(t.BLEND))}onDepthUpdate(t){const e=this.gl;if(!e||!this.readDepth||!this.depthSlot.texture)return;const n=this.subsampleDepth(this.readDepth(t));e.activeTexture(e.TEXTURE0+this.depthSlot.unit),e.bindTexture(e.TEXTURE_2D,this.depthSlot.texture),e.texSubImage2D(e.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,e.RED,e.UNSIGNED_BYTE,n)}recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:n}=this.getViewportSize(),i=Math.min(window.devicePixelRatio,this.qualityParams.dprCap),r=Math.round(e*i),s=Math.round(n*i);(this.canvas.width!==r||this.canvas.height!==s)&&(this.canvas.width=r,this.canvas.height=s,t.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&&(t.useProgram(this.interiorPass.program),t.uniform2f(this.interiorPass.uniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),t.uniform2f(this.interiorPass.uniforms.uUvScale,this.uvScale[0],this.uvScale[1]));const h=e/n,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&&(t.useProgram(this.stencilPass.program),t.uniform2f(this.stencilPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.boundaryPass&&(t.useProgram(this.boundaryPass.program),t.uniform2f(this.boundaryPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform1f(this.boundaryPass.uniforms.uRimWidth,this.config.rimLightWidth),t.uniform2f(this.boundaryPass.uniforms.uTexelSize,1/r,1/s)),this.chamferPass&&(t.useProgram(this.chamferPass.program),t.uniform2f(this.chamferPass.uniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.jfa&&this.jfa.markDirty()}onContextRestored(){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.mediaSource&&(this.animationFrameHandle=window.requestAnimationFrame(()=>this.onRenderFrame())))}disposeTextures(){const t=this.gl;t&&this.textures.disposeAll(t)}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)}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)}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 t=this.gl;if(!t)return;const e=[this.stencilPass,this.maskPass,this.jfaSeedPass,this.jfaFloodPass,this.jfaDistPass,this.interiorPass,this.compositePass,this.boundaryPass,this.chamferPass];for(const n of e)n&&n.dispose(t);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&&(t.deleteVertexArray(this.quadVao),this.quadVao=null)}}async function Rt(o){const t=await fetch(o);if(!t.ok)throw new Error(`Failed to fetch SVG: ${t.status} ${t.statusText}`);const e=await t.text();return en(e)}function en(o){const n=new DOMParser().parseFromString(o,"image/svg+xml").querySelector("svg");if(!n)throw new Error("No <svg> element found in document.");const i=nn(n);if(i.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 R of i)for(let D=0;D<R.length;D+=2)r=Math.min(r,R[D]),a=Math.max(a,R[D]),s=Math.min(s,R[D+1]),l=Math.max(l,R[D+1]);const u=a-r,h=l-s,f=(r+a)/2,c=(s+l)/2,E=2/Math.max(u,h),v=u/h,p=i.map(R=>{const D=[];for(let L=0;L<R.length;L+=2)D.push((R[L]-f)*E),D.push(-((R[L+1]-c)*E));return D}),P=cn(p),x=[],y=[];for(const R of P){const{flatCoords:D,holeIndices:L}=un(R),M=fn(D,L),V=x.length/2;for(const B of M)y.push(B+V);for(const B of D)x.push(B)}const d=x,g=y,b=[],T=[],m=[],S=zt(p);for(let R=0;R<p.length;R++){const D=p[R];T.push(b.length),m.push(S[R]);for(let L=0;L<D.length;L++)b.push(D[L]);D.length>=2&&b.push(D[0],D[1])}let w=1/0,F=1/0,U=-1/0,_=-1/0;for(let R=0;R<d.length;R+=2)w=Math.min(w,d[R]),U=Math.max(U,d[R]),F=Math.min(F,d[R+1]),_=Math.max(_,d[R+1]);return{vertices:new Float32Array(d),indices:new Uint16Array(g),edgeVertices:new Float32Array(b),contourOffsets:T,contourIsHole:m,bounds:{minX:w,maxX:U,minY:F,maxY:_},aspect:v}}function nn(o){const t=[];return o.querySelectorAll("path").forEach(l=>{const u=l.getAttribute("d");if(!u)return;const h=sn(u);t.push(...h)}),o.querySelectorAll("polygon").forEach(l=>{const u=l.getAttribute("points");if(!u)return;const h=Xt(u);h.length>=6&&t.push(h)}),o.querySelectorAll("polyline").forEach(l=>{const u=l.getAttribute("points");if(!u)return;const h=Xt(u);h.length>=6&&t.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&&t.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&&t.push(rn(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&&t.push(on(u,h,f,c))}),t}function Xt(o){const t=[],e=o.trim().split(/[\s,]+/);for(let n=0;n<e.length-1;n+=2){const i=parseFloat(e[n]),r=parseFloat(e[n+1]);Number.isFinite(i)&&Number.isFinite(r)&&t.push(i,r)}return t}function rn(o,t,e,n=64){const i=[];for(let r=0;r<n;r++){const s=2*Math.PI*r/n;i.push(o+e*Math.cos(s),t+e*Math.sin(s))}return i}function on(o,t,e,n,i=64){const r=[];for(let s=0;s<i;s++){const a=2*Math.PI*s/i;r.push(o+e*Math.cos(a),t+n*Math.sin(a))}return r}function sn(o){const t=[];let e=[],n=0,i=0,r=0,s=0,a=0,l=0,u="";const h=an(o);let f=0;function c(){return f>=h.length?0:parseFloat(h[f++])}for(;f<h.length;){const E=h[f];let v;/^[a-zA-Z]$/.test(E)?(v=E,f++):v=u==="M"?"L":u==="m"?"l":u;const p=v===v.toLowerCase();switch(v.toUpperCase()){case"M":{e.length>0&&t.push(e),e=[];const x=c()+(p?n:0),y=c()+(p?i:0);n=x,i=y,r=x,s=y,e.push(n,i),a=n,l=i;break}case"L":{n=c()+(p?n:0),i=c()+(p?i:0),e.push(n,i),a=n,l=i;break}case"H":{n=c()+(p?n:0),e.push(n,i),a=n,l=i;break}case"V":{i=c()+(p?i:0),e.push(n,i),a=n,l=i;break}case"C":{const x=c()+(p?n:0),y=c()+(p?i:0),d=c()+(p?n:0),g=c()+(p?i:0),b=c()+(p?n:0),T=c()+(p?i:0);et(e,n,i,x,y,d,g,b,T),n=b,i=T,a=d,l=g;break}case"S":{const x=2*n-a,y=2*i-l,d=c()+(p?n:0),g=c()+(p?i:0),b=c()+(p?n:0),T=c()+(p?i:0);et(e,n,i,x,y,d,g,b,T),n=b,i=T,a=d,l=g;break}case"Q":{const x=c()+(p?n:0),y=c()+(p?i:0),d=c()+(p?n:0),g=c()+(p?i:0);Gt(e,n,i,x,y,d,g),n=d,i=g,a=x,l=y;break}case"T":{const x=2*n-a,y=2*i-l,d=c()+(p?n:0),g=c()+(p?i:0);Gt(e,n,i,x,y,d,g),n=d,i=g,a=x,l=y;break}case"A":{const x=c(),y=c(),d=c(),g=c(),b=c(),T=c()+(p?n:0),m=c()+(p?i:0);hn(e,n,i,x,y,d,!!g,!!b,T,m),n=T,i=m,a=n,l=i;break}case"Z":{n=r,i=s,e.length>0&&t.push(e),e=[],a=n,l=i;break}default:f++;break}u=v}return e.length>=6&&t.push(e),t}function an(o){const t=[],e=/([a-zA-Z])|([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/g;let n;for(;(n=e.exec(o))!==null;)t.push(n[0]);return t}const ln=.5;function et(o,t,e,n,i,r,s,a,l,u=0){if(u>12){o.push(a,l);return}const h=a-t,f=l-e,c=Math.sqrt(h*h+f*f);if(c<1e-6){o.push(a,l);return}const E=Math.abs((n-a)*f-(i-l)*h)/c,v=Math.abs((r-a)*f-(s-l)*h)/c;if(E+v<ln){o.push(a,l);return}const p=(t+n)/2,P=(e+i)/2,x=(n+r)/2,y=(i+s)/2,d=(r+a)/2,g=(s+l)/2,b=(p+x)/2,T=(P+y)/2,m=(x+d)/2,S=(y+g)/2,w=(b+m)/2,F=(T+S)/2;et(o,t,e,p,P,b,T,w,F,u+1),et(o,w,F,m,S,d,g,a,l,u+1)}function Gt(o,t,e,n,i,r,s){const a=t+.6666666666666666*(n-t),l=e+2/3*(i-e),u=r+2/3*(n-r),h=s+2/3*(i-s);et(o,t,e,a,l,u,h,r,s)}function hn(o,t,e,n,i,r,s,a,l,u){if(n===0||i===0){o.push(l,u);return}let h=Math.abs(n),f=Math.abs(i);const c=r*Math.PI/180,E=Math.cos(c),v=Math.sin(c),p=(t-l)/2,P=(e-u)/2,x=E*p+v*P,y=-v*p+E*P;let d=x*x/(h*h)+y*y/(f*f);if(d>1){const M=Math.sqrt(d);h*=M,f*=M,d=1}const g=h*h,b=f*f,T=x*x,m=y*y;let S=Math.max(0,(g*b-g*m-b*T)/(g*m+b*T));S=Math.sqrt(S),s===a&&(S=-S);const w=S*(h*y)/f,F=S*-(f*x)/h,U=E*w-v*F+(t+l)/2,_=v*w+E*F+(e+u)/2,R=Wt(1,0,(x-w)/h,(y-F)/f);let D=Wt((x-w)/h,(y-F)/f,(-x-w)/h,(-y-F)/f);!a&&D>0&&(D-=2*Math.PI),a&&D<0&&(D+=2*Math.PI);const L=Math.max(4,Math.ceil(Math.abs(D)/(Math.PI/16)));for(let M=1;M<=L;M++){const V=R+M/L*D,B=Math.cos(V),H=Math.sin(V),ot=E*h*B-v*f*H+U,Tt=v*h*B+E*f*H+_;o.push(ot,Tt)}}function Wt(o,t,e,n){const i=o*n-t*e<0?-1:1,r=o*e+t*n,s=Math.sqrt(o*o+t*t),a=Math.sqrt(e*e+n*n),l=r/(s*a);return i*Math.acos(Math.max(-1,Math.min(1,l)))}function un(o){const t=[],e=[];for(let n=0;n<o.length;n++){n>0&&e.push(t.length/2);for(const i of o[n])t.push(i)}return{flatCoords:t,holeIndices:e}}function zt(o){const t=o.length,e=o.map(i=>Math.abs(qt(i))),n=new Array(t).fill(!1);for(let i=0;i<t;i++){let r=0;const s=o[i][0],a=o[i][1];for(let l=0;l<t;l++)i!==l&&e[l]>e[i]&&Yt(s,a,o[l])&&r++;n[i]=r%2===1}return n}function cn(o){if(o.length<=1)return[o];const t=zt(o),e=o.map((s,a)=>{const l=qt(s);return{index:a,contour:s,area:l,isOuter:!t[a]}}),n=e.filter(s=>s.isOuter),i=e.filter(s=>!s.isOuter);if(n.length===0)return o.map(s=>[s]);const r=n.map(s=>({outer:s.contour,holes:[]}));for(const s of i){const a=s.contour[0],l=s.contour[1];let u=-1,h=1/0;for(let f=0;f<n.length;f++)if(Yt(a,l,n[f].contour)){const c=Math.abs(n[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 qt(o){let t=0;const e=o.length;for(let n=0;n<e;n+=2){const i=o[n],r=o[n+1],s=o[(n+2)%e],a=o[(n+3)%e];t+=i*a-s*r}return t/2}function Yt(o,t,e){let n=!1;const i=e.length;for(let r=0,s=i-2;r<i;s=r,r+=2){const a=e[r],l=e[r+1],u=e[s],h=e[s+1];l>t!=h>t&&o<(u-a)*(t-l)/(h-l)+a&&(n=!n)}return n}function fn(o,t,e=2){const n=t&&t.length>0,i=n?t[0]*e:o.length;let r=jt(o,0,i,e,!0);const s=[];if(!r||r.next===r.prev)return s;n&&(r=vn(o,t,r,e));let a=1/0,l=1/0,u=-1/0,h=-1/0,f=0;if(o.length>80*e){for(let c=0;c<i;c+=e){const E=o[c],v=o[c+1];E<a&&(a=E),v<l&&(l=v),E>u&&(u=E),v>h&&(h=v)}f=Math.max(u-a,h-l),f=f!==0?32767/f:0}return nt(r,s,e,a,l,f,0),s}function jt(o,t,e,n,i){let r=null;if(i===Dn(o,t,e,n)>0)for(let s=t;s<e;s+=n)r=Kt(s,o[s],o[s+1],r);else for(let s=e-n;s>=t;s-=n)r=Kt(s,o[s],o[s+1],r);return r&&mt(r,r.next)&&(rt(r),r=r.next),r?(r.next.prev=r,r.prev.next=r,r.next):null}function W(o,t){t||(t=o);let e=o,n;do if(n=!1,!e.steiner&&(mt(e,e.next)||C(e.prev,e,e.next)===0)){if(rt(e),e=t=e.prev,e===e.next)break;n=!0}else e=e.next;while(n||e!==t);return t}function nt(o,t,e,n,i,r,s){if(!o)return;!s&&r&&bn(o,n,i,r);let a=o,l,u;for(;o.prev!==o.next;){if(l=o.prev,u=o.next,r?mn(o,n,i,r):dn(o)){t.push(l.i/e,o.i/e,u.i/e),rt(o),o=u.next,a=u.next;continue}if(o=u,o===a){s?s===1?(o=pn(W(o),t,e),nt(o,t,e,n,i,r,2)):s===2&&gn(o,t,e,n,i,r):nt(W(o),t,e,n,i,r,1);break}}}function dn(o){const t=o.prev,e=o,n=o.next;if(C(t,e,n)>=0)return!1;const i=t.x,r=e.x,s=n.x,a=t.y,l=e.y,u=n.y,h=i<r?i<s?i:s:r<s?r:s,f=a<l?a<u?a:u:l<u?l:u,c=i>r?i>s?i:s:r>s?r:s,E=a>l?a>u?a:u:l>u?l:u;let v=n.next;for(;v!==t;){if(v.x>=h&&v.x<=c&&v.y>=f&&v.y<=E&&$(i,a,r,l,s,u,v.x,v.y)&&C(v.prev,v,v.next)>=0)return!1;v=v.next}return!0}function mn(o,t,e,n){const i=o.prev,r=o,s=o.next;if(C(i,r,s)>=0)return!1;const a=i.x,l=r.x,u=s.x,h=i.y,f=r.y,c=s.y,E=a<l?a<u?a:u:l<u?l:u,v=h<f?h<c?h:c:f<c?f:c,p=a>l?a>u?a:u:l>u?l:u,P=h>f?h>c?h:c:f>c?f:c,x=Dt(E,v,t,e,n),y=Dt(p,P,t,e,n);let d=o.prevZ,g=o.nextZ;for(;d&&d.z>=x&&g&&g.z<=y;){if(d.x>=E&&d.x<=p&&d.y>=v&&d.y<=P&&d!==i&&d!==s&&$(a,h,l,f,u,c,d.x,d.y)&&C(d.prev,d,d.next)>=0||(d=d.prevZ,g.x>=E&&g.x<=p&&g.y>=v&&g.y<=P&&g!==i&&g!==s&&$(a,h,l,f,u,c,g.x,g.y)&&C(g.prev,g,g.next)>=0))return!1;g=g.nextZ}for(;d&&d.z>=x;){if(d.x>=E&&d.x<=p&&d.y>=v&&d.y<=P&&d!==i&&d!==s&&$(a,h,l,f,u,c,d.x,d.y)&&C(d.prev,d,d.next)>=0)return!1;d=d.prevZ}for(;g&&g.z<=y;){if(g.x>=E&&g.x<=p&&g.y>=v&&g.y<=P&&g!==i&&g!==s&&$(a,h,l,f,u,c,g.x,g.y)&&C(g.prev,g,g.next)>=0)return!1;g=g.nextZ}return!0}function pn(o,t,e){let n=o;do{const i=n.prev,r=n.next.next;!mt(i,r)&&Zt(i,n,n.next,r)&&it(i,r)&&it(r,i)&&(t.push(i.i/e,n.i/e,r.i/e),rt(n),rt(n.next),n=o=r),n=n.next}while(n!==o);return W(n)}function gn(o,t,e,n,i,r){let s=o;do{let a=s.next.next;for(;a!==s.prev;){if(s.i!==a.i&&An(s,a)){let l=$t(s,a);s=W(s,s.next),l=W(l,l.next),nt(s,t,e,n,i,r,0),nt(l,t,e,n,i,r,0);return}a=a.next}s=s.next}while(s!==o)}function vn(o,t,e,n){const i=[];for(let r=0;r<t.length;r++){const s=t[r]*n,a=r<t.length-1?t[r+1]*n:o.length,l=jt(o,s,a,n,!1);l&&(l===l.next&&(l.steiner=!0),i.push(Sn(l)))}i.sort((r,s)=>r.x-s.x);for(const r of i)e=xn(r,e);return e}function xn(o,t){const e=Tn(o,t);if(!e)return t;const n=$t(e,o);return W(n,n.next),W(e,e.next)}function Tn(o,t){let e=t;const n=o.x,i=o.y;let r=-1/0,s=null;do{if(i<=e.y&&i>=e.next.y&&e.next.y!==e.y){const f=e.x+(i-e.y)/(e.next.y-e.y)*(e.next.x-e.x);if(f<=n&&f>r&&(r=f,s=e.x<e.next.x?e:e.next,f===n))return s}e=e.next}while(e!==t);if(!s)return null;const a=s,l=s.x,u=s.y;let h=1/0;e=s;do{if(n>=e.x&&e.x>=l&&n!==e.x&&$(i<u?n:r,i,l,u,i<u?r:n,i,e.x,e.y)){const f=Math.abs(i-e.y)/(n-e.x);it(e,o)&&(f<h||f===h&&(e.x>s.x||En(s,e)))&&(s=e,h=f)}e=e.next}while(e!==a);return s}function En(o,t){return C(o.prev,o,t.prev)<0&&C(t.next,o,o.next)<0}function bn(o,t,e,n){let i=o;do i.z===0&&(i.z=Dt(i.x,i.y,t,e,n)),i.prevZ=i.prev,i.nextZ=i.next,i=i.next;while(i!==o);i.prevZ.nextZ=null,i.prevZ=null,yn(i)}function yn(o){let t=1,e;do{let n=o;o=null;let i=null;for(e=0;n;){e++;let r=n,s=0;for(let l=0;l<t&&(s++,r=r.nextZ,!!r);l++);let a=t;for(;s>0||a>0&&r;){let l;s!==0&&(a===0||!r||n.z<=r.z)?(l=n,n=n.nextZ,s--):(l=r,r=r.nextZ,a--),i?i.nextZ=l:o=l,l.prevZ=i,i=l}n=r}i.nextZ=null,t*=2}while(e>1);return o}function Dt(o,t,e,n,i){let r=(o-e)*i|0,s=(t-n)*i|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 Sn(o){let t=o,e=o;do(t.x<e.x||t.x===e.x&&t.y<e.y)&&(e=t),t=t.next;while(t!==o);return e}function $(o,t,e,n,i,r,s,a){return(i-s)*(t-a)-(o-s)*(r-a)>=0&&(o-s)*(n-a)-(e-s)*(t-a)>=0&&(e-s)*(r-a)-(i-s)*(n-a)>=0}function An(o,t){return o.next.i!==t.i&&o.prev.i!==t.i&&!wn(o,t)&&(it(o,t)&&it(t,o)&&Rn(o,t)&&(C(o.prev,o,t.prev)!==0||C(o,t.prev,t)!==0)||mt(o,t)&&C(o.prev,o,o.next)>0&&C(t.prev,t,t.next)>0)}function C(o,t,e){return(t.y-o.y)*(e.x-t.x)-(t.x-o.x)*(e.y-t.y)}function mt(o,t){return o.x===t.x&&o.y===t.y}function Zt(o,t,e,n){const i=gt(C(o,t,e)),r=gt(C(o,t,n)),s=gt(C(e,n,o)),a=gt(C(e,n,t));return!!(i!==r&&s!==a||i===0&&pt(o,e,t)||r===0&&pt(o,n,t)||s===0&&pt(e,o,n)||a===0&&pt(e,t,n))}function pt(o,t,e){return t.x<=Math.max(o.x,e.x)&&t.x>=Math.min(o.x,e.x)&&t.y<=Math.max(o.y,e.y)&&t.y>=Math.min(o.y,e.y)}function gt(o){return o>0?1:o<0?-1:0}function wn(o,t){let e=o;do{if(e.i!==o.i&&e.next.i!==o.i&&e.i!==t.i&&e.next.i!==t.i&&Zt(e,e.next,o,t))return!0;e=e.next}while(e!==o);return!1}function it(o,t){return C(o.prev,o,o.next)<0?C(o,t,o.next)>=0&&C(o,o.prev,t)>=0:C(o,t,o.prev)<0||C(o,o.next,t)<0}function Rn(o,t){let e=o,n=!1;const i=(o.x+t.x)/2,r=(o.y+t.y)/2;do e.y>r!=e.next.y>r&&e.next.y!==e.y&&i<(e.next.x-e.x)*(r-e.y)/(e.next.y-e.y)+e.x&&(n=!n),e=e.next;while(e!==o);return n}function $t(o,t){const e=Pt(o.i,o.x,o.y),n=Pt(t.i,t.x,t.y),i=o.next,r=t.prev;return o.next=t,t.prev=o,e.next=i,i.prev=e,n.next=e,e.prev=n,r.next=n,n.prev=r,n}function Kt(o,t,e,n){const i=Pt(o,t,e);return n?(i.next=n.next,i.prev=n,n.next.prev=i,n.next=i):(i.prev=i,i.next=i),i}function rt(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 Pt(o,t,e){return{i:o,x:t,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function Dn(o,t,e,n){let i=0;for(let r=t,s=e-n;r<e;r+=n)i+=(o[s]-o[r])*(o[r+1]+o[s+1]),s=r;return i}const A={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},K=512,J=512;class Ft{constructor(t,e=.08,n=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=n,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=xt(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=xt(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(),n=(t.clientX-e.left)/e.width*2-1,i=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=Q(n,-1,1),this.pointerTarget.y=Q(i,-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 n=e.clientX-this.touchAnchorX,i=e.clientY-this.touchAnchorY,r=Ft.TOUCH_DRAG_RANGE;this.pointerTarget.x=Q(n/r,-1,1),this.pointerTarget.y=Q(i/r,-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=Q((t.gamma??0)/45,-1,1),n=Q((t.beta??0)/45,-1,1);this.motionTarget.x=xt(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=xt(this.motionTarget.y,n,this.motionLerpFactor)}}class vt extends HTMLElement{static TAG_NAME="layershift-portal";static get observedAttributes(){return["src","depth-src","depth-meta","depth-model","logo-src","source-type","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","depth-model","logo-src","source-type"];canInit(){const t=!!this.getAttribute("logo-src");if(this.sourceType==="camera")return t;const e=!!this.getAttribute("src"),n=!!this.getAttribute("depth-src")&&!!this.getAttribute("depth-meta"),i=!!this.getAttribute("depth-model");return e&&t&&(n||i)}shadow;container=null;renderer=null;inputHandler=null;source=null;depthEstimator=null;mesh=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Ht(this)}getAttrFloat(t,e){const n=this.getAttribute(t);if(n===null)return e;const i=parseFloat(n);return Number.isFinite(i)?i:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const n=this.getAttribute(t);return!(n==="false"||n==="0")}getAttrColor(t,e){const n=this.getAttribute(t)??e;return Pn(n)}getAttrVec3(t,e){const i=(this.getAttribute(t)??e).split(",").map(s=>parseFloat(s.trim()));if(i.length>=3&&i.every(Number.isFinite))return[i[0],i[1],i[2]];const r=e.split(",").map(s=>parseFloat(s.trim()));return[r[0],r[1],r[2]]}get sourceType(){const t=this.getAttribute("source-type");return t==="camera"?"camera":t==="image"?"image":"video"}get parallaxX(){return this.getAttrFloat("parallax-x",A.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",A.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",A.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",A.overscan)}get pomSteps(){return this.getAttrFloat("pom-steps",A.pomSteps)}get quality(){const t=this.getAttribute("quality");if(t==="auto"||t==="high"||t==="medium"||t==="low")return t}get gpuBackend(){return"webgl2"}get rimIntensity(){return this.getAttrFloat("rim-intensity",A.rimIntensity)}get rimWidth(){return this.getAttrFloat("rim-width",A.rimWidth)}get rimColor(){return this.getAttrColor("rim-color",A.rimColor)}get refractionStrength(){return this.getAttrFloat("refraction-strength",A.refractionStrength)}get chromaticStrength(){return this.getAttrFloat("chromatic-strength",A.chromaticStrength)}get occlusionIntensity(){return this.getAttrFloat("occlusion-intensity",A.occlusionIntensity)}get depthPower(){return this.getAttrFloat("depth-power",A.depthPower)}get depthScale(){return this.getAttrFloat("depth-scale",A.depthScale)}get depthBias(){return this.getAttrFloat("depth-bias",A.depthBias)}get fogDensity(){return this.getAttrFloat("fog-density",A.fogDensity)}get fogColor(){return this.getAttrColor("fog-color",A.fogColor)}get colorShift(){return this.getAttrFloat("color-shift",A.colorShift)}get brightnessBias(){return this.getAttrFloat("brightness-bias",A.brightnessBias)}get contrastLow(){return this.getAttrFloat("contrast-low",A.contrastLow)}get contrastHigh(){return this.getAttrFloat("contrast-high",A.contrastHigh)}get verticalReduction(){return this.getAttrFloat("vertical-reduction",A.verticalReduction)}get dofStart(){return this.getAttrFloat("dof-start",A.dofStart)}get dofStrength(){return this.getAttrFloat("dof-strength",A.dofStrength)}get bevelIntensity(){return this.getAttrFloat("bevel-intensity",A.bevelIntensity)}get bevelWidth(){return this.getAttrFloat("bevel-width",A.bevelWidth)}get bevelDarkening(){return this.getAttrFloat("bevel-darkening",A.bevelDarkening)}get bevelDesaturation(){return this.getAttrFloat("bevel-desaturation",A.bevelDesaturation)}get bevelLightAngle(){return this.getAttrFloat("bevel-light-angle",A.bevelLightAngle)}get edgeThickness(){return this.getAttrFloat("edge-thickness",A.edgeThickness)}get edgeSpecular(){return this.getAttrFloat("edge-specular",A.edgeSpecular)}get edgeColor(){return this.getAttrColor("edge-color",A.edgeColor)}get chamferWidth(){return this.getAttrFloat("chamfer-width",A.chamferWidth)}get chamferAngle(){return this.getAttrFloat("chamfer-angle",A.chamferAngle)}get chamferColor(){return this.getAttrColor("chamfer-color",A.chamferColor)}get chamferAmbient(){return this.getAttrFloat("chamfer-ambient",A.chamferAmbient)}get chamferSpecular(){return this.getAttrFloat("chamfer-specular",A.chamferSpecular)}get chamferShininess(){return this.getAttrFloat("chamfer-shininess",A.chamferShininess)}get edgeOcclusionWidth(){return this.getAttrFloat("edge-occlusion-width",A.edgeOcclusionWidth)}get edgeOcclusionStrength(){return this.getAttrFloat("edge-occlusion-strength",A.edgeOcclusionStrength)}get lightDirection3(){return this.getAttrVec3("light-direction",A.lightDirection)}get depthModel(){return this.getAttribute("depth-model")}get shouldAutoplay(){return this.getAttrBool("autoplay",A.autoplay)}get shouldLoop(){return this.getAttrBool("loop",A.loop)}get shouldMute(){return this.getAttrBool("muted",A.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachSourceEventListeners(t){t.addEventListener&&(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",(()=>{this.loopCount+=1,this.emit("layershift-portal:loop",{loopCount:this.loopCount})})))}connectedCallback(){console.warn("[layershift] <layershift-portal> is deprecated and will be removed in a future major version. Use <layershift-effect> with a filter-config.json instead. See https://layershift.io/docs/migration"),this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,n){this.lifecycle.onAttributeChanged(t,e,n)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
1853
998
  :host {
1854
999
  display: block;
1855
1000
  width: 100%;
@@ -1869,4 +1014,4 @@ fn fs_main(in: VsOutput) -> @location(0) vec4f {
1869
1014
  width: 100%;
1870
1015
  height: 100%;
1871
1016
  }
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})({});
1017
+ `,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("logo-src");if(!this.container)return;const n=this.sourceType==="camera",i=this.depthModel;try{let r,s,a,l=null;const u=x=>{this.emit("layershift-portal:model-progress",x)};if(n){const[x,y]=await Promise.all([Bt({video:{facingMode:"user"}},{parent:this.shadow}),Rt(e)]);if(t.aborted){x.dispose();return}if(r=x,a=y,i){if(l=await ut(i,K,J,u),t.aborted){l.dispose(),r.dispose();return}s=N(K,J)}else s=N(r.width,r.height)}else{const x=this.getAttribute("src"),y=this.getAttribute("depth-src"),d=this.getAttribute("depth-meta"),g=!!y&&!!d,b=this.sourceType==="image"||/\.(jpe?g|png|webp|gif|avif|bmp)(\?|$)/i.test(x);if(g){const[T,m,S]=await Promise.all([b?ht(x):lt(x,{parent:this.shadow,loop:this.shouldLoop,muted:this.shouldMute}),Lt(y,d),Rt(e)]);if(t.aborted){T.dispose();return}r=T,s=m,a=S}else if(i){const[T,m,S]=await Promise.all([b?ht(x):lt(x,{parent:this.shadow,loop:this.shouldLoop,muted:this.shouldMute}),ut(i,K,J,u),Rt(e)]);if(t.aborted){T.dispose(),m.dispose();return}if(r=T,l=m,a=S,b||!r.isLive){const w=r.getImageSource();if(w){const F=await l.submitFrameAndWait(w);s={meta:{frameCount:1,fps:1,width:K,height:J,sourceFps:1},frames:[F]}}else s=N(K,J)}else s=N(K,J)}else throw new Error("Either depth-src/depth-meta or depth-model must be provided.")}this.source=r,this.depthEstimator=l,this.mesh=a,this.loopCount=0,this.attachSourceEventListeners(r);const h=this.container?.clientWidth||r.width,f=this.parallaxMax/Math.max(h,1);let c;if(l)c=()=>l.getLatestDepth();else{const x=new Ct(s);c=y=>x.sample(y)}const E={parallaxStrength:f,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};if(t.aborted)return;this.renderer=new tn(this.container,E),this.renderer.initialize(r,s.meta.width,s.meta.height,a),this.inputHandler=new Ft(this);const v=this.parallaxX,p=this.parallaxY,P=l;if(this.renderer.start(r,c,()=>{if(!this.inputHandler)return{x:0,y:0};const x=this.inputHandler.update();return{x:x.x*v,y:x.y*p}},(x,y)=>{if(P){const d=r.getImageSource();d&&P.submitFrame(d)}this.emit("layershift-portal:frame",{currentTime:x,frameNumber:y})}),!n&&r.isLive&&this.shouldAutoplay&&r.play)try{await r.play()}catch{}if(t.aborted)return;this.lifecycle.markInitialized(),this.emit("layershift-portal:ready",{videoWidth:r.width,videoHeight:r.height,duration:r.duration})}catch(r){const s=r instanceof Error?r.message:"Failed to initialize.";console.error("<layershift-portal>: Failed to initialize.",r),this.emit("layershift-portal:error",{message:s})}}doDispose(){this.renderer?.dispose(),this.renderer=null,this.inputHandler?.dispose(),this.inputHandler=null,this.depthEstimator?.dispose(),this.depthEstimator=null,this.source?.dispose(),this.source=null,this.mesh=null,this.loopCount=0,this.container=null}}function Q(o,t,e){return Math.min(e,Math.max(t,o))}function xt(o,t,e){return o+(t-o)*e}function Pn(o){const t=o.replace("#","");if(t.length===3){const e=parseInt(t[0]+t[0],16)/255,n=parseInt(t[1]+t[1],16)/255,i=parseInt(t[2]+t[2],16)/255;return[e,n,i]}if(t.length===6){const e=parseInt(t.substring(0,2),16)/255,n=parseInt(t.substring(2,4),16)/255,i=parseInt(t.substring(4,6),16)/255;return[e,n,i]}return[0,0,0]}return customElements.get(ft.TAG_NAME)||customElements.define(ft.TAG_NAME,ft),customElements.get(vt.TAG_NAME)||customElements.define(vt.TAG_NAME,vt),st.LayershiftElement=ft,st.LayershiftPortalElement=vt,Object.defineProperty(st,Symbol.toStringTag,{value:"Module"}),st})({});