/// LSU EE 4702-1 (Fall 2025), GPU Programming
//
 /// Homework 5  (EE 4702 Fall 2025) -- SOLUTION
 //
 //  Assignment: https://www.ece.lsu.edu/gpup/2025/hw05.pdf
 //
 //  The solution has been be put in this file, hw05-shdr.cc, and hw05.cc

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


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

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

bool opt_tryout1 = com.tryout.x != 0;
bool opt_tryout2 = com.tryout.y != 0;
bool opt_tryout3 = com.tryout.z != 0;
float opt_tryoutf = com.tryoutf.x;

bool opt_hw05_stel = com.opt_hw05.x != 0;
bool opt_hw05_dot = com.opt_hw05.y != 0;
bool opt_hw05_tex = com.opt_hw05.z != 0;
float opt_hw05_height_stel = com.tryoutf.y;


///
 /// Shader Uniforms
///

// Note: this uniform is also defined in transform.h.
layout ( binding = BIND_TRANSFORM ) uniform Uni_Transform
{
  //   Course Name           OpenGL Name
  mat4 eye_from_object;   // gl_ModelViewMatrix
  mat4 clip_from_eye;     // gl_ProjectionMatrix
  mat4 clip_from_object;  // gl_ModelViewProjectionMatrix
  mat4 object_from_eye;   // gl_ModelViewMatrixInverse
  mat4 eye_from_clip;     // gl_ProjectionMatrixInverse
  mat4 object_from_clip;  // gl_ModelViewProjectionMatrixInverse
};

///
 /// Storage Buffers
///

#ifdef BIND_HW5_COLOR
layout ( binding = BIND_HW5_COLOR ) buffer cb1 { vec4 buf_hw05_color[]; };
#endif
#ifdef BIND_HW5_MATRIX
layout ( binding = BIND_HW5_MATRIX ) buffer cb2 { mat4 buf_hw05_matrix[]; };
#endif
#ifdef BIND_DOD_COORS_L
layout ( binding = BIND_DOD_COORS_L ) buffer cb3 { vec4 buf_dod_coors_l[]; };
#endif


#ifdef HW5_TRI

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Inputs
///
layout ( location = LOC_IN_POS ) in vec4 in_vertex;
layout ( location = LOC_IN_COLOR ) in vec4 in_color;
layout ( location = LOC_IN_MAT4 ) in mat4 in_mat;

// Declare some outputs as a group. These are grouped together
// because they are used by the generic lighting routine.
//
layout (location = 0) out VG
{
  vec4 vertex_e;
  vec4 color;
} Out;

void
vs_main()
{
  // Transform vertex to clip space.
  gl_Position = clip_from_object * in_mat * in_vertex;

  // Compute eye-space vertex coordinate.
  // These are outputs of the vertex shader and inputs to the frag shader.
  //
  Out.vertex_e = eye_from_object * in_mat * in_vertex;
  Out.color = in_color;
}



#endif

#ifdef _GEOMETRY_SHADER_

// Indicate type of input primitive expected by geometry shader.
//
layout ( triangles ) in;
layout ( triangle_strip, max_vertices = 9 ) out;

layout (location = 0) in VG
{
  vec4 vertex_e;
  vec4 color;

} In[3];

layout (location = 0) out GF
{
  vec4 vertex_e;
  vec4 color;
  vec3 normal_e;

  vec2 tcoor;
} Out;

