/// LSU EE 4702-1 (Fall 2023), GPU Programming
//
 /// Homework 5 -- SOLUTION
 //
 //  Modified this file and hw05-sol.cc.
 //  Assignment: https://www.ece.lsu.edu/gpup/2023/hw05.pdf
 //  Solution Discussion: https://www.ece.lsu.edu/gpup/2023/hw05_sol.pdf


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

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

layout ( binding = BIND_UNI_COMMON ) uniform Common_Uniform
{
  Shdr_Uni_Common com;
};

bool opt_tryout1 = bool(com.tryout.x);
bool opt_tryout2 = bool(com.tryout.y);
float opt_tryoutf = com.tryoutf.x;

const int TX_None = 0, TX_Square = 1, TX_Circle = 2;

layout ( binding = BIND_HW05 ) uniform HW_05
{
  vec4 color;
  int opt_texture;
  float ring_hole_frac, one_over_delta_r, dy_d_theta;
};

#ifdef BIND_LINKS_POS1
layout ( binding = BIND_LINKS_POS1 ) buffer sr { vec4 pos1[]; };
layout ( binding = BIND_LINKS_POS2 ) buffer sr2 { vec4 pos2[]; };
layout ( binding = BIND_LINKS_V1 ) buffer spr { vec4 v1[]; };
layout ( binding = BIND_LINKS_V2 ) buffer sc { vec4 v2[]; };
#endif


#ifdef _VERTEX_SHADER_

 /// SOLUTION
//
//   Changed declarations of vertex shader inputs.
//
//   Unsolved vertex shader inputs: 
//       int   For link_idx.
//       vec2  For texture coordinate, type pTCoor on CPU.
//
//   Solution vertex shader inputs:
//       int   For link_idx.
//       vec4  For two texture coordinates. Packed into a pCoor.

layout ( location = LOC_IN_INT1 ) in int in_link_idx;

layout ( location = LOC_IN_POS ) in vec4 in_pack;

// Interface block for vertex shader output / geometry shader input.
//
layout ( location = 0 ) out Data_to_GS
{
  /// SOLUTION
  //
  vec4 p1, p2, v1, v2;
  vec2 tc1, tc2;
} Out;

void
vs_hw05()
{
  /// SOLUTION
  //
  // Unpack in_pack into a 2-element array of texture coordinates.
  Out.tc1 = in_pack.xy;
  Out.tc2 = in_pack.zw;

  // Retrieve positions and vectors in vertex shader.
  //
  Out.p1 = pos1[in_link_idx];
  Out.p2 = pos2[in_link_idx];
  Out.v1 = v1[in_link_idx];
  Out.v2 = v2[in_link_idx];
  //
  // It would also be correct to just send in_link_idx to the geometry
  // shader and have the geometry shader retrieve the positions and
  // vectors.
}


#endif

#ifdef _GEOMETRY_SHADER_

layout ( location = 0 ) in Data_to_GS
{
  /// SOLUTION
  //
  vec4 p1, p2, v1, v2;
  vec2 tc1, tc2;
} In[];

layout ( location = 0 ) out Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
} Out;

// Type of primitive at geometry shader input.
//

 /// SOLUTION
//
// Change layout from triangles to lines.
//
layout ( lines ) in;

// Type of primitives emitted geometry shader output.
//

 /// SOLUTION
// 
//   Increase max vertices from 3 to 2 + 2*opt_segments.
//
layout ( triangle_strip, max_vertices = 2 * ( opt_segments + 1 ) ) out;

vec4
bez(vec4 p1, vec4 p2, vec4 v1, vec4 v2, float t)
{
  float t2 = t * t;
  float t3 = t2 * t;
  return
    vec4( ( 2*t3 - 3*t2 + 1 ) * p1.xyz
          + ( -2*t3 + 3*t2 ) * p2.xyz
          + ( t3 - 2*t2 + t ) * v1.xyz
          - ( t3 - t2 ) * v2.xyz,
          1 );
}

vec3
bez_dt(vec4 p1, vec4 p2, vec4 v1, vec4 v2, float t)
{
  float t2 = t * t;
  float t3 = t2 * t;
  return
    ( 6*t2 - 6*t ) * p1.xyz
    + ( -6*t2 + 6*t ) * p2.xyz
    + ( 3*t2 - 4*t + 1 ) * v1.xyz
    - ( 3*t2 - 2*t ) * v2.xyz;
}

