// Minimal Mandelbulb fractal shader - test version

#define MARCH_MAX_STEPS 200
#define MARCH_MAX_DISTANCE 500.0
#define DE_MAX_ITER 25
#define DE_BAILOUT 10.0

#define HIT_STATUS_HIT 1
#define HIT_STATUS_MISS_DISTANCE 2
#define HIT_STATUS_MISS_STEPS 3

struct MarchResult
{
	float distance;
	int steps;
	int hitStatus;
	int totalDEIterations;
};

struct FractalFunctions
{
	// Computes the world-space radius that a sphere must have to appear 1 pixel tall on screen at a given distance.
	static float GetPixelWorldRadius(float distance)
	{
		return distance * ResolvedView.ClipToView._22 / ResolvedView.ViewSizeAndInvSize.y;
	}

	// Mandelbulb distance estimator
	static float MandelbulbDE(float3 pos, float power, out int iterations)
	{
		float3 z = pos;
		float dr = 1.0;
		float r = 0.0;
		
		for (iterations = 0; iterations < DE_MAX_ITER; iterations++)
		{
			r = length(z);
			if (r > DE_BAILOUT)
				break;
			
			// Convert to spherical coordinates
			float theta = acos(z.z / r);
			float phi = atan2(z.y, z.x);
			dr = pow(r, power - 1.0) * power * dr + 1.0;
			
			// Scale and rotate the point
			float zr = pow(r, power);
			theta = theta * power;
			phi = phi * power;
			
			// Convert back to Cartesian coordinates
			float sinTheta = sin(theta);
			z = zr * float3(sinTheta * cos(phi), sinTheta * sin(phi), cos(theta));
			z += pos;
		}
		
		return 0.5 * log(r) * r / dr;
	}

	// Ray marching
	static MarchResult MarchFractal(float3 rayOrigin, float3 rayDir, float power)
	{
		float totalDist = 0.0;
		int steps = 0;
		int totalDEIterations = 0;

		while (totalDist < MARCH_MAX_DISTANCE && steps < MARCH_MAX_STEPS)
		{
			steps++;
			float3 pos = rayOrigin + rayDir * totalDist;
			
			int deIterations;
			float distance = MandelbulbDE(pos, power, deIterations);
			totalDEIterations += deIterations;

			float pixelSize = GetPixelWorldRadius(totalDist);

			if (distance <= pixelSize)
			{
				MarchResult result;
				result.distance = totalDist;
				result.steps = steps;
				result.hitStatus = HIT_STATUS_HIT;
				result.totalDEIterations = totalDEIterations;
				return result;
			}

			float stepSize = max(distance, pixelSize * 0.5);
			totalDist += stepSize;
		}

		MarchResult result;
		result.distance = totalDist;
		result.steps = steps;
		result.hitStatus = (totalDist >= MARCH_MAX_DISTANCE) ? HIT_STATUS_MISS_DISTANCE : HIT_STATUS_MISS_STEPS;
		result.totalDEIterations = totalDEIterations;
		return result;
	}

	static float3 RenderFractal(float2 screenUV, float3 sceneColor, float power)
	{
		// Get camera parameters
		float3 cameraOrigin = LWCHackToFloat(ResolvedView.WorldCameraOrigin);

		// Convert screen UV to clip space
		float2 clipXY = screenUV * 2.0 - 1.0;
		clipXY.y = -clipXY.y;

		float4 clipPos = float4(clipXY, 1.0, 1.0);
		float4 viewPos = mul(clipPos, ResolvedView.ClipToView);
		viewPos.xyz /= viewPos.w;

		// Transform view direction to world space
		float3 worldDir = normalize(mul(viewPos.xyz, (float3x3)ResolvedView.ViewToTranslatedWorld));

		// Scale down the camera origin for better framing
		float3 rayOrigin = cameraOrigin * 0.001;
		float3 rayDir = worldDir;

		// Ray march
		MarchResult finalResult = MarchFractal(rayOrigin, rayDir, power);

		// Calculate color based on total steps taken
		float stepFactor = saturate(finalResult.steps / float(MARCH_MAX_STEPS));
		float t = sqrt(stepFactor);
		
		// Gradient color stops
		float3 almostBlack = float3(0.0, 0.0, 0.05);
		float3 deepBlue = float3(0.05, 0.15, 0.3);
		float3 vibrantBlue = float3(0.2, 0.4, 0.8);
		float3 brightBlue = float3(0.6, 0.8, 1.0);
		
		// Blend through gradient
		float range1 = saturate(t / 0.15);
		float range2 = saturate((t - 0.15) / 0.25);
		float range3 = saturate((t - 0.4) / 0.6);
		float3 fractalColor = lerp(lerp(lerp(almostBlack, deepBlue, range1), vibrantBlue, range2), brightBlue, range3);

		// Add green tint based on total DE iterations
		float iterFactor = saturate(finalResult.totalDEIterations / float(DE_MAX_ITER * finalResult.steps));
		
		if (iterFactor > 0.1)
		{
			float greenAmount = (iterFactor - 0.1) / 0.9;
			greenAmount = pow(greenAmount, 2.0);
			fractalColor = lerp(fractalColor, float3(0.3, 0.5, 0.6), greenAmount);
		}

		if (iterFactor > 0.9)
		{
			float greenAmount = (iterFactor - 0.9) / 0.1;
			greenAmount = pow(greenAmount, 5.0);
			fractalColor = lerp(fractalColor, float3(0.3, 0.9, 0.7), greenAmount);
		}

		if (finalResult.hitStatus == HIT_STATUS_HIT)
		{
			return fractalColor;
		}
		else if (finalResult.hitStatus == HIT_STATUS_MISS_DISTANCE)
		{
			float fogAmount = pow(stepFactor, 0.1);
			return lerp(sceneColor, fractalColor, fogAmount);
		}
		else
		{
			return fractalColor;
		}
	}
};

// Usage in material custom node:
// #include "/Project/FractalMaterial.usf"
// FractalFunctions FF;
// return FF.RenderFractal(ScreenPosition, SceneColor, Power).rgb;