Copyright 2008-2014 Matus Chochlik. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <cmath>
namespace oglplus {
class WavesExample : public Example
{
private:
    shapes::Plane make_plane;
    shapes::DrawingInstructions plane_instr;
    Context gl;
    Lazy<Uniform<Mat4f>> camera_matrix;
    Lazy<Uniform<Vec3f>> camera_position;
    Lazy<Uniform<GLfloat>> anim_time;
    int prev_period;
public:
    WavesExample(void)
     : make_plane(
        Vec3f(  0.0f,   0.0f,   0.0f),
 
        Vec3f(100.0f,   0.0f,   0.0f),
 
        Vec3f(  0.0f,   0.0f,-100.0f),
 
        50, 50
    ), plane_instr(make_plane.Instructions(shapes::DrawMode::Patches()))
     , plane_indices(make_plane.Indices(shapes::DrawMode::Patches()))
     , camera_matrix(prog, "CameraMatrix")
     , camera_position(prog, "CameraPosition")
     , anim_time(prog, "Time")
     , prev_period(-1)
    {
        vs.Source(
            "#version 330\n"
            "uniform vec3 CameraPosition;"
            "in vec3 Position;"
            "out vec3 vertPosition;"
            "out float vertDistance;"
            "void main(void)"
            "{"
            "   vertPosition = Position;"
            "   vertDistance = distance(CameraPosition, Position);"
            "}"
        );
        prog.AttachShader(vs);
        cs.Source(
            "#version 330\n"
            "#extension ARB_tessellation_shader : enable\n"
            "layout(vertices = 3) out;"
            "in vec3 vertPosition[];"
            "in float vertDistance[];"
            "out vec3 tecoPosition[];"
            "int tessLevel(float dist)"
            "{"
            "   return clamp(int(150.0 / (dist+0.1)), 1, 10);"
            "}"
            "void main(void)"
            "{"
            "   tecoPosition[gl_InvocationID] ="
            "       vertPosition[gl_InvocationID];"
            "   if(gl_InvocationID == 0)"
            "   {"
            "       gl_TessLevelInner[0] = tessLevel(("
            "           vertDistance[0]+"
            "           vertDistance[1]+"
            "           vertDistance[2] "
            "       )*0.333);"
            "       gl_TessLevelOuter[0] = tessLevel(("
            "           vertDistance[1]+"
            "           vertDistance[2] "
            "       )*0.5);"
            "       gl_TessLevelOuter[1] = tessLevel(("
            "           vertDistance[2]+"
            "           vertDistance[0] "
            "       )*0.5);"
            "       gl_TessLevelOuter[2] = tessLevel(("
            "           vertDistance[0]+"
            "           vertDistance[1] "
            "       )*0.5);"
            "   }"
            "}"
        );
        prog.AttachShader(cs);
        es.Source(
            "#version 330\n"
            "#extension ARB_tessellation_shader : enable\n"
            "#define MaxWaves 5\n"
            "layout(triangles, equal_spacing, ccw) in;"
            "uniform mat4 ProjectionMatrix, CameraMatrix;"
            "uniform vec3 LightPosition;"
            "uniform vec3 CameraPosition;"
            "uniform float Time;"
            "uniform int WaveCount;"
            "uniform vec3 WaveDirections[MaxWaves];"
            "uniform vec3 WaveDimensions[MaxWaves];"
            "in vec3 tecoPosition[];"
            "out vec3 teevNormal;"
            "out vec3 teevLightDir;"
            "out vec3 teevViewDir;"
            "out float teevDistance;"
            "void main(void)"
            "{"
            "   const vec3 Up = vec3(0.0, 1.0, 0.0);"
            "   vec3 Position ="
            "       gl_TessCoord.x * tecoPosition[0]+"
            "       gl_TessCoord.y * tecoPosition[1]+"
            "       gl_TessCoord.z * tecoPosition[2];"
            "   vec3 Pos = Position;"
            "   vec3 Nml = Up;"
            "   for(int wave=0; wave!=WaveCount; ++wave)"
            "   {"
            "       vec3 Dir = WaveDirections[wave];"
            "       vec3 Dim = WaveDimensions[wave];"
            "       float Dist = dot(Position, Dir);"
            "       float u = Dim.y*sin(Dist/Dim.x + Time*Dim.z);"
            "       Pos += Up * u;"
            "       float w = (Dim.y/Dim.x)*cos(Dist/Dim.x + Time*Dim.z);"
            "       Nml -= Dir * w;"
            "       float d = -0.125*Dim.x*sin(2.0*Dist/Dim.x + Time*Dim.z);"
            "       Pos += Dir * d;"
            "   }"
            "   gl_Position = "
            "       ProjectionMatrix *"
            "       CameraMatrix *"
            "       vec4(Pos, 1.0);"
            "   teevNormal = normalize(Nml);"
            "   teevLightDir = normalize(LightPosition - Pos);"
            "   teevViewDir = normalize(CameraPosition - Pos);"
            "   teevDistance = distance(CameraPosition, Pos);"
            "}"
        );
        prog.AttachShader(es);
        fs.Source(
            "#version 330\n"
            "uniform samplerCube EnvMap;"
            "in vec3 teevNormal;"
            "in vec3 teevLightDir;"
            "in vec3 teevViewDir;"
            "in float teevDistance;"
            "out vec3 fragColor;"
            "void main(void)"
            "{"
            "   float Dim = clamp(30.0/teevDistance, 0.0, 1.0);"
            "   float LightRefl = dot(reflect(-teevLightDir, teevNormal), teevViewDir);"
            "   float LightHit = dot(teevNormal, teevLightDir);"
            "   float Diffuse = clamp(LightHit+0.1, 0.0, 1.0);"
            "   float Specular = pow(clamp(LightRefl, 0.0, 0.91), 32);"
            "   vec3 Environ=texture(EnvMap,reflect(-teevViewDir, teevNormal)).rgb;"
            "   vec3 WaterColor = vec3(0.4, 0.5, 0.5);"
            "   vec3 LightColor = vec3(1.0, 1.0, 1.0);"
            "   vec3 FogColor = vec3(0.9, 0.9, 0.9);"
            "   vec3 WaveColor ="
            "       LightColor*Specular+"
            "       WaterColor*Diffuse+"
            "       Environ*0.02;"
            "   fragColor = mix(WaveColor, FogColor, 1.0-Dim);"
            "}"
        );
        prog.AttachShader(fs);
        prog.Build();
        gl.Use(prog);
        gl.Bind(plane);
        gl.Bind(Buffer::Target::Array, verts);
        {
            std::vector<GLfloat> data;
            GLuint n_per_vertex = make_plane.Positions(data);
            Buffer::Data(Buffer::Target::Array, data);
            VertexArrayAttrib attr(prog, "Position");
            attr.Setup<GLfloat>(n_per_vertex);
            attr.Enable();
        }
        Uniform<Vec3f>(prog, 
"LightPosition").
Set(-100.0, 100.0, 20.0);
        Uniform<Vec3f> wave_directions(prog, "WaveDirections");
        Uniform<Vec3f> wave_dimensions(prog, "WaveDimensions");
        Uniform<GLint>(prog, 
"WaveCount").
Set(5);
        wave_directions[0] = Normalized(
Vec3f(1.0f, 0.0f, 1.0f));
        wave_dimensions[0] = 
Vec3f(5.0f, 1.5f, 1.2f);
        wave_directions[1] = Normalized(
Vec3f(1.0f, 0.0f, 0.5f));
        wave_dimensions[1] = 
Vec3f(4.0f, 0.8f, 1.2f);
        wave_directions[2] = Normalized(
Vec3f(1.0f, 0.0f, 0.1f));
        wave_dimensions[2] = 
Vec3f(2.0f, 0.5f, 2.4f);
        wave_directions[3] = Normalized(
Vec3f(1.0f, 0.0f,-0.1f));
        wave_dimensions[3] = 
Vec3f(1.5f, 0.2f, 3.7f);
        wave_directions[4] = Normalized(
Vec3f(1.0f, 0.0f, 0.4f));
        wave_dimensions[4] = 
Vec3f(1.1f, 0.2f, 4.7f);
        Texture::Active(0);
        {
            auto image = images::Squares(512, 512, 0.9f, 16, 16);
            gl.Bind(Texture::Target::CubeMap, env_map);
            for(int i=0; i!=6; ++i)
            gl.Current(Texture::Target::CubeMap)
                .GenerateMipmap();
        }
        UniformSampler(prog, "EnvMap").Set(0);
        gl.ClearColor(0.9f, 0.9f, 0.9f, 0.0f);
        gl.ClearDepth(1.0f);
    }
    void Reshape(GLuint width, GLuint height)
    {
        gl.Viewport(width, height);
        prog.Use();
        Uniform<Mat4f>(prog, 
"ProjectionMatrix").
Set(
                Degrees(75),
                double(width)/height,
                1, 150
            )
        );
    }
    {
        gl.Clear().ColorBuffer().DepthBuffer();
        int period = int(time * 0.125);
        if(prev_period < period)
        {
            if(period % 2)
            prev_period = period;
        }
            Degrees(45 - 
SineWave(time / 29.0f) * 35)
        );
        camera_matrix.Set(camera);
        camera_position.Set(camera.Position());
        anim_time.Set(time);
        plane_instr.Draw(plane_indices);
    }
    {
        return time < 90.0;
    }
};
void setupExample(ExampleParams& ){ }
std::unique_ptr<ExampleThread> makeExampleThread(
    Example& ,
    unsigned ,
    const ExampleParams& 
){ return std::unique_ptr<ExampleThread>(); }
std::unique_ptr<Example> makeExample(const ExampleParams& )
{
    return std::unique_ptr<Example>(new WavesExample);
}
}