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

 /// Homework 2 -- SOLUTION


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

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



layout ( binding = BIND_SPHERE_POS ) buffer spr { vec4 sphere_pos_rad[]; };
layout ( binding = BIND_SPHERE_ROT ) buffer sr { mat4 sphere_rot[]; };
layout ( binding = BIND_SPHERE_COLOR ) buffer sc { vec4 sphere_color[]; };

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

bool opt_tryout1 = bool(com.tryout.x);
bool opt_tryout2 = bool(com.tryout.y);
float opt_tryoutf = com.tryoutf.x;
float opt_hw02_curl = com.tryoutf.y;



#ifdef _VERTEX_SHADER_

layout ( location = LOC_IN_POS ) in vec4 in_vertex_o;

#ifdef LOC_IN_TCOOR
layout ( location = LOC_IN_TCOOR ) in vec2 in_tex_coor;
#endif

// Interface block for vertex shader output / geometry shader input.
//
layout ( location = 0 ) out Data_to_GS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
  vec4 color;
  vec4 vertex_c;

  // Any changes here must also be made to the fragment shader input.

  /// SOLUTION -- Problem 1
  //
  //  Send along the sphere radius.
  //
  float sphere_radius;
  //
  //  The sphere radius will be used to compute the distance between
  //  the shrunken triangles and the original ones.

};

void
vs_main_instances_sphere()
{
  vec4 pos_rad = sphere_pos_rad[gl_InstanceIndex];
  float rad = pos_rad.w;
  mat4 rot = sphere_rot[gl_InstanceIndex];
  vec4 normr = rot * in_vertex_o;
  vec3 normal_o = normr.xyz;
  vec4 vertex_o = vec4( pos_rad.xyz + rad * normal_o, 1 );

  vertex_c = gl_ModelViewProjectionMatrix * vertex_o;
  vertex_e = gl_ModelViewMatrix * vertex_o;
  normal_e = normalize(gl_NormalMatrix * normal_o );
  tex_coor = in_tex_coor;
  color = sphere_color[gl_InstanceIndex];

  /// SOLUTION
  //
  //  Pass the radius along to the geometry shader.
  sphere_radius = rad;
}
#endif


#ifdef _GEOMETRY_SHADER_

layout ( location = 0 ) in Data_to_GS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
  vec4 color;
  vec4 vertex_c;

  // SOLUTION
  float sphere_radius;
} In[];

layout ( location = 0 ) out Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
  flat vec4 color;

  // SOLUTION
  flat int use_tex;

};

// Type of primitive at geometry shader input.
//
layout ( triangles ) in;

// Type of primitives emitted geometry shader output.
//


// layout ( triangle_strip, max_vertices = 3 ) out;

 /// SOLUTION 
 //
const int slices = 7;
const int max_v = 6 + 2 * slices + 2;
layout ( triangle_strip, max_vertices = max_v ) out;


