328 lines
11 KiB
C++
328 lines
11 KiB
C++
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
|
|
|
|
#include "CesiumFlyToComponent.h"
|
|
#include "CesiumGeoreference.h"
|
|
#include "CesiumGlobeAnchorComponent.h"
|
|
#include "CesiumWgs84Ellipsoid.h"
|
|
#include "Curves/CurveFloat.h"
|
|
#include "GameFramework/Controller.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "UObject/ConstructorHelpers.h"
|
|
#include "VecMath.h"
|
|
|
|
#include <glm/gtc/quaternion.hpp>
|
|
|
|
UCesiumFlyToComponent::UCesiumFlyToComponent() {
|
|
// Structure to hold one-time initialization
|
|
struct FConstructorStatics {
|
|
ConstructorHelpers::FObjectFinder<UCurveFloat> ProgressCurve;
|
|
ConstructorHelpers::FObjectFinder<UCurveFloat> HeightPercentageCurve;
|
|
ConstructorHelpers::FObjectFinder<UCurveFloat> MaximumHeightByDistanceCurve;
|
|
FConstructorStatics()
|
|
: ProgressCurve(TEXT(
|
|
"/CesiumForUnreal/Curves/FlyTo/Curve_CesiumFlyToDefaultProgress_Float.Curve_CesiumFlyToDefaultProgress_Float")),
|
|
HeightPercentageCurve(TEXT(
|
|
"/CesiumForUnreal/Curves/FlyTo/Curve_CesiumFlyToDefaultHeightPercentage_Float.Curve_CesiumFlyToDefaultHeightPercentage_Float")),
|
|
MaximumHeightByDistanceCurve(TEXT(
|
|
"/CesiumForUnreal/Curves/FlyTo/Curve_CesiumFlyToDefaultMaximumHeightByDistance_Float.Curve_CesiumFlyToDefaultMaximumHeightByDistance_Float")) {
|
|
}
|
|
};
|
|
static FConstructorStatics ConstructorStatics;
|
|
|
|
this->ProgressCurve = ConstructorStatics.ProgressCurve.Object;
|
|
this->HeightPercentageCurve = ConstructorStatics.HeightPercentageCurve.Object;
|
|
this->MaximumHeightByDistanceCurve =
|
|
ConstructorStatics.MaximumHeightByDistanceCurve.Object;
|
|
|
|
this->PrimaryComponentTick.bCanEverTick = true;
|
|
}
|
|
|
|
void UCesiumFlyToComponent::FlyToLocationEarthCenteredEarthFixed(
|
|
const FVector& EarthCenteredEarthFixedDestination,
|
|
double YawAtDestination,
|
|
double PitchAtDestination,
|
|
bool CanInterruptByMoving) {
|
|
if (this->_flightInProgress) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Error,
|
|
TEXT("Cannot start a flight because one is already in progress."));
|
|
return;
|
|
}
|
|
|
|
UCesiumGlobeAnchorComponent* GlobeAnchor = this->GetGlobeAnchor();
|
|
if (!IsValid(GlobeAnchor)) {
|
|
return;
|
|
}
|
|
|
|
PitchAtDestination = glm::clamp(PitchAtDestination, -89.99, 89.99);
|
|
|
|
// Compute source location in ECEF
|
|
FVector ecefSource = GlobeAnchor->GetEarthCenteredEarthFixedPosition();
|
|
|
|
// Obtain Ellipsoid
|
|
UCesiumEllipsoid* ellipsoid =
|
|
GlobeAnchor->ResolveGeoreference()->GetEllipsoid();
|
|
|
|
check(IsValid(ellipsoid));
|
|
|
|
// Create curve
|
|
std::optional<CesiumGeospatial::SimplePlanarEllipsoidCurve> curve =
|
|
CesiumGeospatial::SimplePlanarEllipsoidCurve::
|
|
fromEarthCenteredEarthFixedCoordinates(
|
|
ellipsoid->GetNativeEllipsoid(),
|
|
glm::dvec3(ecefSource.X, ecefSource.Y, ecefSource.Z),
|
|
glm::dvec3(
|
|
EarthCenteredEarthFixedDestination.X,
|
|
EarthCenteredEarthFixedDestination.Y,
|
|
EarthCenteredEarthFixedDestination.Z));
|
|
|
|
if (!curve.has_value()) {
|
|
return;
|
|
}
|
|
|
|
this->_currentCurve =
|
|
MakeUnique<CesiumGeospatial::SimplePlanarEllipsoidCurve>(curve.value());
|
|
|
|
this->_length = (EarthCenteredEarthFixedDestination - ecefSource).Length();
|
|
|
|
// The source and destination rotations are expressed in East-South-Up
|
|
// coordinates.
|
|
this->_sourceRotation = this->GetCurrentRotationEastSouthUp();
|
|
this->_destinationRotation =
|
|
FRotator(PitchAtDestination, YawAtDestination, 0.0).Quaternion();
|
|
|
|
this->_currentFlyTime = 0.0f;
|
|
|
|
// Compute a wanted height from curve
|
|
this->_maxHeight = 0.0;
|
|
if (this->HeightPercentageCurve != NULL) {
|
|
this->_maxHeight = 30000.0;
|
|
if (this->MaximumHeightByDistanceCurve != NULL) {
|
|
this->_maxHeight =
|
|
this->MaximumHeightByDistanceCurve->GetFloatValue(this->_length);
|
|
}
|
|
}
|
|
|
|
// Tell the tick we will be flying from now
|
|
this->_canInterruptByMoving = CanInterruptByMoving;
|
|
this->_previousPositionEcef = ecefSource;
|
|
this->_flightInProgress = true;
|
|
this->_destinationEcef = EarthCenteredEarthFixedDestination;
|
|
}
|
|
|
|
void UCesiumFlyToComponent::FlyToLocationLongitudeLatitudeHeight(
|
|
const FVector& LongitudeLatitudeHeightDestination,
|
|
double YawAtDestination,
|
|
double PitchAtDestination,
|
|
bool CanInterruptByMoving) {
|
|
UCesiumEllipsoid* ellipsoid =
|
|
this->GetGlobeAnchor()->ResolveGeoreference()->GetEllipsoid();
|
|
|
|
FVector Ecef =
|
|
ellipsoid->LongitudeLatitudeHeightToEllipsoidCenteredEllipsoidFixed(
|
|
LongitudeLatitudeHeightDestination);
|
|
this->FlyToLocationEarthCenteredEarthFixed(
|
|
Ecef,
|
|
YawAtDestination,
|
|
PitchAtDestination,
|
|
CanInterruptByMoving);
|
|
}
|
|
|
|
void UCesiumFlyToComponent::FlyToLocationUnreal(
|
|
const FVector& UnrealDestination,
|
|
double YawAtDestination,
|
|
double PitchAtDestination,
|
|
bool CanInterruptByMoving) {
|
|
UCesiumGlobeAnchorComponent* GlobeAnchor = this->GetGlobeAnchor();
|
|
if (!IsValid(GlobeAnchor)) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT(
|
|
"CesiumFlyToComponent cannot FlyToLocationUnreal because the Actor has no CesiumGlobeAnchorComponent."));
|
|
return;
|
|
}
|
|
|
|
ACesiumGeoreference* Georeference = GlobeAnchor->ResolveGeoreference();
|
|
if (!IsValid(Georeference)) {
|
|
UE_LOG(
|
|
LogCesium,
|
|
Warning,
|
|
TEXT(
|
|
"CesiumFlyToComponent cannot FlyToLocationUnreal because the globe anchor has no associated CesiumGeoreference."));
|
|
return;
|
|
}
|
|
|
|
this->FlyToLocationEarthCenteredEarthFixed(
|
|
Georeference->TransformUnrealPositionToEarthCenteredEarthFixed(
|
|
UnrealDestination),
|
|
YawAtDestination,
|
|
PitchAtDestination,
|
|
CanInterruptByMoving);
|
|
}
|
|
|
|
void UCesiumFlyToComponent::InterruptFlight() {
|
|
this->_flightInProgress = false;
|
|
|
|
UCesiumGlobeAnchorComponent* GlobeAnchor = this->GetGlobeAnchor();
|
|
if (IsValid(GlobeAnchor)) {
|
|
// fix Actor roll to 0.0
|
|
FRotator currentRotator = this->GetCurrentRotationEastSouthUp().Rotator();
|
|
currentRotator.Roll = 0.0;
|
|
FQuat eastSouthUpRotation = currentRotator.Quaternion();
|
|
this->SetCurrentRotationEastSouthUp(eastSouthUpRotation);
|
|
}
|
|
|
|
// Trigger callback accessible from BP
|
|
UE_LOG(LogCesium, Verbose, TEXT("Broadcasting OnFlightInterrupt"));
|
|
OnFlightInterrupted.Broadcast();
|
|
}
|
|
|
|
void UCesiumFlyToComponent::TickComponent(
|
|
float DeltaTime,
|
|
ELevelTick TickType,
|
|
FActorComponentTickFunction* ThisTickFunction) {
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
|
|
if (!this->_flightInProgress) {
|
|
return;
|
|
}
|
|
|
|
check(this->_currentCurve);
|
|
|
|
UCesiumGlobeAnchorComponent* GlobeAnchor = this->GetGlobeAnchor();
|
|
if (!IsValid(GlobeAnchor)) {
|
|
return;
|
|
}
|
|
|
|
if (this->_canInterruptByMoving &&
|
|
this->_previousPositionEcef !=
|
|
GlobeAnchor->GetEarthCenteredEarthFixedPosition()) {
|
|
this->InterruptFlight();
|
|
return;
|
|
}
|
|
|
|
this->_currentFlyTime += DeltaTime;
|
|
|
|
// In order to accelerate at start and slow down at end, we use a progress
|
|
// profile curve
|
|
float flyPercentage;
|
|
if (this->_currentFlyTime >= this->Duration) {
|
|
flyPercentage = 1.0f;
|
|
} else if (this->ProgressCurve) {
|
|
flyPercentage = glm::clamp(
|
|
this->ProgressCurve->GetFloatValue(
|
|
this->_currentFlyTime / this->Duration),
|
|
0.0f,
|
|
1.0f);
|
|
} else {
|
|
flyPercentage = this->_currentFlyTime / this->Duration;
|
|
}
|
|
|
|
// If we reached the end, set actual destination location and
|
|
// orientation
|
|
if (flyPercentage >= 1.0f ||
|
|
(this->_length == 0.0 &&
|
|
this->_sourceRotation == this->_destinationRotation)) {
|
|
GlobeAnchor->MoveToEarthCenteredEarthFixedPosition(this->_destinationEcef);
|
|
this->SetCurrentRotationEastSouthUp(this->_destinationRotation);
|
|
this->_flightInProgress = false;
|
|
this->_currentFlyTime = 0.0f;
|
|
|
|
// Trigger callback accessible from BP
|
|
UE_LOG(LogCesium, Verbose, TEXT("Broadcasting OnFlightComplete"));
|
|
OnFlightComplete.Broadcast();
|
|
|
|
return;
|
|
}
|
|
|
|
// Get altitude offset from profile curve if one is specified
|
|
double altitudeOffset = 0.0;
|
|
if (this->_maxHeight != 0.0 && this->HeightPercentageCurve) {
|
|
double curveOffset =
|
|
this->_maxHeight *
|
|
this->HeightPercentageCurve->GetFloatValue(flyPercentage);
|
|
altitudeOffset = curveOffset;
|
|
}
|
|
|
|
glm::dvec3 currentPositionEcef =
|
|
_currentCurve->getPosition(flyPercentage, altitudeOffset);
|
|
|
|
FVector currentPositionVector(
|
|
currentPositionEcef.x,
|
|
currentPositionEcef.y,
|
|
currentPositionEcef.z);
|
|
|
|
// Set Location
|
|
GlobeAnchor->MoveToEarthCenteredEarthFixedPosition(currentPositionVector);
|
|
|
|
// Interpolate rotation in the ESU frame. The local ESU ControlRotation will
|
|
// be transformed to the appropriate world rotation as we fly.
|
|
FQuat currentQuat = FQuat::Slerp(
|
|
this->_sourceRotation,
|
|
this->_destinationRotation,
|
|
flyPercentage);
|
|
this->SetCurrentRotationEastSouthUp(currentQuat);
|
|
|
|
this->_previousPositionEcef =
|
|
GlobeAnchor->GetEarthCenteredEarthFixedPosition();
|
|
}
|
|
|
|
FQuat UCesiumFlyToComponent::GetCurrentRotationEastSouthUp() {
|
|
if (this->RotationToUse != ECesiumFlyToRotation::Actor) {
|
|
APawn* Pawn = Cast<APawn>(this->GetOwner());
|
|
const TObjectPtr<AController> Controller =
|
|
IsValid(Pawn) ? Pawn->Controller : nullptr;
|
|
if (Controller) {
|
|
FRotator rotator = Controller->GetControlRotation();
|
|
if (this->RotationToUse ==
|
|
ECesiumFlyToRotation::ControlRotationInUnreal) {
|
|
USceneComponent* PawnRoot = Pawn->GetRootComponent();
|
|
if (IsValid(PawnRoot)) {
|
|
rotator = GetGlobeAnchor()
|
|
->ResolveGeoreference()
|
|
->TransformUnrealRotatorToEastSouthUp(
|
|
Controller->GetControlRotation(),
|
|
PawnRoot->GetRelativeLocation());
|
|
}
|
|
}
|
|
return rotator.Quaternion();
|
|
}
|
|
}
|
|
|
|
return this->GetGlobeAnchor()->GetEastSouthUpRotation();
|
|
}
|
|
|
|
void UCesiumFlyToComponent::SetCurrentRotationEastSouthUp(
|
|
const FQuat& EastSouthUpRotation) {
|
|
bool controlRotationSet = false;
|
|
|
|
if (this->RotationToUse != ECesiumFlyToRotation::Actor) {
|
|
APawn* Pawn = Cast<APawn>(this->GetOwner());
|
|
const TObjectPtr<AController> Controller =
|
|
IsValid(Pawn) ? Pawn->Controller : nullptr;
|
|
if (Controller) {
|
|
FRotator rotator = EastSouthUpRotation.Rotator();
|
|
if (this->RotationToUse ==
|
|
ECesiumFlyToRotation::ControlRotationInUnreal) {
|
|
USceneComponent* PawnRoot = Pawn->GetRootComponent();
|
|
if (IsValid(PawnRoot)) {
|
|
rotator = GetGlobeAnchor()
|
|
->ResolveGeoreference()
|
|
->TransformEastSouthUpRotatorToUnreal(
|
|
EastSouthUpRotation.Rotator(),
|
|
PawnRoot->GetRelativeLocation());
|
|
}
|
|
}
|
|
|
|
Controller->SetControlRotation(EastSouthUpRotation.Rotator());
|
|
controlRotationSet = true;
|
|
}
|
|
}
|
|
|
|
if (!controlRotationSet) {
|
|
this->GetGlobeAnchor()->SetEastSouthUpRotation(EastSouthUpRotation);
|
|
}
|
|
}
|