void
gs_main()
{
  /// Geometry Shader
  //

   /// Geometry Shader

  // Compute Triangle Normal
  //
  vec3 v12 = In[2].vertex_e.xyz - In[1].vertex_e.xyz;
  vec3 v10 = In[0].vertex_e.xyz - In[1].vertex_e.xyz;
  vec3 tn = normalize( cross(v12,v10) );
  float edge_len = length(v12);

  // Compute Center of Pentagon
  //
  const float pi = radians(180);  // No, pi is not a built in. Nor tau.
  vec3 v12o = cross(tn,v12) * float( 1.0 / ( 2 * tan( 2 * pi / 10 ) ) );
  vec4 ctr_pentagon_e = vec4( In[1].vertex_e.xyz + 0.5f * v12 + v12o, 1 );
  //
  // The calculation above is correct when In[1] and In[2] are an edge
  // of the pentagon.

  float rc_pent = length( v12o );
  vec3 ax = normalize( In[0].vertex_e.xyz - ctr_pentagon_e.xyz );
  vec3 ay = cross(tn,ax);
  vec3 vx = ax / rc_pent, vy = ay / rc_pent;
  mat4 tr = mat4(1.0); tr[3] = -ctr_pentagon_e;
  mat4 rot = mat4( transpose( mat3( vx, vy, vec3(0) ) ) );
  mat4x2 m2 = mat4x2( rot * tr );

  vec4 ctr_stel_e =
    opt_hw05_stel
    ? vec4( ctr_pentagon_e.xyz + tn * opt_hw05_height_stel * edge_len, 1 )
    : ctr_pentagon_e;

  // Emit one triangle.
  //
  for ( int i=0; i<3; i++ )
    {
      // This vertex (point) eye-space coordinates.
      //
      vec4 pt_i_e = In[i].vertex_e;
      //
      // The next vertex (point) (in triangle order) eye-space coordinates.
      //
      vec4 pt_n_e = In[(i+1)%3].vertex_e;

      // Check whether pt_i_e and pt_n_e form an edge of the pentagon.
      //
      bool is_pentagon_edge = distance(pt_i_e,pt_n_e) < edge_len * 1.1f;

      Out.tcoor = m2 * pt_i_e;

      if ( opt_hw05_stel )
        {
          if ( !is_pentagon_edge ) continue;

          vec4 pts[3] = { pt_i_e, pt_n_e, ctr_stel_e };
          vec3 n = normalize
            ( cross( vec3( pt_i_e-ctr_stel_e ), vec3( pt_n_e-ctr_stel_e ) ) );

          for ( int j=0; j<3; j++ )
            {
              Out.vertex_e = pts[j];
              gl_Position = clip_from_eye * pts[j];
              Out.color = In[i].color;
              Out.normal_e = n;
              Out.tcoor = m2 * pts[j];
              EmitVertex();
            }

          EndPrimitive();
          continue;
        }

      gl_Position = gl_in[i].gl_Position;
      Out.vertex_e = In[i].vertex_e;
      Out.color = In[i].color;
      Out.normal_e = tn;
      EmitVertex();
    }
  EndPrimitive(); // Triangle Strip Re-Start
}

#endif

#endif // HW5_TRI


#ifdef HW5_PTS_PROB_1

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Inputs
///
#ifdef LOC_IN_POS
layout ( location = LOC_IN_POS ) in vec4 in_vertex;
#endif
#ifdef LOC_IN_INT1
layout ( location = LOC_IN_INT1 ) in int in_color_idx;
#endif
#ifdef LOC_IN_MAT4
layout ( location = LOC_IN_MAT4 ) in mat4 in_mat;
#endif
#ifdef LOC_IN_NORMAL
layout ( location = LOC_IN_NORMAL ) in vec3 in_normal;
#endif

void
vs_main_pts_prob_1()
{
}

#endif

#ifdef _GEOMETRY_SHADER_

// Indicate type of input primitive expected by geometry shader.
//
layout ( points ) in;
layout ( triangle_strip, max_vertices = 15 ) out;

layout (location = 0) out GF
{
  vec4 vertex_e;
  flat vec4 color;
  flat vec3 normal_e;

  vec2 tcoor;

} Out;

