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)
namespace oglplus {
class RippleTexProg
{
private:
    {
            ObjectDesc("Ripple texture vertex shader"),
            "#version 330\n"
            "void main(void)"
            "{"
            "   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);"
            "}"
        ));
            ObjectDesc("Ripple texture geometry shader"),
            "#version 330\n"
            "layout (points) in;"
            "layout (triangle_strip, max_vertices = 4) out;"
            "uniform int TexSize;"
            "out vec2 geomTexCoord;"
            "void make_vertex(vec2 pos, vec2 tc)"
            "{"
            "   gl_Position = vec4(pos, 0.0, 1.0);"
            "   geomTexCoord = tc;"
            "   EmitVertex();"
            "}"
            "void main(void)"
            "{"
            "   float rts = TexSize;"
            "   make_vertex(vec2(-1.0,-1.0), vec2(  0,   0));"
            "   make_vertex(vec2(-1.0, 1.0), vec2(  0, rts));"
            "   make_vertex(vec2( 1.0,-1.0), vec2(rts,   0));"
            "   make_vertex(vec2( 1.0, 1.0), vec2(rts, rts));"
            "   EndPrimitive();"
            "}"
        ));
            ObjectDesc("Ripple texture fragment shader"),
            "#version 330\n"
            "uniform ivec2 NewDrop;"
            "uniform sampler2D Tex1, Tex2;"
            "uniform int TexSize;"
            "in vec2 geomTexCoord;"
            "layout (location = 0) out vec4 fragBump;"
            "layout (location = 1) out float fragHeight;"
            "ivec2 wrap_tc(ivec2 tc)"
            "{"
            "   if(tc.x < 0) tc.x = TexSize-1;"
            "   if(tc.x >= TexSize) tc.x = 0;"
            "   if(tc.y < 0) tc.y = TexSize-1;"
            "   if(tc.y >= TexSize) tc.y = 0;"
            "   return tc;"
            "}"
            "float height_at(sampler2D tex, ivec2 tc, float factor)"
            "{"
            "   return texelFetch(tex, wrap_tc(tc), 0).r * factor;"
            "}"
            "void main(void)"
            "{"
            "   ivec2 TC = ivec2(geomTexCoord);"
            "   float  ch = height_at(Tex2, TC, 1.0);"
            "   float xp1 = height_at(Tex2, TC+ivec2( 1, 0), 0.25);"
            "   float xm1 = height_at(Tex2, TC+ivec2(-1, 0), 0.25);"
            "   float yp1 = height_at(Tex2, TC+ivec2( 0, 1), 0.25);"
            "   float ym1 = height_at(Tex2, TC+ivec2( 0,-1), 0.25);"
            "   fragHeight = xp1 + xm1 + yp1 + ym1;"
            "   fragHeight += height_at(Tex2, TC+ivec2( 1,-1), 0.25);"
            "   fragHeight += height_at(Tex2, TC+ivec2( 1, 1), 0.25);"
            "   fragHeight += height_at(Tex2, TC+ivec2(-1,-1), 0.25);"
            "   fragHeight += height_at(Tex2, TC+ivec2(-1, 1), 0.25);"
            "   fragHeight -= height_at(Tex1, TC, 1.0);"
            "   vec2 d = NewDrop - TC;"
            "   fragHeight += length(d)<8?1.0:0.0;"
            "   vec3 fragNormal = vec3("
            "       (xm1 - ch) + (ch - xp1),"
            "       (ym1 - ch) + (ch - yp1),"
            "       0.1"
            "   );"
            "   fragBump = vec4("
            "       normalize(fragNormal),"
            "       fragHeight"
            "   );"
            "}"
        ));
        prog.Link();
        prog.Use();
        return prog;
    }
    Program& 
self(void) { 
return *
this; }
 
