/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// GLSL shaders for particle rendering
#define STRINGIFY(A) #A

// particle vertex shader
const char *particleVS = STRINGIFY(
  uniform float pointRadius;  // point size in world space         \n
  uniform float pointScale;   // scale to calculate size in pixels \n
  uniform vec4 eyePos;                                             \n
  void main()                                                      \n
  {
    \n
    vec4 wpos = vec4(gl_Vertex.xyz, 1.0);                          \n
    gl_Position = gl_ModelViewProjectionMatrix *wpos;              \n

    // calculate window-space point size                           \n
    vec4 eyeSpacePos = gl_ModelViewMatrix *wpos;                   \n
    float dist = length(eyeSpacePos.xyz);                          \n
    gl_PointSize = pointRadius * (pointScale / dist);              \n

    gl_TexCoord[0] = gl_MultiTexCoord0; // sprite texcoord         \n
    gl_TexCoord[1] = eyeSpacePos;                                  \n

    gl_FrontColor = gl_Color;                                      \n
  }                                                                \n
  );

// motion blur shaders
const char *mblurVS = STRINGIFY(uniform float timestep;                                    \n void
                                              main()                                                \n {
                                        \n vec3 pos  = gl_Vertex.xyz;
                                        \n vec3 vel  = gl_MultiTexCoord0.xyz;
                                        \n vec3 pos2 = (pos - vel * timestep).xyz; // previous position \n

                                        gl_Position = gl_ModelViewMatrix * vec4(pos, 1.0);
                                        \n // eye space
                                            gl_TexCoord[0] = gl_ModelViewMatrix * vec4(pos2, 1.0);
                                        \n

                                            // aging                                                 \n
                                            float lifetime = gl_MultiTexCoord0.w;
                                        \n float  age      = gl_Vertex.w;
                                        \n float  phase    = (lifetime > 0.0) ? (age / lifetime) : 1.0;
                                        \n // [0, 1]

                                            gl_TexCoord[1]
                                                .x    = phase;
                                        \n float fade = 1.0 - phase;
                                        \n
                                            //  float fade = 1.0;                                        \n

                                            //    gl_FrontColor = gl_Color;                              \n
                                            gl_FrontColor = vec4(gl_Color.xyz, gl_Color.w * fade);
                                        \n
                                    }                                                          \n);

// motion blur geometry shader
// - outputs stretched quad between previous and current positions
const char *mblurGS = "#version 120\n"
                      "#extension GL_EXT_geometry_shader4 : enable\n" STRINGIFY(
                          uniform float pointRadius; // point size in world space       \n
                          void          main()                                                    \n {
                              \n
                                  // aging                                                   \n
                                  float phase  = gl_TexCoordIn[0][1].x;
                              \n float  radius = pointRadius;
                              \n

                                  // eye space                                               \n
                                  vec3 pos    = gl_PositionIn[0].xyz;
                              \n vec3  pos2   = gl_TexCoordIn[0][0].xyz;
                              \n vec3  motion = pos - pos2;
                              \n vec3  dir    = normalize(motion);
                              \n float len    = length(motion);
                              \n

                                  vec3 x      = dir * radius;
                              \n vec3  view   = normalize(-pos);
                              \n vec3  y      = normalize(cross(dir, view)) * radius;
                              \n float facing = dot(view, dir);
                              \n

                                  // check for very small motion to avoid jitter             \n
                                  float threshold = 0.01;
                              \n

                                  if ((len < threshold) || (facing > 0.95) || (facing < -0.95))
                              {
                                  \n pos2 = pos;
                                  \n x    = vec3(radius, 0.0, 0.0);
                                  \n y    = vec3(0.0, -radius, 0.0);
                                  \n
                              }
                              \n

                                  // output quad                                             \n
                                  gl_FrontColor  = gl_FrontColorIn[0];
                              \n  gl_TexCoord[0] = vec4(0, 0, 0, phase);
                              \n  gl_TexCoord[1] = gl_PositionIn[0];
                              \n  gl_Position    = gl_ProjectionMatrix * vec4(pos + x + y, 1);
                              \n  EmitVertex();
                              \n

                                  gl_TexCoord[0] = vec4(0, 1, 0, phase);
                              \n  gl_TexCoord[1] = gl_PositionIn[0];
                              \n  gl_Position    = gl_ProjectionMatrix * vec4(pos + x - y, 1);
                              \n  EmitVertex();
                              \n

                                  gl_TexCoord[0] = vec4(1, 0, 0, phase);
                              \n  gl_TexCoord[1] = gl_PositionIn[0];
                              \n  gl_Position    = gl_ProjectionMatrix * vec4(pos2 - x + y, 1);
                              \n  EmitVertex();
                              \n

                                  gl_TexCoord[0] = vec4(1, 1, 0, phase);
                              \n  gl_TexCoord[1] = gl_PositionIn[0];
                              \n  gl_Position    = gl_ProjectionMatrix * vec4(pos2 - x - y, 1);
                              \n  EmitVertex();
                              \n
                          }                                                            \n);


