//VTK::System::Dec

// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

//////////////////////////////////////////////////////////////////////////////
///
/// Inputs
///
//////////////////////////////////////////////////////////////////////////////

/// 3D texture coordinates form vertex shader
in vec3 ip_textureCoords;
in vec3 ip_vertexPos;

//////////////////////////////////////////////////////////////////////////////
///
/// Outputs
///
//////////////////////////////////////////////////////////////////////////////

vec4 g_fragColor = vec4(0.0);

//////////////////////////////////////////////////////////////////////////////
///
/// Uniforms, attributes, and globals
///
//////////////////////////////////////////////////////////////////////////////
vec3 g_dataPos;
vec3 g_dirStep;
vec4 g_srcColor;
bool g_exit;
bool g_skip;
float g_currentT;
float g_terminatePointMax;
vec4 g_scalar;

uniform vec4 in_volume_scale[1];
uniform vec4 in_volume_bias[1];

out vec4 fragOutput0;

// Volume dataset
uniform sampler3D in_volume[1];
uniform int in_noOfComponents;
uniform int in_independentComponents;

uniform sampler2D in_noiseSampler;
#ifndef GL_ES
uniform sampler2D in_depthSampler;
#endif

// Camera position
uniform vec3 in_cameraPos;

// view and model matrices
uniform mat4 in_volumeMatrix[1];
uniform mat4 in_inverseVolumeMatrix[1];
uniform mat4 in_projectionMatrix;
uniform mat4 in_inverseProjectionMatrix;
uniform mat4 in_modelViewMatrix;
uniform mat4 in_inverseModelViewMatrix;
uniform mat4 in_textureDatasetMatrix[1];
uniform mat4 in_inverseTextureDatasetMatrix[1];
in mat4 ip_inverseTextureDataAdjusted;
uniform vec3 in_texMin;
uniform vec3 in_texMax;
uniform vec3 in_eyePosObjs[1];
uniform mat4 in_textureToEye[1];

// Ray step size
uniform vec3 in_cellStep[1];
uniform vec2 in_scalarsRange[4];
uniform vec3 in_cellSpacing[1];

// Sample distance
uniform float in_sampleDistance;

// Scales
uniform vec3 in_cellScale;
uniform vec2 in_windowLowerLeftCorner;
uniform vec2 in_inverseOriginalWindowSize;
uniform vec2 in_inverseWindowSize;
uniform vec3 in_textureExtentsMax;
uniform vec3 in_textureExtentsMin;

// Material and lighting
uniform vec3 in_diffuse[4];
uniform vec3 in_ambient[4];
uniform vec3 in_specular[4];
uniform float in_shininess[4];

// Others
uniform bool in_cellFlag;
uniform bool in_useJittering;
vec3 g_rayJitter = vec3(0.0);
uniform bool in_clampDepthToBackface;

uniform vec2 in_averageIPRange;
uniform bool in_twoSidedLighting;
vec3 g_xvec;
vec3 g_yvec;
vec3 g_zvec;
uniform vec3 in_lightAmbientColor[1];
uniform vec3 in_lightDiffuseColor[1];
uniform vec3 in_lightSpecularColor[1];
vec4 g_lightPosObj;
vec3 g_ldir;
vec3 g_vdir;
vec3 g_h;
bool l_updateDepth;
vec3 l_opaqueFragPos;

const float g_opacityThreshold = 1.0 - 1.0 / 255.0;

int clippingPlanesSize;
vec3 objRayDir;
mat4 textureToObjMat;

//VTK::GradientCache::Dec

uniform sampler2D in_opacityTransferFunc_0[1];
float computeOpacity(vec4 scalar)
{
  return texture2D(in_opacityTransferFunc_0[0], vec2(scalar.w, 0)).r;
}

// c is short for component
vec4 computeGradient(int c)
{
  // Approximate Nabla(F) derivatives with central differences.
  vec3 g1; // F_front
  vec3 g2; // F_back
  g1.x = texture3D(in_volume[0], vec3(g_dataPos + g_xvec))[c];
  g1.y = texture3D(in_volume[0], vec3(g_dataPos + g_yvec))[c];
  g1.z = texture3D(in_volume[0], vec3(g_dataPos + g_zvec))[c];
  g2.x = texture3D(in_volume[0], vec3(g_dataPos - g_xvec))[c];
  g2.y = texture3D(in_volume[0], vec3(g_dataPos - g_yvec))[c];
  g2.z = texture3D(in_volume[0], vec3(g_dataPos - g_zvec))[c];

  // Apply scale and bias to the fetched values.
  g1 = g1 * in_volume_scale[0][c] + in_volume_bias[0][c];
  g2 = g2 * in_volume_scale[0][c] + in_volume_bias[0][c];

  // Central differences: (F_front - F_back) / 2h
  // This version of computeGradient() is only used for lighting
  // calculations (only direction matters), hence the difference is
  // not scaled by 2h and a dummy gradient mag is returned (-1.).
  return vec4((g1 - g2), -1.0);
}



