Files
UnrealEngineUWP/Engine/Plugins/Runtime/SmartObjects/Source/SmartObjectsModule/Private/Annotations/SmartObjectSlotEntryAnnotation.cpp
mikko mononen d2c160c4ce SmartObjects: Added entry annotation selection and validation.
- Added USmartObjectSubsystem::FindEntryLocationForSlot() which allows to query entry points which are on navigable surface
- Improved debug visualizations
- Added visualization shape and size for slots
- Added gameplay interaction state tree task to query entry location

#jira UE-174418
#preflight 6400614fef1b24bf94f42203

[CL 24478702 by mikko mononen in ue5-main branch]
2023-03-02 05:58:30 -05:00

275 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Annotations/SmartObjectSlotEntryAnnotation.h"
#include "SmartObjectSubsystem.h"
#include "SmartObjectDefinition.h"
#include "SmartObjectVisualizationContext.h"
#include "SceneManagement.h" // FPrimitiveDrawInterface
#include "NavigationSystem.h"
#if WITH_GAMEPLAY_DEBUGGER
#include "GameplayDebuggerCategory.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(SmartObjectSlotEntryAnnotation)
namespace UE::SmartObject::Annotations
{
inline bool IsPointInBox(const FVector Point, const FVector BoxCenter, const FVector BoxExtents)
{
const FVector AbsDiff = (Point - BoxCenter).GetAbs();
return AbsDiff.X <= BoxExtents.X && AbsDiff.Y <= BoxExtents.Y && AbsDiff.Z <= BoxExtents.Z;
}
const ANavigationData* GetDefaultNavData(const UWorld& World)
{
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (!NavSys)
{
return nullptr;
}
return NavSys->GetDefaultNavDataInstance();
}
FNavLocation FindNearestNavigableLocation(const UWorld& World, const FVector Location, const FVector SearchExtents)
{
// Find navigation data.
const ANavigationData* NavData = GetDefaultNavData(World);
if (!NavData)
{
return {};
}
FNavLocation NavLocation;
if (NavData->ProjectPoint(Location, NavLocation, SearchExtents, nullptr, nullptr)
&& NavLocation.HasNodeRef()
&& IsPointInBox(NavLocation.Location, Location, SearchExtents))
{
return NavLocation;
}
return {};
}
static constexpr FColor EntryColor(0, 64, 192);
static constexpr FColor InvalidEntryColor(192, 32, 16);
};
#if WITH_EDITOR
void FSmartObjectSlotEntryAnnotation::DrawVisualization(FSmartObjectVisualizationContext& VisContext) const
{
constexpr FVector::FReal MarkerRadius = 20.0;
constexpr FVector::FReal TickSize = 5.0;
constexpr FVector::FReal MinArrowDrawDistance = 20.0;
const FSmartObjectSlotIndex SlotIndex(VisContext.SlotIndex);
const TOptional<FTransform> SlotTransform = VisContext.Definition.GetSlotTransform(VisContext.OwnerLocalToWorld, SlotIndex);
const bool bHasNavData = UE::SmartObject::Annotations::GetDefaultNavData(VisContext.World) != nullptr;
if (SlotTransform.IsSet())
{
const TOptional<FTransform> AnnotationTransform = GetWorldTransform(*SlotTransform);
if (AnnotationTransform.IsSet())
{
const FVector SlotWorldLocation = SlotTransform->GetTranslation();
const FVector EntryWorldLocation = AnnotationTransform->GetTranslation();
const FVector AxisX = AnnotationTransform->GetUnitAxis(EAxis::X);
const FVector AxisY = AnnotationTransform->GetUnitAxis(EAxis::Y);
FLinearColor Color = UE::SmartObject::Annotations::EntryColor;
if (bHasNavData)
{
const FNavLocation NavLocation = UE::SmartObject::Annotations::FindNearestNavigableLocation(VisContext.World, EntryWorldLocation, FVector(FSmartObjectSlotEntryRequest::DefaultValidationExtents));
if (NavLocation.HasNodeRef())
{
FVector TickStart = NavLocation.Location;
FVector TickEnd = NavLocation.Location;
TickStart.Z = FMath::Min(NavLocation.Location.Z, EntryWorldLocation.Z) - TickSize;
TickEnd.Z = FMath::Max(NavLocation.Location.Z, EntryWorldLocation.Z) + TickSize;
VisContext.PDI->DrawTranslucentLine(TickStart, TickEnd, Color, SDPG_World, 1.0f);
}
else
{
Color = UE::SmartObject::Annotations::InvalidEntryColor;
}
}
if (VisContext.bIsAnnotationSelected)
{
Color = VisContext.SelectedColor;
}
if (bIsEntry)
{
const FVector V0 = EntryWorldLocation + AxisX * MarkerRadius;
const FVector V1 = EntryWorldLocation + AxisX * MarkerRadius * 0.25 + AxisY * MarkerRadius;
const FVector V2 = EntryWorldLocation + AxisX * MarkerRadius * 0.25 - AxisY * MarkerRadius;
const FVector V3 = EntryWorldLocation + AxisY * MarkerRadius;
const FVector V4 = EntryWorldLocation - AxisY * MarkerRadius;
VisContext.PDI->DrawTranslucentLine(V0, V1, Color, SDPG_World, 2.0f);
VisContext.PDI->DrawTranslucentLine(V0, V2, Color, SDPG_World, 2.0f);
VisContext.PDI->DrawTranslucentLine(V1, V3, Color, SDPG_World, 2.0f);
VisContext.PDI->DrawTranslucentLine(V2, V4, Color, SDPG_World, 2.0f);
}
if (bIsExit)
{
const FVector V1 = EntryWorldLocation - AxisX * MarkerRadius * 0.75 + AxisY * MarkerRadius;
const FVector V2 = EntryWorldLocation - AxisX * MarkerRadius * 0.75 - AxisY * MarkerRadius;
const FVector V3 = EntryWorldLocation + AxisY * MarkerRadius;
const FVector V4 = EntryWorldLocation - AxisY * MarkerRadius;
VisContext.PDI->DrawTranslucentLine(V1, V2, Color, SDPG_World, 2.0f);
VisContext.PDI->DrawTranslucentLine(V1, V3, Color, SDPG_World, 2.0f);
VisContext.PDI->DrawTranslucentLine(V2, V4, Color, SDPG_World, 2.0f);
}
// Tick at the center.
VisContext.PDI->DrawTranslucentLine(EntryWorldLocation - AxisX * TickSize, EntryWorldLocation + AxisX * TickSize, Color, SDPG_World, 1.0f);
VisContext.PDI->DrawTranslucentLine(EntryWorldLocation - AxisY * TickSize, EntryWorldLocation + AxisY * TickSize, Color, SDPG_World, 1.0f);
// Arrow pointing at the the slot, if far enough from the slot.
if (FVector::DistSquared(EntryWorldLocation, SlotWorldLocation) > FMath::Square(MinArrowDrawDistance))
{
VisContext.DrawArrow(EntryWorldLocation, SlotWorldLocation, Color, 15.0f, 15.0f, /*DepthPrioGroup*/0, /*Thickness*/1.0f, /*DepthBias*/2.0);
}
}
}
}
void FSmartObjectSlotEntryAnnotation::DrawVisualizationHUD(FSmartObjectVisualizationContext& VisContext) const
{
const FSmartObjectSlotIndex SlotIndex(VisContext.SlotIndex);
const TOptional<FTransform> SlotTransform = VisContext.Definition.GetSlotTransform(VisContext.OwnerLocalToWorld, SlotIndex);
if (SlotTransform.IsSet() && VisContext.bIsAnnotationSelected)
{
TOptional<FTransform> AnnotationTransform = GetWorldTransform(*SlotTransform);
if (AnnotationTransform.IsSet())
{
const FVector EntryWorldLocation = AnnotationTransform->GetTranslation();
FString Text(TEXT("Entry\n"));
Text += Tag.ToString();
VisContext.DrawString(EntryWorldLocation, *Text, FLinearColor::White);
}
}
}
void FSmartObjectSlotEntryAnnotation::AdjustWorldTransform(const FTransform& SlotTransform, const FVector& DeltaTranslation, const FRotator& DeltaRotation)
{
if (!DeltaTranslation.IsZero())
{
const FVector LocalTranslation = SlotTransform.InverseTransformVector(DeltaTranslation);
Offset += FVector3f(LocalTranslation);
}
if (!DeltaRotation.IsZero())
{
const FRotator3f LocalRotation = FRotator3f(SlotTransform.InverseTransformRotation(DeltaRotation.Quaternion()).Rotator());
Rotation += LocalRotation;
Rotation.Normalize();
}
}
#endif // WITH_EDITOR
TOptional<FTransform> FSmartObjectSlotEntryAnnotation::GetWorldTransform(const FTransform& SlotTransform) const
{
const FTransform LocalTransform = FTransform(FRotator(Rotation), FVector(Offset));
return TOptional(LocalTransform * SlotTransform);
}
FVector FSmartObjectSlotEntryAnnotation::GetWorldLocation(const FTransform& SlotTransform) const
{
return SlotTransform.TransformPosition(FVector(Offset));
}
FRotator FSmartObjectSlotEntryAnnotation::GetWorldRotation(const FTransform& SlotTransform) const
{
return SlotTransform.TransformRotation(FQuat(Rotation.Quaternion())).Rotator();
}
#if WITH_GAMEPLAY_DEBUGGER
void FSmartObjectSlotEntryAnnotation::CollectDataForGameplayDebugger(FGameplayDebuggerCategory& Category, const FTransform& SlotTransform, const FVector ViewLocation, const FVector ViewDirection, AActor* DebugActor) const
{
constexpr FVector::FReal MarkerRadius = 20.0;
constexpr FVector::FReal TickSize = 5.0;
constexpr FVector::FReal MinArrowDrawDistance = 20.0;
const UWorld* World = Category.GetWorldFromReplicator();
if (!World)
{
return;
}
const TOptional<FTransform> AnnotationTransform = GetWorldTransform(SlotTransform);
if (!AnnotationTransform.IsSet())
{
return;
}
const FVector SlotWorldLocation = SlotTransform.GetTranslation();
const FVector EntryWorldLocation = AnnotationTransform->GetTranslation();
const FVector AxisX = AnnotationTransform->GetUnitAxis(EAxis::X);
const FVector AxisY = AnnotationTransform->GetUnitAxis(EAxis::Y);
const FVector EntryWorldLocationWithOffset = EntryWorldLocation + FVector(0, 0, 2.0);
FColor Color = UE::SmartObject::Annotations::EntryColor;
const FNavLocation NavLocation = UE::SmartObject::Annotations::FindNearestNavigableLocation(*World, EntryWorldLocation, FVector(FSmartObjectSlotEntryRequest::DefaultValidationExtents));
if (NavLocation.HasNodeRef())
{
FVector TickStart = NavLocation.Location;
FVector TickEnd = NavLocation.Location;
TickStart.Z = FMath::Min(NavLocation.Location.Z, EntryWorldLocation.Z) - TickSize;
TickEnd.Z = FMath::Max(NavLocation.Location.Z, EntryWorldLocation.Z) + TickSize;
Category.AddShape(FGameplayDebuggerShape::MakeSegment(TickStart, TickEnd, 1.0f, Color));
}
else
{
Color = UE::SmartObject::Annotations::InvalidEntryColor;
}
TArray<FVector, TInlineAllocator<16>> Polyline;
if (bIsEntry)
{
Category.AddShape(FGameplayDebuggerShape::MakePolyline({
EntryWorldLocationWithOffset + AxisY * MarkerRadius,
EntryWorldLocationWithOffset + AxisX * MarkerRadius * 0.25 + AxisY * MarkerRadius,
EntryWorldLocationWithOffset + AxisX * MarkerRadius,
EntryWorldLocationWithOffset + AxisX * MarkerRadius * 0.25 - AxisY * MarkerRadius,
EntryWorldLocationWithOffset - AxisY * MarkerRadius
}, 2.0f, Color));
}
if (bIsExit)
{
Category.AddShape(FGameplayDebuggerShape::MakePolyline({
EntryWorldLocationWithOffset + AxisY * MarkerRadius,
EntryWorldLocationWithOffset - AxisX * MarkerRadius * 0.5 + AxisY * MarkerRadius,
EntryWorldLocationWithOffset - AxisX * MarkerRadius * 0.5 - AxisY * MarkerRadius,
EntryWorldLocationWithOffset - AxisY * MarkerRadius
}, 2.0f, Color));
}
// Tick at the center.
Category.AddShape(FGameplayDebuggerShape::MakeSegmentList( {
EntryWorldLocationWithOffset - AxisX * TickSize,
EntryWorldLocationWithOffset + AxisX * TickSize,
EntryWorldLocationWithOffset - AxisY * TickSize,
EntryWorldLocationWithOffset + AxisY * TickSize
}, 2.0f, Color));
// Arrow pointing at the the slot, if far enough from the slot.
if (FVector::DistSquared(EntryWorldLocation, SlotWorldLocation) > FMath::Square(MinArrowDrawDistance))
{
Category.AddShape(FGameplayDebuggerShape::MakeSegment(EntryWorldLocationWithOffset, SlotWorldLocation, 1.0f, Color));
}
}
#endif // WITH_GAMEPLAY_DEBUGGER