/// LSU EE 4702-1 (Fall 2012), GPU Programming
//

 /// Homework 3 - Shader Code -- SOLUTION
 //
 // Assignment in: http://www.ece.lsu.edu/koppel/gpup/2012/hw03.pdf

 /// Your Name:

// Specify version of OpenGL Shading Language.
//
#version 430 compatibility
#extension GL_EXT_geometry_shader4 : enable

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


layout ( location = 2 ) uniform float wire_radius;

 /// SOLUTION
// Declare a uniform variable for the door angle, theta.
//
layout ( location = 3 ) uniform float theta;          // Problem 3
layout ( location = 4 ) uniform vec2 texture_scale;   // Problem 1

layout ( binding = 1 ) buffer Helix_Coord  { vec4  helix_coord[];  };
uniform sampler2D tex_unit_0;


///
/// Shader Input and Output Variables
///


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

// Helix Index
//
// A two-element vector.
// The first element is the position along the helix, it ranges from
//   0 to segments_per_helix-1.
// The second element is the position along a circle surrounding the
//   helix (the circle is on the surface of the wire). This element
//   ranges from 0 to seg_per_wire_revolution-1.
//
layout ( location = 1 ) in ivec2 helix_index;

out Data
{
  vec3 normal_e;  // "e" is for eye space.
  vec4 vertex_e;
  ivec2 hidx;
};

out vec2 gl_TexCoord[];  // Redeclaration, to limit size to two elements.

#endif

#ifdef _GEOMETRY_SHADER_

in Data
{
  vec3 normal_e;
  vec4 vertex_e;
  ivec2 hidx;
} In[3];

out Data
{
  vec3 normal_e;
  vec4 vertex_e;
  flat bool no_texture;  /// SOLUTION - Problem 2
};

#endif

#ifdef _FRAGMENT_SHADER_
in Data
{
  vec3 normal_e;
  vec4 vertex_e;
  flat bool no_texture;    /// SOLUTION - Problem 2
};

in vec2 gl_TexCoord[];

#endif


///
/// Shaders
///

#ifdef _VERTEX_SHADER_

void
vs_main_helix()
{
  // Perform basic vertex shading operations, but also:
  //   - Compute wire surface coordinates.
  //
  // Note that this shader only works for the helix.

  // Pass helix index to fragment shader output.
  //
  hidx = helix_index;

  // Compute wire surface location by adding normal to helix coordinate.
  //
  vec4 vertex_o;
  vertex_o.xyz = helix_coord[hidx.x].xyz + wire_radius * gl_Normal;
  vertex_o.w = 1;

  // Transform vertex coordinate to clip space.
  //
  gl_Position = gl_ModelViewProjectionMatrix * vertex_o;

  // Compute eye-space coordinates for vertex and normal.
  //
  vertex_e = gl_ModelViewMatrix * vertex_o;
  normal_e = normalize(gl_NormalMatrix * gl_Normal);

  // Call our lighting routine to compute the lighted color of this
  // vertex.
  //
  gl_FrontColor = gl_Color;

  // Copy texture coordinate to output (no need to modify it).
  // Only copy x and y components since it's a 2D texture.
  //

  /// SOLUTION -- Problem 1
  //  Use scale factor pre-computed on CPU to transform helix indices
  //  to texture coordinates.  Note that all variables below are
  //  two-element vectors and that the multiply operator is element-wise.
  //
  gl_TexCoord[0] = texture_scale * helix_index;

}
#endif

#ifdef _GEOMETRY_SHADER_

void
gs_main_helix()
{
  const bool type_a = In[0].hidx.x < In[2].hidx.x;

  for ( int i=0; i<3; i++ )
    {
      // Pass the other values through unmodified.
      //
      gl_FrontColor = gl_FrontColorIn[i];
      gl_Position = gl_PositionIn[i];
      gl_TexCoord[0] = gl_TexCoordIn[i][0];
      no_texture = type_a;                     /// SOLUTION - Problem 2
      normal_e = In[i].normal_e;
      vertex_e = In[i].vertex_e;

      EmitVertex();
    }
  EndPrimitive();
}

