Files
BXSSP_Andriod/Plugins/CesiumForUnreal/Source/CesiumRuntime/Private/CesiumGlobeAnchorComponent.cpp

794 lines
26 KiB
C++

// Copyright 2020-2024 CesiumGS, Inc. and Contributors
#include "CesiumGlobeAnchorComponent.h"
#include "CesiumCustomVersion.h"
#include "CesiumGeometry/Transforms.h"
#include "CesiumGeoreference.h"
#include "CesiumRuntime.h"
#include "CesiumWgs84Ellipsoid.h"
#include "Components/SceneComponent.h"
#include "GameFramework/Actor.h"
#include "VecMath.h"
#include <glm/gtc/quaternion.hpp>
// quick macro for ellipsoid existence check
#define ELLIPSOID_CHECK(thiz, ret) \
if (!IsValid(thiz->GetEllipsoid())) { \
UE_LOG(LogCesium, Error, TEXT("Expected ellipsoid but got nullptr")); \
return ret; \
}
// These are the "changes" that can happen to this component, how it detects
// them, and what it does about them:
//
// ## Actor Transform Changed
//
// * Detected by subscribing to the `TransformUpdated` event of the root
// component of the Actor to which this component is attached. The subscription
// is added in `OnRegister` and removed in `OnUnregister`.
// * Updates the ECEF transform from the new Actor transform.
// * If `AdjustOrientationForGlobeWhenMoving` is enabled, also applies a
// rotation based on the change in surface normal.
//
// ## Globe (ECEF) Position Changed
//
// * Happens when MoveToECEF (or similar) is called explicitly, or position
// properties are changed in the Editor.
// * Updates the Actor transform from the new ECEF transform.
// * If `AdjustOrientationForGlobeWhenMoving` is enabled, also applies a
// rotation based on the change in surface normal.
//
// ## Georeference Changed
//
// * Detected by subscribing to the `GeoreferenceUpdated` event. The
// subscription is added when a new Georeference is resolved in
// `ResolveGeoreference` (in `OnRegister` at the latest) and removed in
// `InvalidateResolvedGeoreference` (in `OnUnregister` and when the
// Georeference property is changed).
// * Updates the Actor transform from the existing ECEF transform.
// * Ignores `AdjustOrientationForGlobeWhenMoving` because the globe position is
// not changing.
//
// ## OriginLocation Changed
//
// * Handled by Unreal's normal `ApplyWorldOffset` mechanism.
namespace {
CesiumGeospatial::GlobeAnchor
createNativeGlobeAnchor(const FMatrix& actorToECEF) {
return CesiumGeospatial::GlobeAnchor(VecMath::createMatrix4D(actorToECEF));
}
} // namespace
TSoftObjectPtr<ACesiumGeoreference>
UCesiumGlobeAnchorComponent::GetGeoreference() const {
return this->Georeference;
}
void UCesiumGlobeAnchorComponent::SetGeoreference(
TSoftObjectPtr<ACesiumGeoreference> NewGeoreference) {
ACesiumGeoreference* pOriginal = this->ResolvedGeoreference;
if (IsValid(pOriginal)) {
pOriginal->OnGeoreferenceUpdated.RemoveAll(this);
}
this->ResolvedGeoreference = nullptr;
this->Georeference = NewGeoreference;
// If this component is currently registered, we need to re-resolve the
// georeference. If it's not, this will happen when it becomes registered.
if (this->IsRegistered()) {
this->ResolveGeoreference();
}
}
ACesiumGeoreference*
UCesiumGlobeAnchorComponent::GetResolvedGeoreference() const {
return this->ResolvedGeoreference;
}
FVector
UCesiumGlobeAnchorComponent::GetEarthCenteredEarthFixedPosition() const {
if (!this->_actorToECEFIsValid) {
// Only log a warning if we're actually in a world. Otherwise we'll spam the
// log when editing a CDO.
if (this->GetWorld()) {
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 this->ActorToEarthCenteredEarthFixedMatrix.GetOrigin();
}
FMatrix
UCesiumGlobeAnchorComponent::GetActorToEarthCenteredEarthFixedMatrix() const {
if (!this->_actorToECEFIsValid) {
const_cast<UCesiumGlobeAnchorComponent*>(this)
->_setNewActorToECEFFromRelativeTransform();
}
return this->ActorToEarthCenteredEarthFixedMatrix;
}
void UCesiumGlobeAnchorComponent::SetActorToEarthCenteredEarthFixedMatrix(
const FMatrix& Value) {
// This method is equivalent to
// CesiumGlobeAnchorImpl::SetNewLocalToGlobeFixedMatrix in Cesium for Unity.
USceneComponent* pOwnerRoot = this->_getRootComponent(/*warnIfNull*/ true);
if (!IsValid(pOwnerRoot)) {
return;
}
// Update with the new ECEF transform, also rotating based on the new position
// if desired.
CesiumGeospatial::GlobeAnchor nativeAnchor =
this->_createOrUpdateNativeGlobeAnchorFromECEF(Value);
this->_updateFromNativeGlobeAnchor(nativeAnchor);
#if WITH_EDITOR
// In the Editor, mark this component and the root component modified so Undo
// works properly.
this->Modify();
pOwnerRoot->Modify();
#endif
}
bool UCesiumGlobeAnchorComponent::GetTeleportWhenUpdatingTransform() const {
return this->TeleportWhenUpdatingTransform;
}
void UCesiumGlobeAnchorComponent::SetTeleportWhenUpdatingTransform(bool Value) {
this->TeleportWhenUpdatingTransform = Value;
}
bool UCesiumGlobeAnchorComponent::GetAdjustOrientationForGlobeWhenMoving()
const {
return this->AdjustOrientationForGlobeWhenMoving;
}
void UCesiumGlobeAnchorComponent::SetAdjustOrientationForGlobeWhenMoving(
bool Value) {
this->AdjustOrientationForGlobeWhenMoving = Value;
}
void UCesiumGlobeAnchorComponent::MoveToEarthCenteredEarthFixedPosition(
const FVector& TargetEcef) {
if (!this->_actorToECEFIsValid)
this->_setNewActorToECEFFromRelativeTransform();
FMatrix newMatrix = this->ActorToEarthCenteredEarthFixedMatrix;
newMatrix.SetOrigin(TargetEcef);
this->SetActorToEarthCenteredEarthFixedMatrix(newMatrix);
}
void UCesiumGlobeAnchorComponent::SnapLocalUpToEllipsoidNormal() {
if (!this->_actorToECEFIsValid || !IsValid(this->ResolvedGeoreference)) {
UE_LOG(
LogCesium,
Error,
TEXT(
"CesiumGlobeAnchorComponent %s globe orientation cannot be changed because the component is not yet registered."),
*this->GetName());
return;
}
ELLIPSOID_CHECK(this->ResolveGeoreference(), );
UCesiumEllipsoid* ellipsoid = this->ResolveGeoreference()->GetEllipsoid();
// Compute the current local up axis of the actor (the +Z axis) in ECEF
FVector up = this->ActorToEarthCenteredEarthFixedMatrix.GetUnitAxis(EAxis::Z);
// Compute the surface normal of the ellipsoid
FVector ellipsoidNormal = ellipsoid->GeodeticSurfaceNormal(
this->GetEarthCenteredEarthFixedPosition());
// Find the shortest rotation to align local up with the ellipsoid normal
FMatrix alignmentRotation =
FQuat::FindBetween(up, ellipsoidNormal).ToMatrix();
// Apply the new rotation to the Actor->ECEF transform.
// Note that FMatrix multiplication order is opposite glm::dmat4
// multiplication order!
FMatrix newActorToECEF =
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
// In the Editor, mark this component modified so Undo works properly.
this->Modify();
#endif
}
void UCesiumGlobeAnchorComponent::SnapToEastSouthUp() {
this->SetEastSouthUpRotation(FQuat::Identity);
#if WITH_EDITOR
// In the Editor, mark this component modified so Undo works properly.
this->Modify();
#endif
}
void UCesiumGlobeAnchorComponent::Sync() {
// If we don't have a actor -> ECEF matrix yet, we must update from the
// actor's root transform.
bool updateFromTransform = !this->_actorToECEFIsValid;
if (!updateFromTransform && this->_lastRelativeTransformIsValid) {
// We may also need to update from the Transform if it has changed
// since the last time we computed the local -> globe fixed matrix.
updateFromTransform = !this->_lastRelativeTransform.Equals(
this->_getCurrentRelativeTransform(),
0.0);
}
if (updateFromTransform) {
this->_setNewActorToECEFFromRelativeTransform();
} else {
this->SetActorToEarthCenteredEarthFixedMatrix(
this->ActorToEarthCenteredEarthFixedMatrix);
}
}
ACesiumGeoreference*
UCesiumGlobeAnchorComponent::ResolveGeoreference(bool bForceReresolve) {
if (IsValid(this->ResolvedGeoreference) && !bForceReresolve) {
return this->ResolvedGeoreference;
}
ACesiumGeoreference* Previous = this->ResolvedGeoreference;
ACesiumGeoreference* Next =
IsValid(this->Georeference.Get())
? this->ResolvedGeoreference = this->Georeference.Get()
: ACesiumGeoreference::GetDefaultGeoreferenceForActor(
this->GetOwner());
if (Previous != Next) {
if (IsValid(Previous)) {
// If we previously had a valid georeference, first synchronize using the
// old one so that the ECEF and Actor transforms are both up-to-date.
this->Sync();
Previous->OnGeoreferenceUpdated.RemoveAll(this);
}
this->ResolvedGeoreference = Next;
if (this->ResolvedGeoreference) {
this->ResolvedGeoreference->OnGeoreferenceUpdated.AddUniqueDynamic(
this,
&UCesiumGlobeAnchorComponent::_onGeoreferenceChanged);
// Now synchronize based on the new georeference.
this->Sync();
}
}
return this->ResolvedGeoreference;
}
UCesiumEllipsoid* UCesiumGlobeAnchorComponent::GetEllipsoid() const {
ACesiumGeoreference* Georeference = this->GetResolvedGeoreference();
if (!IsValid(Georeference)) {
Georeference =
ACesiumGeoreference::GetDefaultGeoreferenceForActor(this->GetOwner());
}
if (!IsValid(Georeference)) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Unable to find UCesiumGeoreference for UCesiumGlobeAnchorComponent - returning unit ellipsoid."));
return UCesiumEllipsoid::Create(FVector::OneVector);
}
return Georeference->GetEllipsoid();
}
void UCesiumGlobeAnchorComponent::InvalidateResolvedGeoreference() {
// This method is deprecated and no longer does anything.
}
FVector UCesiumGlobeAnchorComponent::GetLongitudeLatitudeHeight() const {
ELLIPSOID_CHECK(this, FVector::ZeroVector);
return this->GetEllipsoid()
->EllipsoidCenteredEllipsoidFixedToLongitudeLatitudeHeight(
this->GetEarthCenteredEarthFixedPosition());
}
void UCesiumGlobeAnchorComponent::MoveToLongitudeLatitudeHeight(
const FVector& TargetLongitudeLatitudeHeight) {
ELLIPSOID_CHECK(this, );
this->MoveToEarthCenteredEarthFixedPosition(
this->GetEllipsoid()
->LongitudeLatitudeHeightToEllipsoidCenteredEllipsoidFixed(
TargetLongitudeLatitudeHeight));
}
namespace {
CesiumGeospatial::LocalHorizontalCoordinateSystem createEastSouthUp(
const CesiumGeospatial::GlobeAnchor& anchor,
const CesiumGeospatial::Ellipsoid& ellipsoid) {
glm::dvec3 ecefPosition;
CesiumGeometry::Transforms::computeTranslationRotationScaleFromMatrix(
anchor.getAnchorToFixedTransform(),
&ecefPosition,
nullptr,
nullptr);
return CesiumGeospatial::LocalHorizontalCoordinateSystem(
ecefPosition,
CesiumGeospatial::LocalDirection::East,
CesiumGeospatial::LocalDirection::South,
CesiumGeospatial::LocalDirection::Up,
1.0,
ellipsoid);
}
} // namespace
FQuat UCesiumGlobeAnchorComponent::GetEastSouthUpRotation() const {
if (!this->_actorToECEFIsValid) {
// Only log a warning if we're actually in a world. Otherwise we'll spam the
// log when editing a CDO.
if (this->GetWorld()) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot get the rotation from CesiumGlobeAnchorComponent %s because the component is not yet registered or does not have a valid CesiumGeoreference."),
*this->GetName());
}
return FQuat::Identity;
}
ELLIPSOID_CHECK(this, FQuat::Identity);
CesiumGeospatial::GlobeAnchor anchor(
VecMath::createMatrix4D(this->ActorToEarthCenteredEarthFixedMatrix));
CesiumGeospatial::LocalHorizontalCoordinateSystem eastSouthUp =
createEastSouthUp(anchor, this->GetEllipsoid()->GetNativeEllipsoid());
glm::dmat4 modelToEastSouthUp = anchor.getAnchorToLocalTransform(eastSouthUp);
glm::dquat rotationToEastSouthUp;
CesiumGeometry::Transforms::computeTranslationRotationScaleFromMatrix(
modelToEastSouthUp,
nullptr,
&rotationToEastSouthUp,
nullptr);
return VecMath::createQuaternion(rotationToEastSouthUp);
}
void UCesiumGlobeAnchorComponent::SetEastSouthUpRotation(
const FQuat& EastSouthUpRotation) {
if (!this->_actorToECEFIsValid) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot set the rotation on CesiumGlobeAnchorComponent %s because the component is not yet registered or does not have a valid CesiumGeoreference."),
*this->GetName());
return;
}
ELLIPSOID_CHECK(this, );
CesiumGeospatial::GlobeAnchor anchor(
VecMath::createMatrix4D(this->ActorToEarthCenteredEarthFixedMatrix));
CesiumGeospatial::LocalHorizontalCoordinateSystem eastSouthUp =
createEastSouthUp(anchor, this->GetEllipsoid()->GetNativeEllipsoid());
glm::dmat4 modelToEastSouthUp = anchor.getAnchorToLocalTransform(eastSouthUp);
glm::dvec3 translation;
glm::dvec3 scale;
CesiumGeometry::Transforms::computeTranslationRotationScaleFromMatrix(
modelToEastSouthUp,
&translation,
nullptr,
&scale);
glm::dmat4 newModelToEastSouthUp =
CesiumGeometry::Transforms::createTranslationRotationScaleMatrix(
translation,
VecMath::createQuaternion(EastSouthUpRotation),
scale);
const CesiumGeospatial::Ellipsoid& ellipsoid =
this->ResolveGeoreference()->GetEllipsoid()->GetNativeEllipsoid();
anchor.setAnchorToLocalTransform(
eastSouthUp,
newModelToEastSouthUp,
false,
ellipsoid);
this->_updateFromNativeGlobeAnchor(anchor);
}
FQuat UCesiumGlobeAnchorComponent::GetEarthCenteredEarthFixedRotation() const {
if (!this->_actorToECEFIsValid) {
// Only log a warning if we're actually in a world. Otherwise we'll spam the
// log when editing a CDO.
if (this->GetWorld()) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot get the rotation from CesiumGlobeAnchorComponent %s because the component is not yet registered or does not have a valid CesiumGeoreference."),
*this->GetName());
}
return FQuat::Identity;
}
glm::dquat rotationToEarthCenteredEarthFixed;
CesiumGeometry::Transforms::computeTranslationRotationScaleFromMatrix(
VecMath::createMatrix4D(this->ActorToEarthCenteredEarthFixedMatrix),
nullptr,
&rotationToEarthCenteredEarthFixed,
nullptr);
return VecMath::createQuaternion(rotationToEarthCenteredEarthFixed);
}
void UCesiumGlobeAnchorComponent::SetEarthCenteredEarthFixedRotation(
const FQuat& EarthCenteredEarthFixedRotation) {
if (!this->_actorToECEFIsValid) {
UE_LOG(
LogCesium,
Error,
TEXT(
"Cannot set the rotation on CesiumGlobeAnchorComponent %s because the component is not yet registered or does not have a valid CesiumGeoreference."),
*this->GetName());
return;
}
glm::dvec3 translation;
glm::dvec3 scale;
CesiumGeometry::Transforms::computeTranslationRotationScaleFromMatrix(
VecMath::createMatrix4D(this->ActorToEarthCenteredEarthFixedMatrix),
&translation,
nullptr,
&scale);
glm::dmat4 newModelToEarthCenteredEarthFixed =
CesiumGeometry::Transforms::createTranslationRotationScaleMatrix(
translation,
VecMath::createQuaternion(EarthCenteredEarthFixedRotation),
scale);
this->ActorToEarthCenteredEarthFixedMatrix =
VecMath::createMatrix(newModelToEarthCenteredEarthFixed);
}
void UCesiumGlobeAnchorComponent::Serialize(FArchive& Ar) {
Super::Serialize(Ar);
Ar.UsingCustomVersion(FCesiumCustomVersion::GUID);
const int32 CesiumVersion = Ar.CustomVer(FCesiumCustomVersion::GUID);
if (CesiumVersion < FCesiumCustomVersion::GeoreferenceRefactoring) {
// In previous versions, there was no _actorToECEFIsValid flag. But we can
// assume that the previously-stored ECEF transform was valid.
this->_actorToECEFIsValid = true;
}
#if WITH_EDITORONLY_DATA
if (CesiumVersion <
FCesiumCustomVersion::GlobeAnchorTransformationAsFMatrix) {
memcpy(
this->ActorToEarthCenteredEarthFixedMatrix.M,
this->_actorToECEF_Array_DEPRECATED,
sizeof(double) * 16);
}
#endif
}
void UCesiumGlobeAnchorComponent::OnComponentCreated() {
Super::OnComponentCreated();
this->_actorToECEFIsValid = false;
}
#if WITH_EDITOR
void UCesiumGlobeAnchorComponent::PostEditChangeProperty(
FPropertyChangedEvent& PropertyChangedEvent) {
if (PropertyChangedEvent.Property) {
FName propertyName = PropertyChangedEvent.Property->GetFName();
if (propertyName ==
GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorComponent, Georeference)) {
this->SetGeoreference(this->Georeference);
}
}
// Call the base class implementation last, because it will call OnRegister,
// which will call Sync. So we need to apply the updated values first.
Super::PostEditChangeProperty(PropertyChangedEvent);
// Calling the Super implementation above shouldn't change the current
// relative transform without also changing _lastRelativeTransform. Except it
// can, because in some cases (e.g., on undo/redo), Unreal reruns the Actor's
// construction scripts, which can cause the relative transform to be
// recomputed from the world transform. That's a problem because later we'll
// think the relative transform has changed and will recompute the globe
// transform from it. So we set (again) the _lastRelativeTransform here.
//
// One possible danger is that the construction script _intentionally_ changes
// the transform. But we can't reliably distinguish that case from a phantom
// transform "change" caused by converting to a world transform and back. And
// surely something dodgy would be happening if something _other_ than the
// globe anchor were intentionally moving the Actor in the globe anchor's
// PostEditChangeProperty, right?
if (this->_lastRelativeTransformIsValid) {
this->_lastRelativeTransform = this->_getCurrentRelativeTransform();
}
}
#endif
void UCesiumGlobeAnchorComponent::OnRegister() {
Super::OnRegister();
const AActor* pOwner = this->GetOwner();
if (!IsValid(pOwner)) {
UE_LOG(
LogCesium,
Warning,
TEXT("CesiumGlobeAnchorComponent %s does not have a valid owner"),
*this->GetName());
return;
}
USceneComponent* pOwnerRoot = pOwner->GetRootComponent();
if (pOwnerRoot) {
pOwnerRoot->TransformUpdated.AddUObject(
this,
&UCesiumGlobeAnchorComponent::_onActorTransformChanged);
}
this->ResolveGeoreference();
}
void UCesiumGlobeAnchorComponent::OnUnregister() {
Super::OnUnregister();
// Unsubscribe from the ResolvedGeoreference.
if (IsValid(this->ResolvedGeoreference)) {
this->ResolvedGeoreference->OnGeoreferenceUpdated.RemoveAll(this);
}
this->ResolvedGeoreference = nullptr;
// Unsubscribe from the TransformUpdated event.
const AActor* pOwner = this->GetOwner();
if (!IsValid(pOwner)) {
UE_LOG(
LogCesium,
Warning,
TEXT("CesiumGlobeAnchorComponent %s does not have a valid owner"),
*this->GetName());
return;
}
USceneComponent* pOwnerRoot = pOwner->GetRootComponent();
if (pOwnerRoot) {
pOwnerRoot->TransformUpdated.RemoveAll(this);
}
}
CesiumGeospatial::GlobeAnchor
UCesiumGlobeAnchorComponent::_createNativeGlobeAnchor() const {
return createNativeGlobeAnchor(this->ActorToEarthCenteredEarthFixedMatrix);
}
USceneComponent*
UCesiumGlobeAnchorComponent::_getRootComponent(bool warnIfNull) const {
const AActor* pOwner = this->GetOwner();
if (!IsValid(pOwner)) {
if (warnIfNull) {
UE_LOG(
LogCesium,
Warning,
TEXT("UCesiumGlobeAnchorComponent %s does not have a valid owner."),
*this->GetName());
}
return nullptr;
}
USceneComponent* pOwnerRoot = pOwner->GetRootComponent();
if (!IsValid(pOwnerRoot)) {
if (warnIfNull) {
UE_LOG(
LogCesium,
Warning,
TEXT(
"The owner of UCesiumGlobeAnchorComponent %s does not have a valid root component."),
*this->GetName());
}
return nullptr;
}
return pOwnerRoot;
}
FTransform UCesiumGlobeAnchorComponent::_getCurrentRelativeTransform() const {
const USceneComponent* pOwnerRoot = this->_getRootComponent(true);
return pOwnerRoot->GetRelativeTransform();
}
void UCesiumGlobeAnchorComponent::_setCurrentRelativeTransform(
const FTransform& relativeTransform) {
AActor* pOwner = this->GetOwner();
if (!IsValid(pOwner)) {
UE_LOG(
LogCesium,
Warning,
TEXT("UCesiumGlobeAnchorComponent %s does not have a valid owner"),
*this->GetName());
return;
}
USceneComponent* pOwnerRoot = pOwner->GetRootComponent();
if (!IsValid(pOwnerRoot)) {
UE_LOG(
LogCesium,
Warning,
TEXT(
"The owner of UCesiumGlobeAnchorComponent %s does not have a valid root component"),
*this->GetName());
return;
}
// Set the new Actor relative transform, taking care not to do this
// recursively.
this->_updatingActorTransform = true;
pOwnerRoot->SetRelativeTransform(
relativeTransform,
false,
nullptr,
this->TeleportWhenUpdatingTransform ? ETeleportType::TeleportPhysics
: ETeleportType::None);
this->_updatingActorTransform = false;
this->_lastRelativeTransform = this->_getCurrentRelativeTransform();
this->_lastRelativeTransformIsValid = true;
}
CesiumGeospatial::GlobeAnchor UCesiumGlobeAnchorComponent::
_createOrUpdateNativeGlobeAnchorFromRelativeTransform(
const FTransform& newRelativeTransform) {
ACesiumGeoreference* pGeoreference = this->ResolvedGeoreference;
assert(pGeoreference != nullptr);
const CesiumGeospatial::LocalHorizontalCoordinateSystem& local =
pGeoreference->GetCoordinateSystem();
glm::dmat4 newModelToLocal =
VecMath::createMatrix4D(newRelativeTransform.ToMatrixWithScale());
if (!this->_actorToECEFIsValid) {
// Create a new anchor initialized at the new position, because there is no
// old one.
return CesiumGeospatial::GlobeAnchor::fromAnchorToLocalTransform(
local,
newModelToLocal);
} else {
assert(this->GetEllipsoid() != nullptr);
// Create an anchor at the old position and move it to the new one.
CesiumGeospatial::GlobeAnchor cppAnchor = this->_createNativeGlobeAnchor();
cppAnchor.setAnchorToLocalTransform(
local,
newModelToLocal,
this->AdjustOrientationForGlobeWhenMoving,
this->GetEllipsoid()->GetNativeEllipsoid());
return cppAnchor;
}
}
CesiumGeospatial::GlobeAnchor
UCesiumGlobeAnchorComponent::_createOrUpdateNativeGlobeAnchorFromECEF(
const FMatrix& newActorToECEFMatrix) {
if (!this->_actorToECEFIsValid) {
// Create a new anchor initialized at the new position, because there is no
// old one.
return CesiumGeospatial::GlobeAnchor(
VecMath::createMatrix4D(newActorToECEFMatrix));
} else {
assert(this->GetEllipsoid() != nullptr);
// Create an anchor at the old position and move it to the new one.
CesiumGeospatial::GlobeAnchor cppAnchor(
VecMath::createMatrix4D(this->ActorToEarthCenteredEarthFixedMatrix));
cppAnchor.setAnchorToFixedTransform(
VecMath::createMatrix4D(newActorToECEFMatrix),
this->AdjustOrientationForGlobeWhenMoving,
this->GetEllipsoid()->GetNativeEllipsoid());
return cppAnchor;
}
}
void UCesiumGlobeAnchorComponent::_updateFromNativeGlobeAnchor(
const CesiumGeospatial::GlobeAnchor& nativeAnchor) {
this->ActorToEarthCenteredEarthFixedMatrix =
VecMath::createMatrix(nativeAnchor.getAnchorToFixedTransform());
this->_actorToECEFIsValid = true;
// Update the Unreal relative transform
ACesiumGeoreference* pGeoreference = this->ResolveGeoreference();
if (IsValid(pGeoreference)) {
glm::dmat4 anchorToLocal = nativeAnchor.getAnchorToLocalTransform(
pGeoreference->GetCoordinateSystem());
this->_setCurrentRelativeTransform(
FTransform(VecMath::createMatrix(anchorToLocal)));
} else {
this->_lastRelativeTransformIsValid = false;
}
}
void UCesiumGlobeAnchorComponent::_onActorTransformChanged(
USceneComponent* InRootComponent,
EUpdateTransformFlags UpdateTransformFlags,
ETeleportType Teleport) {
if (this->_updatingActorTransform) {
return;
}
this->_setNewActorToECEFFromRelativeTransform();
}
void UCesiumGlobeAnchorComponent::_setNewActorToECEFFromRelativeTransform() {
// This method is equivalent to
// CesiumGlobeAnchorImpl::SetNewLocalToGlobeFixedMatrixFromTransform in Cesium
// for Unity.
ACesiumGeoreference* pGeoreference = this->ResolveGeoreference();
if (!IsValid(pGeoreference)) {
UE_LOG(
LogCesium,
Warning,
TEXT(
"CesiumGlobeAnchorComponent %s cannot update globe transform from actor transform because there is no valid Georeference."),
*this->GetName());
return;
}
USceneComponent* pOwnerRoot = this->_getRootComponent(/*warnIfNull*/ true);
if (!IsValid(pOwnerRoot)) {
return;
}
// Update with the new local transform, also rotating based on the new
// position if desired.
FTransform modelToLocal = this->_getCurrentRelativeTransform();
CesiumGeospatial::GlobeAnchor cppAnchor =
this->_createOrUpdateNativeGlobeAnchorFromRelativeTransform(modelToLocal);
this->_updateFromNativeGlobeAnchor(cppAnchor);
#if WITH_EDITOR
// In the Editor, mark this component and the root component modified so Undo
// works properly.
this->Modify();
pOwnerRoot->Modify();
#endif
}
void UCesiumGlobeAnchorComponent::_onGeoreferenceChanged() {
if (this->_actorToECEFIsValid) {
this->SetActorToEarthCenteredEarthFixedMatrix(
this->ActorToEarthCenteredEarthFixedMatrix);
}
}