vec3 calculateBaseHorizonVector(
	vec3 Po,
	vec3 Td,
	vec3 L,
	vec3 N,
	float LdotN
) {
	vec3 negPoLd = Td - Po;
	float D = -dot(negPoLd, N) / LdotN;
	return fNormalize(D * L + negPoLd);
}

float calculateHorizonAngle(
	vec3 position,
	vec2 screencoord,
	vec3 horizondir,
	vec3 viewdir,
	vec3 normal,
	float NdotV,
	float sampleoffset,
    float radius
) {
	vec3 horizonvector = calculateBaseHorizonVector(position, horizondir, viewdir, normal, NdotV);
	float coshorizonangle = clamp(dot(horizonvector, viewdir), -1.0, 1.0);

	for (int i = 0; i < HBAO_ANGLE_SAMPLES; ++i) {
		float sampledistance2D = pow(float(i) / float(HBAO_ANGLE_SAMPLES) + sampleoffset, 2.0);
		vec2 samplecoord = horizondir.xy * sampledistance2D + screencoord;

		if (clamp(samplecoord, 0.0, 1.0) != samplecoord) { break; }

		vec3 samplepos   = vec3(samplecoord, texture(depthtex0, samplecoord).r);
		     samplepos.z = 1e-3 * (1.0 - samplepos.z) + samplepos.z;

		samplepos = screenSpaceToViewSpace(samplepos, gbufferProjectionInverse);

		vec3  samplevector          = samplepos - position;
		float sampledistancesquared = dot(samplevector, samplevector);
		vec3  sampledir       = samplevector * inversesqrt(sampledistancesquared);

		if (sampledistancesquared > radius * radius) { continue; }

		float cossampleangle = dot(viewdir, sampledir);


		coshorizonangle = clamp(cossampleangle, coshorizonangle, 1.0);
	}

	return acos(coshorizonangle);
}

float calculateHBAO(vec3 position, vec2 screencoord, vec3 viewdir, vec3 normal, float NdotV, float radius, float dither, const float ditherSize) {
	dither = ditherSize * dither + 0.5;

	vec2 norm = vec2(gbufferProjection[0].x, gbufferProjection[1].y) * ((-0.5 * radius) / position.z);

	float result = 0.0;
	for (int i = 0; i < HBAO_HORIZON_DIRECTIONS; ++i) {
		float theta = (i * ditherSize + dither) * goldenAngle;
		vec3 horizondir     = vec3(abs(sin(theta)), cos(theta), 0.0);
		     horizondir.xy *= norm;

		float sampleoffset = (i + dither / ditherSize) / (HBAO_ANGLE_SAMPLES * HBAO_HORIZON_DIRECTIONS);
		result += calculateHorizonAngle(position, screencoord,  horizondir, viewdir, normal, NdotV, sampleoffset, radius);
		result += calculateHorizonAngle(position, screencoord, -horizondir, viewdir, normal, NdotV, sampleoffset, radius);
	}

	return result / (HBAO_HORIZON_DIRECTIONS);
}

float calculateHBAO() {
    const float ditherRadius = 16.0*16.0;
    float dither = fract(frameCounter * (1.0 / 15.0) + bayer8(gl_FragCoord.st));
    return calculateHBAO(pd.viewPosition[0], textureCoordinate, -fNormalize(pd.viewPosition[0]), sd.viewNormals, dot(sd.viewNormals, -fNormalize(pd.viewPosition[0])), HBAO_RADIUS, dither, ditherRadius) / pi;
}