/// LSU EE 4702-1 (Fall 2016), GPU Programming
//
 /// Homework 6 -- SOLUTION
 //
 //  See http://www.ece.lsu.edu/koppel/gpup/2016/hw06.pdf


// Specify version of OpenGL Shading Language.
//
#version 450 compatibility

layout ( location = 1 ) uniform vec4 color_front;
layout ( location = 2 ) uniform vec4 color_back;

// A bit vector indicating which lights are on.
layout ( location = 6 ) uniform int lighting_options;

// Use this variable to debug your code. Press 'y' to toggle tryout.x
// and 'Y' to toggle debug_bool.y (between true and false).
//
layout ( location = 3 ) uniform bvec2 tryout;


layout ( location = 4 ) uniform vec2 sides_rad;
layout ( location = 5 ) uniform float delta_tee;

layout ( binding = 1 ) buffer sr { vec4 pos1[]; };
layout ( binding = 2 ) buffer sr2 { vec4 pos2[]; };
layout ( binding = 3 ) buffer spr { vec4 v1[]; };
layout ( binding = 4 ) buffer sc { vec4 v2[]; };
layout ( binding = 5 ) buffer sc5 { vec4 b1_ydir[]; };
layout ( binding = 6 ) buffer sc6 { vec4 b2_ydir[]; };
layout ( binding = 7 ) buffer sc7 { vec4 tex_rad[]; };



#ifdef _VERTEX_SHADER_

// Interface block for vertex shader output / geometry shader input.
//
out Data_to_GS
{
  int iid; // Instance ID
  vec4 gl_Position;
  vec4 vertex_e;

  /// SOLUTION -- Problem 2
  //
  float t;
  vec4 ctr;
  vec3 norm, binorm;

  // Any changes here must also be made to the fragment shader input.
};

void
vs_main_1()
{
  iid = gl_InstanceID;

  vec3 vertex_1 = pos1[iid].xyz;
  vec3 vertex_2 = pos2[iid].xyz;

  /// SOLUTION -- Problem 1
  //
  //  Use the vertex number to select either the first or second vertex.
  //
  vec4 vertex_o = vec4( gl_VertexID == 0 ? vertex_1 : vertex_2, 1 );

  gl_Position = gl_ModelViewProjectionMatrix * vertex_o;
  vertex_e = gl_ModelViewMatrix * vertex_o;
}

void
vs_main_2()
{
  /// SOLUTION -- Problem 2

  // In the vertex shader compute the coordinate of the axis of the
  // cylinder (ctr) and the vectors orthogonal to the axis. The
  // geometry shader will use a pair of these to draw the cylinder
  // surface.

  iid = gl_InstanceID;

  // Compute the values of t, t^2 and t^3 needed to compute point on
  // curve (core of link).
  //
  const int i = gl_VertexID;
  t = i * delta_tee;
  const float t2 = t * t;
  const float t3 = t2 * t;

  // Cubic Hermite interpolation.
  // Compute point along core of link in object space.
  //
  ctr.xyz =
    ( 2*t3 - 3*t2 + 1 ) * pos1[iid].xyz + (-2*t3 + 3*t2 ) * pos2[iid].xyz
    + ( t3 - 2*t2 + t ) * v1[iid].xyz - ( t3 - t2 ) * v2[iid].xyz;
  ctr.w = 1;

  // Compute direction of link at this point.
  //
  vec3 tan_raw =
    ( 6*t2 - 6*t ) * pos1[iid].xyz + ( -6*t2 + 6*t ) * pos2[iid].xyz
    + ( 3*t2 - 4*t + 1 ) * v1[iid].xyz - ( 3*t2 - 2*t ) * v2[iid].xyz;
  vec3 tan = normalize(tan_raw);

  // Compute local x and y axes for drawing a cylinder.
  //
  vec3 ydir = mix(b1_ydir[iid].xyz, b2_ydir[iid].xyz, t);
  vec3 norm_raw = cross(tan,ydir);
  norm = normalize(norm_raw);
  binorm = cross(tan,norm);
}

#endif


#ifdef _GEOMETRY_SHADER_

in Data_to_GS
{
  int iid; // Instance ID
  vec4 gl_Position;
  vec4 vertex_e;

  /// SOLUTION -- Problem 2
  float t;
  vec4 ctr;
  vec3 norm, binorm;
} In[];

out Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
};

// Type of primitive at geometry shader input.
//
layout ( lines ) in;

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

 /// SOLUTION -- Problem 2
//   Increase max_vertices to 42.
layout ( triangle_strip, max_vertices = 42 ) out;


