static const float PI = 3.14159265358979323846;

float radiansFromDegrees(float degrees)
{
	return degrees * (PI / 180.0);
}

float3 rotateX(float3 p, float angle)
{
	float s = sin(angle);
	float c = cos(angle);
	return float3(p.x, c * p.y - s * p.z, s * p.y + c * p.z);
}

float3 rotateY(float3 p, float angle)
{
	float s = sin(angle);
	float c = cos(angle);
	return float3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
}

float3 rotateZ(float3 p, float angle)
{
	float s = sin(angle);
	float c = cos(angle);
	return float3(c * p.x - s * p.y, s * p.x + c * p.y, p.z);
}

float3 rotateWorldToPlane(float3 value)
{
	float pan = radiansFromDegrees(panDegrees);
	float tilt = radiansFromDegrees(tiltDegrees);
	float roll = radiansFromDegrees(rollDegrees);
	return rotateZ(rotateX(rotateY(value, -pan), -tilt), -roll);
}

float planeEdgeMask(float2 uv, float2 inputResolution)
{
	float2 feather = max(edgeFeather, 0.0) / max(inputResolution, float2(1.0, 1.0));
	feather = max(feather, float2(0.00001, 0.00001));

	float left = smoothstep(0.0, feather.x, uv.x);
	float right = 1.0 - smoothstep(1.0 - feather.x, 1.0, uv.x);
	float top = smoothstep(0.0, feather.y, uv.y);
	float bottom = 1.0 - smoothstep(1.0 - feather.y, 1.0, uv.y);
	return saturate(left * right * top * bottom);
}

float4 shadeVideo(ShaderContext context)
{
	float2 outputResolution = max(context.outputResolution, float2(1.0, 1.0));
	float outputAspect = outputResolution.x / outputResolution.y;
	float sourceAspect = context.inputResolution.x / max(context.inputResolution.y, 1.0);
	float tanHalfFov = tan(radiansFromDegrees(clamp(fovDegrees, 5.0, 150.0)) * 0.5);

	float2 screen = float2(context.uv.x * 2.0 - 1.0, 1.0 - context.uv.y * 2.0);
	float3 rayOrigin = float3(0.0, 0.0, 0.0);
	float3 rayDirection = normalize(float3(screen.x * outputAspect * tanHalfFov, screen.y * tanHalfFov, 1.0));

	float3 planePosition = float3(positionX, positionY, max(positionZ, 0.001));
	float3 localOrigin = rotateWorldToPlane(rayOrigin - planePosition);
	float3 localDirection = rotateWorldToPlane(rayDirection);

	float backgroundAmount = saturate(backgroundMix);
	float4 background = float4(lerp(outsideColor.rgb, context.sourceColor.rgb, backgroundAmount), 1.0);
	if (abs(localDirection.z) < 0.00001)
		return background;

	float hitDistance = -localOrigin.z / localDirection.z;
	if (hitDistance <= 0.0)
		return background;

	float3 localHit = localOrigin + localDirection * hitDistance;
	float halfHeight = max(planeScale, 0.001) * 0.5;
	float halfWidth = halfHeight * sourceAspect;
	float2 planeUv = float2(
		localHit.x / max(halfWidth * 2.0, 0.0001) + 0.5,
		0.5 - localHit.y / max(halfHeight * 2.0, 0.0001)
	);

	float mask = planeEdgeMask(planeUv, max(context.inputResolution, float2(1.0, 1.0)));
	float4 planeColor = sampleVideo(clamp(planeUv, 0.0, 1.0));
	return saturate(lerp(background, planeColor, mask));
}
