Editor/Tools/BridgeTool.cs
// Copyright (c) 2026 SubZero Studios LLC. All rights reserved.

using System;
using Sandbox;
using Editor.TerrainEditor;

namespace Editor.SubTerrain;

/// <summary>
/// Bridge tool. Press on the first point and release on the second to raise a land bridge
/// spanning the gap. The deck grades from the start ground height to the end ground height,
/// and the whole span is written once on release rather than painted stroke by stroke.
/// </summary>
[Title( "Bridge" )]
[Description( "Press start, release on end. Raises a land bridge between the two heights." )]
[Icon( "linear_scale" )]
[Alias( "subterrain_bridge" )]
[Group( "1" )]
[Order( 10 )]
public class SubBridgeTool : SubTerrainBaseBrushTool
{
	Vector3 _startLocal;
	Vector3 _endLocal;
	Vector2 _endUV;
	float _halfWidthTexels;

	public SubBridgeTool( SubTerrainTool parent ) : base( parent )
	{
		Mode = SubTerrainSculptMode.Bridge;
	}

	protected override void OnStrokeStart( Terrain terrain, Vector3 hitPosition )
	{
		_startLocal = hitPosition;
		_endLocal = hitPosition;

		SlopeStartUV = new Vector2( hitPosition.x, hitPosition.y ) / terrain.Storage.TerrainSize;
		SlopeStartHeight01 = hitPosition.z / terrain.Storage.TerrainHeight;
	}

	protected override void OnPaint( Terrain terrain, SubTerrainPaintParameters paint )
	{
		_endLocal = paint.HitPosition;
	}

	protected override void OnPaintEnded( Terrain terrain )
	{
		BuildBridge( terrain );
		base.OnPaintEnded( terrain );
	}

	void BuildBridge( Terrain terrain )
	{
		var storage = terrain.Storage;
		var settings = _parent.BrushSettings;

		var startUV = new Vector2( _startLocal.x, _startLocal.y ) / storage.TerrainSize;
		_endUV = new Vector2( _endLocal.x, _endLocal.y ) / storage.TerrainSize;

		_halfWidthTexels = Math.Max( settings.BridgeWidth * 0.5f / storage.TerrainSize * storage.Resolution, 1.0f );

		var spanTexels = (_endUV - startUV) * storage.Resolution;
		if ( spanTexels.Length < 1.0f )
			return;

		int size = (int)Math.Ceiling( Math.Max( Math.Abs( spanTexels.x ), Math.Abs( spanTexels.y ) ) + _halfWidthTexels * 2.0f ) + 2;
		size = Math.Clamp( size, 1, storage.Resolution );

		SlopeStartUV = startUV;
		SlopeStartHeight01 = _startLocal.z / storage.TerrainHeight;

		var paint = new SubTerrainPaintParameters
		{
			HitPosition = _endLocal,
			HitUV = (startUV + _endUV) * 0.5f,
			FlattenHeight = _endLocal.z / storage.TerrainHeight,
			BrushSettings = settings
		};

		var brushTexture = _parent.SelectedBrushTexture;
		if ( brushTexture is null )
			return;

		DispatchSculpt( terrain, paint, brushTexture, size, 1.0f );
	}

	protected override void ApplyExtraAttributes( ComputeShader cs, SubTerrainPaintParameters paint )
	{
		var settings = paint.BrushSettings;

		cs.Attributes.Set( "SlopeEndUV", _endUV );
		cs.Attributes.Set( "SlopeEndHeight", paint.FlattenHeight );
		cs.Attributes.Set( "BridgeHalfWidth", _halfWidthTexels );
		cs.Attributes.Set( "BridgeDeckRatio", 1.0f - settings.BridgeEdgeFalloff );
		cs.Attributes.Set( "BridgeRaiseOnly", settings.BridgeRaiseOnly ? 1 : 0 );
	}

	protected override void DrawToolOverlay( Terrain terrain, Vector3 worldCenter )
	{
		if ( !_dragging )
			return;

		var startWorld = terrain.WorldTransform.PointToWorld( _startLocal );
		var span = worldCenter - startWorld;
		var flat = new Vector3( span.x, span.y, 0.0f );

		Gizmo.Draw.Color = Color.FromBytes( 250, 180, 120 );
		Gizmo.Draw.Line( startWorld, worldCenter );

		if ( flat.Length < 0.001f )
			return;

		var halfWidth = _parent.BrushSettings.BridgeWidth * 0.5f;
		var side = new Vector3( -flat.y, flat.x, 0.0f ) / flat.Length * halfWidth;

		Gizmo.Draw.Line( startWorld + side, worldCenter + side );
		Gizmo.Draw.Line( startWorld - side, worldCenter - side );
		Gizmo.Draw.Line( startWorld - side, startWorld + side );
		Gizmo.Draw.Line( worldCenter - side, worldCenter + side );
	}
}