void
gs_main_helix2()
{
  /// SOLUTION - Problem 3

  // Select which triangles to split into "trap doors".
  //
  const bool trap_door = In[0].hidx.x >= In[2].hidx.x;

  // Set door angle (0 is fully closed, pi/2 is straight out, etc.
  //
  float th = trap_door ? theta : 0;

  // Find center of triangle (in eye-space coordinates).
  //
  vec3 pctr = (In[0].vertex_e + In[1].vertex_e
               + In[2].vertex_e ).xyz * 0.333333f;

  // Find texture coordinate of center of triangle.
  //
  vec4 tctr = ( gl_TexCoordIn[0][0] + gl_TexCoordIn[1][0]
                + gl_TexCoordIn[2][0] ) * 0.333333f;

  // Generate the three "trap door" triangles.
  //
  for ( int tri=0; tri<3; tri++ )
    {
      // Note that calculations performed in eye-space coordinates. If
      // the calculations were performed in clip-space coordinates
      // (for example, using gl_PositionIn) results would be distorted
      // due to the perspective projection.

      // Select two "fixed" vertices.
      //
      vec3 p0 = In[(0+tri)%3].vertex_e.xyz;
      vec3 p1 = In[(1+tri)%3].vertex_e.xyz;

      // Select the "swinging" vertex.
      //
      int p2_idx = (2+tri)%3;
      vec3 p2 = pctr;

      // Compute the two axes defining the plane that point new_p2
      // (below) will move along.

      // Start by finding the point between p0 and p1 that's closest to p2.
      //
      vec3 v01_norm = normalize( p0 - p1 );
      vec3 v02 = p2 - p0;
      vec3 p01_nearest = p0 + v01_norm * dot(v01_norm,v02);

      // Axis 2 is from p2 to the p0-p1 line.
      //
      vec3 axis2 = p2 - p01_nearest;
      float axis2_len = length(axis2);
      vec3 axis2_norm = axis2/axis2_len;

      // Axis 1 is a vector orthogonal to both axis 2 and the p0-p1 line.
      //
      vec3 axis1_norm = cross(v01_norm,axis2_norm);
      vec3 axis1 = axis2_len * axis1_norm;

      // Use a simple circle formula to find new_p2.
      //
      vec4 new_p2 = vec4(p01_nearest + cos(th) * axis2 + sin(th) * axis1,1);

      // Finish by computing the clip-space coordinate of new_p2_c and
      // an eye-space normal for the new triangle.
      //
      vec4 new_p2_c = gl_ProjectionMatrix * new_p2;
      vec3 new_norm = normalize(cross(v01_norm,new_p2.xyz-p0));
      normal_e = -new_norm;

      // Emit the three vertices of this triangle.
      //
      for ( int i=0; i<3; i++ )
        {
          gl_FrontColor = gl_FrontColorIn[i];
          gl_Position = i == p2_idx ? new_p2_c : gl_PositionIn[i];
          gl_TexCoord[0] = i == p2_idx ? tctr : gl_TexCoordIn[i][0];
          vertex_e = i == p2_idx ? new_p2 : In[i].vertex_e;
          no_texture = false;
          EmitVertex();
        }
      EndPrimitive();
    }
}

#endif


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

  // If primitive facing user get texel, otherwise assign a reddish shade.
  //
  vec4 texel = gl_FrontFacing
    ? ( no_texture ? vec4(1,1,1,1) :    /// SOLUTION - Problem 2
        texture(tex_unit_0,gl_TexCoord[0].xy) )
    : vec4(0.6,0.1,0.1,1);

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

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


vec4
generic_lighting(vec4 vertex_e, vec4 color, vec3 normal_e)
{
  // Return lighted color of vertex_e.
  //
  vec4 light_pos = gl_LightSource[0].position;
  vec3 v_vtx_light = light_pos.xyz - vertex_e.xyz;
  float d_n_ve = -dot(normal_e,vertex_e.xyz);
  float d_n_vl = dot(normal_e, normalize(v_vtx_light).xyz);
  bool same_sign = ( d_n_ve > 0 ) == ( d_n_vl > 0 );
  float phase_light = same_sign ? abs(d_n_vl) : 0;

  vec3 ambient_light = gl_LightSource[0].ambient.rgb;
  vec3 diffuse_light = gl_LightSource[0].diffuse.rgb;
  float dist = length(v_vtx_light);
  float distsq = dist * dist;
  float atten_inv =
    gl_LightSource[0].constantAttenuation +
    gl_LightSource[0].linearAttenuation * dist +
    gl_LightSource[0].quadraticAttenuation * distsq;
  vec4 lighted_color;
  lighted_color.rgb =
    color.rgb * gl_LightModel.ambient.rgb
    + color.rgb * ( ambient_light + phase_light * diffuse_light ) / atten_inv;
  lighted_color.a = color.a;
  return lighted_color;
}