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.
- package/dist/components/layershift.js +264 -1119
- package/dist/npm/layershift.es.js +2277 -3774
- package/dist/types/components/layershift/global.d.ts +2 -2
- package/dist/types/components/layershift/index.d.ts +5 -1
- package/dist/types/components/layershift/index.d.ts.map +1 -1
- package/dist/types/components/layershift/layershift-element.d.ts +39 -9
- package/dist/types/components/layershift/layershift-element.d.ts.map +1 -1
- package/dist/types/components/layershift/lifecycle.d.ts +6 -0
- package/dist/types/components/layershift/lifecycle.d.ts.map +1 -1
- package/dist/types/components/layershift/portal-element.d.ts +11 -3
- package/dist/types/components/layershift/portal-element.d.ts.map +1 -1
- package/dist/types/components/layershift/types.d.ts +22 -9
- package/dist/types/components/layershift/types.d.ts.map +1 -1
- package/dist/types/depth-analysis.d.ts +10 -6
- package/dist/types/depth-analysis.d.ts.map +1 -1
- package/dist/types/{parallax-renderer.d.ts → depth-effect-renderer.d.ts} +63 -15
- package/dist/types/depth-effect-renderer.d.ts.map +1 -0
- package/dist/types/depth-estimator.d.ts +96 -0
- package/dist/types/depth-estimator.d.ts.map +1 -0
- package/dist/types/input-handler.d.ts +8 -2
- package/dist/types/input-handler.d.ts.map +1 -1
- package/dist/types/media-source.d.ts +76 -0
- package/dist/types/media-source.d.ts.map +1 -0
- package/dist/types/portal-renderer.d.ts +2 -1
- package/dist/types/portal-renderer.d.ts.map +1 -1
- package/dist/types/precomputed-depth.d.ts +5 -0
- package/dist/types/precomputed-depth.d.ts.map +1 -1
- package/dist/types/renderer-base.d.ts +13 -8
- package/dist/types/renderer-base.d.ts.map +1 -1
- package/dist/types/shared/channel-to-renderer.d.ts +72 -0
- package/dist/types/shared/channel-to-renderer.d.ts.map +1 -0
- package/dist/types/shared/filter-config.d.ts +184 -0
- package/dist/types/shared/filter-config.d.ts.map +1 -0
- package/dist/types/video-source.d.ts +0 -1
- package/dist/types/video-source.d.ts.map +1 -1
- package/package.json +9 -3
- package/dist/types/gpu-backend.d.ts +0 -37
- package/dist/types/gpu-backend.d.ts.map +0 -1
- package/dist/types/parallax-renderer-webgpu.d.ts +0 -103
- package/dist/types/parallax-renderer-webgpu.d.ts.map +0 -1
- package/dist/types/parallax-renderer.d.ts.map +0 -1
- package/dist/types/portal-renderer-webgpu.d.ts +0 -199
- package/dist/types/portal-renderer-webgpu.d.ts.map +0 -1
- package/dist/types/render-pass-webgpu.d.ts +0 -76
- package/dist/types/render-pass-webgpu.d.ts.map +0 -1
- package/dist/types/webgpu-utils.d.ts +0 -42
- package/dist/types/webgpu-utils.d.ts.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var Layershift=(function(
|
|
2
|
-
${
|
|
3
|
-
${
|
|
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
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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=
|
|
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
|
-
*
|
|
251
|
+
* HSV to RGB conversion.
|
|
252
|
+
* Input: vec3(hue [0-1], saturation [0-1], value [0-1]).
|
|
147
253
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
355
|
-
currentLayerDepth += layerD;
|
|
403
|
+
color = mix(color, blurAccum, blurIntensity);
|
|
356
404
|
}
|
|
357
405
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
//
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
@group(0) @binding(2) var rawDepthSampler: sampler;
|
|
440
|
+
if (csIntensity > 0.001) {
|
|
441
|
+
vec3 hsv = rgb2hsv(color.rgb);
|
|
426
442
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
440
|
-
let neighbor = textureSampleLevel(rawDepthTex, rawDepthSampler, uv + offset, 0.0).r;
|
|
455
|
+
vec3 shifted = hsv2rgb(hsv);
|
|
441
456
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
457
|
+
// Tint: blend toward tint color
|
|
458
|
+
float tintAmount = uColorShiftTintStrength * csIntensity;
|
|
459
|
+
shifted = mix(shifted, uColorShiftTintColor, tintAmount);
|
|
445
460
|
|
|
446
|
-
|
|
447
|
-
totalDepth += neighbor * w;
|
|
461
|
+
color.rgb = shifted;
|
|
448
462
|
}
|
|
449
463
|
}
|
|
450
464
|
|
|
451
|
-
|
|
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(
|
|
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
|
-
`,
|
|
495
|
+
`,Ie=`#version 300 es
|
|
480
496
|
precision lowp float;
|
|
481
497
|
out vec4 fragColor;
|
|
482
498
|
void main() { fragColor = vec4(0.0); }
|
|
483
|
-
`,
|
|
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
|
-
`,
|
|
505
|
+
`,Ve=`#version 300 es
|
|
490
506
|
precision lowp float;
|
|
491
507
|
out vec4 fragColor;
|
|
492
508
|
void main() { fragColor = vec4(1.0); }
|
|
493
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
|
|
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
|
-
`,
|
|
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(
|
|
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})({});
|