
/* Infinite grid
 * Author: Clément Foucault */

out vec4 FragColor;

uniform mat4 ProjectionMatrix;
uniform vec3 cameraPos;
uniform vec3 planeNormal;
uniform vec3 planeAxes;
uniform vec3 eye;
uniform vec4 gridSettings;
uniform vec2 viewportSize;
uniform vec4 screenvecs[3];
uniform float gridOneOverLogSubdiv;
uniform sampler2D depthBuffer;

#define gridDistance      gridSettings.x
#define gridResolution    gridSettings.y
#define gridScale         gridSettings.z
#define gridSubdiv        gridSettings.w

uniform int gridFlag;

#define AXIS_X    (1 << 0)
#define AXIS_Y    (1 << 1)
#define AXIS_Z    (1 << 2)
#define GRID      (1 << 3)
#define PLANE_XY  (1 << 4)
#define PLANE_XZ  (1 << 5)
#define PLANE_YZ  (1 << 6)
#define GRID_BACK (1 << 9) /* grid is behind objects */

#define GRID_LINE_SMOOTH 1.15

float get_grid(vec2 co, vec2 fwidthCos, float grid_size)
{
	float half_size = grid_size / 2.0;
	/* triangular wave pattern, amplitude is [0, half_size] */
	vec2 grid_domain = abs(mod(co + half_size, grid_size) - half_size);
	/* modulate by the absolute rate of change of the coordinates
	 * (make lines have the same width under perspective) */
	grid_domain /= fwidthCos;

	/* collapse waves and normalize */
	grid_domain.x = min(grid_domain.x, grid_domain.y) / half_size;

	return 1.0 - smoothstep(0.0, GRID_LINE_SMOOTH / grid_size, grid_domain.x * 0.5);
}

vec3 get_axes(vec3 co, vec3 fwidthCos, float line_size)
{
	vec3 axes_domain = abs(co);
	/* modulate by the absolute rate of change of the coordinates
	 * (make line have the same width under perspective) */
	axes_domain /= fwidthCos;

	return 1.0 - smoothstep(0.0, GRID_LINE_SMOOTH, axes_domain - line_size);
}

vec3 get_floor_pos(vec2 uv, out vec3 wPos)
{
	vec3 camera_vec, camera_pos, corner_pos;
	vec3 floored_pos = planeAxes * floor(screenvecs[2].xyz);
	corner_pos = screenvecs[2].xyz - floored_pos;

	vec3 pixel_pos = corner_pos + uv.x * screenvecs[0].xyz + uv.y * screenvecs[1].xyz;

	/* if perspective */
	if (ProjectionMatrix[3][3] == 0.0) {
		camera_pos = cameraPos - floored_pos;
		camera_vec = normalize(pixel_pos - camera_pos);
	}
	else {
		camera_pos = pixel_pos;
		camera_vec = normalize(eye);
	}

	float plane_normal_dot_camera_vec = dot(planeNormal, camera_vec);
	float p = -dot(planeNormal, camera_pos);
	if (plane_normal_dot_camera_vec != 0) {
		p /= plane_normal_dot_camera_vec;
	}
	vec3 plane = camera_pos + camera_vec * p;

	/* fix residual imprecision */
	plane *= planeAxes;

	/* Recover non-offseted world position */
	wPos = plane + floored_pos;

	return plane;
}

