Skip to content

Add iTwin support to Cesium for Unreal #1665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0d9cfe2
iTwin CCC loader support in Unreal
azrogers Mar 5, 2025
e5a385a
Add mesh export service loader
azrogers Mar 5, 2025
6536153
Add iTwin Reality Data loader
azrogers Mar 6, 2025
15c5370
iTwin blueprint API
azrogers Mar 13, 2025
111480f
Add raster overlay.
azrogers Mar 20, 2025
26fd3db
Basics of a blueprint library for GeoJSON
azrogers Apr 4, 2025
f3d4601
More blueprint functions
azrogers Apr 7, 2025
b30daaf
Basic tests
azrogers Apr 7, 2025
0cd1fe6
More tests
azrogers Apr 8, 2025
009b0dd
Add blueprint function to get properties
azrogers Apr 8, 2025
d70bccc
Add category to UPROPERTY
azrogers Apr 8, 2025
eae0ed3
Fix build with Json
azrogers Apr 8, 2025
cb80106
Get vector document from ion
azrogers Apr 8, 2025
1edae85
Exposing ion server to blueprints
azrogers Apr 9, 2025
39c297e
Fix Unreal build error
azrogers Apr 9, 2025
dea3ad4
Merge branch 'main' of github.com:CesiumGS/cesium-unreal into geojson…
azrogers Apr 9, 2025
45da665
Avoid copying vector document.
azrogers Apr 11, 2025
8d5bee3
Move to IntrusivePointer
azrogers Apr 22, 2025
c799b49
VectorDocumentRasterOverlay
azrogers Apr 25, 2025
5981b19
Vector overlay from ion
azrogers Apr 25, 2025
edb69b5
Update with native changes
azrogers Apr 28, 2025
4d3a810
Update cesium-native
azrogers Apr 30, 2025
158c84c
Fix missing include
azrogers Apr 30, 2025
c4f213e
Remove ITwinResource stuff
azrogers Apr 30, 2025
570f08f
Merge branch 'main' of github.com:CesiumGS/cesium-unreal into itwin-c…
azrogers May 1, 2025
14b1bb0
Fix build with latest native
azrogers May 1, 2025
8f0a56f
Support for vector styling callback
azrogers May 2, 2025
992c2ee
Working style callback
azrogers May 2, 2025
35cf828
Cleanup
azrogers May 2, 2025
5911836
Implement token refresh
azrogers May 13, 2025
5ece3c0
More implementation, use feature-overlay branch of native
azrogers May 13, 2025
a154519
IModel blueprint
azrogers May 13, 2025
dd8adc2
Main iTwin APIs in Cesium for Unreal
azrogers May 14, 2025
e33520d
Update cesium-native
azrogers May 14, 2025
27d30f6
Refactor for geojson-support changes
azrogers May 20, 2025
7e7a960
Merge branch 'main' of github.com:CesiumGS/cesium-unreal into geojson…
azrogers May 20, 2025
3eff421
Bring up to date with geojson-support
azrogers May 20, 2025
d7193a5
Merge from geojson-support
azrogers May 20, 2025
4649dce
Update from changes in Native
azrogers May 20, 2025
eb9fc2a
Format
azrogers May 20, 2025
9542362
Remove unnecessary includes
azrogers May 21, 2025
a7c7ccc
Merge from vector-overlay
azrogers May 22, 2025
dce58da
Update to match Native changes
azrogers May 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Source/CesiumRuntime/CesiumRuntime.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ public CesiumRuntime(ReadOnlyTargetRules Target) : base(Target)
"DeveloperSettings",
"UMG",
"Renderer",
"OpenSSL"
"OpenSSL",
"Json",
"JsonUtilities"
}
);

Expand Down
229 changes: 213 additions & 16 deletions Source/CesiumRuntime/Private/Cesium3DTileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -433,6 +437,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) {
Expand Down Expand Up @@ -1107,30 +1159,106 @@ 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<Cesium3DTilesSelection::Tileset>(
externals,
static_cast<uint32_t>(this->IonAssetID),
TCHAR_TO_UTF8(*token),
options,
ionAssetEndpointUrl);
this->_pTileset = MakeUnique<Cesium3DTilesSelection::Tileset>(
externals,
static_cast<uint32_t>(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);

this->_pTileset = MakeUnique<Cesium3DTilesSelection::Tileset>(
externals,
Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory(
static_cast<uint32_t>(this->ITwinCesiumContentID),
this->ITwinConnection->GetConnection()->getAuthToken().getToken()),
options);
break;
case ETilesetSource::FromIModelMeshExportService:
UE_LOG(
LogCesium,
Log,
TEXT("Loading mesh export for iModel ID %s"),
*this->IModelID);

this->_pTileset = MakeUnique<Cesium3DTilesSelection::Tileset>(
externals,
Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory(
TCHAR_TO_UTF8(*this->IModelID),
std::nullopt,
this->ITwinConnection->GetConnection()->getAuthToken().getToken()),
options);
break;
case ETilesetSource::FromITwinRealityData:
UE_LOG(
LogCesium,
Log,
TEXT("Loading reality data ID %s"),
*this->RealityDataID);

this->_pTileset = MakeUnique<Cesium3DTilesSelection::Tileset>(
externals,
Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory(
TCHAR_TO_UTF8(*this->RealityDataID),
this->ITwinID.IsEmpty() ? std::nullopt
: std::make_optional<std::string>(
TCHAR_TO_UTF8(*this->ITwinID)),
this->ITwinConnection->GetConnection()->getAuthToken().getToken(),
[asyncSystem, pConnection = this->ITwinConnection->GetConnection()](
const std::string&) {
if (!pConnection->getRefreshToken()) {
return asyncSystem.createResolvedFuture<
CesiumUtility::
Result<std::string>>(CesiumUtility::ErrorList::error(
"Access token for reality data is expired, no refresh token available."));
}

return pConnection->me().thenImmediately(
[pConnection](
CesiumUtility::Result<CesiumITwinClient::UserProfile>&&
result) -> CesiumUtility::Result<std::string> {
if (!result.value) {
return CesiumUtility::Result<std::string>(result.errors);
}

if (!pConnection->getAuthToken().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<std::string>(
pConnection->getAuthToken().getToken());
});
}),
options);
break;
}

#ifdef CESIUM_DEBUG_TILE_STATES
Expand Down Expand Up @@ -1180,6 +1308,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) {
Expand Down Expand Up @@ -1221,6 +1370,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
Expand Down Expand Up @@ -1279,6 +1449,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;
}
}