public:
    ProgramUniformSampler tex_1, tex_2;
    ProgramUniform<Vector<GLint,2>> new_drop;
    RippleTexProg(GLuint ripple_tex_size)
     , tex_1(self(), "Tex1")
     , tex_2(self(), "Tex2")
     , new_drop(self(), "NewDrop")
    {
        ProgramUniform<GLint>(self(), "TexSize").Set(ripple_tex_size);
    }
};
class RippleTexHolder
{
private:
    const GLuint first_tex_unit;
    const GLuint nhm;
    Array<Texture> height_maps;
    GLuint curr;
public:
    RippleTexHolder(GLuint ripple_tex_size, GLuint tex_unit)
     : first_tex_unit(tex_unit)
     , nhm(3)
     , height_maps(nhm)
     , curr(0)
    {
        std::vector<GLfloat> v(ripple_tex_size*ripple_tex_size, 0.0f);
        for(GLuint i=0; i!=nhm; ++i)
        {
            Texture::Active(first_tex_unit+i);
            height_maps[i]
                << Texture::Target::_2D
                << images::ImageSpec(
                    ripple_tex_size,
                    ripple_tex_size,
                    v.data()
                );
        }
        Texture::Active(first_tex_unit+nhm);
        bump_map
            << Texture::Target::_2D
            << images::ImageSpec(
                ripple_tex_size,
                ripple_tex_size,
            );
    }
    void Swap(void)
    {
        ++curr;
    }
    GLuint TexUnit1(void) const
    {
        return first_tex_unit + (curr + 0) % nhm;
    }
    GLuint TexUnit2(void) const
    {
        return first_tex_unit + (curr + 1) % nhm;
    }
    Reference<Texture> CurrentHeightMap(void) const
    {
        return height_maps[(curr + 2) % nhm];
    }
    GLuint BumpMapUnit(void) const
    {
        return first_tex_unit + nhm;
    }
    Reference<Texture> BumpMap(void) const
    {
        return bump_map;
    }
};
class RippleGenerator
{
private:
    Context gl;
    GLuint size;
    RippleTexProg prog;
    RippleTexHolder holder;
public:
    RippleGenerator(GLuint first_tex_unit)
     : size(1024)
     , prog(size)
     , holder(size, first_tex_unit)
    {
        fbo.Bind(Framebuffer::Target::Draw);
        Framebuffer::AttachColorTexture(
            Framebuffer::Target::Draw,
            0,
            holder.BumpMap(),
            0
        );
        vao.Bind();
    }
    void Update(void)
    {
        gl.Viewport(size, size);
        fbo.Bind(Framebuffer::Target::Draw);
        Framebuffer::AttachColorTexture(
            Framebuffer::Target::Draw,
            1,
            holder.CurrentHeightMap(),
            0
        );
        };
        gl.DrawBuffers(draw_buffs);
        prog.Use();
        prog.tex_1.Set(holder.TexUnit1());
        prog.tex_2.Set(holder.TexUnit2());
        prog.new_drop.Set(std::rand()%size, std::rand()%size);
        vao.Bind();
        holder.Swap();
    }
    GLuint TexUnit(void) const
    {
        return holder.BumpMapUnit();
    }
};
template <typename ShapeBuilder>
class Shape
{
protected:
    ShapeBuilder make_shape;
    shapes::DrawingInstructions shape_instr;
    typename ShapeBuilder::IndexArray shape_indices;
    Context gl;
    const GLuint nva;
    Array<Buffer> vbos;
public:
    Shape(
const Program& prog, 
const ShapeBuilder& builder)
 
     : make_shape(builder)
     , shape_instr(make_shape.Instructions())
     , shape_indices(make_shape.Indices())
     , nva(4)
     , vbos(nva+1)
    {
        vao.Bind();
        typename ShapeBuilder::VertexAttribs vert_attr_info;
        const GLchar* vert_attr_name[] = {
            "Position",
            "Normal",
            "Tangent",
            "TexCoord"
        };
        for(GLuint va=0; va!=nva; ++va)
        {
            const GLchar* name = vert_attr_name[va];
            std::vector<GLfloat> data;
            auto getter = vert_attr_info.VertexAttribGetter(data, name);
            if(getter != nullptr)
            try
            {
                vbos[va].Bind(Buffer::Target::Array);
                GLuint npv = getter(make_shape, data);
                Buffer::Data(Buffer::Target::Array, data);
                VertexArrayAttrib attr(prog, name);
                attr.Setup<GLfloat>(npv);
                attr.Enable();
            }
            { }
        }
        vbos[nva].Bind(Buffer::Target::ElementArray);
        Buffer::Data(Buffer::Target::ElementArray, shape_indices);
        shape_indices.clear();
    }
    {
        vao.Bind();
        gl.FrontFace(make_shape.FaceWinding());
        shape_instr.Draw(shape_indices);
    }
};
class WaterProg
{
private:
    {
            ObjectDesc("Water vertex shader"),
            "#version 330\n"
            "uniform mat4 ProjectionMatrix, CameraMatrix;"
            "uniform vec3 LightPosition, CameraPosition;"
            "mat4 Matrix = ProjectionMatrix*CameraMatrix;"
            "in vec3 Position;"
            "in vec3 Normal;"
            "in vec3 Tangent;"
            "in vec2 TexCoord;"
            "out vec3 vertNormal, vertTangent, vertBitangent;"
            "out vec3 vertLightDir, vertViewDir;"
            "out vec2 vertTexCoord;"
            "void main(void)"
            "{"
            "   gl_Position = Matrix * vec4(Position, 1.0);"
            "   vertNormal = Normal;"
            "   vertTangent = Tangent;"
            "   vertBitangent = cross(Normal, Tangent);"
            "   vertLightDir = LightPosition - Position;"
            "   vertViewDir = CameraPosition - Position;"
            "   vertTexCoord = TexCoord * 16.0;"
            "}"
        ));
            ObjectDesc("Water fragment shader"),
            "#version 330\n"
            "uniform sampler2D RippleTex;"
            "uniform samplerCube EnvTex;"
            "in vec3 vertNormal, vertTangent, vertBitangent;"
            "in vec3 vertLightDir, vertViewDir;"
            "in vec2 vertTexCoord;"
            "layout (location = 0) out vec4 fragColor;"
            "const vec3 LightColor = vec3(1.0, 1.0, 0.95);"
            "const vec3 BgColor = vec3(0.4, 0.4, 0.4);"
            "void main(void)"
            "{"
            "   vec4 texBump = texture(RippleTex, vertTexCoord);"
            "   vec3 fragNormal = normalize("
            "       texBump.x * vertTangent+"
            "       texBump.y * vertBitangent+"
            "       texBump.z * vertNormal"
            "   );"
            "   vec3 fragLightDir = normalize(vertLightDir);"
            "   vec3 fragLightRefl = reflect(-fragLightDir, fragNormal);"
            "   vec3 fragViewDir = normalize(vertViewDir);"
            "   vec3 fragViewRefl = reflect(-fragViewDir, fragNormal);"
            "   vec3 fragViewRefr = refract("
            "       -normalize(fragViewDir+vec3(0.0, 1.0, 0.0)),"
            "       fragNormal,"
            "       1.2"
            "   );"
            "   float Diffuse = max(dot(fragNormal, fragLightDir), 0.0);"
            "   float Specular = clamp(pow(dot(fragViewDir, fragLightRefl)+0.1, 64.0), 0.0, 2.0);"
            "   float Transparency = max(dot(fragNormal, fragViewDir)+0.3, 0.0);"
            "   float Visibility = min(16.0 / dot(vertViewDir, vertViewDir), 1.0);"
            "   vec3 SkyColor = texture(EnvTex, fragViewRefl).rgb;"
            "   vec3 GroundColor = texture(EnvTex, fragViewRefr).rgb;"
            "   vec3 WaterColor = "
            "       mix("
            "           0.7*SkyColor,"
            "           0.8*GroundColor*LightColor*Diffuse,"
            "           Transparency"
            "       )+"
            "       0.2*LightColor*Diffuse+"
            "       0.05*LightColor*Specular;"
            "   fragColor = vec4("
            "       mix(BgColor, WaterColor, Visibility),"
            "       1.0"
            "   );"
            "}"
        ));
        prog.Link();
        prog.Use();
        return prog;
    }