const char *simplePS = STRINGIFY(void main()                                                    \n {
    \n gl_FragColor = gl_Color;
    \n
}                                                              \n);

// render particle without shadows
const char *particlePS = STRINGIFY(uniform float pointRadius;                                         \n void
                                                 main()                                                        \n {
                                           \n
                                               // calculate eye-space sphere normal from texture coordinates  \n
                                               vec3 N;
                                           \n       N.xy = gl_TexCoord[0].xy * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
                                           \n float r2 = dot(N.xy, N.xy);
                                           \n

                                               if (r2 > 1.0) discard; // kill pixels outside circle         \n
                                           N.z = sqrt(1.0 - r2);
                                           \n

                                               //  float alpha = saturate(1.0 - r2);                              \n
                                               float alpha = clamp((1.0 - r2), 0.0, 1.0);
                                           \n        alpha *= gl_Color.w;
                                           \n

                                               gl_FragColor = vec4(gl_Color.xyz * alpha, alpha);
                                           \n
                                       }                                                                  \n);

// render particle including shadows
const char *particleShadowPS = STRINGIFY(
    uniform float pointRadius;                                         \n uniform sampler2D shadowTex;                                       \n uniform sampler2D depthTex;                                        \n void
        main()                                                        \n {
            \n
                // calculate eye-space sphere normal from texture coordinates  \n
                vec3 N;
            \n       N.xy = gl_TexCoord[0].xy * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
            \n float r2   = dot(N.xy, N.xy);
            \n

                if (r2 > 1.0) discard;
            \n // kill pixels outside circle
                    N.z               = sqrt(1.0 - r2);
            \n vec4 eyeSpacePos       = gl_TexCoord[1];
            \n vec4 eyeSpaceSpherePos = vec4(eyeSpacePos.xyz + N * pointRadius, 1.0);
            \n // point on sphere
                vec4 shadowPos = gl_TextureMatrix[0] * eyeSpaceSpherePos;
            \n vec3  shadow    = vec3(1.0) - texture2DProj(shadowTex, shadowPos.xyw).xyz;
            \n
                //  float alpha = saturate(1.0 - r2);                              \n
                float alpha = clamp((1.0 - r2), 0.0, 1.0);
            \n        alpha *= gl_Color.w;
            \n

                gl_FragColor = vec4(gl_Color.xyz * shadow * alpha, alpha);
            \n // premul alpha
        });

