diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 07554d883..99c83b1cb 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -4,7 +4,11 @@ #include "Async/Async.h" #include "Camera/CameraTypes.h" #include "Camera/PlayerCameraManager.h" +#include "Cesium3DTilesSelection/CesiumIonTilesetContentLoaderFactory.h" #include "Cesium3DTilesSelection/EllipsoidTilesetLoader.h" +#include "Cesium3DTilesSelection/IModelMeshExportContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinCesiumCuratedContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinRealityDataContentLoaderFactory.h" #include "Cesium3DTilesSelection/Tile.h" #include "Cesium3DTilesSelection/TilesetLoadFailureDetails.h" #include "Cesium3DTilesSelection/TilesetOptions.h" @@ -436,6 +440,54 @@ void ACesium3DTileset::SetIonAccessToken(const FString& InAccessToken) { } } +void ACesium3DTileset::SetITwinCesiumContentID(int64 InContentID) { + if (InContentID >= 0 && InContentID != this->ITwinCesiumContentID) { + if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent) { + this->DestroyTileset(); + } + this->ITwinCesiumContentID = InContentID; + } +} + +void ACesium3DTileset::SetIModelID(const FString& InModelID) { + if (InModelID != this->IModelID) { + if (this->TilesetSource == ETilesetSource::FromIModelMeshExportService) { + this->DestroyTileset(); + } + this->IModelID = InModelID; + } +} + +void ACesium3DTileset::SetRealityDataID(const FString& InRealityDataID) { + if (InRealityDataID != this->RealityDataID) { + if (this->TilesetSource == ETilesetSource::FromITwinRealityData) { + this->DestroyTileset(); + } + this->RealityDataID = InRealityDataID; + } +} + +void ACesium3DTileset::SetITwinID(const FString& InITwinID) { + if (InITwinID != this->ITwinID) { + if (this->TilesetSource == ETilesetSource::FromITwinRealityData) { + this->DestroyTileset(); + } + this->ITwinID = InITwinID; + } +} + +void ACesium3DTileset::SetITwinConnection( + UCesiumITwinConnection* InConnection) { + if (this->ITwinConnection != InConnection) { + if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent || + this->TilesetSource == ETilesetSource::FromIModelMeshExportService || + this->TilesetSource == ETilesetSource::FromITwinRealityData) { + this->DestroyTileset(); + } + this->ITwinConnection = InConnection; + } +} + void ACesium3DTileset::SetCesiumIonServer(UCesiumIonServer* Server) { if (this->CesiumIonServer != Server) { if (this->TilesetSource == ETilesetSource::FromCesiumIon) { @@ -1110,28 +1162,137 @@ void ACesium3DTileset::LoadTileset() { Log, TEXT("Loading tileset for asset ID %d"), this->IonAssetID); - FString token = this->IonAccessToken.IsEmpty() - ? this->CesiumIonServer->DefaultIonAccessToken - : this->IonAccessToken; + { + FString token = this->IonAccessToken.IsEmpty() + ? this->CesiumIonServer->DefaultIonAccessToken + : this->IonAccessToken; #if WITH_EDITOR - this->CesiumIonServer->ResolveApiUrl(); + this->CesiumIonServer->ResolveApiUrl(); #endif - std::string ionAssetEndpointUrl = - TCHAR_TO_UTF8(*this->CesiumIonServer->ApiUrl); + std::string ionAssetEndpointUrl = + TCHAR_TO_UTF8(*this->CesiumIonServer->ApiUrl); - if (!ionAssetEndpointUrl.empty()) { - // Make sure the URL ends with a slash - if (!ionAssetEndpointUrl.empty() && *ionAssetEndpointUrl.rbegin() != '/') - ionAssetEndpointUrl += '/'; + if (!ionAssetEndpointUrl.empty()) { + // Make sure the URL ends with a slash + if (!ionAssetEndpointUrl.empty() && + *ionAssetEndpointUrl.rbegin() != '/') + ionAssetEndpointUrl += '/'; + this->_pTileset = MakeUnique( + externals, + static_cast(this->IonAssetID), + TCHAR_TO_UTF8(*token), + options, + ionAssetEndpointUrl); + } + } + break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Log, + TEXT("Loading tileset for asset ID %d"), + this->ITwinCesiumContentID); + + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( + static_cast(this->ITwinCesiumContentID), + token), + options); + } + break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Loading mesh export for iModel ID %s"), + *this->IModelID); + + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; this->_pTileset = MakeUnique( externals, - static_cast(this->IonAssetID), - TCHAR_TO_UTF8(*token), - options, - ionAssetEndpointUrl); + Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( + TCHAR_TO_UTF8(*this->IModelID), + std::nullopt, + token), + options); + } + break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Loading reality data ID %s"), + *this->RealityDataID); + + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; + + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory( + TCHAR_TO_UTF8(*this->RealityDataID), + this->ITwinID.IsEmpty() ? std::nullopt + : std::make_optional( + TCHAR_TO_UTF8(*this->ITwinID)), + token, + [asyncSystem, + pConnectionObject = this->ITwinConnection](const std::string&) { + if (!IsValid(pConnectionObject) || + !pConnectionObject->GetConnection().IsValid()) { + return asyncSystem.createResolvedFuture< + CesiumUtility:: + Result>(CesiumUtility::ErrorList::error( + "iTwin connection property on tileset is not valid.")); + } + + const TSharedPtr pConnection = + pConnectionObject->GetConnection(); + if (!pConnection->getRefreshToken()) { + return asyncSystem.createResolvedFuture< + CesiumUtility:: + Result>(CesiumUtility::ErrorList::error( + "Access token for reality data is expired, no refresh token available.")); + } + + return pConnection->me().thenImmediately( + [pConnection]( + CesiumUtility::Result&& + result) -> CesiumUtility::Result { + if (!result.value) { + return CesiumUtility::Result( + result.errors); + } + + if (!pConnection->getAuthenticationToken().isValid()) { + return CesiumUtility::Result< + std::string>(CesiumUtility::ErrorList::error( + "Tried to refresh access token for reality data, but was not able to obtain valid token.")); + } + + return CesiumUtility::Result( + pConnection->getAuthenticationToken().getToken()); + }); + }), + options); } break; } @@ -1183,6 +1344,27 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading tileset for asset ID %d done"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Log, + TEXT("Loading tileset for asset ID %d done"), + this->ITwinCesiumContentID); + break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Loading mesh export for iModel ID %s done"), + *this->IModelID); + break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Loading reality data ID %s done"), + *this->RealityDataID); + break; } switch (ApplyDpiScaling) { @@ -1224,6 +1406,27 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Verbose, + TEXT("Destroying tileset for asset ID %d"), + this->ITwinCesiumContentID); + break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for iModel ID %s"), + *this->IModelID); + break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for reality data ID %s done"), + *this->RealityDataID); + break; } // The way CesiumRasterOverlay::add is currently implemented, destroying the @@ -1282,6 +1485,27 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d done"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Verbose, + TEXT("Destroying tileset for asset ID %d done"), + this->ITwinCesiumContentID); + break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for iModel ID %s done"), + *this->IModelID); + break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for reality data ID %s done"), + *this->RealityDataID); + break; } } @@ -2168,6 +2392,12 @@ void ACesium3DTileset::PostEditChangeProperty( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Url) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAssetID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAccessToken) || + PropName == + GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinCesiumContentID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IModelID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, RealityDataID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinConnection) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreatePhysicsMeshes) || PropName == diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp new file mode 100644 index 000000000..441e3f3fe --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -0,0 +1,421 @@ +#include "CesiumITwinAPIBlueprintLibrary.h" +#include "Async/Async.h" +#include "CesiumITwinCesiumCuratedContentRasterOverlay.h" +#include "CesiumRuntime.h" +#include +#include + +namespace { +TArray errorListToArray(const CesiumUtility::ErrorList& errors) { + TArray Errors; + for (const std::string& error : errors.errors) { + Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); + } + + for (const std::string& warning : errors.warnings) { + Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); + } + + return Errors; +} +} // namespace + +UCesiumITwinAPIAuthorizeAsyncAction* +UCesiumITwinAPIAuthorizeAsyncAction::Authorize(const FString& ClientID) { + UCesiumITwinAPIAuthorizeAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->_clientId = ClientID; + return pAsyncAction; +} + +void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { + CesiumITwinClient::Connection::authorize( + getAsyncSystem(), + getAssetAccessor(), + "Cesium for Unreal", + TCHAR_TO_UTF8(*this->_clientId), + "/itwin/auth/redirect", + 5081, + std::vector{"itwin-platform", "offline_access"}, + [&Callback = this->OnAuthorizationEvent](const std::string& url) { + Callback.Broadcast( + ECesiumITwinAuthorizationDelegateType::OpenUrl, + UTF8_TO_TCHAR(url.c_str()), + nullptr, + TArray()); + }) + .thenInMainThread([this](CesiumUtility::Result< + CesiumITwinClient::Connection>&& connection) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Authorization finished but authorize async action is no longer valid.")); + return; + } + + if (!connection.value) { + AsyncTask(ENamedThreads::GameThread, [connection, this]() { + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinAuthorizationDelegateType::Failure, + FString(), + nullptr, + errorListToArray(connection.errors)); + this->SetReadyToDestroy(); + }); + } else { + TSharedPtr pInternalConnection = + MakeShared( + MoveTemp(*connection.value)); + UCesiumITwinConnection* pConnection = + NewObject(); + pConnection->SetConnection(pInternalConnection); + AsyncTask(ENamedThreads::GameThread, [pConnection, this]() { + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinAuthorizationDelegateType::Success, + FString(), + pConnection, + TArray()); + this->SetReadyToDestroy(); + }); + } + }); +} + +UCesiumITwinAPIGetProfileAsyncAction* +UCesiumITwinAPIGetProfileAsyncAction::GetProfile( + UCesiumITwinConnection* pConnection) { + UCesiumITwinAPIGetProfileAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + return pAsyncAction; +} + +void UCesiumITwinAPIGetProfileAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnProfileResult.Broadcast(nullptr, Errors); + return; + } + + this->pConnection->me().thenInMainThread([this](CesiumUtility::Result< + CesiumITwinClient:: + UserProfile>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get profile finished but get profile async action is no longer valid.")); + return; + } + + if (!result.value) { + OnProfileResult.Broadcast(nullptr, errorListToArray(result.errors)); + } else { + UCesiumITwinUserProfile* pProfile = NewObject(); + pProfile->SetProfile(std::move(*result.value)); + OnProfileResult.Broadcast(pProfile, TArray()); + } + }); +} + +const int PageSize = 50; + +UCesiumITwinAPIGetITwinsAsyncAction* +UCesiumITwinAPIGetITwinsAsyncAction::GetITwins( + UCesiumITwinConnection* pConnection, + int Page) { + UCesiumITwinAPIGetITwinsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + return pAsyncAction; +} + +void UCesiumITwinAPIGetITwinsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnITwinsResult.Broadcast(TArray(), false, Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->itwins(params).thenInMainThread( + [this](CesiumUtility::Result< + CesiumITwinClient::PagedList>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get itwins finished but get itwins async action is no longer valid.")); + return; + } + + if (!result.value) { + OnITwinsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iTwins; + iTwins.Reserve(result.value->size()); + for (CesiumITwinClient::ITwin& iTwin : *result.value) { + UCesiumITwin* pITwin = NewObject(); + pITwin->SetITwin(MoveTemp(iTwin)); + iTwins.Emplace(pITwin); + } + OnITwinsResult.Broadcast( + iTwins, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIGetIModelsAsyncAction* +UCesiumITwinAPIGetIModelsAsyncAction::GetIModels( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page) { + UCesiumITwinAPIGetIModelsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iTwinId = iTwinId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetIModelsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnIModelsResult.Broadcast(TArray(), false, Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->imodels(TCHAR_TO_UTF8(*this->iTwinId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get imodels finished but get imodels async action is no longer valid.")); + return; + } + + if (!result.value) { + OnIModelsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iModels; + iModels.Reserve(result.value->size()); + for (CesiumITwinClient::IModel& iModel : *result.value) { + UCesiumIModel* pIModel = NewObject(); + pIModel->SetIModel(MoveTemp(iModel)); + iModels.Emplace(pIModel); + } + OnIModelsResult.Broadcast( + iModels, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIGetIModelMeshExportsAsyncAction* +UCesiumITwinAPIGetIModelMeshExportsAsyncAction::GetIModelMeshExports( + UCesiumITwinConnection* pConnection, + const FString& iModelId, + int Page) { + UCesiumITwinAPIGetIModelMeshExportsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iModelId = iModelId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetIModelMeshExportsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnIModelMeshExportsResult.Broadcast( + TArray(), + false, + Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->meshExports(TCHAR_TO_UTF8(*this->iModelId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& + result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get imodel mesh exports finished but get imodel mesh exports async action is no longer valid.")); + return; + } + + if (!result.value) { + OnIModelMeshExportsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iModels; + iModels.Reserve(result.value->size()); + for (CesiumITwinClient::IModelMeshExport& iModel : *result.value) { + UCesiumIModelMeshExport* pIModel = + NewObject(); + pIModel->SetIModelMeshExport(MoveTemp(iModel)); + iModels.Emplace(pIModel); + } + OnIModelMeshExportsResult.Broadcast( + iModels, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIGetRealityDataAsyncAction* +UCesiumITwinAPIGetRealityDataAsyncAction::GetITwinRealityData( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page) { + UCesiumITwinAPIGetRealityDataAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iTwinId = iTwinId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetRealityDataAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnITwinRealityDataResult.Broadcast( + TArray(), + false, + Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->realityData(TCHAR_TO_UTF8(*this->iTwinId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& + result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get reality data finished but get reality data async action is no longer valid.")); + return; + } + + if (!result.value) { + OnITwinRealityDataResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray realityDataObjects; + realityDataObjects.Reserve(result.value->size()); + for (CesiumITwinClient::ITwinRealityData& realityData : + *result.value) { + UCesiumITwinRealityData* pRealityData = + NewObject(); + pRealityData->SetITwinRealityData(MoveTemp(realityData)); + realityDataObjects.Emplace(pRealityData); + } + OnITwinRealityDataResult.Broadcast( + realityDataObjects, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIListCesiumCuratedContentAsyncAction* +UCesiumITwinAPIListCesiumCuratedContentAsyncAction:: + GetCesiumCuratedContentAssets(UCesiumITwinConnection* pConnection) { + UCesiumITwinAPIListCesiumCuratedContentAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + return pAsyncAction; +} + +void UCesiumITwinAPIListCesiumCuratedContentAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnListCesiumCuratedContentDelegate.Broadcast( + TArray(), + Errors); + return; + } + + this->pConnection->cesiumCuratedContent().thenInMainThread( + [this](CesiumUtility::Result>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get cesium curated content finished but get cesium curated content async action is no longer valid.")); + return; + } + + if (!result.value) { + OnListCesiumCuratedContentDelegate.Broadcast( + TArray(), + errorListToArray(result.errors)); + } else { + TArray assets; + assets.Reserve(result.value->size()); + for (CesiumITwinClient::CesiumCuratedContentAsset& asset : + *result.value) { + UCesiumCuratedContentAsset* pAsset = + NewObject(); + pAsset->SetCesiumCuratedContentAsset(MoveTemp(asset)); + assets.Emplace(pAsset); + } + OnListCesiumCuratedContentDelegate.Broadcast( + assets, + TArray()); + } + }); +} diff --git a/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp b/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp new file mode 100644 index 000000000..b0a2ad316 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp @@ -0,0 +1,30 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#include "CesiumITwinCesiumCuratedContentRasterOverlay.h" +#include "Cesium3DTilesSelection/Tileset.h" +#include "CesiumActors.h" +#include "CesiumCustomVersion.h" +#include "CesiumIonServer.h" +#include "CesiumRasterOverlays/ITwinCesiumCuratedContentRasterOverlay.h" +#include "CesiumRuntime.h" +#include "CesiumRuntimeSettings.h" + +#if WITH_EDITOR +#include "FileHelpers.h" +#endif + +std::unique_ptr +UCesiumITwinCesiumCuratedContentRasterOverlay::CreateOverlay( + const CesiumRasterOverlays::RasterOverlayOptions& options) { + if (this->AssetID <= 0) { + // Don't create an overlay for an invalid asset ID. + return nullptr; + } + + return std::make_unique< + CesiumRasterOverlays::ITwinCesiumCuratedContentRasterOverlay>( + TCHAR_TO_UTF8(*this->MaterialLayerKey), + this->AssetID, + TCHAR_TO_UTF8(*this->ITwinAccessToken), + options); +} diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 2aa951bb1..8a8e984fa 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -10,6 +10,7 @@ #include "CesiumEncodedMetadataComponent.h" #include "CesiumFeaturesMetadataComponent.h" #include "CesiumGeoreference.h" +#include "CesiumITwinAPIBlueprintLibrary.h" #include "CesiumIonServer.h" #include "CesiumPointCloudShading.h" #include "CesiumSampleHeightResult.h" @@ -83,7 +84,27 @@ enum class ETilesetSource : uint8 { /** * The tileset will be loaded from the georeference ellipsoid. */ - FromEllipsoid UMETA(DisplayName = "From Ellipsoid") + FromEllipsoid UMETA(DisplayName = "From Ellipsoid"), + + /** + * The tileset will be loaded from the Bentley iTwin Cesium Curated Content + * API using the provided ITwinCesiumContentID and ITwinAccessToken. + */ + FromITwinCesiumCuratedContent UMETA( + DisplayName = "From iTwin Cesium Curated Content"), + + /** + * The tileset will be loaded from the iModel Mesh Export Service using the + * provided IModelID and ITwinAccessToken. + */ + FromIModelMeshExportService UMETA( + DisplayName = "From iModel Mesh Export Service"), + + /** + * The tileset will be loaded from the iModel Mesh Export Service using the + * provided RealityDataID and ITwinAccessToken. + */ + FromITwinRealityData UMETA(DisplayName = "From iTwin Reality Data") }; UENUM(BlueprintType) @@ -764,6 +785,87 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { meta = (EditCondition = "TilesetSource==ETilesetSource::FromCesiumIon")) UCesiumIonServer* CesiumIonServer; + /** + * The ID of the iTwin Cesium Curated Content asset to use. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Cesium + * Curated Content" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinCesiumContentID, + BlueprintSetter = SetITwinCesiumContentID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent", + ClampMin = 0)) + int64 ITwinCesiumContentID; + + /** + * The ID of the iModel to use when interacting with the iModel Mesh Export + * Service. + * + * This property is ignored if TilesetSource isn't set to "From iModel Mesh + * Export Service" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetIModelID, + BlueprintSetter = SetIModelID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromIModelMeshExportService")) + FString IModelID; + + /** + * The ID of the iTwin Reality Data to use when interacting with the Reality + * Data service. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Reality + * Data" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetRealityDataID, + BlueprintSetter = SetRealityDataID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinRealityData")) + FString RealityDataID; + + /** + * The ID of the iTwin to use when interacting with the Reality + * Data service. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Reality + * Data" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinID, + BlueprintSetter = SetITwinID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinRealityData")) + FString ITwinID; + + /** + * The access token to use to access the iTwin resource. + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinConnection, + BlueprintSetter = SetITwinConnection, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent || TilesetSource==ETilesetSource::FromIModelMeshExportService || TilesetSource==ETilesetSource::FromITwinRealityData")) + UCesiumITwinConnection* ITwinConnection; + /** * Headers to be attached to each request made for this tileset. */ @@ -1038,6 +1140,36 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UFUNCTION(BlueprintGetter, Category = "Cesium") UCesiumIonServer* GetCesiumIonServer() const { return CesiumIonServer; } + UFUNCTION(BlueprintGetter, Category = "Cesium") + int64 GetITwinCesiumContentID() const { return ITwinCesiumContentID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinCesiumContentID(int64 InContentID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetIModelID() const { return IModelID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetIModelID(const FString& InModelID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetRealityDataID() const { return RealityDataID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetRealityDataID(const FString& InModelID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetITwinID() const { return ITwinID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinID(const FString& InITwinID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + UCesiumITwinConnection* GetITwinConnection() const { return ITwinConnection; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinConnection(UCesiumITwinConnection* InAccessToken); + UFUNCTION(BlueprintGetter, Category = "VirtualTexture") TArray GetRuntimeVirtualTextures() const { return RuntimeVirtualTextures; diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h new file mode 100644 index 000000000..e21b26787 --- /dev/null +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -0,0 +1,761 @@ +// Copyright 2020-2025 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CesiumGeoreference.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Templates/SharedPointer.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END + +#include "CesiumITwinAPIBlueprintLibrary.generated.h" + +UCLASS(BlueprintType) +class UCesiumITwinConnection : public UObject { + GENERATED_BODY() +public: + UCesiumITwinConnection() : UObject(), pConnection(nullptr) {} + + UCesiumITwinConnection(TSharedPtr pConnection) + : UObject(), pConnection(MoveTemp(pConnection)) {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + bool IsValid() { return this->pConnection != nullptr; } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetAccessToken() { + return UTF8_TO_TCHAR( + this->pConnection->getAuthenticationToken().getToken().c_str()); + } + + TSharedPtr& GetConnection() { + return this->pConnection; + } + + void SetConnection(TSharedPtr pConnection_) { + this->pConnection = pConnection_; + } + + TSharedPtr pConnection; +}; + +UENUM(BlueprintType) +enum class ECesiumITwinAuthorizationDelegateType : uint8 { + Invalid = 0, + OpenUrl = 1, + Failure = 2, + Success = 3 +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( + FCesiumITwinAuthorizationDelegate, + ECesiumITwinAuthorizationDelegateType, + Type, + const FString&, + Url, + UCesiumITwinConnection*, + Connection, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIAuthorizeAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() + +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIAuthorizeAsyncAction* + Authorize(const FString& ClientID); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinAuthorizationDelegate OnAuthorizationEvent; + + virtual void Activate() override; + +private: + FString _clientId; +}; + +UCLASS(BlueprintType) +class UCesiumITwinUserProfile : public UObject { + GENERATED_BODY() +public: + UCesiumITwinUserProfile() : UObject(), _profile() {} + + UCesiumITwinUserProfile(CesiumITwinClient::UserProfile&& profile) + : UObject(), _profile(std::move(profile)) {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_profile.id.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_profile.displayName.c_str()); + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetGivenName() { return UTF8_TO_TCHAR(_profile.givenName.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSurname() { return UTF8_TO_TCHAR(_profile.surname.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetEmail() { return UTF8_TO_TCHAR(_profile.email.c_str()); } + + void SetProfile(CesiumITwinClient::UserProfile&& profile) { + this->_profile = std::move(profile); + } + +private: + CesiumITwinClient::UserProfile _profile; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCesiumITwinGetProfileDelegate, + UCesiumITwinUserProfile*, + Profile, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetProfileAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetProfileAsyncAction* + GetProfile(UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinGetProfileDelegate OnProfileResult; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; + +UENUM(BlueprintType) +enum class ECesiumITwinStatus : uint8 { + Unknown = 0, + Active = 1, + Inactive = 2, + Trial = 3 +}; + +/** + * @brief Information on a single iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/operations/get-my-itwins/#itwin-summary + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumITwin : public UObject { + GENERATED_BODY() +public: + UCesiumITwin() : UObject(), _iTwin() {} + + /** @brief The iTwin Id. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_iTwin.id.c_str()); } + + /** + * @brief The `Class` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetClass() { return UTF8_TO_TCHAR(_iTwin.iTwinClass.c_str()); } + + /** + * @brief The `subClass` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSubClass() { return UTF8_TO_TCHAR(_iTwin.subClass.c_str()); } + + /** @brief An open ended property to better define your iTwin's Type. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetType() { return UTF8_TO_TCHAR(_iTwin.type.c_str()); } + + /** + * @brief A unique number or code for the iTwin. + * + * This is the value that uniquely identifies the iTwin within your + * organization. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetNumber() { return UTF8_TO_TCHAR(_iTwin.number.c_str()); } + + /** @brief A display name for the iTwin. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { return UTF8_TO_TCHAR(_iTwin.displayName.c_str()); } + + /** @brief The status of the iTwin. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumITwinStatus GetStatus() { return (ECesiumITwinStatus)_iTwin.status; } + + void SetITwin(CesiumITwinClient::ITwin&& iTwin) { + this->_iTwin = std::move(iTwin); + } + +private: + CesiumITwinClient::ITwin _iTwin; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListITwinsDelegate, + TArray, + ITwins, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetITwinsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetITwinsAsyncAction* + GetITwins(UCesiumITwinConnection* pConnection, int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListITwinsDelegate OnITwinsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; +}; + +UENUM(BlueprintType) +enum class ECesiumIModelState : uint8 { + Unknown = 0, + Initialized = 1, + NotInitialized = 2 +}; + +/** + * @brief An iModel. + * + * See + * https://developer.bentley.com/apis/imodels-v2/operations/get-imodel-details/#imodel + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumIModel : public UObject { + GENERATED_BODY() +public: + UCesiumIModel() : UObject(), _iModel() {} + + /** @brief The iModel Id. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_iModel.id.c_str()); } + + /** @brief Display name of the iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_iModel.displayName.c_str()); + } + + /** @brief Name of the iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetName() { return UTF8_TO_TCHAR(_iModel.name.c_str()); } + + /** @brief Description of the iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { + return UTF8_TO_TCHAR(_iModel.description.c_str()); + } + + /** @brief Indicates the state of the iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelState GetState() { return (ECesiumIModelState)_iModel.state; } + + /** + * @brief The maximum rectangular area on the Earth which encloses the iModel. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FBox GetExtent() { + const CesiumGeospatial::Cartographic& southWest = + _iModel.extent.getSouthwest(); + const CesiumGeospatial::Cartographic& northEast = + _iModel.extent.getNortheast(); + return FBox( + FVector( + CesiumUtility::Math::radiansToDegrees(southWest.longitude), + CesiumUtility::Math::radiansToDegrees(southWest.latitude), + southWest.height), + FVector( + CesiumUtility::Math::radiansToDegrees(northEast.longitude), + CesiumUtility::Math::radiansToDegrees(northEast.latitude), + northEast.height)); + } + + void SetIModel(CesiumITwinClient::IModel&& iModel) { + this->_iModel = std::move(iModel); + } + +private: + CesiumITwinClient::IModel _iModel; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListIModelsDelegate, + TArray, + IModels, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetIModelsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetIModelsAsyncAction* GetIModels( + UCesiumITwinConnection* pConnection, + const FString& ITwinId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListIModelsDelegate OnIModelsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iTwinId; +}; + +/** + * @brief The status of an iModel Mesh Export. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#exportstatus + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumIModelMeshExportStatus : uint8 { + Unknown = 0, + NotStarted = 1, + InProgress = 2, + Complete = 3, + Invalid = 4 +}; + +/** + * @brief The type of mesh exported. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#startexport-exporttype + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumIModelMeshExportType : uint8 { + Unknown = 0, + /** + * @brief iTwin "3D Fast Transmission" (3DFT) format. + */ + ITwin3DFT = 1, + IModel = 2, + Cesium = 3, + Cesium3DTiles = 4, +}; + +/** + * @brief An iModel mesh export. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#export + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumIModelMeshExport : public UObject { + GENERATED_BODY() +public: + UCesiumIModelMeshExport() : UObject(), _meshExport() {} + + /** @brief ID of the export request. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_meshExport.id.c_str()); } + + /** @brief Name of the exported iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_meshExport.displayName.c_str()); + } + + /** @brief The status of the export job. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelMeshExportStatus GetState() { + return (ECesiumIModelMeshExportStatus)_meshExport.status; + } + + /** @brief The status of the export job. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelMeshExportType GetExportType() { + return (ECesiumIModelMeshExportType)_meshExport.exportType; + } + + void SetIModelMeshExport(CesiumITwinClient::IModelMeshExport&& iModel) { + this->_meshExport = std::move(iModel); + } + +private: + CesiumITwinClient::IModelMeshExport _meshExport; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListIModelMeshExportsDelegate, + TArray, + MeshExports, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetIModelMeshExportsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetIModelMeshExportsAsyncAction* GetIModelMeshExports( + UCesiumITwinConnection* pConnection, + const FString& iModelId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListIModelMeshExportsDelegate OnIModelMeshExportsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iModelId; +}; + +/** + * @brief Indicates the nature of the reality data. + * + * See + * https://developer.bentley.com/apis/reality-management/rm-rd-details/#classification + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumITwinRealityDataClassification : uint8 { + Unknown = 0, + Terrain = 1, + Imagery = 2, + Pinned = 3, + Model = 4, + Undefined = 5 +}; + +/** + * @brief Information on reality data. + * + * See + * https://developer.bentley.com/apis/reality-management/operations/get-all-reality-data/#reality-data-metadata + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumITwinRealityData : public UObject { + GENERATED_BODY() +public: + UCesiumITwinRealityData() : UObject(), _realityData() {} + + /** + * @brief Identifier of the reality data. + * + * This identifier is assigned by the service at the creation of the reality + * data. It is also unique. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_realityData.id.c_str()); } + + /** + * @brief The name of the reality data. + * + * This property may not contain any control sequence such as a URL or code. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_realityData.displayName.c_str()); + } + + /** + * @brief A textual description of the reality data. + * + * This property may not contain any control sequence such as a URL or code. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { + return UTF8_TO_TCHAR(_realityData.description.c_str()); + } + + /** + * @brief Specific value constrained field that indicates the nature of the + * reality data. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumITwinRealityDataClassification GetClassification() { + return (ECesiumITwinRealityDataClassification)_realityData.classification; + } + + /** + * @brief A key indicating the format of the data. + * + * The type property should be a specific indication of the format of the + * reality data. Given a type, the consuming software should be able to + * determine if it has the capacity to open the reality data. Although the + * type field is a free string some specific values are reserved and other + * values should be selected judiciously. Look at the documentation for an + * exhaustive list of reserved reality data types. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetType() { return UTF8_TO_TCHAR(_realityData.type.c_str()); } + + /** + * @brief Contains the rectangular area on the Earth which encloses the + * reality data. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FBox GetExtent() { + const CesiumGeospatial::Cartographic& southWest = + _realityData.extent.getSouthwest(); + const CesiumGeospatial::Cartographic& northEast = + _realityData.extent.getNortheast(); + return FBox( + FVector( + CesiumUtility::Math::radiansToDegrees(southWest.longitude), + CesiumUtility::Math::radiansToDegrees(southWest.latitude), + southWest.height), + FVector( + CesiumUtility::Math::radiansToDegrees(northEast.longitude), + CesiumUtility::Math::radiansToDegrees(northEast.latitude), + northEast.height)); + } + + /** + * @brief A boolean value that is true if the data is being created. It is + * false if the data has been completely uploaded. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + bool GetAuthoring() { return this->_realityData.authoring; } + + void SetITwinRealityData(CesiumITwinClient::ITwinRealityData&& realityData) { + this->_realityData = std::move(realityData); + } + +private: + CesiumITwinClient::ITwinRealityData _realityData; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListRealityDataDelegate, + TArray, + RealityData, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetRealityDataAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetRealityDataAsyncAction* GetITwinRealityData( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListRealityDataDelegate OnITwinRealityDataResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iTwinId; +}; + +/** + * @brief The type of content obtained from the iTwin Cesium Curated Content + * API. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#contenttype + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumCuratedContentType : uint8 { + /** @brief The content type returned is not a known type. */ + Unknown = 0, + /** @brief The content is a 3D Tiles tileset. */ + Cesium3DTiles = 1, + /** @brief The content is a glTF model. */ + Gltf = 2, + /** + * @brief The content is imagery that can be loaded with \ref + * CesiumRasterOverlays::ITwinCesiumCuratedContentRasterOverlay. + */ + Imagery = 3, + /** @brief The content is quantized mesh terrain. */ + Terrain = 4, + /** @brief The content is in the Keyhole Markup Language (KML) format. */ + Kml = 5, + /** + * @brief The content is in the Cesium Language (CZML) format. + * + * See https://github.com/AnalyticalGraphicsInc/czml-writer/wiki/CZML-Guide + * for more information. + */ + Czml = 6, + /** @brief The content is in the GeoJSON format. */ + GeoJson = 7, +}; + +/** + * @brief Describes the state of the content during the upload and tiling + * processes. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#contentstatus + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumCuratedContentStatus : uint8 { + Unknown = 0, + AwaitingFiles = 1, + NotStarted = 2, + InProgress = 3, + Complete = 4, + Error = 5, + DataError = 6 +}; + +/** + * @brief A single asset obtained from the iTwin Cesium Curated Content API. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#content + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumCuratedContentAsset : public UObject { + GENERATED_BODY() +public: + UCesiumCuratedContentAsset() : UObject(), _asset() {} + + /** + * @brief The unique identifier for the content. + * + * @remark The value returned from the API is a `uint64`, which Unreal does + * not support in Blueprint. As this value is only used as a unique idenitifer + * and not a numeric value, it is converted to a string for use in Blueprint. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return FString::Printf(TEXT("%llu"), _asset.id); } + + /** @brief The type of the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumCuratedContentType GetType() { + return (ECesiumCuratedContentType)_asset.type; + } + + /** @brief Name of the exported iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetName() { return UTF8_TO_TCHAR(_asset.name.c_str()); } + + /** @brief A Markdown string describing the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { return UTF8_TO_TCHAR(_asset.description.c_str()); } + + /** + * @brief A Markdown compatible string containing any required attribution for + * the content. + * + * Clients will be required to display the attribution to end users. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetAttribution() { return UTF8_TO_TCHAR(_asset.attribution.c_str()); } + + /** @brief The status of the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumCuratedContentStatus GetState() { + return (ECesiumCuratedContentStatus)_asset.status; + } + + void SetCesiumCuratedContentAsset( + CesiumITwinClient::CesiumCuratedContentAsset&& asset) { + this->_asset = std::move(asset); + } + +private: + CesiumITwinClient::CesiumCuratedContentAsset _asset; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCesiumITwinListCesiumCuratedContentDelegate, + TArray, + Assets, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIListCesiumCuratedContentAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIListCesiumCuratedContentAsyncAction* + GetCesiumCuratedContentAssets(UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListCesiumCuratedContentDelegate + OnListCesiumCuratedContentDelegate; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; diff --git a/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h b/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h new file mode 100644 index 000000000..fb7a26eb9 --- /dev/null +++ b/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h @@ -0,0 +1,37 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CesiumRasterOverlay.h" +#include "CoreMinimal.h" +#include "CesiumITwinCesiumCuratedContentRasterOverlay.generated.h" + +/** + * A raster overlay that uses an IMAGERY asset from iTwin Cesium Curated + * Content. + */ +UCLASS( + DisplayName = "iTwin Cesium Curated Content Raster Overlay", + ClassGroup = (Cesium), + meta = (BlueprintSpawnableComponent)) +class CESIUMRUNTIME_API UCesiumITwinCesiumCuratedContentRasterOverlay + : public UCesiumRasterOverlay { + GENERATED_BODY() + +public: + /** + * The ID of the Cesium Curated Content asset to use. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cesium") + int64 AssetID; + + /** + * The access token to use to access the Cesium Curated Content resource. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cesium") + FString ITwinAccessToken; + +protected: + virtual std::unique_ptr CreateOverlay( + const CesiumRasterOverlays::RasterOverlayOptions& options = {}) override; +};