using Sandbox.Engine.Resources;
using Sandbox.Navigation;
using System;
namespace Sandbox;
///
/// NavigationLinks connect navigation mesh polygons for pathfinding and enable shortcuts like ladders, jumps, or teleports.
///
[Expose]
[Title( "NavMesh - Link" )]
[Category( "Navigation" )]
[Icon( "link" )]
[EditorHandle( "materials/gizmo/navmeshagent.png" )]
[Alias( "NavAgent" )]
public class NavMeshLink : Component, Component.ExecuteInEditor
{
///
/// Start position relative to the game object's position.
///
[Property]
public Vector3 LocalStartPosition
{
get => _start;
set
{
_start = value;
CreateOrUpdateLink();
}
}
private Vector3 _start;
///
/// End position relative to the game object's position.
///
[Property]
public Vector3 LocalEndPosition
{
get => _end;
set
{
_end = value;
CreateOrUpdateLink();
}
}
///
/// Start position in world space snapped to the navmesh.
///
public Vector3? WorldStartPositionOnNavmesh
{
get => linkData.IsStartConnected ? linkData.StartPositionOnNavMesh : null;
}
///
/// End position in world space snapped to the navmesh.
///
public Vector3? WorldEndPositionOnNavmesh
{
get => linkData.IsEndConnected ? linkData.EndPositionOnNavMesh : null;
}
private Vector3 _end;
///
/// Whether this link can be traverse bi-directional or only start towards end.
///
[Property]
public bool IsBiDirectional = true;
///
/// Radius that will be searched at the start and end positions for a connection to the navmesh.
///
[Property]
public float ConnectionRadius = 16f;
///
/// The NavMesh area definition to apply to this link.
///
[Property]
public NavMeshAreaDefinition Area
{
get => _area;
set
{
_area = value;
CreateOrUpdateLink();
}
}
private NavMeshAreaDefinition _area;
///
/// Emitted when an agent enters the link.
///
public Action LinkEntered { get; set; }
///
/// Emitted when an agent exits the link.
///
public Action LinkExited { get; set; }
private NavMeshLinkData linkData;
///
/// Start position in world space.
///
public Vector3 WorldStartPosition
{
get => WorldTransform.PointToWorld( LocalStartPosition );
set
{
LocalStartPosition = WorldTransform.PointToLocal( value );
}
}
///
/// End position in world space.
///
public Vector3 WorldEndPosition
{
get => WorldTransform.PointToWorld( LocalEndPosition );
set
{
LocalEndPosition = WorldTransform.PointToLocal( value );
}
}
///
/// Called when an agent enters the link.
///
protected virtual void OnLinkEntered( NavMeshAgent agent )
{
}
///
/// Called when an agent exits the link.
///
protected virtual void OnLinkExited( NavMeshAgent agent )
{
}
internal void TriggetEntered( NavMeshAgent agent )
{
if ( !Active )
return;
LinkEntered?.Invoke( agent );
OnLinkEntered( agent );
}
internal void TriggetExited( NavMeshAgent agent )
{
if ( !Active )
return;
LinkExited?.Invoke( agent );
OnLinkExited( agent );
}
internal override void OnEnabledInternal()
{
CreateOrUpdateLink();
Transform.OnTransformChanged += TransformChanged;
base.OnEnabledInternal();
}
internal override void OnDisabledInternal()
{
ClearLink();
Transform.OnTransformChanged -= TransformChanged;
base.OnDisabledInternal();
}
private void CreateOrUpdateLink()
{
if ( !Active )
return;
if ( linkData == null )
{
linkData = new NavMeshLinkData();
linkData.UserData = this;
Scene.NavMesh.AddSpatiaData( linkData );
}
linkData.StartPosition = WorldStartPosition;
linkData.EndPosition = WorldEndPosition;
linkData.IsBiDirectional = IsBiDirectional;
linkData.ConnectionRadius = ConnectionRadius;
linkData.AreaDefinition = Area;
linkData.HasChanged = true;
}
private void ClearLink()
{
if ( linkData != null )
{
// tile cache will take care of removal
linkData.IsPendingRemoval = true;
linkData = null;
}
}
protected override void DrawGizmos()
{
if ( linkData == null )
return;
Gizmo.Draw.Color = linkData.IsStartConnected ? NavMesh.debugInnerLineColor.WithAlpha( 1 ) : Color.Red;
Gizmo.Draw.LineSphere( LocalStartPosition, ConnectionRadius );
Gizmo.Draw.Color = linkData.IsEndConnected ? NavMesh.debugInnerLineColor.WithAlpha( 1 ) : Color.Red;
Gizmo.Draw.LineSphere( LocalEndPosition, ConnectionRadius );
var drawStartPosition = LocalStartPosition;
if ( linkData.IsStartConnected )
{
drawStartPosition = WorldTransform.PointToLocal( linkData.StartPositionOnNavMesh ) + NavMesh.debugDrawGroundOffset + new Vector3( 0, 0, 0.01f );
Gizmo.Draw.Color = NavMesh.debugInnerLineColor.WithAlpha( 1 );
Gizmo.Draw.SolidSphere( drawStartPosition, 8f );
}
var drawEndPosition = LocalEndPosition;
if ( linkData.IsEndConnected )
{
drawEndPosition = WorldTransform.PointToLocal( linkData.EndPositionOnNavMesh ) + NavMesh.debugDrawGroundOffset + new Vector3( 0, 0, 0.01f );
Gizmo.Draw.Color = NavMesh.debugInnerLineColor.WithAlpha( 1 );
Gizmo.Draw.SolidSphere( drawEndPosition, 8f );
}
Gizmo.Draw.LineThickness = 2f;
Gizmo.Draw.Color = NavMesh.debugTileBorderColor.WithAlpha( 1 );
// Draw arc between start end
int segments = 8;
int endToStartArrowSegment = segments / 4;
int startToEndArrowSegment = segments - endToStartArrowSegment;
Vector3 previousPoint = drawStartPosition;
for ( int i = 1; i <= segments; i++ )
{
float t = (float)i / segments;
Vector3 point = Vector3.Lerp( drawStartPosition, drawEndPosition, t );
point.z += MathF.Sin( t * MathF.PI ) * MathF.Abs( drawStartPosition.z - drawEndPosition.z ); // curve upwards
if ( i == startToEndArrowSegment )
{
Gizmo.Draw.Arrow( previousPoint, point, 8f );
}
else if ( i == endToStartArrowSegment + 1 && IsBiDirectional )
{
Gizmo.Draw.Arrow( point, previousPoint, 8f );
}
else
{
Gizmo.Draw.Line( previousPoint, point );
}
previousPoint = point;
}
}
private void TransformChanged()
{
CreateOrUpdateLink();
}
}