layershift 0.2.2 → 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/README.md +14 -0
- package/dist/components/layershift.js +921 -647
- package/dist/npm/layershift.es.js +3354 -2266
- 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 +41 -10
- 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 +13 -4
- package/dist/types/components/layershift/portal-element.d.ts.map +1 -1
- package/dist/types/components/layershift/types.d.ts +30 -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/depth-effect-renderer.d.ts +186 -0
- 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/jfa-distance-field.d.ts +78 -0
- package/dist/types/jfa-distance-field.d.ts.map +1 -0
- 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 +65 -66
- package/dist/types/portal-renderer.d.ts.map +1 -1
- package/dist/types/precomputed-depth.d.ts +14 -51
- package/dist/types/precomputed-depth.d.ts.map +1 -1
- package/dist/types/quality.d.ts +95 -0
- package/dist/types/quality.d.ts.map +1 -0
- package/dist/types/render-pass.d.ts +171 -0
- package/dist/types/render-pass.d.ts.map +1 -0
- package/dist/types/renderer-base.d.ts +129 -0
- package/dist/types/renderer-base.d.ts.map +1 -0
- 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/dist/types/webgl-utils.d.ts +29 -0
- package/dist/types/webgl-utils.d.ts.map +1 -0
- package/package.json +31 -5
- package/dist/types/parallax-renderer.d.ts +0 -175
- package/dist/types/parallax-renderer.d.ts.map +0 -1
|
@@ -1,196 +1,472 @@
|
|
|
1
|
-
var Layershift=(function(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
in vec2 aPosition;
|
|
5
|
+
|
|
6
|
+
// UV coordinates for cover-fit + overscan.
|
|
7
|
+
// Computed on the CPU and passed as a uniform to avoid
|
|
8
|
+
// recreating geometry on every resize.
|
|
9
|
+
uniform vec2 uUvOffset;
|
|
10
|
+
uniform vec2 uUvScale;
|
|
11
|
+
|
|
12
|
+
out vec2 vUv;
|
|
13
|
+
out vec2 vScreenUv;
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
// Map from clip space [-1,1] to [0,1], then apply cover-fit transform
|
|
17
|
+
vec2 baseUv = aPosition * 0.5 + 0.5;
|
|
18
|
+
vUv = baseUv * uUvScale + uUvOffset;
|
|
19
|
+
// Screen-space UV always [0,1] -- used for tilted focal plane and edge fade
|
|
20
|
+
// which should operate on screen position, not texture coordinates.
|
|
21
|
+
vScreenUv = baseUv;
|
|
22
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
23
|
+
}
|
|
24
|
+
`,ve=`#version 300 es
|
|
25
|
+
in vec2 aPosition;
|
|
26
|
+
out vec2 vUv;
|
|
27
|
+
|
|
28
|
+
void main() {
|
|
29
|
+
vUv = aPosition * 0.5 + 0.5;
|
|
30
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
31
|
+
}
|
|
32
|
+
`,xe=`#version 300 es
|
|
33
|
+
precision highp float;
|
|
34
|
+
|
|
35
|
+
// BILATERAL_RADIUS is injected as a #define at compile time.
|
|
36
|
+
// Radius 2 -> 5x5 kernel (high/medium), radius 1 -> 3x3 kernel (low).
|
|
37
|
+
|
|
38
|
+
uniform sampler2D uRawDepth;
|
|
39
|
+
uniform vec2 uTexelSize;
|
|
40
|
+
uniform float uSpatialSigma2;
|
|
41
|
+
|
|
42
|
+
in vec2 vUv;
|
|
43
|
+
out vec4 fragColor;
|
|
44
|
+
|
|
45
|
+
void main() {
|
|
46
|
+
const float depthSigma2 = 0.01; // 0.1^2
|
|
47
|
+
|
|
48
|
+
float center = texture(uRawDepth, vUv).r;
|
|
49
|
+
float totalWeight = 1.0;
|
|
50
|
+
float totalDepth = center;
|
|
51
|
+
|
|
52
|
+
for (int dy = -BILATERAL_RADIUS; dy <= BILATERAL_RADIUS; dy++) {
|
|
53
|
+
for (int dx = -BILATERAL_RADIUS; dx <= BILATERAL_RADIUS; dx++) {
|
|
54
|
+
if (dx == 0 && dy == 0) continue;
|
|
55
|
+
|
|
56
|
+
vec2 offset = vec2(float(dx), float(dy)) * uTexelSize;
|
|
57
|
+
float neighbor = texture(uRawDepth, vUv + offset).r;
|
|
58
|
+
|
|
59
|
+
float spatialDist2 = float(dx * dx + dy * dy);
|
|
60
|
+
float depthDiff = neighbor - center;
|
|
61
|
+
float w = exp(-spatialDist2 / uSpatialSigma2 - (depthDiff * depthDiff) / depthSigma2);
|
|
62
|
+
|
|
63
|
+
totalWeight += w;
|
|
64
|
+
totalDepth += neighbor * w;
|
|
65
|
+
}
|
|
21
66
|
}
|
|
22
|
-
`,Gt=`#version 300 es
|
|
23
|
-
precision highp float;
|
|
24
67
|
|
|
25
|
-
|
|
68
|
+
fragColor = vec4(totalDepth / totalWeight, 0.0, 0.0, 1.0);
|
|
69
|
+
}
|
|
70
|
+
`,Te=`#version 300 es
|
|
71
|
+
precision highp float;
|
|
26
72
|
|
|
27
|
-
|
|
28
|
-
uniform sampler2D uImage;
|
|
73
|
+
// ---- Uniforms ----
|
|
29
74
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Pre-filtered with a bilateral filter on the CPU before upload,
|
|
33
|
-
* so a single texture() read gives smooth, edge-preserving depth.
|
|
34
|
-
* Uploaded as R8 format (auto-normalized to [0,1]).
|
|
35
|
-
*/
|
|
36
|
-
uniform sampler2D uDepth;
|
|
75
|
+
/** Color video frame, uploaded from HTMLVideoElement. */
|
|
76
|
+
uniform sampler2D uImage;
|
|
37
77
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Single-channel depth map (R channel, 0=far, 1=near).
|
|
80
|
+
* Depth Anything v2 outputs higher=closer; no inversion is applied.
|
|
81
|
+
* Bilateral-filtered on the GPU via a dedicated render pass,
|
|
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.
|
|
86
|
+
*/
|
|
87
|
+
uniform sampler2D uDepth;
|
|
43
88
|
|
|
44
|
-
|
|
45
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Current parallax input from mouse or gyroscope.
|
|
91
|
+
* Range [-1, 1] for both x (horizontal) and y (vertical).
|
|
92
|
+
*/
|
|
93
|
+
uniform vec2 uOffset;
|
|
46
94
|
|
|
47
|
-
|
|
48
|
-
|
|
95
|
+
/** Parallax displacement magnitude in UV space (e.g. 0.05 = 5%). */
|
|
96
|
+
uniform float uStrength;
|
|
49
97
|
|
|
50
|
-
|
|
51
|
-
|
|
98
|
+
/** Whether to use POM ray-marching instead of basic displacement. */
|
|
99
|
+
uniform bool uPomEnabled;
|
|
52
100
|
|
|
53
|
-
|
|
54
|
-
|
|
101
|
+
/** Number of ray-march steps for POM (runtime-adjustable). */
|
|
102
|
+
uniform int uPomSteps;
|
|
55
103
|
|
|
56
|
-
|
|
57
|
-
|
|
104
|
+
/** Smoothstep lower bound for depth contrast curve (depth-adaptive). */
|
|
105
|
+
uniform float uContrastLow;
|
|
58
106
|
|
|
59
|
-
|
|
60
|
-
|
|
107
|
+
/** Smoothstep upper bound for depth contrast curve (depth-adaptive). */
|
|
108
|
+
uniform float uContrastHigh;
|
|
61
109
|
|
|
62
|
-
|
|
63
|
-
|
|
110
|
+
/** Y-axis displacement multiplier (depth-adaptive). */
|
|
111
|
+
uniform float uVerticalReduction;
|
|
64
112
|
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
/** Depth threshold where DOF blur ramp begins (depth-adaptive). */
|
|
114
|
+
uniform float uDofStart;
|
|
67
115
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* Used by the depth-of-field effect to sample neighboring pixels.
|
|
71
|
-
*/
|
|
72
|
-
uniform vec2 uImageTexelSize;
|
|
116
|
+
/** Maximum DOF blur blend factor (depth-adaptive). */
|
|
117
|
+
uniform float uDofStrength;
|
|
73
118
|
|
|
74
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Texel size for video/image texture (1.0 / videoResolution).
|
|
121
|
+
* Used by the depth-of-field effect to sample neighboring pixels.
|
|
122
|
+
*/
|
|
123
|
+
uniform vec2 uImageTexelSize;
|
|
75
124
|
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
132
|
|
|
79
|
-
|
|
80
|
-
|
|
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;
|
|
81
139
|
|
|
82
|
-
|
|
83
|
-
|
|
140
|
+
/** Whether curve LUT textures are bound (0 = use legacy scalar math). */
|
|
141
|
+
uniform bool uCurvesEnabled;
|
|
84
142
|
|
|
85
|
-
|
|
143
|
+
/** Maximum blur radius in UV space, from blur channel params. */
|
|
144
|
+
uniform float uBlurRadius;
|
|
86
145
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* boundaries.
|
|
90
|
-
*/
|
|
91
|
-
float edgeFade(vec2 uv) {
|
|
92
|
-
float margin = uStrength * 1.5;
|
|
93
|
-
float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
|
|
94
|
-
float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
|
|
95
|
-
return fadeX * fadeY;
|
|
96
|
-
}
|
|
146
|
+
/** Per-frame depth offset for focal band shifting (0 = no shift). */
|
|
147
|
+
uniform float uFocalBandOffset;
|
|
97
148
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*/
|
|
101
|
-
float vignette(vec2 uv) {
|
|
102
|
-
float dist = length(uv - 0.5) * 1.4;
|
|
103
|
-
return 1.0 - pow(dist, 2.5);
|
|
104
|
-
}
|
|
149
|
+
/** Whether tilted focal plane blur is enabled (Scheimpflug simulation). */
|
|
150
|
+
uniform bool uTiltEnabled;
|
|
105
151
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
|
|
211
|
+
// ---- Varyings ----
|
|
212
|
+
|
|
213
|
+
/** Interpolated texture coordinates from vertex shader (cover-fit transformed). */
|
|
214
|
+
in vec2 vUv;
|
|
215
|
+
|
|
216
|
+
/** Screen-space UV [0,1] -- always covers the full viewport. */
|
|
217
|
+
in vec2 vScreenUv;
|
|
218
|
+
|
|
219
|
+
/** Fragment output color. */
|
|
220
|
+
out vec4 fragColor;
|
|
221
|
+
|
|
222
|
+
// ---- Helper functions ----
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Compute an edge fade factor that reduces displacement near UV
|
|
226
|
+
* boundaries.
|
|
227
|
+
*/
|
|
228
|
+
float edgeFade(vec2 uv) {
|
|
229
|
+
float margin = uStrength * 1.5;
|
|
230
|
+
float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
|
|
231
|
+
float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
|
|
232
|
+
return fadeX * fadeY;
|
|
233
|
+
}
|
|
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
|
+
|
|
250
|
+
/**
|
|
251
|
+
* HSV to RGB conversion.
|
|
252
|
+
* Input: vec3(hue [0-1], saturation [0-1], value [0-1]).
|
|
253
|
+
*/
|
|
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);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---- Displacement functions ----
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Basic UV displacement with edge fade.
|
|
264
|
+
*/
|
|
265
|
+
vec2 basicDisplace(vec2 uv) {
|
|
266
|
+
float depth = texture(uDepth, uv).r;
|
|
267
|
+
depth = smoothstep(uContrastLow, uContrastHigh, depth);
|
|
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;
|
|
119
274
|
}
|
|
120
275
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
276
|
+
displacement *= edgeFade(uv);
|
|
277
|
+
vec2 offset = uOffset * displacement;
|
|
278
|
+
offset.y *= uVerticalReduction;
|
|
279
|
+
return uv + offset;
|
|
280
|
+
}
|
|
126
281
|
|
|
127
|
-
|
|
128
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Parallax Occlusion Mapping (POM) ray-marching displacement.
|
|
284
|
+
*/
|
|
285
|
+
vec2 pomDisplace(vec2 uv) {
|
|
286
|
+
float layerDepth = 1.0 / float(uPomSteps);
|
|
129
287
|
|
|
130
|
-
|
|
288
|
+
vec2 scaledOffset = uOffset;
|
|
289
|
+
scaledOffset.y *= uVerticalReduction;
|
|
131
290
|
|
|
132
|
-
|
|
133
|
-
vec2 currentUV = uv;
|
|
291
|
+
vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
|
|
134
292
|
|
|
135
|
-
|
|
293
|
+
float currentLayerDepth = 0.0;
|
|
294
|
+
vec2 currentUV = uv;
|
|
136
295
|
|
|
137
|
-
|
|
138
|
-
if (i >= uPomSteps) break;
|
|
296
|
+
float fade = edgeFade(uv);
|
|
139
297
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
float depthAtUV = 1.0 - rawDepth;
|
|
298
|
+
for (int i = 0; i < MAX_POM_STEPS; i++) {
|
|
299
|
+
if (i >= uPomSteps) break;
|
|
143
300
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
301
|
+
float rawDepth = texture(uDepth, currentUV).r;
|
|
302
|
+
rawDepth = smoothstep(uContrastLow, uContrastHigh, rawDepth);
|
|
303
|
+
float depthAtUV;
|
|
304
|
+
if (uCurvesEnabled) {
|
|
305
|
+
// Curve outputs displacement intensity (far=high, near=low with descending curve).
|
|
306
|
+
// POM needs a height value where 0=surface, 1=deep. The displacement intensity
|
|
307
|
+
// IS the height: far objects are "deep" (high displacement), near are "surface".
|
|
308
|
+
depthAtUV = texture(uDisplacementCurve, vec2(rawDepth, 0.5)).r;
|
|
309
|
+
} else {
|
|
310
|
+
depthAtUV = 1.0 - rawDepth;
|
|
311
|
+
}
|
|
154
312
|
|
|
155
|
-
|
|
156
|
-
|
|
313
|
+
if (currentLayerDepth > depthAtUV) {
|
|
314
|
+
vec2 prevUV = currentUV - deltaUV;
|
|
315
|
+
float prevLayerDepth = currentLayerDepth - layerDepth;
|
|
316
|
+
|
|
317
|
+
float prevRaw = texture(uDepth, prevUV).r;
|
|
318
|
+
prevRaw = smoothstep(uContrastLow, uContrastHigh, prevRaw);
|
|
319
|
+
float prevDepthAtUV;
|
|
320
|
+
if (uCurvesEnabled) {
|
|
321
|
+
prevDepthAtUV = texture(uDisplacementCurve, vec2(prevRaw, 0.5)).r;
|
|
322
|
+
} else {
|
|
323
|
+
prevDepthAtUV = 1.0 - prevRaw;
|
|
157
324
|
}
|
|
158
325
|
|
|
159
|
-
|
|
160
|
-
|
|
326
|
+
float afterDepth = depthAtUV - currentLayerDepth;
|
|
327
|
+
float beforeDepth = prevDepthAtUV - prevLayerDepth;
|
|
328
|
+
float t = afterDepth / (afterDepth - beforeDepth);
|
|
329
|
+
|
|
330
|
+
vec2 hitUV = mix(currentUV, prevUV, t);
|
|
331
|
+
return mix(uv, hitUV, fade);
|
|
161
332
|
}
|
|
162
333
|
|
|
163
|
-
|
|
334
|
+
currentUV += deltaUV;
|
|
335
|
+
currentLayerDepth += layerDepth;
|
|
164
336
|
}
|
|
165
337
|
|
|
166
|
-
|
|
338
|
+
return mix(uv, currentUV, fade);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---- Main ----
|
|
342
|
+
|
|
343
|
+
void main() {
|
|
344
|
+
vec2 displaced = uPomEnabled ? pomDisplace(vUv) : basicDisplace(vUv);
|
|
345
|
+
displaced = clamp(displaced, vec2(0.0), vec2(1.0));
|
|
346
|
+
|
|
347
|
+
vec4 color = texture(uImage, displaced);
|
|
348
|
+
|
|
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
|
|
353
|
+
float dofDepth = texture(uDepth, displaced).r;
|
|
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
|
+
}
|
|
167
374
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
);
|
|
171
394
|
|
|
172
|
-
vec4
|
|
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);
|
|
400
|
+
}
|
|
401
|
+
blurAccum /= 13.0;
|
|
173
402
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
403
|
+
color = mix(color, blurAccum, blurIntensity);
|
|
404
|
+
}
|
|
405
|
+
|
|
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;
|
|
426
|
+
|
|
427
|
+
// Combine direct glow mask with spread, apply softness
|
|
428
|
+
float finalGlow = mix(spreadMask * 0.5, glowMask, uGlowSoftness);
|
|
429
|
+
color.rgb += uGlowColor * finalGlow;
|
|
430
|
+
}
|
|
431
|
+
|
|
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;
|
|
439
|
+
|
|
440
|
+
if (csIntensity > 0.001) {
|
|
441
|
+
vec3 hsv = rgb2hsv(color.rgb);
|
|
442
|
+
|
|
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);
|
|
446
|
+
|
|
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);
|
|
450
|
+
|
|
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);
|
|
454
|
+
|
|
455
|
+
vec3 shifted = hsv2rgb(hsv);
|
|
184
456
|
|
|
185
|
-
|
|
186
|
-
|
|
457
|
+
// Tint: blend toward tint color
|
|
458
|
+
float tintAmount = uColorShiftTintStrength * csIntensity;
|
|
459
|
+
shifted = mix(shifted, uColorShiftTintColor, tintAmount);
|
|
187
460
|
|
|
188
|
-
|
|
461
|
+
color.rgb = shifted;
|
|
462
|
+
}
|
|
189
463
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
#define MAX_POM_STEPS ${Z.MAX_POM_STEPS}`),i=Tt(t,t.VERTEX_SHADER,Wt),o=Tt(t,t.FRAGMENT_SHADER,e);this.program=jt(t,i,o),this.uniforms=Yt(t,this.program);const n=new Float32Array([-1,-1,1,-1,-1,1,1,1]);this.vao=t.createVertexArray(),t.bindVertexArray(this.vao);const s=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,s),t.bufferData(t.ARRAY_BUFFER,n,t.STATIC_DRAW);const h=t.getAttribLocation(this.program,"aPosition");t.enableVertexAttribArray(h),t.vertexAttribPointer(h,2,t.FLOAT,!1,0,0),t.bindVertexArray(null),t.disable(t.DEPTH_TEST)}static isRVFCSupported(){return"requestVideoFrameCallback"in HTMLVideoElement.prototype}videoFrameLoop=(t,e)=>{const i=this.playbackVideo;if(!i)return;this.rvfcHandle=i.requestVideoFrameCallback(this.videoFrameLoop);const o=e.mediaTime??i.currentTime;this.updateDepthTexture(o),this.onVideoFrame&&this.onVideoFrame(o,e.presentedFrames??0)};renderLoop=()=>{this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop);const t=this.gl,e=this.playbackVideo;if(!(!t||!this.program||!this.uniforms||!this.vao)&&!(!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)){if(t.useProgram(this.program),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),this.rvfcSupported||this.updateDepthTexture(e.currentTime),this.readInput){const i=this.readInput();t.uniform2f(this.uniforms.uOffset,-i.x,i.y)}t.bindVertexArray(this.vao),t.drawArrays(t.TRIANGLE_STRIP,0,4)}};updateDepthTexture(t){const e=this.gl;if(!e||!this.readDepth||!this.depthTexture)return;const i=this.readDepth(t);e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.depthTexture),e.texSubImage2D(e.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,e.RED,e.UNSIGNED_BYTE,i)}setupResizeHandling(){typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.scheduleResizeRecalculate()}),this.resizeObserver.observe(this.container)),window.addEventListener("resize",this.scheduleResizeRecalculate),this.recalculateViewportLayout()}scheduleResizeRecalculate=()=>{this.resizeTimer!==null&&window.clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.resizeTimer=null,this.recalculateViewportLayout()},Z.RESIZE_DEBOUNCE_MS)};recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:i}=this.getViewportSize(),o=Math.min(window.devicePixelRatio,2),n=Math.round(e*o),s=Math.round(i*o);(this.canvas.width!==n||this.canvas.height!==s)&&(this.canvas.width=n,this.canvas.height=s,t.viewport(0,0,n,s));const h=e/i,a=this.config.parallaxStrength+this.config.overscanPadding;let l=1,u=1;h>this.videoAspect?u=this.videoAspect/h:l=h/this.videoAspect;const f=1+a*2;l/=f,u/=f,this.uvOffset=[(1-l)/2,(1-u)/2],this.uvScale=[l,u],this.program&&this.uniforms&&(t.useProgram(this.program),t.uniform2f(this.uniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),t.uniform2f(this.uniforms.uUvScale,this.uvScale[0],this.uvScale[1]))}getViewportSize(){const t=Math.max(1,Math.round(this.container.clientWidth||window.innerWidth)),e=Math.max(1,Math.round(this.container.clientHeight||window.innerHeight));return{width:t,height:e}}handleContextLost=t=>{t.preventDefault(),this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0)};handleContextRestored=()=>{const t=this.canvas.getContext("webgl2");t&&(this.gl=t,t.clearColor(0,0,0,1),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.playbackVideo&&this.depthWidth>0&&this.initialize(this.playbackVideo,this.depthWidth,this.depthHeight),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)))};disposeTextures(){const t=this.gl;t&&(this.videoTexture&&(t.deleteTexture(this.videoTexture),this.videoTexture=null),this.depthTexture&&(t.deleteTexture(this.depthTexture),this.depthTexture=null))}disposeGPUResources(){const t=this.gl;t&&(this.program&&(t.deleteProgram(this.program),this.program=null),this.vao&&(t.deleteVertexArray(this.vao),this.vao=null),this.uniforms=null)}}class Et{abortController=null;initialized=!1;initializing=!1;element;constructor(t){this.element=t}onConnected(){this.element.setupShadowDOM(),this.tryInit()}onDisconnected(){this.cancelInit(),this.element.doDispose(),this.initialized=!1}onAttributeChanged(t,e,i){this.element.reinitAttributes.includes(t)&&e!==i&&(this.initialized?(this.cancelInit(),this.element.doDispose(),this.initialized=!1,this.element.setupShadowDOM(),this.tryInit()):this.initializing||this.tryInit())}get isInitialized(){return this.initialized}markInitialized(){this.initialized=!0,this.initializing=!1}async tryInit(){if(this.initializing)return;const t=this.element;if(!t.isConnected)return;for(const i of t.reinitAttributes)if(!t.getAttribute(i))return;this.cancelInit();const e=new AbortController;this.abortController=e,this.initializing=!0;try{if(await t.doInit(e.signal),e.signal.aborted){this.initializing=!1;return}}catch{this.initializing=!1}}cancelInit(){this.abortController?.abort(),this.abortController=null,this.initializing=!1}}const H={parallaxX:.4,parallaxY:1,parallaxMax:30,overscan:.05,autoplay:!0,loop:!0,muted:!0};let qt=class Ct{constructor(t,e=.08,i=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const t=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,e=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=ot(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=ot(this.smoothedOutput.y,t.y,e),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=t=>{const e=this.host.getBoundingClientRect(),i=(t.clientX-e.left)/e.width*2-1,o=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=N(i,-1,1),this.pointerTarget.y=N(o,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=t=>{const e=t.touches[0];e&&(this.touchActive=!0,this.touchAnchorX=e.clientX,this.touchAnchorY=e.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=t=>{const e=t.touches[0];if(!e)return;const i=e.clientX-this.touchAnchorX,o=e.clientY-this.touchAnchorY,n=Ct.TOUCH_DRAG_RANGE;this.pointerTarget.x=N(i/n,-1,1),this.pointerTarget.y=N(o/n,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const t=DeviceOrientationEvent;if(typeof t.requestPermission=="function")try{if(await t.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=t=>{const e=N((t.gamma??0)/45,-1,1),i=N((t.beta??0)/45,-1,1);this.motionTarget.x=ot(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=ot(this.motionTarget.y,i,this.motionLerpFactor)}};class rt extends HTMLElement{static TAG_NAME="layershift-parallax";static get observedAttributes(){return["src","depth-src","depth-meta","parallax-x","parallax-y","parallax-max","layers","overscan","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta"];shadow;container=null;renderer=null;inputHandler=null;depthWorker=null;video=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Et(this)}getAttrFloat(t,e){const i=this.getAttribute(t);if(i===null)return e;const o=parseFloat(i);return Number.isFinite(o)?o:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return!(i==="false"||i==="0")}get parallaxX(){return this.getAttrFloat("parallax-x",H.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",H.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",H.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",H.overscan)}get shouldAutoplay(){return this.getAttrBool("autoplay",H.autoplay)}get shouldLoop(){return this.getAttrBool("loop",H.loop)}get shouldMute(){return this.getAttrBool("muted",H.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachVideoEventListeners(t){t.addEventListener("play",()=>{this.emit("layershift-parallax:play",{currentTime:t.currentTime})}),t.addEventListener("pause",()=>{this.emit("layershift-parallax:pause",{currentTime:t.currentTime})}),t.addEventListener("ended",()=>{t.loop&&(this.loopCount+=1,this.emit("layershift-parallax:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,i){this.lifecycle.onAttributeChanged(t,e,i)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
|
|
464
|
+
|
|
465
|
+
fragColor = color;
|
|
466
|
+
}
|
|
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=`
|
|
194
470
|
:host {
|
|
195
471
|
display: block;
|
|
196
472
|
width: 100%;
|
|
@@ -210,517 +486,515 @@ ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.delete
|
|
|
210
486
|
width: 100%;
|
|
211
487
|
height: 100%;
|
|
212
488
|
}
|
|
213
|
-
`,this.shadow.appendChild(t),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
`,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
`,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
`,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
`,ee=`#version 300 es
|
|
266
|
-
in vec2 aPosition;
|
|
267
|
-
out vec2 vUv;
|
|
268
|
-
void main() {
|
|
269
|
-
vUv = aPosition * 0.5 + 0.5;
|
|
270
|
-
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
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
|
|
490
|
+
in vec2 aPosition;
|
|
491
|
+
uniform vec2 uMeshScale;
|
|
492
|
+
void main() {
|
|
493
|
+
gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
|
|
494
|
+
}
|
|
495
|
+
`,Ie=`#version 300 es
|
|
496
|
+
precision lowp float;
|
|
497
|
+
out vec4 fragColor;
|
|
498
|
+
void main() { fragColor = vec4(0.0); }
|
|
499
|
+
`,Be=`#version 300 es
|
|
500
|
+
in vec2 aPosition;
|
|
501
|
+
uniform vec2 uMeshScale;
|
|
502
|
+
void main() {
|
|
503
|
+
gl_Position = vec4(aPosition * uMeshScale, 0.0, 1.0);
|
|
504
|
+
}
|
|
505
|
+
`,Ve=`#version 300 es
|
|
506
|
+
precision lowp float;
|
|
507
|
+
out vec4 fragColor;
|
|
508
|
+
void main() { fragColor = vec4(1.0); }
|
|
509
|
+
`,Oe=`#version 300 es
|
|
510
|
+
in vec2 aPosition;
|
|
511
|
+
out vec2 vUv;
|
|
512
|
+
void main() {
|
|
513
|
+
vUv = aPosition * 0.5 + 0.5;
|
|
514
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
515
|
+
}
|
|
516
|
+
`,ke=`#version 300 es
|
|
517
|
+
precision highp float;
|
|
518
|
+
uniform sampler2D uMask;
|
|
519
|
+
uniform vec2 uTexelSize;
|
|
520
|
+
in vec2 vUv;
|
|
521
|
+
out vec2 fragSeed;
|
|
522
|
+
|
|
523
|
+
void main() {
|
|
524
|
+
float center = texture(uMask, vUv).r;
|
|
525
|
+
float left = texture(uMask, vUv + vec2(-uTexelSize.x, 0.0)).r;
|
|
526
|
+
float right = texture(uMask, vUv + vec2( uTexelSize.x, 0.0)).r;
|
|
527
|
+
float up = texture(uMask, vUv + vec2(0.0, uTexelSize.y)).r;
|
|
528
|
+
float down = texture(uMask, vUv + vec2(0.0, -uTexelSize.y)).r;
|
|
529
|
+
|
|
530
|
+
bool isEdge = (step(0.5, center) != step(0.5, left)) ||
|
|
531
|
+
(step(0.5, center) != step(0.5, right)) ||
|
|
532
|
+
(step(0.5, center) != step(0.5, up)) ||
|
|
533
|
+
(step(0.5, center) != step(0.5, down));
|
|
534
|
+
|
|
535
|
+
if (isEdge) {
|
|
536
|
+
fragSeed = vUv;
|
|
537
|
+
} else {
|
|
538
|
+
fragSeed = vec2(-1.0);
|
|
271
539
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
540
|
+
}
|
|
541
|
+
`,Ne=`#version 300 es
|
|
542
|
+
in vec2 aPosition;
|
|
543
|
+
out vec2 vUv;
|
|
544
|
+
void main() {
|
|
545
|
+
vUv = aPosition * 0.5 + 0.5;
|
|
546
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
547
|
+
}
|
|
548
|
+
`,He=`#version 300 es
|
|
549
|
+
precision highp float;
|
|
550
|
+
uniform sampler2D uSeedTex;
|
|
551
|
+
uniform float uStepSize;
|
|
552
|
+
in vec2 vUv;
|
|
553
|
+
out vec2 fragSeed;
|
|
554
|
+
|
|
555
|
+
void main() {
|
|
556
|
+
vec2 bestSeed = texture(uSeedTex, vUv).rg;
|
|
557
|
+
float bestDist = (bestSeed.x < 0.0) ? 1.0e10 : distance(vUv, bestSeed);
|
|
558
|
+
|
|
559
|
+
for (int dy = -1; dy <= 1; dy++) {
|
|
560
|
+
for (int dx = -1; dx <= 1; dx++) {
|
|
561
|
+
if (dx == 0 && dy == 0) continue;
|
|
562
|
+
vec2 offset = vec2(float(dx), float(dy)) * uStepSize;
|
|
563
|
+
vec2 sampleUv = vUv + offset;
|
|
564
|
+
if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
|
|
565
|
+
vec2 neighborSeed = texture(uSeedTex, sampleUv).rg;
|
|
566
|
+
if (neighborSeed.x < 0.0) continue;
|
|
567
|
+
float d = distance(vUv, neighborSeed);
|
|
568
|
+
if (d < bestDist) {
|
|
569
|
+
bestDist = d;
|
|
570
|
+
bestSeed = neighborSeed;
|
|
296
571
|
}
|
|
297
572
|
}
|
|
298
|
-
|
|
299
|
-
fragSeed = bestSeed;
|
|
300
|
-
}
|
|
301
|
-
`,re=`#version 300 es
|
|
302
|
-
in vec2 aPosition;
|
|
303
|
-
out vec2 vUv;
|
|
304
|
-
void main() {
|
|
305
|
-
vUv = aPosition * 0.5 + 0.5;
|
|
306
|
-
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
307
573
|
}
|
|
308
|
-
`,oe=`#version 300 es
|
|
309
|
-
precision highp float;
|
|
310
|
-
uniform sampler2D uSeedTex;
|
|
311
|
-
uniform sampler2D uMask;
|
|
312
|
-
uniform float uBevelWidth;
|
|
313
|
-
in vec2 vUv;
|
|
314
|
-
out vec4 fragDist;
|
|
315
|
-
|
|
316
|
-
void main() {
|
|
317
|
-
float mask = texture(uMask, vUv).r;
|
|
318
|
-
if (mask < 0.5) {
|
|
319
|
-
fragDist = vec4(0.0);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
vec2 seed = texture(uSeedTex, vUv).rg;
|
|
324
|
-
if (seed.x < 0.0) {
|
|
325
|
-
fragDist = vec4(1.0);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
574
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
uniform sampler2D uDepth;
|
|
352
|
-
uniform vec2 uOffset;
|
|
353
|
-
uniform float uStrength;
|
|
354
|
-
uniform int uPomSteps;
|
|
355
|
-
|
|
356
|
-
// Lens transform: remap depth curve for exaggerated/compressed depth feel
|
|
357
|
-
uniform float uDepthPower; // >1 = telephoto, <1 = wide-angle
|
|
358
|
-
uniform float uDepthScale; // multiplier on depth range
|
|
359
|
-
uniform float uDepthBias; // shift depth origin
|
|
360
|
-
|
|
361
|
-
// Depth-adaptive contrast
|
|
362
|
-
uniform float uContrastLow;
|
|
363
|
-
uniform float uContrastHigh;
|
|
364
|
-
uniform float uVerticalReduction;
|
|
365
|
-
|
|
366
|
-
// DOF
|
|
367
|
-
uniform float uDofStart;
|
|
368
|
-
uniform float uDofStrength;
|
|
369
|
-
uniform vec2 uImageTexelSize;
|
|
370
|
-
|
|
371
|
-
// Interior mood
|
|
372
|
-
uniform float uFogDensity; // volumetric fog bias (0 = none, 0.3 = subtle)
|
|
373
|
-
uniform vec3 uFogColor; // fog tint color
|
|
374
|
-
uniform float uColorShift; // warm/cool grading shift
|
|
375
|
-
uniform float uBrightnessBias; // overall brightness adjustment
|
|
376
|
-
|
|
377
|
-
in vec2 vUv;
|
|
378
|
-
in vec2 vScreenUv;
|
|
379
|
-
|
|
380
|
-
layout(location = 0) out vec4 fragColor;
|
|
381
|
-
layout(location = 1) out vec4 fragDepth;
|
|
382
|
-
|
|
383
|
-
// Apply lens transform to raw depth
|
|
384
|
-
float lensDepth(float raw) {
|
|
385
|
-
float d = smoothstep(uContrastLow, uContrastHigh, raw);
|
|
386
|
-
d = pow(d, uDepthPower) * uDepthScale + uDepthBias;
|
|
387
|
-
return clamp(d, 0.0, 1.0);
|
|
575
|
+
fragSeed = bestSeed;
|
|
576
|
+
}
|
|
577
|
+
`,Xe=`#version 300 es
|
|
578
|
+
in vec2 aPosition;
|
|
579
|
+
out vec2 vUv;
|
|
580
|
+
void main() {
|
|
581
|
+
vUv = aPosition * 0.5 + 0.5;
|
|
582
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
583
|
+
}
|
|
584
|
+
`,Ge=`#version 300 es
|
|
585
|
+
precision highp float;
|
|
586
|
+
uniform sampler2D uSeedTex;
|
|
587
|
+
uniform sampler2D uMask;
|
|
588
|
+
uniform float uBevelWidth;
|
|
589
|
+
in vec2 vUv;
|
|
590
|
+
out vec4 fragDist;
|
|
591
|
+
|
|
592
|
+
void main() {
|
|
593
|
+
float mask = texture(uMask, vUv).r;
|
|
594
|
+
if (mask < 0.5) {
|
|
595
|
+
fragDist = vec4(0.0);
|
|
596
|
+
return;
|
|
388
597
|
}
|
|
389
598
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return fadeX * fadeY;
|
|
599
|
+
vec2 seed = texture(uSeedTex, vUv).rg;
|
|
600
|
+
if (seed.x < 0.0) {
|
|
601
|
+
fragDist = vec4(1.0);
|
|
602
|
+
return;
|
|
395
603
|
}
|
|
396
604
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
605
|
+
float d = distance(vUv, seed);
|
|
606
|
+
float normalized = clamp(d / max(uBevelWidth, 0.001), 0.0, 1.0);
|
|
607
|
+
fragDist = vec4(normalized, 0.0, 0.0, 1.0);
|
|
608
|
+
}
|
|
609
|
+
`,We=`#version 300 es
|
|
610
|
+
in vec2 aPosition;
|
|
611
|
+
uniform vec2 uUvOffset;
|
|
612
|
+
uniform vec2 uUvScale;
|
|
613
|
+
out vec2 vUv;
|
|
614
|
+
out vec2 vScreenUv;
|
|
615
|
+
void main() {
|
|
616
|
+
vec2 baseUv = aPosition * 0.5 + 0.5;
|
|
617
|
+
vUv = baseUv * uUvScale + uUvOffset;
|
|
618
|
+
vScreenUv = baseUv;
|
|
619
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
620
|
+
}
|
|
621
|
+
`,ze=`#version 300 es
|
|
622
|
+
precision highp float;
|
|
623
|
+
|
|
624
|
+
#define MAX_POM_STEPS 32
|
|
625
|
+
|
|
626
|
+
uniform sampler2D uImage;
|
|
627
|
+
uniform sampler2D uDepth;
|
|
628
|
+
uniform vec2 uOffset;
|
|
629
|
+
uniform float uStrength;
|
|
630
|
+
uniform int uPomSteps;
|
|
631
|
+
|
|
632
|
+
// Lens transform: remap depth curve for exaggerated/compressed depth feel
|
|
633
|
+
uniform float uDepthPower; // >1 = telephoto, <1 = wide-angle
|
|
634
|
+
uniform float uDepthScale; // multiplier on depth range
|
|
635
|
+
uniform float uDepthBias; // shift depth origin
|
|
636
|
+
|
|
637
|
+
// Depth-adaptive contrast
|
|
638
|
+
uniform float uContrastLow;
|
|
639
|
+
uniform float uContrastHigh;
|
|
640
|
+
uniform float uVerticalReduction;
|
|
641
|
+
|
|
642
|
+
// DOF
|
|
643
|
+
uniform float uDofStart;
|
|
644
|
+
uniform float uDofStrength;
|
|
645
|
+
uniform vec2 uImageTexelSize;
|
|
646
|
+
|
|
647
|
+
// Interior mood
|
|
648
|
+
uniform float uFogDensity; // volumetric fog bias (0 = none, 0.3 = subtle)
|
|
649
|
+
uniform vec3 uFogColor; // fog tint color
|
|
650
|
+
uniform float uColorShift; // warm/cool grading shift
|
|
651
|
+
uniform float uBrightnessBias; // overall brightness adjustment
|
|
652
|
+
|
|
653
|
+
in vec2 vUv;
|
|
654
|
+
in vec2 vScreenUv;
|
|
655
|
+
|
|
656
|
+
layout(location = 0) out vec4 fragColor;
|
|
657
|
+
layout(location = 1) out vec4 fragDepth;
|
|
658
|
+
|
|
659
|
+
// Apply lens transform to raw depth
|
|
660
|
+
float lensDepth(float raw) {
|
|
661
|
+
float d = smoothstep(uContrastLow, uContrastHigh, raw);
|
|
662
|
+
d = pow(d, uDepthPower) * uDepthScale + uDepthBias;
|
|
663
|
+
return clamp(d, 0.0, 1.0);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
float edgeFade(vec2 uv) {
|
|
667
|
+
float margin = uStrength * 1.5;
|
|
668
|
+
float fadeX = smoothstep(0.0, margin, uv.x) * smoothstep(0.0, margin, 1.0 - uv.x);
|
|
669
|
+
float fadeY = smoothstep(0.0, margin, uv.y) * smoothstep(0.0, margin, 1.0 - uv.y);
|
|
670
|
+
return fadeX * fadeY;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// POM ray-march with lens-transformed depth
|
|
674
|
+
vec2 pomDisplace(vec2 uv, out float hitDepth) {
|
|
675
|
+
float layerD = 1.0 / float(uPomSteps);
|
|
676
|
+
vec2 scaledOffset = uOffset;
|
|
677
|
+
scaledOffset.y *= uVerticalReduction;
|
|
678
|
+
vec2 deltaUV = scaledOffset * uStrength / float(uPomSteps);
|
|
679
|
+
float currentLayerDepth = 0.0;
|
|
680
|
+
vec2 currentUV = uv;
|
|
681
|
+
float fade = edgeFade(uv);
|
|
682
|
+
|
|
683
|
+
for (int i = 0; i < MAX_POM_STEPS; i++) {
|
|
684
|
+
if (i >= uPomSteps) break;
|
|
685
|
+
float raw = texture(uDepth, currentUV).r;
|
|
686
|
+
float depthAtUV = 1.0 - lensDepth(raw);
|
|
687
|
+
if (currentLayerDepth > depthAtUV) {
|
|
688
|
+
vec2 prevUV = currentUV - deltaUV;
|
|
689
|
+
float prevLayerD = currentLayerDepth - layerD;
|
|
690
|
+
float prevRaw = texture(uDepth, prevUV).r;
|
|
691
|
+
float prevDepthAtUV = 1.0 - lensDepth(prevRaw);
|
|
692
|
+
float afterD = depthAtUV - currentLayerDepth;
|
|
693
|
+
float beforeD = prevDepthAtUV - prevLayerD;
|
|
694
|
+
float t = afterD / (afterD - beforeD);
|
|
695
|
+
vec2 hitUV = mix(currentUV, prevUV, t);
|
|
696
|
+
hitDepth = mix(depthAtUV, prevDepthAtUV, t);
|
|
697
|
+
return mix(uv, hitUV, fade);
|
|
425
698
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
void main() {
|
|
431
|
-
float hitDepth;
|
|
432
|
-
vec2 displaced = pomDisplace(vUv, hitDepth);
|
|
433
|
-
displaced = clamp(displaced, vec2(0.0), vec2(1.0));
|
|
434
|
-
|
|
435
|
-
vec4 color = texture(uImage, displaced);
|
|
436
|
-
|
|
437
|
-
// DOF: blur far objects
|
|
438
|
-
float rawDepthAtHit = texture(uDepth, displaced).r;
|
|
439
|
-
float lensD = lensDepth(rawDepthAtHit);
|
|
440
|
-
float dof = smoothstep(uDofStart, 1.0, lensD) * uDofStrength;
|
|
441
|
-
if (dof > 0.01) {
|
|
442
|
-
vec2 ts = uImageTexelSize;
|
|
443
|
-
vec4 blurred = (
|
|
444
|
-
texture(uImage, displaced + vec2( ts.x, 0.0)) +
|
|
445
|
-
texture(uImage, displaced + vec2(-ts.x, 0.0)) +
|
|
446
|
-
texture(uImage, displaced + vec2( 0.0, ts.y)) +
|
|
447
|
-
texture(uImage, displaced + vec2( 0.0, -ts.y)) +
|
|
448
|
-
texture(uImage, displaced + vec2( ts.x, ts.y)) +
|
|
449
|
-
texture(uImage, displaced + vec2(-ts.x, -ts.y)) +
|
|
450
|
-
texture(uImage, displaced + vec2( ts.x, -ts.y)) +
|
|
451
|
-
texture(uImage, displaced + vec2(-ts.x, ts.y))
|
|
452
|
-
) * 0.125;
|
|
453
|
-
color = mix(color, blurred, dof);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Volumetric fog bias: far objects fade into fog color
|
|
457
|
-
float fogFactor = smoothstep(0.3, 1.0, lensD) * uFogDensity;
|
|
458
|
-
color.rgb = mix(color.rgb, uFogColor, fogFactor);
|
|
459
|
-
|
|
460
|
-
// Color grading shift: warm near, cool far (or vice versa)
|
|
461
|
-
float gradeAmount = (lensD - 0.5) * uColorShift;
|
|
462
|
-
color.r += gradeAmount * 0.08;
|
|
463
|
-
color.b -= gradeAmount * 0.08;
|
|
464
|
-
|
|
465
|
-
// Brightness bias
|
|
466
|
-
color.rgb *= (1.0 + uBrightnessBias);
|
|
467
|
-
|
|
468
|
-
// Subtle vignette inside portal
|
|
469
|
-
float dist = length(vScreenUv - 0.5) * 1.4;
|
|
470
|
-
color.rgb *= 1.0 - pow(dist, 3.0) * 0.3;
|
|
471
|
-
|
|
472
|
-
fragColor = color;
|
|
473
|
-
// Write lens-transformed depth to second attachment for boundary effects
|
|
474
|
-
fragDepth = vec4(lensD, 0.0, 0.0, 1.0);
|
|
475
|
-
}
|
|
476
|
-
`,ae=`#version 300 es
|
|
477
|
-
in vec2 aPosition;
|
|
478
|
-
out vec2 vUv;
|
|
479
|
-
void main() {
|
|
480
|
-
vUv = aPosition * 0.5 + 0.5;
|
|
481
|
-
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
699
|
+
currentUV += deltaUV;
|
|
700
|
+
currentLayerDepth += layerD;
|
|
482
701
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
fragColor = vec4(toSRGB(linear), color.a);
|
|
512
|
-
}
|
|
513
|
-
`,le=`#version 300 es
|
|
514
|
-
in vec2 aPosition;
|
|
515
|
-
in vec2 aNormal;
|
|
516
|
-
uniform float uRimWidth;
|
|
517
|
-
uniform vec2 uMeshScale;
|
|
518
|
-
out vec2 vNormal;
|
|
519
|
-
out vec2 vEdgeUv; // screen-space UV for sampling FBO textures
|
|
520
|
-
out float vEdgeDist; // 0 at edge, 1 at outer extent
|
|
521
|
-
|
|
522
|
-
void main() {
|
|
523
|
-
vec2 scaledPos = aPosition * uMeshScale;
|
|
524
|
-
vec2 scaledNormal = normalize(aNormal * uMeshScale);
|
|
525
|
-
vec2 pos = scaledPos + scaledNormal * uRimWidth;
|
|
526
|
-
|
|
527
|
-
// Pass screen-space UV of this fragment for FBO sampling
|
|
528
|
-
vEdgeUv = pos * 0.5 + 0.5;
|
|
529
|
-
vNormal = scaledNormal;
|
|
530
|
-
|
|
531
|
-
// Distance from the actual edge (0) to the outer rim extent (1)
|
|
532
|
-
vEdgeDist = length(pos - scaledPos) / max(uRimWidth, 0.001);
|
|
533
|
-
|
|
534
|
-
gl_Position = vec4(pos, 0.0, 1.0);
|
|
535
|
-
}
|
|
536
|
-
`,ce=`#version 300 es
|
|
537
|
-
precision highp float;
|
|
538
|
-
|
|
539
|
-
uniform sampler2D uInteriorColor;
|
|
540
|
-
uniform sampler2D uInteriorDepth;
|
|
541
|
-
uniform sampler2D uDistField;
|
|
542
|
-
uniform float uRimIntensity;
|
|
543
|
-
uniform vec3 uRimColor;
|
|
544
|
-
uniform float uRefractionStrength;
|
|
545
|
-
uniform float uChromaticStrength;
|
|
546
|
-
uniform float uOcclusionIntensity;
|
|
547
|
-
uniform vec2 uTexelSize; // 1.0 / viewport resolution
|
|
548
|
-
|
|
549
|
-
// Volumetric edge wall
|
|
550
|
-
uniform float uEdgeThickness;
|
|
551
|
-
uniform float uEdgeSpecular;
|
|
552
|
-
uniform vec3 uEdgeColor;
|
|
553
|
-
uniform vec2 uLightDir;
|
|
554
|
-
uniform float uBevelIntensity;
|
|
555
|
-
|
|
556
|
-
in vec2 vNormal;
|
|
557
|
-
in vec2 vEdgeUv;
|
|
558
|
-
in float vEdgeDist;
|
|
559
|
-
out vec4 fragColor;
|
|
560
|
-
|
|
561
|
-
void main() {
|
|
562
|
-
// Clamp UV to valid range for texture sampling
|
|
563
|
-
vec2 sampleUv = clamp(vEdgeUv, vec2(0.001), vec2(0.999));
|
|
564
|
-
|
|
565
|
-
// Sample interior depth at this boundary location
|
|
566
|
-
float interiorDepth = texture(uInteriorDepth, sampleUv).r;
|
|
567
|
-
|
|
568
|
-
// === DEPTH-REACTIVE RIM (structural seam) ===
|
|
569
|
-
float depthReactivity = 1.0 - interiorDepth; // 1=near, 0=far
|
|
570
|
-
float rimProfile = 1.0 - smoothstep(0.0, 1.0, vEdgeDist);
|
|
571
|
-
rimProfile = pow(rimProfile, 1.5); // sharper falloff = more structural
|
|
572
|
-
|
|
573
|
-
float depthPressure = mix(0.2, 1.0, depthReactivity * depthReactivity);
|
|
574
|
-
float rim = rimProfile * depthPressure * uRimIntensity;
|
|
575
|
-
|
|
576
|
-
vec3 rimCol = uRimColor;
|
|
577
|
-
rimCol.r += depthReactivity * 0.15;
|
|
578
|
-
rimCol.g += depthReactivity * 0.05;
|
|
579
|
-
|
|
580
|
-
// === REFRACTION DISTORTION ===
|
|
581
|
-
vec2 ts = uTexelSize * 3.0;
|
|
582
|
-
float dLeft = texture(uInteriorDepth, sampleUv + vec2(-ts.x, 0.0)).r;
|
|
583
|
-
float dRight = texture(uInteriorDepth, sampleUv + vec2( ts.x, 0.0)).r;
|
|
584
|
-
float dUp = texture(uInteriorDepth, sampleUv + vec2(0.0, ts.y)).r;
|
|
585
|
-
float dDown = texture(uInteriorDepth, sampleUv + vec2(0.0, -ts.y)).r;
|
|
586
|
-
vec2 depthGradient = vec2(dRight - dLeft, dUp - dDown);
|
|
587
|
-
vec2 refractUv = sampleUv + depthGradient * uRefractionStrength * rimProfile;
|
|
588
|
-
refractUv = clamp(refractUv, vec2(0.001), vec2(0.999));
|
|
589
|
-
|
|
590
|
-
vec4 refractedColor = texture(uInteriorColor, refractUv);
|
|
591
|
-
|
|
592
|
-
// === CHROMATIC FRINGE ===
|
|
593
|
-
float chromaticAmount = uChromaticStrength * depthReactivity * rimProfile;
|
|
594
|
-
vec2 chromaticDir = vNormal * chromaticAmount;
|
|
595
|
-
float cr = texture(uInteriorColor, refractUv + chromaticDir).r;
|
|
596
|
-
float cg = refractedColor.g;
|
|
597
|
-
float cb = texture(uInteriorColor, refractUv - chromaticDir).b;
|
|
598
|
-
vec3 chromaticColor = vec3(cr, cg, cb);
|
|
599
|
-
|
|
600
|
-
// === OCCLUSION CONTACT SHADOW ===
|
|
601
|
-
float occlusionAmount = smoothstep(0.4, 0.0, interiorDepth) * uOcclusionIntensity * rimProfile;
|
|
602
|
-
|
|
603
|
-
// === VOLUMETRIC EDGE WALL ===
|
|
604
|
-
// Sample distance field to get the inner-side distance at this boundary location
|
|
605
|
-
float edgeDist = texture(uDistField, sampleUv).r;
|
|
606
|
-
float wallZone = smoothstep(uEdgeThickness, 0.0, edgeDist) * rimProfile;
|
|
607
|
-
|
|
608
|
-
// Wall lighting from distance field gradient
|
|
609
|
-
vec2 dtx = vec2(1.0) / vec2(textureSize(uDistField, 0));
|
|
610
|
-
float wdL = texture(uDistField, sampleUv + vec2(-dtx.x, 0.0)).r;
|
|
611
|
-
float wdR = texture(uDistField, sampleUv + vec2( dtx.x, 0.0)).r;
|
|
612
|
-
float wdU = texture(uDistField, sampleUv + vec2(0.0, dtx.y)).r;
|
|
613
|
-
float wdD = texture(uDistField, sampleUv + vec2(0.0, -dtx.y)).r;
|
|
614
|
-
vec2 wallNormal = vec2(wdR - wdL, wdU - wdD);
|
|
615
|
-
float wnLen = length(wallNormal);
|
|
616
|
-
if (wnLen > 0.001) wallNormal /= wnLen;
|
|
617
|
-
|
|
618
|
-
float wallSpec = pow(max(dot(wallNormal, uLightDir), 0.0), 16.0) * uEdgeSpecular;
|
|
619
|
-
vec3 wallColor = mix(refractedColor.rgb * 0.4, uEdgeColor, 0.3);
|
|
620
|
-
wallColor += vec3(wallSpec);
|
|
621
|
-
|
|
622
|
-
// === COMPOSITE ===
|
|
623
|
-
vec3 color = mix(refractedColor.rgb, chromaticColor, min(chromaticAmount * 10.0, 1.0));
|
|
624
|
-
color *= (1.0 - occlusionAmount * 0.4);
|
|
625
|
-
|
|
626
|
-
// Blend in volumetric wall
|
|
627
|
-
color = mix(color, wallColor, wallZone * uBevelIntensity);
|
|
628
|
-
|
|
629
|
-
// Add rim energy on top
|
|
630
|
-
color += rimCol * rim;
|
|
631
|
-
|
|
632
|
-
// Alpha: rim edge fades out
|
|
633
|
-
float alpha = rimProfile * max(rim, occlusionAmount + chromaticAmount * 5.0 + wallZone * 0.5);
|
|
634
|
-
alpha = clamp(alpha, 0.0, 1.0);
|
|
635
|
-
|
|
636
|
-
fragColor = vec4(color * alpha, alpha);
|
|
637
|
-
}
|
|
638
|
-
`,ue=`#version 300 es
|
|
639
|
-
in vec2 aPosition;
|
|
640
|
-
in vec3 aNormal3;
|
|
641
|
-
in float aLerpT; // 0 = inner (at silhouette), 1 = outer edge
|
|
642
|
-
uniform vec2 uMeshScale;
|
|
643
|
-
out vec3 vNormal;
|
|
644
|
-
out vec2 vScreenUv;
|
|
645
|
-
out float vLerpT;
|
|
646
|
-
|
|
647
|
-
void main() {
|
|
648
|
-
vec2 sp = aPosition * uMeshScale;
|
|
649
|
-
vNormal = aNormal3;
|
|
650
|
-
vScreenUv = sp * 0.5 + 0.5;
|
|
651
|
-
vLerpT = aLerpT;
|
|
652
|
-
gl_Position = vec4(sp, 0.0, 1.0);
|
|
653
|
-
}
|
|
654
|
-
`,fe=`#version 300 es
|
|
655
|
-
precision highp float;
|
|
656
|
-
uniform vec3 uLightDir3;
|
|
657
|
-
uniform vec3 uChamferColor;
|
|
658
|
-
uniform float uChamferAmbient;
|
|
659
|
-
uniform float uChamferSpecular;
|
|
660
|
-
uniform float uChamferShininess;
|
|
661
|
-
uniform sampler2D uInteriorColor;
|
|
662
|
-
uniform vec2 uTexelSize; // 1 / viewport resolution
|
|
663
|
-
|
|
664
|
-
in vec3 vNormal;
|
|
665
|
-
in vec2 vScreenUv;
|
|
666
|
-
in float vLerpT;
|
|
667
|
-
out vec4 fragColor;
|
|
668
|
-
|
|
669
|
-
vec3 toLinear(vec3 s) {
|
|
670
|
-
return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
|
|
671
|
-
}
|
|
672
|
-
vec3 toSRGB(vec3 l) {
|
|
673
|
-
return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Approximate gaussian blur via 13-tap poisson disc, radius scaled by vLerpT.
|
|
677
|
-
vec3 blurSample(vec2 center, float radius) {
|
|
678
|
-
// Poisson disc offsets (normalized to unit circle)
|
|
679
|
-
const vec2 offsets[12] = vec2[12](
|
|
680
|
-
vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457),
|
|
681
|
-
vec2(-0.203, 0.621), vec2( 0.962, -0.195), vec2( 0.473, -0.480),
|
|
682
|
-
vec2( 0.519, 0.767), vec2( 0.185, -0.893), vec2( 0.507, 0.064),
|
|
683
|
-
vec2(-0.321, -0.860), vec2(-0.791, 0.557), vec2( 0.330, 0.418)
|
|
684
|
-
);
|
|
685
|
-
vec3 sum = texture(uInteriorColor, center).rgb;
|
|
686
|
-
for (int i = 0; i < 12; i++) {
|
|
687
|
-
vec2 uv = center + offsets[i] * radius;
|
|
688
|
-
uv = clamp(uv, vec2(0.001), vec2(0.999));
|
|
689
|
-
sum += texture(uInteriorColor, uv).rgb;
|
|
690
|
-
}
|
|
691
|
-
return sum / 13.0;
|
|
702
|
+
hitDepth = 1.0 - lensDepth(texture(uDepth, currentUV).r);
|
|
703
|
+
return mix(uv, currentUV, fade);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
void main() {
|
|
707
|
+
float hitDepth;
|
|
708
|
+
vec2 displaced = pomDisplace(vUv, hitDepth);
|
|
709
|
+
displaced = clamp(displaced, vec2(0.0), vec2(1.0));
|
|
710
|
+
|
|
711
|
+
vec4 color = texture(uImage, displaced);
|
|
712
|
+
|
|
713
|
+
// DOF: blur far objects
|
|
714
|
+
float rawDepthAtHit = texture(uDepth, displaced).r;
|
|
715
|
+
float lensD = lensDepth(rawDepthAtHit);
|
|
716
|
+
float dof = smoothstep(uDofStart, 1.0, lensD) * uDofStrength;
|
|
717
|
+
if (dof > 0.01) {
|
|
718
|
+
vec2 ts = uImageTexelSize;
|
|
719
|
+
vec4 blurred = (
|
|
720
|
+
texture(uImage, displaced + vec2( ts.x, 0.0)) +
|
|
721
|
+
texture(uImage, displaced + vec2(-ts.x, 0.0)) +
|
|
722
|
+
texture(uImage, displaced + vec2( 0.0, ts.y)) +
|
|
723
|
+
texture(uImage, displaced + vec2( 0.0, -ts.y)) +
|
|
724
|
+
texture(uImage, displaced + vec2( ts.x, ts.y)) +
|
|
725
|
+
texture(uImage, displaced + vec2(-ts.x, -ts.y)) +
|
|
726
|
+
texture(uImage, displaced + vec2( ts.x, -ts.y)) +
|
|
727
|
+
texture(uImage, displaced + vec2(-ts.x, ts.y))
|
|
728
|
+
) * 0.125;
|
|
729
|
+
color = mix(color, blurred, dof);
|
|
692
730
|
}
|
|
693
731
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
732
|
+
// Volumetric fog bias: far objects fade into fog color
|
|
733
|
+
float fogFactor = smoothstep(0.3, 1.0, lensD) * uFogDensity;
|
|
734
|
+
color.rgb = mix(color.rgb, uFogColor, fogFactor);
|
|
735
|
+
|
|
736
|
+
// Color grading shift: warm near, cool far (or vice versa)
|
|
737
|
+
float gradeAmount = (lensD - 0.5) * uColorShift;
|
|
738
|
+
color.r += gradeAmount * 0.08;
|
|
739
|
+
color.b -= gradeAmount * 0.08;
|
|
740
|
+
|
|
741
|
+
// Brightness bias
|
|
742
|
+
color.rgb *= (1.0 + uBrightnessBias);
|
|
743
|
+
|
|
744
|
+
// Subtle vignette inside portal
|
|
745
|
+
float dist = length(vScreenUv - 0.5) * 1.4;
|
|
746
|
+
color.rgb *= 1.0 - pow(dist, 3.0) * 0.3;
|
|
747
|
+
|
|
748
|
+
fragColor = color;
|
|
749
|
+
// Write lens-transformed depth to second attachment for boundary effects
|
|
750
|
+
fragDepth = vec4(lensD, 0.0, 0.0, 1.0);
|
|
751
|
+
}
|
|
752
|
+
`,qe=`#version 300 es
|
|
753
|
+
in vec2 aPosition;
|
|
754
|
+
out vec2 vUv;
|
|
755
|
+
void main() {
|
|
756
|
+
vUv = aPosition * 0.5 + 0.5;
|
|
757
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
758
|
+
}
|
|
759
|
+
`,Ye=`#version 300 es
|
|
760
|
+
precision highp float;
|
|
761
|
+
uniform sampler2D uInteriorColor;
|
|
762
|
+
uniform sampler2D uDistField;
|
|
763
|
+
uniform float uEdgeOcclusionWidth; // how far edge darkening extends
|
|
764
|
+
uniform float uEdgeOcclusionStrength; // how strong (0=none, 1=full black)
|
|
765
|
+
|
|
766
|
+
in vec2 vUv;
|
|
767
|
+
out vec4 fragColor;
|
|
768
|
+
|
|
769
|
+
// sRGB <-> linear conversions for correct lighting math
|
|
770
|
+
vec3 toLinear(vec3 s) {
|
|
771
|
+
return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
|
|
772
|
+
}
|
|
773
|
+
vec3 toSRGB(vec3 l) {
|
|
774
|
+
return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
void main() {
|
|
778
|
+
vec4 color = texture(uInteriorColor, vUv);
|
|
779
|
+
float dist = texture(uDistField, vUv).r; // 0=edge, 1=deep interior
|
|
780
|
+
|
|
781
|
+
// Emissive passthrough: preserve original video luminance.
|
|
782
|
+
// Only apply a subtle edge occlusion ramp to sell chamfer->interior depth.
|
|
783
|
+
vec3 linear = toLinear(color.rgb);
|
|
784
|
+
float occ = smoothstep(0.0, uEdgeOcclusionWidth, dist);
|
|
785
|
+
linear *= mix(1.0 - uEdgeOcclusionStrength, 1.0, occ);
|
|
786
|
+
|
|
787
|
+
fragColor = vec4(toSRGB(linear), color.a);
|
|
788
|
+
}
|
|
789
|
+
`,je=`#version 300 es
|
|
790
|
+
in vec2 aPosition;
|
|
791
|
+
in vec2 aNormal;
|
|
792
|
+
uniform float uRimWidth;
|
|
793
|
+
uniform vec2 uMeshScale;
|
|
794
|
+
out vec2 vNormal;
|
|
795
|
+
out vec2 vEdgeUv; // screen-space UV for sampling FBO textures
|
|
796
|
+
out float vEdgeDist; // 0 at edge, 1 at outer extent
|
|
797
|
+
|
|
798
|
+
void main() {
|
|
799
|
+
vec2 scaledPos = aPosition * uMeshScale;
|
|
800
|
+
vec2 scaledNormal = normalize(aNormal * uMeshScale);
|
|
801
|
+
vec2 pos = scaledPos + scaledNormal * uRimWidth;
|
|
802
|
+
|
|
803
|
+
// Pass screen-space UV of this fragment for FBO sampling
|
|
804
|
+
vEdgeUv = pos * 0.5 + 0.5;
|
|
805
|
+
vNormal = scaledNormal;
|
|
806
|
+
|
|
807
|
+
// Distance from the actual edge (0) to the outer rim extent (1)
|
|
808
|
+
vEdgeDist = length(pos - scaledPos) / max(uRimWidth, 0.001);
|
|
809
|
+
|
|
810
|
+
gl_Position = vec4(pos, 0.0, 1.0);
|
|
811
|
+
}
|
|
812
|
+
`,Ze=`#version 300 es
|
|
813
|
+
precision highp float;
|
|
814
|
+
|
|
815
|
+
uniform sampler2D uInteriorColor;
|
|
816
|
+
uniform sampler2D uInteriorDepth;
|
|
817
|
+
uniform sampler2D uDistField;
|
|
818
|
+
uniform float uRimIntensity;
|
|
819
|
+
uniform vec3 uRimColor;
|
|
820
|
+
uniform float uRefractionStrength;
|
|
821
|
+
uniform float uChromaticStrength;
|
|
822
|
+
uniform float uOcclusionIntensity;
|
|
823
|
+
uniform vec2 uTexelSize; // 1.0 / viewport resolution
|
|
824
|
+
|
|
825
|
+
// Volumetric edge wall
|
|
826
|
+
uniform float uEdgeThickness;
|
|
827
|
+
uniform float uEdgeSpecular;
|
|
828
|
+
uniform vec3 uEdgeColor;
|
|
829
|
+
uniform vec2 uLightDir;
|
|
830
|
+
uniform float uBevelIntensity;
|
|
831
|
+
|
|
832
|
+
in vec2 vNormal;
|
|
833
|
+
in vec2 vEdgeUv;
|
|
834
|
+
in float vEdgeDist;
|
|
835
|
+
out vec4 fragColor;
|
|
836
|
+
|
|
837
|
+
void main() {
|
|
838
|
+
// Clamp UV to valid range for texture sampling
|
|
839
|
+
vec2 sampleUv = clamp(vEdgeUv, vec2(0.001), vec2(0.999));
|
|
840
|
+
|
|
841
|
+
// Sample interior depth at this boundary location
|
|
842
|
+
float interiorDepth = texture(uInteriorDepth, sampleUv).r;
|
|
843
|
+
|
|
844
|
+
// === DEPTH-REACTIVE RIM (structural seam) ===
|
|
845
|
+
float depthReactivity = 1.0 - interiorDepth; // 1=near, 0=far
|
|
846
|
+
float rimProfile = 1.0 - smoothstep(0.0, 1.0, vEdgeDist);
|
|
847
|
+
rimProfile = pow(rimProfile, 1.5); // sharper falloff = more structural
|
|
848
|
+
|
|
849
|
+
float depthPressure = mix(0.2, 1.0, depthReactivity * depthReactivity);
|
|
850
|
+
float rim = rimProfile * depthPressure * uRimIntensity;
|
|
851
|
+
|
|
852
|
+
vec3 rimCol = uRimColor;
|
|
853
|
+
rimCol.r += depthReactivity * 0.15;
|
|
854
|
+
rimCol.g += depthReactivity * 0.05;
|
|
855
|
+
|
|
856
|
+
// === REFRACTION DISTORTION ===
|
|
857
|
+
vec2 ts = uTexelSize * 3.0;
|
|
858
|
+
float dLeft = texture(uInteriorDepth, sampleUv + vec2(-ts.x, 0.0)).r;
|
|
859
|
+
float dRight = texture(uInteriorDepth, sampleUv + vec2( ts.x, 0.0)).r;
|
|
860
|
+
float dUp = texture(uInteriorDepth, sampleUv + vec2(0.0, ts.y)).r;
|
|
861
|
+
float dDown = texture(uInteriorDepth, sampleUv + vec2(0.0, -ts.y)).r;
|
|
862
|
+
vec2 depthGradient = vec2(dRight - dLeft, dUp - dDown);
|
|
863
|
+
vec2 refractUv = sampleUv + depthGradient * uRefractionStrength * rimProfile;
|
|
864
|
+
refractUv = clamp(refractUv, vec2(0.001), vec2(0.999));
|
|
865
|
+
|
|
866
|
+
vec4 refractedColor = texture(uInteriorColor, refractUv);
|
|
867
|
+
|
|
868
|
+
// === CHROMATIC FRINGE ===
|
|
869
|
+
float chromaticAmount = uChromaticStrength * depthReactivity * rimProfile;
|
|
870
|
+
vec2 chromaticDir = vNormal * chromaticAmount;
|
|
871
|
+
float cr = texture(uInteriorColor, refractUv + chromaticDir).r;
|
|
872
|
+
float cg = refractedColor.g;
|
|
873
|
+
float cb = texture(uInteriorColor, refractUv - chromaticDir).b;
|
|
874
|
+
vec3 chromaticColor = vec3(cr, cg, cb);
|
|
875
|
+
|
|
876
|
+
// === OCCLUSION CONTACT SHADOW ===
|
|
877
|
+
float occlusionAmount = smoothstep(0.4, 0.0, interiorDepth) * uOcclusionIntensity * rimProfile;
|
|
878
|
+
|
|
879
|
+
// === VOLUMETRIC EDGE WALL ===
|
|
880
|
+
// Sample distance field to get the inner-side distance at this boundary location
|
|
881
|
+
float edgeDist = texture(uDistField, sampleUv).r;
|
|
882
|
+
float wallZone = smoothstep(uEdgeThickness, 0.0, edgeDist) * rimProfile;
|
|
883
|
+
|
|
884
|
+
// Wall lighting from distance field gradient
|
|
885
|
+
vec2 dtx = vec2(1.0) / vec2(textureSize(uDistField, 0));
|
|
886
|
+
float wdL = texture(uDistField, sampleUv + vec2(-dtx.x, 0.0)).r;
|
|
887
|
+
float wdR = texture(uDistField, sampleUv + vec2( dtx.x, 0.0)).r;
|
|
888
|
+
float wdU = texture(uDistField, sampleUv + vec2(0.0, dtx.y)).r;
|
|
889
|
+
float wdD = texture(uDistField, sampleUv + vec2(0.0, -dtx.y)).r;
|
|
890
|
+
vec2 wallNormal = vec2(wdR - wdL, wdU - wdD);
|
|
891
|
+
float wnLen = length(wallNormal);
|
|
892
|
+
if (wnLen > 0.001) wallNormal /= wnLen;
|
|
893
|
+
|
|
894
|
+
float wallSpec = pow(max(dot(wallNormal, uLightDir), 0.0), 16.0) * uEdgeSpecular;
|
|
895
|
+
vec3 wallColor = mix(refractedColor.rgb * 0.4, uEdgeColor, 0.3);
|
|
896
|
+
wallColor += vec3(wallSpec);
|
|
897
|
+
|
|
898
|
+
// === COMPOSITE ===
|
|
899
|
+
vec3 color = mix(refractedColor.rgb, chromaticColor, min(chromaticAmount * 10.0, 1.0));
|
|
900
|
+
color *= (1.0 - occlusionAmount * 0.4);
|
|
901
|
+
|
|
902
|
+
// Blend in volumetric wall
|
|
903
|
+
color = mix(color, wallColor, wallZone * uBevelIntensity);
|
|
904
|
+
|
|
905
|
+
// Add rim energy on top
|
|
906
|
+
color += rimCol * rim;
|
|
907
|
+
|
|
908
|
+
// Alpha: rim edge fades out
|
|
909
|
+
float alpha = rimProfile * max(rim, occlusionAmount + chromaticAmount * 5.0 + wallZone * 0.5);
|
|
910
|
+
alpha = clamp(alpha, 0.0, 1.0);
|
|
911
|
+
|
|
912
|
+
fragColor = vec4(color * alpha, alpha);
|
|
913
|
+
}
|
|
914
|
+
`,$e=`#version 300 es
|
|
915
|
+
in vec2 aPosition;
|
|
916
|
+
in vec3 aNormal3;
|
|
917
|
+
in float aLerpT; // 0 = inner (at silhouette), 1 = outer edge
|
|
918
|
+
uniform vec2 uMeshScale;
|
|
919
|
+
out vec3 vNormal;
|
|
920
|
+
out vec2 vScreenUv;
|
|
921
|
+
out float vLerpT;
|
|
922
|
+
|
|
923
|
+
void main() {
|
|
924
|
+
vec2 sp = aPosition * uMeshScale;
|
|
925
|
+
vNormal = aNormal3;
|
|
926
|
+
vScreenUv = sp * 0.5 + 0.5;
|
|
927
|
+
vLerpT = aLerpT;
|
|
928
|
+
gl_Position = vec4(sp, 0.0, 1.0);
|
|
929
|
+
}
|
|
930
|
+
`,Ke=`#version 300 es
|
|
931
|
+
precision highp float;
|
|
932
|
+
uniform vec3 uLightDir3;
|
|
933
|
+
uniform vec3 uChamferColor;
|
|
934
|
+
uniform float uChamferAmbient;
|
|
935
|
+
uniform float uChamferSpecular;
|
|
936
|
+
uniform float uChamferShininess;
|
|
937
|
+
uniform sampler2D uInteriorColor;
|
|
938
|
+
uniform vec2 uTexelSize; // 1 / viewport resolution
|
|
939
|
+
|
|
940
|
+
in vec3 vNormal;
|
|
941
|
+
in vec2 vScreenUv;
|
|
942
|
+
in float vLerpT;
|
|
943
|
+
out vec4 fragColor;
|
|
944
|
+
|
|
945
|
+
vec3 toLinear(vec3 s) {
|
|
946
|
+
return mix(s / 12.92, pow((s + 0.055) / 1.055, vec3(2.4)), step(0.04045, s));
|
|
947
|
+
}
|
|
948
|
+
vec3 toSRGB(vec3 l) {
|
|
949
|
+
return mix(l * 12.92, 1.055 * pow(l, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, l));
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Approximate gaussian blur via 13-tap poisson disc, radius scaled by vLerpT.
|
|
953
|
+
vec3 blurSample(vec2 center, float radius) {
|
|
954
|
+
// Poisson disc offsets (normalized to unit circle)
|
|
955
|
+
const vec2 offsets[12] = vec2[12](
|
|
956
|
+
vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457),
|
|
957
|
+
vec2(-0.203, 0.621), vec2( 0.962, -0.195), vec2( 0.473, -0.480),
|
|
958
|
+
vec2( 0.519, 0.767), vec2( 0.185, -0.893), vec2( 0.507, 0.064),
|
|
959
|
+
vec2(-0.321, -0.860), vec2(-0.791, 0.557), vec2( 0.330, 0.418)
|
|
960
|
+
);
|
|
961
|
+
vec3 sum = texture(uInteriorColor, center).rgb;
|
|
962
|
+
for (int i = 0; i < 12; i++) {
|
|
963
|
+
vec2 uv = center + offsets[i] * radius;
|
|
964
|
+
uv = clamp(uv, vec2(0.001), vec2(0.999));
|
|
965
|
+
sum += texture(uInteriorColor, uv).rgb;
|
|
720
966
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.deleteShader(e),i}function de(r){const t=[];let e=0;for(let i=0;i<r.length-2;i+=2){const o=r[i],n=r[i+1],s=r[i+2],h=r[i+3],a=s-o,l=h-n,u=Math.sqrt(a*a+l*l);if(u<1e-6)continue;const f=-l/u,c=a/u;t.push(o,n,f,c,o,n,-f,-c,s,h,f,c,s,h,f,c,o,n,-f,-c,s,h,-f,-c),e+=6}return{vertices:new Float32Array(t),count:e}}function me(r,t,e,i,o){if(i<=0)return{vertices:new Float32Array(0),count:0};const n=o*Math.PI/180,s=-Math.cos(n),h=Math.sin(n),a=[];let l=0;for(let u=0;u<t.length;u++){const f=t[u],d=((u+1<t.length?t[u+1]:r.length)-f)/2;if(d<3)continue;const m=d-1;let U=0;for(let x=0;x<m;x++){const A=f+x*2,S=r[A],D=r[A+1],P=r[A+2],_=r[A+3];U+=S*_-P*D}const y=U>=0?1:-1,E=[],p=[];for(let x=0;x<m;x++){const A=f+x*2,S=r[A+2]-r[A],D=r[A+3]-r[A+1],P=Math.sqrt(S*S+D*D);P<1e-8?(E.push(x>0?E[x-1]:0),p.push(x>0?p[x-1]:0)):(E.push(-D/P*y),p.push(S/P*y))}const v=[],T=[];for(let x=0;x<m;x++){const A=(x-1+m)%m;let S=E[A]+E[x],D=p[A]+p[x];const P=Math.sqrt(S*S+D*D);P>1e-8?(S/=P,D/=P):(S=E[x],D=p[x]),v.push(S),T.push(D)}for(let x=0;x<m;x++){const A=x,S=(x+1)%m,D=f+x*2,P=f+(x+1)%m*2,_=r[D],k=r[D+1],F=r[P],R=r[P+1],C=v[A]*h,M=T[A]*h,B=s,I=v[S]*h,G=T[S]*h,tt=s,ct=_+v[A]*i,Lt=k+T[A]*i,ze=F+v[S]*i,We=R+T[S]*i;a.push(_,k,C,M,B,0),a.push(ct,Lt,C,M,B,1),a.push(F,R,I,G,tt,0),a.push(F,R,I,G,tt,0),a.push(ct,Lt,C,M,B,1),a.push(ze,We,I,G,tt,1),l+=6}}return{vertices:new Float32Array(a),count:l}}class ft{static RESIZE_DEBOUNCE_MS=100;canvas;gl=null;container;stencilProgram=null;maskProgram=null;jfaSeedProgram=null;jfaFloodProgram=null;jfaDistProgram=null;interiorProgram=null;compositeProgram=null;boundaryProgram=null;chamferProgram=null;stencilUniforms={};maskUniforms={};jfaSeedUniforms={};jfaFloodUniforms={};jfaDistUniforms={};interiorUniforms={};compositeUniforms={};boundaryUniforms={};chamferUniforms={};quadVao=null;stencilVao=null;stencilIndexCount=0;maskVao=null;boundaryVao=null;boundaryVertexCount=0;chamferVao=null;chamferVertexCount=0;videoTexture=null;depthTexture=null;interiorFbo=null;interiorColorTex=null;interiorDepthTex=null;fboWidth=0;fboHeight=0;maskFbo=null;maskTex=null;jfaPingFbo=null;jfaPingTex=null;jfaPongFbo=null;jfaPongTex=null;distFbo=null;distTex=null;jfaWidth=0;jfaHeight=0;distFieldDirty=!0;hasColorBufferFloat=!1;depthWidth=0;depthHeight=0;videoAspect=1.7777777777777777;meshAspect=1;meshScaleX=.65;meshScaleY=.65;readDepth=null;readInput=null;playbackVideo=null;onVideoFrame=null;animationFrameHandle=0;rvfcHandle=0;rvfcSupported=!1;resizeObserver=null;resizeTimer=null;uvOffset=[0,0];uvScale=[1,1];lightDirX=-.707;lightDirY=.707;lightDir3=[-.5,.7,-.3];config;constructor(t,e){this.container=t,this.config={...e};const i=this.config.bevelLightAngle*Math.PI/180;this.lightDirX=Math.cos(i),this.lightDirY=Math.sin(i);const o=this.config.lightDirection,n=Math.sqrt(o[0]*o[0]+o[1]*o[1]+o[2]*o[2]);n>1e-6&&(this.lightDir3=[o[0]/n,o[1]/n,o[2]/n]),this.canvas=document.createElement("canvas");const s=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0,premultipliedAlpha:!0,stencil:!0,desynchronized:!0,powerPreference:"high-performance"});if(!s)throw new Error("WebGL 2 is not supported.");this.gl=s,"drawingBufferColorSpace"in s&&(s.drawingBufferColorSpace="srgb"),this.hasColorBufferFloat=!!s.getExtension("EXT_color_buffer_float"),s.clearColor(0,0,0,0),s.pixelStorei(s.UNPACK_FLIP_Y_WEBGL,!0),this.container.appendChild(this.canvas),this.initGPUResources(),this.setupResizeHandling(),this.canvas.addEventListener("webglcontextlost",this.handleContextLost),this.canvas.addEventListener("webglcontextrestored",this.handleContextRestored)}initialize(t,e,i,o){const n=this.gl;n&&(this.disposeTextures(),this.disposeFBO(),this.disposeJFA(),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.videoAspect=t.videoWidth/t.videoHeight,this.meshAspect=o.aspect,this.depthWidth=e,this.depthHeight=i,this.videoTexture=n.createTexture(),n.activeTexture(n.TEXTURE0),n.bindTexture(n.TEXTURE_2D,this.videoTexture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),this.depthTexture=n.createTexture(),n.activeTexture(n.TEXTURE1),n.bindTexture(n.TEXTURE_2D,this.depthTexture),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),n.texStorage2D(n.TEXTURE_2D,1,n.R8,e,i),this.uploadStencilMesh(o),this.uploadMaskMesh(o),this.uploadBoundaryMesh(o),this.uploadChamferMesh(o),this.interiorProgram&&(n.useProgram(this.interiorProgram),n.uniform1i(this.interiorUniforms.uImage,0),n.uniform1i(this.interiorUniforms.uDepth,1),n.uniform1f(this.interiorUniforms.uStrength,this.config.parallaxStrength),n.uniform1i(this.interiorUniforms.uPomSteps,this.config.pomSteps),n.uniform1f(this.interiorUniforms.uDepthPower,this.config.depthPower),n.uniform1f(this.interiorUniforms.uDepthScale,this.config.depthScale),n.uniform1f(this.interiorUniforms.uDepthBias,this.config.depthBias),n.uniform1f(this.interiorUniforms.uContrastLow,this.config.contrastLow),n.uniform1f(this.interiorUniforms.uContrastHigh,this.config.contrastHigh),n.uniform1f(this.interiorUniforms.uVerticalReduction,this.config.verticalReduction),n.uniform1f(this.interiorUniforms.uDofStart,this.config.dofStart),n.uniform1f(this.interiorUniforms.uDofStrength,this.config.dofStrength),n.uniform2f(this.interiorUniforms.uImageTexelSize,1/t.videoWidth,1/t.videoHeight),n.uniform1f(this.interiorUniforms.uFogDensity,this.config.fogDensity),n.uniform3f(this.interiorUniforms.uFogColor,...this.config.fogColor),n.uniform1f(this.interiorUniforms.uColorShift,this.config.colorShift),n.uniform1f(this.interiorUniforms.uBrightnessBias,this.config.brightnessBias)),this.compositeProgram&&(n.useProgram(this.compositeProgram),n.uniform1i(this.compositeUniforms.uInteriorColor,2),n.uniform1i(this.compositeUniforms.uDistField,4),n.uniform1f(this.compositeUniforms.uEdgeOcclusionWidth,this.config.edgeOcclusionWidth),n.uniform1f(this.compositeUniforms.uEdgeOcclusionStrength,this.config.edgeOcclusionStrength)),this.chamferProgram&&(n.useProgram(this.chamferProgram),n.uniform3f(this.chamferUniforms.uLightDir3,...this.lightDir3),n.uniform3f(this.chamferUniforms.uChamferColor,...this.config.chamferColor),n.uniform1f(this.chamferUniforms.uChamferAmbient,this.config.chamferAmbient),n.uniform1f(this.chamferUniforms.uChamferSpecular,this.config.chamferSpecular),n.uniform1f(this.chamferUniforms.uChamferShininess,this.config.chamferShininess),n.uniform1i(this.chamferUniforms.uInteriorColor,2)),this.boundaryProgram&&(n.useProgram(this.boundaryProgram),n.uniform1i(this.boundaryUniforms.uInteriorColor,2),n.uniform1i(this.boundaryUniforms.uInteriorDepth,3),n.uniform1i(this.boundaryUniforms.uDistField,4),n.uniform1f(this.boundaryUniforms.uRimIntensity,this.config.rimLightIntensity),n.uniform3f(this.boundaryUniforms.uRimColor,...this.config.rimLightColor),n.uniform1f(this.boundaryUniforms.uRefractionStrength,this.config.refractionStrength),n.uniform1f(this.boundaryUniforms.uChromaticStrength,this.config.chromaticStrength),n.uniform1f(this.boundaryUniforms.uOcclusionIntensity,this.config.occlusionIntensity),n.uniform1f(this.boundaryUniforms.uEdgeThickness,this.config.edgeThickness),n.uniform1f(this.boundaryUniforms.uEdgeSpecular,this.config.edgeSpecular),n.uniform3f(this.boundaryUniforms.uEdgeColor,...this.config.edgeColor),n.uniform2f(this.boundaryUniforms.uLightDir,this.lightDirX,this.lightDirY),n.uniform1f(this.boundaryUniforms.uBevelIntensity,this.config.bevelIntensity)),this.recalculateViewportLayout())}uploadStencilMesh(t){const e=this.gl;if(!e||!this.stencilProgram)return;this.stencilVao=e.createVertexArray(),e.bindVertexArray(this.stencilVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const o=e.getAttribLocation(this.stencilProgram,"aPosition");e.enableVertexAttribArray(o),e.vertexAttribPointer(o,2,e.FLOAT,!1,0,0);const n=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,n),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),this.stencilIndexCount=t.indices.length,e.bindVertexArray(null)}uploadMaskMesh(t){const e=this.gl;if(!e||!this.maskProgram)return;this.maskVao=e.createVertexArray(),e.bindVertexArray(this.maskVao);const i=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,t.vertices,e.STATIC_DRAW);const o=e.getAttribLocation(this.maskProgram,"aPosition");e.enableVertexAttribArray(o),e.vertexAttribPointer(o,2,e.FLOAT,!1,0,0);const n=e.createBuffer();e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,n),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),e.bindVertexArray(null)}uploadBoundaryMesh(t){const e=this.gl;if(!e||!this.boundaryProgram)return;const i=de(t.edgeVertices);if(i.count===0)return;this.boundaryVao=e.createVertexArray(),e.bindVertexArray(this.boundaryVao);const o=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,o),e.bufferData(e.ARRAY_BUFFER,i.vertices,e.STATIC_DRAW);const n=16,s=e.getAttribLocation(this.boundaryProgram,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,n,0);const h=e.getAttribLocation(this.boundaryProgram,"aNormal");h>=0&&(e.enableVertexAttribArray(h),e.vertexAttribPointer(h,2,e.FLOAT,!1,n,8)),this.boundaryVertexCount=i.count,e.bindVertexArray(null)}uploadChamferMesh(t){const e=this.gl;if(!e||!this.chamferProgram||this.config.chamferWidth<=0)return;const i=me(t.edgeVertices,t.contourOffsets,t.contourIsHole,this.config.chamferWidth,this.config.chamferAngle);if(i.count===0)return;this.chamferVao=e.createVertexArray(),e.bindVertexArray(this.chamferVao);const o=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,o),e.bufferData(e.ARRAY_BUFFER,i.vertices,e.STATIC_DRAW);const n=24,s=e.getAttribLocation(this.chamferProgram,"aPosition");e.enableVertexAttribArray(s),e.vertexAttribPointer(s,2,e.FLOAT,!1,n,0);const h=e.getAttribLocation(this.chamferProgram,"aNormal3");h>=0&&(e.enableVertexAttribArray(h),e.vertexAttribPointer(h,3,e.FLOAT,!1,n,8));const a=e.getAttribLocation(this.chamferProgram,"aLerpT");a>=0&&(e.enableVertexAttribArray(a),e.vertexAttribPointer(a,1,e.FLOAT,!1,n,20)),this.chamferVertexCount=i.count,e.bindVertexArray(null)}disposeChamferGeometry(){const t=this.gl;t&&(this.chamferVao&&(t.deleteVertexArray(this.chamferVao),this.chamferVao=null),this.chamferVertexCount=0)}createFBO(t,e){const i=this.gl;if(!i)return;this.disposeFBO(),this.fboWidth=t,this.fboHeight=e,this.interiorFbo=i.createFramebuffer(),i.bindFramebuffer(i.FRAMEBUFFER,this.interiorFbo),this.interiorColorTex=i.createTexture(),i.activeTexture(i.TEXTURE2),i.bindTexture(i.TEXTURE_2D,this.interiorColorTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,t,e),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,this.interiorColorTex,0),this.interiorDepthTex=i.createTexture(),i.activeTexture(i.TEXTURE3),i.bindTexture(i.TEXTURE_2D,this.interiorDepthTex),i.texStorage2D(i.TEXTURE_2D,1,i.RGBA8,t,e),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT1,i.TEXTURE_2D,this.interiorDepthTex,0),i.drawBuffers([i.COLOR_ATTACHMENT0,i.COLOR_ATTACHMENT1]);const o=i.checkFramebufferStatus(i.FRAMEBUFFER);o!==i.FRAMEBUFFER_COMPLETE&&console.error("Interior FBO incomplete:",o),i.bindFramebuffer(i.FRAMEBUFFER,null)}createJFAResources(t,e){const i=this.gl;if(!i)return;this.disposeJFA();const o=Math.max(1,Math.round(t/2)),n=Math.max(1,Math.round(e/2));this.jfaWidth=o,this.jfaHeight=n;const s=(a,l,u,f)=>{const c=i.createFramebuffer();return i.bindFramebuffer(i.FRAMEBUFFER,c),i.bindTexture(i.TEXTURE_2D,a),i.texStorage2D(i.TEXTURE_2D,1,l,u,f),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.LINEAR),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE),i.framebufferTexture2D(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,a,0),i.bindFramebuffer(i.FRAMEBUFFER,null),c};this.maskTex=i.createTexture(),this.maskFbo=s(this.maskTex,i.R8,o,n);const h=this.hasColorBufferFloat?i.RG16F:i.RGBA8;this.jfaPingTex=i.createTexture(),this.jfaPingFbo=s(this.jfaPingTex,h,o,n),this.jfaPongTex=i.createTexture(),this.jfaPongFbo=s(this.jfaPongTex,h,o,n),this.distTex=i.createTexture(),this.distFbo=s(this.distTex,i.RGBA8,o,n),this.distFieldDirty=!0}computeDistanceField(){const t=this.gl;if(!t||!this.maskFbo||!this.maskVao||!this.quadVao||!this.jfaPingFbo||!this.jfaPongFbo||!this.distFbo)return;const e=this.jfaWidth,i=this.jfaHeight;if(e===0||i===0)return;t.viewport(0,0,e,i),t.disable(t.STENCIL_TEST),t.disable(t.BLEND),t.bindFramebuffer(t.FRAMEBUFFER,this.maskFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.maskProgram),t.uniform2f(this.maskUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.bindVertexArray(this.maskVao),t.drawElements(t.TRIANGLES,this.stencilIndexCount,t.UNSIGNED_SHORT,0),t.bindFramebuffer(t.FRAMEBUFFER,this.jfaPingFbo),t.clearColor(-1,-1,0,0),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.jfaSeedProgram),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(this.jfaSeedUniforms.uMask,5),t.uniform2f(this.jfaSeedUniforms.uTexelSize,1/e,1/i),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const o=Math.max(e,i),n=[];let s=Math.ceil(o/2);for(;s>=1;)n.push(s),s=Math.floor(s/2);t.useProgram(this.jfaFloodProgram);let h=this.jfaPingTex,a=this.jfaPongFbo,l=this.jfaPongTex;for(let f=0;f<n.length;f++){const c=n[f]/Math.max(e,i);t.bindFramebuffer(t.FRAMEBUFFER,a),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,h),t.uniform1i(this.jfaFloodUniforms.uSeedTex,5),t.uniform1f(this.jfaFloodUniforms.uStepSize,c),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4);const g=h,d=a;h=l,a=d===this.jfaPongFbo?this.jfaPingFbo:this.jfaPongFbo,l=g}t.bindFramebuffer(t.FRAMEBUFFER,this.distFbo),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.jfaDistProgram),t.activeTexture(t.TEXTURE5),t.bindTexture(t.TEXTURE_2D,h),t.uniform1i(this.jfaDistUniforms.uSeedTex,5),t.activeTexture(t.TEXTURE6),t.bindTexture(t.TEXTURE_2D,this.maskTex),t.uniform1i(this.jfaDistUniforms.uMask,6);const u=Math.max(this.config.bevelWidth,this.config.edgeOcclusionWidth);t.uniform1f(this.jfaDistUniforms.uBevelWidth,u),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.activeTexture(t.TEXTURE4),t.bindTexture(t.TEXTURE_2D,this.distTex),t.bindFramebuffer(t.FRAMEBUFFER,null),this.distFieldDirty=!1}start(t,e,i,o){this.stop(),this.playbackVideo=t,this.readDepth=e,this.readInput=i,this.onVideoFrame=o??null,this.rvfcSupported="requestVideoFrameCallback"in HTMLVideoElement.prototype,this.rvfcSupported&&(this.rvfcHandle=t.requestVideoFrameCallback(this.videoFrameLoop)),this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)}stop(){this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0),this.rvfcHandle&&this.playbackVideo&&(this.playbackVideo.cancelVideoFrameCallback(this.rvfcHandle),this.rvfcHandle=0),this.playbackVideo=null,this.readDepth=null,this.readInput=null,this.onVideoFrame=null,this.rvfcSupported=!1}dispose(){this.stop(),this.disposeTextures(),this.disposeFBO(),this.disposeJFA(),this.disposeStencilGeometry(),this.disposeBoundaryGeometry(),this.disposeChamferGeometry(),this.disposeGPUResources(),this.canvas.removeEventListener("webglcontextlost",this.handleContextLost),this.canvas.removeEventListener("webglcontextrestored",this.handleContextRestored),this.gl&&(this.gl.getExtension("WEBGL_lose_context")?.loseContext(),this.gl=null),this.canvas.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),window.removeEventListener("resize",this.scheduleResizeRecalculate),this.resizeTimer!==null&&(window.clearTimeout(this.resizeTimer),this.resizeTimer=null)}initGPUResources(){const t=this.gl;if(!t)return;this.stencilProgram=O(t,L(t,t.VERTEX_SHADER,Zt),L(t,t.FRAGMENT_SHADER,$t)),this.stencilUniforms={uMeshScale:t.getUniformLocation(this.stencilProgram,"uMeshScale")},this.maskProgram=O(t,L(t,t.VERTEX_SHADER,Jt),L(t,t.FRAGMENT_SHADER,Kt)),this.maskUniforms={uMeshScale:t.getUniformLocation(this.maskProgram,"uMeshScale")},this.jfaSeedProgram=O(t,L(t,t.VERTEX_SHADER,Qt),L(t,t.FRAGMENT_SHADER,te)),this.jfaSeedUniforms=this.getUniforms(this.jfaSeedProgram,["uMask","uTexelSize"]),this.jfaFloodProgram=O(t,L(t,t.VERTEX_SHADER,ee),L(t,t.FRAGMENT_SHADER,ie)),this.jfaFloodUniforms=this.getUniforms(this.jfaFloodProgram,["uSeedTex","uStepSize"]),this.jfaDistProgram=O(t,L(t,t.VERTEX_SHADER,re),L(t,t.FRAGMENT_SHADER,oe)),this.jfaDistUniforms=this.getUniforms(this.jfaDistProgram,["uSeedTex","uMask","uBevelWidth"]),this.interiorProgram=O(t,L(t,t.VERTEX_SHADER,ne),L(t,t.FRAGMENT_SHADER,se)),this.interiorUniforms=this.getUniforms(this.interiorProgram,["uImage","uDepth","uOffset","uStrength","uPomSteps","uDepthPower","uDepthScale","uDepthBias","uContrastLow","uContrastHigh","uVerticalReduction","uDofStart","uDofStrength","uImageTexelSize","uFogDensity","uFogColor","uColorShift","uBrightnessBias","uUvOffset","uUvScale"]),this.compositeProgram=O(t,L(t,t.VERTEX_SHADER,ae),L(t,t.FRAGMENT_SHADER,he)),this.compositeUniforms=this.getUniforms(this.compositeProgram,["uInteriorColor","uDistField","uEdgeOcclusionWidth","uEdgeOcclusionStrength"]),this.boundaryProgram=O(t,L(t,t.VERTEX_SHADER,le),L(t,t.FRAGMENT_SHADER,ce)),this.boundaryUniforms=this.getUniforms(this.boundaryProgram,["uInteriorColor","uInteriorDepth","uDistField","uRimIntensity","uRimColor","uRimWidth","uMeshScale","uRefractionStrength","uChromaticStrength","uOcclusionIntensity","uTexelSize","uEdgeThickness","uEdgeSpecular","uEdgeColor","uLightDir","uBevelIntensity"]),this.chamferProgram=O(t,L(t,t.VERTEX_SHADER,ue),L(t,t.FRAGMENT_SHADER,fe)),this.chamferUniforms=this.getUniforms(this.chamferProgram,["uMeshScale","uLightDir3","uChamferColor","uChamferAmbient","uChamferSpecular","uChamferShininess","uInteriorColor","uTexelSize"]);const e=new Float32Array([-1,-1,1,-1,-1,1,1,1]);this.quadVao=t.createVertexArray(),t.bindVertexArray(this.quadVao);const i=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,i),t.bufferData(t.ARRAY_BUFFER,e,t.STATIC_DRAW);const o=t.getAttribLocation(this.interiorProgram,"aPosition");t.enableVertexAttribArray(o),t.vertexAttribPointer(o,2,t.FLOAT,!1,0,0),t.bindVertexArray(null),t.disable(t.DEPTH_TEST)}getUniforms(t,e){const i=this.gl,o={};for(const n of e)o[n]=i.getUniformLocation(t,n);return o}videoFrameLoop=(t,e)=>{const i=this.playbackVideo;if(!i)return;this.rvfcHandle=i.requestVideoFrameCallback(this.videoFrameLoop);const o=e.mediaTime??i.currentTime;this.updateDepthTexture(o),this.onVideoFrame&&this.onVideoFrame(o,e.presentedFrames??0)};renderLoop=()=>{this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop);const t=this.gl,e=this.playbackVideo;if(!t||!this.interiorProgram||!this.quadVao||!e||e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA||!this.interiorFbo||!this.interiorColorTex||!this.interiorDepthTex)return;this.distFieldDirty&&this.maskVao&&this.distFbo&&(this.computeDistanceField(),t.viewport(0,0,this.canvas.width,this.canvas.height)),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),this.rvfcSupported||this.updateDepthTexture(e.currentTime);let i=0,o=0;if(this.readInput){const n=this.readInput();i=-n.x,o=n.y}if(t.bindFramebuffer(t.FRAMEBUFFER,this.interiorFbo),t.checkFramebufferStatus(t.FRAMEBUFFER)!==t.FRAMEBUFFER_COMPLETE){t.bindFramebuffer(t.FRAMEBUFFER,null);return}t.viewport(0,0,this.fboWidth,this.fboHeight),t.clearColor(0,0,0,1),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this.interiorProgram),t.uniform2f(this.interiorUniforms.uOffset,i,o),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.videoTexture),t.activeTexture(t.TEXTURE1),t.bindTexture(t.TEXTURE_2D,this.depthTexture),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.bindFramebuffer(t.FRAMEBUFFER,null),t.clearColor(0,0,0,0),t.viewport(0,0,this.canvas.width,this.canvas.height),t.clear(t.COLOR_BUFFER_BIT|t.STENCIL_BUFFER_BIT),this.stencilVao&&this.stencilProgram&&this.stencilIndexCount>0&&(t.enable(t.STENCIL_TEST),t.stencilFunc(t.ALWAYS,1,255),t.stencilOp(t.KEEP,t.KEEP,t.REPLACE),t.stencilMask(255),t.colorMask(!1,!1,!1,!1),t.useProgram(this.stencilProgram),t.bindVertexArray(this.stencilVao),t.drawElements(t.TRIANGLES,this.stencilIndexCount,t.UNSIGNED_SHORT,0),t.colorMask(!0,!0,!0,!0)),t.stencilFunc(t.EQUAL,1,255),t.stencilMask(0),t.activeTexture(t.TEXTURE2),t.bindTexture(t.TEXTURE_2D,this.interiorColorTex),t.activeTexture(t.TEXTURE3),t.bindTexture(t.TEXTURE_2D,this.interiorDepthTex),t.activeTexture(t.TEXTURE4),t.bindTexture(t.TEXTURE_2D,this.distTex),t.useProgram(this.compositeProgram),t.bindVertexArray(this.quadVao),t.drawArrays(t.TRIANGLE_STRIP,0,4),t.disable(t.STENCIL_TEST),this.chamferVao&&this.chamferProgram&&this.chamferVertexCount>0&&(t.useProgram(this.chamferProgram),t.uniform2f(this.chamferUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform2f(this.chamferUniforms.uTexelSize,1/this.canvas.width,1/this.canvas.height),t.bindVertexArray(this.chamferVao),t.drawArrays(t.TRIANGLES,0,this.chamferVertexCount)),this.boundaryVao&&this.boundaryProgram&&this.boundaryVertexCount>0&&this.config.rimLightIntensity>0&&(t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA),t.useProgram(this.boundaryProgram),t.bindVertexArray(this.boundaryVao),t.drawArrays(t.TRIANGLES,0,this.boundaryVertexCount),t.disable(t.BLEND))};updateDepthTexture(t){const e=this.gl;if(!e||!this.readDepth||!this.depthTexture)return;const i=this.readDepth(t);e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.depthTexture),e.texSubImage2D(e.TEXTURE_2D,0,0,0,this.depthWidth,this.depthHeight,e.RED,e.UNSIGNED_BYTE,i)}setupResizeHandling(){typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.scheduleResizeRecalculate()}),this.resizeObserver.observe(this.container)),window.addEventListener("resize",this.scheduleResizeRecalculate),this.recalculateViewportLayout()}scheduleResizeRecalculate=()=>{this.resizeTimer!==null&&window.clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.resizeTimer=null,this.recalculateViewportLayout()},ft.RESIZE_DEBOUNCE_MS)};recalculateViewportLayout(){const t=this.gl;if(!t)return;const{width:e,height:i}=this.getViewportSize(),o=Math.min(window.devicePixelRatio,2),n=Math.round(e*o),s=Math.round(i*o);(this.canvas.width!==n||this.canvas.height!==s)&&(this.canvas.width=n,this.canvas.height=s,t.viewport(0,0,n,s)),(this.fboWidth!==n||this.fboHeight!==s)&&this.createFBO(n,s);const h=Math.max(1,Math.round(n/2)),a=Math.max(1,Math.round(s/2));(this.jfaWidth!==h||this.jfaHeight!==a)&&this.createJFAResources(n,s);const l=e/i,u=this.config.parallaxStrength+this.config.overscanPadding;let f=1,c=1;l>this.videoAspect?c=this.videoAspect/l:f=l/this.videoAspect;const g=1+u*2;f/=g,c/=g,this.uvOffset=[(1-f)/2,(1-c)/2],this.uvScale=[f,c],this.interiorProgram&&(t.useProgram(this.interiorProgram),t.uniform2f(this.interiorUniforms.uUvOffset,this.uvOffset[0],this.uvOffset[1]),t.uniform2f(this.interiorUniforms.uUvScale,this.uvScale[0],this.uvScale[1]));const d=.65;this.meshScaleX=d,this.meshScaleY=d,l>this.meshAspect?this.meshScaleX=d*(this.meshAspect/l):this.meshScaleY=d*(l/this.meshAspect),this.stencilProgram&&(t.useProgram(this.stencilProgram),t.uniform2f(this.stencilUniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.boundaryProgram&&(t.useProgram(this.boundaryProgram),t.uniform2f(this.boundaryUniforms.uMeshScale,this.meshScaleX,this.meshScaleY),t.uniform1f(this.boundaryUniforms.uRimWidth,this.config.rimLightWidth),t.uniform2f(this.boundaryUniforms.uTexelSize,1/n,1/s)),this.chamferProgram&&(t.useProgram(this.chamferProgram),t.uniform2f(this.chamferUniforms.uMeshScale,this.meshScaleX,this.meshScaleY)),this.distFieldDirty=!0}getViewportSize(){const t=Math.max(1,Math.round(this.container.clientWidth||window.innerWidth)),e=Math.max(1,Math.round(this.container.clientHeight||window.innerHeight));return{width:t,height:e}}handleContextLost=t=>{t.preventDefault(),this.animationFrameHandle&&(window.cancelAnimationFrame(this.animationFrameHandle),this.animationFrameHandle=0)};handleContextRestored=()=>{const t=this.canvas.getContext("webgl2",{alpha:!0,premultipliedAlpha:!0,stencil:!0});t&&(this.gl=t,this.hasColorBufferFloat=!!t.getExtension("EXT_color_buffer_float"),t.clearColor(0,0,0,0),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,!0),this.initGPUResources(),this.recalculateViewportLayout(),this.playbackVideo&&(this.animationFrameHandle=window.requestAnimationFrame(this.renderLoop)))};disposeTextures(){const t=this.gl;t&&(this.videoTexture&&(t.deleteTexture(this.videoTexture),this.videoTexture=null),this.depthTexture&&(t.deleteTexture(this.depthTexture),this.depthTexture=null))}disposeFBO(){const t=this.gl;t&&(this.interiorColorTex&&(t.deleteTexture(this.interiorColorTex),this.interiorColorTex=null),this.interiorDepthTex&&(t.deleteTexture(this.interiorDepthTex),this.interiorDepthTex=null),this.interiorFbo&&(t.deleteFramebuffer(this.interiorFbo),this.interiorFbo=null),this.fboWidth=0,this.fboHeight=0)}disposeJFA(){const t=this.gl;t&&(this.maskTex&&(t.deleteTexture(this.maskTex),this.maskTex=null),this.maskFbo&&(t.deleteFramebuffer(this.maskFbo),this.maskFbo=null),this.jfaPingTex&&(t.deleteTexture(this.jfaPingTex),this.jfaPingTex=null),this.jfaPingFbo&&(t.deleteFramebuffer(this.jfaPingFbo),this.jfaPingFbo=null),this.jfaPongTex&&(t.deleteTexture(this.jfaPongTex),this.jfaPongTex=null),this.jfaPongFbo&&(t.deleteFramebuffer(this.jfaPongFbo),this.jfaPongFbo=null),this.distTex&&(t.deleteTexture(this.distTex),this.distTex=null),this.distFbo&&(t.deleteFramebuffer(this.distFbo),this.distFbo=null),this.jfaWidth=0,this.jfaHeight=0,this.distFieldDirty=!0)}disposeStencilGeometry(){const t=this.gl;t&&(this.stencilVao&&(t.deleteVertexArray(this.stencilVao),this.stencilVao=null),this.maskVao&&(t.deleteVertexArray(this.maskVao),this.maskVao=null),this.stencilIndexCount=0)}disposeBoundaryGeometry(){const t=this.gl;t&&(this.boundaryVao&&(t.deleteVertexArray(this.boundaryVao),this.boundaryVao=null),this.boundaryVertexCount=0)}disposeGPUResources(){const t=this.gl;t&&(this.stencilProgram&&(t.deleteProgram(this.stencilProgram),this.stencilProgram=null),this.maskProgram&&(t.deleteProgram(this.maskProgram),this.maskProgram=null),this.jfaSeedProgram&&(t.deleteProgram(this.jfaSeedProgram),this.jfaSeedProgram=null),this.jfaFloodProgram&&(t.deleteProgram(this.jfaFloodProgram),this.jfaFloodProgram=null),this.jfaDistProgram&&(t.deleteProgram(this.jfaDistProgram),this.jfaDistProgram=null),this.interiorProgram&&(t.deleteProgram(this.interiorProgram),this.interiorProgram=null),this.compositeProgram&&(t.deleteProgram(this.compositeProgram),this.compositeProgram=null),this.boundaryProgram&&(t.deleteProgram(this.boundaryProgram),this.boundaryProgram=null),this.chamferProgram&&(t.deleteProgram(this.chamferProgram),this.chamferProgram=null),this.quadVao&&(t.deleteVertexArray(this.quadVao),this.quadVao=null),this.stencilUniforms={},this.maskUniforms={},this.jfaSeedUniforms={},this.jfaFloodUniforms={},this.jfaDistUniforms={},this.interiorUniforms={},this.compositeUniforms={},this.boundaryUniforms={},this.chamferUniforms={})}}async function pe(r){const t=await fetch(r);if(!t.ok)throw new Error(`Failed to fetch SVG: ${t.status} ${t.statusText}`);const e=await t.text();return ge(e)}function ge(r){const i=new DOMParser().parseFromString(r,"image/svg+xml").querySelector("svg");if(!i)throw new Error("No <svg> element found in document.");const o=ve(i);if(o.length===0)throw new Error("No path data found in SVG.");let n=1/0,s=1/0,h=-1/0,a=-1/0;for(const F of o)for(let R=0;R<F.length;R+=2)n=Math.min(n,F[R]),h=Math.max(h,F[R]),s=Math.min(s,F[R+1]),a=Math.max(a,F[R+1]);const l=h-n,u=a-s,f=(n+h)/2,c=(s+a)/2,g=2/Math.max(l,u),d=l/u,m=o.map(F=>{const R=[];for(let C=0;C<F.length;C+=2)R.push((F[C]-f)*g),R.push(-((F[C+1]-c)*g));return R}),U=Re(m),y=[],E=[];for(const F of U){const{flatCoords:R,holeIndices:C}=Se(F),M=Fe(R,C),B=y.length/2;for(const I of M)E.push(I+B);for(const I of R)y.push(I)}const p=y,v=E,T=[],x=[],A=[],S=St(m);for(let F=0;F<m.length;F++){const R=m[F];x.push(T.length),A.push(S[F]);for(let C=0;C<R.length;C++)T.push(R[C]);R.length>=2&&T.push(R[0],R[1])}let D=1/0,P=1/0,_=-1/0,k=-1/0;for(let F=0;F<p.length;F+=2)D=Math.min(D,p[F]),_=Math.max(_,p[F]),P=Math.min(P,p[F+1]),k=Math.max(k,p[F+1]);return{vertices:new Float32Array(p),indices:new Uint16Array(v),edgeVertices:new Float32Array(T),contourOffsets:x,contourIsHole:A,bounds:{minX:D,maxX:_,minY:P,maxY:k},aspect:d}}function ve(r){const t=[];return r.querySelectorAll("path").forEach(a=>{const l=a.getAttribute("d");if(!l)return;const u=Ee(l);t.push(...u)}),r.querySelectorAll("polygon").forEach(a=>{const l=a.getAttribute("points");if(!l)return;const u=bt(l);u.length>=6&&t.push(u)}),r.querySelectorAll("polyline").forEach(a=>{const l=a.getAttribute("points");if(!l)return;const u=bt(l);u.length>=6&&t.push(u)}),r.querySelectorAll("rect").forEach(a=>{const l=parseFloat(a.getAttribute("x")||"0"),u=parseFloat(a.getAttribute("y")||"0"),f=parseFloat(a.getAttribute("width")||"0"),c=parseFloat(a.getAttribute("height")||"0");f>0&&c>0&&t.push([l,u,l+f,u,l+f,u+c,l,u+c])}),r.querySelectorAll("circle").forEach(a=>{const l=parseFloat(a.getAttribute("cx")||"0"),u=parseFloat(a.getAttribute("cy")||"0"),f=parseFloat(a.getAttribute("r")||"0");f>0&&t.push(xe(l,u,f))}),r.querySelectorAll("ellipse").forEach(a=>{const l=parseFloat(a.getAttribute("cx")||"0"),u=parseFloat(a.getAttribute("cy")||"0"),f=parseFloat(a.getAttribute("rx")||"0"),c=parseFloat(a.getAttribute("ry")||"0");f>0&&c>0&&t.push(Te(l,u,f,c))}),t}function bt(r){const t=[],e=r.trim().split(/[\s,]+/);for(let i=0;i<e.length-1;i+=2){const o=parseFloat(e[i]),n=parseFloat(e[i+1]);Number.isFinite(o)&&Number.isFinite(n)&&t.push(o,n)}return t}function xe(r,t,e,i=64){const o=[];for(let n=0;n<i;n++){const s=2*Math.PI*n/i;o.push(r+e*Math.cos(s),t+e*Math.sin(s))}return o}function Te(r,t,e,i,o=64){const n=[];for(let s=0;s<o;s++){const h=2*Math.PI*s/o;n.push(r+e*Math.cos(h),t+i*Math.sin(h))}return n}function Ee(r){const t=[];let e=[],i=0,o=0,n=0,s=0,h=0,a=0,l="";const u=be(r);let f=0;function c(){return f>=u.length?0:parseFloat(u[f++])}for(;f<u.length;){const g=u[f];let d;/^[a-zA-Z]$/.test(g)?(d=g,f++):d=l==="M"?"L":l==="m"?"l":l;const m=d===d.toLowerCase();switch(d.toUpperCase()){case"M":{e.length>0&&t.push(e),e=[];const y=c()+(m?i:0),E=c()+(m?o:0);i=y,o=E,n=y,s=E,e.push(i,o),h=i,a=o;break}case"L":{i=c()+(m?i:0),o=c()+(m?o:0),e.push(i,o),h=i,a=o;break}case"H":{i=c()+(m?i:0),e.push(i,o),h=i,a=o;break}case"V":{o=c()+(m?o:0),e.push(i,o),h=i,a=o;break}case"C":{const y=c()+(m?i:0),E=c()+(m?o:0),p=c()+(m?i:0),v=c()+(m?o:0),T=c()+(m?i:0),x=c()+(m?o:0);$(e,i,o,y,E,p,v,T,x),i=T,o=x,h=p,a=v;break}case"S":{const y=2*i-h,E=2*o-a,p=c()+(m?i:0),v=c()+(m?o:0),T=c()+(m?i:0),x=c()+(m?o:0);$(e,i,o,y,E,p,v,T,x),i=T,o=x,h=p,a=v;break}case"Q":{const y=c()+(m?i:0),E=c()+(m?o:0),p=c()+(m?i:0),v=c()+(m?o:0);yt(e,i,o,y,E,p,v),i=p,o=v,h=y,a=E;break}case"T":{const y=2*i-h,E=2*o-a,p=c()+(m?i:0),v=c()+(m?o:0);yt(e,i,o,y,E,p,v),i=p,o=v,h=y,a=E;break}case"A":{const y=c(),E=c(),p=c(),v=c(),T=c(),x=c()+(m?i:0),A=c()+(m?o:0);Ae(e,i,o,y,E,p,!!v,!!T,x,A),i=x,o=A,h=i,a=o;break}case"Z":{i=n,o=s,e.length>0&&t.push(e),e=[],h=i,a=o;break}default:f++;break}l=d}return e.length>=6&&t.push(e),t}function be(r){const t=[],e=/([a-zA-Z])|([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/g;let i;for(;(i=e.exec(r))!==null;)t.push(i[0]);return t}const ye=.5;function $(r,t,e,i,o,n,s,h,a,l=0){if(l>12){r.push(h,a);return}const u=h-t,f=a-e,c=Math.sqrt(u*u+f*f);if(c<1e-6){r.push(h,a);return}const g=Math.abs((i-h)*f-(o-a)*u)/c,d=Math.abs((n-h)*f-(s-a)*u)/c;if(g+d<ye){r.push(h,a);return}const m=(t+i)/2,U=(e+o)/2,y=(i+n)/2,E=(o+s)/2,p=(n+h)/2,v=(s+a)/2,T=(m+y)/2,x=(U+E)/2,A=(y+p)/2,S=(E+v)/2,D=(T+A)/2,P=(x+S)/2;$(r,t,e,m,U,T,x,D,P,l+1),$(r,D,P,A,S,p,v,h,a,l+1)}function yt(r,t,e,i,o,n,s){const h=t+.6666666666666666*(i-t),a=e+2/3*(o-e),l=n+2/3*(i-n),u=s+2/3*(o-s);$(r,t,e,h,a,l,u,n,s)}function Ae(r,t,e,i,o,n,s,h,a,l){if(i===0||o===0){r.push(a,l);return}let u=Math.abs(i),f=Math.abs(o);const c=n*Math.PI/180,g=Math.cos(c),d=Math.sin(c),m=(t-a)/2,U=(e-l)/2,y=g*m+d*U,E=-d*m+g*U;let p=y*y/(u*u)+E*E/(f*f);if(p>1){const M=Math.sqrt(p);u*=M,f*=M,p=1}const v=u*u,T=f*f,x=y*y,A=E*E;let S=Math.max(0,(v*T-v*A-T*x)/(v*A+T*x));S=Math.sqrt(S),s===h&&(S=-S);const D=S*(u*E)/f,P=S*-(f*y)/u,_=g*D-d*P+(t+a)/2,k=d*D+g*P+(e+l)/2,F=At(1,0,(y-D)/u,(E-P)/f);let R=At((y-D)/u,(E-P)/f,(-y-D)/u,(-E-P)/f);!h&&R>0&&(R-=2*Math.PI),h&&R<0&&(R+=2*Math.PI);const C=Math.max(4,Math.ceil(Math.abs(R)/(Math.PI/16)));for(let M=1;M<=C;M++){const B=F+M/C*R,I=Math.cos(B),G=Math.sin(B),tt=g*u*I-d*f*G+_,ct=d*u*I+g*f*G+k;r.push(tt,ct)}}function At(r,t,e,i){const o=r*i-t*e<0?-1:1,n=r*e+t*i,s=Math.sqrt(r*r+t*t),h=Math.sqrt(e*e+i*i),a=n/(s*h);return o*Math.acos(Math.max(-1,Math.min(1,a)))}function Se(r){const t=[],e=[];for(let i=0;i<r.length;i++){i>0&&e.push(t.length/2);for(const o of r[i])t.push(o)}return{flatCoords:t,holeIndices:e}}function St(r){const t=r.length,e=r.map(o=>Math.abs(Rt(o))),i=new Array(t).fill(!1);for(let o=0;o<t;o++){let n=0;const s=r[o][0],h=r[o][1];for(let a=0;a<t;a++)o!==a&&e[a]>e[o]&&Ft(s,h,r[a])&&n++;i[o]=n%2===1}return i}function Re(r){if(r.length<=1)return[r];const t=St(r),e=r.map((s,h)=>{const a=Rt(s);return{index:h,contour:s,area:a,isOuter:!t[h]}}),i=e.filter(s=>s.isOuter),o=e.filter(s=>!s.isOuter);if(i.length===0)return r.map(s=>[s]);const n=i.map(s=>({outer:s.contour,holes:[]}));for(const s of o){const h=s.contour[0],a=s.contour[1];let l=-1,u=1/0;for(let f=0;f<i.length;f++)if(Ft(h,a,i[f].contour)){const c=Math.abs(i[f].area);c<u&&(u=c,l=f)}l>=0?n[l].holes.push(s.contour):n.push({outer:s.contour,holes:[]})}return n.map(s=>[s.outer,...s.holes])}function Rt(r){let t=0;const e=r.length;for(let i=0;i<e;i+=2){const o=r[i],n=r[i+1],s=r[(i+2)%e],h=r[(i+3)%e];t+=o*h-s*n}return t/2}function Ft(r,t,e){let i=!1;const o=e.length;for(let n=0,s=o-2;n<o;s=n,n+=2){const h=e[n],a=e[n+1],l=e[s],u=e[s+1];a>t!=u>t&&r<(l-h)*(t-a)/(u-a)+h&&(i=!i)}return i}function Fe(r,t,e=2){const i=t&&t.length>0,o=i?t[0]*e:r.length;let n=Dt(r,0,o,e,!0);const s=[];if(!n||n.next===n.prev)return s;i&&(n=Le(r,t,n,e));let h=1/0,a=1/0,l=-1/0,u=-1/0,f=0;if(r.length>80*e){for(let c=0;c<o;c+=e){const g=r[c],d=r[c+1];g<h&&(h=g),d<a&&(a=d),g>l&&(l=g),d>u&&(u=d)}f=Math.max(l-h,u-a),f=f!==0?32767/f:0}return J(n,s,e,h,a,f,0),s}function Dt(r,t,e,i,o){let n=null;if(o===Xe(r,t,e,i)>0)for(let s=t;s<e;s+=i)n=wt(s,r[s],r[s+1],n);else for(let s=e-i;s>=t;s-=i)n=wt(s,r[s],r[s+1],n);return n&&nt(n,n.next)&&(Q(n),n=n.next),n?(n.next.prev=n,n.prev.next=n,n.next):null}function X(r,t){t||(t=r);let e=r,i;do if(i=!1,!e.steiner&&(nt(e,e.next)||w(e.prev,e,e.next)===0)){if(Q(e),e=t=e.prev,e===e.next)break;i=!0}else e=e.next;while(i||e!==t);return t}function J(r,t,e,i,o,n,s){if(!r)return;!s&&n&&Ie(r,i,o,n);let h=r,a,l;for(;r.prev!==r.next;){if(a=r.prev,l=r.next,n?Ue(r,i,o,n):De(r)){t.push(a.i/e,r.i/e,l.i/e),Q(r),r=l.next,h=l.next;continue}if(r=l,r===h){s?s===1?(r=Pe(X(r),t,e),J(r,t,e,i,o,n,2)):s===2&&we(r,t,e,i,o,n):J(X(r),t,e,i,o,n,1);break}}}function De(r){const t=r.prev,e=r,i=r.next;if(w(t,e,i)>=0)return!1;const o=t.x,n=e.x,s=i.x,h=t.y,a=e.y,l=i.y,u=o<n?o<s?o:s:n<s?n:s,f=h<a?h<l?h:l:a<l?a:l,c=o>n?o>s?o:s:n>s?n:s,g=h>a?h>l?h:l:a>l?a:l;let d=i.next;for(;d!==t;){if(d.x>=u&&d.x<=c&&d.y>=f&&d.y<=g&&z(o,h,n,a,s,l,d.x,d.y)&&w(d.prev,d,d.next)>=0)return!1;d=d.next}return!0}function Ue(r,t,e,i){const o=r.prev,n=r,s=r.next;if(w(o,n,s)>=0)return!1;const h=o.x,a=n.x,l=s.x,u=o.y,f=n.y,c=s.y,g=h<a?h<l?h:l:a<l?a:l,d=u<f?u<c?u:c:f<c?f:c,m=h>a?h>l?h:l:a>l?a:l,U=u>f?u>c?u:c:f>c?f:c,y=dt(g,d,t,e,i),E=dt(m,U,t,e,i);let p=r.prevZ,v=r.nextZ;for(;p&&p.z>=y&&v&&v.z<=E;){if(p.x>=g&&p.x<=m&&p.y>=d&&p.y<=U&&p!==o&&p!==s&&z(h,u,a,f,l,c,p.x,p.y)&&w(p.prev,p,p.next)>=0||(p=p.prevZ,v.x>=g&&v.x<=m&&v.y>=d&&v.y<=U&&v!==o&&v!==s&&z(h,u,a,f,l,c,v.x,v.y)&&w(v.prev,v,v.next)>=0))return!1;v=v.nextZ}for(;p&&p.z>=y;){if(p.x>=g&&p.x<=m&&p.y>=d&&p.y<=U&&p!==o&&p!==s&&z(h,u,a,f,l,c,p.x,p.y)&&w(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;v&&v.z<=E;){if(v.x>=g&&v.x<=m&&v.y>=d&&v.y<=U&&v!==o&&v!==s&&z(h,u,a,f,l,c,v.x,v.y)&&w(v.prev,v,v.next)>=0)return!1;v=v.nextZ}return!0}function Pe(r,t,e){let i=r;do{const o=i.prev,n=i.next.next;!nt(o,n)&&Ut(o,i,i.next,n)&&K(o,n)&&K(n,o)&&(t.push(o.i/e,i.i/e,n.i/e),Q(i),Q(i.next),i=r=n),i=i.next}while(i!==r);return X(i)}function we(r,t,e,i,o,n){let s=r;do{let h=s.next.next;for(;h!==s.prev;){if(s.i!==h.i&&ke(s,h)){let a=Pt(s,h);s=X(s,s.next),a=X(a,a.next),J(s,t,e,i,o,n,0),J(a,t,e,i,o,n,0);return}h=h.next}s=s.next}while(s!==r)}function Le(r,t,e,i){const o=[];for(let n=0;n<t.length;n++){const s=t[n]*i,h=n<t.length-1?t[n+1]*i:r.length,a=Dt(r,s,h,i,!1);a&&(a===a.next&&(a.steiner=!0),o.push(Oe(a)))}o.sort((n,s)=>n.x-s.x);for(const n of o)e=Ce(n,e);return e}function Ce(r,t){const e=Me(r,t);if(!e)return t;const i=Pt(e,r);return X(i,i.next),X(e,e.next)}function Me(r,t){let e=t;const i=r.x,o=r.y;let n=-1/0,s=null;do{if(o<=e.y&&o>=e.next.y&&e.next.y!==e.y){const f=e.x+(o-e.y)/(e.next.y-e.y)*(e.next.x-e.x);if(f<=i&&f>n&&(n=f,s=e.x<e.next.x?e:e.next,f===i))return s}e=e.next}while(e!==t);if(!s)return null;const h=s,a=s.x,l=s.y;let u=1/0;e=s;do{if(i>=e.x&&e.x>=a&&i!==e.x&&z(o<l?i:n,o,a,l,o<l?n:i,o,e.x,e.y)){const f=Math.abs(o-e.y)/(i-e.x);K(e,r)&&(f<u||f===u&&(e.x>s.x||_e(s,e)))&&(s=e,u=f)}e=e.next}while(e!==h);return s}function _e(r,t){return w(r.prev,r,t.prev)<0&&w(t.next,r,r.next)<0}function Ie(r,t,e,i){let o=r;do o.z===0&&(o.z=dt(o.x,o.y,t,e,i)),o.prevZ=o.prev,o.nextZ=o.next,o=o.next;while(o!==r);o.prevZ.nextZ=null,o.prevZ=null,Ve(o)}function Ve(r){let t=1,e;do{let i=r;r=null;let o=null;for(e=0;i;){e++;let n=i,s=0;for(let a=0;a<t&&(s++,n=n.nextZ,!!n);a++);let h=t;for(;s>0||h>0&&n;){let a;s!==0&&(h===0||!n||i.z<=n.z)?(a=i,i=i.nextZ,s--):(a=n,n=n.nextZ,h--),o?o.nextZ=a:r=a,a.prevZ=o,o=a}i=n}o.nextZ=null,t*=2}while(e>1);return r}function dt(r,t,e,i,o){let n=(r-e)*o|0,s=(t-i)*o|0;return n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,s=(s|s<<8)&16711935,s=(s|s<<4)&252645135,s=(s|s<<2)&858993459,s=(s|s<<1)&1431655765,n|s<<1}function Oe(r){let t=r,e=r;do(t.x<e.x||t.x===e.x&&t.y<e.y)&&(e=t),t=t.next;while(t!==r);return e}function z(r,t,e,i,o,n,s,h){return(o-s)*(t-h)-(r-s)*(n-h)>=0&&(r-s)*(i-h)-(e-s)*(t-h)>=0&&(e-s)*(n-h)-(o-s)*(i-h)>=0}function ke(r,t){return r.next.i!==t.i&&r.prev.i!==t.i&&!Be(r,t)&&(K(r,t)&&K(t,r)&&He(r,t)&&(w(r.prev,r,t.prev)!==0||w(r,t.prev,t)!==0)||nt(r,t)&&w(r.prev,r,r.next)>0&&w(t.prev,t,t.next)>0)}function w(r,t,e){return(t.y-r.y)*(e.x-t.x)-(t.x-r.x)*(e.y-t.y)}function nt(r,t){return r.x===t.x&&r.y===t.y}function Ut(r,t,e,i){const o=at(w(r,t,e)),n=at(w(r,t,i)),s=at(w(e,i,r)),h=at(w(e,i,t));return!!(o!==n&&s!==h||o===0&&st(r,e,t)||n===0&&st(r,i,t)||s===0&&st(e,r,i)||h===0&&st(e,t,i))}function st(r,t,e){return t.x<=Math.max(r.x,e.x)&&t.x>=Math.min(r.x,e.x)&&t.y<=Math.max(r.y,e.y)&&t.y>=Math.min(r.y,e.y)}function at(r){return r>0?1:r<0?-1:0}function Be(r,t){let e=r;do{if(e.i!==r.i&&e.next.i!==r.i&&e.i!==t.i&&e.next.i!==t.i&&Ut(e,e.next,r,t))return!0;e=e.next}while(e!==r);return!1}function K(r,t){return w(r.prev,r,r.next)<0?w(r,t,r.next)>=0&&w(r,r.prev,t)>=0:w(r,t,r.prev)<0||w(r,r.next,t)<0}function He(r,t){let e=r,i=!1;const o=(r.x+t.x)/2,n=(r.y+t.y)/2;do e.y>n!=e.next.y>n&&e.next.y!==e.y&&o<(e.next.x-e.x)*(n-e.y)/(e.next.y-e.y)+e.x&&(i=!i),e=e.next;while(e!==r);return i}function Pt(r,t){const e=mt(r.i,r.x,r.y),i=mt(t.i,t.x,t.y),o=r.next,n=t.prev;return r.next=t,t.prev=r,e.next=o,o.prev=e,i.next=e,e.prev=i,n.next=i,i.prev=n,i}function wt(r,t,e,i){const o=mt(r,t,e);return i?(o.next=i.next,o.prev=i,i.next.prev=o,i.next=o):(o.prev=o,o.next=o),o}function Q(r){r.next.prev=r.prev,r.prev.next=r.next,r.prevZ&&(r.prevZ.nextZ=r.nextZ),r.nextZ&&(r.nextZ.prevZ=r.prevZ)}function mt(r,t,e){return{i:r,x:t,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function Xe(r,t,e,i){let o=0;for(let n=t,s=e-i;n<e;n+=i)o+=(r[s]-r[n])*(r[n+1]+r[s+1]),s=n;return o}const b={parallaxX:.4,parallaxY:.8,parallaxMax:30,overscan:.06,pomSteps:16,rimIntensity:.6,rimColor:"#ffffff",rimWidth:.025,refractionStrength:.015,chromaticStrength:.008,occlusionIntensity:.4,depthPower:.7,depthScale:1.2,depthBias:-.05,fogDensity:.15,fogColor:"#1a1a2e",colorShift:.6,brightnessBias:.05,contrastLow:.02,contrastHigh:.98,verticalReduction:.5,dofStart:.5,dofStrength:.5,bevelIntensity:.5,bevelWidth:.04,bevelDarkening:.2,bevelDesaturation:.12,bevelLightAngle:135,edgeThickness:.01,edgeSpecular:.35,edgeColor:"#a0a0a0",chamferWidth:.025,chamferAngle:45,chamferColor:"#262630",chamferAmbient:.12,chamferSpecular:.3,chamferShininess:24,edgeOcclusionWidth:.03,edgeOcclusionStrength:.2,lightDirection:"-0.5,0.7,-0.3",autoplay:!0,loop:!0,muted:!0};class pt{constructor(t,e=.08,i=.06){this.host=t,this.lerpFactor=e,this.motionLerpFactor=i,this.host.addEventListener("mousemove",this.handleMouseMove),this.host.addEventListener("mouseleave",this.resetPointerTarget),this.host.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),this.host.addEventListener("touchmove",this.handleTouchMove,{passive:!0}),this.host.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),this.host.addEventListener("touchcancel",this.handleTouchEnd,{passive:!0})}pointerTarget={x:0,y:0};motionTarget={x:0,y:0};smoothedOutput={x:0,y:0};usingMotionInput=!1;motionListenerAttached=!1;motionRequested=!1;touchActive=!1;touchAnchorX=0;touchAnchorY=0;lerpFactor;motionLerpFactor;static TOUCH_DRAG_RANGE=100;update(){const t=this.touchActive?this.pointerTarget:this.usingMotionInput?this.motionTarget:this.pointerTarget,e=this.usingMotionInput&&!this.touchActive?this.motionLerpFactor:this.lerpFactor;return this.smoothedOutput.x=lt(this.smoothedOutput.x,t.x,e),this.smoothedOutput.y=lt(this.smoothedOutput.y,t.y,e),this.smoothedOutput}dispose(){this.host.removeEventListener("mousemove",this.handleMouseMove),this.host.removeEventListener("mouseleave",this.resetPointerTarget),this.host.removeEventListener("touchstart",this.handleTouchStart),this.host.removeEventListener("touchmove",this.handleTouchMove),this.host.removeEventListener("touchend",this.handleTouchEnd),this.host.removeEventListener("touchcancel",this.handleTouchEnd),this.motionListenerAttached&&(window.removeEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!1)}handleMouseMove=t=>{const e=this.host.getBoundingClientRect(),i=(t.clientX-e.left)/e.width*2-1,o=(t.clientY-e.top)/e.height*2-1;this.pointerTarget.x=W(i,-1,1),this.pointerTarget.y=W(o,-1,1)};resetPointerTarget=()=>{this.pointerTarget.x=0,this.pointerTarget.y=0};handleTouchStart=t=>{const e=t.touches[0];e&&(this.touchActive=!0,this.touchAnchorX=e.clientX,this.touchAnchorY=e.clientY,this.pointerTarget.x=0,this.pointerTarget.y=0,this.motionRequested||(this.motionRequested=!0,this.requestMotionPermission()))};handleTouchMove=t=>{const e=t.touches[0];if(!e)return;const i=e.clientX-this.touchAnchorX,o=e.clientY-this.touchAnchorY,n=pt.TOUCH_DRAG_RANGE;this.pointerTarget.x=W(i/n,-1,1),this.pointerTarget.y=W(o/n,-1,1)};handleTouchEnd=()=>{this.touchActive=!1,this.pointerTarget.x=0,this.pointerTarget.y=0};async requestMotionPermission(){if(typeof DeviceOrientationEvent>"u")return;const t=DeviceOrientationEvent;if(typeof t.requestPermission=="function")try{if(await t.requestPermission()!=="granted")return}catch{return}this.motionListenerAttached||(window.addEventListener("deviceorientation",this.handleDeviceOrientation),this.motionListenerAttached=!0),this.usingMotionInput=!0}handleDeviceOrientation=t=>{const e=W((t.gamma??0)/45,-1,1),i=W((t.beta??0)/45,-1,1);this.motionTarget.x=lt(this.motionTarget.x,e,this.motionLerpFactor),this.motionTarget.y=lt(this.motionTarget.y,i,this.motionLerpFactor)}}class ht extends HTMLElement{static TAG_NAME="layershift-portal";static get observedAttributes(){return["src","depth-src","depth-meta","logo-src","parallax-x","parallax-y","parallax-max","overscan","pom-steps","rim-intensity","rim-color","rim-width","refraction-strength","chromatic-strength","occlusion-intensity","depth-power","depth-scale","depth-bias","fog-density","fog-color","color-shift","brightness-bias","contrast-low","contrast-high","vertical-reduction","dof-start","dof-strength","bevel-intensity","bevel-width","bevel-darkening","bevel-desaturation","bevel-light-angle","edge-thickness","edge-specular","edge-color","chamfer-width","chamfer-angle","chamfer-color","chamfer-ambient","chamfer-specular","chamfer-shininess","edge-occlusion-width","edge-occlusion-strength","light-direction","autoplay","loop","muted"]}reinitAttributes=["src","depth-src","depth-meta","logo-src"];shadow;container=null;renderer=null;inputHandler=null;depthWorker=null;video=null;mesh=null;loopCount=0;lifecycle;constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.lifecycle=new Et(this)}getAttrFloat(t,e){const i=this.getAttribute(t);if(i===null)return e;const o=parseFloat(i);return Number.isFinite(o)?o:e}getAttrBool(t,e){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return!(i==="false"||i==="0")}getAttrColor(t,e){const i=this.getAttribute(t)??e;return Ne(i)}getAttrVec3(t,e){const o=(this.getAttribute(t)??e).split(",").map(s=>parseFloat(s.trim()));if(o.length>=3&&o.every(Number.isFinite))return[o[0],o[1],o[2]];const n=e.split(",").map(s=>parseFloat(s.trim()));return[n[0],n[1],n[2]]}get parallaxX(){return this.getAttrFloat("parallax-x",b.parallaxX)}get parallaxY(){return this.getAttrFloat("parallax-y",b.parallaxY)}get parallaxMax(){return this.getAttrFloat("parallax-max",b.parallaxMax)}get overscan(){return this.getAttrFloat("overscan",b.overscan)}get pomSteps(){return this.getAttrFloat("pom-steps",b.pomSteps)}get rimIntensity(){return this.getAttrFloat("rim-intensity",b.rimIntensity)}get rimWidth(){return this.getAttrFloat("rim-width",b.rimWidth)}get rimColor(){return this.getAttrColor("rim-color",b.rimColor)}get refractionStrength(){return this.getAttrFloat("refraction-strength",b.refractionStrength)}get chromaticStrength(){return this.getAttrFloat("chromatic-strength",b.chromaticStrength)}get occlusionIntensity(){return this.getAttrFloat("occlusion-intensity",b.occlusionIntensity)}get depthPower(){return this.getAttrFloat("depth-power",b.depthPower)}get depthScale(){return this.getAttrFloat("depth-scale",b.depthScale)}get depthBias(){return this.getAttrFloat("depth-bias",b.depthBias)}get fogDensity(){return this.getAttrFloat("fog-density",b.fogDensity)}get fogColor(){return this.getAttrColor("fog-color",b.fogColor)}get colorShift(){return this.getAttrFloat("color-shift",b.colorShift)}get brightnessBias(){return this.getAttrFloat("brightness-bias",b.brightnessBias)}get contrastLow(){return this.getAttrFloat("contrast-low",b.contrastLow)}get contrastHigh(){return this.getAttrFloat("contrast-high",b.contrastHigh)}get verticalReduction(){return this.getAttrFloat("vertical-reduction",b.verticalReduction)}get dofStart(){return this.getAttrFloat("dof-start",b.dofStart)}get dofStrength(){return this.getAttrFloat("dof-strength",b.dofStrength)}get bevelIntensity(){return this.getAttrFloat("bevel-intensity",b.bevelIntensity)}get bevelWidth(){return this.getAttrFloat("bevel-width",b.bevelWidth)}get bevelDarkening(){return this.getAttrFloat("bevel-darkening",b.bevelDarkening)}get bevelDesaturation(){return this.getAttrFloat("bevel-desaturation",b.bevelDesaturation)}get bevelLightAngle(){return this.getAttrFloat("bevel-light-angle",b.bevelLightAngle)}get edgeThickness(){return this.getAttrFloat("edge-thickness",b.edgeThickness)}get edgeSpecular(){return this.getAttrFloat("edge-specular",b.edgeSpecular)}get edgeColor(){return this.getAttrColor("edge-color",b.edgeColor)}get chamferWidth(){return this.getAttrFloat("chamfer-width",b.chamferWidth)}get chamferAngle(){return this.getAttrFloat("chamfer-angle",b.chamferAngle)}get chamferColor(){return this.getAttrColor("chamfer-color",b.chamferColor)}get chamferAmbient(){return this.getAttrFloat("chamfer-ambient",b.chamferAmbient)}get chamferSpecular(){return this.getAttrFloat("chamfer-specular",b.chamferSpecular)}get chamferShininess(){return this.getAttrFloat("chamfer-shininess",b.chamferShininess)}get edgeOcclusionWidth(){return this.getAttrFloat("edge-occlusion-width",b.edgeOcclusionWidth)}get edgeOcclusionStrength(){return this.getAttrFloat("edge-occlusion-strength",b.edgeOcclusionStrength)}get lightDirection3(){return this.getAttrVec3("light-direction",b.lightDirection)}get shouldAutoplay(){return this.getAttrBool("autoplay",b.autoplay)}get shouldLoop(){return this.getAttrBool("loop",b.loop)}get shouldMute(){return this.getAttrBool("muted",b.muted)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}attachVideoEventListeners(t){t.addEventListener("play",()=>{this.emit("layershift-portal:play",{currentTime:t.currentTime})}),t.addEventListener("pause",()=>{this.emit("layershift-portal:pause",{currentTime:t.currentTime})}),t.addEventListener("ended",()=>{t.loop&&(this.loopCount+=1,this.emit("layershift-portal:loop",{loopCount:this.loopCount}))})}connectedCallback(){this.lifecycle.onConnected()}disconnectedCallback(){this.lifecycle.onDisconnected()}attributeChangedCallback(t,e,i){this.lifecycle.onAttributeChanged(t,e,i)}setupShadowDOM(){this.shadow.innerHTML="";const t=document.createElement("style");t.textContent=`
|
|
967
|
+
return sum / 13.0;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
void main() {
|
|
971
|
+
vec3 N = normalize(vNormal);
|
|
972
|
+
vec3 L = normalize(uLightDir3);
|
|
973
|
+
vec3 V = vec3(0.0, 0.0, -1.0); // orthographic view direction
|
|
974
|
+
|
|
975
|
+
// Blinn-Phong lighting in linear space
|
|
976
|
+
float diff = max(dot(N, L), 0.0);
|
|
977
|
+
vec3 H = normalize(L + V);
|
|
978
|
+
float spec = pow(max(dot(N, H), 0.0), uChamferShininess) * uChamferSpecular;
|
|
979
|
+
|
|
980
|
+
// Sample interior video with progressive blur (sharper at inner edge)
|
|
981
|
+
vec2 uv = clamp(vScreenUv, vec2(0.001), vec2(0.999));
|
|
982
|
+
float blurRadius = vLerpT * 12.0 * length(uTexelSize);
|
|
983
|
+
vec3 videoSample = blurRadius > 0.0001
|
|
984
|
+
? blurSample(uv, blurRadius)
|
|
985
|
+
: texture(uInteriorColor, uv).rgb;
|
|
986
|
+
|
|
987
|
+
// Base color: video tinted through chamfer color (like frosted glass)
|
|
988
|
+
vec3 video = toLinear(videoSample);
|
|
989
|
+
vec3 tint = toLinear(uChamferColor);
|
|
990
|
+
// Blend: mostly video near inner edge, more tinted at outer edge
|
|
991
|
+
vec3 base = mix(video, video * tint * 3.0, vLerpT * 0.5);
|
|
992
|
+
|
|
993
|
+
// Apply Blinn-Phong
|
|
994
|
+
vec3 lit = base * (uChamferAmbient + (1.0 - uChamferAmbient) * diff) + vec3(spec);
|
|
995
|
+
fragColor = vec4(toSRGB(lit), 1.0);
|
|
996
|
+
}
|
|
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=`
|
|
724
998
|
:host {
|
|
725
999
|
display: block;
|
|
726
1000
|
width: 100%;
|
|
@@ -740,4 +1014,4 @@ ${o}`)}return r.detachShader(i,t),r.detachShader(i,e),r.deleteShader(t),r.delete
|
|
|
740
1014
|
width: 100%;
|
|
741
1015
|
height: 100%;
|
|
742
1016
|
}
|
|
743
|
-
`,this.shadow.appendChild(t),this.container=document.createElement("div"),this.container.className="container",this.shadow.appendChild(this.container)}async doInit(t){const e=this.getAttribute("src"),i=this.getAttribute("
|
|
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})({});
|