/// LSU EE 4702-1 (Fall 2023), 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"

 /// Declare Uniform Variables
//
layout ( binding = BIND_MATERIAL ) uniform Uni_Material
{
  vec4 color_front, color_back;
};

layout ( binding = BIND_BULGE_INFO ) uniform Buldge_Info
{
  float bulge_loc;
  float bulge_dist_thresh;
  float wire_radius;
  bool opt_texture;
};

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


// 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 in_helix_index;

layout (location = LOC_IN_NORMAL) in vec3 in_normal_o;
layout (location = LOC_IN_TCOOR) in vec2 in_tex_coord;

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

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

  Out.normal_o = in_normal_o;
  Out.hidx = in_helix_index;
  Out.tex_coord = in_tex_coord;
}
#endif


#ifdef _GEOMETRY_SHADER_

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

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;
} Out;

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

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.
          //
          Out.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 = ut.clip_from_object * pos_o;
          Out.normal_e = mat3(ut.eye_from_object) * In[i].normal_o;
          Out.vertex_e = ut.eye_from_object * pos_o;
          Out.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);

      Out.color = base_shade;
      gl_Position = ut.clip_from_object * pos0;
      Out.vertex_e = ut.eye_from_object * pos0;
      Out.normal_e = mat3(ut.eye_from_object) * nx0; // Not quite correct.
      Out.tex_coord = vec2(-1,0);  // Save this for a homework assignment.
      EmitVertex();

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

#endif


#ifdef _FRAGMENT_SHADER_

layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;

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

layout (location = 0) out vec4 out_frag_color;

void
fs_main_phong()
{
  // Perform lighting, fetch and blend texture, then emit fragment.
  //

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

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

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

  // Multiply filtered texel color with lighted color of fragment.
  //
  out_frag_color = opt_texture ? texel * lighted_color : lighted_color;
}
#endif