// Created on: 2013-10-10
// Created by: Denis BOGOLEPOV
// Copyright (c) 2013-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

varying vec3 View;          //!< Direction to the viewer
varying vec3 Normal;        //!< Vertex normal in view space
varying vec4 Position;      //!< Vertex position in view space.
varying vec4 PositionWorld; //!< Vertex position in world space

vec3 Ambient;  //!< Ambient  contribution of light sources
vec3 Diffuse;  //!< Diffuse  contribution of light sources
vec3 Specular; //!< Specular contribution of light sources

//! Computes contribution of isotropic point light source
void pointLight (in int  theId,
                 in vec3 theNormal,
                 in vec3 theView,
                 in vec3 thePoint)
{
  vec3 aLight = occLight_Position (theId).xyz;
  if (!occLight_IsHeadlight (theId))
  {
    aLight = vec3 (occWorldViewMatrix * vec4 (aLight, 1.0));
  }
  aLight -= thePoint;

  float aDist = length (aLight);
  aLight = aLight * (1.0 / aDist);

  float anAtten = 1.0 / (occLight_ConstAttenuation  (theId)
                       + occLight_LinearAttenuation (theId) * aDist);

  vec3 aHalf = normalize (aLight + theView);

  vec3  aFaceSideNormal = gl_FrontFacing ? theNormal : -theNormal;
  float aNdotL = max (0.0, dot (aFaceSideNormal, aLight));
  float aNdotH = max (0.0, dot (aFaceSideNormal, aHalf ));

  float aSpecl = 0.0;
  if (aNdotL > 0.0)
  {
    aSpecl = pow (aNdotH, occMaterial_Shininess (gl_FrontFacing));
  }

  Diffuse  += occLight_Diffuse  (theId).rgb * aNdotL * anAtten;
  Specular += occLight_Specular (theId).rgb * aSpecl * anAtten;
}

//! Computes contribution of spotlight source
void spotLight (in int  theId,
                in vec3 theNormal,
                in vec3 theView,
                in vec3 thePoint)
{
  vec3 aLight   = occLight_Position      (theId).xyz;
  vec3 aSpotDir = occLight_SpotDirection (theId).xyz;
  if (!occLight_IsHeadlight (theId))
  {
    aLight   = vec3 (occWorldViewMatrix * vec4 (aLight,   1.0));
    aSpotDir = vec3 (occWorldViewMatrix * vec4 (aSpotDir, 0.0));
  }
  aLight -= thePoint;

  float aDist = length (aLight);
  aLight = aLight * (1.0 / aDist);

  aSpotDir = normalize (aSpotDir);

  // light cone
  float aCosA = dot (aSpotDir, -aLight);
  if (aCosA >= 1.0 || aCosA < cos (occLight_SpotCutOff (theId)))
  {
    return;
  }

  float anExponent = occLight_SpotExponent (theId);
  float anAtten    = 1.0 / (occLight_ConstAttenuation  (theId)
                          + occLight_LinearAttenuation (theId) * aDist);
  if (anExponent > 0.0)
  {
    anAtten *= pow (aCosA, anExponent * 128.0);
  }

  vec3 aHalf = normalize (aLight + theView);

  vec3  aFaceSideNormal = gl_FrontFacing ? theNormal : -theNormal;
  float aNdotL = max (0.0, dot (aFaceSideNormal, aLight));
  float aNdotH = max (0.0, dot (aFaceSideNormal, aHalf ));

  float aSpecl = 0.0;
  if (aNdotL > 0.0)
  {
    aSpecl = pow (aNdotH, occMaterial_Shininess (gl_FrontFacing));
  }

  Diffuse  += occLight_Diffuse  (theId).rgb * aNdotL * anAtten;
  Specular += occLight_Specular (theId).rgb * aSpecl * anAtten;
}

//! Computes contribution of directional light source
void directionalLight (in int  theId,
                       in vec3 theNormal,
                       in vec3 theView)
{
  vec3 aLight = normalize (occLight_Position (theId).xyz);
  if (!occLight_IsHeadlight (theId))
  {
    aLight = vec3 (occWorldViewMatrix * vec4 (aLight, 0.0));
  }

  vec3 aHalf = normalize (aLight + theView);

  vec3  aFaceSideNormal = gl_FrontFacing ? theNormal : -theNormal;
  float aNdotL = max (0.0, dot (aFaceSideNormal, aLight));
  float aNdotH = max (0.0, dot (aFaceSideNormal, aHalf ));

  float aSpecl = 0.0;
  if (aNdotL > 0.0)
  {
    aSpecl = pow (aNdotH, occMaterial_Shininess (gl_FrontFacing));
  }

  Diffuse  += occLight_Diffuse  (theId).rgb * aNdotL;
  Specular += occLight_Specular (theId).rgb * aSpecl;
}

//! Computes illumination from light sources
vec4 computeLighting (in vec3 theNormal,
                      in vec3 theView,
                      in vec4 thePoint)
{
  // Clear the light intensity accumulators
  Ambient  = occLightAmbient.rgb;
  Diffuse  = vec3 (0.0);
  Specular = vec3 (0.0);
  vec3 aPoint = thePoint.xyz / thePoint.w;
  for (int anIndex = 0; anIndex < occLightSourcesCount; ++anIndex)
  {
    int aType = occLight_Type (anIndex);
    if (aType == OccLightType_Direct)
    {
      directionalLight (anIndex, theNormal, theView);
    }
    else if (aType == OccLightType_Point)
    {
      pointLight (anIndex, theNormal, theView, aPoint);
    }
    else if (aType == OccLightType_Spot)
    {
      spotLight (anIndex, theNormal, theView, aPoint);
    }
  }

  vec3 aMatAmbient  = occMaterial_Ambient (gl_FrontFacing);
  vec4 aMatDiffuse  = occMaterial_Diffuse (gl_FrontFacing);
  vec3 aMatSpecular = occMaterial_Specular(gl_FrontFacing);
  vec3 aMatEmission = occMaterial_Emission(gl_FrontFacing);
  vec3 aColor = Ambient  * aMatAmbient.rgb
              + Diffuse  * aMatDiffuse.rgb
              + Specular * aMatSpecular.rgb
                         + aMatEmission.rgb;
  return vec4 (aColor, aMatDiffuse.a);
}

//! Entry point to the Fragment Shader
void main()
{
  // process clipping planes
  for (int anIndex = 0; anIndex < occClipPlaneCount; ++anIndex)
  {
    vec4 aClipEquation = occClipPlaneEquations[anIndex];
    if (dot (aClipEquation.xyz, PositionWorld.xyz / PositionWorld.w) + aClipEquation.w < 0.0)
    {
      discard;
    }
  }

  vec4 aColor = computeLighting (normalize (Normal), normalize (View), Position);
  occSetFragColor (aColor);
}