Conveyance/BridgePlacer.cs
using Sandbox;
using System.Collections.Generic;

/// <summary>
/// Places N clones of a BridgePlank prefab in a row between StartAnchor and EndAnchor
/// to form a rope-bridge style assembly.
///
/// PHILOSOPHY: This script does NOT create any joints from code. Each plank instance is
/// a self-contained prefab with its own anchors and springs already wired up. We just
/// clone the prefab at calculated positions and let each clone's internal physics work
/// independently. No plank-to-plank constraints, no shared anchors — just N independent
/// suspended planks that visually form a bridge.
///
/// SETUP:
///   1. Build one BridgePlank prefab manually (per the bridge plank guide). Confirm it
///      works in isolation.
///   2. Save it as a prefab asset.
///   3. Add this component to an empty GameObject called "Bridge".
///   4. Place two empty GameObjects "StartAnchor" and "EndAnchor" in the scene where
///      the bridge should start and end.
///   5. Drag the BridgePlank prefab into the PlankPrefab slot.
///   6. Drag the start/end anchors into their slots.
///   7. Set PlankCount and SagAmount to taste.
///   8. Click "Build Bridge".
///
/// REBUILD: Clicking Build again destroys existing planks (tracked internally) and
/// rebuilds with current parameters. Safe to iterate on.
///
/// COORDINATE NOTE: planks are placed in a straight line between the two anchors,
/// then optionally shifted downward in a parabolic sag for that authentic rope-bridge
/// droop. Each plank is rotated to face along the bridge direction so it sits naturally.
/// </summary>
public sealed class BridgePlacer : Component
{
	/// <summary>
	/// The BridgePlank prefab. Must contain its own anchors, plank rigidbody, and springs
	/// pre-wired in the editor. This script does NOT modify the prefab's internals.
	/// </summary>
	[Property] public GameObject PlankPrefab { get; set; }

	/// <summary>
	/// Empty GameObject marking the start of the bridge. The first plank is placed here.
	/// </summary>
	[Property] public GameObject StartAnchor { get; set; }

	/// <summary>
	/// Empty GameObject marking the end of the bridge. The last plank is placed here.
	/// </summary>
	[Property] public GameObject EndAnchor { get; set; }

	/// <summary>
	/// Number of planks to spawn between (and including) the start and end positions.
	/// Below ~4 looks sparse; above ~12 starts to feel dense. 6–8 is the rope-bridge sweet spot.
	/// </summary>
	[Property, Range( 2, 30 )] public int PlankCount { get; set; } = 8;

	/// <summary>
	/// How much the bridge dips in the middle, in world units. 0 = perfectly horizontal
	/// line, looks artificial. Real rope bridges sag — try 30–80 depending on bridge length.
	/// The sag follows a parabolic curve, deepest at the midpoint.
	/// </summary>
	[Property, Range( 0, 200 )] public float SagAmount { get; set; } = 40f;

	/// <summary>
	/// Internal tracking of the GameObjects this script has created, so we can clean them
	/// up on rebuild without nuking unrelated bridge decorations the user might add.
	/// </summary>
	private readonly List<GameObject> spawnedPlanks = new();

	/// <summary>
	/// Wipe existing planks and stamp PlankCount new ones in a row.
	/// </summary>
	[Button( "Build Bridge" )]
	public void Build()
	{
		if ( !PlankPrefab.IsValid() )
		{
			Log.Warning( $"{nameof( BridgePlacer )}: PlankPrefab is not set." );
			return;
		}
		if ( !StartAnchor.IsValid() || !EndAnchor.IsValid() )
		{
			Log.Warning( $"{nameof( BridgePlacer )}: StartAnchor or EndAnchor is not set." );
			return;
		}

		// Step 1: clean up any planks from a previous build.
		ClearExisting();

		// Step 2: compute the geometry.
		var startPos = StartAnchor.WorldPosition;
		var endPos = EndAnchor.WorldPosition;
		var bridgeVector = endPos - startPos;
		var bridgeLength = bridgeVector.Length;

		if ( bridgeLength < 1f )
		{
			Log.Warning( $"{nameof( BridgePlacer )}: Start and End anchors are at the same position." );
			return;
		}

		// Bridge direction (used to orient each plank along the bridge axis).
		// LookAt produces a rotation whose Forward points toward the target. We want planks
		// to face "across" the bridge — their long axis pointing from start to end. So we
		// take a LookAt and use it directly for plank rotation.
		var bridgeRotation = Rotation.LookAt( bridgeVector.Normal );

		// Step 3: place planks evenly spaced from start to end. The parameter t goes 0 → 1
		// as we walk from start to end. We include both endpoints, so the first plank sits
		// at t=0 (start) and the last at t=1 (end). With N planks, the step is 1/(N-1).
		for ( int i = 0; i < PlankCount; i++ )
		{
			// Linear interpolation parameter, 0 at first plank, 1 at last.
			float t = PlankCount == 1 ? 0.5f : (float)i / ( PlankCount - 1 );

			// Position along the straight line from start to end.
			var linearPos = Vector3.Lerp( startPos, endPos, t );

			// Parabolic sag: maximum at t=0.5 (midpoint), zero at endpoints.
			// Formula: 1 - (2t - 1)^2 is a parabola that peaks at t=0.5 with value 1.
			// Using x*x instead of MathF.Pow because s&box sandboxes System.MathF out.
			float u = 2f * t - 1f;
			float sagFactor = 1f - u * u;
			var sagOffset = Vector3.Down * SagAmount * sagFactor;

			var plankPos = linearPos + sagOffset;

			// Step 4: clone the prefab at this position with the bridge's facing rotation.
			// The clone includes the plank's anchors as children, so they move with it —
			// each plank's springs reference its own anchors (relative GUIDs in the prefab),
			// so no cross-wiring happens.
			var plank = PlankPrefab.Clone( plankPos, bridgeRotation );

			// Parent under this GameObject for tidiness. Setting parent moves the object,
			// so we set parent first if we want to preserve world position — actually,
			// SetParent has an option for this. Default is to keep world transform.
			plank.SetParent( GameObject, true );

			// Name it for easier debugging in the hierarchy.
			plank.Name = $"Plank_{i:D2}";

			spawnedPlanks.Add( plank );
		}

		Log.Info( $"{nameof( BridgePlacer )}: built bridge with {PlankCount} planks over {bridgeLength:F0} units." );
	}

	/// <summary>
	/// Destroy all planks this script has created. Does not touch anything else under
	/// the bridge GameObject (e.g. LineRenderers for the rope visuals are safe).
	/// </summary>
	[Button( "Clear Bridge" )]
	public void ClearExisting()
	{
		foreach ( var p in spawnedPlanks )
		{
			if ( p.IsValid() )
				p.Destroy();
		}
		spawnedPlanks.Clear();
	}
}