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

 /// SOLUTION -- Homework 3


// 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_hw03_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;
  float sphere_radius;

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

};

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];
  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;
  float sphere_radius;
} In[];

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

};

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

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


const int slices = 5;
const int max_v = 6 + ( 2 + 4 ) * (slices+1) + 2;
layout ( triangle_strip, max_vertices = max_v ) out;


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

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

  /// SOLUTION -- Problem 1, Constant Volume Constraint
  //

  // Compute a distance between the original triangle and the shrunken
  // one.
  //
  float ht0 = In[0].sphere_radius * 0.1;
  //
  // This distance, ht0, will be used to compute the constant volume.
  // That volume is computed using height ht0 and the triangle at its
  // original size.
  //
  // Let
  //   l0 denote the length of a side of the base triangle.
  //   l1 denote the length of a side of the shrunken triangle.
  //   y  ∋ [0,1] denote the normalized distance from the base triangle
  //        to the shrunken triangle (top of the frustum). Here
  //        y=0 corresponds to a point on the base, y=1 corresponds
  //        to a point on the top.
  //   h  denote the (unormalized) height of the frustum.
  //   c  denote the value of opt_hw03_curl.
  //
  // As a simplification, the volume of a frustum with a square
  // base will be computed.
  //
  // ∫ y=0, y=1   ( (1-y)l0 + yl1 )^2 h dy
  //
  //  To simplify let l1 = (1-c)l0 and l0 = 1
  // y=0, y=1   ( (1-y) + y(1-c) )^2 h dy
  // y=0, y=1   ( 1-y + y - yc )^2 h dy
  // y=0, y=1   ( 1 - yc )^2 h dy
  // y=0, y=1   ( 1 + y^2c^2 -2yc ) h dy
  // y=0, y=1   ( y + y^3c^2/3 -y^2c ) h
  // ( 1 + c^2/3 - c ) h
  //
  // Next solve for h in (1+c^2/3-c) h == l0^2 h0 :
  // h = h0 / ( 1 + c^2/3  -c )

  float ht = ht0 / ( 1 + opt_hw03_curl * ( opt_hw03_curl / 3 - 1 ) );



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

  vec3 vtx_shr_e[3];

  // 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_hw03_curl );

      // Interpolate vertex coordinate.
      //
      vtx_shr_e[i] = vertex_e.xyz =
        mix( In[i].vertex_e.xyz, ctr_e, opt_hw03_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();

  /// SOLUTION -- Emit the Sides
  //
  vec4 gr_color = vec4( 0.5 * In[0].color.rgb, 1 );

  for ( int i=0; i<3; i++ )
    {
      int i1 = (i+1) % 3;
      vec3 vo_0 = In[i].vertex_e.xyz;
      vec3 vo_1 = In[i1].vertex_e.xyz;
      vec3 vs_0 = vtx_shr_e[i];
      vec3 vs_1 = vtx_shr_e[i1];
      vec3 n_e = normalize(cross( vs_0 - vo_0, vs_1 - vs_0 ));

      vec3 vstrip[] = { vs_0, vo_0, vs_1, vo_1 };
      int tstrip[] =  { i,    i,    i1,   i1   };

      for ( int j=0; j<4; j++ )
        {
          vertex_e = vec4( vstrip[j], 1 );
          normal_e = n_e;
          color = gr_color;
          use_tex = 1;
          tex_coor = In[tstrip[j]].tex_coor;
          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_hw03_curl;
  const float f0 = 1 - p_frac;

  // 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 = mix( In[idx].normal_e, n1_e, f );
          vertex_e = mix( In[idx].vertex_e, p1_e, f );
          use_tex = i < 1 ? 1 : 0;
          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 the part 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;

  vec3 peel_v[2][slices+1];
  vec3 peel_n[slices+1];

  // 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;
      peel_n[i] = 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;
          peel_v[j][i] = vertex_e.xyz;

          // 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();
        }
    }
  EndPrimitive();

  /// SOLUTION -- To Unassigned Problem 2

  // Iterate over parts of the peeled triangle. The peeling starts at i=0.

  float th = In[0].sphere_radius * .01f;

  for ( int j=0; j<2; j++ )
    {
      for ( int i=0; i<=slices; i++ )
        {
          int i1 = i < slices ? i+1 : slices-1;
          vec3 v0 = peel_v[j][i];
          vec3 n0 = peel_n[i];
          vec3 v0l = v0 + n0 * th;
          vec3 v1 = peel_v[j][i1];
          vec3 n1 = peel_n[i];
          vec3 v1l = v1 + n1 * th;

          vec3 sn = normalize(cross( v1l - v0l, v0 - v0l ));
          if ( i == slices ) sn = -sn;

          vertex_e = vec4(j==1?v0:v0l,1);
          normal_e = sn;
          use_tex = 0;
          color = vec4(0.2,0.2,0.2,1);
          gl_Position = gl_ProjectionMatrix * vertex_e;
          EmitVertex();

          vertex_e = vec4(j==1?v0l:v0,1);
          normal_e = sn;
          use_tex = 0;
          color = vec4(0.2,0.2,0.2,1);
          gl_Position = gl_ProjectionMatrix * vertex_e;
          EmitVertex();
        }
      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;
  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

  //  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 = true || 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