void
gs_main_shrink()
{
  const bool opt_normal_sphere = bool(com.tryout.z);
  const bool unworn = false;

  if ( unworn )
    {
      // This code renders an ordinary triangle, and includes the
      // sphere-normal/triangle normal effect.
      for ( int i=0; i<3; i++ )
        {
          normal_e = In[opt_normal_sphere?i:0].normal_e;
          vertex_e = In[i].vertex_e;
          color = In[i].color;
          use_tex = 1; // SOLUTION.
          gl_Position = In[i].vertex_c;
          tex_coor = In[i].tex_coor;
          EmitVertex();
        }
      EndPrimitive();
      return;
    }

  /// SOLUTION -- Problem 1

  // Compute the coordinates of the center of the triangle.
  vec3 ctr_e = vec3(0), cnorm_e = vec3(0);
  for ( int i=0; i<3; i++ )
    {
      ctr_e += In[i].vertex_e.xyz;
      cnorm_e += In[i].normal_e;
    }
  ctr_e *= 0.333333333333f;
  cnorm_e *= 0.333333333333f;

  // Compute a distance between the original triangle and the shrunken
  // one.
  //
  float ht = In[0].sphere_radius * 0.01;
  //
  // If this distance were zero then the shrunken triangle would be in
  // the same plane as the original triangle. It is possible that the
  // original triangle would cover the shrunken one, at least in some
  // places.

  /// The mix OGSL Library Function
  //
  //  mix( a, b, f )  ->  a * (1-f) + b * f

  // Emit Shrunken Triangle
  //
  for ( int i=0; i<3; i++ )
    {
      // Interpolate normal value so that it's appropriate for this
      // position on the sphere.
      //
      normal_e = mix( In[i].normal_e, cnorm_e, opt_hw02_curl );

      // Interpolate vertex coordinate.
      //
      vertex_e.xyz =
        mix( In[i].vertex_e.xyz, ctr_e, opt_hw02_curl ) + normal_e * ht;
      vertex_e.w = 1;

      // Use eye-space coordinate to compute clip-space coordinate.
      //
      gl_Position = gl_ProjectionMatrix * vertex_e;
      //
      // Note that interpolating clip-space coordinates is wrong
      // because distances between them are distorted by the
      // perspective transformation.

      // The texture coordinate should not be interpolated, because we
      // want the texture image to shrink along with the triangle.
      // There is no point interpolating the color because all
      // vertices are the same color. (See the vertex shader.)
      //
      tex_coor = In[i].tex_coor;
      color = In[i].color;

      // Signal the fragment shader to apply the texture.
      use_tex = 1;

      EmitVertex();
    }
  EndPrimitive();

  // Emit Untextured Original Triangle
  //
  for ( int i=0; i<3; i++ )
    {
      vertex_e = In[i].vertex_e;
      normal_e = In[opt_normal_sphere?i:0].normal_e;

      // Darken the input color.
      //
      color.rgb = 0.5 * In[i].color.rgb;
      color.a = In[i].color.a; // Just in case a is set.

      // Signal the fragment shader to not apply the texture.
      //
      use_tex = 0;

      gl_Position = gl_ProjectionMatrix * vertex_e;
      EmitVertex();
    }
  EndPrimitive();
}