Expand Down Expand Up @@ -2175,6 +2366,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 ==
Expand Down
2 changes: 1 addition & 1 deletion Source/CesiumRuntime/Private/CesiumCartographicPolygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ ACesiumCartographicPolygon::CreateCartographicPolygon(
int32 splinePointsCount = this->Polygon->GetNumberOfSplinePoints();

if (splinePointsCount < 3) {
return CartographicPolygon({});
return CartographicPolygon(std::vector<glm::dvec2>{});
}

std::vector<glm::dvec2> polygon(splinePointsCount);
Expand Down
92 changes: 92 additions & 0 deletions Source/CesiumRuntime/Private/CesiumGeoJsonDocument.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "CesiumGeoJsonDocument.h"
#include "CesiumRuntime.h"

#include "CesiumUtility/Result.h"

#include <span>

bool UCesiumGeoJsonDocumentBlueprintLibrary::LoadGeoJsonFromString(
const FString& InString,
FCesiumGeoJsonDocument& OutVectorDocument) {
const std::string str = TCHAR_TO_UTF8(*InString);
std::span<const std::byte> bytes(
reinterpret_cast<const std::byte*>(str.data()),
str.size());
CesiumUtility::Result<
CesiumUtility::IntrusivePointer<CesiumVectorData::GeoJsonDocument>>
documentResult = CesiumVectorData::GeoJsonDocument::fromGeoJson(bytes);

if (!documentResult.errors.errors.empty()) {
documentResult.errors.logError(
spdlog::default_logger(),
"Errors while loading GeoJSON from string");
}

if (!documentResult.errors.warnings.empty()) {
documentResult.errors.logWarning(
spdlog::default_logger(),
"Warnings while loading GeoJSON from string");
}

if (documentResult.pValue) {
OutVectorDocument =
FCesiumGeoJsonDocument(std::move(documentResult.pValue));
return true;
}

return false;
}

FCesiumGeoJsonObject UCesiumGeoJsonDocumentBlueprintLibrary::GetRootObject(
const FCesiumGeoJsonDocument& InVectorDocument) {
if (!InVectorDocument._document) {
return FCesiumGeoJsonObject();
}

return FCesiumGeoJsonObject(
InVectorDocument._document,
&InVectorDocument._document->getRootObject());
}

UCesiumLoadVectorDocumentFromIonAsyncAction*
UCesiumLoadVectorDocumentFromIonAsyncAction::LoadFromIon(
int64 AssetId,
const FString& IonAccessToken,
const FString& IonAssetEndpointUrl) {
UCesiumLoadVectorDocumentFromIonAsyncAction* pAction =
NewObject<UCesiumLoadVectorDocumentFromIonAsyncAction>();
pAction->AssetId = AssetId;
pAction->IonAccessToken = IonAccessToken;
pAction->IonAssetEndpointUrl = IonAssetEndpointUrl;
return pAction;
}

void UCesiumLoadVectorDocumentFromIonAsyncAction::Activate() {
CesiumVectorData::GeoJsonDocument::fromCesiumIonAsset(
getAsyncSystem(),
getAssetAccessor(),
this->AssetId,
TCHAR_TO_UTF8(*this->IonAccessToken),
TCHAR_TO_UTF8(*this->IonAssetEndpointUrl))
.thenInMainThread(
[Callback = this->OnLoadResult](
CesiumUtility::Result<CesiumUtility::IntrusivePointer<
CesiumVectorData::GeoJsonDocument>>&& result) {
if (result.errors.hasErrors()) {
result.errors.logError(
spdlog::default_logger(),
"Errors loading GeoJSON:");
result.errors.logWarning(
spdlog::default_logger(),
"Warnings loading GeoJSON:");
}

if (result.pValue) {
Callback.Broadcast(
true,
FCesiumGeoJsonDocument(MoveTemp(result.pValue)));
} else {
Callback.Broadcast(false, {});
}
});
}
Loading
Loading