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

 /// Homework 5 --- SOLUTION
//
//   Modify this file AND hw05.cc.
//   Assignment: https://www.ece.lsu.edu/koppel/gpup/2022/hw05.pdf


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

#extension GL_GOOGLE_include_directive : enable

layout ( binding = BIND_MISC ) uniform Uni_Misc
{
  ivec4 uni_misc;
  vec4 opt_tryoutf4;

  /// SOLUTION -- Problems 1 and 2.

  // Problem 1, provide radius of hole.
  //
  float radius_inner;
  float p1, p2, p3; // Pad so that number of floats is a multiple 4.
  //
  // If the number of consecutive floats is not a multiple of 4 then
  // the values of mat4 and vec4 (and others) will not be read
  // correctly. This is because in OGSL variables of type vec4 and
  // mat4 must start at an offset that is a multiple of 16.
  // Placement of the padding variables is necessary on the CPU
  // code but optional on the GPU code.

  // Problem 2, provide rotation matrix.
  //
  mat4 rot;
};

bool opt_tryout1 = bool(uni_misc.x);
bool opt_tryout2 = bool(uni_misc.y);
float opt_tryoutf = opt_tryoutf4.x;

const int ncolors = 10;
layout ( binding = BIND_HW05 ) uniform I_can_forget_this_name_no_problem
{
  vec4 front[ncolors], back[ncolors];
} uc;


 /// Uniforms
//
layout ( binding = BIND_LIGHT_SIMPLE ) uniform Uni_Light
{
  vec4 position;
  vec4 color;
} uni_light;

layout ( binding = BIND_TRANSFORM ) uniform Uni_Transform
{
  // Transformation Matrices
  mat4 eye_from_object, clip_from_eye, clip_from_object;
  mat4 object_from_eye, eye_from_clip;
} ut;

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Inputs -- For all Problems
//
layout (location = LOC_IN_POS) in vec4 in_vertex_o;
layout (location = LOC_IN_NORMAL) in vec3 in_normal_o;
layout (location = LOC_IN_INT1) in int in_color_idx;
//
// The CPU must provide these inputs.

#endif


#ifdef HW05_PROB1
//////////////////////////////////////////////////////////////////////////////
//
 /// Homework 5 Problem 1
//
//   Put solution to Problem 1 here.
//   (The section for Problem 2 is further below.)


#ifdef _VERTEX_SHADER_

 /// Vertex Shader Output
//
layout (location = 0) out Data_VG
{
  vec4 vertex_c;
  vec3 vertex_e, normal_e;
  int color_idx;

  /// SOLUTION -- Problem 1
  //
  vec3 inner_e;  // Eye-space coordinates of point on circle forming hole.
  vec4 inner_c;  // Clip-space coordinates of point on circle forming hole.

  /// SOLUTION -- Problem 2
  //
  int vtx_idx;   // Vertex index. (Needed to retrieve rotation.)
};

 /// The Vertex Shader
void
vs_main_prob1()
{
  // Transform the vertex object-space coordinate to clip space.
  //
  vertex_c = ut.clip_from_object * in_vertex_o;

  // Compute eye-space coordinates for vertex and normal.
  //
  vertex_e = vec3( ut.eye_from_object * in_vertex_o );
  normal_e = normalize( mat3(ut.eye_from_object) * in_normal_o );
  color_idx = in_color_idx;

  /// SOLUTION -- Problem 1 (Until end of procedure).
  //

  /// Compute eye-space coordinate of gear center.
  //
  // Object-space coordinate of gear center.
  //
  vec4 ctr_o = vec4(0,0,in_vertex_o.z,1);
  //
  // This is the same as the local-space coordinate.
  //
  // Transform object space to eye space.
  //
  vec4 ctr_e = ut.eye_from_object * vec4(0,0,in_vertex_o.z,1);

  /// Compute coordinates of point on the hole corresponding to this vtx.
  //
  // Compute a unit vector from gear center to this vertex.
  //
  vec3 v = normalize( vertex_e - ctr_e.xyz );
  //
  // Use unit vector to compute coordinate of vertex on hole circle.
  //
  inner_e = ctr_e.xyz + v * radius_inner;
  inner_c = ut.clip_from_eye * vec4(inner_e,1);

  // Pass along vertex index. Will be used in the geometry shader.
  //
  vtx_idx = gl_VertexIndex;
}

#endif

#ifdef _GEOMETRY_SHADER_
layout (location = 0) in Data_VG
{
  vec4 vertex_c;
  vec3 vertex_e, normal_e;
  int color_idx;

  /// SOLUTION -- Problem 1
  //
  vec3 inner_e;  // Eye-space coordinate of point on circle forming hole.
  vec4 inner_c;  // Clip-space coordinate of point on circle forming hole.

  /// SOLUTION -- Problem 2
  //
  int vtx_idx;   // Vertex index. (Needed to retrieve rotation.)
} In[];

layout (location = 0) out Data_GF
{
  vec3 vertex_e;
  vec3 normal_e;
  flat int color_idx;
};

layout ( triangles ) in;

 /// SOLUTION -- Problem 1
//
//  Increase max vertices from 3 to 9.
//
layout ( triangle_strip, max_vertices = 9 ) out;

