/// LSU EE 4702-1 (Fall 2021), GPU Programming
//
 /// Demonstration of Geometry Shader

 /// See demo-10-shader.cc for details.


// Specify version of OpenGL Shading Language.
//
#version 460

#extension GL_GOOGLE_include_directive : enable
#include <light.h>
#include <transform.h>
#include "shader-common-generic-lighting.h"

layout (std140, binding = BIND_MATERIAL ) uniform Uni_Material
{
  vec4 color_front, color_back;
};

 /// Declare Uniform Variables
//
layout ( binding = BIND_BULGE_INFO ) uniform Buldge_Info
{
  float bulge_loc;
  float bulge_dist_thresh;
  float wire_radius;
};

 /// Declare (Bind) Buffer Objects
//
layout ( binding = BIND_HELIX_COORDS ) buffer Helix_Coord
{ vec4  helix_coord[];  };

layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;


// Declare variables for communication between vertex shader
// and fragment shader.
//
#ifdef _VERTEX_SHADER_


// Helix Index: An integer giving position along helix.
// Used to index the helix coordinate array.
//
layout ( location = LOC_IN_INT1 ) in int helix_index;


layout (location = LOC_IN_NORMAL, component = 0) in vec3 in_normal_o;
layout (location = LOC_IN_TCOOR) in vec2 texcoord;
#define gl_MultiTexCoord0 texcoord

layout (location = 0) out Data
{
  int hidx;
  vec3 normal_o;
  vec2 tex_coord;
};

#endif

#ifdef _GEOMETRY_SHADER_

// Indicate type of input primitive expected by geometry shader.
//
layout ( triangles ) in;


const int slices = 7;

layout ( triangle_strip, max_vertices = ( slices + 1 ) * 2 ) out;

layout (location = 0) in Data
{
  int hidx;
  vec3 normal_o;
  vec2 tex_coord;
} In[];

layout (location = 0) out Data_GF
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coord;
  flat vec4 color;
};

#endif

#ifdef _FRAGMENT_SHADER_

layout (location = 0) in Data_GF
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coord;
  flat vec4 color;
};

layout (location = 0) out vec4 frag_color;
#define gl_FragColor frag_color

#endif

///
/// Shader Code
///

#ifdef _VERTEX_SHADER_
void
vs_main_helix()
{
  // Here, the vertex shader does nothing except pass variables
  // to the geometry shader.

  normal_o = in_normal_o;
  hidx = helix_index;
  tex_coord = gl_MultiTexCoord0.xy;
}
#endif

#ifdef _GEOMETRY_SHADER_

void
gs_main_helix()
{
  // If "bulge" is far away, just emit one triangle.
  // If bulge is near, emit multiple triangles so that bulge is
  // smooth.

  float bulge_dist_0 = abs( bulge_loc - In[0].hidx );

  const bool type_a = bool(gl_PrimitiveIDIn & 1);

  const vec4 base_shade = type_a ? vec4(1,1,1,1) : vec4(0.625,1,1,1);

  if ( bulge_dist_0 > bulge_dist_thresh )
    {
      /// Bulge is far away, just emit one triangle.
      //
      for ( int i=0; i<3; i++ )
        {
          // Lighten the color to distinguish this area of helix from
          // the part with the bulge.
          //
          color = vec4(0.8,0.8,0.8,1) * base_shade;

          // Compute the vertex coordinate.
          //
          int hidx = In[i].hidx;
          vec4 c = helix_coord[hidx];  // Coordinate of core of helix.

          // Vertex normal in object space.
          vec3 norm_o = wire_radius * In[i].normal_o;

          // Vertex coordinate in object space.
          vec4 pos_o = c + vec4(norm_o,0);

          gl_Position = gl_ModelViewProjectionMatrix * pos_o;
          normal_e = gl_NormalMatrix * In[i].normal_o;
          vertex_e = gl_ModelViewMatrix * pos_o;
          tex_coord = In[i].tex_coord;

          // Indicate that vertex is finished.
          EmitVertex();
        }

      // Indicate that primitive is finished.
      EndPrimitive();
      return;
    }

  // Sort indices so that gs_vidx_10 and gs_vidx_11 are at same
  // position along helix (though at different positions on surface of
  // wire).

  int gs_vidx_00;
  int gs_vidx_10;
  int gs_vidx_11;

  if ( In[0].hidx == In[1].hidx )
    { gs_vidx_00 = 2;  gs_vidx_10 = 1;  gs_vidx_11 = 0; }
  else if ( In[0].hidx == In[2].hidx )
    { gs_vidx_00 = 1;  gs_vidx_10 = 0;  gs_vidx_11 = 2; }
  else
    { gs_vidx_00 = 0;  gs_vidx_10 = 2;  gs_vidx_11 = 1; }

  int hidx0 = In[gs_vidx_00].hidx;
  int hidx1 = In[gs_vidx_10].hidx;
  vec3 c0 = helix_coord[hidx0].xyz;
  vec3 c1 = helix_coord[hidx1].xyz;
  vec3 delta_c = c0 - c1;
  vec3 n0 = In[gs_vidx_00].normal_o;
  vec3 n10 = In[gs_vidx_10].normal_o;
  vec3 n11 = In[gs_vidx_11].normal_o;

  // 

  // Number of times to split triangle.
  float bulge_rad = 1;
  float delta_f = 1.0f/slices;

  for ( int i=0; i<=slices; i++ )
    {
      float f = 1 - i * delta_f;
      float hidxx = mix( float(hidx1), float(hidx0), f );
      vec3 c =   mix( c1,  c0, f );
      vec3 nx0 = mix( n10, n0, f );
      vec3 nx1 = mix( n11, n0, f );

      float bulge_dist = abs( bulge_loc - hidxx );

      // Make surface of bulge curved.
      //
      float mult = 1.25f
        - 0.25f *
        cos( 3.1415f * max(0.0f, ( bulge_rad - bulge_dist ) / bulge_rad ) );
      float rx = wire_radius * mult;

      vec4 pos0 = vec4( c + rx * nx0, 1);
      vec4 pos1 = vec4( c + rx * nx1, 1);

      color = base_shade;
      gl_Position = gl_ModelViewProjectionMatrix * pos0;
      vertex_e = gl_ModelViewMatrix * pos0;
      normal_e = gl_NormalMatrix * nx0; // Not quite correct.
      tex_coord = vec2(-1,0);  // Save this for a homework assignment.
      EmitVertex();

      color = vec4(1,0.8,1,1) * base_shade;
      gl_Position = gl_ModelViewProjectionMatrix * pos1;
      vertex_e = gl_ModelViewMatrix * pos1;
      normal_e = gl_NormalMatrix * nx1;  // Not quite correct.
      tex_coord = vec2(-1,0);  // Save this for a homework assignment.
      EmitVertex();
    }
  EndPrimitive();
}

#endif

#ifdef _FRAGMENT_SHADER_
void
fs_main_phong()
{
  // Perform lighting, fetch and blend texture, then emit fragment.
  //
  // Note that in the fixed-function pipeline lighting would be performed
  // in the vertex shader.

  // Get filtered texel.
  //
  vec4 texel =
    tex_coord.x == -1f ? vec4(1,1,1,1) : texture(tex_unit_0,tex_coord);

  vec4 material_color = gl_FrontFacing ? color_front : color_back;
  vec4 shaded_color = material_color * color;

  // Compute lighted color of fragment.
  //
  vec4 lighted_color = generic_lighting( vertex_e, shaded_color, normal_e);

  // Multiply filtered texel color with lighted color of fragment.
  //
  gl_FragColor = texel * lighted_color;
}
#endif