void main()
{
	vec2 sPos = gl_FragCoord.xy / viewportSize; /* Screen [0,1] position */

	/* To reduce artifacts, use a local version of the positions
	 * to compute derivatives since they are not position dependent.
	 * This gets rid of the blocky artifacts. Unfortunately we still
	 * need the world position for the grid to scale properly from the origin. */
	vec3 gPos, wPos; /* Grid pos., World pos. */
	gPos = get_floor_pos(sPos, wPos);

	vec3 fwidthPos = fwidth(gPos);

	float dist, fade;
	/* if persp */
	if (ProjectionMatrix[3][3] == 0.0) {
		vec3 viewvec = cameraPos - wPos;
		dist = length(viewvec);
		viewvec /= dist;

		float angle;
		if ((gridFlag & PLANE_XZ) != 0) {
			angle = viewvec.y;
		}
		else if ((gridFlag & PLANE_YZ) != 0) {
			angle = viewvec.x;
		}
		else {
			angle = viewvec.z;
		}

		angle = 1.0 - abs(angle);
		angle *= angle;
		fade = 1.0 - angle * angle;
		fade *= 1.0 - smoothstep(0.0, gridDistance, dist - gridDistance);
	}
	else {
		dist = abs(gl_FragCoord.z * 2.0 - 1.0);
		fade = 1.0 - smoothstep(0.0, 0.5, dist - 0.5);
		dist = 1.0; /* avoid branch after */

		if ((gridFlag & PLANE_XY) != 0) {
			float angle = 1.0 - abs(eye.z);
			dist = 1.0 + angle * 2.0;
			angle *= angle;
			fade *= 1.0 - angle * angle;
		}
	}

	if ((gridFlag & GRID) != 0) {
		float grid_res = log(dist * gridResolution) * gridOneOverLogSubdiv;

		float blend = fract(-max(grid_res, 0.0));
		float lvl = floor(grid_res);

		/* from biggest to smallest */
		float scaleA = gridScale * pow(gridSubdiv, max(lvl - 1.0, 0.0));
		float scaleB = gridScale * pow(gridSubdiv, max(lvl + 0.0, 0.0));
		float scaleC = gridScale * pow(gridSubdiv, max(lvl + 1.0, 1.0));

		vec2 grid_pos, grid_fwidth;
		if ((gridFlag & PLANE_XZ) != 0) {
			grid_pos = wPos.xz;
			grid_fwidth = fwidthPos.xz;
		}
		else if ((gridFlag & PLANE_YZ) != 0) {
			grid_pos = wPos.yz;
			grid_fwidth = fwidthPos.yz;
		}
		else {
			grid_pos = wPos.xy;
			grid_fwidth = fwidthPos.xy;
		}

		float gridA = get_grid(grid_pos, grid_fwidth, scaleA);
		float gridB = get_grid(grid_pos, grid_fwidth, scaleB);
		float gridC = get_grid(grid_pos, grid_fwidth, scaleC);

		FragColor = vec4(colorGrid.rgb, gridA * blend);
		FragColor = mix(FragColor, vec4(mix(colorGrid.rgb, colorGridEmphasise.rgb, blend), 1.0), gridB);
		FragColor = mix(FragColor, vec4(colorGridEmphasise.rgb, 1.0), gridC);
	}
	else {
		FragColor = vec4(colorGrid.rgb, 0.0);
	}

	if ((gridFlag & (AXIS_X | AXIS_Y | AXIS_Z)) != 0) {
		/* Setup axes 'domains' */
		vec3 axes_dist, axes_fwidth;

		if ((gridFlag & AXIS_X) != 0) {
			axes_dist.x = dot(wPos.yz, planeAxes.yz);
			axes_fwidth.x = dot(fwidthPos.yz, planeAxes.yz);
		}
		if ((gridFlag & AXIS_Y) != 0) {
			axes_dist.y = dot(wPos.xz, planeAxes.xz);
			axes_fwidth.y = dot(fwidthPos.xz, planeAxes.xz);
		}
		if ((gridFlag & AXIS_Z) != 0) {
			axes_dist.z = dot(wPos.xy, planeAxes.xy);
			axes_fwidth.z = dot(fwidthPos.xy, planeAxes.xy);
		}

		/* Computing all axes at once using vec3 */
		vec3 axes = get_axes(axes_dist, axes_fwidth, 0.1);

		if ((gridFlag & AXIS_X) != 0) {
			FragColor.a = max(FragColor.a, axes.x);
			FragColor.rgb = (axes.x < 1e-8) ? FragColor.rgb : colorGridAxisX.rgb;
		}
		if ((gridFlag & AXIS_Y) != 0) {
			FragColor.a = max(FragColor.a, axes.y);
			FragColor.rgb = (axes.y < 1e-8) ? FragColor.rgb : colorGridAxisY.rgb;
		}
		if ((gridFlag & AXIS_Z) != 0) {
			FragColor.a = max(FragColor.a, axes.z);
			FragColor.rgb = (axes.z < 1e-8) ? FragColor.rgb : colorGridAxisZ.rgb;
		}
	}

	/* Add a small bias so the grid will always
	 * be on top of a mesh with the same depth. */
	float grid_depth = gl_FragCoord.z - 6e-8 - fwidth(gl_FragCoord.z);
	float scene_depth = texture(depthBuffer, sPos).r;
	if ((gridFlag & GRID_BACK) != 0) {
		fade *= (scene_depth == 1.0) ? 1.0 : 0.0;
	}
	else {
		/* Manual, non hard, depth test:
		 * Progressively fade the grid below occluders
		 * (avoids popping visuals due to depth buffer precision) */
		/* Harder settings tend to flicker more,
		 * but have less "see through" appearance. */
		const float test_hardness = 1e7;
		fade *= 1.0 - clamp((grid_depth - scene_depth) * test_hardness, 0.0, 1.0);
	}

	FragColor.a *= fade;
}
