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


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

/// Background -- True Spheres
///

 /// True Sphere
 //
 //  A sphere rendered perfectly.
 //  (A tessellation using triangles is not perfect since the triangles
 //   approximate the sphere surface.)

 // Each sphere rendered using one (one!) vertex.
 //
 // Storage Buffers / Arrays
 //
 //  Each array has one element per sphere.
 //
 //  sphere_pos_rad.  A vec4.
 //    sphere_pos_rad[i].xyz
 //      Coordinates of center of sphere i.
 //    sphere_pos_rad[i].w
 //      Radius of sphere i.
 //
 //  sphere_rot. A mat4
 //    Orientation of sphere. (A rotation matrix.)
 //
 //  sphere_color. A vec4.
 //    Sphere color.

 /// Rendering Pass
 //
 //   Input primitive is point.
 //   There are no vertex shader inputs ..
 //   .. other than gl_VertexIndex which is automatically generated.
 //
 ///   Vertex Shader
 //
 //      Passes gl_VertexIndex to geometry shader.
 //      That's all the vertex shader does.
 //
 ///   Geometry Shader
 //
 //      Input: Point
 //      Output: Triangle Strip
 //
 //      Generates a rectangle (two triangles) around sphere.
 //      From eye, the rectangle perfectly frames the sphere.
 //      The rectangle is a square for spheres in the center of the window,
 //       and grow more rectangular closer to the window edges.
 //
 ///   Fragment Shader
 //
 //      Determines whether pixel is covered by sphere ..
 //      .. and if so, computes the coordinates of that point on the sphere.
 //      Fragment is dropped if pixel not covered by sphere ..
 //      .. otherwise determines normal and texture coordinates.

 /// Efficiency: True Sphere v. Tessellated Sphere
 //
 //  Operation Background
 //
 //    mad: 
 //      Multiply add.
 //      Performed by a FP32 unit (in older GPUs performed by CUDA core).
 //      "Cost" of one multiply or one add same as cost of one mad.
 //
 //    special:
 //      Operations using special functional unit.
 //      Include reciprocal, reciprocal square root. Used for division, sqrt.
 //      From CC 5.0 (Maxwell) to present (Ampere) ..
 //      .. cost is 4 times that of a mad.
 //
 //    trig:
 //      Operations requiring library code.
 //      Assume cost is 20 * mad.
 //
 //
 /// Tessellated Sphere
 //    Code in links-shdr.cc.
 //
 //    Rendered using an instanced rendering pass:
 //      One rendering pass renders n spheres.
 //      Vertex shader input is for vertex on a radius=1 sphere at origin.
 //      Instance ID used to retrieve actual radius, center, and rotation.
 //
 //    Vertex Shader Estimated Work per Vertex
 //      59 mad, 1 special.
 //      Cost: 63
 //
 //    Let s denote number of slices. Approximate: s^2 vertices in tessellation.
 //
 /// True Sphere
 //    Code in this file.
 //
 //    Fragment Shader Extra Work per Fragment
 //      Note: Extra work means work not done by tessellated sphere's fragment
 //            shader. 
 //      61 mad, 7 special, 2 trig
 //      Cost: 61 + 7*4 + 2*20 = 129
 //
 /// Comparison
 //
 //   Work FS True / Work VS Tess = 129 / 63 approx 2
 //
 //   Expect True Sphere better when:
 //     Tess Sphere  Frag / Vtx ratio < 0.5
 //

#ifdef BIND_SPHERE_POS
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[]; };
#endif

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

bool tryout1 = bool(com.tryout.x);
bool tryout2 = bool(com.tryout.y);

#endif


#ifdef _VERTEX_SHADER_

// Interface block for vertex shader output / geometry shader input.
//
layout ( location = 0 ) out Data_to_GS { int vertex_id; };

void
vs_main()
{
  vertex_id = gl_VertexIndex;
}

#endif


#ifdef _GEOMETRY_SHADER_

layout ( location = 0 ) in Data_to_GS { int vertex_id; } In[1];

layout ( location = 0 ) out Data_to_FS
{
  flat int vertex_id;
  vec3 vertex_o;
};

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

// Type of primitives emitted geometry shader output.
//
layout ( triangle_strip, max_vertices = 4 ) out;

