/// LSU EE 4702-1 (Fall 2024), GPU Programming
//
 /// Homework 5 -- SOLUTION
//
//   Modify this file AND hw05.cc.
//   Assignment: https://www.ece.lsu.edu/koppel/gpup/2024/hw05.pdf

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

#extension GL_GOOGLE_include_directive : enable

layout ( binding = BIND_MISC ) uniform Uni_Misc
{
  ivec4 opt_tryout;
  ivec4 opt_assign;
  float opt_tryoutf;
};

bool opt_tryout1 = bool(opt_tryout.x);
bool opt_tryout2 = bool(opt_tryout.y);
bool opt_tryout3 = bool(opt_tryout.z);
int  opt_tryouti = opt_tryout.w;
bool opt_texture = bool(opt_assign.x);


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

  /// SOLUTION -- Problem 3
  //
  mat4 local_from_object;
  int s_sides, s_levels, n_sides, n_levels;
  float n_sides_inv, n_levels_inv;

} uc;


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

layout ( binding = BIND_HYPERB_COORDS ) buffer sc { vec4 hyperb_coords[]; };
layout ( binding = BIND_HYPERB_NORMS ) buffer sn { vec4 hyperb_norms[]; };


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

 /// Vertex Shader Inputs
//
// None.


 /// Vertex Shader Output
//
layout (location = 0) out Data_VG
{
  vec4 padding;
};

 /// The Vertex Shader
void
vs_points()
{
  // Pre-Defined Input Variable
  //
  // Reference: OGSL 4.6 Section 7.1.1
  //
  // in int gl_VertexIndex;
  // Integer index of this vertex. Ranges from 0 to n_vertices - 1,
  // where n_vertices is the number of vertices sent to the rendering
  // pipeline.

  // Homework 5: If code is added here, don't forget to
  // declare vertex shader outputs and geometry shader inputs.

}
#endif

#ifdef _GEOMETRY_SHADER_

layout ( points ) in;

 /// SOLUTION -- Problem 3
//
//   Increase max_vertices from 3 to 4.
//
layout ( triangle_strip, max_vertices = 4 ) out;

layout (location = 0) in Data_VG
{
  vec4 padding;
} In[];

layout (location = 0) out Data_GF
{
  vec4 vertex_e;
  vec3 normal_e;
  int color_idx;
  vec2 tcoor;
} Out;

void
gs_points()
{
  // Pre-Defined Input Variable
  //
  // Reference: OGSL 4.6 Section 7.1.4
  //
  // in int gl_PrimitiveIDIn;
  //
  // Integer index of this primitive. Ranges from 0 to n_primitives-1,
  // where n_primitives depends on the grouping (topology). For
  // vk::PrimitiveTopology::ePointList n_primitives is the same as the
  // number of vertices sent to the rendering pipeline in the draw
  // command.

  /// SOLUTION -- Problem 3
  //
  //  This invocation of the geometry shader will emit two triangles,
  //  something like the loop body in the PV_Individ case in
  //  World::render.
  //
  //  First, compute the level and side based on gl_PrimitiveIDIn.
  //
  int level0 = gl_PrimitiveIDIn % uc.n_levels;
  int side0 = gl_PrimitiveIDIn / uc.n_levels;
  //
  //  Because the draw command specified n_levels * n_sides "vertices"
  //  the value of gl_PrimitiveIDIn will range from 0 to n_levels *
  //  n_sides -1, and so the value of side0 will range from 0 to
  //  n_sides-1, and clearly level0 will range from 0 to n_levels-1.

  //  The two triangles will be drawn using a triangle strip. This
  //  can conveniently by done using a loop nest:
  //
  for ( int dl = 0; dl<2; dl++ )
    for ( int ds = 0; ds < 2; ds ++ )
      {
        int level = level0 + dl;
        int side = side0 + ds;

        // Compute the array index in the same way idxf would.
        //
        int idx = level + side * uc.s_levels;

        // Retrieve the coordinate and normal.
        //
        vec4 p = hyperb_coords[ idx ];
        vec4 n = hyperb_norms[ idx ];

        // Convert coordinate to clip space and eye space, and normal
        // to eye space.
        //
        gl_Position = ut.clip_from_object * p;
        Out.vertex_e = ut.eye_from_object * p;
        Out.normal_e = normalize( mat3(ut.eye_from_object) * n.xyz );

        // Compute the color index.
        //
        Out.color_idx = side0 != 0 ? 1 :
          ds == 0 ? ( level0 % 2 == 0 ? 2 : 5 ) : ( level0 % 2 == 0 ? 4 : 3 );

        if ( opt_tryout1 )
          {
            // PARTIAL CREDIT Texture coordinates.
            Out.tcoor = vec2( 10 * side * uc.n_sides_inv,
                              1 - level * uc.n_levels_inv );
          }
        else
          {
            // FULL CREDIT Texture coordinates.
            vec4 ploc = uc.local_from_object * p;
            float theta = atan( ploc.y, ploc.x );
            Out.tcoor = vec2( 1.5 * theta, 1 - level * uc.n_levels_inv );
          }

        EmitVertex();
      }
  EndPrimitive();
}

#endif

#ifdef _FRAGMENT_SHADER_

layout ( binding = BIND_TEXUNIT ) uniform sampler2D tex_unit_0;

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

// 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_points()
{
  // Homework 5:  NO NEED to modify this routine.

  vec4 texel = opt_texture ? texture(tex_unit_0,tcoor) : vec4(1,1,1,1);

  // Vector from fragment to light.
  vec3 vec_vl = uni_light.position.xyz - vertex_e.xyz;

  // Distance squared.
  float dist_sq = dot( vec_vl, vec_vl );

  // False if the light illuminates the side we can't see.
  bool lit_side = dot( normal_e, vec_vl )>0 == dot( normal_e, -vertex_e.xyz )>0;

  float ambient_attn = 0.01;

  // Amount of light reaching the fragment.
  float attenuation = lit_side ? abs( dot( normal_e, vec_vl ) / dist_sq ) : 0;

  // Material color.
  vec4 mat_color = gl_FrontFacing ? uc.front[color_idx] : uc.back[color_idx];

  // Compute lighted color.
  frag_color =
    texel * mat_color * uni_light.color * ( attenuation + ambient_attn );
  //
  // One important thing fragment shaders do--but not this one--is
  // apply textures.
}
#endif