/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkGPUVolumeRayCastMapper_ShadeFS.glsl

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
// Fragment shader that implements initShade() and shade() in the case of
// shading.
// The functions are used in composite mode.

#version 110

// "value" is a sample of the dataset.
// Think of "value" as an object.

// from 1- vs 4-component shader.
vec4 colorFromValue(vec4 value);

uniform sampler3D dataSetTexture; // need neighbors for gradient

// Change-of-coordinate matrix from eye space to texture space
uniform mat3 eyeToTexture3;
uniform mat4 eyeToTexture4;

// Tranpose of Change-of-coordinate matrix from texture space to eye space
uniform mat3 transposeTextureToEye;

// Used to compute the gradient.
uniform vec3 cellStep;
uniform vec3 cellScale;


// Entry position (global scope), updated in the loop
vec3 pos;
// Incremental vector in texture space (global scope)
vec3 rayDir;


// local to the implementation, shared between initShade() and shade()
const vec3 minusOne=vec3(-1.0,-1.0,-1.0);
const vec4 clampMin=vec4(0.0,0.0,0.0,0.0);
const vec4 clampMax=vec4(1.0,1.0,1.0,1.0);

vec3 xvec;
vec3 yvec;
vec3 zvec;
vec3 wReverseRayDir;
vec3 lightPos;
vec3 ldir;
vec3 h;
vec4 hPos; // homogeneous position

// ----------------------------------------------------------------------------
void initShade()
{
  xvec=vec3(cellStep.x,0.0,0.0); // 0.01
  yvec=vec3(0.0,cellStep.y,0.0);
  zvec=vec3(0.0,0.0,cellStep.z);

  // Reverse ray direction in eye space
  wReverseRayDir=eyeToTexture3*rayDir;
  wReverseRayDir=wReverseRayDir*minusOne;
  wReverseRayDir=normalize(wReverseRayDir);

  // Directonal light: w==0
  if(gl_LightSource[0].position.w==0.0)
    {
    ldir=gl_LightSource[0].position.xyz;
    ldir=normalize(ldir);
    h=normalize(ldir+wReverseRayDir);
    }
  else
    {
    lightPos=gl_LightSource[0].position.xyz/gl_LightSource[0].position.w;
    hPos.w=1.0; // used later
    }
}

// ----------------------------------------------------------------------------
vec4 shade(vec4 value)
{
  vec3 g1;
  vec3 g2;
  vec4 tmp;
  float att;
  float spot;

  g1.x=texture3D(dataSetTexture,pos+xvec).x;
  g1.y=texture3D(dataSetTexture,pos+yvec).x;
  g1.z=texture3D(dataSetTexture,pos+zvec).x;
  g2.x=texture3D(dataSetTexture,pos-xvec).x;
  g2.y=texture3D(dataSetTexture,pos-yvec).x;
  g2.z=texture3D(dataSetTexture,pos-zvec).x;
  // g1-g2 is  the gradient in texture coordinates
  // the result is the normalized gradient in eye coordinates.

  g2=g1-g2;
  g2=g2*cellScale;

  float normalLength=length(g2);
  if(normalLength>0.0)
    {
    g2=normalize(transposeTextureToEye*g2);
    }
  else
    {
    g2=vec3(0.0,0.0,0.0);
    }

  vec4 color=colorFromValue(value);

  vec4 frontLightProduct_Diffuse= gl_LightSource[0].diffuse*gl_FrontMaterial.diffuse;
  vec4 frontLightProduct_Specular= gl_LightSource[0].specular*gl_FrontMaterial.specular;

  // initialize color to 0.0
  vec4 finalColor=vec4(0.0,0.0,0.0,0.0);

  if(gl_LightSource[0].position.w!=0.0)
    {
    // We need to know the eye position only if light is positional
    // ldir= vertex position in eye coordinates
    hPos.xyz=pos;
    tmp=eyeToTexture4*hPos;
    ldir=tmp.xyz/tmp.w;
    // ldir=light direction
    ldir=lightPos-ldir;
    float sqrDistance=dot(ldir,ldir);
    ldir=normalize(ldir);
    h=normalize(ldir+wReverseRayDir);
    att=1.0/(gl_LightSource[0].constantAttenuation+gl_LightSource[0].linearAttenuation*sqrt(sqrDistance)+gl_LightSource[0].quadraticAttenuation*sqrDistance);
    }
  else
    {
    att=1.0;
    }

  if(att>0.0)
    {

    if(gl_LightSource[0].spotCutoff==180.0)
      {
      spot=1.0;
      }
    else
      {
      float coef=-dot(ldir,gl_LightSource[0].spotDirection);
      if(coef>=gl_LightSource[0].spotCosCutoff)
        {
        spot=pow(coef,gl_LightSource[0].spotExponent);
        }
      else
        {
        spot=0.0;
        }
      }

    if(spot>0.0)
      {
      // LIT operation...
      float nDotL=dot(g2,ldir);
      float nDotH=dot(g2,h);

      // separate nDotL and nDotH for two-sided shading, otherwise we
      // get black spots.

      if(nDotL<0.0) // two-sided shading
        {
        nDotL=-nDotL;
        }

      if(nDotH<0.0) // two-sided shading
        {
        nDotH=-nDotH;
        }

      // ambient term for this light
      finalColor+=gl_FrontLightProduct[0].ambient;

      // diffuse term for this light
      if(nDotL>0.0)
        {
        finalColor +=(frontLightProduct_Diffuse*nDotL)*color;
        }

      // specular term for this light
      float shininessFactor=pow(nDotH,gl_FrontMaterial.shininess);
      finalColor+=frontLightProduct_Specular*shininessFactor;
      finalColor*=att*spot;
      }
    }

  // scene ambient term
  finalColor+=gl_FrontLightModelProduct.sceneColor*color;

  // clamp. otherwise we get black spots
  finalColor=clamp(finalColor,clampMin,clampMax);

  return finalColor;
}