uniform sampler2D in_colorTransferFunc_0[1];
vec3 computeRayDirection()
{
  return normalize(ip_vertexPos.xyz - in_eyePosObjs[0].xyz);
}

//VTK::Picking::Dec

//VTK::RenderToImage::Dec

//VTK::DepthPeeling::Dec

/// We support only 8 clipping planes for now
/// The first value is the size of the data array for clipping
/// planes (origin, normal)
uniform float in_clippingPlanes[49];
uniform float in_scale;
uniform float in_bias;

//////////////////////////////////////////////////////////////////////////////
///
/// Helper functions
///
//////////////////////////////////////////////////////////////////////////////

/**
 * Transform window coordinate to NDC.
 */
vec4 WindowToNDC(const float xCoord, const float yCoord, const float zCoord)
{
  vec4 NDCCoord = vec4(0.0, 0.0, 0.0, 1.0);

  NDCCoord.x =
    (xCoord - in_windowLowerLeftCorner.x) * 2.0 * in_inverseWindowSize.x - 1.0;
  NDCCoord.y =
    (yCoord - in_windowLowerLeftCorner.y) * 2.0 * in_inverseWindowSize.y - 1.0;
  NDCCoord.z = (2.0 * zCoord - (gl_DepthRange.near + gl_DepthRange.far)) /
    gl_DepthRange.diff;

  return NDCCoord;
}

/**
 * Transform NDC coordinate to window coordinates.
 */
vec4 NDCToWindow(const float xNDC, const float yNDC, const float zNDC)
{
  vec4 WinCoord = vec4(0.0, 0.0, 0.0, 1.0);

  WinCoord.x =
    (xNDC + 1.f) / (2.f * in_inverseWindowSize.x) + in_windowLowerLeftCorner.x;
  WinCoord.y =
    (yNDC + 1.f) / (2.f * in_inverseWindowSize.y) + in_windowLowerLeftCorner.y;
  WinCoord.z =
    (zNDC * gl_DepthRange.diff + (gl_DepthRange.near + gl_DepthRange.far)) /
    2.f;

  return WinCoord;
}

//////////////////////////////////////////////////////////////////////////////
///
/// Ray-casting
///
//////////////////////////////////////////////////////////////////////////////

/**
 * Global initialization. This method should only be called once per shader
 * invocation regardless of whether castRay() is called several times (e.g.
 * vtkDualDepthPeelingPass). Any castRay() specific initialization should be
 * placed within that function.
 */
