Skip to content

Commit

Permalink
Add anchor tests, fix SnapLocalUpToEllipsoidNormal method.
Browse files Browse the repository at this point in the history
  • Loading branch information
kring committed Aug 14, 2023
1 parent 6954bb1 commit 9240ab8
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 12 deletions.
26 changes: 14 additions & 12 deletions Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ void UCesiumGlobeAnchorComponent::SetGeoreference(
}
}

ACesiumGeoreference*
UCesiumGlobeAnchorComponent::GetResolvedGeoreference() const {
return this->ResolvedGeoreference;
}

FVector
UCesiumGlobeAnchorComponent::GetEarthCenteredEarthFixedPosition() const {
if (!this->_actorToECEFIsValid) {
Expand Down Expand Up @@ -184,9 +189,16 @@ void UCesiumGlobeAnchorComponent::SnapLocalUpToEllipsoidNormal() {
FMatrix alignmentRotation =
FQuat::FindBetween(up, ellipsoidNormal).ToMatrix();

// Compute the new actor rotation and apply it
// Apply the new rotation to the Actor->ECEF transform.
// Note that FMatrix multiplication order is opposite glm::dmat4
// multiplication order!
FMatrix newActorToECEF =
alignmentRotation * this->ActorToEarthCenteredEarthFixedMatrix;
this->ActorToEarthCenteredEarthFixedMatrix * alignmentRotation;

// We don't want to rotate the origin, though, so re-set it.
newActorToECEF.SetOrigin(
this->ActorToEarthCenteredEarthFixedMatrix.GetOrigin());

this->_updateFromNativeGlobeAnchor(createNativeGlobeAnchor(newActorToECEF));

#if WITH_EDITOR
Expand Down Expand Up @@ -250,16 +262,6 @@ void UCesiumGlobeAnchorComponent::InvalidateResolvedGeoreference() {
}

FVector UCesiumGlobeAnchorComponent::GetLongitudeLatitudeHeight() const {
if (!this->_actorToECEFIsValid || !this->ResolvedGeoreference) {
UE_LOG(
LogCesium,
Warning,
TEXT(
"CesiumGlobeAnchorComponent %s globe position is invalid because the component is not yet registered."),
*this->GetName());
return FVector(0.0);
}

return UCesiumWgs84Ellipsoid::
EarthCenteredEarthFixedToLongitudeLatitudeHeight(
this->GetEarthCenteredEarthFixedPosition());
Expand Down
223 changes: 223 additions & 0 deletions Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2020-2023 CesiumGS, Inc. and Contributors

#include "CesiumGeoreference.h"
#include "CesiumGlobeAnchorComponent.h"
#include "CesiumTestHelpers.h"
#include "CesiumWgs84Ellipsoid.h"
#include "Misc/AutomationTest.h"

BEGIN_DEFINE_SPEC(
FCesiumGlobeAnchorSpec,
"Cesium.GlobeAnchor",
EAutomationTestFlags::ApplicationContextMask |
EAutomationTestFlags::ProductFilter)

TObjectPtr<AActor> pActor;
TObjectPtr<UCesiumGlobeAnchorComponent> pGlobeAnchor;

END_DEFINE_SPEC(FCesiumGlobeAnchorSpec)

void FCesiumGlobeAnchorSpec::Define() {
BeforeEach([this]() {
UWorld* pWorld = CesiumTestHelpers::getGlobalWorldContext();
ACesiumGeoreference* pGeoreference =
ACesiumGeoreference::GetDefaultGeoreference(pWorld);
pGeoreference->SetOriginLongitudeLatitudeHeight(FVector(1.0, 2.0, 3.0));

this->pActor = pWorld->SpawnActor<AActor>();
this->pActor->AddComponentByClass(
USceneComponent::StaticClass(),
false,
FTransform::Identity,
false);
this->pActor->SetActorRelativeTransform(FTransform());

this->pGlobeAnchor =
Cast<UCesiumGlobeAnchorComponent>(pActor->AddComponentByClass(
UCesiumGlobeAnchorComponent::StaticClass(),
false,
FTransform::Identity,
false));
});

It("immediately syncs globe position from transform when added", [this]() {
TestEqual("Longitude", this->pGlobeAnchor->GetLongitude(), 1.0);
TestEqual("Latitude", this->pGlobeAnchor->GetLatitude(), 2.0);
TestEqual("Height", this->pGlobeAnchor->GetHeight(), 3.0);
});

It("maintains globe position when switching to a new georeference", [this]() {
FTransform beforeTransform = this->pActor->GetActorTransform();
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();

UWorld* pWorld = this->pActor->GetWorld();
ACesiumGeoreference* pNewGeoref = pWorld->SpawnActor<ACesiumGeoreference>();
pNewGeoref->SetOriginLongitudeLatitudeHeight(FVector(10.0, 20.0, 30.0));
this->pGlobeAnchor->SetGeoreference(pNewGeoref);

TestEqual(
"ResolvedGeoreference",
this->pGlobeAnchor->GetResolvedGeoreference(),
pNewGeoref);
TestFalse(
"Transforms are equal",
this->pActor->GetActorTransform().Equals(beforeTransform));
TestEqual(
"Globe Position",
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
beforeLLH);
});

It("updates actor transform when globe anchor position is changed", [this]() {
FTransform beforeTransform = this->pActor->GetActorTransform();
this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(FVector(4.0, 5.0, 6.0));
TestEqual(
"LongitudeLatitudeHeight",
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
FVector(4.0, 5.0, 6.0));
TestFalse(
"Transforms are equal",
this->pActor->GetActorTransform().Equals(beforeTransform));
});

It("updates globe anchor position when actor transform is changed", [this]() {
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();
this->pActor->SetActorLocation(FVector(1000.0, 2000.0, 3000.0));
TestNotEqual(
"globe position",
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
beforeLLH);
});

It("allows the actor transform to be set when not registered", [this]() {
FVector beforeLLH = this->pGlobeAnchor->GetLongitudeLatitudeHeight();

this->pGlobeAnchor->UnregisterComponent();
this->pActor->SetActorLocation(FVector(1000.0, 2000.0, 3000.0));

// Globe position doesn't initially update while unregistered
TestEqual(
"globe position",
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
beforeLLH);

// After we re-register, actor transform should be maintained and globe
// transform should be updated.
this->pGlobeAnchor->RegisterComponent();
TestEqual(
"actor position",
this->pActor->GetActorLocation(),
FVector(1000.0, 2000.0, 3000.0));
TestNotEqual(
"globe position",
this->pGlobeAnchor->GetLongitudeLatitudeHeight(),
beforeLLH);
});

It("adjusts orientation for globe when actor position is set immediately after adding anchor",
[this]() {
FRotator beforeRotation = this->pActor->GetActorRotation();

ACesiumGeoreference* pGeoreference =
this->pGlobeAnchor->GetResolvedGeoreference();
this->pActor->SetActorLocation(
pGeoreference->TransformLongitudeLatitudeHeightPositionToUnreal(
FVector(90.0, 2.0, 3.0)));
TestNotEqual(
"rotation",
this->pActor->GetActorRotation(),
beforeRotation);
});

It("adjusts orientation for globe when globe position is set immediately after adding anchor",
[this]() {
FRotator beforeRotation = this->pActor->GetActorRotation();

this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
FVector(90.0, 2.0, 3.0));
TestNotEqual(
"rotation",
this->pActor->GetActorRotation(),
beforeRotation);
});

It("does not adjust orientation for globe when that feature is disabled",
[this]() {
this->pGlobeAnchor->SetAdjustOrientationForGlobeWhenMoving(false);
FRotator beforeRotation = this->pActor->GetActorRotation();

ACesiumGeoreference* pGeoreference =
this->pGlobeAnchor->GetResolvedGeoreference();
this->pActor->SetActorLocation(
pGeoreference->TransformLongitudeLatitudeHeightPositionToUnreal(
FVector(90.0, 2.0, 3.0)));
TestEqual("rotation", this->pActor->GetActorRotation(), beforeRotation);

this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
FVector(45.0, 25.0, 300.0));
TestEqual("rotation", this->pActor->GetActorRotation(), beforeRotation);
});

It("gains correct orientation on call to SnapToEastSouthUp", [this]() {
ACesiumGeoreference* pGeoreference =
this->pGlobeAnchor->GetResolvedGeoreference();

this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
FVector(-20.0, -10.0, 1000.0));
this->pGlobeAnchor->SnapToEastSouthUp();

const FTransform& transform = this->pActor->GetActorTransform();
FVector actualEcefEast =
pGeoreference
->TransformUnrealDirectionToEarthCenteredEarthFixed(
transform.TransformVector(FVector::XAxisVector))
.GetSafeNormal();
FVector actualEcefSouth =
pGeoreference
->TransformUnrealDirectionToEarthCenteredEarthFixed(
transform.TransformVector(FVector::YAxisVector))
.GetSafeNormal();
FVector actualEcefUp =
pGeoreference
->TransformUnrealDirectionToEarthCenteredEarthFixed(
transform.TransformVector(FVector::ZAxisVector))
.GetSafeNormal();

FMatrix enuToEcef =
UCesiumWgs84Ellipsoid::EastNorthUpToEarthCenteredEarthFixed(
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition());
FVector expectedEcefEast =
enuToEcef.TransformVector(FVector::XAxisVector).GetSafeNormal();
FVector expectedEcefSouth =
-enuToEcef.TransformVector(FVector::YAxisVector).GetSafeNormal();
FVector expectedEcefUp =
enuToEcef.TransformVector(FVector::ZAxisVector).GetSafeNormal();

TestEqual("east", actualEcefEast, expectedEcefEast);
TestEqual("south", actualEcefSouth, expectedEcefSouth);
TestEqual("up", actualEcefUp, expectedEcefUp);
});

It("gains correct orientation on call to SnapLocalUpToEllipsoidNormal",
[this]() {
ACesiumGeoreference* pGeoreference =
this->pGlobeAnchor->GetResolvedGeoreference();

this->pGlobeAnchor->MoveToLongitudeLatitudeHeight(
FVector(-20.0, -10.0, 1000.0));
this->pActor->SetActorRotation(FQuat::Identity);
this->pGlobeAnchor->SnapLocalUpToEllipsoidNormal();

const FTransform& transform = this->pActor->GetActorTransform();
FVector actualEcefUp =
pGeoreference
->TransformUnrealDirectionToEarthCenteredEarthFixed(
transform.TransformVector(FVector::ZAxisVector))
.GetSafeNormal();

FVector surfaceNormal = UCesiumWgs84Ellipsoid::GeodeticSurfaceNormal(
this->pGlobeAnchor->GetEarthCenteredEarthFixedPosition());

TestEqual("up", actualEcefUp, surfaceNormal);
});
}
17 changes: 17 additions & 0 deletions Source/CesiumRuntime/Public/CesiumGlobeAnchorComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent {
* If this is null, the Component will find and use the first Georeference
* Actor in the level, or create one if necessary. To get the active/effective
* Georeference from Blueprints or C++, use ResolvedGeoreference instead.
*
* If setting this property changes the CesiumGeoreference, the globe position
* will be maintained and the Actor's transform will be updated according to
* the new CesiumGeoreference.
*/
UPROPERTY(
EditAnywhere,
Expand Down Expand Up @@ -97,6 +101,7 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent {
UPROPERTY(
Transient,
BlueprintReadOnly,
BlueprintGetter = GetResolvedGeoreference,
Category = "Cesium",
Meta = (AllowPrivateAccess))
ACesiumGeoreference* ResolvedGeoreference = nullptr;
Expand Down Expand Up @@ -221,6 +226,18 @@ class CESIUMRUNTIME_API UCesiumGlobeAnchorComponent : public UActorComponent {
UFUNCTION(BlueprintSetter)
void SetGeoreference(TSoftObjectPtr<ACesiumGeoreference> NewGeoreference);

/**
* Gets the resolved georeference used by this component. This is not
* serialized because it may point to a Georeference in the PersistentLevel
* while this component is in a sub-level. If the Georeference property is
* specified, however then this property will have the same value.
*
* This property will be null before ResolveGeoreference is called, which
* happens automatically when the component is registered.
*/
UFUNCTION(BlueprintGetter)
ACesiumGeoreference* GetResolvedGeoreference() const;

/**
* Resolves the Cesium Georeference to use with this Component. Returns
* the value of the Georeference property if it is set. Otherwise, finds a
Expand Down

0 comments on commit 9240ab8

Please sign in to comment.