void
gs_main_prob1()
{
  /// Geometry Shader
  //

  for ( int i=0; i<3; i++ )
    {
      gl_Position = In[i].vertex_c;
      vertex_e = In[i].vertex_e;
      normal_e = In[i].normal_e;
      color_idx = In[i].color_idx;
      EmitVertex();
    }
  EndPrimitive();

  /// SOLUTION -- Problem 1 (Until end of this procedure.)

  /// Determine which two vertices to use to draw the sides.
  //
  //  There are three input vertices, two will be on one side of the
  //  triangle strip (or gear), one will be on the other. If the three
  //  input vertices, In[0], In[1], and In[2], were in the same order
  //  as the vertices sent to the GPU, then vertices In[0] and In[2]
  //  would be on the same side of the strip. But for odd-numbered
  //  triangles In[1] is the vertex after In[2]. This is so that
  //  triangle In[0], In[1], In[2] is always facing up.

  int idx_skip = In[1].vtx_idx - In[0].vtx_idx;

  // Compute eye-space normal of gear sides.
  //
  vec3 normal_side_o = vec3(0,0,1);
  //
  vec3 normal_side_e = mat3(ut.eye_from_object) * normal_side_o;

  // Emit coordinates of triangles forming sides.
  //
  for ( int i=0; i<3; i++ )
    if ( i != idx_skip )
      {
        gl_Position = In[i].vertex_c;
        vertex_e = In[i].vertex_e;
        color_idx = 6;
        normal_e = normal_side_e;
        EmitVertex();
      }

  for ( int i=0; i<3; i++ )
    if ( i != idx_skip )
      {
        gl_Position = In[i].inner_c;
        vertex_e = In[i].inner_e.xyz;
        color_idx = 6;
        normal_e = normal_side_e;
        EmitVertex();
      }

  EndPrimitive();
}

#endif

#endif

#ifdef HW05_PROB2
//////////////////////////////////////////////////////////////////////////////
//
 /// Homework 5 Problem 2
//
//   Put solution to Problem 2 here.
//   (The section for Problem 1 is above.)


layout ( binding = BIND_XF ) buffer Gears { mat4 gears_xform[]; };

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Output
//
layout (location = 0) out Data_VG
{
  vec4 vertex_c;
  vec3 vertex_e, normal_e;
  int color_idx;
};

void
vs_main_prob2()
{
  int inst = gl_InstanceIndex;

  /// SOLUTION -- Problem 2
  //

  // Compute transformation from local space to global space.
  //
  mat4 g_from_l = gears_xform[inst] * rot;
  //
  // Array gears_xform gives the position of the gear and rot gives
  // the rotation. Matrix rot is updated every frame, but gears_xform
  // is only updated when the number of gears change.

  // Transform input vertex from local space to global space.
  //
  vec4 vertex_g = g_from_l * in_vertex_o;

  // Transform the vertex object space to clip space.
  //
  vertex_c = ut.clip_from_object * vertex_g;

  // Compute eye-space coordinates for vertex and normal.
  //
  vertex_e = vec3( ut.eye_from_object * vertex_g );
  normal_e =
    normalize( mat3(ut.eye_from_object) * mat3(g_from_l) * in_normal_o );
  color_idx = in_color_idx;
}

#endif

#ifdef _GEOMETRY_SHADER_
layout (location = 0) in Data_VG
{
  vec4 vertex_c;
  vec3 vertex_e, normal_e;
  int color_idx;
} In[];

layout (location = 0) out Data_GF
{
  vec3 vertex_e;
  vec3 normal_e;
  flat int color_idx;
};

layout ( triangles ) in;
layout ( triangle_strip, max_vertices = 3 ) out;

void
gs_main_prob2()
{
  /// Geometry Shader
  //

  for ( int i=0; i<3; i++ )
    {
      gl_Position = In[i].vertex_c;
      vertex_e = In[i].vertex_e;
      normal_e = In[i].normal_e;
      color_idx = In[i].color_idx;
      EmitVertex();
    }
  EndPrimitive();
}

#endif

#endif



//////////////////////////////////////////////////////////////////////////////
//
 /// Homework 5: No need to modify the fragment shader.
//   The same fragment shader should work with all problems.

#ifdef _FRAGMENT_SHADER_

layout (location = 0) in Data_GF
{
  vec3 vertex_e;
  vec3 normal_e;
  flat int color_idx;
};

// Fragment Shader Output
//
layout (location = 0) out vec4 frag_color;
//
// This will get written to frame buffer if tests pass, such as the
// depth (z-buffer) test.

void
fs_main()
{
  // Compute lighted color.
  //
  vec3 vec_vl = uni_light.position.xyz - vertex_e;
  float dist_to_light = length( vec_vl );
  bool lit_side = dot( normal_e, vec_vl ) > 0 == dot( normal_e, -vertex_e ) > 0;
  float phase = lit_side ? abs( dot( normal_e, vec_vl ) / dist_to_light ) : 0;
  vec4 illum = uni_light.color * max( 0.015, phase / dist_to_light );
  vec4 color = gl_FrontFacing ? uc.front[color_idx] : uc.back[color_idx];

  frag_color = illum * color;
}
#endif