初始提交: UE5.3项目基础框架
This commit is contained in:
@ -0,0 +1,793 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user