Code/Water/WaterManager.Displacement.cs
using System;
using Sandbox;

namespace RedSnail.WaterTool;

public partial class WaterManager
{
	// --- Hull test ---

	private static bool IsInsideHull(HullCollider hull, Vector3 position)
	{
		if (!hull.IsValid())
			return false;

		var local = hull.WorldTransform.PointToLocal(position);

		if (hull.Type == HullCollider.PrimitiveType.Box)
		{
			var half = hull.BoxSize * 0.5f;
			return float.Abs(local.x) <= half.x &&
				   float.Abs(local.y) <= half.y &&
				   float.Abs(local.z - hull.Center.z) <= half.z;
		}

		if (hull.Type == HullCollider.PrimitiveType.Cylinder)
		{
			Vector2 flat = new(local.x, local.y);
			return flat.LengthSquared <= hull.Radius * hull.Radius &&
				   float.Abs(local.z - hull.Center.z) <= hull.Height * 0.5f;
		}

		return false;
	}

	// --- Nearest-source lookup ---

	private static WaterQuad FindQuadAtPosition(Vector3 position)
	{
		WaterQuad best = null;
		float bestDist = float.MaxValue;

		foreach (WaterQuad quad in Current?.Quads ?? [])
		{
			if (!quad.HullCollider.IsValid())
				continue;
			
			var local = quad.HullCollider.WorldTransform.PointToLocal(position);
			bool insideXY;

			if (quad.HullCollider.Type == HullCollider.PrimitiveType.Cylinder)
			{
				Vector2 flat = new(local.x, local.y);
				insideXY = flat.LengthSquared <= quad.HullCollider.Radius * quad.HullCollider.Radius;
			}
			else
			{
				Vector3 half = quad.HullCollider.BoxSize * 0.5f;
				insideXY = MathF.Abs(local.x) <= half.x && MathF.Abs(local.y) <= half.y;
			}

			if (!insideXY)
				continue;

			float dist = MathF.Abs(position.z - quad.WorldPosition.z);
			if (dist < bestDist) { bestDist = dist; best = quad; }
		}

		return best;
	}

	private static WaterBody FindBodyAtPosition(Vector3 position)
	{
		WaterBody best = null;
		float bestDist = float.MaxValue;

		foreach (WaterBody body in Current?.Bodies ?? [])
		{
			if (!body.IsValid() || !body.Active || !body.ContainsPointXY(position))
				continue;

			float dist = body.GetVerticalDistanceToSurface(position);
			if (dist < bestDist) { bestDist = dist; best = body; }
		}

		return best;
	}

	// --- Public static wave queries ---

	public static bool IsPositionInsideAny(Vector3 position)
	{
		if (Current is null)
			return false;

		foreach (WaterQuad quad in Current.Quads)
		{
			if (!quad.IsValid() || !quad.Active)
				continue;

			if (!IsInsideHull(quad.HullCollider, position))
				continue;

			if (position.z <= quad.GetWaveHeightAt(position))
				return true;
		}

		foreach (WaterBody body in Current.Bodies)
		{
			if (!body.IsValid() || !body.Active)
				continue;

			if (!body.ContainsPointInVolume(position))
				continue;

			if (position.z <= body.GetWaveHeightAt(position))
				return true;
		}

		return false;
	}

	public static Vector3 GetWaveDisplacementAt(Vector3 position)
	{
		WaterQuad quad = FindQuadAtPosition(position);
		WaterBody body = FindBodyAtPosition(position);

		if (quad.IsValid() && body.IsValid())
			return quad.GetWaveHeightAt(position) >= body.GetWaveHeightAt(position)
				? quad.GetWaveDisplacementAt(position)
				: body.GetWaveDisplacementAt(position);

		if (quad.IsValid()) return quad.GetWaveDisplacementAt(position);
		if (body.IsValid()) return body.GetWaveDisplacementAt(position);
		return Vector3.Zero;
	}

	public static Vector3 GetWaveVelocityAt(Vector3 position)
	{
		WaterQuad quad = FindQuadAtPosition(position);
		WaterBody body = FindBodyAtPosition(position);

		if (quad.IsValid() && body.IsValid())
			return quad.GetWaveHeightAt(position) >= body.GetWaveHeightAt(position)
				? quad.GetWaveVelocityAt(position)
				: body.GetWaveVelocityAt(position);

		if (quad.IsValid()) return quad.GetWaveVelocityAt(position);
		if (body.IsValid()) return body.GetWaveVelocityAt(position);
		return Vector3.Zero;
	}

	public static float GetFlatWaterHeightAt(Vector3 position)
	{
		WaterQuad quad = FindQuadAtPosition(position);
		WaterBody body = FindBodyAtPosition(position);

		if (quad.IsValid() && body.IsValid())
			return MathF.Max(quad.WorldPosition.z, body.GetSurfaceHeight());

		if (quad.IsValid()) return quad.WorldPosition.z;
		if (body.IsValid()) return body.GetSurfaceHeight();
		return float.MinValue;
	}

	public static float GetWaterHeightAt(Vector3 position)
	{
		WaterQuad quad = FindQuadAtPosition(position);
		WaterBody body = FindBodyAtPosition(position);

		if (quad.IsValid() && body.IsValid())
			return MathF.Max(quad.GetWaveHeightAt(position), body.GetWaveHeightAt(position));

		if (quad.IsValid()) return quad.GetWaveHeightAt(position);
		if (body.IsValid()) return body.GetWaveHeightAt(position);
		return float.MinValue;
	}
}