void
gs_main_1()
{
  vec3 v01 = In[1].vertex_e.xyz - In[0].vertex_e.xyz;
  vec3 ax = normalize(v01.x == 0 ? vec3(0,v01.z,-v01.y) : vec3(v01.y,-v01.x,0));

  /// SOLUTION -- Problem 1
  //
  //  Since we are only rendering using two vertices v01 will be longer,
  //  and so for the links to be the same width as when we used more
  //  segments multiply by delta_tee.
  //
  float l = length(v01) * delta_tee;

  for ( int i=0; i<2; i++ )
    {
      vertex_e = In[i].vertex_e;
      gl_Position = In[i].gl_Position;
      EmitVertex();
      vertex_e = In[i].vertex_e + vec4( ax * l * 0.1, 1 );
      gl_Position = gl_ProjectionMatrix * vertex_e;
      EmitVertex();
    }
  EndPrimitive();
}

void
gs_main_2()
{
  /// SOLUTION -- Problem 2

  // Emit primitives for a cylinder. One end of the cylinder is
  // defined by data in In[0], the other end by data in In[1].

  const float M_PI = 3.1415926535;
  const vec2 tex_scale = tex_rad[In[1].iid].xy;
  const float rad = tex_rad[In[1].iid].z;
  const float sides = sides_rad.x;

  for ( int j=0; j<=sides; j++ )
    {
      const float theta = j * ( 2 * M_PI / sides );

      vec3 vect0 = cos(theta) * In[0].norm + sin(theta) * In[0].binorm;
      vec3 vect1 = cos(theta) * In[1].norm + sin(theta) * In[1].binorm;
      vec4 pt0 = In[0].ctr + vec4(rad * vect0,0);
      vec4 pt1 = In[1].ctr + vec4(rad * vect1,0);

      normal_e = gl_NormalMatrix * vect1;
      vertex_e = gl_ModelViewMatrix * pt1;
      gl_TexCoord[0] =
        vec2( In[1].t * tex_scale.x, 0.5 + theta * tex_scale.y );
      gl_Position = gl_ModelViewProjectionMatrix * pt1;
      EmitVertex();

      normal_e = gl_NormalMatrix * vect0;
      vertex_e = gl_ModelViewMatrix * pt0;
      gl_TexCoord[0] =
        vec2( In[0].t * tex_scale.x, 0.5 + theta * tex_scale.y );
      gl_Position = gl_ModelViewProjectionMatrix * pt0;
      EmitVertex();
    }
  EndPrimitive();
}


#endif


#ifdef _FRAGMENT_SHADER_

uniform sampler2D tex_unit_0;

in Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
};

vec4 generic_lighting(vec4 vertex_e, vec4 color, vec3 normal_e);


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

  // Get filtered texel, unless the fragment belongs to an edge primitive.
  //
  vec4 texel = texture(tex_unit_0,gl_TexCoord[0]);

  vec4 color2 = gl_FrontFacing ? color_front : color_back;

  // Multiply filtered texel color with lighted color of fragment.
  //
  gl_FragColor = texel * generic_lighting(vertex_e, color2, normal_e);

  // Copy fragment depth unmodified.
  //
  gl_FragDepth = gl_FragCoord.z;
}


///
/// Routine used by Either Vertex or Fragment Shader
///


vec4
generic_lighting(vec4 vertex_e, vec4 color, vec3 normal_er)
{
  // Return lighted color of vertex_e.
  //

  vec3 nspc_color = color.rgb * gl_LightModel.ambient.rgb;
  vec3 spec_color = vec3(0);
  vec3 normal_e = normalize(normal_er);

  for ( int i=0; i<2; i++ )
    {
      if ( ( lighting_options & ( 1 << i ) ) == 0 ) continue;
      vec4 light_pos = gl_LightSource[i].position;
      vec3 v_vtx_light = light_pos.xyz - vertex_e.xyz;
      float dist = length(v_vtx_light);
      float dist_vl_inv = 1.0 / dist;
      vec3 v_vtx_l_n = v_vtx_light * dist_vl_inv;

      float d_n_vl = dot(normal_e, v_vtx_l_n);
      float phase_light = max(0,gl_FrontFacing ? d_n_vl : -d_n_vl );

      vec3 ambient_light = gl_LightSource[i].ambient.rgb;
      vec3 diffuse_light = gl_LightSource[i].diffuse.rgb;
      float distsq = dist * dist;
      float atten_inv =
        gl_LightSource[i].constantAttenuation +
        gl_LightSource[i].linearAttenuation * dist +
        gl_LightSource[i].quadraticAttenuation * distsq;
      vec3 lighted_color =
        color.rgb
        * ( ambient_light + phase_light * diffuse_light ) / atten_inv;
      nspc_color += lighted_color;

      vec3 h = normalize( v_vtx_l_n - normalize(vertex_e.xyz) );

      spec_color +=
        pow(max(0.0,dot(normal_e,h)),16)
        * color.rgb
        * gl_LightSource[i].specular.rgb / atten_inv;
    }

  return vec4(nspc_color+spec_color,1);
}


#endif