|
|
|
|
@@ -0,0 +1,474 @@
|
|
|
|
|
#include "PS_BehaviorEditor_AISpline.h"
|
|
|
|
|
#include "PS_Editor_SpawnableComponent.h"
|
|
|
|
|
#include "PS_Editor_EditableComponent.h"
|
|
|
|
|
#include "Components/SplineComponent.h"
|
|
|
|
|
#include "ProceduralMeshComponent.h"
|
|
|
|
|
#include "Components/StaticMeshComponent.h"
|
|
|
|
|
#include "Engine/StaticMesh.h"
|
|
|
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
|
|
|
|
|
|
|
|
APS_BehaviorEditor_AISpline::APS_BehaviorEditor_AISpline()
|
|
|
|
|
{
|
|
|
|
|
PrimaryActorTick.bCanEverTick = false;
|
|
|
|
|
|
|
|
|
|
// SplineComp is already created by APS_AI_Behavior_SplinePath as root.
|
|
|
|
|
// Clear default editor-placed points so we start empty for runtime placement.
|
|
|
|
|
if (SplineComp)
|
|
|
|
|
{
|
|
|
|
|
SplineComp->ClearSplinePoints(false);
|
|
|
|
|
SplineComp->SetClosedLoop(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Line mesh
|
|
|
|
|
SplineMeshComp = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("SplineMesh"));
|
|
|
|
|
SplineMeshComp->SetupAttachment(RootComponent);
|
|
|
|
|
SplineMeshComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
|
|
|
|
SplineMeshComp->SetCollisionResponseToAllChannels(ECR_Ignore);
|
|
|
|
|
SplineMeshComp->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
|
|
|
|
SplineMeshComp->bUseComplexAsSimpleCollision = true;
|
|
|
|
|
SplineMeshComp->SetCastShadow(false);
|
|
|
|
|
|
|
|
|
|
// Handle parent
|
|
|
|
|
HandleRoot = CreateDefaultSubobject<USceneComponent>(TEXT("HandleRoot"));
|
|
|
|
|
HandleRoot->SetupAttachment(RootComponent);
|
|
|
|
|
|
|
|
|
|
// Spawnable: makes this actor appear in the editor catalogue
|
|
|
|
|
SpawnableComp = CreateDefaultSubobject<UPS_Editor_SpawnableComponent>(TEXT("PS_Editor_Spawnable"));
|
|
|
|
|
SpawnableComp->DisplayName = TEXT("AI Spline");
|
|
|
|
|
SpawnableComp->Category = TEXT("AI");
|
|
|
|
|
|
|
|
|
|
// Editable: exposes AI + visual properties in the runtime property panel
|
|
|
|
|
EditableComp = CreateDefaultSubobject<UPS_Editor_EditableComponent>(TEXT("PS_Editor_Editable"));
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineCategory"))});
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("bBidirectional"))});
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineWalkSpeed"))});
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("Priority"))});
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineColor"))});
|
|
|
|
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineSelectedColor"))});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::BeginPlay()
|
|
|
|
|
{
|
|
|
|
|
Super::BeginPlay();
|
|
|
|
|
|
|
|
|
|
// Gizmo material (no depth test) for handles and selected state
|
|
|
|
|
UMaterialInterface* GizmoMat = LoadObject<UMaterialInterface>(
|
|
|
|
|
nullptr, TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
|
|
|
|
|
|
|
|
|
// Opaque depth-tested material for spline line
|
|
|
|
|
UMaterialInterface* SplineLineMat = LoadObject<UMaterialInterface>(
|
|
|
|
|
nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine.M_PS_Editor_SplineLine"));
|
|
|
|
|
if (!SplineLineMat)
|
|
|
|
|
{
|
|
|
|
|
SplineLineMat = LoadObject<UMaterialInterface>(nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SplineLineMat)
|
|
|
|
|
{
|
|
|
|
|
SplineMat = UMaterialInstanceDynamic::Create(SplineLineMat, this);
|
|
|
|
|
SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (GizmoMat)
|
|
|
|
|
{
|
|
|
|
|
SplineMatSelected = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
|
|
|
|
SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
|
|
|
|
|
|
|
|
|
HandleMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
|
|
|
|
HandleMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 1.0f, 1.0f));
|
|
|
|
|
|
|
|
|
|
HandleHighlightMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
|
|
|
|
HandleHighlightMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 1.0f, 0.0f));
|
|
|
|
|
|
|
|
|
|
CenterCubeMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
|
|
|
|
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Center cube (selection handle)
|
|
|
|
|
CenterCube = NewObject<UStaticMeshComponent>(this);
|
|
|
|
|
CenterCube->SetupAttachment(GetRootComponent());
|
|
|
|
|
CenterCube->RegisterComponent();
|
|
|
|
|
UStaticMesh* CubeMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"));
|
|
|
|
|
if (CubeMesh)
|
|
|
|
|
{
|
|
|
|
|
CenterCube->SetStaticMesh(CubeMesh);
|
|
|
|
|
}
|
|
|
|
|
CenterCube->SetWorldScale3D(FVector(CenterCubeScale));
|
|
|
|
|
CenterCube->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
|
|
|
|
CenterCube->SetCollisionResponseToAllChannels(ECR_Ignore);
|
|
|
|
|
CenterCube->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
|
|
|
|
CenterCube->SetCastShadow(false);
|
|
|
|
|
if (CenterCubeMat)
|
|
|
|
|
{
|
|
|
|
|
CenterCube->SetMaterial(0, CenterCubeMat);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Point manipulation ----
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::AddPoint(const FVector& WorldPosition)
|
|
|
|
|
{
|
|
|
|
|
SplineComp->AddSplinePoint(WorldPosition, ESplineCoordinateSpace::World, true);
|
|
|
|
|
PointHandles.Add(CreatePointHandle(WorldPosition));
|
|
|
|
|
UpdateVisualization();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::MovePoint(int32 Index, const FVector& NewWorldPosition)
|
|
|
|
|
{
|
|
|
|
|
if (Index < 0 || Index >= GetNumPoints()) return;
|
|
|
|
|
SplineComp->SetLocationAtSplinePoint(Index, NewWorldPosition, ESplineCoordinateSpace::World, true);
|
|
|
|
|
if (PointHandles.IsValidIndex(Index) && PointHandles[Index])
|
|
|
|
|
{
|
|
|
|
|
PointHandles[Index]->SetWorldLocation(NewWorldPosition);
|
|
|
|
|
}
|
|
|
|
|
UpdateVisualization();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::RemovePoint(int32 Index)
|
|
|
|
|
{
|
|
|
|
|
if (Index < 0 || Index >= GetNumPoints()) return;
|
|
|
|
|
SplineComp->RemoveSplinePoint(Index, true);
|
|
|
|
|
if (PointHandles.IsValidIndex(Index))
|
|
|
|
|
{
|
|
|
|
|
if (PointHandles[Index]) PointHandles[Index]->DestroyComponent();
|
|
|
|
|
PointHandles.RemoveAt(Index);
|
|
|
|
|
}
|
|
|
|
|
UpdateVisualization();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 APS_BehaviorEditor_AISpline::GetNumPoints() const
|
|
|
|
|
{
|
|
|
|
|
return SplineComp ? SplineComp->GetNumberOfSplinePoints() : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector APS_BehaviorEditor_AISpline::GetPointLocation(int32 Index) const
|
|
|
|
|
{
|
|
|
|
|
if (!SplineComp || Index < 0 || Index >= GetNumPoints()) return FVector::ZeroVector;
|
|
|
|
|
return SplineComp->GetLocationAtSplinePoint(Index, ESplineCoordinateSpace::World);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<FVector> APS_BehaviorEditor_AISpline::GetAllPointLocations() const
|
|
|
|
|
{
|
|
|
|
|
TArray<FVector> Result;
|
|
|
|
|
const int32 Num = GetNumPoints();
|
|
|
|
|
Result.Reserve(Num);
|
|
|
|
|
for (int32 i = 0; i < Num; i++)
|
|
|
|
|
{
|
|
|
|
|
Result.Add(GetPointLocation(i));
|
|
|
|
|
}
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::SetAllPointLocations(const TArray<FVector>& Points)
|
|
|
|
|
{
|
|
|
|
|
if (!SplineComp) return;
|
|
|
|
|
SplineComp->ClearSplinePoints(false);
|
|
|
|
|
for (const FVector& WorldPos : Points)
|
|
|
|
|
{
|
|
|
|
|
SplineComp->AddSplinePoint(WorldPos, ESplineCoordinateSpace::World, false);
|
|
|
|
|
}
|
|
|
|
|
SplineComp->UpdateSpline();
|
|
|
|
|
RebuildHandles();
|
|
|
|
|
UpdateVisualization();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::InsertPoint(int32 Index, const FVector& WorldPosition)
|
|
|
|
|
{
|
|
|
|
|
if (!SplineComp) return;
|
|
|
|
|
Index = FMath::Clamp(Index, 0, GetNumPoints());
|
|
|
|
|
TArray<FVector> Points = GetAllPointLocations();
|
|
|
|
|
Points.Insert(WorldPosition, Index);
|
|
|
|
|
SetAllPointLocations(Points);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 APS_BehaviorEditor_AISpline::FindInsertIndex(const FVector& WorldLocation) const
|
|
|
|
|
{
|
|
|
|
|
if (!SplineComp || GetNumPoints() < 2) return GetNumPoints();
|
|
|
|
|
const float InputKey = SplineComp->FindInputKeyClosestToWorldLocation(WorldLocation);
|
|
|
|
|
return FMath::Clamp(FMath::FloorToInt(InputKey) + 1, 1, GetNumPoints());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector APS_BehaviorEditor_AISpline::GetCentroid() const
|
|
|
|
|
{
|
|
|
|
|
const int32 Num = GetNumPoints();
|
|
|
|
|
if (Num == 0) return GetActorLocation();
|
|
|
|
|
FVector Centroid = FVector::ZeroVector;
|
|
|
|
|
for (int32 i = 0; i < Num; i++)
|
|
|
|
|
{
|
|
|
|
|
Centroid += GetPointLocation(i);
|
|
|
|
|
}
|
|
|
|
|
return Centroid / Num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Visualization ----
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::UpdateVisualization()
|
|
|
|
|
{
|
|
|
|
|
if (!SplineMeshComp || !SplineComp || GetNumPoints() < 2)
|
|
|
|
|
{
|
|
|
|
|
if (SplineMeshComp) SplineMeshComp->ClearAllMeshSections();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float SplineLength = SplineComp->GetSplineLength();
|
|
|
|
|
const int32 NumSamples = FMath::Max(2, FMath::CeilToInt(SplineLength / SampleInterval));
|
|
|
|
|
const int32 Segs = TubeSegments;
|
|
|
|
|
|
|
|
|
|
TArray<FVector> Vertices;
|
|
|
|
|
TArray<int32> Triangles;
|
|
|
|
|
TArray<FVector> Normals;
|
|
|
|
|
TArray<FVector2D> UVs;
|
|
|
|
|
|
|
|
|
|
Vertices.Reserve(NumSamples * Segs);
|
|
|
|
|
Normals.Reserve(NumSamples * Segs);
|
|
|
|
|
UVs.Reserve(NumSamples * Segs);
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < NumSamples; i++)
|
|
|
|
|
{
|
|
|
|
|
const float Distance = (static_cast<float>(i) / (NumSamples - 1)) * SplineLength;
|
|
|
|
|
const FVector Location = SplineComp->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
|
|
|
|
const FVector SplineRight = SplineComp->GetRightVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
|
|
|
|
const FVector SplineUp = SplineComp->GetUpVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
|
|
|
|
|
|
|
|
|
const float U = static_cast<float>(i) / (NumSamples - 1);
|
|
|
|
|
|
|
|
|
|
for (int32 j = 0; j < Segs; j++)
|
|
|
|
|
{
|
|
|
|
|
const float Angle = (static_cast<float>(j) / Segs) * 2.0f * PI;
|
|
|
|
|
const FVector Normal = FMath::Cos(Angle) * SplineRight + FMath::Sin(Angle) * SplineUp;
|
|
|
|
|
Vertices.Add(Location + Normal * TubeRadius);
|
|
|
|
|
Normals.Add(Normal);
|
|
|
|
|
UVs.Add(FVector2D(U, static_cast<float>(j) / Segs));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Triangles.Reserve((NumSamples - 1) * Segs * 6);
|
|
|
|
|
for (int32 i = 0; i < NumSamples - 1; i++)
|
|
|
|
|
{
|
|
|
|
|
for (int32 j = 0; j < Segs; j++)
|
|
|
|
|
{
|
|
|
|
|
const int32 NextJ = (j + 1) % Segs;
|
|
|
|
|
const int32 A = i * Segs + j;
|
|
|
|
|
const int32 B = i * Segs + NextJ;
|
|
|
|
|
const int32 C = (i + 1) * Segs + j;
|
|
|
|
|
const int32 D = (i + 1) * Segs + NextJ;
|
|
|
|
|
Triangles.Add(A); Triangles.Add(C); Triangles.Add(B);
|
|
|
|
|
Triangles.Add(B); Triangles.Add(C); Triangles.Add(D);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SplineMeshComp->ClearAllMeshSections();
|
|
|
|
|
SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray<FColor>(), TArray<FProcMeshTangent>(), true);
|
|
|
|
|
SplineMeshComp->UpdateBounds();
|
|
|
|
|
SplineMeshComp->MarkRenderStateDirty();
|
|
|
|
|
|
|
|
|
|
const bool bCurrentlySelected = (PointHandles.Num() > 0 && PointHandles[0] && PointHandles[0]->IsVisible());
|
|
|
|
|
UMaterialInstanceDynamic* LineMat = (bCurrentlySelected && SplineMatSelected) ? SplineMatSelected : SplineMat;
|
|
|
|
|
if (LineMat)
|
|
|
|
|
{
|
|
|
|
|
SplineMeshComp->SetMaterial(0, LineMat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateCenterCube();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::UpdateCenterCube()
|
|
|
|
|
{
|
|
|
|
|
if (!CenterCube) return;
|
|
|
|
|
if (GetNumPoints() == 0)
|
|
|
|
|
{
|
|
|
|
|
CenterCube->SetVisibility(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
FVector Centroid = FVector::ZeroVector;
|
|
|
|
|
for (int32 i = 0; i < GetNumPoints(); i++)
|
|
|
|
|
{
|
|
|
|
|
Centroid += GetPointLocation(i);
|
|
|
|
|
}
|
|
|
|
|
Centroid /= GetNumPoints();
|
|
|
|
|
CenterCube->SetWorldLocation(Centroid);
|
|
|
|
|
CenterCube->SetVisibility(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::UpdateSplineMaterials()
|
|
|
|
|
{
|
|
|
|
|
if (SplineMat) SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
|
|
|
|
if (SplineMatSelected) SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
|
|
|
|
if (CenterCubeMat) CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Handle interaction ----
|
|
|
|
|
|
|
|
|
|
UStaticMeshComponent* APS_BehaviorEditor_AISpline::CreatePointHandle(const FVector& WorldPosition)
|
|
|
|
|
{
|
|
|
|
|
UStaticMeshComponent* Handle = NewObject<UStaticMeshComponent>(this);
|
|
|
|
|
Handle->SetupAttachment(HandleRoot);
|
|
|
|
|
Handle->RegisterComponent();
|
|
|
|
|
UStaticMesh* SphereMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Sphere.Sphere"));
|
|
|
|
|
if (SphereMesh) Handle->SetStaticMesh(SphereMesh);
|
|
|
|
|
Handle->SetWorldLocation(WorldPosition);
|
|
|
|
|
Handle->SetWorldScale3D(FVector(HandleScale));
|
|
|
|
|
Handle->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
|
|
|
|
Handle->SetCollisionResponseToAllChannels(ECR_Ignore);
|
|
|
|
|
Handle->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
|
|
|
|
Handle->SetCastShadow(false);
|
|
|
|
|
Handle->SetRenderInDepthPass(false);
|
|
|
|
|
if (HandleMat) Handle->SetMaterial(0, HandleMat);
|
|
|
|
|
return Handle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::RebuildHandles()
|
|
|
|
|
{
|
|
|
|
|
for (UStaticMeshComponent* Handle : PointHandles)
|
|
|
|
|
{
|
|
|
|
|
if (Handle) Handle->DestroyComponent();
|
|
|
|
|
}
|
|
|
|
|
PointHandles.Empty();
|
|
|
|
|
for (int32 i = 0; i < GetNumPoints(); i++)
|
|
|
|
|
{
|
|
|
|
|
PointHandles.Add(CreatePointHandle(GetPointLocation(i)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 APS_BehaviorEditor_AISpline::GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius) const
|
|
|
|
|
{
|
|
|
|
|
int32 BestIndex = -1;
|
|
|
|
|
float BestDist = MAX_FLT;
|
|
|
|
|
for (int32 i = 0; i < PointHandles.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (!PointHandles[i] || !PointHandles[i]->IsVisible()) continue;
|
|
|
|
|
const FVector HandlePos = PointHandles[i]->GetComponentLocation();
|
|
|
|
|
const FVector ToHandle = HandlePos - RayOrigin;
|
|
|
|
|
const float Proj = FVector::DotProduct(ToHandle, RayDirection);
|
|
|
|
|
if (Proj < 0.0f) continue;
|
|
|
|
|
const FVector ClosestPoint = RayOrigin + RayDirection * Proj;
|
|
|
|
|
const float Dist = FVector::Dist(ClosestPoint, HandlePos);
|
|
|
|
|
if (Dist < SphereRadius && Proj < BestDist)
|
|
|
|
|
{
|
|
|
|
|
BestDist = Proj;
|
|
|
|
|
BestIndex = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return BestIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::SetHandlesVisible(bool bVisible)
|
|
|
|
|
{
|
|
|
|
|
for (UStaticMeshComponent* Handle : PointHandles)
|
|
|
|
|
{
|
|
|
|
|
if (Handle)
|
|
|
|
|
{
|
|
|
|
|
Handle->SetVisibility(bVisible);
|
|
|
|
|
Handle->SetCollisionEnabled(bVisible ? ECollisionEnabled::QueryOnly : ECollisionEnabled::NoCollision);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::SetSelected(bool bSelected)
|
|
|
|
|
{
|
|
|
|
|
if (SplineMeshComp)
|
|
|
|
|
{
|
|
|
|
|
UMaterialInstanceDynamic* Mat = bSelected ? SplineMatSelected : SplineMat;
|
|
|
|
|
if (Mat) SplineMeshComp->SetMaterial(0, Mat);
|
|
|
|
|
}
|
|
|
|
|
if (CenterCubeMat)
|
|
|
|
|
{
|
|
|
|
|
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), bSelected ? SplineSelectedColor : SplineColor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool APS_BehaviorEditor_AISpline::IsCenterCube(const UPrimitiveComponent* Comp) const
|
|
|
|
|
{
|
|
|
|
|
return Comp && Comp == CenterCube;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool APS_BehaviorEditor_AISpline::IsSplineMesh(const UPrimitiveComponent* Comp) const
|
|
|
|
|
{
|
|
|
|
|
return Comp && Comp == SplineMeshComp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::HighlightHandle(int32 Index)
|
|
|
|
|
{
|
|
|
|
|
for (int32 i = 0; i < PointHandles.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (PointHandles[i])
|
|
|
|
|
{
|
|
|
|
|
PointHandles[i]->SetMaterial(0, (i == Index) ? HandleHighlightMat : HandleMat);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::ClearHandleHighlight()
|
|
|
|
|
{
|
|
|
|
|
for (UStaticMeshComponent* Handle : PointHandles)
|
|
|
|
|
{
|
|
|
|
|
if (Handle && HandleMat) Handle->SetMaterial(0, HandleMat);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Serialization ----
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::ExportToCustomProperties(TMap<FString, FString>& OutProperties) const
|
|
|
|
|
{
|
|
|
|
|
// Spline points
|
|
|
|
|
const int32 Num = GetNumPoints();
|
|
|
|
|
OutProperties.Add(TEXT("SplinePointCount"), FString::FromInt(Num));
|
|
|
|
|
for (int32 i = 0; i < Num; i++)
|
|
|
|
|
{
|
|
|
|
|
const FVector Loc = GetPointLocation(i);
|
|
|
|
|
OutProperties.Add(
|
|
|
|
|
FString::Printf(TEXT("SplinePoint_%d"), i),
|
|
|
|
|
FString::Printf(TEXT("X=%.4f Y=%.4f Z=%.4f"), Loc.X, Loc.Y, Loc.Z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Visual properties
|
|
|
|
|
OutProperties.Add(TEXT("SplineColor"), SplineColor.ToString());
|
|
|
|
|
OutProperties.Add(TEXT("SplineSelectedColor"), SplineSelectedColor.ToString());
|
|
|
|
|
|
|
|
|
|
// AI properties
|
|
|
|
|
OutProperties.Add(TEXT("AISplineCategory"), FString::FromInt(static_cast<int32>(SplineCategory)));
|
|
|
|
|
OutProperties.Add(TEXT("AIBidirectional"), bBidirectional ? TEXT("1") : TEXT("0"));
|
|
|
|
|
OutProperties.Add(TEXT("AIWalkSpeed"), FString::SanitizeFloat(SplineWalkSpeed));
|
|
|
|
|
OutProperties.Add(TEXT("AIPriority"), FString::FromInt(Priority));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void APS_BehaviorEditor_AISpline::ImportFromCustomProperties(const TMap<FString, FString>& Properties)
|
|
|
|
|
{
|
|
|
|
|
// Spline points
|
|
|
|
|
const FString* CountStr = Properties.Find(TEXT("SplinePointCount"));
|
|
|
|
|
if (CountStr)
|
|
|
|
|
{
|
|
|
|
|
const int32 Num = FCString::Atoi(**CountStr);
|
|
|
|
|
if (Num >= 2)
|
|
|
|
|
{
|
|
|
|
|
TArray<FVector> Points;
|
|
|
|
|
for (int32 i = 0; i < Num; i++)
|
|
|
|
|
{
|
|
|
|
|
const FString Key = FString::Printf(TEXT("SplinePoint_%d"), i);
|
|
|
|
|
const FString* ValStr = Properties.Find(Key);
|
|
|
|
|
if (!ValStr) continue;
|
|
|
|
|
FVector Loc;
|
|
|
|
|
if (Loc.InitFromString(*ValStr)) Points.Add(Loc);
|
|
|
|
|
}
|
|
|
|
|
if (Points.Num() >= 2) SetAllPointLocations(Points);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Visual properties
|
|
|
|
|
if (const FString* ColorStr = Properties.Find(TEXT("SplineColor")))
|
|
|
|
|
SplineColor.InitFromString(*ColorStr);
|
|
|
|
|
if (const FString* SelColorStr = Properties.Find(TEXT("SplineSelectedColor")))
|
|
|
|
|
SplineSelectedColor.InitFromString(*SelColorStr);
|
|
|
|
|
|
|
|
|
|
// AI properties
|
|
|
|
|
if (const FString* CatStr = Properties.Find(TEXT("AISplineCategory")))
|
|
|
|
|
SplineCategory = static_cast<EPS_AI_Behavior_NPCType>(FCString::Atoi(**CatStr));
|
|
|
|
|
if (const FString* BiStr = Properties.Find(TEXT("AIBidirectional")))
|
|
|
|
|
bBidirectional = FCString::Atoi(**BiStr) != 0;
|
|
|
|
|
if (const FString* SpeedStr = Properties.Find(TEXT("AIWalkSpeed")))
|
|
|
|
|
SplineWalkSpeed = FCString::Atof(**SpeedStr);
|
|
|
|
|
if (const FString* PriStr = Properties.Find(TEXT("AIPriority")))
|
|
|
|
|
Priority = FCString::Atoi(**PriStr);
|
|
|
|
|
|
|
|
|
|
UpdateSplineMaterials();
|
|
|
|
|
}
|