Files
UnrealEngine/Engine/Source/Runtime/CinematicCamera/Private/CameraRig_Rail.cpp
2025-05-18 13:04:45 +08:00

238 lines
7.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CameraRig_Rail.h"
#include "UObject/ConstructorHelpers.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Engine/CollisionProfile.h"
#include "Engine/World.h"
#include "Components/SplineComponent.h"
#include "Components/SplineMeshComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CameraRig_Rail)
#define LOCTEXT_NAMESPACE "CameraRig_Rail"
ACameraRig_Rail::ACameraRig_Rail(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
CurrentPositionOnRail = 0.f;
bLockOrientationToRail = false;
#if WITH_EDITORONLY_DATA
bShowRailVisualization = true;
PreviewMeshScale = 1.f;
#endif
// Create components
TransformComponent = CreateDefaultSubobject<USceneComponent>(TEXT("TransformComponent"));
// Make the scene component the root component
RootComponent = TransformComponent;
RailSplineComponent = CreateDefaultSubobject<USplineComponent>(TEXT("RailSplineComponent"));
RailSplineComponent->SetupAttachment(TransformComponent);
RailCameraMount = CreateDefaultSubobject<USceneComponent>(TEXT("RailCameraMount"));
RailCameraMount->SetupAttachment(RailSplineComponent);
#if WITH_EDITORONLY_DATA
if (!IsRunningDedicatedServer())
{
static ConstructorHelpers::FObjectFinder<UStaticMesh> RailMesh(TEXT("/Engine/EditorMeshes/Camera/SM_RailRig_Track.SM_RailRig_Track"));
PreviewRailStaticMesh = RailMesh.Object;
static ConstructorHelpers::FObjectFinder<UStaticMesh> MountMesh(TEXT("/Engine/EditorMeshes/Camera/SM_RailRig_Mount.SM_RailRig_Mount"));
PreviewMesh_Mount = CreateOptionalDefaultSubobject<UStaticMeshComponent>(TEXT("PreviewMesh_Mount"));
if (PreviewMesh_Mount)
{
PreviewMesh_Mount->SetCanEverAffectNavigation(false);
PreviewMesh_Mount->SetStaticMesh(MountMesh.Object);
PreviewMesh_Mount->SetIsVisualizationComponent(true);
PreviewMesh_Mount->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
PreviewMesh_Mount->bHiddenInGame = true;
PreviewMesh_Mount->CastShadow = false;
PreviewMesh_Mount->SetupAttachment(RailCameraMount);
}
}
#endif
}
#if WITH_EDITORONLY_DATA
USplineMeshComponent* ACameraRig_Rail::CreateSplinePreviewSegment()
{
USplineMeshComponent* const Segment = NewObject<USplineMeshComponent>(this);
if (Segment)
{
Segment->SetStaticMesh(PreviewRailStaticMesh);
Segment->SetMobility(EComponentMobility::Movable);
Segment->SetIsVisualizationComponent(true);
Segment->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
Segment->bHiddenInGame = true;
Segment->CastShadow = false;
Segment->SetupAttachment(TransformComponent);
Segment->RegisterComponent();
}
return Segment;
}
void ACameraRig_Rail::UpdatePreviewMeshes()
{
if (RailSplineComponent)
{
if (PreviewRailStaticMesh)
{
int32 const NumNeededPreviewMeshes = FMath::Max(0, RailSplineComponent->GetNumberOfSplineSegments());
// make sure our preview mesh array is correctly sized and populated
{
PreviewRailMeshSegments.RemoveAll([](USplineMeshComponent* Segment) { return Segment == nullptr; });
int32 const NumExistingPreviewMeshes = PreviewRailMeshSegments.Num();
if (NumExistingPreviewMeshes > NumNeededPreviewMeshes)
{
// we have too many meshes, remove some
int32 const NumToRemove = NumExistingPreviewMeshes - NumNeededPreviewMeshes;
for (int Idx = 0; Idx < NumToRemove; ++Idx)
{
USplineMeshComponent* const ElementToRemove = PreviewRailMeshSegments.Pop();
ElementToRemove->UnregisterComponent();
}
}
else if (NumExistingPreviewMeshes < NumNeededPreviewMeshes)
{
int32 const NumToAdd = NumNeededPreviewMeshes - NumExistingPreviewMeshes;
for (int32 Idx = 0; Idx < NumToAdd; ++Idx)
{
USplineMeshComponent* PreviewMesh = CreateSplinePreviewSegment();
PreviewRailMeshSegments.Add(PreviewMesh);
}
}
check(PreviewRailMeshSegments.Num() == NumNeededPreviewMeshes);
}
for (int PtIdx = 0; PtIdx < NumNeededPreviewMeshes; ++PtIdx)
{
FVector StartLoc = RailSplineComponent->GetLocationAtSplinePoint(PtIdx, ESplineCoordinateSpace::Local);
FVector StartTangent = RailSplineComponent->GetLeaveTangentAtSplinePoint(PtIdx, ESplineCoordinateSpace::Local);
FVector EndTangent = RailSplineComponent->GetArriveTangentAtSplinePoint(PtIdx + 1,ESplineCoordinateSpace::Local);
FVector EndLoc = RailSplineComponent->GetLocationAtSplinePoint(PtIdx + 1, ESplineCoordinateSpace::Local);
USplineMeshComponent* const SplineMeshComp = PreviewRailMeshSegments[PtIdx];
if (SplineMeshComp)
{
SplineMeshComp->SetVisibility(bShowRailVisualization,false);
SplineMeshComp->SetStartScale(FVector2D(PreviewMeshScale, PreviewMeshScale),false);
SplineMeshComp->SetEndScale(FVector2D(PreviewMeshScale, PreviewMeshScale),false);
SplineMeshComp->SetForwardAxis(ESplineMeshAxis::Z,false);
SplineMeshComp->SetStartAndEnd(StartLoc, StartTangent, EndLoc, EndTangent, false);
SplineMeshComp->UpdateMesh();
}
}
// Unregister any owned components that aren't in PreviewRailMeshSegments
TArray<USplineMeshComponent*> OwnedSplineMeshComponents;
GetComponents(OwnedSplineMeshComponents);
for (USplineMeshComponent* OwnedComponent : OwnedSplineMeshComponents)
{
if (!PreviewRailMeshSegments.Contains(OwnedComponent) && OwnedComponent->IsRegistered())
{
OwnedComponent->UnregisterComponent();
}
}
}
// make visualization of the mount follow the contour of the rail
if (PreviewMesh_Mount)
{
PreviewMesh_Mount->SetVisibility(bShowRailVisualization);
PreviewMesh_Mount->SetWorldScale3D(FVector(PreviewMeshScale, PreviewMeshScale, PreviewMeshScale));
}
}
}
#endif
void ACameraRig_Rail::UpdateRailComponents()
{
if (!GetWorld())
{
return;
}
if (RailSplineComponent && RailCameraMount)
{
float const SplineLen = RailSplineComponent->GetSplineLength();
FVector const MountPos = RailSplineComponent->GetLocationAtDistanceAlongSpline(CurrentPositionOnRail*SplineLen, ESplineCoordinateSpace::World);
if (bLockOrientationToRail)
{
FQuat const RailRot = RailSplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentPositionOnRail*SplineLen, ESplineCoordinateSpace::World);
RailCameraMount->SetWorldTransform(FTransform(RailRot, MountPos));
}
else
{
RailCameraMount->SetWorldLocation(MountPos);
}
}
#if WITH_EDITOR
if (GIsEditor)
{
const UWorld* const MyWorld = GetWorld();
if (MyWorld && !MyWorld->IsGameWorld())
{
// set up preview mesh to match #todo
UpdatePreviewMeshes();
}
}
#endif
}
USceneComponent* ACameraRig_Rail::GetDefaultAttachComponent() const
{
return RailCameraMount;
}
#if WITH_EDITOR
void ACameraRig_Rail::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
UpdateRailComponents();
}
void ACameraRig_Rail::PostEditUndo()
{
Super::PostEditUndo();
UpdateRailComponents();
}
void ACameraRig_Rail::PostEditMove(bool bFinished)
{
Super::PostEditMove(bFinished);
UpdateRailComponents();
}
#endif
void ACameraRig_Rail::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// feed exposed API into underlying components
UpdateRailComponents();
}
bool ACameraRig_Rail::ShouldTickIfViewportsOnly() const
{
return true;
}
#undef LOCTEXT_NAMESPACE