/// LSU EE 4702-1 (Fall 2025), GPU Programming
//
 /// Homework 4 -- SOLUTION
 //
 //  Assignment: https://www.ece.lsu.edu/gpup/2025/hw04.pdf
 //
 //  Placed solution in routines vs_main, gs_main, and vs_main and
 //  surrounding code.
 //
 //  DID NOT put solution in vs_main_clean, gs_main_clean, and vs_main_clean.
 //  Left them untouched.

// 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"

///
 /// Shader Uniforms
///

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

// Convenience Variables
//
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_hw04_stel = com.opt_hw04.x != 0;
bool opt_hw04_dot = com.opt_hw04.y != 0;
bool opt_hw04_tex = com.opt_hw04.z != 0;
float opt_hw04_height_stel = com.tryoutf.y;

// 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
};


#ifdef HW4_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_object_from_local;

layout (location = 0) out VG
{
  vec4 vertex_e;
  vec4 color;
} Out;

void
vs_main()
{
  // Homework 4 - Put solution here and in surrounding code.

  // Transform to clip space ..
  //
  gl_Position = clip_from_object * in_mat_object_from_local * in_vertex;
  //
  // .. and eye-space.
  //
  Out.vertex_e = eye_from_object * in_mat_object_from_local * in_vertex;

  // Pass color unchanged.
  //
  Out.color = in_color;
}

#endif

#ifdef _GEOMETRY_SHADER_

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

 /// SOLUTION -- Problem 2
//
//   Set max_vertices to six for the two triangles needed in stellation.
//
layout ( triangle_strip, max_vertices = 6 ) 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;

  /// SOLUTION -- Problem 1
  vec2 tcoor;
} Out;

void
gs_main()
{
  // Homework 4 - Put solution here and in surrounding code.

  /// Geometry Shader
  //
  // Notes: 
  //
  //   - In[0] is the same vertex on all three triangles used to
  //     render a dodecahedron face.

  // 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.


  /// SOLUTION -- Problem 2
  //
  //  Compute information needed to transform an eye space coordinate
  //  to pentagon local space, in which the pentagon inscribed circle
  //  has a radius of one.

  // Radius of inscribed circle in eye-space coordinates.
  //
  float ri_pent = length( v12o );

  // Use vertex In[0] to compute ax since it is the same pentagon
  // vertex for all  three triangles.
  //
  vec3 ax = normalize( In[0].vertex_e.xyz - ctr_pentagon_e.xyz );
  vec3 ay = cross(tn,ax);

  // Size axes so that the axes will scale from eye space to local space.
  //
  vec3 vx = ax / ri_pent;
  vec3 vy = ay / ri_pent;
  //
  // These axes can be used to find the pentagon local coordinates, or
  // the axes can be used to compute a transformation matrix which
  // will compute the pentagon local coordinates.

  // Compute a matrix that will transform from eye space to pentagon
  // local space. Notice that pentagon local space is 2d.
  //
  // Start with a translation matrix.
  //
  mat4 tr = mat4(1.0); tr[3] = -ctr_pentagon_e;
  //
  // Then use axes to scale and rotate.
  //
  mat4 rot = mat4( transpose( mat3( vx, vy, vec3(0) ) ) );
  //
  // Multiplying these two yields the transformation matrix.
  //
  mat4 m = rot * tr;
  mat4x2 m2 = mat4x2(m);

  /// SOLUTION -- Problem 2
  //
  // Compute a vertex of the stellated face if stellation is on.
  //
  vec4 ctr_stel_e =
    opt_hw04_stel
    ? vec4( ctr_pentagon_e.xyz + tn * opt_hw04_height_stel * edge_len, 1 )
    : ctr_pentagon_e;

  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;

      if ( opt_tryout3 )
        {
          /// SOLUTION -- Problem 1a

          // Move vertices toward pentagon center.
          //
          vec3 to_ctr = vec3( ctr_pentagon_e - In[i].vertex_e );
          vec4 px_e = vec4( In[i].vertex_e.xyz + 0.1 * to_ctr, 1 );

          gl_Position = clip_from_eye * px_e;
          Out.vertex_e = px_e;
          Out.color = In[ i ].color;
          Out.normal_e = tn;
          Out.tcoor = m2 * Out.vertex_e;
          EmitVertex();
          continue;
        }

      if ( opt_hw04_stel )
        {
          /// SOLUTION -- Problem 2
          //
          // Emit one triangle if this is a pentagon edge.

          if ( !is_pentagon_edge ) continue;

          // Compute normal for triangle.
          //
          vec3 n = normalize
            ( cross( vec3( pt_i_e-ctr_stel_e ), vec3( pt_n_e-ctr_stel_e ) ) );

          vec4 pts[3] = { pt_i_e, pt_n_e, ctr_stel_e };
          for ( int j=0; j<3; j++ )
            {
              Out.vertex_e = pts[j];
              gl_Position = clip_from_eye * Out.vertex_e;
              Out.color = In[ i ].color;
              Out.normal_e = n;

              // Compute pentagon local coordinates, either using
              // dot products or the transformation matrix.
              //             
              // Setting this to true selects the alternate solution.
              bool use_dot_products = false;
              //
              if ( use_dot_products )
                {
                  // This is correct, but longer.
                  vec3 vctr = Out.vertex_e.xyz - ctr_pentagon_e.xyz;
                  Out.tcoor = vec2( dot(vctr,vx), dot(vctr,vy) );
                }
              else
                {
                  Out.tcoor = m2 * Out.vertex_e;
                }
              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;
      Out.tcoor = m2 * pt_i_e;
      EmitVertex();
    }
  EndPrimitive(); // Triangle Strip Re-Start
}

#endif

#ifdef _FRAGMENT_SHADER_

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

  /// SOLUTION
 vec2 tcoor;

} In;

layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;

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

void
fs_main()
{
  // Homework 4 - Put solution here and in surrounding code.

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

  /// SOLUTION -- Problem 1
  //
  //  Use gold if close enough to center.
  //
  if ( opt_hw04_dot && length(In.tcoor) < 0.5 ) our_color = vec4(1,1,0,1);

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

  if ( opt_hw04_tex )
    {
      // Scale texture coordinates so they fit inside inscribed circle.
      //
      float a = pow(2,-0.5);
      vec2 tc = ( In.tcoor + vec2(a,a) ) * vec2(a,a);
      tc.y = 1 - tc.y;

      // Check whether we are inside the square ..
      //
      bool out_of_range = any( greaterThan(tc,vec2(1)) )
        || any( lessThan(tc,vec2(0)) );
      //
      // .. if so, read the texture and use it to modulate the color.
      //
      if ( !out_of_range )
        lighted_color = lighted_color * texture(tex_unit_0,tc);
    }

  out_frag_color = lighted_color;
}
#endif

#endif // HW4_TRI


/// Homework 4:
//
 ///  Code below should not be part of the solution.
 //   Use it for experimentation or keep it unmodified, as a reference.

#ifdef HW4_CLEAN
#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_object_from_local;

layout (location = 0) out VG
{
  vec4 vertex_e;
  vec4 color;
} Out;

void
vs_main_clean()
{
  /// Not part of solution. Modify vs_main instead.

  // Transform to clip space ..
  //
  gl_Position = clip_from_object * in_mat_object_from_local * in_vertex;
  //
  // .. and eye-space.
  //
  Out.vertex_e = eye_from_object * in_mat_object_from_local * in_vertex;

  // Pass color unchanged.
  //
  Out.color = in_color;

  /// Not part of solution. Modify vs_main instead.
}

#endif

#ifdef _GEOMETRY_SHADER_

// Indicate type of input primitive expected by geometry shader.
//
layout ( triangles ) in;
layout ( triangle_strip, max_vertices = 3 ) 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;

} Out;

void
gs_main_clean()
{
  /// Not part of solution. Modify gs_main instead.

  /// Geometry Shader
  //
  // Notes: 
  //
  //   - In[0] is the same vertex on all three triangles used to
  //     render a dodecahedron face.

  // 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.

  /// Not part of solution. Modify gs_main instead.

  // Emit one triangle.
  //
  for ( int i=0; i<3; i++ )
    {
      // For convenience, write this vertex and next one in to variables. 
      //
      vec4 pt_i_e = In[ i ].vertex_e;
      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;

      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

  /// Not part of solution. Modify gs_main instead.
}

#endif

#ifdef _FRAGMENT_SHADER_

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

layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;

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

void
fs_main_clean()
{
  /// Not part of solution. Modify fs_main instead.

  vec2 tc = vec2(0.5,0.5);
  vec4 texel = texture(tex_unit_0,tc);

  vec4 our_color = gl_FrontFacing ? In.color : vec4(1,0,0,1);
  vec4 lighted_color = generic_lighting( In.vertex_e, our_color, In.normal_e );
  out_frag_color = lighted_color;
}

#endif // _FRAGMENT_SHADER_

#endif // HW4_CLEAN