#pragma kernel CubeMapToEquirectangular
#pragma kernel CubeMapToEquirectangularPositiveY
#pragma kernel CubeMapToEquirectangularNegativeY

RWStructuredBuffer<uint> result;
StructuredBuffer<uint> cameraPixels;
SamplerState MyLinearClampSampler;
uint equirectangularWidth;
uint equirectangularHeight;
uint ssaaFactor;
uint cameraWidth;
uint cameraHeight;
uint startY;
uint sliceHeight;
uint cameraPixelsSentinelIdx;
uint sentinelIdx;

[numthreads(32,32,1)] // Must match threadsX, threadsY in CapturePanorama.cs
void CubeMapToEquirectangular (uint3 dtid : SV_DispatchThreadID)
{
	if (dtid.x >= equirectangularWidth || dtid.y >= sliceHeight) // In case width/height not multiple of numthreads
	    return;
	if (dtid.x == 0u && dtid.y == 0u)
		result[sentinelIdx] = cameraPixels[cameraPixelsSentinelIdx]; // Sentinel value - set correctly only if set correctly in input buffer

    // Must match enum UnityEngine.CubemapFace
    static const uint PositiveX = 0u;
    static const uint NegativeX = 1u;
    static const uint PositiveY = 2u;
    static const uint NegativeY = 3u;
    static const uint PositiveZ = 4u;
    static const uint NegativeZ = 5u;

	static const float pi = 3.14159265f;
	float4 totalColor = float4(0.0f, 0.0f, 0.0f, 0.0f);

	uint2 pos = uint2(dtid.x, dtid.y + startY);
	uint2 loopStart = pos * ssaaFactor;
	uint2 loopEnd = loopStart + uint2(ssaaFactor, ssaaFactor);

	for (uint y = loopStart.y; y < loopEnd.y; y++)
	{
		for (uint x = loopStart.x; x < loopEnd.x; x++)
		{
			float xcoord = (float)x / (equirectangularWidth  * ssaaFactor);
			float ycoord = (float)y / (equirectangularHeight * ssaaFactor);
			float latitude = (ycoord - 0.5f) * pi;
			float longitude = (xcoord * 2.0f - 1.0f) * pi;

			float cosLat = cos(latitude);
			float equirectRayDirectionX = cosLat * sin (longitude);
			float equirectRayDirectionY = sin (latitude);
			float equirectRayDirectionZ = cosLat * cos (longitude);

			float distance;
			float u, v;
			uint cameraNum;

			distance = 1.0f / equirectRayDirectionY;
			u = equirectRayDirectionX * distance; v = equirectRayDirectionZ * distance;
			if (u * u <= 1.0f && v * v <= 1.0f) {
				if (equirectRayDirectionY > 0.0f) {
					cameraNum = PositiveY;
				} else {
					u = -u;
					cameraNum = NegativeY;
				}
			}
	        else
			{
				distance = 1.0f / equirectRayDirectionX;
				u = -equirectRayDirectionZ * distance; v = equirectRayDirectionY * distance;
				if (u * u <= 1.0f && v * v <= 1.0f) {
					if (equirectRayDirectionX > 0.0f) {
						v = -v;
						cameraNum = PositiveX;
					} else {
						cameraNum = NegativeX;
					}
				}
				else
				{
					distance = 1.0f / equirectRayDirectionZ;
					u = equirectRayDirectionX * distance; v = equirectRayDirectionY * distance;
					if (u * u <= 1.0f && v * v <= 1.0f) {
						if (equirectRayDirectionZ > 0.0f) {
							v = -v;
							cameraNum = PositiveZ;
						} else {
							cameraNum = NegativeZ;
						}
					}
				}
			}

			u = (u + 1.0f) * 0.5f;
			v = (v + 1.0f) * 0.5f;

			// GetCameraPixelBilinear(cameraPixels, cameraNum, u, v);

			u *= cameraWidth;
			v *= cameraHeight;
			uint left   = min(cameraWidth  - 1u, (uint)floor(u)); // Modified to add check
			uint right  = min(cameraWidth  - 1u, left + 1u);
			uint top    = min(cameraHeight - 1u, (uint)floor(v)); // Modified to add check
			uint bottom = min(cameraHeight - 1u, top  + 1u);
			float uFrac = frac(u);
			float vFrac = frac(v);

			uint baseIdx = cameraNum * cameraWidth * cameraHeight;
			uint topRow    = baseIdx + top    * cameraWidth;
			uint bottomRow = baseIdx + bottom * cameraWidth;
			uint topLeft     = cameraPixels[topRow    + left ];
			uint topRight    = cameraPixels[topRow    + right];
			uint bottomLeft  = cameraPixels[bottomRow + left ];
			uint bottomRight = cameraPixels[bottomRow + right];

			float r = lerp(lerp( topLeft  >> 16u         ,  bottomLeft  >> 16u         , vFrac),
				           lerp( topRight >> 16u         ,  bottomRight >> 16u         , vFrac), uFrac);
			float g = lerp(lerp((topLeft  >>  8u) & 0xFFu, (bottomLeft  >>  8u) & 0xFFu, vFrac),
					       lerp((topRight >>  8u) & 0xFFu, (bottomRight >>  8u) & 0xFFu, vFrac), uFrac);
			float b = lerp(lerp( topLeft          & 0xFFu,  bottomLeft          & 0xFFu, vFrac),
						   lerp( topRight         & 0xFFu,  bottomRight         & 0xFFu, vFrac), uFrac);

			totalColor += float4(r, g, b, 255.0f);
		}
	}

	totalColor /= ssaaFactor * ssaaFactor;
	result[(dtid.y * equirectangularWidth) + dtid.x] =
	    ((uint)totalColor.r << 16u) | ((uint)totalColor.g << 8u) | (uint)totalColor.b;
}