void
gs_main_pts_prob_1()
{
  const float pi = radians(180);
  const float delta_th = 2 * pi / 5;

  // Consecutive numbering of primitives (vertices in this case
  // because the input topology is points).
  //
  const int prim_id = gl_PrimitiveIDIn;

  /// SOLUTION -- Problem 1
  //
  // Use gl_PrimitiveIDIn to determine which dodecahedron we are to
  // render, dod_idx, and which face of that dodecahedron, face_idx.
  //
  int dod_idx = gl_PrimitiveIDIn / 12;
  int face_idx = gl_PrimitiveIDIn % 12;
  //
  // Note: dod_idx will be used in the same way color_idx was used
  // in the original points code, gs_main_pts_clean. It will
  // also be used to retrieve the transformation matrix that in
  // the original code was a vertex shader input.

  vec4 color = buf_hw05_color[dod_idx];
  mat4 eye_from_local = eye_from_object * buf_hw05_matrix[dod_idx];

  vec4 dod_ctr_e = eye_from_local * vec4(0,0,0,1);

  // Retrieve pentagon vertex coordinates from the storage buffer ..
  // .. and transform them to eye space.
  //
  // Start by computing the index of the first vertex of this
  // pentagon ..
  //
  int pt0_idx = face_idx * 5;
  //
  // .. then retrieve and transform them.
  //
  vec4 pent_pts_e[5];
  for ( int i=0; i<5; i++ )
    pent_pts_e[i] = eye_from_local * buf_dod_coors_l[pt0_idx+i];

  // Compute the pentagon tangent and use as a local z axis.
  //
  vec3 az =
    normalize( cross( vec3( pent_pts_e[2] - pent_pts_e[1] ),
                      vec3( pent_pts_e[0] - pent_pts_e[1] ) ) );

  // Compute the pentagon center.
  //
  float edge_len = distance( pent_pts_e[1], pent_pts_e[2] );
  // ( 5^(1/2) GoldenRatio^5 / 20 )^(1/2)
  float ri_e = edge_len * 1.11351636441;
  vec4 pent_ctr_e = dod_ctr_e + vec4(ri_e * az,0);
  //
  // The pentagon center could also have been computed by taking
  // the average of the pentagon vertices.

  vec3 vx = vec3( pent_pts_e[0] - pent_ctr_e );
  vec3 vy = cross( az, vx );

  // Compute a matrix, m2, that will transform from eye coordinates to
  // pentagon-local coordinates (2D) where the center is at the origin.
  //
  mat4 tr = mat4(1.0); tr[3] = -pent_ctr_e;
  mat4 scale = mat4( 1 / ( cos( 2 * pi / 10 ) * dot( vx, vx ) ) );
  mat4 rot = mat4( transpose( mat3( vx, vy, vec3(0) ) ) );
  mat4x2 m2 = mat4x2( scale * rot * tr );

  // Compute the coordinates of the center of the stellation, and
  // use it if ctr_stel_e is true.
  //
  vec4 ctr_stel_e =
    opt_hw05_stel
    ? vec4( pent_ctr_e.xyz + az * opt_hw05_height_stel * edge_len, 1 )
    : pent_ctr_e;

  for ( int i=0; i<5; i++ )
    {
      // Put the coordinates in an array so that a loop can be
      // used to emit the vertices.
      //
      vec4 pts[3] = { ctr_stel_e, pent_pts_e[i], pent_pts_e[(i+1)%5] };

      vec3 n = normalize
        ( cross( vec3(pts[0] - pts[1]), vec3(pts[0] - pts[2]) ) );
      for ( int j=0; j<3; j++ )
        {
          Out.vertex_e = pts[j];
          gl_Position = clip_from_eye * Out.vertex_e;
          Out.color = color;
          Out.normal_e = n;
          Out.tcoor = m2 * pts[j];
          EmitVertex();
        }
      EndPrimitive();
      //
      // Note: It would be possible to emit a triangle strip.
      // Possible final exam question.
    }
}

#endif

#endif


#ifdef HW5_INST_PROB_2

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Inputs
///
 /// SOLUTION -- Problem 2
 //
 //  Remove in_mat and in_ball_idx ..
 //  .. because those don't apply to every dodecahedron.
 //
 //  Add in_tcoor because they do apply to every dodecahedron.
 //
layout ( location = LOC_IN_POS ) in vec4 in_vertex;
layout ( location = LOC_IN_TCOOR ) in vec2 in_tcoor;

// Declare some outputs as a group. These are grouped together
// because they are used by the generic lighting routine.
//
layout (location = 0) out VG
{
  vec4 vertex_e;
  vec4 color;
  vec2 tcoor;
} Out;

