/**
 * Mandelbulber v2, a 3D fractal generator  _%}}i*<.        ____                _______
 * Copyright (C) 2023 Mandelbulber Team   _>]|=||i=i<,     / __ \___  ___ ___  / ___/ /
 *                                        \><||i|=>>%)    / /_/ / _ \/ -_) _ \/ /__/ /__
 * This file is part of Mandelbulber.     )<=i=]=|=i<>    \____/ .__/\__/_//_/\___/____/
 * The project is licensed under GPLv3,   -<>>=|><|||`        /_/
 * see also COPYING file in this folder.    ~+{i%+++
 *
 * formula by TGlad,
 * https://fractalforums.org/fractal-mathematics-and-new-theories/28/fractal-clusters/5107

 * This file has been autogenerated by tools/populateUiInformation.php
 * from the file "fractal_sphere_cluster_v3.cpp" in the folder formula/definition
 * D O    N O T    E D I T    T H I S    F I L E !
 */

REAL4 SphereClusterV3Iteration(REAL4 z, __constant sFractalCl *fractal, sExtendedAuxCl *aux)
{
	REAL t = 0.0f;
	REAL3 tv = (REAL3){0.0f, 0.0f, 0.0f};
	REAL4 oldZ = z;
	REAL col = 0.0f;
	REAL4 ColV = (REAL4){0.0f, 0.0f, 0.0f, 0.0f};
	REAL3 p = (REAL3){z.x, z.y, z.z}; // convert to vec3

	p *= fractal->transformCommon.scaleG1; // master scale
	aux->DE *= fractal->transformCommon.scaleG1;

	REAL phi = (1.0f + native_sqrt(5.0f)) / fractal->transformCommon.scale2;

	// Isocahedral geometry
	REAL3 ta0 = (REAL3){0.0f, 1.0f, phi};
	REAL3 ta1 = (REAL3){0.0f, -1.0f, phi};
	REAL3 ta2 = (REAL3){phi, 0.0f, 1.0f};
	REAL3 na0 = cross(ta0, ta1 - ta0);
	na0 = na0 / length(na0);
	REAL3 na1 = cross(ta1, ta2 - ta1);
	na1 = na1 / length(na1);
	REAL3 na2 = cross(ta2, ta0 - ta2);
	na2 = na2 / length(na2);
	REAL mid_to_edgea = atan(phi / (1.0f + 2.0f * phi));
	REAL xxa = 1.0f / native_sin(mid_to_edgea);
	REAL ra = 2.0f / native_sqrt(-4.0f + xxa * xxa);
	REAL la = native_sqrt(1.0f + ra * ra);
	REAL3 mida = (ta0 + ta1 + ta2);
	mida = mida / length(mida);
	REAL minra = (la - ra * fractal->transformCommon.scaleC1) * fractal->transformCommon.scaleA1;

	// Dodecahedral geometry
	REAL3 tb0 = (REAL3){1.0f / phi, 0.0f, phi};
	REAL3 tb1 = (REAL3){1.0f, -1.0f, 1.0f};
	REAL3 tb2 = (REAL3){phi, -1.0f / phi, 0.0f};
	REAL3 tb3 = (REAL3){phi, 1.0f / phi, 0.0f};
	REAL3 tb4 = (REAL3){1.0f, 1.0f, 1.0f};

	REAL3 nb0 = (cross(tb0, tb1 - tb0));
	nb0 = nb0 / length(nb0);
	REAL3 nb1 = (cross(tb1, tb2 - tb1));
	nb1 = nb1 / length(nb1);
	REAL3 nb2 = (cross(tb2, tb3 - tb2));
	nb2 = nb2 / length(nb2);
	REAL3 nb3 = (cross(tb3, tb4 - tb3));
	nb3 = nb3 / length(nb3);
	REAL3 nb4 = (cross(tb4, tb0 - tb4));
	nb4 = nb4 / length(nb4);
	REAL3 dirb = (tb0 + tb1 + tb2 + tb3 + tb4);
	dirb = dirb / length(dirb);
	REAL mid_to_edgeb = atan(dirb.z / dirb.x);
	REAL xxb = 1.0f / native_sin(mid_to_edgeb);
	REAL rb = sqrt(2.0f) / native_sqrt(-2.0f + xxb * xxb);
	REAL lb = native_sqrt(1.0f + rb * rb);
	REAL3 midb = dirb;
	REAL minrb = (lb - rb * fractal->transformCommon.scaleD1) * fractal->transformCommon.scaleB1;

	REAL k = fractal->transformCommon.scale08;				// PackRatio;
	REAL excess = fractal->transformCommon.offset105; // adds a skin width

	REAL minr = 0.0f;
	REAL l, r;
	REAL3 mid;

	int n;
	bool recurse = true;
	for (n = 0; n < fractal->transformCommon.int16; n++)
	{
		if (fractal->transformCommon.functionEnabledPFalse
				&& n >= fractal->transformCommon.startIterationsP
				&& n < fractal->transformCommon.stopIterationsP1)
		{
			if (fractal->transformCommon.functionEnabledCxFalse)
				p.x = fabs(p.x) + fractal->transformCommon.offsetA000.x;
			if (fractal->transformCommon.functionEnabledCyFalse)
				p.y = fabs(p.y) + fractal->transformCommon.offsetA000.y;
			if (fractal->transformCommon.functionEnabledCzFalse)
				p.z = fabs(p.z) + fractal->transformCommon.offsetA000.z;
		}

		bool is = true;
		if (n >= fractal->transformCommon.startIterationsA
				&& n < fractal->transformCommon.stopIterationsA)
			is = false; // Isocahedral

		bool on = true;
		if (n >= fractal->transformCommon.startIterationsB
				&& n < fractal->transformCommon.stopIterationsB)
			on = false;

		if (recurse && n >= fractal->transformCommon.startIterationsC
				&& n < fractal->transformCommon.stopIterationsC)
		{
			if (length(p) > excess)
			{
				if (fractal->transformCommon.functionEnabledAFalse)
				{
					// tv = (REAL3) {0.0f, 0.0f, 0.0f};
					tv = (REAL3){z.x, z.y, z.z};
					p = fabs(tv);
				}
				if (!fractal->transformCommon.functionEnabledBFalse) break;
			}
			if (is == true)
			{
				minr = minrb; // Dodecahedral
			}
			else
			{
				minr = minra; // Isocahedral
			}
			if (on == false)
			{

				REAL sc = minr / dot(p, p);
				p *= sc;
				aux->DE *= sc;
				ColV.z += 1.0f;
				recurse = false;
			}
		}

		if (is == true) // Dodecahedral
		{
			l = lb;
			r = rb;
			mid = midb;
			minr = minrb;
			if (dot(p, nb0) < 0.0f) p -= 2.0f * nb0 * dot(p, nb0);
			if (dot(p, nb1) < 0.0f) p -= 2.0f * nb1 * dot(p, nb1);
			if (dot(p, nb2) < 0.0f) p -= 2.0f * nb2 * dot(p, nb2);
			if (dot(p, nb3) < 0.0f) p -= 2.0f * nb3 * dot(p, nb3);
			if (dot(p, nb4) < 0.0f) p -= 2.0f * nb4 * dot(p, nb4);

			if (dot(p, nb0) < 0.0f) p -= 2.0f * nb0 * dot(p, nb0);
			if (dot(p, nb1) < 0.0f) p -= 2.0f * nb1 * dot(p, nb1);
			if (dot(p, nb2) < 0.0f) p -= 2.0f * nb2 * dot(p, nb2);
			if (dot(p, nb3) < 0.0f) p -= 2.0f * nb3 * dot(p, nb3);
			if (dot(p, nb4) < 0.0f) p -= 2.0f * nb4 * dot(p, nb4);
		}
		else // Isocahedral
		{
			l = la;
			r = ra;
			mid = mida;
			minr = minra;
			if (dot(p, na0) < 0.0f) p -= 2.0f * na0 * dot(p, na0);
			if (dot(p, na1) < 0.0f) p -= 2.0f * na1 * dot(p, na1);
			if (dot(p, na2) < 0.0f) p -= 2.0f * na2 * dot(p, na2);

			if (dot(p, na0) < 0.0f) p -= 2.0f * na0 * dot(p, na0);
			if (dot(p, na1) < 0.0f) p -= 2.0f * na1 * dot(p, na1);
			if (dot(p, na2) < 0.0f) p -= 2.0f * na2 * dot(p, na2);

			if (dot(p, na0) < 0.0f) p -= 2.0f * na0 * dot(p, na0);
			if (dot(p, na1) < 0.0f) p -= 2.0f * na1 * dot(p, na1);
			if (dot(p, na2) < 0.0f) p -= 2.0f * na2 * dot(p, na2);
		}

		REAL m = minr * k;
		t = 1.0f / m;
		REAL3 tv = p - mid * l;
		REAL dist = length(tv);
		if (dist < r || n == fractal->transformCommon.int16 - 1)
		{
			p -= mid * l;

			REAL inv = 1.0f / dot(p, p);

			REAL sc = r * r;
			if (!fractal->transformCommon.functionEnabledMFalse)
				sc = sc * inv;
			else
				sc = (sc + (minr - sc) * fractal->transformCommon.scale0) * inv;

			p *= sc;
			aux->DE *= sc;
			p += mid * l;

			if (fractal->transformCommon.functionEnabledTFalse) m = minr;
			if (length(p) < m * fractal->transformCommon.radius1 && on == false)
			{
				ColV.y += 1.0f;
				p *= t;
				aux->DE *= t;
				recurse = true;
			}
		}

		if (!fractal->transformCommon.functionEnabledIFalse)
		{
			if (on == true && n >= fractal->transformCommon.startIterationsD
					&& n < fractal->transformCommon.stopIterationsD)
			{
				p *= t;
				aux->DE *= t;
			}
		}
		else
		{
			if (recurse && n >= fractal->transformCommon.startIterationsD
					&& n < fractal->transformCommon.stopIterationsD)
			{
				p *= t;
				aux->DE *= t;
			}
		}

		k *= fractal->transformCommon.scale1; // PackRatioScale;
		// post scale
		p *= fractal->transformCommon.scaleF1;
		aux->DE *= fabs(fractal->transformCommon.scaleF1);
		// DE tweaks
		aux->DE = aux->DE * fractal->analyticDE.scale1 + fractal->analyticDE.offset0;

		if (fractal->foldColor.auxColorEnabled && n >= fractal->foldColor.startIterationsA
				&& n < fractal->foldColor.stopIterationsA)
		{
			t = length(z);

			aux->temp1000 = min(aux->temp1000, t);
			ColV.x = aux->temp1000;

			col += ColV.x * fractal->foldColor.difs0000.x + ColV.y * fractal->foldColor.difs0000.y
						 + ColV.z * fractal->foldColor.difs0000.z;
			if (fractal->foldColor.difs1 > dist) col += fractal->foldColor.difs0000.w;
		}
	}

	z = (REAL4){p.x, p.y, p.z, z.w};

	REAL4 zc = z - fractal->transformCommon.offset000;
	if (!fractal->transformCommon.functionEnabledFFalse) zc = fabs(zc);
	REAL d = max(max(zc.x, zc.y), zc.z) - fractal->transformCommon.offset0;
	d = (d - minr * k) / aux->DE;
	// if (d < 0.0f) d = 0.0f; // mmmmmmmmmmmmmmmmmmmmmmmmmm

	if (fractal->transformCommon.functionEnabledCFalse)
	{
		REAL4 cv = aux->const_c;
		cv.z += fractal->transformCommon.offsetF0;
		REAL dst1 = length(cv) - fractal->transformCommon.offsetR1;
		d = max(d, dst1);
		//		d = fabs(d); // mmmmmmmmmmmmmmmmmmmmmmmmmm
	}

	if (!fractal->transformCommon.functionEnabledXFalse)
		aux->dist = min(aux->dist, d);
	else
		aux->dist = d;

	if (fractal->analyticDE.enabledFalse) z = oldZ;

	if (!fractal->transformCommon.functionEnabledGFalse)
		aux->color += col;
	else
		aux->color = col;
	return z;
}