/**
Copyright (c) 2018 Jacob Maximilian Fober

This work is licensed under the Creative Commons 
Attribution-ShareAlike 4.0 International License. 
To view a copy of this license, visit 
http://creativecommons.org/licenses/by-sa/4.0/.
*/
// https://reshade.me/forum/shader-presentation/3916-distortion-correction-conformal-perspective
// Perfect Perspective PS ver. 2.3.3

#ifndef PERFECT_PERSPECTIVE_FXH
#define PERFECT_PERSPECTIVE_FXH

struct PerfectPerspectiveStruct
{
	// Borders
	bool bUseBorder;//true
	
	//debug
	bool bDebug;//false
	
	// 0 Horizontal, 1 Diagonal, 2 Vertical
	// If the image bulges in movement (too high FOV), change it to 'Diagonal'
	// When proportions are distorted at the periphery (too low FOV), choose 'Vertical'
	int type;//0
	
	float FOV;//90
	
	// Vertical Amount
	// 0.0 - cylindrical projection
	// 1.0 - spherical projection
	float vertical;//0.618
	
	//debug
	// DSR scale factor
	// (DSR) Dynamic Super Resolution...
	// Simulate application running beyond-native screen resolution
	float resScale;//1.0
	
	//Border Scale
	float zooming;//1.0
	
	float aspectR;//reverse aspect ratio
	
	float2 pixSize;//screen pixel size
	
	// Borders
	// Use Alpha to adjust opacity
	float4 color;//(0.027, 0.027, 0.027, 0.902)
};

// Stereographic-Gnomonic lookup function by Jacob Max Fober
// Input data:
	// FOV >> Camera Field of View in degrees
	// Coordinates >> UV coordinates (from -1, to 1), where (0,0) is at the center of the screen
float Formula(float2 Coordinates, float FOV)
{
	// Convert 1/4 FOV to radians and calc tangent squared
	float SqrTanFOVq = tan(radians(FOV * 0.25));
	SqrTanFOVq *= SqrTanFOVq;
	return (1.0 - SqrTanFOVq) / (1.0 - SqrTanFOVq * dot(Coordinates, Coordinates));
}

float3 applyPerfectPerspective(sampler2D sp, float2 uv, PerfectPerspectiveStruct value)
{
	// Get Aspect Ratio
	// float AspectR = 1.0 / ReShade::AspectRatio;
	// Get Screen Pixel Size
	// float2 ScrPixelSize = ReShade::PixelSize;

	// Convert FOV type..
	float FovType = (value.type == 1) ? sqrt(value.aspectR * value.aspectR + 1.0) : value.type == 2 ? value.aspectR : 1.0;

	// Convert UV to Radial Coordinates
	float2 SphCoord = uv * 2.0 - 1.0;
	// Aspect Ratio correction
	SphCoord.y *= value.aspectR;
	// Zoom in image and adjust FOV type (pass 1 of 2)
	SphCoord *= value.zooming / FovType;

	// Stereographic-Gnomonic lookup, vertical distortion amount and FOV type (pass 2 of 2)
	SphCoord *= Formula(float2(SphCoord.x, sqrt(value.vertical) * SphCoord.y), value.FOV) * FovType;

	// Aspect Ratio back to square
	SphCoord.y /= value.aspectR;

	// Get Pixel Size in stereographic coordinates
	float2 PixelSize = fwidth(SphCoord);

	// Outside borders check with Anti-Aliasing
	float2 AtBorders = smoothstep( 1 - value.pixSize, value.pixSize + 1, abs(SphCoord) );

	// Back to UV Coordinates
	SphCoord = SphCoord * 0.5 + 0.5;

	// Sample display image
	float3 Display = tex2D(sp, SphCoord).rgb;

	// Mask outside-border pixels or mirror
	Display = lerp(
		Display, 
		lerp(
			value.bUseBorder ? Display : tex2D(sp, uv).rgb, 
			value.color.rgb, 
			value.color.a
		), 
		max(AtBorders.x, AtBorders.y)
	);

	// Output type choice
	if (value.bDebug)
	{
		// Calculate radial screen coordinates before and after perspective transformation
		float4 RadialCoord = float4(uv, SphCoord) * 2 - 1;
		// Correct vertical aspect ratio
		RadialCoord.yw *= value.aspectR;

		// Define Mapping color
		float3 UnderSmpl = float3(1, 0, 0.2); // Red
		float3 SuperSmpl = float3(0, 1, 0.5); // Green
		float3 NeutralSmpl = float3(0, 0.5, 1); // Blue

		// Calculate Pixel Size difference...
		float PixelScale = fwidth( length(RadialCoord.xy) );
		// ...and simulate Dynamic Super Resolution (DSR) scalar
		PixelScale /= value.resScale * fwidth( length(RadialCoord.zw) );
		PixelScale -= 1;

		// Generate supersampled-undersampled color map
		float3 ResMap = lerp(
			SuperSmpl,
			UnderSmpl,
			saturate(ceil(PixelScale))
		);

		// Create black-white gradient mask of scale-neutral pixels
		PixelScale = 1 - abs(PixelScale);
		PixelScale = saturate(PixelScale * 4 - 3); // Clamp to more representative values

		// Color neutral scale pixels
		ResMap = lerp(ResMap, NeutralSmpl, PixelScale);

		// Blend color map with display image
		Display = normalize(ResMap) * (0.8 * max( max(Display.r, Display.g), Display.b ) + 0.2);
	}

	return Display;
}
#endif//PERFECT_PERSPECTIVE_FXH