/// LSU EE 4702-1 (Fall 2017), GPU Programming
//
 /// Homework 4 -- SOLUTION
 //

 /// Instructions
 //
 //  Read the assignment: http://www.ece.lsu.edu/koppel/gpup/2017/hw04.pdf
 //

// Specify version of OpenGL Shading Language.
//
#version 450 compatibility


layout ( binding = 1 ) buffer sr { mat4 sphere_rot[]; };
layout ( binding = 2 ) buffer spr { vec4 sphere_pos_rad[]; };
layout ( binding = 3 ) buffer sc { vec4 sphere_color[]; };

// Use this variable to debug your code. Press 'y' to toggle tryout.x
// and 'Y' to toggle debug_bool.y (between true and false).
//
layout ( location = 3 ) uniform bvec3 tryout;
layout ( location = 2 ) uniform int lighting_options;

layout ( location = 4 ) uniform int opt_spirals;

const vec4 color_lsu_spirit_purple =
  vec4(0x58 / 256.0, 0x0d/ 256.0, 0xa6/256.0, 1);

#ifdef _VERTEX_SHADER_

in vec2 gl_MultiTexCoord0;

// Interface block for vertex shader output / geometry shader input.
//
out Data_to_GS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
  vec4 gl_Position;

  vec4 pos_loc_theta;

  int instance_id;

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


void
vs_main_p0()
{
  /// Do not put solution here.  Use this file for comparison or minor experiments.
  //  Instead, make vertex shader changes to vs_main_p1.

  vec4 pos_rad = sphere_pos_rad[gl_InstanceID];

  vec3 sphere_center = pos_rad.xyz;
  float radius = pos_rad.w;

  // Sphere orientation. Transpose is needed to convert C ordering to Fortran.
  mat4 rot = transpose(sphere_rot[gl_InstanceID]);

  // Coordinate of slice in the sphere's local coordinate space.
  vec4 vtx_local = vec4(gl_Vertex.xyz,1);

  // Transform slice normal / local coordinate to an object-space
  // normal and coordinate
  //
  vec4 normr = rot * vtx_local;
  vec3 normal_o = normr.xyz;
  vec4 vertex_o = vec4( sphere_center + radius * normal_o, 1 );

  // Transform coordinate to clip and eye space, and transform normal to
  // eye space.
  //
  gl_Position = gl_ModelViewProjectionMatrix * vertex_o;
  vertex_e = gl_ModelViewMatrix * vertex_o;
  normal_e = normalize(gl_NormalMatrix * normal_o );

  // Compute texture coordinates using theta and local y coordinate.
  //
  float theta = gl_Vertex.w;
  float y_loc = gl_Vertex.y;
  float tex_s = -theta / ( 2 * 3.1415926535f );
  float tex_t = 0.5f - 0.5f * y_loc;
  gl_TexCoord[0] = vec2(tex_s,tex_t);

  // Pass instance id down pipeline.
  //
  instance_id = gl_InstanceID;
}

void
vs_main_p1()
{
  /// Vertex Shader for Homework 4 Solution
  //
  // Homework 4 solution goes here, and other places.
  /// SOLUTION
  //
  //  Move code that computes vertex coordinates to the geometry shader.
  //  Pass along vertex coordinates/theta and instance number.

  pos_loc_theta = gl_Vertex;
  instance_id = gl_InstanceID;
}

#endif


#ifdef _GEOMETRY_SHADER_

in Data_to_GS
{

  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
  vec4 gl_Position;

  /// SOLUTION
  //
  // The declarations above are not needed and will be optimized
  // out by the compiler.  They were not removed because they are
  // still needed by gs_main_p0, which shares this interface block.
  //
  // The declarations below are needed for the solution.

  vec4 pos_loc_theta;
  int instance_id;
} In[];

out Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
  flat int instance_id;
};

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

// Type of primitives emitted geometry shader output.
//
 /// Homework 4 solution goes here, and other places.
//
// SOLUTION - Increase max_vertices from 3 to 60.
//
layout ( triangle_strip, max_vertices = 60 ) out;


void
gs_main_p0()
{
  /// Do not put solution here.  Use this file for comparison or minor experiments.
  //  Instead, make geometry shader changes to gs_main_p1.

  // If true use sphere normal, otherwise use one normal for whole triangle.
  const bool opt_normal_sphere = tryout.z;

  for ( int i=0; i<3; i++ )
    {
      // Optionally use same normal for each vertex.
      normal_e = In[opt_normal_sphere?i:0].normal_e;

      // Pass along values unchanged.
      vertex_e = In[i].vertex_e;
      gl_Position = In[i].gl_Position;
      gl_TexCoord[0] = In[i].gl_TexCoord[0];
      instance_id = In[i].instance_id;
      EmitVertex();
    }
  EndPrimitive();
}