// render particle as lit sphere
const char *particleSpherePS = STRINGIFY(
    uniform float pointRadius;                                         \n uniform vec3 lightDir = vec3(0.577, 0.577, 0.577);                 \n void
        main()                                                        \n {
            \n
                // calculate eye-space sphere normal from texture coordinates  \n
                vec3 N;
            \n       N.xy = gl_TexCoord[0].xy * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
            \n float r2   = dot(N.xy, N.xy);
            \n

                if (r2 > 1.0) discard; // kill pixels outside circle         \n
            N.z = sqrt(1.0 - r2);
            \n

                // calculate depth                                             \n
                vec4 eyeSpacePos =
                    vec4(gl_TexCoord[1].xyz + N * pointRadius, 1.0); // position of this pixel on sphere in eye space \n
            vec4 clipSpacePos = gl_ProjectionMatrix * eyeSpacePos;
            \n   gl_FragDepth = (clipSpacePos.z / clipSpacePos.w) * 0.5 + 0.5;
            \n

                float diffuse = max(0.0, dot(N, lightDir));
            \n

                gl_FragColor = diffuse * gl_Color;
            \n
        }                                                                  \n);

const char *passThruVS = STRINGIFY(void main()                                                        \n {
    \n gl_Position    = gl_Vertex;
    \n gl_TexCoord[0] = gl_MultiTexCoord0;
    \n gl_FrontColor  = gl_Color;
    \n
}                                                                  \n);

const char *texture2DPS = STRINGIFY(uniform sampler2D tex;                                             \n void
                                                      main()                                                        \n {
                                            \n gl_FragColor = texture2D(tex, gl_TexCoord[0].xy);
                                            \n
                                        }                                                                  \n);

// 4 tap 3x3 gaussian blur
const char *blurPS = STRINGIFY(
    uniform sampler2D tex;                                    \n uniform vec2 texelSize;                                   \n uniform float blurRadius;                                 \n void
        main()                                               \n {
            \n vec4 c;
            \n      c = texture2D(tex, gl_TexCoord[0].xy + vec2(-0.5, -0.5) * texelSize * blurRadius);
            \n      c += texture2D(tex, gl_TexCoord[0].xy + vec2(0.5, -0.5) * texelSize * blurRadius);
            \n      c += texture2D(tex, gl_TexCoord[0].xy + vec2(0.5, 0.5) * texelSize * blurRadius);
            \n      c += texture2D(tex, gl_TexCoord[0].xy + vec2(-0.5, 0.5) * texelSize * blurRadius);
            \n      c *= 0.25;
            \n

                gl_FragColor = c;
            \n
        }                                                  \n);

// floor shader
const char *floorVS = STRINGIFY(
  varying vec4 vertexPosEye;  // vertex position in eye space  \n
  varying vec3 normalEye;                                      \n
  void main()                                                  \n
  {
    \n
    gl_Position = gl_ModelViewProjectionMatrix *gl_Vertex;  \n
    gl_TexCoord[0] = gl_MultiTexCoord0;                      \n
    vertexPosEye = gl_ModelViewMatrix *gl_Vertex;           \n
    normalEye = gl_NormalMatrix *gl_Normal;                 \n
    gl_FrontColor = gl_Color;                                \n
  }                                                            \n
  );

const char *floorPS = STRINGIFY(
  uniform vec3 lightPosEye; // light position in eye space           \n
  uniform vec3 lightColor;                                           \n
  uniform sampler2D tex;                                             \n
  uniform sampler2D shadowTex;                                       \n
  varying vec4 vertexPosEye;  // vertex position in eye space        \n
  varying vec3 normalEye;                                            \n
  void main()                                                        \n
  {
    \n
    vec4 shadowPos = gl_TextureMatrix[0] * vertexPosEye;                    \n
    vec4 colorMap  = texture2D(tex, gl_TexCoord[0].xy);                     \n

    vec3 N = normalize(normalEye);                                          \n
    vec3 L = normalize(lightPosEye - vertexPosEye.xyz);                     \n
    float diffuse = max(0.0, dot(N, L));                                    \n

    vec3 shadow = vec3(1.0) - texture2DProj(shadowTex, shadowPos.xyw).xyz;  \n

    if (shadowPos.w < 0.0) shadow = lightColor;   \n // avoid back projections
    gl_FragColor = vec4(gl_Color.xyz *colorMap.xyz *diffuse * shadow, 1.0); \n
  }                                                                         \n
);