void
vs_main_inst_prob_2()
{
  // Transform vertex to clip space.

  const int instance_id = gl_InstanceIndex;
  //
  // Values range for 0 to n_instances-1.

  const int vertex_id = gl_VertexIndex;
  //
  // Values range from 0 to number of vertices - 1.
  // Values range from 0 to bset_inst_prob2.size() - 1.

  /// SOLUTION -- Problem 2
  //
  //  Retrieve transformation matrix using gl_InstanceIndex ..
  //
  mat4 object_from_local = buf_hw05_matrix[gl_InstanceIndex];
  //
  // .. and use it, in part, to compute clip-space coordinates.
  //
  gl_Position = clip_from_object * object_from_local * in_vertex;

  // Compute eye-space vertex coordinate.
  //
  Out.vertex_e = eye_from_object * object_from_local * in_vertex;
  Out.color = buf_hw05_color[gl_InstanceIndex];
  Out.tcoor = in_tcoor;
}



#endif

#ifdef _GEOMETRY_SHADER_

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

 /// SOLUTION -- Problem 2
//
//   Reduce number of vertices from 9 to 3.
//
layout ( triangle_strip, max_vertices = 3 ) out;

layout (location = 0) in VG
{
  vec4 vertex_e;
  vec4 color;
  vec2 tcoor;
} In[3];

layout (location = 0) out GF
{
  vec4 vertex_e;
  vec4 color;
  vec3 normal_e;

  vec2 tcoor;
} Out;

void
gs_main_inst_prob_2()
{
  /// Geometry Shader
  //

  /// SOLUTION -- Problem 2
  //
  //  Here there is just one of the pentagon with one vertex in the
  //  center of the pentagon and the other two forming an edge.

  // Compute Triangle Normal
  //
  vec3 v12 = In[2].vertex_e.xyz - In[1].vertex_e.xyz;
  vec3 v10 = In[0].vertex_e.xyz - In[1].vertex_e.xyz;
  vec3 tn = normalize( cross(v12,v10) );
  float edge_len = length(v12);

  vec4 ctr_pentagon_e = In[0].vertex_e;

  vec4 ctr_stel_e =
    opt_hw05_stel
    ? vec4( ctr_pentagon_e.xyz + tn * opt_hw05_height_stel * edge_len, 1 )
    : ctr_pentagon_e;

  if ( opt_hw05_stel )
    {
      vec3 v10s = ctr_stel_e.xyz - In[1].vertex_e.xyz;
      tn = normalize( cross(v12, v10s ) );
    }

  // Emit one triangle.
  //
  for ( int i=0; i<3; i++ )
    {
      if ( i == 0 && opt_hw05_stel )
        {
          Out.vertex_e = ctr_stel_e;
          gl_Position = clip_from_eye * Out.vertex_e;
        }
      else
        {
          Out.vertex_e = In[i].vertex_e;
          gl_Position = gl_in[i].gl_Position;
        }

      Out.color = In[i].color;
      Out.tcoor = In[i].tcoor;
      Out.normal_e = tn;
      EmitVertex();
    }
  EndPrimitive(); // Triangle Strip Re-Start
}

#endif

#endif // HW5_INST_PROB_2





#ifdef HW5_PTS_CLEAN

#ifdef _VERTEX_SHADER_

 /// Vertex Shader Inputs
///
layout ( location = LOC_IN_POS ) in vec4 in_vertex;
layout ( location = LOC_IN_INT1 ) in int in_color_idx;
layout ( location = LOC_IN_MAT4 ) in mat4 in_mat;
layout ( location = LOC_IN_NORMAL ) in vec3 in_normal;

// Declare some outputs as a group. These are grouped together
// because they are used by the generic lighting routine.
//
layout (location = 0) out VG
{
  mat4 obj_from_local;
  vec4 pent_pt0_l;
  vec3 dod_to_pctr_l;
  int color_idx;
} Out;

void
vs_main_pts_clean()
{
  Out.obj_from_local = in_mat;
  Out.pent_pt0_l = in_vertex;
  Out.dod_to_pctr_l = in_normal;
  Out.color_idx = in_color_idx;
}

#endif

#ifdef _GEOMETRY_SHADER_

