// Copyright (C) 2011 by The Alexandra Institute Ltd.
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#version 330

precision mediump float;

// Attributes
// See SceneVS.glsl
in vec3 kWorldPos;
in vec3 kNormal;
in vec2 kTexcoord;

out vec4 FRAGDATA0; // Final output color

// Uniforms
// Intensity LPV grids
uniform sampler3D kVolumeRed;
uniform sampler3D kVolumeGreen;
uniform sampler3D kVolumeBlue;

uniform vec3 kCameraPos;	// World-space camera position

uniform vec3 kLightPos;			// World-space light position
uniform vec3 kLightDir;			// World-space light direction
uniform vec3 kLightIntensity;	// Light intensity
uniform float fSpecularPow;		// Material shininess
uniform float fSpecularScale;	// Material shininess

uniform vec3 kGridOrigin;		// World-space grid origin
uniform float fInvWSGridSize;	// Inverse world-space grid cell size
uniform float fInvGridSize;		// Inverse clip-space grid cell size
uniform int iGridSize;			// Grid cell count
uniform float fCellScale;		// 2.0f / (fCellWorldSpaceWidth * fCellWorldSpaceWidth)
uniform float fRadianceScale1;	// 1.0f / sqrtf(4.0f * PI)
uniform float fRadianceScale2;	// sqrtf(3.0f / (4.0f * PI)) * 0.5f

uniform float fIOR;		// Index of refraction
uniform float fInvIOR;	// Inverse index of refraction

uniform float fFluxScale; // Scale of final radiance - Artist directability

// Shader code
void main()
{	
	vec3 kNrm = normalize(kNormal);	

	// Find normalized pos - camera vector	
	vec3 kWSViewDir = normalize(kWorldPos - kCameraPos);	

	// Fresnel
	vec3 kRefractDir = refract(kWSViewDir, kNrm, fInvIOR);
	
	float fThetaI = acos(max(0.0, dot(kNrm, -kWSViewDir)));
	float fThetaR = acos(max(0.0, dot(-kNrm, kRefractDir)));
	
	float fFresnel = 0.0;
	if (fThetaI == 0.0)
	{
		fFresnel = pow((fIOR - 1.0) / (fIOR + 1.0), 2.0);
	}
	else
	{
		fFresnel = 0.5 *
			(
			  (sin(fThetaI - fThetaR) * sin(fThetaI - fThetaR)) /
			  (sin(fThetaI + fThetaR) * sin(fThetaI + fThetaR))
			) *
			(
			1.0 +
			(cos(fThetaI + fThetaR) * cos(fThetaI + fThetaR)) /
			(cos(fThetaI - fThetaR) * cos(fThetaI - fThetaR))
			);
	}
	
	// Direct light calculation
	vec3 kReflectedRadiance = vec3(0.0, 0.0, 0.0);
	vec3 kWSLightDir = (kLightPos - kWorldPos);
	vec3 kWSLightDirNrm = normalize(kWSLightDir);

	if (dot(kNrm, kWSLightDirNrm) >= 0.0)
	{
		// Diffuse term
		float fDTerm = clamp(dot(kNrm, kWSLightDirNrm), 0.0, 1.0);
	
		// Specular term
		float fSTerm = pow(clamp(dot(kWSViewDir, reflect(kWSLightDirNrm, kNrm)), 0.0, 1.0), fSpecularPow) * fSpecularScale;	

		// Fresnel
		vec3 kRefractDir = refract(-kWSLightDirNrm, kNrm, fInvIOR);
		kRefractDir = normalize(kRefractDir);

		float fThetaI = acos(max(0.0, dot(kNrm, kWSLightDirNrm)));
		float fThetaR = acos(max(0.0, dot(-kNrm, kRefractDir)));

		float fFresnel = 0.0;
		if (fThetaI == 0.0)
		{
			fFresnel = pow((fIOR - 1.0) / (fIOR + 1.0), 2.0);
		}
		else
		{
			fFresnel = 0.5 *
				(
				  (sin(fThetaI - fThetaR) * sin(fThetaI - fThetaR)) /
				  (sin(fThetaI + fThetaR) * sin(fThetaI + fThetaR))
				) *
				(
				1.0 +
				(cos(fThetaI + fThetaR) * cos(fThetaI + fThetaR)) /
				(cos(fThetaI - fThetaR) * cos(fThetaI - fThetaR))
				);
		}

		kReflectedRadiance += (kLightIntensity / dot(kWSLightDir, kWSLightDir)) * fFresnel * (fDTerm + fSTerm);
	}
	
	// Find normalized grid position	
	vec3 kGridPos = (kWorldPos - kGridOrigin) * fInvWSGridSize;
		
	// Sample voxel light SH coeffs
	vec4 kCoeffsRed = texture(kVolumeRed, kGridPos);
	vec4 kCoeffsGreen = texture(kVolumeGreen, kGridPos);
	vec4 kCoeffsBlue = texture(kVolumeBlue, kGridPos);

	// Find rotation axis
	float fCosBeta = kNrm.z;
	float fBeta = acos(fCosBeta);
	float fSinBeta = sin(fBeta);

	vec3 kRotationMat = vec3(kNrm.x * fSinBeta, kNrm.y * fSinBeta, fCosBeta);

	// Transform SH coeffs
	float fRotCoeffZRed   = dot(kRotationMat, kCoeffsRed.wyz);
	float fRotCoeffZGreen = dot(kRotationMat, kCoeffsGreen.wyz);
	float fRotCoeffZBlue  = dot(kRotationMat, kCoeffsBlue.wyz);

	//	Apply radiance scale
	vec3 kOutgoingRadiance;
	kOutgoingRadiance.r = fCellScale * (kCoeffsRed.x * fRadianceScale1 + fRotCoeffZRed * fRadianceScale2);
	kOutgoingRadiance.g = fCellScale * (kCoeffsGreen.x * fRadianceScale1 + fRotCoeffZGreen * fRadianceScale2);
	kOutgoingRadiance.b = fCellScale * (kCoeffsBlue.x * fRadianceScale1 + fRotCoeffZBlue * fRadianceScale2);
	
	vec3 kTransmittedRadiance = fFluxScale * kOutgoingRadiance * (1.0 - fFresnel);
	kTransmittedRadiance = max(vec3(0,0,0), kTransmittedRadiance);
	
	// Output final radiance
	FRAGDATA0 = vec4((kTransmittedRadiance + kReflectedRadiance), 1.0);
}
