/**
*  Raycasting-Fragment-Shader
*/

uniform sampler3D volume;     // the volume-texture

uniform sampler2D frontSide;  // rendered front-side-texture
uniform sampler2D backSide;   // rendered back-side-texture

uniform sampler1D tf;         // transfer-function

uniform mat4      invRotMatrix;            // inverse rotation matrix

uniform float     numberOfSamplesPerRay;   // max number of steps per ray
uniform float     maxRayLength;            // max ray length (volume-diagonal)
uniform int       renderMode;              // switch for render-mode

uniform float     vsX;                    // voxelSize in x-dimension
uniform float     vsY;                    // voxelSize in y-dimension
uniform float     vsZ;                    // voxelSize in z-dimension

uniform float     kd;                      // diffuse-constant for phong
uniform float     ka;                      // ambient-constant for phong
uniform float     ks;                      // specular-constant for phong
uniform float     pn;                      // specular-exponent for phong

varying vec2      texCoordBBox;            // holds the current bounding-cube-texture-coordiants



void main()
{

  vec4 entryPoint = texture2D( frontSide, texCoordBBox );   // get ray-entry-point
  vec4 exitPoint  = texture2D( backSide,  texCoordBBox );   // get ray-exit-point


  // check if volume hit
  if ( entryPoint.w == 0.0 ) 
  {
    discard;
  } 
  else
  {

    vec4  rayDirection   = exitPoint - entryPoint;    // calculate ray-direction  
    float rayLength      = length( rayDirection );    // calculate ray-length
    vec4  n_RayDirection = normalize( rayDirection ); // normalize ray

    vec4  currentSampleColor    = { 0.0, 0.0, 0.0, 0.0 };   // color of the current sample
    vec3  currentVolumePosition = entryPoint.xyz;           // current position in the volume
    float currentAlpha          = 0.0;                      // current alpha value
    vec4  currentIntensity      = { 0.0, 0.0, 0.0, 0.0 };   // intensity of the current sample
    
    float oneStepLength = maxRayLength / numberOfSamplesPerRay;  // length of one step
    vec3  oneStep       = n_RayDirection.xyz * oneStepLength;    // vector for one step
    
    vec4  accSampleColor = { 0.0, 0.0, 0.0, 0.0 };    // acculmulated color of the current ray
    float accAlpha  = 0.0;                            // accumulated alpha value of the current ray
    float accLength = 0.0;                            // accumulated length of the current ray

    float n = 0.0;                                    // number of sample per ray for average
    int   maxIter = int( floor( numberOfSamplesPerRay ) ) + 10;   // maximum number of iterations along ray
    vec4  tfColor = { 0.0, 0.0, 0.0, 0.0 };           // color looked-up from the TF
    

    // trace ray
    for (int i=0; i<maxIter; i++)
    {

      currentIntensity = texture3D( volume, currentVolumePosition );    // lookup sample in volume
      
      if ( currentIntensity.x >= 0.2 ) { // workarround for error in TF 
      
      tfColor = texture1D( tf, currentIntensity.x );    // lookup sample in TF
      
      currentSampleColor = tfColor;

      
      // --- accumulated average (compositing) ---
      if ( renderMode == 0 )  
      {
        currentAlpha = currentSampleColor.a;
        //currentAlpha = currentSampleColor.a * oneStepLength;    // scale alpha

        accSampleColor.r = accSampleColor.r + ( 1.0 - accAlpha ) * currentAlpha * currentSampleColor.r;
        accSampleColor.g = accSampleColor.g + ( 1.0 - accAlpha ) * currentAlpha * currentSampleColor.g;
        accSampleColor.b = accSampleColor.b + ( 1.0 - accAlpha ) * currentAlpha * currentSampleColor.b;
        //accSampleColor = accSampleColor + ( 1.0 - accAlpha ) * currentAlpha * currentSampleColor;
        accAlpha = accAlpha + ( 1.0 - accAlpha ) * currentAlpha;
      }


      // --- accumulated average (compositing) with gradient ---
      if ( renderMode == 5 )  
      {
      
        // calculate gradient
        vec3 gradient = { 0.0, 0.0, 0.0 };
        
        // x
        vec4 val1 = texture3D( volume, vec3( currentVolumePosition.x-vsX, currentVolumePosition.y, currentVolumePosition.z ) );
        vec4 val2 = texture3D( volume, vec3( currentVolumePosition.x+vsX, currentVolumePosition.y, currentVolumePosition.z ) );
        gradient.x = (val2.x - val1.x ) * 0.5;

        // y
        val1 = texture3D( volume, vec3( currentVolumePosition.x, currentVolumePosition.y-vsY, currentVolumePosition.z ) );
        val2 = texture3D( volume, vec3( currentVolumePosition.x, currentVolumePosition.y+vsY, currentVolumePosition.z ) );
        gradient.y = (val2.x - val1.x ) * 0.5;

        // z
        val1 = texture3D( volume, vec3( currentVolumePosition.x, currentVolumePosition.y, currentVolumePosition.z-vsZ ) );
        val2 = texture3D( volume, vec3( currentVolumePosition.x, currentVolumePosition.y, currentVolumePosition.z+vsZ ) );
        gradient.z = (val2.x - val1.x ) * 0.5;

        float gradLength = length( gradient );      // not used currently
        vec3  n_gradient = normalize( gradient );   // normalized gradient
        

        // apply phong shading
        // params
        vec3 viewVector  = { 0.0, 0.0, 1.0 };
        vec3 lightVector = { 1.0, 1.0, 1.0 };
        
        lightVector = normalize( lightVector );
        viewVector  = normalize( viewVector  );

        vec4 transLightVector = invRotMatrix * vec4( lightVector.xyz, 0.0 );
        lightVector = transLightVector.xyz;

        vec4 transViewVector = invRotMatrix * vec4( viewVector.xyz, 0.0 );
        viewVector = transViewVector.xyz;

        lightVector = normalize( lightVector );
        viewVector  = normalize( viewVector  );

        vec3 reflectionVector = reflect( lightVector, n_gradient );
        vec4 incomingLight    = tfColor;
        vec4 phongIntensity   = { 0.0, 0.0, 0.0, 0.0 };

        float NDotL = dot( lightVector, n_gradient );
        float VDotR = dot( reflectionVector, viewVector );
        
        float maxNDotL = max( NDotL , 0.0 );
        float maxVDotR = 0.0;
        
        if ( maxNDotL > 0.0 ) 
          maxVDotR = max( VDotR, 0.0 );

        // calculate phong-value
        //                 | ------ ambient ------  | ------------ diffuse ------------ | ----------- specular ------------|
        phongIntensity.rgb = incomingLight.rgb * ka + incomingLight.rgb * kd * maxNDotL + incomingLight.rgb * ks * pow( maxVDotR, pn );

        
        // set fragment-values
        currentSampleColor.rgb = phongIntensity.rgb;
        currentAlpha = currentSampleColor.a;

        accSampleColor.rgb = accSampleColor.rgb + ( 1.0 - accAlpha ) * currentAlpha * currentSampleColor.rgb;
        accAlpha = accAlpha + ( 1.0 - accAlpha ) * currentAlpha;
      }


      // --- maximum intensity projection --- 
      if ( renderMode == 1 )  
      {
        if ( currentIntensity.x > accSampleColor.x ) 
          accSampleColor = currentIntensity;
      }


      // --- first hit ---
      if ( renderMode == 2 )
      {
        if ( tfColor.a > 0.1 ) 
        {
          accSampleColor = currentSampleColor;
          //accLength = rayLength + 1.0;
          break;
        }
      }
      

      // --- average (x-ray) --- 
      if ( renderMode == 3 )  
      {
        if ( currentIntensity.x > 0.0 )
        {
          n = n + 1.0;
          accSampleColor += currentIntensity;
        }
      }


      // --- average with TF ("bright x-ray") --- 
      if ( renderMode == 4 ) {
        if ( currentSampleColor.a > 0.0 )
        {
          n = n + 1.0;
          accSampleColor += currentSampleColor;
        }
      }
      }
      
      // advance in volume
      currentVolumePosition += oneStep;
      accLength             += oneStepLength;
      
      if ( accLength > rayLength || accAlpha >= 1.0 ) break;  // if outside volume (should never happen!) or alpha already 1 -> we're done

    } // for

    
    if ( renderMode == 3 || renderMode == 4 )
    {
      accSampleColor = accSampleColor / n;
    }
    
    if ( renderMode == 0 || renderMode == 5 )
    {
      accSampleColor.a = accAlpha;
    }
    
    gl_FragColor = accSampleColor;

  } // else

}