void
gs_hw05()
{
  float delta_t = 1.0 / opt_segments;

  /// SOLUTION
  //
  //  The input to the geometry shader is a line to GLSL with one end
  //  point described by In[0] and the other endpoint described by
  //  In[1]. But to us, In[0] is spoke 0 and In[1] is spoke 1. (These
  //  can be any pair of adjacent spokes.)
  //
  //  In the loop below the geometry shader emits a triangle strip
  //  to form a surface between the two spokes. The loop below computes
  //  the same values as the s loop in hw05.cc:World::render_hw04.
  //
  //  Note that In[0].p1 in the code below corresponds to pos11 in
  //  render_hw04, In[1].p1 corresponds to pos21 in render_hw04, etc.

  for ( int s=0; s<=opt_segments; s++ )
    {
      float t = s * delta_t;

      // Compute interpolated coordinate along spokes 0 and 1 at position t.
      //
      vec4 pos0t_o = bez( In[0].p1, In[0].p2, In[0].v1, In[0].v2, t );
      //                  pos11     pos12     v11       v12
      //
      vec4 pos1t_o = bez( In[1].p1, In[1].p2, In[1].v1, In[1].v2, t );
      //                  pos21     pos22     v21       v22
      //
      // Note: Curve from In[0].p1 to In[0].p2 is a 3rd degree polynomial,
      // bez finds a point on this curve.

      //  Compute direction along each spoke. Needed to find normals.
      //
      vec3 tan0 = bez_dt( In[0].p1, In[0].p2, In[0].v1, In[0].v2, t );
      vec3 tan1 = bez_dt( In[1].p1, In[1].p2, In[1].v1, In[1].v2, t );

      //  Compute a vector from point on spoke 0 to spoke 1. For normals.
      //
      vec3 vx = pos1t_o.xyz - pos0t_o.xyz;

      //
      ///  Emit the vertex along spoke 0  ( In[0] )
      //

      //  Emit clip-space coordinate and eye-space coordinate.
      //
      gl_Position = ut.clip_from_object * pos0t_o;
      Out.vertex_e = ut.eye_from_object * pos0t_o;

      //  Compute and emit interpolated texture coordinate.
      //
      Out.tex_coor = mix( In[0].tc1, In[0].tc2, t );
      //
      //  Simple linear interpolation is sufficient for textures. 

      //  Compute and emit surface normal.
      //
      vec3 normal_1_o = cross( tan0, vx );
      Out.normal_e = normalize( mat3(ut.eye_from_object) * normal_1_o );

      EmitVertex();

      //
      /// Emit the vertex along spoke 1  ( In[1] ).
      //
      gl_Position = ut.clip_from_object * pos1t_o;
      Out.vertex_e = ut.eye_from_object * pos1t_o;
      Out.tex_coor = mix( In[1].tc1, In[1].tc2, t );
      vec3 normal_2_o = cross( tan1, vx );
      Out.normal_e = normalize( mat3(ut.eye_from_object) * normal_2_o );

      EmitVertex();
    }

  EndPrimitive();
}



#endif


#ifdef _FRAGMENT_SHADER_

#ifdef BIND_TEXUNIT
layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;
#endif

layout ( location = 0 ) in Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
} In;

layout ( location = 0 ) out vec4 out_frag_color;


void
fs_main()
{
  vec4 lit_color = generic_lighting(In.vertex_e, color, In.normal_e);

  if ( opt_texture == TX_None )
    {
      out_frag_color = lit_color;
    }
  else if ( opt_texture == TX_Circle )
    {
      float dist = length(In.tex_coor);
      float angle = atan(In.tex_coor.y,In.tex_coor.x);
      vec2 tc =
        vec2( ( dist - ring_hole_frac ) * one_over_delta_r,
              angle * dy_d_theta );
      out_frag_color = lit_color * texture(tex_unit_0,tc);
    }
  else
    {
      out_frag_color = lit_color * texture(tex_unit_0,In.tex_coor);
    }
}


#endif