void
gs_main_p1()
{
  /// Geometry Shader for Homework 4 Solution 
  //
  //  Put Homework 4 solution here, and other places.

  // If true use sphere normal, otherwise use one normal for whole triangle.
  //
  const bool opt_normal_sphere = tryout.z;

  /// SOLUTION

  const float delta_theta = 2f * 3.1415926535f / opt_spirals;

  // Load rotation matrix. Note that all 3 vertices have same instance ID,
  // so we can use the instance ID of vertex 0, In[0].instance_id.
  //
  const mat4 rot4 = transpose(sphere_rot[In[0].instance_id]);

  // Make into a 3x3 since local coordinates won't have a w component.
  //
  mat3 rot = { rot4[0].xyz, rot4[1].xyz, rot4[2].xyz };

  const vec4 pos_rad = sphere_pos_rad[In[0].instance_id];

  // Extract object-space coordinate of center of sphere, and its radius.
  //
  const vec3 ctr_o = pos_rad.xyz;
  const float radius = pos_rad.w;

  // Compute a rotation matrix that will rotate by 2 π / opt_spirals
  // around the xz plane.
  //
  const float cosdtheta = cos(delta_theta);
  const float sindtheta = sin(delta_theta);
  const vec3 ax = vec3(cosdtheta,0,sindtheta);
  const vec3 az = vec3(-sindtheta,0,cosdtheta);
  const mat3 rotdt = mat3( ax, vec3(0,1,0), az );

  for ( int s=0; s<opt_spirals; s++ )
    {
      const float theta0 = s * delta_theta;

      for ( int i=0; i<3; i++ )
        {
          const vec3 pos_loc = In[i].pos_loc_theta.xyz;
          const float theta = In[i].pos_loc_theta.w + theta0;

          const float tex_s = -theta / ( 2 * 3.1415926535f );
          const float tex_t = 0.5f - 0.5f * pos_loc.y;
          gl_TexCoord[0] = vec2( tex_s, tex_t );

          const vec3 normal_o = rot * pos_loc;
          const vec4 vertex_o = vec4( ctr_o + radius * normal_o, 1 );

          gl_Position = gl_ModelViewProjectionMatrix * vertex_o;
          vertex_e = gl_ModelViewMatrix * vertex_o;

          if ( opt_normal_sphere || i == 0 )
            normal_e = normalize(gl_NormalMatrix * normal_o );

          instance_id = bool(s & 1) ? -1 : In[i].instance_id;

          EmitVertex();
        }

      EndPrimitive();

      // Rotate rotation matrix by delta_theta. With a decent compiler
      // this will take 8 multiply/adds because all but 4 elements of
      // rotdt are 0 or 1.
      //
      rot = rot * rotdt;
    }
}

#endif


#ifdef _FRAGMENT_SHADER_

uniform sampler2D tex_unit_0;

in Data_to_FS
{
  vec3 normal_e;
  vec4 vertex_e;
  vec2 gl_TexCoord[1];
  flat int instance_id;
};

vec4 generic_lighting(vec4 vertex_e, vec4 color, vec3 normal_e);


void
fs_main_p0()
{
  /// Do not put solution here. Use this for minor experiments.
  //
  //  Use fs_main_p1 for the Homework 4 solution.

  // Perform lighting, fetch and blend texture, then emit fragment.
  //

  // Get filtered texel.
  //
  vec4 texel = texture(tex_unit_0,gl_TexCoord[0]);

  vec4 color = sphere_color[instance_id];
  vec4 color2 = gl_FrontFacing ? color : vec4(0.5,0,0,1);

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

  // Copy fragment depth unmodified.
  //
  gl_FragDepth = gl_FragCoord.z;
}

void
fs_main_p1()
{
  /// Problem 1 Solution goes here and other places.

  // Perform lighting, fetch and blend texture, then emit fragment.
  //

  // Get filtered texel.
  //
  vec4 texel = texture(tex_unit_0,gl_TexCoord[0]);

  /// SOLUTION: Substitute purple.
  vec4 color =
    instance_id < 0 ? color_lsu_spirit_purple : sphere_color[instance_id];
  vec4 color2 = gl_FrontFacing ? color : vec4(0.5,0,0,1);

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

  // Copy fragment depth unmodified.
  //
  gl_FragDepth = gl_FragCoord.z;
}


///
/// Routine used by Either Vertex or Fragment Shader
///

vec4
generic_lighting(vec4 vertex_e, vec4 color, vec3 normal_er)
{
  // Return lighted color of vertex_e.
  //

  vec3 nspc_color = color.rgb * gl_LightModel.ambient.rgb;
  vec3 spec_color = vec3(0);
  vec3 normal_e = normalize(normal_er);

  for ( int i=0; i<2; i++ )
    {
      if ( ( lighting_options & ( 1 << i ) ) == 0 ) continue;
      vec4 light_pos = gl_LightSource[i].position;
      vec3 v_vtx_light = light_pos.xyz - vertex_e.xyz;
      float dist = length(v_vtx_light);
      float dist_vl_inv = 1.0 / dist;
      vec3 v_vtx_l_n = v_vtx_light * dist_vl_inv;

      float d_n_vl = dot(normal_e, v_vtx_l_n);
      float phase_light = max(0,gl_FrontFacing ? d_n_vl : -d_n_vl );

      vec3 ambient_light = gl_LightSource[i].ambient.rgb;
      vec3 diffuse_light = gl_LightSource[i].diffuse.rgb;
      float distsq = dist * dist;
      float atten_inv =
        gl_LightSource[i].constantAttenuation +
        gl_LightSource[i].linearAttenuation * dist +
        gl_LightSource[i].quadraticAttenuation * distsq;
      vec3 lighted_color =
        color.rgb
        * ( ambient_light + phase_light * diffuse_light ) / atten_inv;
      nspc_color += lighted_color;

      vec3 h = normalize( v_vtx_l_n - normalize(vertex_e.xyz) );

      spec_color +=
        pow(max(0.0,dot(normal_e,h)),16)
        * color.rgb
        * gl_LightSource[i].specular.rgb / atten_inv;
    }

  return vec4(nspc_color+spec_color,1);
}


#endif