// Indicate type of input primitive expected by geometry shader.
//
layout ( points ) in;
layout ( triangle_strip, max_vertices = 15 ) out;

layout (location = 0) in VG
{
  mat4 obj_from_local;
  vec4 pent_pt0_l;
  vec3 dod_to_pctr_l;
  int color_idx;

} In[1];

layout (location = 0) out GF
{
  vec4 vertex_e;
  flat vec4 color;
  flat vec3 normal_e;

  vec2 tcoor;

} Out;

void
gs_main_pts_clean()
{

  const float pi = radians(180);
  const float delta_th = 2 * pi / 5;

  mat4 eye_from_local = eye_from_object * In[0].obj_from_local;

  vec4 dod_ctr_e = eye_from_local * vec4(0,0,0,1);
  vec4 pent_ctr_l = vec4( In[0].dod_to_pctr_l, 1 );
  vec4 pent_ctr_e = eye_from_local * pent_ctr_l;
  vec4 pt0_l = In[0].pent_pt0_l;
  vec4 pt0_e = eye_from_local * pt0_l;
  vec3 az = normalize( pent_ctr_e.xyz - dod_ctr_e.xyz );
  vec3 vx = pt0_e.xyz - pent_ctr_e.xyz;
  vec3 vy = cross( az, vx );
  mat4 tr = mat4(1.0); tr[3] = -pent_ctr_e;
  mat4 scale = mat4( 1 / ( cos( 2 * pi / 10 ) * dot( vx, vx ) ) );
  mat4 rot = mat4( transpose( mat3( vx, vy, vec3(0) ) ) );
  mat4x2 m2 = mat4x2( scale * rot * tr );

  vec4 pent_pts_e[5];
  pent_pts_e[0] = pt0_e;

  vec4 color = buf_hw05_color[In[0].color_idx];

  for ( int i=1; i<5; i++ )
    pent_pts_e[i] = pent_ctr_e
      + vec4( cos(i*delta_th) * vx + sin(i*delta_th) * vy, 0 );

  float edge_len = distance(pent_pts_e[1],pent_pts_e[2]);
  vec4 ctr_stel_e =
    opt_hw05_stel
    ? vec4( pent_ctr_e.xyz + az * opt_hw05_height_stel * edge_len, 1 )
    : pent_ctr_e;

  for ( int i=0; i<5; i++ )
    {
      vec4 pts[3] = { ctr_stel_e, pent_pts_e[i], pent_pts_e[(i+1)%5] };
      vec3 n = normalize
        ( cross( pts[0].xyz - pts[1].xyz, pts[0].xyz - pts[2].xyz ) );
      for ( int j=0; j<3; j++ )
        {
          Out.vertex_e = pts[j];
          gl_Position = clip_from_eye * Out.vertex_e;
          Out.color = color;
          Out.normal_e = n;
          Out.tcoor = m2 * pts[j];
          EmitVertex();
        }
      EndPrimitive();
    }
}

#endif

#endif

#ifdef _FRAGMENT_SHADER_

 /// Fragment Shader Inputs
//
layout ( location = 0 ) in GF
{
  vec4 vertex_e;
  vec4 color;
  vec3 normal_e;
  vec2 tcoor;
} In;

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

 /// Fragment Shader Output
//
layout ( location = 0 ) out vec4 out_frag_color;

void
fs_main()
{
  vec4 our_color = gl_FrontFacing ? In.color : vec4(1,0,0,1);

  float cd = length(In.tcoor);
  if ( opt_hw05_dot && cd < 0.5 ) our_color = vec4(1,1,0,1);
  float a = pow(2,-0.5);
  vec2 tc = ( In.tcoor + vec2(a,a) ) * vec2(a,a);
  tc.y = 1 - tc.y;

  vec4 texel = texture(tex_unit_0,tc);
  bool out_of_range = any( greaterThan(tc,vec2(1)) )
    || any( lessThan(tc,vec2(0)) );

  vec4 lighted_color = generic_lighting( In.vertex_e, our_color, In.normal_e );

  if ( opt_hw05_tex && !out_of_range )
    lighted_color = lighted_color * texel;

  out_frag_color = lighted_color;
}
#endif