[numthreads(32,32,1)] // Must match threadsX, threadsY in CapturePanorama.cs
void CubeMapToEquirectangularPositiveY (uint3 dtid : SV_DispatchThreadID)
{
	if (dtid.x >= equirectangularWidth || dtid.y >= sliceHeight) // In case width/height not multiple of numthreads
	    return;
	if (dtid.x == 0u && dtid.y == 0u)
		result[sentinelIdx] = cameraPixels[cameraPixelsSentinelIdx]; // Sentinel value - set correctly only if set correctly in input buffer

    static const uint cameraNum = 2; /* PositiveY */
	static const float pi = 3.14159265f;
	float4 totalColor = float4(0.0f, 0.0f, 0.0f, 0.0f);

	uint2 pos = uint2(dtid.x, dtid.y + startY);
	uint2 loopStart = pos * ssaaFactor;
	uint2 loopEnd = loopStart + uint2(ssaaFactor, ssaaFactor);

	for (uint y = loopStart.y; y < loopEnd.y; y++)
	{
		for (uint x = loopStart.x; x < loopEnd.x; x++)
		{
			float xcoord = (float)x / (equirectangularWidth * ssaaFactor);
			float ycoord = (float)y / (equirectangularHeight * ssaaFactor);
			float latitude = (ycoord - 0.5f) * pi;
			float longitude = (xcoord * 2.0f - 1.0f) * pi;

			float cosLat = cos(latitude);
			float equirectRayDirectionX = cosLat * sin (longitude);
			float equirectRayDirectionY = sin (latitude);
			float equirectRayDirectionZ = cosLat * cos (longitude);

			float distance = 1.0f / equirectRayDirectionY;
			float u = equirectRayDirectionX * distance, v = equirectRayDirectionZ * distance;

			u = (u + 1.0f) * 0.5f;
			v = (v + 1.0f) * 0.5f;
			
			// GetCameraPixelBilinear(cameraPixels, cameraNum, u, v);

			u *= cameraWidth;
			v *= cameraHeight;
			uint left   = (uint)floor(u);
			uint right  = min(cameraWidth  - 1u, left + 1u);
			uint top    = (uint)floor(v);
			uint bottom = min(cameraHeight - 1u, top  + 1u);
			float uFrac = frac(u);
			float vFrac = frac(v);

			uint baseIdx = cameraNum * cameraWidth * cameraHeight;
			uint topRow    = baseIdx + top    * cameraWidth;
			uint bottomRow = baseIdx + bottom * cameraWidth;
			uint topLeft     = cameraPixels[topRow    + left ];
			uint topRight    = cameraPixels[topRow    + right];
			uint bottomLeft  = cameraPixels[bottomRow + left ];
			uint bottomRight = cameraPixels[bottomRow + right];

			float r = lerp(lerp( topLeft  >> 16u         ,  bottomLeft  >> 16u         , vFrac),
				           lerp( topRight >> 16u         ,  bottomRight >> 16u         , vFrac), uFrac);
			float g = lerp(lerp((topLeft  >>  8u) & 0xFFu, (bottomLeft  >>  8u) & 0xFFu, vFrac),
					       lerp((topRight >>  8u) & 0xFFu, (bottomRight >>  8u) & 0xFFu, vFrac), uFrac);
			float b = lerp(lerp( topLeft          & 0xFFu,  bottomLeft          & 0xFFu, vFrac),
						   lerp( topRight         & 0xFFu,  bottomRight         & 0xFFu, vFrac), uFrac);

			totalColor += float4(r, g, b, 255.0f);
		}
	}

	totalColor /= ssaaFactor * ssaaFactor;
	result[(dtid.y * equirectangularWidth) + dtid.x] =
	    ((uint)totalColor.r << 16u) | ((uint)totalColor.g << 8u) | (uint)totalColor.b;
}