void
gs_main_curl()
{
  const bool opt_normal_sphere = bool(com.tryout.z);

  const bool unworn = false;

  if ( unworn )
    {
      // This code renders an ordinary triangle, and includes the
      // sphere-normal/triangle normal effect.
      for ( int i=0; i<3; i++ )
        {
          normal_e = In[opt_normal_sphere?i:0].normal_e;
          vertex_e = In[i].vertex_e;
          color = In[i].color;
          gl_Position = In[i].vertex_c;
          tex_coor = In[i].tex_coor;
          EmitVertex();
        }
      EndPrimitive();
      return;
    }

  // The start of a solution to Homework 2 Problem 2 appears below.

  // The index of the vertex that will peel up.
  int v1_idx = 0;

  const int v0_idx[2] = { ( v1_idx + 1 ) % 3, ( v1_idx + 2 ) % 3 };
  vec4 p1_e = In[v1_idx].vertex_e;  // Vertex peeling up.
  vec3 n1_e = In[v1_idx].normal_e;  // Its normal.
  vec2 tc1 = In[v1_idx].tex_coor;

  const float p_frac = opt_hw02_curl;
  const float f0 = 1 - p_frac;

  /// SOLUTION - Problem 2
  //
  // Emit the uncurled part of the triangle.
  //
  // The loop below emits four triangles (including one with a zero
  // area). The four triangles together form the original triangle.
  // The first two are the part of the original triangle that has not
  // yet curled up. The last two (one of which has zero area) are the
  // part of the original triangle that is underneath the curled up
  // part.
  //
  float fi[] = { 0, f0, 1 };
  for ( int i=0; i<3; i++ )
    {
      const float f = fi[i];
      for ( int j=0; j<2; j++ )
        {
          const int idx = v0_idx[j];
          normal_e = opt_normal_sphere
            ? mix( In[idx].normal_e, n1_e, f ) : In[0].normal_e;
          vertex_e = mix( In[idx].vertex_e, p1_e, f );
          use_tex = int( i < 1 );
          color = i < 1 ? In[0].color : In[0].color * vec4(0.5,0.5,0.5,1);
          gl_Position = gl_ProjectionMatrix * vertex_e;
          tex_coor = mix(In[idx].tex_coor, tc1, f);
          EmitVertex();
        }
    }
  EndPrimitive();

  //
  // Curled part of the triangle.
  //

  // Number of times that peeled part wraps around cylinder.
  const float n_rev = 0.5;

  const float two_pi = 2 * 3.14159265;

  const float delta_theta = n_rev * p_frac * two_pi / slices;
  const float delta_f = p_frac / slices;

  // The two vertices that are opposite the peeling vertex.
  vec3 p00_e = In[v0_idx[0]].vertex_e.xyz;
  vec3 p01_e = In[v0_idx[1]].vertex_e.xyz;

  // Compute local axes for cylinder.
  //
  vec3 ax = normalize(p01_e - p00_e);
  vec3 v001 = p1_e.xyz - p00_e;
  vec3 az = normalize( cross(ax,v001) );
  vec3 ay = cross(az,ax);
  //
  // Cylinder axis is parallel to ax.  
  // Axes ay and az are used to draw a circle on the cylinder surface.

  // Choose the radius of the cylinder.
  const float d = dot(v001,ay);
  const float r = d / ( n_rev * two_pi );

  // A point on the cylinder axis.
  vec3 ctr_e = p1_e.xyz - p_frac * d * ay + r * az;

  // Iterate over parts of the peeled triangle. The peeling starts at i=0.
  for ( int i=0; i<=slices; i++ )
    {
      const float theta = 1.5f * 3.1415927f + delta_theta * i;
      const float f = f0 + delta_f * i;
      vec3 nc = ay * cos(theta) + az * sin(theta);
      vec3 vc = r * nc;

      // Iterate over the two legs of the triangle.
      for ( int j=0; j<2; j++ )
        {
          const int idx = v0_idx[j];
          vec3 p_e = mix( In[idx].vertex_e.xyz, p1_e.xyz, f );
          vec3 v_pc = ctr_e - p_e;
          // Compute the coordinate of this edge (leg) of the triangle
          // wrapped on the cylinder.
          vertex_e.xyz = ctr_e + vc - dot(v_pc,ax) * ax;
          vertex_e.w = 1;

          /// SOLUTION - Problem 2
          //
          // Write the remaining vertex attributes and emit the vertex.

          // Use the cylinder normal for the surface normal.
          normal_e = nc;

          use_tex = 1;
          tex_coor = mix( In[idx].tex_coor, tc1, f );
          color = In[0].color;
          gl_Position = gl_ProjectionMatrix * vertex_e;
          EmitVertex();
        }
    }

  /// SOLUTION - Problem 2
  EndPrimitive();
}

#endif


#ifdef _FRAGMENT_SHADER_

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

layout ( location = 0 ) in Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 tex_coor;
  flat vec4 color;

  // SOLUTION
  flat int use_tex;
};

layout ( location = 0 ) out vec4 frag_color;

void
fs_main_common()
{
  // Perform lighting, fetch and blend texture, then emit fragment.
  //

  // Get filtered texel.
  //
#ifdef BIND_TEXUNIT

  /// SOLUTION
  //
  //  Apply the texture only if use_tex == 1
  //
  vec4 texel = gl_FrontFacing && use_tex == 1
    ? texture(tex_unit_0,tex_coor) : vec4(1,1,1,1);
#else
  vec4 texel = vec4(1,1,1,1);
#endif

  vec4 color2 = gl_FrontFacing ? color : vec4(0.5,0,0,1);

  // Multiply filtered texel color with lighted color of fragment.
  //
  frag_color = texel * generic_lighting(vertex_e, color2, normal_e);
}


#endif