/**
 * Mandelbulber v2, a 3D fractal generator  _%}}i*<.        ____                _______
 * Copyright (C) 2017 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%+++
 *
 * GeneralizedFoldBoxIteration - Quaternion fractal with extended controls
 * @reference http://www.fractalforums.com/new-theories-and-research/generalized-box-fold/
 * This formula contains aux.color

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

REAL4 GeneralizedFoldBoxIteration(REAL4 z, __constant sFractalCl *fractal, sExtendedAuxCl *aux)
{
	REAL3 zXYZ = z.xyz;
	int i;
	__constant REAL3 *Nv;
	int sides;

	switch (fractal->genFoldBox.type)
	{
		default:
		case generalizedFoldBoxTypeCl_foldTet:
			Nv = fractal->genFoldBox.Nv_tet;
			sides = fractal->genFoldBox.sides_tet;
			break;
		case generalizedFoldBoxTypeCl_foldCube:
			Nv = fractal->genFoldBox.Nv_cube;
			sides = fractal->genFoldBox.sides_cube;
			break;
		case generalizedFoldBoxTypeCl_foldOct:
			Nv = fractal->genFoldBox.Nv_oct;
			sides = fractal->genFoldBox.sides_oct;
			break;
		case generalizedFoldBoxTypeCl_foldDodeca:
			Nv = fractal->genFoldBox.Nv_dodeca;
			sides = fractal->genFoldBox.sides_dodeca;
			break;
		case generalizedFoldBoxTypeCl_foldOctCube:
			Nv = fractal->genFoldBox.Nv_oct_cube;
			sides = fractal->genFoldBox.sides_oct_cube;
			break;
		case generalizedFoldBoxTypeCl_foldIcosa:
			Nv = fractal->genFoldBox.Nv_icosa;
			sides = fractal->genFoldBox.sides_icosa;
			break;
		case generalizedFoldBoxTypeCl_foldBox6:
			Nv = fractal->genFoldBox.Nv_box6;
			sides = fractal->genFoldBox.sides_box6;
			break;
		case generalizedFoldBoxTypeCl_foldBox5:
			Nv = fractal->genFoldBox.Nv_box5;
			sides = fractal->genFoldBox.sides_box5;
			break;
	}

	REAL melt = fractal->mandelbox.melt;
	REAL solid = fractal->mandelbox.solid;

	// Find the closest cutting plane if any that cuts the line between the origin and z.
	// Line is parameterized as X = Y + L*a;
	// Cutting plane is dot(X, Nv) = Solid.
	// (Y + L*a).Dot(Nv) = solid.
	// a = (solid - dot(Y, Nv))/dot(L, Nv) = b/c
	REAL3 L = zXYZ;
	REAL a = 1.0f;
	REAL3 Y; // Y is the origin in this case.
	int side = -1;
	REAL b, c;

	for (i = 0; i < sides; i++)
	{
		b = solid;
		c = dot(L, Nv[i]);
		// A bit subtle here. a_r must be positive and I want to avoid divide by zero.
		if ((c > 0.0f) && ((a * c) > b))
		{
			side = i;
			a = native_divide(b, c);
		}
	}

	// If z is above the foldingValue we may have to fold. Else early out.
	if (side != -1)
	{ // mirror check
		int side_m = side;
		REAL3 Nv_m = Nv[side_m];
		REAL3 X_m = mad(-(dot(zXYZ, Nv_m) - solid), Nv_m, zXYZ);

		// Find any plane (Nv_r) closest to X_m that cuts the line between Nv_m and X_m.
		// Nv_m cross Nv_r will define a possible rotation axis.
		// a = (solid - dot(Y, Nv)/dot(L, Nv) = b/c.
		L = X_m - Nv_m;
		Y = Nv_m;
		a = 1.0f;
		side = -1;

		for (i = 0; i < sides; i++)
		{
			if (i != side_m)
			{
				b = solid - dot(Y, Nv[i]);
				c = dot(L, Nv[i]);
				// A bit subtle here. a_r must be positive and I want to avoid divide by zero.
				if ((c > 0.0f) && ((a * c) > b))
				{
					side = i;
					a = native_divide(b, c);
				}
			}
		}

		// Was a cutting plane found?
		if (side != -1)
		{ // rotation check
			REAL3 Xmr_intersect = mad(a, L, Y);
			int side_r = side;
			REAL3 Nv_r = Nv[side_r];
			// The axis of rotation is define by the cross product of Nv_m and Nv_r and
			// the intersection of the line between Nv_m and Nv_r and  Xmr_intersect.
			REAL3 L_r = cross(Nv_m, Nv_r);
			// The closest point between z and the line of rotation can be found by minimizing
			// the square of the distance (D) between z and the line
			// X = mad(a_rmin., L_r, Xmr_intersect)
			// Setting dD/da_rmin equal to zero and solving for a_rmin.
			REAL a_rmin = native_divide((dot(zXYZ, L_r) - dot(Xmr_intersect, L_r)), (dot(L_r, L_r)));

			// force a_rmin to be positive. I think I made an even number of sign errors here.
			if (a_rmin < 0.0f)
			{
				a_rmin = -a_rmin;
				L_r = L_r * (-1.0f);
			}
			REAL3 X_r = mad(a_rmin, L_r, Xmr_intersect);

			// Find any plane (Nv_i) closest to Xmr_intersect that cuts the line between
			// Xmr_intersect and X_r. This will define a possible inversion point.
			// a = (solid - dot(Y, Nv)/dot(L, Nv) = b/c.
			L = X_r - Xmr_intersect;
			Y = Xmr_intersect;
			a = 1.0f;
			side = -1;

			for (i = 0; i < sides; i++)
			{
				if ((i != side_m) && (i != side_r))
				{
					b = solid - dot(Y, Nv[i]);
					c = dot(L, Nv[i]);
					// A bit subtle here. a must be positive and I want to avoid divide by zero.
					if ((c > 0.0f) && ((a * c) > b))
					{
						side = i;
						a = native_divide(b, c);
					}
				}
			}

			if (side != -1)
			{ // inversion check
				// Only inversion point possible but still need to check for melt.

				REAL3 X_i = mad(a, L, Y);
				REAL3 z2X = X_i - zXYZ;
				// Is z above the melt layer.
				if (dot(z2X, z2X) > (melt * melt))
				{
					REAL z2X_mag = length(z2X);
					zXYZ += z2X * (2.0f * native_divide((z2X_mag - melt), (z2X_mag + .00000001f)));
					aux->color += fractal->mandelbox.color.factor.z;
				}
			}
			else
			{
				// Only rotation line possible but still need to check for melt.
				// Is z above the melt layer.
				REAL3 z2X = X_r - zXYZ;
				if (dot(z2X, z2X) > (melt * melt))
				{
					REAL z2X_mag = length(z2X);
					zXYZ += z2X * (2.0f * native_divide((z2X_mag - melt), (z2X_mag + .00000001f)));
					aux->color += fractal->mandelbox.color.factor.y;
				}
			}
		}
		else
		{
			// Only mirror plane possible but still need to check for melt.
			REAL3 z2X = X_m - zXYZ;
			if (dot(z2X, z2X) > (melt * melt))
			{
				REAL z2X_mag = length(z2X);
				zXYZ += z2X * (2.0f * native_divide((z2X_mag - melt), (z2X_mag + .00000001f)));
				aux->color += fractal->mandelbox.color.factor.x;
			}
		}
	} // outside solid

	REAL r2 = dot(zXYZ, zXYZ);

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

	z += fractal->mandelbox.offset;

	if (r2 < fractal->mandelbox.mR2)
	{
		z *= fractal->mandelbox.mboxFactor1;
		aux->DE *= fractal->mandelbox.mboxFactor1;
		aux->color += fractal->mandelbox.color.factorSp1;
	}
	else if (r2 < fractal->mandelbox.fR2)
	{
		REAL tglad_factor2 = native_divide(fractal->mandelbox.fR2, r2);
		z *= tglad_factor2;
		aux->DE *= tglad_factor2;
		aux->color += fractal->mandelbox.color.factorSp2;
	}

	z -= fractal->mandelbox.offset;

	if (fractal->mandelbox.mainRotationEnabled)
	{
		z = Matrix33MulFloat4(fractal->mandelbox.mainRot, z);
	}

	z *= fractal->mandelbox.scale;
	aux->DE = mad(aux->DE, fabs(fractal->mandelbox.scale), 1.0f);
	return z;
}