void AmbientOcclusionManager::setTextureSize(AOTextureSize textureSize)
	{
		this->textureSize = textureSize;

		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::setNumSteps(unsigned int numSteps)
	{
		this->numSteps = numSteps;

		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
    void AmbientOcclusionManager::setDistanceAttenuation(float depthStartAttenuation, float depthEndAttenuation)
    {
        this->depthStartAttenuation = depthStartAttenuation;
        this->depthEndAttenuation = depthEndAttenuation;

		createOrUpdateAOShader();
    }
	void AmbientOcclusionManager::setKernelSamples(unsigned int kernelSamples)
	{
		this->kernelSamples = kernelSamples;

		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::onCameraProjectionUpdate(const Camera *const camera)
	{
		nearPlane = camera->getNearPlane();
		farPlane = camera->getFarPlane();
		projectionScale = camera->getProjectionMatrix()(1, 1);

		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::onResize(unsigned int width, unsigned int height)
	{
		sceneWidth = width;
		sceneHeight = height;
		
		createOrUpdateAOTexture();
		createOrUpdateAOShader();

		ShaderManager::instance()->bind(hbaoShader);
		Vector2<float> invResolution(1.0f/sceneWidth, 1.0f/sceneHeight);
		glUniform2fv(invResolutionLoc, 1, invResolution);
	}
	AmbientOcclusionManager::AmbientOcclusionManager(unsigned int depthTexID, unsigned int normalAndAmbientTexID) :
		sceneWidth(0),
		sceneHeight(0),
		nearPlane(0.0f),
		farPlane(0.0f),
		projectionScale(0.0f),

		textureSize(DEFAULT_TEXTURE_SIZE),
		textureSizeX(0),
		textureSizeY(0),
        kernelSamples(DEFAULT_KERNEL_SAMPLES),
		radius(DEFAULT_RADIUS),
		ambientOcclusionStrength(DEFAULT_AO_STRENGTH),
		depthStartAttenuation(DEFAULT_DEPTH_START_ATTENUATION),
		depthEndAttenuation(DEFAULT_DEPTH_END_ATTENUATION),
		noiseTextureSize(DEFAULT_NOISE_TEXTURE_SIZE),
		bias(DEFAULT_BIAS),
		blurSize(DEFAULT_BLUR_SIZE),
		blurSharpness(DEFAULT_BLUR_SHARPNESS),


		fboID(0),
		ambientOcclusionTexID(0),

		ambientOcclusionShader(0),
		mInverseViewProjectionLoc(0),
        mProjectionLoc(0),
		mViewLoc(0),
        resolutionLoc(0),
		noiseTexId(0),

		depthTexID(depthTexID),
		normalAndAmbientTexID(normalAndAmbientTexID),
		ambientOcclusionTexLoc(0),
		verticalBlurFilter(nullptr),
		horizontalBlurFilter(nullptr),
		isBlurActivated(true)
	{
		glBindTexture(GL_TEXTURE_2D, depthTexID);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindTexture(GL_TEXTURE_2D, normalAndAmbientTexID);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		quadDisplayer = std::make_unique<QuadDisplayerBuilder>()->build();

		//frame buffer object
		glGenFramebuffers(1, &fboID);
		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::onResize(unsigned int width, unsigned int height)
	{
		sceneWidth = width;
		sceneHeight = height;
		
		createOrUpdateAOTexture();
		createOrUpdateAOShader();

		ShaderManager::instance()->bind(ambientOcclusionShader);
        Vector2<float> resolution(sceneWidth, sceneHeight);
        glUniform2fv(resolutionLoc, 1, (const float *)resolution);
	}
	AmbientOcclusionManager::AmbientOcclusionManager(unsigned int depthTexID, unsigned int normalAndAmbientTexID) :
		sceneWidth(0),
		sceneHeight(0),
		nearPlane(0.0f),
		farPlane(0.0f),
		projectionScale(0.0f),

		textureSize((AOTextureSize)DEFAULT_TEXTURE_SIZE),
		textureSizeX(0),
		textureSizeY(0),
		numDirections(DEFAULT_NUM_DIRECTIONS),
		numSteps(DEFAULT_NUM_STEPS),
		radius(DEFAULT_RADIUS),
		ambientOcclusionExponent(DEFAULT_AO_EXPONENT),
		biasAngleInDegree(DEFAULT_BIAS_ANGLE_IN_DEGREE),
		blurSize(DEFAULT_BLUR_SIZE),
		blurSharpness(DEFAULT_BLUR_SHARPNESS),
		randomTextureSize(RANDOM_TEXTURE_SIZE),

		fboID(0),
		ambientOcclusionTexID(0),

		hbaoShader(0),
		mInverseViewProjectionLoc(0),
		cameraPlanesLoc(0),
		invResolutionLoc(0),
		nearPlaneScreenRadiusLoc(0),
		randomTexID(0),

		depthTexID(depthTexID),
		normalAndAmbientTexID(normalAndAmbientTexID),
		ambienOcclusionTexLoc(0),
		verticalBlurFilter(nullptr),
		horizontalBlurFilter(nullptr),
		isBlurActivated(true)
	{
		glBindTexture(GL_TEXTURE_2D, depthTexID);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindTexture(GL_TEXTURE_2D, normalAndAmbientTexID);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		quadDisplayer = std::make_unique<QuadDisplayerBuilder>()->build();

		//frame buffer object
		glGenFramebuffers(1, &fboID);
		createOrUpdateAOTexture();
		createOrUpdateAOShader();
	}
	/**
	 * @param biasAngle Bias angle in degree. If angle between two faces is > (180 degree - 2*bias angle): faces will not produce occlusion on each other.
	 * A value of 0 degree will produce maximum of occlusion. A value of 90 degrees won't produce occlusion.
	 * This bias angle allows to eliminate some unexpected artifacts especially when camera is near to a surface.
	 */
	void AmbientOcclusionManager::setBiasAngleInDegree(float biasAngleInDegree)
	{
		this->biasAngleInDegree = biasAngleInDegree;

		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::setAmbientOcclusionExponent(float ambientOcclusionExponent)
	{
		this->ambientOcclusionExponent = ambientOcclusionExponent;

		createOrUpdateAOShader();
	}
	/**
	 * @param radius Scope radius in units.
	 * Example: if 1.0f represents one meter, a radius of 0.5f will represent of radius of 50 centimeters.
	 */
	void AmbientOcclusionManager::setRadius(float radius)
	{
		this->radius = radius;

		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::setBias(float bias)
	{
		this->bias = bias;

		createOrUpdateAOShader();
	}
    void AmbientOcclusionManager::setNoiseTextureSize(unsigned int noiseTextureSize)
    {
	    this->noiseTextureSize = noiseTextureSize;

		createOrUpdateAOShader();
	}
	void AmbientOcclusionManager::setAmbientOcclusionStrength(float ambientOcclusionStrength)
	{
		this->ambientOcclusionStrength = ambientOcclusionStrength;

		createOrUpdateAOShader();
	}