void initializeRayCast()
{
  /// Initialize g_fragColor (output) to 0
  g_fragColor = vec4(0.0);
  g_dirStep = vec3(0.0);
  g_srcColor = vec4(0.0);
  g_exit = false;

  bool l_adjustTextureExtents = !in_cellFlag;
  // Get the 3D texture coordinates for lookup into the in_volume dataset
  g_dataPos = ip_textureCoords.xyz;

  // Getting the ray marching direction (in dataset space);
  vec3 rayDir = computeRayDirection();

  // Multiply the raymarching direction with the step size to get the
  // sub-step size we need to take at each raymarching step
  g_dirStep =
    (ip_inverseTextureDataAdjusted * vec4(rayDir, 0.0)).xyz * in_sampleDistance;

  // 2D Texture fragment coordinates [0,1] from fragment coordinates.
  // The frame buffer texture has the size of the plain buffer but
  // we use a fraction of it. The texture coordinate is less than 1 if
  // the reduction factor is less than 1.
  // Device coordinates are between -1 and 1. We need texture
  // coordinates between 0 and 1. The in_noiseSampler and in_depthSampler
  // buffers have the original size buffer.
  vec2 fragTexCoord =
    (gl_FragCoord.xy - in_windowLowerLeftCorner) * in_inverseWindowSize;

  if (in_useJittering)
  {
    float jitterValue = texture2D(in_noiseSampler, fragTexCoord).x;
    g_rayJitter = g_dirStep * jitterValue;
    g_dataPos += g_rayJitter;
  }
  else
  {
    g_dataPos += g_dirStep;
  }

  // Flag to deternmine if voxel should be considered for the rendering
  g_skip = false;
  // Light position in dataset space
  g_lightPosObj = (in_inverseVolumeMatrix[0] * vec4(in_cameraPos, 1.0));
  if (g_lightPosObj.w != 0.0)
  {
    g_lightPosObj.x /= g_lightPosObj.w;
    g_lightPosObj.y /= g_lightPosObj.w;
    g_lightPosObj.z /= g_lightPosObj.w;
    g_lightPosObj.w = 1.0;
  }
  g_ldir = normalize(g_lightPosObj.xyz - ip_vertexPos);
  g_vdir = normalize(in_eyePosObjs[0].xyz - ip_vertexPos);
  g_h = normalize(g_ldir + g_vdir);
  g_xvec = vec3(in_cellStep[0].x, 0.0, 0.0);
  g_yvec = vec3(0.0, in_cellStep[0].y, 0.0);
  g_zvec = vec3(0.0, 0.0, in_cellStep[0].z);

  l_updateDepth = true;
  l_opaqueFragPos = vec3(0.0);

  // Flag to indicate if the raymarch loop should terminate
  bool stop = false;

  g_terminatePointMax = 0.0;

#ifdef GL_ES
  vec4 l_depthValue = vec4(1.0, 1.0, 1.0, 1.0);
#else
  vec4 l_depthValue = texture2D(in_depthSampler, fragTexCoord);
#endif
  // Depth test
  if (gl_FragCoord.z >= l_depthValue.x)
  {
    discard;
  }

  // color buffer or max scalar buffer have a reduced size.
  fragTexCoord =
    (gl_FragCoord.xy - in_windowLowerLeftCorner) * in_inverseOriginalWindowSize;

  // Compute max number of iterations it will take before we hit
  // the termination point

  // Abscissa of the point on the depth buffer along the ray.
  // point in texture coordinates
  vec4 terminatePoint =
    WindowToNDC(gl_FragCoord.x, gl_FragCoord.y, l_depthValue.x);

  // From normalized device coordinates to eye coordinates.
  // in_projectionMatrix is inversed because of way VT
  // From eye coordinates to texture coordinates
  terminatePoint = ip_inverseTextureDataAdjusted * in_inverseVolumeMatrix[0] *
    in_inverseModelViewMatrix * in_inverseProjectionMatrix * terminatePoint;
  terminatePoint /= terminatePoint.w;

  g_terminatePointMax =
    length(terminatePoint.xyz - g_dataPos.xyz) / length(g_dirStep);
  g_currentT = 0.0;

  //VTK::RenderToImage::Init

  //VTK::DepthPass::Init
}

/**
 * March along the ray direction sampling the volume texture.  This function
 * takes a start and end point as arguments but it is up to the specific render
 * pass implementation to use these values (e.g. vtkDualDepthPeelingPass). The
 * mapper does not use these values by default, instead it uses the number of
 * steps defined by g_terminatePointMax.
 */
vec4 castRay(const float zStart, const float zEnd)
{
  //VTK::DepthPeeling::Ray::Init

  //VTK::DepthPeeling::Ray::PathCheck

  /// For all samples along the ray
  while (!g_exit)
  {

    g_skip = false;
    if (!g_skip && g_srcColor.a > 0.0 && l_updateDepth)
    {
      l_opaqueFragPos = g_dataPos;
      l_updateDepth = false;
    }

    //VTK::PreComputeGradients::Impl

    if (!g_skip)
    {
      vec4 scalar = texture3D(in_volume[0], g_dataPos);
      scalar.r = scalar.r * in_volume_scale[0].r + in_volume_bias[0].r;
      scalar = vec4(scalar.r, scalar.r, scalar.r, scalar.r);
      g_scalar = scalar;
      g_srcColor = vec4(0.0);
      g_srcColor.a = computeOpacity(scalar);
    }

    //VTK::RenderToImage::Impl

    //VTK::DepthPass::Impl

    /// Advance ray
    g_dataPos += g_dirStep;

    if (any(greaterThan(g_dataPos, in_texMax)) ||
      any(lessThan(g_dataPos, in_texMin)))
    {
      break;
    }

    // Early ray termination
    // if the currently composited colour alpha is already fully saturated
    // we terminated the loop or if we have hit an obstacle in the
    // direction of they ray (using depth buffer) we terminate as well.
    if ((g_fragColor.a > g_opacityThreshold) ||
      g_currentT >= g_terminatePointMax)
    {
      break;
    }
    ++g_currentT;
  }

  return g_fragColor;
}

/**
 * Finalize specific modes and set output data.
 */
void finalizeRayCast()
{

  //VTK::Picking::Exit

  //VTK::RenderToImage::Exit
  if (l_opaqueFragPos == vec3(0.0))
  {
    gl_FragData[0] = vec4(0.0);
  }
  else
  {
    gl_FragData[0] =
      texture2D(in_colorTransferFunc_0[0], vec2(l_opaqueFragPos.z, 0.0)).xyzw;
  }

  //VTK::DepthPass::Exit
}

//////////////////////////////////////////////////////////////////////////////
///
/// Main
///
//////////////////////////////////////////////////////////////////////////////
void main()
{
  //VTK::CallWorker::Impl
}
