// Original code is MIT licensed by Cory Petkovsek
// https://github.com/TokisanGames/godot-fractal-art

shader_type canvas_item;

uniform bool smoothing = true;
uniform bool use_color_gradient = false;
uniform vec4 gradient_start : source_color = vec4(0.18, 0., 0.45, 1.);
uniform vec4 gradient_end : source_color = vec4(1., 1., 0., 1.);
uniform vec4 gradient_accent : source_color = vec4(1., 1., 0., 1.);
uniform float accent_position = 0.65;
uniform float accent_width = 0.34;
uniform float red_frequency = 1.6615;
uniform float green_frequency = 1.246;
uniform float blue_frequency = 0.831;
uniform float red_phase = 6.3;
uniform float green_phase = 6.3;
uniform float blue_phase = 6.3;

uniform highp float scale = 0.5;
uniform highp vec2 position = vec2(0, 0);
uniform float aspect_ratio = 1.33333;

uniform highp vec2 seed = vec2(-0.794084, 0.136444);
uniform int power = 2;
uniform float iterations = 50.0;
uniform float escape = 4.0;

// Convert from rectangular to polar
highp vec2 c_pol(highp vec2 c) {
	highp float radius = length(c);
	highp float theta = atan(c.y, c.x);
	return vec2(radius, theta);
}

// Convert from rectangular to Polar
highp vec2 c_rec(highp vec2 c) {
	highp float radius = abs(c.x);
	highp float theta = c.y;
	highp float a = radius * cos(theta);
	highp float b = radius * sin(theta);
	return vec2(a, b);
}

// Calculate base ^ exponent
highp vec2 c_pow(highp vec2 base, highp float ex) {
	highp vec2 b = c_pol(base);
	return c_rec(vec2(pow(b.x, ex), b.y * ex));
}

vec4 alpha_blend(vec4 top, vec4 bot) {
	return vec4(top.r * top.a + bot.r * bot.a * (1. - top.a),
			top.g * top.a + bot.g * bot.a * (1. - top.a),
			top.b * top.a + bot.b * bot.a * (1. - top.a),
			1.0);
}

vec4 getColor(float i) {
	if (use_color_gradient) {
		// Create gradient
		vec4 ramp = mix(gradient_start, gradient_end, i);

		// gaussian formula ae^( -(x-b)^2 / 2c^2 )
		//  a = curve's peak = 1.0
		//  b = center of peak position
		//  c = standard deviation which controls width of bell

		float gaussianx = exp(-((i - accent_position) * (i - accent_position)) / (2. * accent_width * accent_width));
		vec4 accent = vec4(gradient_accent.r * gaussianx,
				gradient_accent.g * gaussianx,
				gradient_accent.b * gaussianx,
				gaussianx);

		return alpha_blend(accent, ramp);
	} else {
		/* Sin/Cos creates a smooth wave between 1 and -1, offset from each other.
		 *  You can have 4 evenly distributed offsets from sin, cos, -sin, -cos
		 *  (sin(x) +1) / 2 -> changes the wave to go between 0 and 1 with the same frequency, +2../4 will go from 0.5 and 1.0
		 *  Calculate full cycles with: sin(cycles*6.5*i)   or   (sin(cycles*5.4*i)+1.0)/2.0   or   (cos(cycles*3.8*i)+1.0)/2.0
		 */
		return vec4((sin(red_frequency * 6.5 * i + red_phase) + 1.0) / 2.0,
				(sin(green_frequency * 6.5 * i + green_phase) + 1.0) / 2.0,
				(sin(blue_frequency * 6.5 * i + blue_phase) + 1.0) / 2.0,
				1.);
	}
}

vec2 random(vec2 uv) {
	return vec2(fract(sin(dot(uv.xy,
		vec2(12.9898,78.233))) * 43758.5453123));
}

vec2 voronoi(vec2 uv, float columns, float rows) {
	vec2 index_uv = floor(vec2(uv.x * columns, uv.y * rows));
	vec2 fract_uv = fract(vec2(uv.x * columns, uv.y * rows));
	
	float minimum_dist = 1.0;  
	vec2 minimum_point;

	for (int y= -1; y <= 1; y++) {
		for (int x= -1; x <= 1; x++) {
			vec2 neighbor = vec2(float(x),float(y));
			vec2 point = random(index_uv + neighbor);

			vec2 diff = neighbor + point - fract_uv;
			float dist = length(diff);
			
			if(dist < minimum_dist) {
				minimum_dist = dist;
				minimum_point = point;
			}
		}
	}
	return minimum_point;
}


void fragment() {
	float i = 0.0;

	highp vec2 c = seed;

	highp vec2 z;
	z.x = aspect_ratio * (UV.x - 0.5) / scale - position.x;
	z.y = (UV.y - 0.5) / scale + position.y;

	if (power == 2) {
		while (i < iterations && length(z) <= escape) {
			z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; // 20fps
			i++;
		}
	} else {
		while (i < iterations && length(z) <= escape) {
			z = c_pow(z, float(power)) + c; // 6-7fps
			i++;
		}
	}

	if (i >= iterations) {
		float r = voronoi(UV + seed, 10.0, 5.0).r;
		COLOR = getColor(r);
	} else {
		if (smoothing && power > 1) {
			i -= log(log(length(z))) / log(float(power));
		}
		COLOR = getColor(i / iterations);
	}
}
