2023-12-12 15:42:16 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "SplineNavModifierComponent.h"
# include "AI/NavigationSystemBase.h"
2024-01-17 20:34:52 -05:00
# include "AI/Navigation/NavigationRelevantData.h"
2024-02-19 11:01:37 -05:00
# include "Curves/BezierUtilities.h"
2023-12-12 15:42:16 -05:00
# include "Components/SplineComponent.h"
# include "VisualLogger/VisualLogger.h"
# include UE_INLINE_GENERATED_CPP_BY_NAME(SplineNavModifierComponent)
namespace
{
2024-02-16 16:54:19 -05:00
// Fetch the spline component from the actor
2023-12-12 15:42:16 -05:00
const USplineComponent * GetSpline ( const AActor * Owner )
{
if ( ! Owner )
{
UE_LOG ( LogNavigation , Warning , TEXT ( " USplineNavModifierComponent has no owner, cannot proceed " ) ) ;
return nullptr ;
}
const USplineComponent * Spline = Owner - > GetComponentByClass < USplineComponent > ( ) ;
2023-12-13 09:59:54 -05:00
UE_CVLOG_UELOG ( ! Spline , Owner , LogNavigation , Warning , TEXT ( " USplineNavModifierComponent attached to \" %s \" could not find a spline component, cannot proceed " ) , * Owner - > GetName ( ) ) ;
2023-12-12 15:42:16 -05:00
return Spline ;
}
2024-02-16 16:54:19 -05:00
// Subdivide the spline into linear segments, adapting to its curvature (more curvy means more linear segments)
void SubdivideSpline ( TArray < FVector > & OutSubdivisions , const USplineComponent & Spline , const float SubdivisionThreshold )
{
// Sample at least 2 points
const int32 NumSplinePoints = FMath : : Max ( Spline . GetNumberOfSplinePoints ( ) , 2 ) ;
// The USplineComponent's Hermite spline tangents are 3 times larger than Bezier tangents and we need to convert before tessellation
constexpr double HermiteToBezierFactor = 3.0 ;
2024-02-19 16:52:18 -05:00
// Tessellate the spline segments
int32 PrevIndex = Spline . IsClosedLoop ( ) ? ( NumSplinePoints - 1 ) : INDEX_NONE ;
for ( int32 SplinePointIndex = 0 ; SplinePointIndex < NumSplinePoints ; SplinePointIndex + + )
2024-02-16 16:54:19 -05:00
{
2024-02-19 16:52:18 -05:00
if ( PrevIndex > = 0 )
2024-02-16 16:54:19 -05:00
{
2024-02-19 16:52:18 -05:00
const FSplinePoint PrevSplinePoint = Spline . GetSplinePointAt ( PrevIndex , ESplineCoordinateSpace : : World ) ;
const FSplinePoint CurrSplinePoint = Spline . GetSplinePointAt ( SplinePointIndex , ESplineCoordinateSpace : : World ) ;
2024-02-16 16:54:19 -05:00
// The first point of the segment is appended before tessellation since UE::CubicBezier::Tessellate does not add it
OutSubdivisions . Add ( PrevSplinePoint . Position ) ;
// Convert this segment of the spline from Hermite to Bezier and subdivide it
2024-02-19 11:01:37 -05:00
UE : : CubicBezier : : Tessellate ( OutSubdivisions ,
2024-02-16 16:54:19 -05:00
PrevSplinePoint . Position ,
PrevSplinePoint . Position + PrevSplinePoint . LeaveTangent / HermiteToBezierFactor ,
CurrSplinePoint . Position - CurrSplinePoint . ArriveTangent / HermiteToBezierFactor ,
CurrSplinePoint . Position ,
SubdivisionThreshold ) ;
}
2024-02-19 16:52:18 -05:00
PrevIndex = SplinePointIndex ;
2024-02-16 16:54:19 -05:00
}
}
2023-12-12 15:42:16 -05:00
}
void USplineNavModifierComponent : : CalculateBounds ( ) const
{
2024-02-16 16:54:19 -05:00
const USplineComponent * Spline = GetSpline ( GetOwner ( ) ) ;
if ( ! Spline )
2023-12-12 15:42:16 -05:00
{
2024-02-16 16:54:19 -05:00
return ;
2023-12-12 15:42:16 -05:00
}
2024-02-16 16:54:19 -05:00
const double Buffer = FMath : : Max ( StrokeWidth / 2.0 , StrokeHeight / 2.0 ) ;
Bounds = Spline - > CalcBounds ( Spline - > GetComponentTransform ( ) ) . GetBox ( ) . ExpandBy ( Buffer ) ;
2023-12-12 15:42:16 -05:00
}
void USplineNavModifierComponent : : GetNavigationData ( FNavigationRelevantData & Data ) const
{
const USplineComponent * Spline = GetSpline ( GetOwner ( ) ) ;
if ( ! Spline )
{
return ;
}
2024-02-16 16:54:19 -05:00
// Build a rectangle in the YZ plane used to sample the spline at each cross section
constexpr int32 NumCrossSectionVertices = 4 ;
const double StrokeHalfWidth = StrokeWidth / 2.0 ;
const double StrokeHalfHeight = StrokeHeight / 2.0 ;
TStaticArray < FVector , NumCrossSectionVertices > CrossSectionRect ;
CrossSectionRect [ 0 ] = FVector ( 0.0 , - StrokeHalfWidth , - StrokeHalfHeight ) ;
CrossSectionRect [ 1 ] = FVector ( 0.0 , StrokeHalfWidth , - StrokeHalfHeight ) ;
CrossSectionRect [ 2 ] = FVector ( 0.0 , StrokeHalfWidth , StrokeHalfHeight ) ;
CrossSectionRect [ 3 ] = FVector ( 0.0 , - StrokeHalfWidth , StrokeHalfHeight ) ;
2023-12-12 15:42:16 -05:00
2024-02-16 16:54:19 -05:00
// Vertices (in an arbitrary order) of a prism which will enclose each segment of the spline
TStaticArray < FVector , NumCrossSectionVertices * 2 > Tube ;
2023-12-12 15:42:16 -05:00
2024-02-16 16:54:19 -05:00
// Subdivide the spline so that high curvature sections get smaller and more linear segments than straighter sections
TArray < FVector > Subdivisions ;
SubdivideSpline ( Subdivisions , * Spline , GetSudivisionThreshold ( ) ) ;
const int32 NumSubdivisions = Subdivisions . Num ( ) ;
2023-12-12 15:42:16 -05:00
2024-02-16 16:54:19 -05:00
// Create volumes from the spline subdivisions and use them to mark the nav mesh with the given are
const FTransform ComponentTransform = Spline - > GetComponentTransform ( ) ;
2024-02-19 16:52:18 -05:00
int32 PrevIndex = 0 ;
for ( int32 SubdivisionIndex = 1 ; SubdivisionIndex < NumSubdivisions ; SubdivisionIndex + + )
2023-12-12 15:42:16 -05:00
{
2024-02-19 16:52:18 -05:00
// Compute the rotation of this tube segment
const double TubeAngle = ( Subdivisions [ SubdivisionIndex ] - Subdivisions [ PrevIndex ] ) . HeadingAngle ( ) ;
const FQuat TubeRotation ( FVector : : UnitZ ( ) , TubeAngle ) ;
// Compute the vertices of this tube segment
for ( int i = 0 ; i < NumCrossSectionVertices ; i + + )
2023-12-12 15:42:16 -05:00
{
2024-02-19 16:52:18 -05:00
// For each vertex of the tube segment, first rotate about the positive Z axis, then translate to the subdivision point
Tube [ i ] = ( TubeRotation * CrossSectionRect [ i ] ) + Subdivisions [ PrevIndex ] ;
Tube [ i + NumCrossSectionVertices ] = ( TubeRotation * CrossSectionRect [ i ] ) + Subdivisions [ SubdivisionIndex ] ;
2023-12-12 15:42:16 -05:00
}
2024-02-19 16:52:18 -05:00
// From the tube construct a convex hull whose volume will be used to mark the nav mesh with the selected AreaClass
const FAreaNavModifier NavModifier ( Tube , ENavigationCoordSystem : : Type : : Unreal , ComponentTransform , AreaClass ) ;
Data . Modifiers . Add ( NavModifier ) ;
PrevIndex = SubdivisionIndex ;
2023-12-12 15:42:16 -05:00
}
2024-02-16 16:54:19 -05:00
}
float USplineNavModifierComponent : : GetSudivisionThreshold ( ) const
{
switch ( SubdivisionLOD )
{
case ESubdivisionLOD : : Ultra :
return 10.0f ;
case ESubdivisionLOD : : High :
return 100.0f ;
case ESubdivisionLOD : : Medium :
return 250.0f ;
case ESubdivisionLOD : : Low :
default : // Fallthrough
return 500.0f ;
}
}