[numthreads(32,32,1)] // Must match threadsX, threadsY in CapturePanorama.cs
void CubeMapToEquirectangularNegativeY (uint3 dtid : SV_DispatchThreadID)
{
	if (dtid.x >= equirectangularWidth || dtid.y >= sliceHeight) // In case width/height not multiple of numthreads
	    return;
	if (dtid.x == 0u && dtid.y == 0u)
		result[sentinelIdx] = cameraPixels[cameraPixelsSentinelIdx]; // Sentinel value - set correctly only if set correctly in input buffer

    static const uint cameraNum = 3; /* NegativeY */
	static const float pi = 3.14159265f;
	float4 totalColor = float4(0.0f, 0.0f, 0.0f, 0.0f);

	uint2 pos = uint2(dtid.x, dtid.y + startY);
	uint2 loopStart = pos * ssaaFactor;
	uint2 loopEnd = loopStart + uint2(ssaaFactor, ssaaFactor);

	for (uint y = loopStart.y; y < loopEnd.y; y++)
	{
		for (uint x = loopStart.x; x < loopEnd.x; x++)
		{
			float xcoord = (float)x / (equirectangularWidth * ssaaFactor);
			float ycoord = (float)y / (equirectangularHeight * ssaaFactor);
			float latitude = (ycoord - 0.5f) * pi;
			float longitude = (xcoord * 2.0f - 1.0f) * pi;

			float cosLat = cos(latitude);
			float equirectRayDirectionX = cosLat * sin (longitude);
			float equirectRayDirectionY = sin (latitude);
			float equirectRayDirectionZ = cosLat * cos (longitude);

			float distance = 1.0f / equirectRayDirectionY;
			float u = equirectRayDirectionX * distance, v = equirectRayDirectionZ * distance;
			u = -u;

			u = (u + 1.0f) * 0.5f;
			v = (v + 1.0f) * 0.5f;
			
			// GetCameraPixelBilinear(cameraPixels, cameraNum, u, v);

			u *= cameraWidth;
			v *= cameraHeight;
			uint left   = (uint)floor(u);
			uint right  = min(cameraWidth  - 1u, left + 1u);
			uint top    = (uint)floor(v);
			uint bottom = min(cameraHeight - 1u, top  + 1u);
			float uFrac = frac(u);
			float vFrac = frac(v);

			uint baseIdx = cameraNum * cameraWidth * cameraHeight;
			uint topRow    = baseIdx + top    * cameraWidth;
			uint bottomRow = baseIdx + bottom * cameraWidth;
			uint topLeft     = cameraPixels[topRow    + left ];
			uint topRight    = cameraPixels[topRow    + right];
			uint bottomLeft  = cameraPixels[bottomRow + left ];
			uint bottomRight = cameraPixels[bottomRow + right];

			float r = lerp(lerp( topLeft  >> 16u         ,  bottomLeft  >> 16u         , vFrac),
				           lerp( topRight >> 16u         ,  bottomRight >> 16u         , vFrac), uFrac);
			float g = lerp(lerp((topLeft  >>  8u) & 0xFFu, (bottomLeft  >>  8u) & 0xFFu, vFrac),
					       lerp((topRight >>  8u) & 0xFFu, (bottomRight >>  8u) & 0xFFu, vFrac), uFrac);
			float b = lerp(lerp( topLeft          & 0xFFu,  bottomLeft          & 0xFFu, vFrac),
						   lerp( topRight         & 0xFFu,  bottomRight         & 0xFFu, vFrac), uFrac);

			totalColor += float4(r, g, b, 255.0f);
		}
	}

	totalColor /= ssaaFactor * ssaaFactor;
	result[(dtid.y * equirectangularWidth) + dtid.x] =
	    ((uint)totalColor.r << 16u) | ((uint)totalColor.g << 8u) | (uint)totalColor.b;
}
