static const float PI = 3.14159265358979323846; float2 rotateAroundCenter(float2 uv, float radians) { float2 centered = uv - 0.5; float s = sin(radians); float c = cos(radians); return float2(c * centered.x - s * centered.y, s * centered.x + c * centered.y) + 0.5; } float mirroredCoordinate(float coordinate) { float wrapped = frac(coordinate * 0.5) * 2.0; return wrapped <= 1.0 ? wrapped : 2.0 - wrapped; } float2 applyEdgeMode(float2 uv, out bool inside) { inside = uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0; if (edgeMode == 1) return clamp(uv, 0.0, 1.0); if (edgeMode == 2) return frac(uv); if (edgeMode == 3) return float2(mirroredCoordinate(uv.x), mirroredCoordinate(uv.y)); return uv; } float selectedCropAspect() { if (cropAspect == 1) return 4.0 / 3.0; if (cropAspect == 2) return 3.0 / 2.0; if (cropAspect == 3) return 1.0; if (cropAspect == 4) return 9.0 / 16.0; return 0.0; } bool insideCropWindow(float2 uv, float2 resolution) { float targetAspect = selectedCropAspect(); if (targetAspect <= 0.0) return true; float outputAspect = resolution.x / max(resolution.y, 1.0); float2 cropSize = float2(1.0, 1.0); if (outputAspect > targetAspect) cropSize.x = targetAspect / outputAspect; else cropSize.y = outputAspect / targetAspect; float2 cropMin = (1.0 - cropSize) * 0.5; float2 cropMax = cropMin + cropSize; return uv.x >= cropMin.x && uv.x <= cropMax.x && uv.y >= cropMin.y && uv.y <= cropMax.y; } float4 shadeVideo(ShaderContext context) { if (!insideCropWindow(context.uv, max(context.outputResolution, float2(1.0, 1.0)))) return outsideColor; float safeZoom = max(zoom, 0.001); float2 sourceUv = (context.uv - 0.5) / safeZoom + 0.5; sourceUv -= pan; sourceUv = rotateAroundCenter(sourceUv, -rotationDegrees * (PI / 180.0)); bool inside = false; float2 sampledUv = applyEdgeMode(sourceUv, inside); if (!inside && edgeMode == 0) return outsideColor; return sampleVideo(sampledUv); }