//##################################################################################################
//
//   Custom Visualization Core library
//   Copyright (C) 2011-2012 Ceetron AS
//    
//   This library is free software: you can redistribute it and/or modify 
//   it under the terms of the GNU General Public License as published by 
//   the Free Software Foundation, either version 3 of the License, or 
//   (at your option) any later version. 
//    
//   This library is distributed in the hope that it will be useful, but WITHOUT ANY 
//   WARRANTY; without even the implied warranty of MERCHANTABILITY or 
//   FITNESS FOR A PARTICULAR PURPOSE.   
//    
//   See the GNU General Public License at <<http://www.gnu.org/licenses/gpl.html>> 
//   for more details. 
//
//##################################################################################################

#include "cvfBase.h"
#include "cvfGaussianBlur.h"
#include "cvfTexture.h"
#include "cvfSampler.h"
#include "cvfFramebufferObject.h"
#include "cvfRenderbufferObject.h"
#include "cvfString.h"
#include "cvfSingleQuadRenderingGenerator.h"
#include "cvfShaderSourceProvider.h"
#include "cvfUniform.h"
#include "cvfRenderSequence.h"
#include "cvfCamera.h"

namespace cvf {



//==================================================================================================
///
/// \class cvf::GaussianBlur
/// \ingroup Viewing
///
/// Helper to create and manage two pass Gaussian blur of a texture.
///
/// The sigma value is the standard deviation for the Gaussian distribution. A higher sigma value 
/// means more blur, but note that the sigma value should be tuned to the kernel size.
/// The gaussian distribution will have the vast majority of values in the interval [-3*sigma, 3*sigma], 
/// so a kernel width of 6*sigma should suffice. Note that this guideline is very defensive and will
/// require a large kernel for significant blur. In practice, you can get away with a larger sigma value
/// for many of our applications. See more documentation in fs_GaussianBlur.glsl
///
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
GaussianBlur::GaussianBlur(Texture* textureToBlur, int kernelSize, double sigma)
:   m_textureToBlur(textureToBlur),
    m_kernelSize(kernelSize),
    m_sigma(sigma)
{
    CVF_ASSERT(m_textureToBlur.notNull());
    CVF_ASSERT(m_kernelSize >= 3);
    CVF_ASSERT(sigma > 0);

    createRenderings();
}



//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void GaussianBlur::createRenderings()
{
    CVF_ASSERT(m_textureToBlur.notNull());

    // Determine if we should be using texture rectangle or normal texture
    // If the input texture's type is texture rectangle, we'll use texture rectangle
    bool useTextureRect = (m_textureToBlur->textureType() == Texture::TEXTURE_RECTANGLE);

    m_tempColorTexture = new Texture(useTextureRect ? Texture::TEXTURE_RECTANGLE : Texture::TEXTURE_2D, Texture::RGBA);
    m_tempColorTexture->setSize(1, 1);

    String defineString = String("#define KERNEL_SIZE %1\n").arg(m_kernelSize);
    if (useTextureRect)
    {
        defineString += "#define USE_TEXTURE_RECT\n";
    }

    m_uniformSigma = new UniformFloat("u_sigma", static_cast<float>(m_sigma));

    ref<Sampler> sampler = new Sampler;
    sampler->setWrapMode(cvf::Sampler::CLAMP_TO_EDGE);
    sampler->setMinFilter(cvf::Sampler::NEAREST);
    sampler->setMagFilter(cvf::Sampler::NEAREST);

    // First blur pass (vertical)
    {
        ref<FramebufferObject> vertBlurFbo = new FramebufferObject;
        vertBlurFbo->attachColorTexture2d(0, m_tempColorTexture.p());

        SingleQuadRenderingGenerator quadRenderGen("VertBlurRendering");
        quadRenderGen.addTexture(m_textureToBlur.p(), sampler.p(), "u_blurSampler");
        quadRenderGen.addFragmentShaderCode(defineString);
        quadRenderGen.addFragmentShaderCode("#define VERTICAL_BLUR\n");
        quadRenderGen.addFragmentShaderCode(ShaderSourceProvider::instance()->getSourceFromRepository(ShaderSourceRepository::fs_GaussianBlur));
        quadRenderGen.setUniform(m_uniformSigma.p());

        if (!useTextureRect)
        {
            m_uniformVertBlurSize = new UniformFloat("u_blurSize", 1);
            quadRenderGen.setUniform(m_uniformVertBlurSize.p());
        }

        m_vertBlurRendering = quadRenderGen.generate();
        m_vertBlurRendering->setTargetFramebuffer(vertBlurFbo.p());
    }

    // Second blur pass (horizontal)
    {
        ref<FramebufferObject> horBlurFbo = new FramebufferObject;
        horBlurFbo->attachColorTexture2d(0, m_textureToBlur.p());

        SingleQuadRenderingGenerator quadRenderGen("HorBlurRendering");
        quadRenderGen.addTexture(m_tempColorTexture.p(), sampler.p(), "u_blurSampler");
        quadRenderGen.addFragmentShaderCode(defineString);
        quadRenderGen.addFragmentShaderCode("#define HORIZONTAL_BLUR\n");
        quadRenderGen.addFragmentShaderCode(ShaderSourceProvider::instance()->getSourceFromRepository(ShaderSourceRepository::fs_GaussianBlur));
        quadRenderGen.setUniform(m_uniformSigma.p());

        if (!useTextureRect)
        {
            m_uniformHorBlurSize = new UniformFloat("u_blurSize", 1);
            quadRenderGen.setUniform(m_uniformHorBlurSize.p());
        }

        m_horBlurRendering = quadRenderGen.generate();
        m_horBlurRendering->setTargetFramebuffer(horBlurFbo.p());
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void GaussianBlur::setSigma(double sigma)
{
    CVF_ASSERT(sigma > 0);
    m_sigma = sigma;

    CVF_ASSERT(m_uniformSigma.notNull());
    m_uniformSigma->set(static_cast<float>(sigma));
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void GaussianBlur::addRenderingsToSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);
    CVF_ASSERT(m_vertBlurRendering.notNull());
    CVF_ASSERT(m_horBlurRendering.notNull());
    renderSequence->addRendering(m_vertBlurRendering.p());
    renderSequence->addRendering(m_horBlurRendering.p());
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void GaussianBlur::removeRenderingsFromSequence(RenderSequence* renderSequence)
{
    CVF_ASSERT(renderSequence);
    if (m_vertBlurRendering.notNull()) renderSequence->removeRendering(m_vertBlurRendering.p());
    if (m_horBlurRendering.notNull())  renderSequence->removeRendering(m_horBlurRendering.p());
}


//--------------------------------------------------------------------------------------------------
/// Resizes the internal worker objects based on the size of the texture that is to be blurred
///
/// Must be called whenever the size of the texture changes
//--------------------------------------------------------------------------------------------------
void GaussianBlur::resizeFromTextureSize()
{
    CVF_ASSERT(m_textureToBlur.notNull());
    cvf::uint width = m_textureToBlur->width();
    cvf::uint height = m_textureToBlur->height();

    m_tempColorTexture->setSize(width, height);

    m_vertBlurRendering->camera()->setViewport(0, 0, width, height);
    m_horBlurRendering->camera()->setViewport(0, 0, width, height);

    if (m_uniformVertBlurSize.notNull() && m_uniformHorBlurSize.notNull())
    {
        // The size here should be the size of the texture that is read from in the pass
        m_uniformVertBlurSize->set(1.0f/static_cast<float>(height));
        m_uniformHorBlurSize->set(1.0f/static_cast<float>(width));
    }
}


}