void
gs_main()
{
  vertex_id = In[0].vertex_id;
  vec4 pos_rad = sphere_pos_rad[vertex_id];
  vec3 ctr_o = pos_rad.xyz;
  float r = pos_rad.w;

  // Eye location in object space.
  vec3 e_o = -transpose(mat3(gl_ModelViewMatrix)) * gl_ModelViewMatrix[3].xyz;

  // Vector from eye to sphere center.
  vec3 ec_o = ctr_o - e_o;  // Eye to Center (of sphere).

  // Vectors orthogonal to ec_o.
  //
  vec3 atr_o = abs(ec_o);
  int min_idx = atr_o.x < atr_o.y ? ( atr_o.x < atr_o.z ? 0 : 2 )
    : ( atr_o.y < atr_o.z ? 1 : 2 );
  vec3 ax_raw = min_idx == 0 ? vec3(0,-ec_o.z,ec_o.y)
    : min_idx == 1 ? vec3(-ec_o.z,0,ec_o.x) : vec3(-ec_o.y,ec_o.x,0);
  vec3 ax = normalize(ax_raw);
  vec3 ay = normalize(cross(ec_o,ax));

  // Compute center of the limb, lmb_o, the circle formed by the most
  // distant visible parts of the sphere surface.
  float sin_theta_sq = r * r / dot(ec_o,ec_o);

  // The following is equivalent to the simplified expression for lmb_o:
  //  vec3 lmb_o = ctr_o - sin_theta * r * ec_o / length(ec_o);
  vec3 lmb_o = ctr_o - sin_theta_sq * ec_o;

  // Compute axes to draw the limb.
  float b = r * sqrt( 1 - sin_theta_sq );
  vec3 vx = b * ax;
  vec3 vy = b * ay;

  mat4 mvp = gl_ModelViewProjectionMatrix;

  // Emit a bounding square for the limb.
  for ( int i = -1; i < 2; i += 2 )
    for ( int j = -1; j < 2; j += 2 )
      {
        vertex_id = In[0].vertex_id;
        vertex_o = lmb_o + vx * i + vy * j;
        gl_Position = mvp * vec4(vertex_o,1);
        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
{
  flat int vertex_id;
  vec3 vertex_o;
};

layout ( location = 0 ) out vec4 frag_color;


void
fs_main()
{
  vec4 pos_rad = sphere_pos_rad[vertex_id];

  // Center of sphere in original object-space coordinates.
  vec3 ctr_o = pos_rad.xyz;

  float rsq = pos_rad.w * pos_rad.w;   // mad: 1

  // Eye location in object-space coordinates.  // mad: 9  Or, just use uniform.
  vec3 e_o = -transpose(mat3(gl_ModelViewMatrix)) * gl_ModelViewMatrix[3].xyz;

  // Prepare to compute intersection of ray from eye to through fragment with
  // sphere. That intersection is the point on the sphere corresponding
  // to this fragment.
  //
  vec3 ef = vertex_o - e_o;
  float ef_d_ef = dot(ef,ef);  // mad: 3
  vec3 ce = e_o-ctr_o;
  float ce_d_ce = dot(ce,ce);  // mad: 3
  float ef_d_ce = dot(ef,ce);  // mad: 3
  float qfa = ef_d_ef;
  float qfb = 2 * ef_d_ce;     // mad: 1
  float qfc = ce_d_ce - rsq;
  float discr = qfb*qfb - 4 * qfa * qfc;  // mad: 3

  // If outside the limb, return.
  if ( discr < 0 ) discard;

  // Finish computing coordinate of point for this fragment.
  //
  float t = ( -qfb - sqrt( discr ) ) / ( 2 * qfa ); // mad: 1;  spc: 2
  vec4 sur_o = vec4(e_o + t * ef, 1);               // mad: 1

  {
    // Compute clip-space depth.
    vec4 sur_c = gl_ModelViewProjectionMatrix * sur_o; // mad: 4
    gl_FragDepth = sur_c.z / sur_c.w; // spc: 1
  }

  // Compute eye-space coordinate and vector of unreflected point.
  //
  vec3 normal_o = normalize(sur_o.xyz - ctr_o);           // mad: 3  spc: 1
  vec3 normal_e = normalize(gl_NormalMatrix * normal_o);  // mad: 12 spc: 1
  vec4 sur_e = gl_ModelViewMatrix * sur_o;                // mad: 16

  // Use sphere-local coordinates to compute texture coordinates.
  //
  vec3 sur_l = transpose(mat3(sphere_rot[vertex_id])) * normal_o;  // mad: 9
  float pi = 3.14159265359;
  float tpi = 2 * pi;
  float theta = atan(sur_l.x,sur_l.z);                     // trg: 1
  float eta = acos(sur_l.y);                               // trg: 1
  vec2 tcoord = vec2( ( 1.5 * pi + theta ) / tpi, eta / pi );  // spc: 2

  // Totals: mad: 69   spc: 7   trg: 2

  // Get filtered texel.
  //
  vec4 texel = tryout1 ? vec4(1,1,1,1) : texture(tex_unit_0,tcoord);

  vec4 color = sphere_color[vertex_id];
  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(sur_e, color2, normal_e);
}


#endif