public:
    ProgramUniform<Mat4f> projection_matrix, camera_matrix;
    ProgramUniform<Vec3f> camera_position, light_position;
    ProgramUniformSampler ripple_tex, env_tex;
    WaterProg(void)
     , projection_matrix(self(), "ProjectionMatrix")
     , camera_matrix(self(), "CameraMatrix")
     , camera_position(self(), "CameraPosition")
     , light_position(self(), "LightPosition")
     , ripple_tex(self(), "RippleTex")
     , env_tex(self(), "EnvTex")
    { }
};
class EnvMap
{
private:
    GLuint _tex_unit;
public:
    EnvMap(GLuint tex_unit)
     : _tex_unit(tex_unit)
    {
        Texture::Active(_tex_unit);
        *this   << Texture::Target::CubeMap
        Texture::CubeMapFace(0) <<
        Texture::CubeMapFace(1) <<
        Texture::CubeMapFace(2) <<
        Texture::CubeMapFace(3) <<
        Texture::CubeMapFace(4) <<
        Texture::CubeMapFace(5) <<
    }
    GLuint TexUnit(void) const
    {
        return _tex_unit;
    }
};
class RainExample : public Example
{
private:
    Context gl;
    GLuint width, height;
    EnvMap env_map;
    RippleGenerator ripples;
    WaterProg water_prog;
    Shape<shapes::Plane> water;
public:
    RainExample(void)
     : env_map(0)
     , ripples(1)
     , water_prog()
     , water(water_prog, shapes::Plane(
Vec3f(40,0,0), 
Vec3f(0,0,-40)))
 
    {
        water_prog.light_position.Set(2.0, 10.0, -4.0);
        water_prog.ripple_tex.Set(ripples.TexUnit());
        water_prog.env_tex.Set(env_map.TexUnit());
        gl.ClearColor(0.4f, 0.4f, 0.4f, 0.0f);
        gl.ClearDepth(1.0f);
    }
    void Reshape(GLuint vp_width, GLuint vp_height)
    {
        width = vp_width;
        height = vp_height;
        water_prog.projection_matrix.Set(
                Degrees(60),
                float(width)/float(height),
                1.0, 100.0
            )
        );
    }
    {
        ripples.Update();
        gl.Viewport(width, height);
        gl.Clear().ColorBuffer().DepthBuffer();
            Degrees(50 + 
SineWave(time / 21.0) * 35)
 
        );
        water_prog.Use();
        water_prog.camera_matrix.Set(camera);
        water_prog.camera_position.Set(camera.Position());
        water.Draw();
    }
    bool Continue(
const ExampleClock& clock)
 
    {
        return clock.RealTime().Minutes() < 2.0;
    }
    double HeatUpTime(void) const
    {
        return 0.0;
    }
    double ScreenshotTime(void) const
    {
        return 3.0;
    }
    double FrameTime(void) const
    {
        return 1.0/60.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 RainExample);
}
}