Editor/Features/RiverStream.cs
using Editor;
using Sandbox;
using System;

namespace Sturnus.TerrainGenerationTool.RiverStream;
public static class RiverStream
{
	public static float[,] AddRiversAndStreams(
	float[,] heightmap,
	int frequency, // Number of rivers/streams
	float widthScale, // Relative width of rivers/streams
	long seed
)
	{
		int width = heightmap.GetLength( 0 );
		int height = heightmap.GetLength( 1 );
		float[,] modifiedHeightmap = (float[,])heightmap.Clone();
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );

		// Generate river starting points based on frequency
		for ( int i = 0; i < frequency; i++ )
		{
			int startX = random.Next( 0, width );
			int startY = random.Next( 0, height );

			// Ensure the river starts at a relatively high elevation
			while ( modifiedHeightmap[startX, startY] < 0.5f )
			{
				startX = random.Next( 0, width );
				startY = random.Next( 0, height );
			}

			// Trace the river path
			AddRiverPath( modifiedHeightmap, startX, startY, width, height, widthScale, random );
		}

		return modifiedHeightmap;
	}

	private static void AddRiverPath(
		float[,] heightmap,
		int startX,
		int startY,
		int width,
		int height,
		float widthScale,
		Random random
	)
	{
		int currentX = startX;
		int currentY = startY;

		// Determine the river width based on widthScale
		int riverWidth = Math.Max( 1, (int)(widthScale * width) );

		for ( int steps = 0; steps < width * 2; steps++ ) // Ensure rivers stretch long distances
		{
			// Lower the terrain at the current position to form a river bed
			CarveRiverAtPosition( heightmap, currentX, currentY, riverWidth, width, height );

			// Find the next position by prioritizing downhill movement
			(int nextX, int nextY) = FindNextRiverPosition( heightmap, currentX, currentY, width, height, random );

			// Stop if the river can no longer flow
			if ( nextX == currentX && nextY == currentY )
				break;

			currentX = nextX;
			currentY = nextY;
		}
	}

	private static (int, int) FindNextRiverPosition(
		float[,] heightmap,
		int x,
		int y,
		int width,
		int height,
		Random random
	)
	{
		float currentHeight = heightmap[x, y];
		int nextX = x;
		int nextY = y;
		float lowestHeight = currentHeight;

		// Check all 8 neighbors to find the steepest downhill path
		for ( int offsetY = -1; offsetY <= 1; offsetY++ )
		{
			for ( int offsetX = -1; offsetX <= 1; offsetX++ )
			{
				int nx = x + offsetX;
				int ny = y + offsetY;

				// Skip out-of-bounds and current position
				if ( nx < 0 || nx >= width || ny < 0 || ny >= height || (nx == x && ny == y) )
					continue;

				float neighborHeight = heightmap[nx, ny];
				if ( neighborHeight < lowestHeight )
				{
					lowestHeight = neighborHeight;
					nextX = nx;
					nextY = ny;
				}
			}
		}

		// Add slight randomness to avoid perfectly straight rivers
		if ( random.NextDouble() < 0.3 ) // 30% chance to adjust path
		{
			nextX = Math.Clamp( nextX + random.Next( -1, 2 ), 0, width - 1 );
			nextY = Math.Clamp( nextY + random.Next( -1, 2 ), 0, height - 1 );
		}

		return (nextX, nextY);
	}

	private static void CarveRiverAtPosition(
		float[,] heightmap,
		int x,
		int y,
		int riverWidth,
		int width,
		int height
	)
	{
		for ( int offsetY = -riverWidth / 2; offsetY <= riverWidth / 2; offsetY++ )
		{
			for ( int offsetX = -riverWidth / 2; offsetX <= riverWidth / 2; offsetX++ )
			{
				int nx = x + offsetX;
				int ny = y + offsetY;

				// Ensure we're within bounds
				if ( nx >= 0 && nx < width && ny >= 0 && ny < height )
				{
					// Lower the terrain for the river bed
					float distance = MathF.Sqrt( offsetX * offsetX + offsetY * offsetY );
					float factor = Math.Clamp( 1.0f - (distance / (riverWidth / 2.0f)), 0.0f, 1.0f );
					heightmap[nx, ny] -= factor * 0.03f; // Adjust depth for river carving
				}
			}
		}
	}




}