diff --git a/CHANGELOG.md b/CHANGELOG.md index 601eccb..27edb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] 2024-10-05 + +### Added + +- Pagination support added to UI samples +- WebApis updates to fallback to cache if available + +### Fixed + +- WebApi's refactored to fix a number of bugs + + ## [0.1.0] 2024-09-05 ### Added diff --git a/Content/Samples/BasicLoader/BP_RpmActor.uasset b/Content/Samples/BasicLoader/BP_RpmActor.uasset deleted file mode 100644 index b37b7db..0000000 Binary files a/Content/Samples/BasicLoader/BP_RpmActor.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/BP_RpmActor_C.uasset b/Content/Samples/BasicLoader/BP_RpmActor_C.uasset deleted file mode 100644 index 5de0f0e..0000000 Binary files a/Content/Samples/BasicLoader/BP_RpmActor_C.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/BasicLoaderSample.umap b/Content/Samples/BasicLoader/BasicLoaderSample.umap deleted file mode 100644 index f9ca51c..0000000 Binary files a/Content/Samples/BasicLoader/BasicLoaderSample.umap and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset deleted file mode 100644 index 0cc5e93..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset deleted file mode 100644 index 055f579..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset b/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset deleted file mode 100644 index 33daf78..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/BP_SampleRpmActor.uasset b/Content/Samples/BasicLoader/Blueprints/BP_SampleRpmActor.uasset new file mode 100644 index 0000000..c4b2c17 Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/BP_SampleRpmActor.uasset differ diff --git a/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset b/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset deleted file mode 100644 index 64cf9ec..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset deleted file mode 100644 index 2e1f211..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset deleted file mode 100644 index a022f5a..0000000 Binary files a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI.uasset new file mode 100644 index 0000000..b5a4e30 Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI.uasset differ diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI_NEW.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI_NEW.uasset new file mode 100644 index 0000000..41e2705 Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/WBP_RpmLoaderUI_NEW.uasset differ diff --git a/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset b/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset deleted file mode 100644 index 6253a3c..0000000 Binary files a/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset and /dev/null differ diff --git a/Content/Samples/BasicLoader/LoaderSample.umap b/Content/Samples/BasicLoader/LoaderSample.umap deleted file mode 100644 index 87bde8b..0000000 Binary files a/Content/Samples/BasicLoader/LoaderSample.umap and /dev/null differ diff --git a/Content/Samples/BasicLoader/RpmBasicLoader.umap b/Content/Samples/BasicLoader/RpmBasicLoader.umap new file mode 100644 index 0000000..31f765f Binary files /dev/null and b/Content/Samples/BasicLoader/RpmBasicLoader.umap differ diff --git a/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap b/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap deleted file mode 100644 index bb88ae8..0000000 Binary files a/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap and /dev/null differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset b/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset deleted file mode 100644 index 2fe6816..0000000 Binary files a/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset and /dev/null differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset b/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset deleted file mode 100644 index 9036355..0000000 Binary files a/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset and /dev/null differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset b/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset deleted file mode 100644 index a0e8efa..0000000 Binary files a/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset and /dev/null differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset b/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset deleted file mode 100644 index faf6e70..0000000 Binary files a/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset and /dev/null differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetPanel.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetPanel.uasset new file mode 100644 index 0000000..438ffd1 Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetPanel.uasset differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmBasicUI.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmBasicUI.uasset new file mode 100644 index 0000000..4cfdaac Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmBasicUI.uasset differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryButton.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryButton.uasset new file mode 100644 index 0000000..b6f3c3d Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryButton.uasset differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset index 63c1b44..913996e 100644 Binary files a/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset and b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmPaginator.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmPaginator.uasset new file mode 100644 index 0000000..de4d000 Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmPaginator.uasset differ diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmUI.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmUI.uasset new file mode 100644 index 0000000..35a8ad7 Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmUI.uasset differ diff --git a/Content/Samples/BasicUI/RpmBasicUISample.umap b/Content/Samples/BasicUI/RpmBasicUISample.umap index f81c34e..03f4e16 100644 Binary files a/Content/Samples/BasicUI/RpmBasicUISample.umap and b/Content/Samples/BasicUI/RpmBasicUISample.umap differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset b/Content/Samples/Icons/T-rpm-Custom-bottom.uasset similarity index 86% rename from Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset rename to Content/Samples/Icons/T-rpm-Custom-bottom.uasset index cfde8a4..4d5db28 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset and b/Content/Samples/Icons/T-rpm-Custom-bottom.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset b/Content/Samples/Icons/T-rpm-Custom-footwear.uasset similarity index 85% rename from Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset rename to Content/Samples/Icons/T-rpm-Custom-footwear.uasset index 5d7def3..4dc0922 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset and b/Content/Samples/Icons/T-rpm-Custom-footwear.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-top.uasset b/Content/Samples/Icons/T-rpm-Custom-top.uasset similarity index 84% rename from Content/Samples/BasicUI/Icons/T-rpm-top.uasset rename to Content/Samples/Icons/T-rpm-Custom-top.uasset index b6acb8e..a57c9dd 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-top.uasset and b/Content/Samples/Icons/T-rpm-Custom-top.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset b/Content/Samples/Icons/T-rpm-baseModel.uasset similarity index 86% rename from Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset rename to Content/Samples/Icons/T-rpm-baseModel.uasset index 2536a4f..ada5385 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset and b/Content/Samples/Icons/T-rpm-baseModel.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-custom.uasset b/Content/Samples/Icons/T-rpm-custom.uasset similarity index 85% rename from Content/Samples/BasicUI/Icons/T-rpm-custom.uasset rename to Content/Samples/Icons/T-rpm-custom.uasset index fd63df0..c1b140a 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-custom.uasset and b/Content/Samples/Icons/T-rpm-custom.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset b/Content/Samples/Icons/T-rpm-facial-hair.uasset similarity index 86% rename from Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset rename to Content/Samples/Icons/T-rpm-facial-hair.uasset index bb1e8e8..d8b63c8 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset and b/Content/Samples/Icons/T-rpm-facial-hair.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset b/Content/Samples/Icons/T-rpm-glasses.uasset similarity index 85% rename from Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset rename to Content/Samples/Icons/T-rpm-glasses.uasset index 19b0ae4..fc789d0 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset and b/Content/Samples/Icons/T-rpm-glasses.uasset differ diff --git a/Content/Samples/BasicUI/Icons/T-rpm-hair.uasset b/Content/Samples/Icons/T-rpm-hair.uasset similarity index 82% rename from Content/Samples/BasicUI/Icons/T-rpm-hair.uasset rename to Content/Samples/Icons/T-rpm-hair.uasset index f2daba7..0139833 100644 Binary files a/Content/Samples/BasicUI/Icons/T-rpm-hair.uasset and b/Content/Samples/Icons/T-rpm-hair.uasset differ diff --git a/RpmNextGen.uplugin b/RpmNextGen.uplugin index 2091eb5..8f3560c 100644 --- a/RpmNextGen.uplugin +++ b/RpmNextGen.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "0.1", + "VersionName": "0.2", "FriendlyName": "RpmNextGen", "Description": "Ready Player Me Next Gen SDK", "Category": "Other", diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp index d22540b..973ab13 100644 --- a/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp +++ b/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp @@ -1,52 +1,216 @@ -#include "Api/Assets/AssetApi.h" +#include "Api/Assets/AssetApi.h" + +#include "RpmNextGen.h" #include "Settings/RpmDeveloperSettings.h" #include "Api/Assets/Models/AssetListRequest.h" #include "Api/Assets/Models/AssetListResponse.h" +#include "Api/Assets/Models/AssetTypeListRequest.h" +#include "Api/Auth/ApiKeyAuthStrategy.h" +#include "Cache/AssetCacheManager.h" +#include "Interfaces/IHttpResponse.h" + +struct FCachedAssetData; +struct FAssetTypeListRequest; +const FString FAssetApi::BaseModelType = TEXT("baseModel"); + +FAssetApi::FAssetApi() : ApiRequestStrategy(EApiRequestStrategy::FallbackToCache) +{ + Initialize(); +} -FAssetApi::FAssetApi() +FAssetApi::FAssetApi(EApiRequestStrategy InApiRequestStrategy) : ApiRequestStrategy(InApiRequestStrategy) { - OnApiResponse.BindRaw(this, &FAssetApi::HandleListAssetResponse); + Initialize(); } +FAssetApi::~FAssetApi() +{ +} + +void FAssetApi::Initialize() +{ + if (bIsInitialized) return; + + const URpmDeveloperSettings* Settings = GetDefault(); + OnRequestComplete.BindRaw(this, &FAssetApi::HandleAssetResponse); + if (Settings->ApplicationId.IsEmpty()) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Application ID is empty. Please set the Application ID in the Ready Player Me Developer Settings")); + } + + if (!Settings->ApiKey.IsEmpty() || Settings->ApiProxyUrl.IsEmpty()) + { + SetAuthenticationStrategy(MakeShared()); + } + bIsInitialized = true; +} void FAssetApi::ListAssetsAsync(const FAssetListRequest& Request) { + if(ApiRequestStrategy == EApiRequestStrategy::CacheOnly) + { + LoadAssetsFromCache(Request.BuildQueryMap()); + return; + } const URpmDeveloperSettings* RpmSettings = GetDefault(); ApiBaseUrl = RpmSettings->GetApiBaseUrl(); if(RpmSettings->ApplicationId.IsEmpty()) { - UE_LOG(LogTemp, Error, TEXT("Application ID is empty")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Application ID is empty")); OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false); return; } - FString QueryString = Request.BuildQueryString(); - const FString Url = FString::Printf(TEXT("%s/v1/phoenix-assets%s"), *ApiBaseUrl, *QueryString); - FApiRequest ApiRequest = FApiRequest(); - ApiRequest.Url = Url; - ApiRequest.Method = GET; + const FString Url = FString::Printf(TEXT("%s/v1/phoenix-assets"), *ApiBaseUrl); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = Url; + ApiRequest->Method = GET; + ApiRequest->QueryParams = Request.BuildQueryMap(); DispatchRawWithAuth(ApiRequest); } -void FAssetApi::HandleListAssetResponse(FString Response, bool bWasSuccessful) +void FAssetApi::ListAssetTypesAsync(const FAssetTypeListRequest& Request) { - if(bWasSuccessful) + if(ApiRequestStrategy == EApiRequestStrategy::CacheOnly) { - FAssetListResponse AssetListResponse = FAssetListResponse(); - if(FJsonObjectConverter::JsonObjectStringToUStruct(Response, &AssetListResponse, 0, 0)) + LoadAssetTypesFromCache(); + return; + } + URpmDeveloperSettings* Settings = GetMutableDefault(); + ApiBaseUrl = Settings->GetApiBaseUrl(); + if(Settings->ApplicationId.IsEmpty()) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Application ID is empty")); + OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false); + return; + } + FString QueryString = Request.BuildQueryString(); + const FString Url = FString::Printf(TEXT("%s/v1/phoenix-assets/types%s"), *ApiBaseUrl, *QueryString); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = Url; + ApiRequest->Method = GET; + + DispatchRawWithAuth( ApiRequest); +} + +void FAssetApi::HandleAssetResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) +{ + const bool bIsTypeRequest = ApiRequest->Url.Contains(TEXT("/types")); + if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode())) + { + FAssetTypeListResponse AssetTypeListResponse; + + if (bIsTypeRequest && FJsonObjectConverter::JsonObjectStringToUStruct(Response->GetContentAsString(), &AssetTypeListResponse, 0, 0)) + { + OnListAssetTypeResponse.ExecuteIfBound(AssetTypeListResponse, true); + return; + } + FAssetListResponse AssetListResponse; + if (!bIsTypeRequest && FJsonObjectConverter::JsonObjectStringToUStruct(Response->GetContentAsString(), &AssetListResponse, 0, 0)) { OnListAssetsResponse.ExecuteIfBound(AssetListResponse, true); return; } - UE_LOG(LogTemp, Error, TEXT("Failed to parse API response")); + } + + if(ApiRequestStrategy == EApiRequestStrategy::ApiOnly) + { + if(bIsTypeRequest) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("AssetApi:ListAssetTypesAsync request failed: %s"), *Response->GetContentAsString()); + OnListAssetTypeResponse.ExecuteIfBound(FAssetTypeListResponse(), false); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("AssetApi:ListAssetsAsync request failed: %s"), *Response->GetContentAsString()); + OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false); + return; + } + if(bIsTypeRequest) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("FAssetApi::ListAssetTypesAsync request failed, falling back to cache.")); + LoadAssetTypesFromCache(); + return; + } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("FAssetApi::ListAssetsAsync request failed, falling back to cache.")); + LoadAssetsFromCache(ApiRequest->QueryParams); +} + +void FAssetApi::LoadAssetsFromCache(TMap QueryParams) +{ + if(QueryParams.Num() < 1) + { + OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false); + return; + } + const FString TypeKey = TEXT("type"); + const FString ExcludeTypeKey = TEXT("excludeType"); + FString Type = QueryParams.Contains(TypeKey) ? *QueryParams.Find(TypeKey) : FString(); + FString ExcludeType = QueryParams.Contains(ExcludeTypeKey) ? *QueryParams.Find(ExcludeTypeKey) : FString(); + TArray CachedAssets; + + if(ExcludeType.IsEmpty()) + { + CachedAssets = FAssetCacheManager::Get().GetAssetsOfType(Type); } else { - UE_LOG(LogTemp, Error, TEXT("API Response was unsuccessful")); + auto ExtractQueryString = TEXT("excludeType=") + ExcludeType; + auto ExtractQueryArray = ExtractQueryValues(ExcludeType, ExcludeTypeKey); + CachedAssets = FAssetCacheManager::Get().GetAssetsExcludingTypes(ExtractQueryArray); + } + + if (CachedAssets.Num() > 0) + { + FAssetListResponse AssetListResponse; + for (const FCachedAssetData& CachedAsset : CachedAssets) + { + FAsset Asset = CachedAsset.ToAsset(); + AssetListResponse.Data.Add(Asset); + } + OnListAssetsResponse.ExecuteIfBound(AssetListResponse, true); + return; } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("No assets found in the cache.")); OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false); } -void FAssetApi::HandleListAssetTypeResponse(FString Response, bool bWasSuccessful) +void FAssetApi::LoadAssetTypesFromCache() { + TArray AssetTypes = FAssetCacheManager::Get().LoadAssetTypes(); + if (AssetTypes.Num() > 0) + { + FAssetTypeListResponse AssetListResponse; + AssetListResponse.Data.Append(AssetTypes); + OnListAssetTypeResponse.ExecuteIfBound(AssetListResponse, true); + return; + } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("No assets found in the cache.")); + OnListAssetTypeResponse.ExecuteIfBound(FAssetTypeListResponse(), false); +} + +TArray FAssetApi::ExtractQueryValues(const FString& QueryString, const FString& Key) +{ + TArray Values; + TArray Pairs; + + // Split the query string by '&' to separate each key-value pair + QueryString.ParseIntoArray(Pairs, TEXT("&"), true); + + // Iterate through all pairs and check if they match the key + for (const FString& Pair : Pairs) + { + FString KeyPart, ValuePart; + + // Split the pair by '=' to get the key and value + if (Pair.Split(TEXT("="), &KeyPart, &ValuePart)) + { + // If the key matches the one we're looking for, add the value to the array + if (KeyPart.Equals(Key, ESearchCase::IgnoreCase)) + { + Values.Add(ValuePart); + } + } + } + + return Values; } diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetGlbLoader.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetGlbLoader.cpp new file mode 100644 index 0000000..dcba160 --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Assets/AssetGlbLoader.cpp @@ -0,0 +1,61 @@ +#include "Api/Assets/AssetGlbLoader.h" +#include "Api/Assets/AssetLoaderContext.h" +#include "HttpModule.h" +#include "Cache/AssetCacheManager.h" +#include "Interfaces/IHttpResponse.h" + +FAssetGlbLoader::FAssetGlbLoader() +{ + Http = &FHttpModule::Get(); +} + +FAssetGlbLoader::~FAssetGlbLoader() +{ +} + +void FAssetGlbLoader::LoadGlb(const FAsset& Asset, const FString& BaseModelId, bool bStoreInCache) +{ + FCachedAssetData StoredAsset; + if (FAssetCacheManager::Get().GetCachedAsset(Asset.Id, StoredAsset)) + { + TArray GlbData; + const FString StoredGlbPath = StoredAsset.GetGlbPathForBaseModelId(BaseModelId); + if(FFileHelper::LoadFileToArray(GlbData, *StoredGlbPath)) + { + OnGlbLoaded.ExecuteIfBound(Asset, GlbData); + return; + } + } + const TSharedRef Context = MakeShared(Asset, BaseModelId, bStoreInCache); + LoadGlb(Context); +} + +void FAssetGlbLoader::LoadGlb(TSharedRef Context) +{ + TSharedRef Request = Http->CreateRequest(); + Request->SetURL(Context->Asset.GlbUrl); + Request->SetVerb(TEXT("GET")); + + TSharedPtr ThisPtr = SharedThis(this); + Request->OnProcessRequestComplete().BindLambda([ThisPtr, Context](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) + { + ThisPtr->GlbLoaded(Response, bWasSuccessful, Context); + }); + Request->ProcessRequest(); +} + +void FAssetGlbLoader::GlbLoaded(TSharedPtr Response, const bool bWasSuccessful, const TSharedRef& Context) +{ + if (bWasSuccessful && Response.IsValid()) + { + Context->Data = Response->GetContent(); + if (Context->bStoreInCache) + { + FAssetCacheManager::Get().StoreAndTrackGlb(*Context); + } + OnGlbLoaded.ExecuteIfBound(Context->Asset, Context->Data); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load GLB from URL: %s"), *Context->Asset.GlbUrl); + OnGlbLoaded.ExecuteIfBound(Context->Asset, TArray()); +} \ No newline at end of file diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetIconLoader.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetIconLoader.cpp new file mode 100644 index 0000000..b9d8a1b --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Assets/AssetIconLoader.cpp @@ -0,0 +1,60 @@ +#include "Api/Assets/AssetIconLoader.h" +#include "Api/Assets/AssetLoaderContext.h" +#include "HttpModule.h" +#include "Cache/AssetCacheManager.h" +#include "Interfaces/IHttpResponse.h" + +FAssetIconLoader::FAssetIconLoader() +{ + Http = &FHttpModule::Get(); +} + +FAssetIconLoader::~FAssetIconLoader() +{ +} + +void FAssetIconLoader::LoadIcon(const FAsset& Asset, bool bStoreInCache) +{ + FCachedAssetData StoredAsset; + if (FAssetCacheManager::Get().GetCachedAsset(Asset.Id, StoredAsset)) + { + TArray IconData; + if(FFileHelper::LoadFileToArray(IconData, * FFileUtility::GetFullPersistentPath(StoredAsset.RelativeIconFilePath))) + { + OnIconLoaded.ExecuteIfBound(Asset, IconData); + return; + } + } + const TSharedRef Context = MakeShared(Asset, "", bStoreInCache); + LoadIcon(Context); +} + +void FAssetIconLoader::LoadIcon(TSharedRef Context) +{ + TSharedRef Request = Http->CreateRequest(); + Request->SetURL(Context->Asset.IconUrl); + Request->SetVerb(TEXT("GET")); + + TSharedPtr ThisPtr = SharedThis(this); + Request->OnProcessRequestComplete().BindLambda([ThisPtr, Context](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) + { + ThisPtr->IconLoaded(Response, bWasSuccessful, Context); + }); + Request->ProcessRequest(); +} + +void FAssetIconLoader::IconLoaded(TSharedPtr Response, bool bWasSuccessful, const TSharedRef& Context) +{ + if (bWasSuccessful && Response.IsValid()) + { + Context->Data = Response->GetContent(); + if (Context->bStoreInCache) + { + FAssetCacheManager::Get().StoreAndTrackIcon(*Context); + } + OnIconLoaded.ExecuteIfBound(Context->Asset, Context->Data); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load icon from URL: %s"), *Context->Asset.IconUrl); + OnIconLoaded.ExecuteIfBound(Context->Asset, TArray()); +} diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp deleted file mode 100644 index 88f8f09..0000000 --- a/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "Api/Assets/AssetLoader.h" -#include "HttpModule.h" -#include "RpmNextGen.h" -#include "glTFRuntime/Public/glTFRuntimeFunctionLibrary.h" -#include "Interfaces/IHttpResponse.h" -#include "Misc/FileHelper.h" -#include "HAL/PlatformFilemanager.h" - -FAssetLoader::FAssetLoader() -{ - GltfConfig = new FglTFRuntimeConfig(); - GltfConfig->TransformBaseType = EglTFRuntimeTransformBaseType::YForward; - DownloadDirectory = FPaths::ProjectContentDir() / TEXT("ReadyPlayerMe/"); - - // Ensure the directory exists - IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - if (!PlatformFile.DirectoryExists(*DownloadDirectory)) - { - PlatformFile.CreateDirectory(*DownloadDirectory); - } -} - -FAssetLoader::FAssetLoader(FglTFRuntimeConfig* Config) : GltfConfig(Config) -{ -} - -FAssetLoader::~FAssetLoader() -{ -} - -void FAssetLoader::LoadGLBFromURL(const FString& URL) -{ - // TODO replace this with use of WebApi class - TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); - HttpRequest->OnProcessRequestComplete().BindRaw(this, &FAssetLoader::OnLoadComplete); - HttpRequest->SetURL(URL); - HttpRequest->SetVerb(TEXT("GET")); - HttpRequest->ProcessRequest(); -} - -void FAssetLoader::OnLoadComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - TArray Content = TArray(); - UglTFRuntimeAsset* gltfAsset = nullptr; - bool AssetFileSaved = false; - if (bWasSuccessful && Response.IsValid()) - { - Content = Response->GetContent(); - - const FString FileName = FPaths::GetCleanFilename(Request->GetURL()); - const FString FilePath = DownloadDirectory / FileName; - - if(bSaveToDisk && FFileHelper::SaveArrayToFile(Response->GetContent(), *FilePath)) - { - UE_LOG(LogReadyPlayerMe, Log, TEXT("Downloaded GLB file to %s"), *FilePath); - AssetFileSaved = true; - } - if(OnGLtfAssetLoaded.IsBound()) - { - gltfAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(Content, *GltfConfig); - } - OnRequestDataReceived.ExecuteIfBound(Content, Content.Num() > 0); - OnGLtfAssetLoaded.ExecuteIfBound(gltfAsset, gltfAsset != nullptr); - OnAssetSaved.ExecuteIfBound(FilePath, AssetFileSaved); - return; - } - UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load GLB from URL")); - OnRequestDataReceived.ExecuteIfBound(Content, Content.Num() > 0); - OnGLtfAssetLoaded.ExecuteIfBound(gltfAsset, gltfAsset != nullptr); - OnAssetSaved.ExecuteIfBound("", AssetFileSaved); -} diff --git a/Source/RpmNextGen/Private/Api/Auth/ApiKeyAuthStrategy.cpp b/Source/RpmNextGen/Private/Api/Auth/ApiKeyAuthStrategy.cpp index 897fbd2..80632d2 100644 --- a/Source/RpmNextGen/Private/Api/Auth/ApiKeyAuthStrategy.cpp +++ b/Source/RpmNextGen/Private/Api/Auth/ApiKeyAuthStrategy.cpp @@ -1,4 +1,6 @@ #include "Api/Auth/ApiKeyAuthStrategy.h" + +#include "RpmNextGen.h" #include "Settings/RpmDeveloperSettings.h" class URpmSettings; @@ -7,26 +9,25 @@ FApiKeyAuthStrategy::FApiKeyAuthStrategy() { } -void FApiKeyAuthStrategy::AddAuthToRequest(TSharedPtr Request) +void FApiKeyAuthStrategy::AddAuthToRequest(TSharedPtr ApiRequest) { const URpmDeveloperSettings* RpmSettings = GetDefault(); if(RpmSettings->ApiKey.IsEmpty()) { - UE_LOG(LogTemp, Error, TEXT("API Key is empty")); - OnAuthComplete.ExecuteIfBound(false); + UE_LOG(LogReadyPlayerMe, Error, TEXT("API Key is empty")); + OnAuthComplete.ExecuteIfBound(ApiRequest, false); return; } - Request->Headers.Add(TEXT("X-API-KEY"), RpmSettings->ApiKey); - OnAuthComplete.ExecuteIfBound(true); + ApiRequest->Headers.Add(TEXT("X-API-KEY"), RpmSettings->ApiKey); + OnAuthComplete.ExecuteIfBound(ApiRequest, true); } -void FApiKeyAuthStrategy::OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful) +void FApiKeyAuthStrategy::OnRefreshTokenResponse(TSharedPtr ApiRequest, const FRefreshTokenResponse& Response, bool bWasSuccessful) { + OnTokenRefreshed.ExecuteIfBound(ApiRequest, Response.Data, bWasSuccessful); } -void FApiKeyAuthStrategy::TryRefresh(TSharedPtr Request) +void FApiKeyAuthStrategy::TryRefresh(TSharedPtr ApiRequest) { + OnAuthComplete.ExecuteIfBound(ApiRequest, false); } - - - diff --git a/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp b/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp index fa43fa2..9b7dca3 100644 --- a/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp +++ b/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp @@ -1,4 +1,6 @@ #include "Api/Auth/AuthApi.h" + +#include "RpmNextGen.h" #include "Interfaces/IHttpResponse.h" #include "Api/Auth/Models/RefreshTokenRequest.h" #include "Api/Auth/Models/RefreshTokenResponse.h" @@ -8,27 +10,45 @@ FAuthApi::FAuthApi() { const URpmDeveloperSettings* RpmSettings = GetDefault(); ApiUrl = FString::Printf(TEXT("%s/refresh"), *RpmSettings->ApiBaseAuthUrl); + OnRequestComplete.BindRaw(this, &FAuthApi::OnProcessComplete); } void FAuthApi::RefreshToken(const FRefreshTokenRequest& Request) { - FApiRequest ApiRequest = FApiRequest(); - ApiRequest.Url = ApiUrl; - ApiRequest.Method = POST; - ApiRequest.Headers.Add(TEXT("Content-Type"), TEXT("application/json")); - ApiRequest.Payload = Request.ToJsonString(); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = ApiUrl; + ApiRequest->Method = POST; + ApiRequest->Headers.Add(TEXT("Content-Type"), TEXT("application/json")); + ApiRequest->Payload = Request.ToJsonString(); DispatchRaw(ApiRequest); } -void FAuthApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +void FAuthApi::OnProcessComplete(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) { - FString data = Response->GetContentAsString(); - - FRefreshTokenResponse TokenResponse; - if (bWasSuccessful && !data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(data, &TokenResponse, 0, 0)) + if (!ApiRequest.IsValid()) { - OnRefreshTokenResponse.ExecuteIfBound(TokenResponse, true); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Invalid ApiRequest in FAuthApi::OnProcessComplete.")); return; } - OnRefreshTokenResponse.ExecuteIfBound(FRefreshTokenResponse(), false); + + if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode())) + { + FString Data = Response->GetContentAsString(); + FRefreshTokenResponse TokenResponse; + + if (!Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &TokenResponse, 0, 0)) + { + if (OnRefreshTokenResponse.IsBound()) + { + OnRefreshTokenResponse.ExecuteIfBound(ApiRequest, TokenResponse, true); + } + return; + } + } + + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to refresh token")); + if (OnRefreshTokenResponse.IsBound()) + { + OnRefreshTokenResponse.ExecuteIfBound(ApiRequest, FRefreshTokenResponse(), false); + } } diff --git a/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp b/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp index 8245e5d..8c83bf6 100644 --- a/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp +++ b/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp @@ -1,5 +1,6 @@ #include "Api/Characters/CharacterApi.h" #include "HttpModule.h" +#include "RpmNextGen.h" #include "Api/Auth/ApiKeyAuthStrategy.h" #include "Api/Characters/Models/CharacterFindByIdRequest.h" #include "Api/Characters/Models/CharacterPreviewRequest.h" @@ -11,12 +12,11 @@ FCharacterApi::FCharacterApi() { const URpmDeveloperSettings* RpmSettings = GetDefault(); BaseUrl = FString::Printf(TEXT("%s/v1/characters"), *RpmSettings->GetApiBaseUrl()); - Http = &FHttpModule::Get(); - SetAuthenticationStrategy(nullptr); if(!RpmSettings->ApiKey.IsEmpty() && RpmSettings->ApiProxyUrl.IsEmpty()) { - SetAuthenticationStrategy(new FApiKeyAuthStrategy()); + SetAuthenticationStrategy(MakeShared()); } + OnRequestComplete.BindRaw(this, &FCharacterApi::HandleCharacterResponse); } FCharacterApi::~FCharacterApi() @@ -25,30 +25,32 @@ FCharacterApi::~FCharacterApi() void FCharacterApi::CreateAsync(const FCharacterCreateRequest& Request) { - FApiRequest ApiRequest; - ApiRequest.Url = FString::Printf(TEXT("%s"), *BaseUrl); - ApiRequest.Method = POST; - ApiRequest.Payload = ConvertToJsonString(Request); - ApiRequest.Headers.Add(TEXT("Content-Type"), TEXT("application/json")); + AssetByType.Append(Request.Data.Assets); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = FString::Printf(TEXT("%s"), *BaseUrl); + ApiRequest->Method = POST; + ApiRequest->Payload = ConvertToJsonString(Request); + ApiRequest->Headers.Add(TEXT("Content-Type"), TEXT("application/json")); DispatchRawWithAuth(ApiRequest); } void FCharacterApi::UpdateAsync(const FCharacterUpdateRequest& Request) { - FApiRequest ApiRequest; - ApiRequest.Url = FString::Printf(TEXT("%s/%s"), *BaseUrl, *Request.Id); - ApiRequest.Method = PATCH; - ApiRequest.Payload = ConvertToJsonString(Request); - ApiRequest.Headers.Add(TEXT("Content-Type"), TEXT("application/json")); + AssetByType.Append(Request.Payload.Assets); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = FString::Printf(TEXT("%s/%s"), *BaseUrl, *Request.Id); + ApiRequest->Method = PATCH; + ApiRequest->Payload = ConvertToJsonString(Request); + ApiRequest->Headers.Add(TEXT("Content-Type"), TEXT("application/json")); DispatchRawWithAuth(ApiRequest); } void FCharacterApi::FindByIdAsync(const FCharacterFindByIdRequest& Request) { - FApiRequest ApiRequest; - ApiRequest.Url = FString::Printf(TEXT("%s/%s"), *BaseUrl, *Request.Id); - ApiRequest.Method = GET; - ApiRequest.Headers.Add(TEXT("Content-Type"), TEXT("application/json")); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = FString::Printf(TEXT("%s/%s"), *BaseUrl, *Request.Id); + ApiRequest->Method = GET; + ApiRequest->Headers.Add(TEXT("Content-Type"), TEXT("application/json")); DispatchRawWithAuth(ApiRequest); } @@ -59,44 +61,75 @@ FString FCharacterApi::GeneratePreviewUrl(const FCharacterPreviewRequest& Reques return url; } -void FCharacterApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +void FCharacterApi::HandleCharacterResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) { - FWebApiWithAuth::OnProcessResponse(Request, Response, bWasSuccessful); + const FString Verb = ApiRequest->GetVerb(); bool bSuccess = bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()); - if (Response->GetResponseCode() == 401) + if (Response.IsValid() && Response->GetResponseCode() == 401) { - URpmDeveloperSettings* Settings = GetMutableDefault(); - - UE_LOG(LogTemp, Error,TEXT("The request to the character API failed with a 401 response code. Please ensure that your API Key or proxy is correctly configured.")); - UE_LOG(LogTemp, Error,TEXT("API Key: %s. Proxy url = %s"), *Settings->ApiKey, *Settings->ApiProxyUrl); - return; + UE_LOG(LogReadyPlayerMe, Error,TEXT("The request to the character API failed with a 401 response code. Please ensure that your API Key or proxy is correctly configured.")); + bSuccess = false; } - - const FString Verb = Request->GetVerb(); - if (Verb == "POST") + switch (ApiRequest->Method) + { + case POST: + HandleCharacterCreateResponse(Response, bSuccess); + break; + case PATCH: + HandleUpdateResponse( Response, bSuccess); + break; + case GET: + HandleFindResponse(Response, bSuccess); + break; + default: + break; + } +} + +void FCharacterApi::HandleCharacterCreateResponse(FHttpResponsePtr Response, bool bWasSuccessful) +{ + if(bWasSuccessful) { FCharacterCreateResponse CharacterCreateResponse; - bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct( - Response->GetContentAsString(), &CharacterCreateResponse, 0, 0); - OnCharacterCreateResponse.ExecuteIfBound(CharacterCreateResponse, bSuccess); + if(FJsonObjectConverter::JsonObjectStringToUStruct( Response->GetContentAsString(), &CharacterCreateResponse, 0, 0)) + { + OnCharacterCreateResponse.ExecuteIfBound(CharacterCreateResponse, true); + return; + } } - else if (Verb == "PATCH") + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Character CREATE request failed.")); + OnCharacterCreateResponse.ExecuteIfBound(FCharacterCreateResponse(), false); +} + +void FCharacterApi::HandleUpdateResponse(FHttpResponsePtr Response, bool bWasSuccessful) +{ + if(bWasSuccessful) { FCharacterUpdateResponse CharacterUpdateResponse; - bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct( - Response->GetContentAsString(), &CharacterUpdateResponse, 0, 0); - OnCharacterUpdateResponse.ExecuteIfBound(CharacterUpdateResponse, bSuccess); + if(FJsonObjectConverter::JsonObjectStringToUStruct( Response->GetContentAsString(), &CharacterUpdateResponse, 0, 0)) + { + OnCharacterUpdateResponse.ExecuteIfBound(CharacterUpdateResponse, true); + return; + } } - else if (Verb == "GET") + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Character UPDATE request failed.")); + OnCharacterUpdateResponse.ExecuteIfBound(FCharacterUpdateResponse(), false); +} + +void FCharacterApi::HandleFindResponse(FHttpResponsePtr Response, bool bWasSuccessful) +{ + if(bWasSuccessful) { FCharacterFindByIdResponse CharacterFindByIdResponse; - bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct( - Response->GetContentAsString(), &CharacterFindByIdResponse, 0, 0); - OnCharacterFindResponse.ExecuteIfBound(CharacterFindByIdResponse, bSuccess); - } - else - { - UE_LOG(LogTemp, Warning, TEXT("Unhandled verb")); + if(FJsonObjectConverter::JsonObjectStringToUStruct( Response->GetContentAsString(), &CharacterFindByIdResponse, 0, 0)) + { + OnCharacterFindResponse.ExecuteIfBound(CharacterFindByIdResponse, true); + return; + } } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Character FIND request failed.")); + OnCharacterFindResponse.ExecuteIfBound(FCharacterFindByIdResponse(), false); } + + diff --git a/Source/RpmNextGen/Private/Api/Common/WebApi.cpp b/Source/RpmNextGen/Private/Api/Common/WebApi.cpp index 8d53855..cd5a1ff 100644 --- a/Source/RpmNextGen/Private/Api/Common/WebApi.cpp +++ b/Source/RpmNextGen/Private/Api/Common/WebApi.cpp @@ -1,6 +1,6 @@ #include "Api/Common/WebApi.h" #include "HttpModule.h" -#include "GenericPlatform/GenericPlatformHttp.h" +#include "RpmNextGen.h" #include "Interfaces/IHttpResponse.h" FWebApi::FWebApi() @@ -8,38 +8,43 @@ FWebApi::FWebApi() Http = &FHttpModule::Get(); } -FWebApi::~FWebApi() {} - -void FWebApi::DispatchRaw(const FApiRequest& Data) +FWebApi::~FWebApi() { - TSharedRef Request = Http->CreateRequest(); - Request->SetURL(Data.Url); - Request->SetVerb(Data.GetVerb()); + +} - for (const auto& Header : Data.Headers) +void FWebApi::DispatchRaw(TSharedPtr ApiRequest) +{ + TSharedPtr Request = Http->CreateRequest(); + FString Url = ApiRequest->Url + BuildQueryString(ApiRequest->QueryParams); + Request->SetURL(Url); + Request->SetVerb(ApiRequest->GetVerb()); + Request->SetTimeout(10); + FString Headers; + for (const auto& Header : ApiRequest->Headers) { Request->SetHeader(Header.Key, Header.Value); + Headers.Append(FString::Printf(TEXT("%s: %s\n"), *Header.Key, *Header.Value)); } - if (!Data.Payload.IsEmpty()) + if (!ApiRequest->Payload.IsEmpty() && ApiRequest->Method != ERequestMethod::GET) { - Request->SetContentAsString(Data.Payload); + Request->SetContentAsString(ApiRequest->Payload); } - - Request->OnProcessRequestComplete().BindRaw(this, &FWebApi::OnProcessResponse); + Request->OnProcessRequestComplete().BindRaw(this, &FWebApi::OnProcessResponse, ApiRequest); Request->ProcessRequest(); } -void FWebApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +void FWebApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TSharedPtr ApiRequest) { if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode())) { - OnApiResponse.ExecuteIfBound(Response->GetContentAsString(), true); + OnRequestComplete.ExecuteIfBound(ApiRequest, Response, true); return; } FString ErrorMessage = Response.IsValid() ? Response->GetContentAsString() : TEXT("Request failed"); - UE_LOG(LogTemp, Warning, TEXT("WebApi from URL %s request failed: %s"), *Request->GetURL(), *ErrorMessage); - OnApiResponse.ExecuteIfBound(Response->GetContentAsString(), false); + UE_LOG(LogReadyPlayerMe, Warning, TEXT("WebApi from URL %s request failed: %s"), *Request->GetURL(), *ErrorMessage); + OnRequestComplete.ExecuteIfBound(ApiRequest, Response, false); } FString FWebApi::BuildQueryString(const TMap& QueryParams) @@ -50,9 +55,10 @@ FString FWebApi::BuildQueryString(const TMap& QueryParams) QueryString.Append(TEXT("?")); for (const auto& Param : QueryParams) { - QueryString.Append(FString::Printf(TEXT("%s=%s&"), *FGenericPlatformHttp::UrlEncode(Param.Key), *FGenericPlatformHttp::UrlEncode(Param.Value))); + QueryString.Append(FString::Printf(TEXT("%s=%s&"), *Param.Key, *Param.Value)); } QueryString.RemoveFromEnd(TEXT("&")); } + QueryString = QueryString.Replace( TEXT(" "), TEXT("%20") ); return QueryString; } diff --git a/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp b/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp index 5877a38..95a6b8c 100644 --- a/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp +++ b/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp @@ -1,75 +1,82 @@ #include "Api/Common/WebApiWithAuth.h" +#include "RpmNextGen.h" #include "Interfaces/IHttpResponse.h" -FWebApiWithAuth::FWebApiWithAuth() : ApiRequestData(nullptr), AuthenticationStrategy(nullptr) +FWebApiWithAuth::FWebApiWithAuth() : AuthenticationStrategy(nullptr) { FWebApi(); + SetAuthenticationStrategy(nullptr); } -FWebApiWithAuth::FWebApiWithAuth(IAuthenticationStrategy* InAuthenticationStrategy) : AuthenticationStrategy(nullptr) +FWebApiWithAuth::FWebApiWithAuth(const TSharedPtr& InAuthenticationStrategy) : AuthenticationStrategy(nullptr) { SetAuthenticationStrategy(InAuthenticationStrategy); } -void FWebApiWithAuth::SetAuthenticationStrategy(IAuthenticationStrategy* InAuthenticationStrategy) +void FWebApiWithAuth::SetAuthenticationStrategy(const TSharedPtr& InAuthenticationStrategy) { AuthenticationStrategy = InAuthenticationStrategy; - - if (AuthenticationStrategy != nullptr) + if (AuthenticationStrategy.IsValid()) { + AuthenticationStrategy->OnAuthComplete.Unbind(); + AuthenticationStrategy->OnTokenRefreshed.Unbind(); + AuthenticationStrategy->OnAuthComplete.BindRaw(this, &FWebApiWithAuth::OnAuthComplete); AuthenticationStrategy->OnTokenRefreshed.BindRaw(this, &FWebApiWithAuth::OnAuthTokenRefreshed); } } -void FWebApiWithAuth::OnAuthComplete(bool bWasSuccessful) +void FWebApiWithAuth::OnAuthComplete(TSharedPtr ApiRequest, bool bWasSuccessful) { - if(bWasSuccessful && ApiRequestData != nullptr) + if(bWasSuccessful && ApiRequest.IsValid()) { - DispatchRaw(*ApiRequestData); + DispatchRaw(ApiRequest); return; } - OnApiResponse.ExecuteIfBound(TEXT("Auth failed"), false); + OnRequestComplete.ExecuteIfBound(ApiRequest, FHttpResponsePtr() , false); } -void FWebApiWithAuth::OnAuthTokenRefreshed(const FRefreshTokenResponseBody& Response, bool bWasSuccessful) +void FWebApiWithAuth::OnAuthTokenRefreshed(TSharedPtr ApiRequest, const FRefreshTokenResponseBody& Response, bool bWasSuccessful) { - if(bWasSuccessful) + if(bWasSuccessful && ApiRequest.IsValid()) { const FString Key = TEXT("Authorization"); - if (ApiRequestData->Headers.Contains(Key)) + if (ApiRequest->Headers.Contains(Key)) { - ApiRequestData->Headers.Remove(Key); + ApiRequest->Headers.Remove(Key); } - ApiRequestData->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Response.Token)); - DispatchRaw(*ApiRequestData); + ApiRequest->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Response.Token)); + DispatchRaw(ApiRequest); return; } - OnApiResponse.ExecuteIfBound(TEXT("Auth failed"), false); + OnRequestComplete.ExecuteIfBound(ApiRequest, FHttpResponsePtr() , false); } -void FWebApiWithAuth::DispatchRawWithAuth(FApiRequest& Data) +void FWebApiWithAuth::DispatchRawWithAuth(TSharedPtr ApiRequest) { - this->ApiRequestData = MakeShared(Data); if (AuthenticationStrategy == nullptr) { - DispatchRaw(Data); + DispatchRaw(ApiRequest); return; } - AuthenticationStrategy->AddAuthToRequest(this->ApiRequestData); + AuthenticationStrategy->AddAuthToRequest(ApiRequest); } -void FWebApiWithAuth::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +void FWebApiWithAuth::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TSharedPtr ApiRequest) { if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode())) { - OnApiResponse.ExecuteIfBound(Response->GetContentAsString(), true); - return; + OnRequestComplete.ExecuteIfBound(ApiRequest, Response, true); + } + else if(Response.IsValid() && Response->GetResponseCode() == EHttpResponseCodes::Denied && AuthenticationStrategy != nullptr) + { + AuthenticationStrategy->TryRefresh(ApiRequest); } - if(EHttpResponseCodes::Denied == Response->GetResponseCode() && AuthenticationStrategy != nullptr) + else { - AuthenticationStrategy->TryRefresh(ApiRequestData); + UE_LOG(LogReadyPlayerMe, Warning, TEXT("WebApi from URL %s request failed"), *Request->GetURL()); + OnRequestComplete.ExecuteIfBound(ApiRequest, Response, false); } } diff --git a/Source/RpmNextGen/Private/Api/Files/FileApi.cpp b/Source/RpmNextGen/Private/Api/Files/FileApi.cpp new file mode 100644 index 0000000..edd48a3 --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Files/FileApi.cpp @@ -0,0 +1,75 @@ +#include "Api/Files/FileApi.h" + +#include "HttpModule.h" +#include "RpmNextGen.h" +#include "Api/Assets/Models/Asset.h" +#include "Interfaces/IHttpResponse.h" + +FFileApi::FFileApi() +{ +} + +FFileApi::~FFileApi() +{ +} + +void FFileApi::LoadFileFromUrl(const FString& URL) +{ + TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); + HttpRequest->OnProcessRequestComplete().BindRaw(this, &FFileApi::FileRequestComplete); + HttpRequest->SetURL(URL); + HttpRequest->SetVerb("GET"); + HttpRequest->ProcessRequest(); +} + +void FFileApi::LoadAssetFileFromUrl(const FString& URL, FAsset Asset) +{ + TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); + HttpRequest->OnProcessRequestComplete().BindRaw(this, &FFileApi::AssetFileRequestComplete, Asset); + HttpRequest->SetURL(URL); + HttpRequest->SetVerb("GET"); + HttpRequest->ProcessRequest(); +} + +void FFileApi::FileRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +{ + FString URL = Request->GetURL(); + FString FileName; + if(URL.Contains(".glb") || URL.Contains(".png")) + { + FileName = FPaths::GetCleanFilename(Request->GetURL()); + } + + if (bWasSuccessful && Response.IsValid() && Response->GetContentLength() > 0) + { + TArray Content = Response->GetContent(); + OnFileRequestComplete.ExecuteIfBound(&Content, FileName); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load file from URL")); + OnFileRequestComplete.ExecuteIfBound(nullptr, FileName); +} + +void FFileApi::AssetFileRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FAsset Asset) +{ + + if (bWasSuccessful && Response.IsValid() && Response->GetContentLength() > 0) + { + TArray Content = Response->GetContent(); + OnAssetFileRequestComplete.ExecuteIfBound(&Content, Asset); + return; + } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Failed to load file from URL. Try loading from cache")); + OnAssetFileRequestComplete.ExecuteIfBound(nullptr, Asset); +} + +bool FFileApi::LoadFileFromPath(const FString& Path, TArray& OutContent) +{ + if (!FPaths::FileExists(Path)) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Path does not exist %s"), *Path); + return false; + } + + return FFileHelper::LoadFileToArray(OutContent, *Path); +} diff --git a/Source/RpmNextGen/Private/Api/Files/FileUtility.cpp b/Source/RpmNextGen/Private/Api/Files/FileUtility.cpp new file mode 100644 index 0000000..d180e5a --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Files/FileUtility.cpp @@ -0,0 +1,3 @@ +#include "Api/Files/FileUtility.h" + +const FString FFileUtility::RelativeCachePath = TEXT("ReadyPlayerMe/AssetCache"); diff --git a/Source/RpmNextGen/Private/Api/Files/GlbLoader.cpp b/Source/RpmNextGen/Private/Api/Files/GlbLoader.cpp new file mode 100644 index 0000000..aaf4d3f --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Files/GlbLoader.cpp @@ -0,0 +1,41 @@ +#include "Api/Files/GlbLoader.h" +#include "RpmNextGen.h" +#include "glTFRuntime/Public/glTFRuntimeFunctionLibrary.h" +#include "Api/Files//FileUtility.h" + +FGlbLoader::FGlbLoader() : GltfConfig(new FglTFRuntimeConfig()), FileWriter(new FFileUtility()) +{ + GltfConfig->TransformBaseType = EglTFRuntimeTransformBaseType::YForward; + OnFileRequestComplete.BindRaw( this, &FGlbLoader::HandleFileRequestComplete); +} + +FGlbLoader::FGlbLoader(FglTFRuntimeConfig* Config) : FGlbLoader() +{ + if (Config) + { + delete GltfConfig; + GltfConfig = Config; + } +} + +FGlbLoader::~FGlbLoader() +{ + delete GltfConfig; + delete FileWriter; +} + +void FGlbLoader::HandleFileRequestComplete(const TArray* Data, const FString& FileName) +{ + UglTFRuntimeAsset* GltfAsset = nullptr; + if (Data) + { + if(OnGLtfAssetLoaded.IsBound()) + { + GltfAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(*Data, *GltfConfig); + OnGLtfAssetLoaded.ExecuteIfBound(GltfAsset, TEXT("AssetType")); + } + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load GLB from URL")); + OnGLtfAssetLoaded.ExecuteIfBound(GltfAsset, TEXT("")); +} diff --git a/Source/RpmNextGen/Private/Api/Files/PakFileUtility.cpp b/Source/RpmNextGen/Private/Api/Files/PakFileUtility.cpp new file mode 100644 index 0000000..ae4fff4 --- /dev/null +++ b/Source/RpmNextGen/Private/Api/Files/PakFileUtility.cpp @@ -0,0 +1,229 @@ +#include "Api/Files/PakFileUtility.h" + +#include "Api/Files/FileUtility.h" +#include "IPlatformFilePak.h" + +const FString ResponseFilePath = FPaths::ProjectContentDir() / TEXT("ReadyPlayerMe/Cache/RpmCache_ResponseFile.txt"); + +#if WITH_EDITOR +const FString FPakFileUtility::CachePakFilePath = FPaths::ProjectContentDir() / TEXT("ReadyPlayerMe/Cache/RpmAssetCache.pak"); +#else +const FString FPakFileUtility::CachePakFilePath = FPaths::ProjectDir() / TEXT("Content/ReadyPlayerMe/Cache/RpmAssetCache.pak"); +#endif + +void FPakFileUtility::CreatePakFile(const FString& PakFilePath) +{ + const FString UnrealPakPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/Win64/UnrealPak.exe")); + const FString CommandLineArgs = FString::Printf(TEXT("%s -Create=%s -IoStore"), *PakFilePath, *ResponseFilePath); // Add the -IoStore flag + FProcHandle ProcHandle = FPlatformProcess::CreateProc(*UnrealPakPath, *CommandLineArgs, true, false, false, nullptr, 0, nullptr, nullptr); + + if (ProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(ProcHandle); + FPlatformProcess::CloseProc(ProcHandle); + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Pak file and I/O Store files created successfully: %s"), *PakFilePath); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to create Pak and I/O Store files: %s"), *PakFilePath); + } +} + +void FPakFileUtility::GeneratePakResponseFile() +{ + TArray Files; + const FString FolderToPak = FFileUtility::GetCachePath(); + IFileManager::Get().FindFilesRecursive(Files, *FolderToPak, TEXT("*.*"), true, false); + + FString ResponseFileContent; + int FileCount = 0; + for (const FString& File : Files) + { + FString RelativePath = File; + FPaths::MakePathRelativeTo(RelativePath, *FolderToPak); // Make relative to the root of the folder being packed + + + // Ensure the relative path preserves the subfolder structure + ResponseFileContent += FString::Printf(TEXT("\"%s\" \"%s\"\n"), *File, *RelativePath); + FileCount++; + } + + FFileHelper::SaveStringToFile(ResponseFileContent, *ResponseFilePath); +} + +void FPakFileUtility::CreatePakFile() +{ + GeneratePakResponseFile(); + CreatePakFile(CachePakFilePath); + IFileManager& FileManager = IFileManager::Get(); + const FString FolderToPak = FFileUtility::GetCachePath(); + // Ensure the folder exists before attempting to delete it + if (FileManager.DirectoryExists(*FolderToPak)) + { + // Attempt to delete the directory and all its contents + bool bDeleted = FileManager.DeleteDirectory(*FolderToPak, false, true); + + if (bDeleted) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully deleted folder: %s"), *FolderToPak); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to delete folder: %s"), *FolderToPak); + } + } + else + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Folder does not exist: %s"), *FolderToPak); + } +} + +void FPakFileUtility::ExtractPakFile(const FString& PakFilePath) +{ + if(!FPaths::FileExists(PakFilePath) ) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Pak file does not exist: %s"), *PakFilePath); + return; + } + const FString UnrealPakPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/Win64/UnrealPak.exe")); + const FString DestinationPath = FFileUtility::GetFullPersistentPath(FFileUtility::RelativeCachePath); + const FString CommandLineArgs = FString::Printf(TEXT("%s -Extract %s"), *PakFilePath, *DestinationPath); + + FProcHandle ProcHandle = FPlatformProcess::CreateProc(*UnrealPakPath, *CommandLineArgs, true, false, false, nullptr, 0, nullptr, nullptr); + + if (ProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(ProcHandle); + FPlatformProcess::CloseProc(ProcHandle); + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Pak file extracted successfully to: %s"), *DestinationPath); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to extract Pak file: %s"), *PakFilePath); + } +} + +void FPakFileUtility::ExtractFilesFromPak(const FString& PakFilePath) +{ + if(!FPaths::FileExists(PakFilePath) ) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Pak file does not exist: %s"), *PakFilePath); + return; + } + + // Step 1: Get the current platform file and initialize the Pak platform + IPlatformFile& InnerPlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + FPakPlatformFile* PakPlatformFile = static_cast(FPlatformFileManager::Get().FindPlatformFile(TEXT("PakFile"))); + + // If the PakPlatformFile is null, initialize it + if (!PakPlatformFile) + { + PakPlatformFile = new FPakPlatformFile(); + FPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile); + if (!PakPlatformFile->Initialize(&InnerPlatformFile, TEXT(""))) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to initialize Pak Platform File")); + delete PakPlatformFile; + return; + } + UE_LOG(LogReadyPlayerMe, Log, TEXT("Initializing new Pak Platform File")); + } + + // Step 2: Define mount point and destination paths + const FString MountPoint = TEXT("/AssetCache/"); // Virtual mount point inside Unreal + const FString DestinationPath = FFileUtility::GetFullPersistentPath(FFileUtility::RelativeCachePath); + + // Step 3: Mount the Pak file + if (!PakPlatformFile->Mount(*PakFilePath, 0, *MountPoint)) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to mount Pak file: %s"), *PakFilePath); + //return; + } + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully mounted Pak file: %s"), *PakFilePath); + + // Step 4: List files in the mounted Pak file + TArray Files; + PakPlatformFile->FindFilesRecursively(Files, *MountPoint, TEXT("")); + + if (Files.Num() == 0) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("No files found in Pak file: %s"), *PakFilePath); + return; + } + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Found %d files in Pak file: %s"), Files.Num(), *PakFilePath); + + // Step 5: Extract each file from the Pak + for (const FString& File : Files) + { + // Ensure the file is relative to the mount point + FString RelativeFilePath = File; + FPaths::MakePathRelativeTo(RelativeFilePath, *MountPoint); + FString DestinationFilePath = DestinationPath / RelativeFilePath; + + // Step 6: Handle JSON files separately + if (File.EndsWith(TEXT(".json"))) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Processing JSON file: %s"), *File); + FString JsonContent; + if (FFileHelper::LoadFileToString(JsonContent, *File)) + { + // Ensure destination directory exists + IFileManager::Get().MakeDirectory(*FPaths::GetPath(DestinationFilePath), true); + + // Try to deserialize JSON + TSharedPtr JsonObject; + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonContent); + + if (FJsonSerializer::Deserialize(Reader, JsonObject)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully parsed JSON content")); + } + else + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Invalid JSON content in file: %s"), *File); + } + + // Save JSON file to disk + if (FFileHelper::SaveStringToFile(JsonContent, *DestinationFilePath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully saved JSON file: %s"), *DestinationFilePath); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to save JSON file: %s"), *DestinationFilePath); + } + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to read JSON file from Pak: %s"), *File); + } + } + else + { + // Handle binary files + TArray FileData; + if (FFileHelper::LoadFileToArray(FileData, *File)) + { + if (FFileHelper::SaveArrayToFile(FileData, *DestinationFilePath)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully extracted file: %s"), *DestinationFilePath); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to save file: %s"), *DestinationFilePath); + } + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to read file from Pak: %s"), *File); + } + } + } + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Finished extracting files from Pak: %s"), *PakFilePath); +} + diff --git a/Source/RpmNextGen/Private/RpmActor.cpp b/Source/RpmNextGen/Private/RpmActor.cpp index 5a0aef0..4aee3eb 100644 --- a/Source/RpmNextGen/Private/RpmActor.cpp +++ b/Source/RpmNextGen/Private/RpmActor.cpp @@ -4,275 +4,234 @@ #include "RpmActor.h" #include "Components/InstancedStaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" -#include "Animation/AnimSequence.h" #include "glTFRuntimeSkeletalMeshComponent.h" +#include "RpmNextGen.h" +#include "RpmCharacterTypes.h" +#include "Api/Assets/AssetApi.h" -// Sets default values ARpmActor::ARpmActor() { - // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. - PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bCanEverTick = false; AssetRoot = CreateDefaultSubobject(TEXT("AssetRoot")); RootComponent = AssetRoot; - RootNodeIndex = INDEX_NONE; - bStaticMeshesAsSkeletalOnMorphTargets = true; + MasterPoseComponent = nullptr; } -// Called when the game starts or when spawned void ARpmActor::BeginPlay() { Super::BeginPlay(); - - if (!Asset) - { - return; - } - - SetupAsset(); -} - -void ARpmActor::LoadGltfAsset(UglTFRuntimeAsset* GltfAsset) -{ - // Before loading a new asset, clear existing components - ClearLoadedComponents(); - - Asset = GltfAsset; - SetupAsset(); } -void ARpmActor::ClearLoadedComponents() +void ARpmActor::LoadCharacter(const FRpmCharacterData& InCharacterData, UglTFRuntimeAsset* GltfAsset) { - if (RootComponent) + CharacterData = InCharacterData; + if(AnimationConfigsByBaseModelId.Contains(CharacterData.BaseModelId)) { - TArray ChildComponents; - RootComponent->GetChildrenComponents(true, ChildComponents); - - for (USceneComponent* ChildComponent : ChildComponents) - { - if (ChildComponent && ChildComponent != RootComponent) - { - ChildComponent->DestroyComponent(); - } - } + AnimationConfig = AnimationConfigsByBaseModelId[CharacterData.BaseModelId]; + SkeletalMeshConfig.Skeleton = AnimationConfig.Skeleton; + SkeletalMeshConfig.SkeletonConfig.CopyRotationsFrom = AnimationConfig.Skeleton; } + LoadAsset(FAsset(), GltfAsset); } -void ARpmActor::SetupAsset() +void ARpmActor::LoadAsset(const FAsset& Asset, UglTFRuntimeAsset* GltfAsset) { - if (!Asset) + if (!GltfAsset) { UE_LOG(LogGLTFRuntime, Warning, TEXT("No asset to setup")); return; } - - double LoadingStartTime = FPlatformTime::Seconds(); - - if (RootNodeIndex > INDEX_NONE) + if(Asset.Type == FAssetApi::BaseModelType) { - FglTFRuntimeNode Node; - if (!Asset->GetNode(RootNodeIndex, Node)) + CharacterData.BaseModelId = Asset.Id; + if(AnimationConfigsByBaseModelId.Contains(CharacterData.BaseModelId)) { - return; + AnimationConfig = AnimationConfigsByBaseModelId[CharacterData.BaseModelId]; + SkeletalMeshConfig.Skeleton = AnimationConfig.Skeleton; + SkeletalMeshConfig.SkeletonConfig.CopyRotationsFrom = AnimationConfig.Skeleton; } - AssetRoot = nullptr; - ProcessNode(nullptr, NAME_None, Node); } - else + RemoveMeshComponentsOfType(Asset.Type); + double LoadingStartTime = FPlatformTime::Seconds(); + + const TArray NewMeshComponents = LoadMeshComponents(GltfAsset, Asset.Type); + if (NewMeshComponents.Num() > 0) { - TArray Scenes = Asset->GetScenes(); - for (FglTFRuntimeScene& Scene : Scenes) + LoadedMeshComponentsByAssetType.Add(Asset.Type, NewMeshComponents); + if(AnimationConfigsByBaseModelId.Contains(CharacterData.BaseModelId)) { - USceneComponent* SceneComponent = NewObject(this, *FString::Printf(TEXT("Scene %d"), Scene.Index)); - SceneComponent->SetupAttachment(RootComponent); - SceneComponent->RegisterComponent(); - AddInstanceComponent(SceneComponent); - for (int32 NodeIndex : Scene.RootNodesIndices) + // Check if MasterPoseComponent is valid before using it + if (MasterPoseComponent == nullptr) { - FglTFRuntimeNode Node; - if (!Asset->GetNode(NodeIndex, Node)) - { - return; - } - ProcessNode(SceneComponent, NAME_None, Node); + UE_LOG(LogReadyPlayerMe, Error, TEXT("MasterPoseComponent is null for base model %s"), *CharacterData.BaseModelId); + return; } - } - } - for (TPair& Pair : SocketMapping) - { - for (USkeletalMeshComponent* SkeletalMeshComponent : DiscoveredSkeletalMeshComponents) - { - if (SkeletalMeshComponent->DoesSocketExist(Pair.Value)) + // Check if Animation Blueprint is valid + if (!AnimationConfig.AnimationBlueprint) { - Pair.Key->AttachToComponent(SkeletalMeshComponent, FAttachmentTransformRules::KeepRelativeTransform, Pair.Value); - Pair.Key->SetRelativeTransform(FTransform::Identity); - break; + UE_LOG(LogReadyPlayerMe, Error, TEXT("AnimationBlueprint is null for base model %s"), *CharacterData.BaseModelId); + return; } + + MasterPoseComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint); + MasterPoseComponent->SetAnimClass(AnimationConfig.AnimationBlueprint); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Set Animation Blueprint for %s"), *CharacterData.BaseModelId); } - } - UE_LOG(LogGLTFRuntime, Log, TEXT("Asset loaded in %f seconds"), FPlatformTime::Seconds() - LoadingStartTime); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Asset loaded in %f seconds"), FPlatformTime::Seconds() - LoadingStartTime); + return; + } + + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load mesh components")); } +void ARpmActor::LoadGltfAssetWithSkeleton(UglTFRuntimeAsset* GltfAsset, const FAsset& Asset, const FRpmAnimationConfig& InAnimationCharacter) +{ + AnimationConfig = InAnimationCharacter; + SkeletalMeshConfig.Skeleton = AnimationConfig.Skeleton; + SkeletalMeshConfig.SkeletonConfig.CopyRotationsFrom = AnimationConfig.Skeleton; + LoadAsset(Asset, GltfAsset); +} -void ARpmActor::ProcessNode(USceneComponent* NodeParentComponent, const FName SocketName, FglTFRuntimeNode& Node) +void ARpmActor::RemoveMeshComponentsOfType(const FString& AssetType) { - if (Asset->NodeIsBone(Node.Index)) + if (LoadedMeshComponentsByAssetType.IsEmpty()) { - ProcessBoneNode(NodeParentComponent, Node); + UE_LOG( LogReadyPlayerMe, Log, TEXT("No mesh components to remove")); return; } - USceneComponent* NewComponent = CreateNewComponent(NodeParentComponent, Node); - - if (!NewComponent) + // Remove components by type, or remove all if AssetType is empty or it's a new base model + if (AssetType.IsEmpty() || AssetType == FAssetApi::BaseModelType) { - return; + RemoveAllMeshes(); } - - SetupComponentTags(NewComponent, Node, SocketName); - ProcessChildNodes(NewComponent, Node); -} - -void ARpmActor::ProcessBoneNode(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node) -{ - for (int32 ChildIndex : Node.ChildrenIndices) + else if (LoadedMeshComponentsByAssetType.Contains(AssetType)) { - FglTFRuntimeNode Child; - if (!Asset->GetNode(ChildIndex, Child)) + TArray& ComponentsToRemove = LoadedMeshComponentsByAssetType[AssetType]; + for (USceneComponent* ComponentToRemove : ComponentsToRemove) { - return; + if (ComponentToRemove) + { + ComponentToRemove->DestroyComponent(); + } } - ProcessNode(NodeParentComponent, *Child.Name, Child); + LoadedMeshComponentsByAssetType.Remove(AssetType); } } -USceneComponent* ARpmActor::CreateNewComponent(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node) -{ - USceneComponent* NewComponent = nullptr; - - // Check if the node should be a skeletal mesh component - if (Node.SkinIndex >= 0 || (bStaticMeshesAsSkeletalOnMorphTargets && Asset->MeshHasMorphTargets(Node.MeshIndex))) - { - // Create a skeletal mesh component - USkeletalMeshComponent* SkeletalMeshComponent = nullptr; - - if (SkeletalMeshConfig.bPerPolyCollision) - { - SkeletalMeshComponent = NewObject(this, GetSafeNodeName(Node)); - SkeletalMeshComponent->bEnablePerPolyCollision = true; - SkeletalMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); - } - else - { - SkeletalMeshComponent = NewObject(this, GetSafeNodeName(Node)); - } - - // Load and set the skeletal mesh - USkeletalMesh* SkeletalMesh = Asset->LoadSkeletalMesh(Node.MeshIndex, Node.SkinIndex, SkeletalMeshConfig); - SkeletalMeshComponent->SetSkeletalMesh(SkeletalMesh); - - // Attach and register the component - SkeletalMeshComponent->SetupAttachment(NodeParentComponent ? NodeParentComponent : RootComponent.Get()); - SkeletalMeshComponent->RegisterComponent(); - SkeletalMeshComponent->SetRelativeTransform(Node.Transform); - - // Add the component to the list of discovered skeletal mesh components - DiscoveredSkeletalMeshComponents.Add(SkeletalMeshComponent); - - NewComponent = SkeletalMeshComponent; - - // Custom event handling for when a skeletal mesh component is created - ReceiveOnSkeletalMeshComponentCreated(SkeletalMeshComponent, Node); - } - else - { - // Create a static mesh component - UStaticMeshComponent* StaticMeshComponent = nullptr; - TArray GPUInstancingTransforms; - if (Asset->GetNodeGPUInstancingTransforms(Node.Index, GPUInstancingTransforms)) - { - UInstancedStaticMeshComponent* InstancedStaticMeshComponent = NewObject(this, GetSafeNodeName(Node)); - for (const FTransform& GPUInstanceTransform : GPUInstancingTransforms) - { - InstancedStaticMeshComponent->AddInstance(GPUInstanceTransform); - } - StaticMeshComponent = InstancedStaticMeshComponent; - } - else - { - StaticMeshComponent = NewObject(this, GetSafeNodeName(Node)); - } - - // Load and set the static mesh - UStaticMesh* StaticMesh = Asset->LoadStaticMeshLODs({Node.MeshIndex}, StaticMeshConfig); - StaticMeshComponent->SetStaticMesh(StaticMesh); - - // Attach and register the component - StaticMeshComponent->SetupAttachment(NodeParentComponent ? NodeParentComponent : RootComponent.Get()); - StaticMeshComponent->RegisterComponent(); - StaticMeshComponent->SetRelativeTransform(Node.Transform); - - NewComponent = StaticMeshComponent; - - // Custom event handling for when a static mesh component is created - ReceiveOnStaticMeshComponentCreated(StaticMeshComponent, Node); - } - - // Add the component to the actor's list of instance components - AddInstanceComponent(NewComponent); - - return NewComponent; -} - - -void ARpmActor::SetupComponentTags(USceneComponent* Component, FglTFRuntimeNode& Node, const FName SocketName) +void ARpmActor::RemoveAllMeshes() { - Component->ComponentTags.Add(*FString::Printf(TEXT("glTFRuntime:NodeName:%s"), *Node.Name)); - Component->ComponentTags.Add(*FString::Printf(TEXT("glTFRuntime:NodeIndex:%d"), Node.Index)); - - if (SocketName != NAME_None) - { - SocketMapping.Add(Component, SocketName); + for (const auto Pairs : LoadedMeshComponentsByAssetType){ + + TArray ComponentsToRemove = Pairs.Value; + for (USceneComponent* ComponentToRemove : ComponentsToRemove) + { + if (ComponentToRemove) + { + ComponentToRemove->DestroyComponent(); + } + } } + LoadedMeshComponentsByAssetType.Empty(); } -void ARpmActor::ProcessChildNodes(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node) +TArray ARpmActor::LoadMeshComponents(UglTFRuntimeAsset* GltfAsset, const FString& AssetType) { - for (int32 ChildIndex : Node.ChildrenIndices) + TArray AllNodes = GltfAsset->GetNodes(); + TArray NewMeshComponents; + //if baseModel or full character asset changes we need to update master pose component + bool bIsMasterPoseUpdateRequired = AssetType == FAssetApi::BaseModelType || AssetType.IsEmpty(); + + // Loop through all nodes to create mesh components + for (const FglTFRuntimeNode& Node : AllNodes) { - FglTFRuntimeNode Child; - if (!Asset->GetNode(ChildIndex, Child)) + // Skip bones and armature + if(GltfAsset->NodeIsBone(Node.Index) || Node.Name.Contains("Armature")) { - return; + continue; + } + + if (Node.SkinIndex >= 0) + { + USkeletalMeshComponent* SkeletalMeshComponent = CreateSkeletalMeshComponent(GltfAsset, Node); + if(bIsMasterPoseUpdateRequired) + { + UE_LOG( LogReadyPlayerMe, Log, TEXT("Setting master pose component")); + MasterPoseComponent = SkeletalMeshComponent; + NewMeshComponents.Add(SkeletalMeshComponent); + bIsMasterPoseUpdateRequired = false; + continue; + } + SkeletalMeshComponent->SetMasterPoseComponent(MasterPoseComponent.Get()); + NewMeshComponents.Add(SkeletalMeshComponent); + } + else + { + NewMeshComponents.Add(CreateStaticMeshComponent(GltfAsset, Node)); + } - ProcessNode(NodeParentComponent, NAME_None, Child); } + return NewMeshComponents; } -// Called every frame -void ARpmActor::Tick(float DeltaTime) +USkeletalMeshComponent* ARpmActor::CreateSkeletalMeshComponent(UglTFRuntimeAsset* GltfAsset, const FglTFRuntimeNode& Node) { - Super::Tick(DeltaTime); -} + USkeletalMeshComponent* SkeletalMeshComponent = nullptr; -void ARpmActor::ReceiveOnStaticMeshComponentCreated_Implementation(UStaticMeshComponent* StaticMeshComponent, const FglTFRuntimeNode& Node) -{ + if (SkeletalMeshConfig.bPerPolyCollision) + { + SkeletalMeshComponent = NewObject(this, GetSafeNodeName(Node)); + SkeletalMeshComponent->bEnablePerPolyCollision = true; + SkeletalMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + } + else + { + SkeletalMeshComponent = NewObject(this, GetSafeNodeName(Node)); + } + USkeletalMesh* SkeletalMesh = GltfAsset->LoadSkeletalMesh(Node.MeshIndex, Node.SkinIndex, SkeletalMeshConfig); + SkeletalMeshComponent->SetSkeletalMesh(SkeletalMesh); + SkeletalMeshComponent->SetupAttachment(AssetRoot); + SkeletalMeshComponent->SetRelativeTransform(Node.Transform); + SkeletalMeshComponent->RegisterComponent(); + AddInstanceComponent(SkeletalMeshComponent); + + return SkeletalMeshComponent; } -void ARpmActor::ReceiveOnSkeletalMeshComponentCreated_Implementation(USkeletalMeshComponent* SkeletalMeshComponent, const FglTFRuntimeNode& Node) +UStaticMeshComponent* ARpmActor::CreateStaticMeshComponent(UglTFRuntimeAsset* GltfAsset, const FglTFRuntimeNode& Node) { + UStaticMeshComponent* StaticMeshComponent = nullptr; + TArray GPUInstancingTransforms; + + if (GltfAsset->GetNodeGPUInstancingTransforms(Node.Index, GPUInstancingTransforms)) + { + UInstancedStaticMeshComponent* InstancedStaticMeshComponent = NewObject(this, GetSafeNodeName(Node)); + for (const FTransform& GPUInstanceTransform : GPUInstancingTransforms) + { + InstancedStaticMeshComponent->AddInstance(GPUInstanceTransform); + } + StaticMeshComponent = InstancedStaticMeshComponent; + } + else + { + StaticMeshComponent = NewObject(this, GetSafeNodeName(Node)); + } + UStaticMesh* StaticMesh = GltfAsset->LoadStaticMeshLODs({Node.MeshIndex}, StaticMeshConfig); + StaticMeshComponent->SetStaticMesh(StaticMesh); + StaticMeshComponent->SetupAttachment(AssetRoot); + StaticMeshComponent->SetRelativeTransform(Node.Transform); + StaticMeshComponent->RegisterComponent(); + AddInstanceComponent(StaticMeshComponent); + + return StaticMeshComponent; } -void ARpmActor::PostUnregisterAllComponents() +void ARpmActor::Tick(float DeltaTime) { - if (Asset) - { - Asset->ClearCache(); - Asset = nullptr; - } - Super::PostUnregisterAllComponents(); + Super::Tick(DeltaTime); } diff --git a/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp b/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp deleted file mode 100644 index 3bc7f19..0000000 --- a/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "RpmAssetLoaderComponent.h" -#include "RpmNextGen.h" -#include "Api/Assets/AssetLoader.h" - -class URpmDeveloperSettings; - -// Sets default values for this component's properties -URpmAssetLoaderComponent::URpmAssetLoaderComponent() -{ - // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features - // off to improve performance if you don't need them. - PrimaryComponentTick.bCanEverTick = false; - AssetLoader = MakeShared(); - AssetLoader->OnGLtfAssetLoaded.BindUObject( - this, - &URpmAssetLoaderComponent::HandleGLtfAssetLoaded - ); -} - -// Called when the game starts -void URpmAssetLoaderComponent::BeginPlay() -{ - Super::BeginPlay(); -} - -void URpmAssetLoaderComponent::LoadCharacterFromUrl(const FString Url) -{ - AssetLoader->LoadGLBFromURL(Url); -} - -void URpmAssetLoaderComponent::HandleGLtfAssetLoaded(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful) -{ - if (!gltfAsset || !bWasSuccessful) - { - UE_LOG(LogReadyPlayerMe, Log, TEXT("Failed to load gltf asset")); - OnGltfAssetLoaded.Broadcast(nullptr); - return; - } - OnGltfAssetLoaded.Broadcast(gltfAsset); -} - -// Called every frame -void URpmAssetLoaderComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - - // ... -} - diff --git a/Source/RpmNextGen/Private/RpmFunctionLibrary.cpp b/Source/RpmNextGen/Private/RpmFunctionLibrary.cpp index 54e4c42..2bddb27 100644 --- a/Source/RpmNextGen/Private/RpmFunctionLibrary.cpp +++ b/Source/RpmNextGen/Private/RpmFunctionLibrary.cpp @@ -2,41 +2,73 @@ #include "RpmFunctionLibrary.h" +#include "RpmNextGen.h" #include "Api/Assets/AssetApi.h" #include "Api/Assets/Models/AssetListRequest.h" #include "Api/Assets/Models/AssetListResponse.h" -#include "Api/Auth/ApiKeyAuthStrategy.h" +#include "Api/Files/PakFileUtility.h" +#include "Cache/AssetCacheManager.h" +#include "Cache/CachedAssetData.h" #include "Settings/RpmDeveloperSettings.h" void URpmFunctionLibrary::FetchFirstAssetId(UObject* WorldContextObject, const FString& AssetType, FOnAssetIdFetched OnAssetIdFetched) { - TSharedPtr AssetApi = MakeShared(); - const URpmDeveloperSettings* RpmSettings = GetDefault(); - if(!RpmSettings->ApiKey.IsEmpty() || RpmSettings->ApiProxyUrl.IsEmpty()) - { - AssetApi->SetAuthenticationStrategy(new FApiKeyAuthStrategy()); - } - - FAssetListQueryParams QueryParams; - QueryParams.Type = AssetType; - QueryParams.ApplicationId = RpmSettings->ApplicationId; - FAssetListRequest AssetListRequest = FAssetListRequest(QueryParams); - - if (!WorldContextObject) - { - UE_LOG(LogTemp, Error, TEXT("WorldContextObject is null")); - return; - } - - AssetApi->OnListAssetsResponse.BindLambda([OnAssetIdFetched, AssetApi](const FAssetListResponse& Response, bool bWasSuccessful) - { - FString FirstAssetId; - if (bWasSuccessful && Response.Data.Num() > 0) - { - FirstAssetId = Response.Data[0].Id; - } - OnAssetIdFetched.ExecuteIfBound(FirstAssetId); - }); - - AssetApi->ListAssetsAsync(AssetListRequest); + if (!WorldContextObject) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("WorldContextObject is null")); + OnAssetIdFetched.ExecuteIfBound(FString()); + return; + } + + TSharedPtr AssetApi = MakeShared(); + TSharedPtr SharedDelegate = MakeShared(OnAssetIdFetched); + + const URpmDeveloperSettings* RpmSettings = GetDefault(); + FAssetListQueryParams QueryParams; + QueryParams.Type = AssetType; + QueryParams.ApplicationId = RpmSettings->ApplicationId; + QueryParams.Limit = 1; + FAssetListRequest AssetListRequest = FAssetListRequest(QueryParams); + + TWeakObjectPtr WeakContextObject(WorldContextObject); + + AssetApi->OnListAssetsResponse.BindLambda([WeakContextObject, SharedDelegate, AssetApi, AssetType](const FAssetListResponse& Response, bool bWasSuccessful) + { + if (!WeakContextObject.IsValid()) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("WorldContextObject is no longer valid.")); + SharedDelegate->ExecuteIfBound(FString()); + return; + } + + if (bWasSuccessful && Response.Data.Num() > 0) + { + FString FirstAssetId = Response.Data[0].Id; + UE_LOG(LogReadyPlayerMe, Warning, TEXT("FirstAssetId fetched: %s"), *FirstAssetId); + SharedDelegate->ExecuteIfBound(FirstAssetId); + return; + } + + // Fallback to cache if online request failed or returned no data + TArray Assets = FAssetCacheManager::Get().GetAssetsOfType(AssetType); + if (Assets.Num() > 0) + { + FString FirstAssetId = Assets[0].Id; + UE_LOG(LogReadyPlayerMe, Warning, TEXT("FirstAssetId fetched from cache: %s"), *FirstAssetId); + SharedDelegate->ExecuteIfBound(FirstAssetId); + return; + } + + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to fetch FirstAssetId")); + SharedDelegate->ExecuteIfBound(FString()); + }); + + AssetApi->ListAssetsAsync(AssetListRequest); +} + +void URpmFunctionLibrary::ExtractCachePakFile() +{ + const FString PakFilePath = FFileUtility::GetFullPersistentPath(FPakFileUtility::CachePakFilePath); + + FPakFileUtility::ExtractFilesFromPak(PakFilePath); } diff --git a/Source/RpmNextGen/Private/RpmImageLoader.cpp b/Source/RpmNextGen/Private/RpmImageLoader.cpp deleted file mode 100644 index 9a32b8c..0000000 --- a/Source/RpmNextGen/Private/RpmImageLoader.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "RpmImageLoader.h" -#include "HttpModule.h" -#include "Interfaces/IHttpRequest.h" -#include "Interfaces/IHttpResponse.h" -#include "IImageWrapper.h" -#include "IImageWrapperModule.h" -#include "Async/Async.h" -#include "Modules/ModuleManager.h" -#include "Engine/Texture2D.h" -#include "Components/Image.h" -#include "Widgets/Images/SImage.h" - -void FRpmImageLoader::LoadUImageFromURL(UImage* Image, const FString& URL) -{ - if (!Image) - { - UE_LOG(LogTemp, Error, TEXT("LoadImageFromURL: Image is null")); - return; - } - - TWeakObjectPtr WeakImagePtr(Image); - DownloadImage(URL, [WeakImagePtr](UTexture2D* Texture) - { - if (WeakImagePtr.IsValid() && Texture) - { - FSlateBrush Brush; - Brush.SetResourceObject(Texture); - Brush.ImageSize = WeakImagePtr->Brush.ImageSize; - WeakImagePtr->SetBrush(Brush); - } - }); -} - -void FRpmImageLoader::LoadSImageFromURL(TSharedPtr ImageWidget, const FString& URL, - TFunction OnImageUpdated) -{ - if (!ImageWidget.IsValid()) - { - UE_LOG(LogTemp, Error, TEXT("LoadImageFromURL: SImage widget is invalid")); - return; - } - - DownloadImage(URL, [ImageWidget, OnImageUpdated](UTexture2D* Texture) - { - if (ImageWidget.IsValid() && Texture) - { - FSlateBrush* Brush = new FSlateBrush(); - Brush->SetResourceObject(Texture); - Brush->ImageSize = FVector2D(100.0f, 100.0f); // Adjust size as needed - ImageWidget->SetImage(Brush); - OnImageUpdated(Texture); - } - }); -} - -void FRpmImageLoader::DownloadImage(const FString& URL, TFunction OnImageDownloaded) -{ - TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); - HttpRequest->OnProcessRequestComplete().BindRaw(this, &FRpmImageLoader::OnImageDownloadComplete, OnImageDownloaded); - HttpRequest->SetURL(URL); - HttpRequest->SetVerb("GET"); - HttpRequest->ProcessRequest(); -} - -void FRpmImageLoader::OnImageDownloadComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TFunction OnImageDownloaded) -{ - UTexture2D* Texture = nullptr; - if (bWasSuccessful && Response.IsValid() && Response->GetContentLength() > 0) - { - Texture = CreateTextureFromImageData(Response->GetContent()); - } - - if (!Texture) - { - UE_LOG(LogTemp, Error, TEXT("Failed to download or create texture from URL: %s"), *Request->GetURL()); - } - - AsyncTask(ENamedThreads::GameThread, [OnImageDownloaded, Texture]() - { - OnImageDownloaded(Texture); - }); -} - -UTexture2D* FRpmImageLoader::CreateTextureFromImageData(const TArray& ImageData) -{ - IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked( - FName("ImageWrapper")); - EImageFormat ImageFormat = ImageWrapperModule.DetectImageFormat(ImageData.GetData(), ImageData.Num()); - - if (ImageFormat == EImageFormat::Invalid) - { - return nullptr; - } - - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageFormat); - if (!ImageWrapper.IsValid() || !ImageWrapper->SetCompressed(ImageData.GetData(), ImageData.Num())) - { - return nullptr; - } - - TArray UncompressedBGRA; - if (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) - { - return nullptr; - } - - UTexture2D* Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8); - if (!Texture) - { - return nullptr; - } - - void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); - FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num()); - Texture->GetPlatformData()->Mips[0].BulkData.Unlock(); - Texture->UpdateResource(); - - return Texture; -} diff --git a/Source/RpmNextGen/Private/RpmLoaderComponent.cpp b/Source/RpmNextGen/Private/RpmLoaderComponent.cpp new file mode 100644 index 0000000..4d6ac76 --- /dev/null +++ b/Source/RpmNextGen/Private/RpmLoaderComponent.cpp @@ -0,0 +1,203 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RpmLoaderComponent.h" + +#include "glTFRuntimeFunctionLibrary.h" +#include "RpmFunctionLibrary.h" +#include "Api/Assets/AssetApi.h" +#include "Api/Assets/Models/Asset.h" +#include "Api/Characters/CharacterApi.h" +#include "Api/Characters/Models/CharacterCreateResponse.h" +#include "Api/Characters/Models/CharacterFindByIdResponse.h" +#include "Api/Characters/Models/CharacterUpdateResponse.h" +#include "Api/Files/GlbLoader.h" +#include "Cache/AssetCacheManager.h" +#include "GenericPlatform/GenericPlatformCrashContext.h" +#include "Settings/RpmDeveloperSettings.h" + +URpmLoaderComponent::URpmLoaderComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + const URpmDeveloperSettings* RpmSettings = GetDefault(); + AppId = RpmSettings->ApplicationId; + FileApi = MakeShared(); + FileApi->OnAssetFileRequestComplete.BindUObject(this, &URpmLoaderComponent::HandleAssetLoaded); + FileApi->OnFileRequestComplete.BindUObject(this, &URpmLoaderComponent::HandleCharacterAssetLoaded); + CharacterApi = MakeShared(); + CharacterApi->OnCharacterCreateResponse.BindUObject(this, &URpmLoaderComponent::HandleCharacterCreateResponse); + CharacterApi->OnCharacterUpdateResponse.BindUObject(this, &URpmLoaderComponent::HandleCharacterUpdateResponse); + CharacterApi->OnCharacterFindResponse.BindUObject(this, &URpmLoaderComponent::HandleCharacterFindResponse); + CharacterData = FRpmCharacterData(); + GltfConfig = FglTFRuntimeConfig(); + GltfConfig.TransformBaseType = EglTFRuntimeTransformBaseType::YForward; +} + +void URpmLoaderComponent::SetGltfConfig(const FglTFRuntimeConfig* Config) +{ + GltfConfig = *Config; +} + +void URpmLoaderComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +void URpmLoaderComponent::CreateCharacter(const FString& BaseModelId) +{ + CharacterData.BaseModelId = BaseModelId; + FAsset BaseModelAsset = FAsset(); + BaseModelAsset.Id = BaseModelId; + BaseModelAsset.Type = FAssetApi::BaseModelType; + CharacterData.Assets.Add( FAssetApi::BaseModelType, BaseModelAsset); + FCharacterCreateRequest CharacterCreateRequest = FCharacterCreateRequest(); + CharacterCreateRequest.Data.Assets = TMap(); + CharacterCreateRequest.Data.Assets.Add(FAssetApi::BaseModelType, BaseModelId); + CharacterCreateRequest.Data.ApplicationId = AppId; + CharacterApi->CreateAsync(CharacterCreateRequest); +} + +void URpmLoaderComponent::LoadCharacterFromUrl(FString Url) +{ + FileApi->LoadFileFromUrl(Url); +} + +void URpmLoaderComponent::LoadGltfRuntimeAssetFromCache(const FAsset& Asset) +{ + FCachedAssetData ExistingAsset; + if(FAssetCacheManager::Get().GetCachedAsset(Asset.Id, ExistingAsset)) + { + CharacterData.Assets.Add(ExistingAsset.Type, Asset); + TArray Data; + if(FFileApi::LoadFileFromPath(ExistingAsset.GetGlbPathForBaseModelId(CharacterData.BaseModelId), Data)) + { + UglTFRuntimeAsset* GltfRuntimeAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(Data, GltfConfig); + if(!GltfRuntimeAsset) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load gltf asset")); + } + OnNewAssetLoaded.Broadcast(Asset, GltfRuntimeAsset); + return; + } + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load gltf asset from cache")); + OnNewAssetLoaded.Broadcast(Asset, nullptr); +} + +void URpmLoaderComponent::LoadCharacterAssetsFromCache(TMap AssetMap) +{ + for (auto Element : AssetMap) + { + LoadGltfRuntimeAssetFromCache(Element.Value); + } +} + +void URpmLoaderComponent::LoadAssetsFromCacheWithNewStyle() +{ + for (auto PreviewAssets : CharacterData.Assets) + { + if(PreviewAssets.Value.Type == FAssetApi::BaseModelType) + { + continue; + } + LoadGltfRuntimeAssetFromCache(PreviewAssets.Value); + } +} + +void URpmLoaderComponent::LoadAssetPreview(FAsset AssetData, bool bUseCache) +{ + if (CharacterData.BaseModelId.IsEmpty()) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("BaseModelId is empty")); + return; + } + const bool bIsBaseModel = AssetData.Type == FAssetApi::BaseModelType; + if(bIsBaseModel) + { + CharacterData.BaseModelId = AssetData.Id; + } + CharacterData.Assets.Add(AssetData.Type, AssetData); + + if(CharacterData.Id.IsEmpty()) + { + LoadGltfRuntimeAssetFromCache(AssetData); + if(bIsBaseModel && CharacterData.Assets.Num() > 1) + { + LoadAssetsFromCacheWithNewStyle(); + } + return; + } + TMap ParamAssets; + for (auto Asset : CharacterData.Assets) + { + ParamAssets.Add(Asset.Key, Asset.Value.Id); + } + FCharacterPreviewRequest PreviewRequest; + PreviewRequest.Id = CharacterData.Id; + PreviewRequest.Params.Assets = ParamAssets; + const FString& Url = CharacterApi->GeneratePreviewUrl(PreviewRequest); + + FileApi->LoadAssetFileFromUrl(Url, AssetData); +} + +void URpmLoaderComponent::HandleAssetLoaded(const TArray* Data, const FAsset& Asset) +{ + if(!Data) + { + LoadGltfRuntimeAssetFromCache(Asset); + return; + } + UglTFRuntimeAsset* GltfRuntimeAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(*Data, GltfConfig); + if(!GltfRuntimeAsset) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load gltf asset")); + } + OnNewAssetLoaded.Broadcast(Asset, GltfRuntimeAsset); +} + +void URpmLoaderComponent::HandleCharacterAssetLoaded(const TArray* Data, const FString& FileName) +{ + if(!Data) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load character asset data")); + return; + } + UglTFRuntimeAsset* GltfRuntimeAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(*Data, GltfConfig); + if(!GltfRuntimeAsset) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load gltf asset")); + } + OnCharacterAssetLoaded.Broadcast(CharacterData, GltfRuntimeAsset); +} + +void URpmLoaderComponent::HandleCharacterCreateResponse(FCharacterCreateResponse CharacterCreateResponse, bool bWasSuccessful) +{ + if(bWasSuccessful && CharacterCreateResponse.IsValid()) + { + CharacterData.Id = CharacterCreateResponse.Data.Id; + OnCharacterCreated.Broadcast(CharacterData); + LoadCharacterFromUrl(CharacterCreateResponse.Data.GlbUrl); + return; + } + + + OnCharacterCreated.Broadcast(CharacterData); + UE_LOG( LogReadyPlayerMe, Error, TEXT("Failed to create character from web Api. Falling back to cache.")); + LoadCharacterAssetsFromCache(CharacterData.Assets); +} + +void URpmLoaderComponent::HandleCharacterUpdateResponse(FCharacterUpdateResponse CharacterUpdateResponse, bool bWasSuccessful) +{ + OnCharacterUpdated.Broadcast(CharacterData); +} + +void URpmLoaderComponent::HandleCharacterFindResponse(FCharacterFindByIdResponse CharacterFindByIdResponse, bool bWasSuccessful) +{ + OnCharacterFound.Broadcast(CharacterData); +} + +void URpmLoaderComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + diff --git a/Source/RpmNextGen/Private/RpmNextGen.cpp b/Source/RpmNextGen/Private/RpmNextGen.cpp index 818e442..f7a9290 100644 --- a/Source/RpmNextGen/Private/RpmNextGen.cpp +++ b/Source/RpmNextGen/Private/RpmNextGen.cpp @@ -8,7 +8,6 @@ DEFINE_LOG_CATEGORY(LogReadyPlayerMe); void FRpmNextGenModule::StartupModule() { - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module } void FRpmNextGenModule::ShutdownModule() diff --git a/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp b/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp deleted file mode 100644 index 7ebf553..0000000 --- a/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "RpmPreviewLoaderComponent.h" - -#include "RpmNextGen.h" -#include "Api/Assets/Models/Asset.h" -#include "Api/Characters/CharacterApi.h" -#include "Api/Characters/Models/CharacterCreateResponse.h" -#include "Api/Characters/Models/CharacterFindByIdResponse.h" -#include "Api/Characters/Models/CharacterUpdateResponse.h" -#include "Settings/RpmDeveloperSettings.h" - -class URpmDeveloperSettings; - -// Sets default values for this component's properties -URpmPreviewLoaderComponent::URpmPreviewLoaderComponent() -{ - PrimaryComponentTick.bCanEverTick = false; - const URpmDeveloperSettings* RpmSettings = GetDefault(); - AppId = RpmSettings->ApplicationId; - CharacterApi = MakeShared(); - PreviewAssetMap = TMap(); - CharacterApi->OnCharacterCreateResponse.BindUObject(this, &URpmPreviewLoaderComponent::HandleCharacterCreateResponse); - CharacterApi->OnCharacterUpdateResponse.BindUObject(this, &URpmPreviewLoaderComponent::HandleCharacterUpdateResponse); - CharacterApi->OnCharacterFindResponse.BindUObject(this, &URpmPreviewLoaderComponent::HandleCharacterFindResponse); -} - -void URpmPreviewLoaderComponent::CreateCharacter(const FString& BaseModelId) -{ - FCharacterCreateRequest CharacterCreateRequest = FCharacterCreateRequest(); - CharacterCreateRequest.Data.Assets = TMap(); - CharacterCreateRequest.Data.Assets.Add("baseModel", BaseModelId); - CharacterCreateRequest.Data.ApplicationId = AppId; - - CharacterApi->CreateAsync(CharacterCreateRequest); -} - -void URpmPreviewLoaderComponent::HandleCharacterCreateResponse(FCharacterCreateResponse CharacterCreateResponse, - bool bWasSuccessful) -{ - Character = CharacterCreateResponse.Data; - LoadCharacter(Character); -} - -void URpmPreviewLoaderComponent::HandleCharacterUpdateResponse(FCharacterUpdateResponse CharacterUpdateResponse, - bool bWasSuccessful) -{ - Character = CharacterUpdateResponse.Data; -} - -void URpmPreviewLoaderComponent::HandleCharacterFindResponse(FCharacterFindByIdResponse CharacterFindByIdResponse, - bool bWasSuccessful) -{ - Character = CharacterFindByIdResponse.Data; -} - -void URpmPreviewLoaderComponent::LoadCharacter(FRpmCharacter CharacterData) -{ - LoadCharacterFromUrl(CharacterData.GlbUrl); -} - -void URpmPreviewLoaderComponent::LoadAssetPreview(FAsset AssetData) -{ - if (Character.Id.IsEmpty()) - { - UE_LOG(LogReadyPlayerMe, Warning, TEXT("Character Id is empty")); - return; - } - - PreviewAssetMap.Add(AssetData.Type, AssetData.Id); - FCharacterPreviewRequest PreviewRequest; - PreviewRequest.Id = Character.Id; - PreviewRequest.Params.Assets = PreviewAssetMap; - const FString& Url = CharacterApi->GeneratePreviewUrl(PreviewRequest); - LoadCharacterFromUrl(Url); -} - diff --git a/Source/RpmNextGen/Private/RpmTextureLoader.cpp b/Source/RpmNextGen/Private/RpmTextureLoader.cpp new file mode 100644 index 0000000..c7b0315 --- /dev/null +++ b/Source/RpmNextGen/Private/RpmTextureLoader.cpp @@ -0,0 +1,30 @@ +#include "RpmTextureLoader.h" + +#include "RpmNextGen.h" +#include "Api/Assets/AssetIconLoader.h" +#include "Async/Async.h" +#include "Modules/ModuleManager.h" +#include "Engine/Texture2D.h" +#include "Utilities/RpmImageHelper.h" + +FRpmTextureLoader::FRpmTextureLoader() +{ + AssetIconLoader = MakeShared(); + AssetIconLoader->OnIconLoaded.BindRaw( this, &FRpmTextureLoader::OnIconLoaded); +} + +void FRpmTextureLoader::LoadIconFromAsset(const FAsset& Asset, bool bStoreInCache) +{ + AssetIconLoader->LoadIcon(Asset, bStoreInCache); +} + +void FRpmTextureLoader::OnIconLoaded(const FAsset& Asset, const TArray& Array) +{ + if (UTexture2D* Texture = FRpmImageHelper::CreateTextureFromData(Array)) + { + OnTextureLoaded.ExecuteIfBound(Texture); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load icon for asset: %s"), *Asset.Id); + OnTextureLoaded.ExecuteIfBound(nullptr); +} diff --git a/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp index b168236..9d7a1ef 100644 --- a/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp +++ b/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp @@ -1,10 +1,11 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "Samples/RpmAssetButtonWidget.h" -#include "RpmImageLoader.h" +#include "RpmTextureLoader.h" #include "Components/Button.h" #include "Components/Image.h" #include "Components/SizeBox.h" +#include "Utilities/RpmImageHelper.h" void URpmAssetButtonWidget::NativeConstruct() { @@ -13,6 +14,12 @@ void URpmAssetButtonWidget::NativeConstruct() { AssetButton->OnClicked.AddDynamic(this, &URpmAssetButtonWidget::HandleButtonClicked); DefaultColor = AssetButton->BackgroundColor; + + } + if(!TextureLoader.IsValid()) + { + TextureLoader = MakeShared(); + TextureLoader->OnTextureLoaded.BindUObject(this, &URpmAssetButtonWidget::OnTextureLoaded); } } @@ -23,9 +30,7 @@ void URpmAssetButtonWidget::InitializeButton(const FAsset& InAssetData, const FV if (AssetImage) { AssetImage->SetDesiredSizeOverride(InImageSize); - - FRpmImageLoader ImageLoader; - ImageLoader.LoadUImageFromURL(AssetImage, AssetData.IconUrl); + TextureLoader->LoadIconFromAsset(AssetData); } } @@ -45,3 +50,8 @@ void URpmAssetButtonWidget::SetSelected(const bool bInIsSelected) AssetButton->SetBackgroundColor(NewColor); } } + +void URpmAssetButtonWidget::OnTextureLoaded(UTexture2D* Texture2D) +{ + FRpmImageHelper::LoadTextureToUImage(Texture2D, AssetImage->Brush.ImageSize, AssetImage); +} diff --git a/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp index 10da885..6ae9008 100644 --- a/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp +++ b/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp @@ -1,14 +1,21 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "Samples/RpmAssetCardWidget.h" -#include "RpmImageLoader.h" +#include "RpmTextureLoader.h" #include "Api/Assets/Models/Asset.h" +#include "Components/Image.h" #include "Components/TextBlock.h" +#include "Utilities/RpmImageHelper.h" void URpmAssetCardWidget::NativeConstruct() { Super::NativeConstruct(); this->SetVisibility(ESlateVisibility::Hidden); + if(!TextureLoader.IsValid()) + { + TextureLoader = MakeShared(); + TextureLoader->OnTextureLoaded.BindUObject(this, &URpmAssetCardWidget::OnTextureLoaded); + } } void URpmAssetCardWidget::InitializeCard(const FAsset& Asset) @@ -21,12 +28,18 @@ void URpmAssetCardWidget::InitializeCard(const FAsset& Asset) AssetNameText->SetVisibility( AssetData.Name.IsEmpty() ? ESlateVisibility::Hidden : ESlateVisibility::Visible ); AssetIdText->SetText(FText::FromString(AssetData.Id)); - LoadImage(AssetData.IconUrl); + LoadImage(AssetData); } -void URpmAssetCardWidget::LoadImage(const FString& URL) +void URpmAssetCardWidget::LoadImage(const FAsset& Asset) { - FRpmImageLoader ImageLoader; - ImageLoader.LoadUImageFromURL(AssetImage, URL); + AssetData = Asset; + TextureLoader->LoadIconFromAsset(AssetData); } +void URpmAssetCardWidget::OnTextureLoaded(UTexture2D* Texture2D) +{ + FRpmImageHelper::LoadTextureToUImage(Texture2D, AssetImage->Brush.ImageSize, AssetImage); +} + + diff --git a/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp index a8dc7a4..1cc17ca 100644 --- a/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp +++ b/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp @@ -2,9 +2,11 @@ #include "Samples/RpmAssetPanelWidget.h" +#include "RpmNextGen.h" #include "Api/Assets/AssetApi.h" #include "Api/Assets/Models/AssetListRequest.h" -#include "Api/Auth/ApiKeyAuthStrategy.h" +#include "Cache/AssetCacheManager.h" +#include "Cache/CachedAssetData.h" #include "Components/PanelWidget.h" #include "Components/SizeBox.h" #include "Samples/RpmAssetButtonWidget.h" @@ -13,45 +15,61 @@ void URpmAssetPanelWidget::NativeConstruct() { Super::NativeConstruct(); - const URpmDeveloperSettings* RpmSettings = GetDefault(); AssetApi = MakeShared(); - // TODO - add smarter setting of auth strategy - if(!RpmSettings->ApiKey.IsEmpty() || RpmSettings->ApiProxyUrl.IsEmpty()) - { - AssetApi->SetAuthenticationStrategy(new FApiKeyAuthStrategy()); - } - AssetApi->OnListAssetsResponse.BindUObject(this, &URpmAssetPanelWidget::OnAssetListResponse); - - ButtonSize = FVector2D(200, 200); - ImageSize = FVector2D(200, 200); } void URpmAssetPanelWidget::OnAssetListResponse(const FAssetListResponse& AssetListResponse, bool bWasSuccessful) { if(bWasSuccessful && AssetListResponse.Data.Num() > 0) { + Pagination = AssetListResponse.Pagination; + OnPaginationUpdated.Broadcast(Pagination); CreateButtonsFromAssets(AssetListResponse.Data); return; } - UE_LOG(LogTemp, Error, TEXT("Failed to fetch assets")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to fetch assets")); +} + +void URpmAssetPanelWidget::LoadAssetsFromCache(const FString& AssetType) +{ + TArray CachedAssets = FAssetCacheManager::Get().GetAssetsOfType(AssetType); + for (auto CachedAsset : CachedAssets) + { + CreateButton(CachedAsset.ToAsset()); + } } void URpmAssetPanelWidget::CreateButtonsFromAssets(TArray Assets) { + if(Assets.Num() < 1) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("No assets found")); + return; + } for (auto Asset : Assets) { CreateButton(Asset); } - UE_LOG(LogTemp, Warning, TEXT("No assets found") ); } void URpmAssetPanelWidget::ClearAllButtons() { - if(!AssetButtons.IsEmpty()) + if (ButtonContainer) { - AssetButtons.Empty(); + ButtonContainer->ClearChildren(); + + for (auto& ButtonPair : AssetButtonMap) + { + if (URpmAssetButtonWidget* ButtonWidget = Cast(ButtonPair.Value->GetDefaultObject())) + { + ButtonWidget->RemoveFromParent(); + ButtonWidget->ConditionalBeginDestroy(); + } + } } + + AssetButtonMap.Empty(); SelectedAssetButton = nullptr; } @@ -68,9 +86,7 @@ void URpmAssetPanelWidget::CreateButton(const FAsset& AssetData) { if (AssetButtonBlueprint) { - UWorld* World = GetWorld(); - - if (World) + if (UWorld* World = GetWorld()) { if (URpmAssetButtonWidget* AssetButtonInstance = CreateWidget(World, AssetButtonBlueprint)) { @@ -84,17 +100,22 @@ void URpmAssetPanelWidget::CreateButton(const FAsset& AssetData) } AssetButtonInstance->InitializeButton(AssetData, ImageSize); - AssetButtons.Add(AssetButtonBlueprint); + AssetButtonMap.Add(AssetData.Id, AssetButtonBlueprint); AssetButtonInstance->OnAssetButtonClicked.AddDynamic(this, &URpmAssetPanelWidget::OnAssetButtonClicked); } } } else { - UE_LOG(LogTemp, Error, TEXT("AssetButtonBlueprint is not set!")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("AssetButtonBlueprint is not set!")); } } +void URpmAssetPanelWidget::SynchronizeProperties() +{ + Super::SynchronizeProperties(); +} + void URpmAssetPanelWidget::OnAssetButtonClicked(const URpmAssetButtonWidget* AssetButton) { UpdateSelectedButton(const_cast(AssetButton)); @@ -103,15 +124,43 @@ void URpmAssetPanelWidget::OnAssetButtonClicked(const URpmAssetButtonWidget* Ass void URpmAssetPanelWidget::LoadAssetsOfType(const FString& AssetType) { + CurrentAssetType = AssetType; if (!AssetApi.IsValid()) { - UE_LOG(LogTemp, Error, TEXT("AssetApi is null or invalid")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("AssetApi is null or invalid")); return; } + const URpmDeveloperSettings* RpmSettings = GetDefault(); FAssetListQueryParams QueryParams; QueryParams.Type = AssetType; QueryParams.ApplicationId = RpmSettings->ApplicationId; - FAssetListRequest AssetListRequest = FAssetListRequest(QueryParams); + QueryParams.Limit = PaginationLimit; + QueryParams.Page = Pagination.Page; + const FAssetListRequest AssetListRequest = FAssetListRequest(QueryParams); AssetApi->ListAssetsAsync(AssetListRequest); } + +void URpmAssetPanelWidget::LoadNextPage() +{ + if (!Pagination.HasNextPage) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Already on the last page")); + return; + } + ClearAllButtons(); + Pagination.Page++; + LoadAssetsOfType(CurrentAssetType); +} + +void URpmAssetPanelWidget::LoadPreviousPage() +{ + if (!Pagination.HasPrevPage) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Already on the first page")); + return; + } + ClearAllButtons(); + Pagination.Page--; + LoadAssetsOfType(CurrentAssetType); +} diff --git a/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp index 119bd8c..7382dca 100644 --- a/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp +++ b/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp @@ -1,6 +1,8 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "Samples/RpmCategoryButtonWidget.h" + +#include "RpmNextGen.h" #include "Components/Button.h" #include "Components/Image.h" @@ -19,14 +21,19 @@ void URpmCategoryButtonWidget::NativeConstruct() } } -void URpmCategoryButtonWidget::InitializeButton(FString Category, UTexture2D* Image) +void URpmCategoryButtonWidget::InitializeButton(FString Category, UTexture2D* Image, const FVector2D& InImageSize) { AssetCategoryName = Category; if (CategoryImage) - { - CategoryImageTexture = Image; - CategoryImage->SetBrushFromTexture(CategoryImageTexture); + { + CategoryImage->SetDesiredSizeOverride(InImageSize); + + if(Image) + { + CategoryImageTexture = Image; + CategoryImage->SetBrushFromTexture(CategoryImageTexture); + } } } diff --git a/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp index b25abcd..b6cf8a5 100644 --- a/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp +++ b/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp @@ -1,28 +1,28 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "Samples/RpmCategoryPanelWidget.h" +#include "RpmNextGen.h" +#include "Api/Assets/AssetApi.h" +#include "Api/Assets/Models/AssetTypeListRequest.h" #include "Blueprint/WidgetTree.h" +#include "Components/SizeBox.h" #include "Samples/RpmCategoryButtonWidget.h" +#include "Settings/RpmDeveloperSettings.h" + +class URpmDeveloperSettings; void URpmCategoryPanelWidget::NativeConstruct() { Super::NativeConstruct(); - InitializeCategoryButtons(); -} - -void URpmCategoryPanelWidget::InitializeCategoryButtons() -{ - TArray Widgets; - WidgetTree->GetAllWidgets(Widgets); - - for (UWidget* Widget : Widgets) + if(bIsInitialized) { - if (URpmCategoryButtonWidget* CategoryButton = Cast(Widget)) - { - CategoryButton->InitializeButton(CategoryButton->AssetCategoryName, CategoryButton->CategoryImageTexture); - CategoryButton->OnCategoryClicked.AddDynamic(this, &URpmCategoryPanelWidget::OnCategoryButtonClicked); - } + UE_LOG(LogReadyPlayerMe, Warning, TEXT("URpmCategoryPanelWidget Already initialized")); + return; } + bIsInitialized = true; + AssetApi = MakeShared(); + AssetApi->OnListAssetTypeResponse.BindUObject(this, &URpmCategoryPanelWidget::AssetTypesLoaded); + AssetButtons = TArray>(); } void URpmCategoryPanelWidget::UpdateSelectedButton(URpmCategoryButtonWidget* CategoryButton) @@ -34,9 +34,72 @@ void URpmCategoryPanelWidget::UpdateSelectedButton(URpmCategoryButtonWidget* Cat SelectedCategoryButton = CategoryButton; } +void URpmCategoryPanelWidget::LoadAndCreateButtons() +{ + const URpmDeveloperSettings* Settings = GetDefault(); + FAssetTypeListRequest AssetTypeListRequest; + FAssetTypeListQueryParams QueryParams = FAssetTypeListQueryParams(); + QueryParams.ApplicationId = Settings->ApplicationId; + AssetTypeListRequest.Params = QueryParams; + AssetApi->ListAssetTypesAsync(AssetTypeListRequest); +} + void URpmCategoryPanelWidget::OnCategoryButtonClicked(URpmCategoryButtonWidget* CategoryButton) { UpdateSelectedButton(CategoryButton); OnCategorySelected.Broadcast(CategoryButton->AssetCategoryName); } +void URpmCategoryPanelWidget::CreateButton(const FString& AssetType) +{ + if (UWorld* World = GetWorld()) + { + URpmCategoryButtonWidget* CategoryButton = CreateWidget(World, CategoryButtonBlueprint); + UTexture2D* ButtonTexture = nullptr; + const auto CleanAssetType = AssetType.Replace(TEXT(" "), TEXT("-")); + if(UObject* LoadedAsset = StaticLoadObject(UTexture2D::StaticClass(), nullptr, *FString::Printf(TEXT("/RpmNextGen/Samples/Icons/T-rpm-%s"), *CleanAssetType))) + { + ButtonTexture = Cast(LoadedAsset); + } + if (CategoryButton) + { + + USizeBox* ButtonSizeBox = NewObject(this); + if (ButtonSizeBox && ButtonContainer) + { + ButtonSizeBox->SetWidthOverride(ButtonSize.X); + ButtonSizeBox->SetHeightOverride(ButtonSize.Y); + ButtonSizeBox->AddChild(CategoryButton); + ButtonContainer->AddChild(ButtonSizeBox); + CategoryButton->InitializeButton(AssetType, ButtonTexture, ButtonSize); + CategoryButton->OnCategoryClicked.AddDynamic(this, &URpmCategoryPanelWidget::OnCategoryButtonClicked); + AssetButtons.Add(CategoryButton->GetClass()); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to Load %s button"), *AssetType); + } + } +} + +void URpmCategoryPanelWidget::SynchronizeProperties() +{ + Super::SynchronizeProperties(); +} + +void URpmCategoryPanelWidget::AssetTypesLoaded(const FAssetTypeListResponse& AssetTypeListResponse, bool bWasSuccessful) +{ + if(bWasSuccessful && ButtonContainer) + { + ButtonContainer->ClearChildren(); + + for (const FString& AssetType : AssetTypeListResponse.Data) + { + CreateButton(AssetType); + } + OnCategoriesLoaded.Broadcast(AssetTypeListResponse.Data); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load asset types")); + OnCategoriesLoaded.Broadcast(TArray()); +} + diff --git a/Source/RpmNextGen/Private/Samples/RpmCreatorWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmCreatorWidget.cpp new file mode 100644 index 0000000..3b17b5c --- /dev/null +++ b/Source/RpmNextGen/Private/Samples/RpmCreatorWidget.cpp @@ -0,0 +1,98 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Samples/RpmCreatorWidget.h" + +#include "RpmNextGen.h" +#include "Blueprint/WidgetTree.h" +#include "Components/VerticalBox.h" +#include "Components/WidgetSwitcher.h" +#include "Samples/RpmAssetPanelWidget.h" + +class UVerticalBox; + +void URpmCreatorWidget::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void URpmCreatorWidget::CreateAssetPanelsFromCategories(const TArray& CategoryArray) +{ + if (CategoryArray.Num() == 0) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("No categories to create asset panels from!")); + return; + } + if (!AssetPanelSwitcher || !AssetPanelBlueprint) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("WidgetSwitcher or WidgetBlueprintClass is not set!")); + return; + } + + AssetPanelSwitcher->ClearChildren(); + + IndexMapByCategory.Empty(); + for (int i = 0; i < CategoryArray.Num(); ++i) + { + CreateAssetPanel(CategoryArray[i]); + IndexMapByCategory.Add(CategoryArray[i], i ); + } + if(IndexMapByCategory.Num() > 0) + { + SwitchToPanel(CategoryArray[0]); + } +} + +void URpmCreatorWidget::SwitchToPanel(const FString& Category) +{ + if(AssetPanelSwitcher) + { + if(IndexMapByCategory.Num() < 1 || IndexMapByCategory[Category] == -1) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Category %s not found! Category length %d"), *Category, IndexMapByCategory.Num()); + return; + } + AssetPanelSwitcher->SetActiveWidgetIndex(IndexMapByCategory[Category]); + } +} + +void URpmCreatorWidget::SynchronizeProperties() +{ + Super::SynchronizeProperties(); +} + +void URpmCreatorWidget::HandleAssetSelectedFromPanel(const FAsset& AssetData) +{ + OnAssetSelected.Broadcast(AssetData); +} + +UUserWidget* URpmCreatorWidget::CreateAssetPanel(const FString& Category) +{ + if (!AssetPanelBlueprint) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("WidgetBlueprintClass is not set!")); + return nullptr; + } + + UWorld* World = GetWorld(); + if (!World) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("World is null!")); + return nullptr; + } + + URpmAssetPanelWidget* AssetPanelWidget = CreateWidget(World, AssetPanelBlueprint); + + if (!AssetPanelWidget) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to create widget from blueprint class!")); + return nullptr; + } + AssetPanelSwitcher->AddChild(AssetPanelWidget); + AssetPanelWidget->PaginationLimit = PaginationLimit; + AssetPanelWidget->Rename(*Category); + AssetPanelWidget->ButtonSize = FVector2D(200, 200); + AssetPanelWidget->ImageSize = FVector2D(200, 200); + AssetPanelWidget->OnAssetSelected.AddDynamic(this, &URpmCreatorWidget::HandleAssetSelectedFromPanel); + AssetPanelWidget->LoadAssetsOfType(Category); + return AssetPanelWidget; +} diff --git a/Source/RpmNextGen/Private/Samples/RpmPaginatorWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmPaginatorWidget.cpp new file mode 100644 index 0000000..4ac4107 --- /dev/null +++ b/Source/RpmNextGen/Private/Samples/RpmPaginatorWidget.cpp @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Samples/RpmPaginatorWidget.h" + +#include "Components/Button.h" +#include "Components/TextBlock.h" + +void URpmPaginatorWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + if (PreviousButton) + { + PreviousButton->OnClicked.AddDynamic(this, &URpmPaginatorWidget::OnPrevButtonClicked); + } + + if (NextButton) + { + NextButton->OnClicked.AddDynamic(this, &URpmPaginatorWidget::OnNextButtonClicked); + } + UpdateState(FPagination()); +} + +void URpmPaginatorWidget::UpdateState(const FPagination& Pagination) +{ + if (PageText) + { + PageText->SetText(GetPageCountText(Pagination)); + } + + if (PreviousButton) + { + PreviousButton->SetIsEnabled(Pagination.Page > 1); + } + + if (NextButton) + { + NextButton->SetIsEnabled(Pagination.Page < Pagination.TotalPages); + } +} + +FText URpmPaginatorWidget::GetPageCountText(const FPagination& Pagination) +{ + return FText::FromString(FString::Printf(TEXT("%d / %d"), Pagination.Page, Pagination.TotalPages)); +} + +void URpmPaginatorWidget::OnPrevButtonClicked() +{ + OnPreviousButtonEvent.Broadcast(); +} + +void URpmPaginatorWidget::OnNextButtonClicked() +{ + OnNextButtonEvent.Broadcast(); +} \ No newline at end of file diff --git a/Source/RpmNextGen/Private/Utilities/RpmImageHelper.cpp b/Source/RpmNextGen/Private/Utilities/RpmImageHelper.cpp new file mode 100644 index 0000000..c7838a3 --- /dev/null +++ b/Source/RpmNextGen/Private/Utilities/RpmImageHelper.cpp @@ -0,0 +1,119 @@ +#include "Utilities/RpmImageHelper.h" +#include "IImageWrapper.h" +#include "IImageWrapperModule.h" +#include "RpmNextGen.h" +#include "Components/Image.h" +#include "Widgets/Images/SImage.h" +#include "Engine/Texture.h" +#include "Engine/Texture2D.h" + +UTexture2D* FRpmImageHelper::CreateTextureFromData(const TArray& ImageData) +{ + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked( + FName("ImageWrapper")); + EImageFormat ImageFormat = ImageWrapperModule.DetectImageFormat(ImageData.GetData(), ImageData.Num()); + + if (ImageFormat == EImageFormat::Invalid) + { + return nullptr; + } + + TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageFormat); + if (!ImageWrapper.IsValid() || !ImageWrapper->SetCompressed(ImageData.GetData(), ImageData.Num())) + { + return nullptr; + } + + TArray UncompressedBGRA; + if (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) + { + return nullptr; + } + + // Create the texture + UTexture2D* Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8); + if (!Texture) + { + return nullptr; + } + + // Disable mipmaps and streaming for icons + Texture->CompressionSettings = TC_EditorIcon; // Optional: Prevent unnecessary compression for icons. + //Texture->MipGenSettings = TMGS_NoMipmaps; + Texture->LODGroup = TEXTUREGROUP_UI; // UI textures typically don’t use mipmaps. + Texture->NeverStream = true; + Texture->SRGB = true; // If you're working with UI icons, they are usually in sRGB space. + Texture->UpdateResource(); + + // Lock the texture and copy data + void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num()); + Texture->GetPlatformData()->Mips[0].BulkData.Unlock(); + + // Update texture resource + Texture->UpdateResource(); + + return Texture; +} + + +UImage* FRpmImageHelper::CreateUImageFromData(const TArray& ImageData, const FVector2D& ImageSize) +{ + UImage* Image = NewObject(); + if (UTexture2D* Texture = CreateTextureFromData(ImageData)) + { + FSlateBrush Brush; + Brush.SetResourceObject(Texture); + Brush.ImageSize = ImageSize; + Image->SetBrush(Brush); + return Image; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load image data to UImage")); + return Image; +} + +void FRpmImageHelper::LoadDataToUImage(const TArray& ImageData, UImage*& Image) +{ + if (UTexture2D* Texture = CreateTextureFromData(ImageData)) + { + LoadTextureToUImage(Texture, Image->Brush.ImageSize, Image); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load data to Texture")); +} + +void FRpmImageHelper::LoadDataToSImage(const TArray& ImageData, const FVector2D& ImageSize, TSharedPtr ImageWidget) +{ + if (ImageWidget.IsValid()) + { + UTexture2D* Texture = CreateTextureFromData(ImageData); + LoadTextureToSImage(Texture, ImageSize, ImageWidget); + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load data to Texture")); +} + +void FRpmImageHelper::LoadTextureToSImage(UTexture2D* Texture, const FVector2D& ImageSize, TSharedPtr ImageWidget) +{ + if (ImageWidget.IsValid()) + { + FSlateBrush* Brush = new FSlateBrush(); + Brush->SetResourceObject(Texture); + Brush->ImageSize = ImageSize; + ImageWidget->SetImage(Brush); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load texture to SImage")); +} + +void FRpmImageHelper::LoadTextureToUImage(UTexture2D* Texture, const FVector2D& ImageSize, UImage*& Image) +{ + if (Image) + { + FSlateBrush Brush; + Brush.SetResourceObject(Texture); + Brush.ImageSize = ImageSize; + Image->SetBrush(Brush); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load texture to UImage")); +} diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetApi.h b/Source/RpmNextGen/Public/Api/Assets/AssetApi.h index a8d7548..b89a713 100644 --- a/Source/RpmNextGen/Public/Api/Assets/AssetApi.h +++ b/Source/RpmNextGen/Public/Api/Assets/AssetApi.h @@ -1,22 +1,41 @@ -#pragma once +#pragma once +#include "Api/Common/ApiRequestStrategy.h" #include "Api/Common/WebApiWithAuth.h" +#include "Models/AssetTypeListResponse.h" +struct FApiRequest; +struct FAssetTypeListRequest; struct FAssetListRequest; struct FAssetListResponse; DECLARE_DELEGATE_TwoParams(FOnListAssetsResponse, const FAssetListResponse&, bool); -DECLARE_DELEGATE_TwoParams(FOnListAssetTypeResponse, const FAssetListResponse&, bool); +DECLARE_DELEGATE_TwoParams(FOnListAssetTypeResponse, const FAssetTypeListResponse&, bool); -class RPMNEXTGEN_API FAssetApi : public FWebApiWithAuth +class RPMNEXTGEN_API FAssetApi : public FWebApiWithAuth { public: - FAssetApi(); - void ListAssetsAsync(const FAssetListRequest& Request); + static const FString BaseModelType; + FOnListAssetsResponse OnListAssetsResponse; FOnListAssetTypeResponse OnListAssetTypeResponse; -private: - void HandleListAssetResponse(FString Response, bool bWasSuccessful); - void HandleListAssetTypeResponse(FString Response, bool bWasSuccessful); + FAssetApi(); + FAssetApi(EApiRequestStrategy InApiRequestStrategy); + virtual ~FAssetApi() override; + + void Initialize(); + void ListAssetsAsync(const FAssetListRequest& Request); + void ListAssetTypesAsync(const FAssetTypeListRequest& Request); +protected: + EApiRequestStrategy ApiRequestStrategy; + +private: FString ApiBaseUrl; + bool bIsInitialized = false; + void HandleAssetResponse(TSharedPtr, FHttpResponsePtr Response, bool bWasSuccessful); + + void LoadAssetsFromCache(TMap QueryParams); + void LoadAssetTypesFromCache(); + + TArray ExtractQueryValues(const FString& QueryString, const FString& Key); }; diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetGlbLoader.h b/Source/RpmNextGen/Public/Api/Assets/AssetGlbLoader.h new file mode 100644 index 0000000..8aea007 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Assets/AssetGlbLoader.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Models/Asset.h" + +struct FAssetLoadingContext; +struct FCachedAssetData; +class IHttpResponse; +class FHttpModule; +struct FAsset; + +class RPMNEXTGEN_API FAssetGlbLoader : public TSharedFromThis +{ +public: + DECLARE_DELEGATE_TwoParams(FOnGlbLoaded, const FAsset&, const TArray&); + + FOnGlbLoaded OnGlbLoaded; + + FAssetGlbLoader(); + virtual ~FAssetGlbLoader(); + void LoadGlb(const FAsset& Asset, const FString& BaseModelId, bool bStoreInCache); + +private: + FHttpModule* Http; + + void LoadGlb(TSharedRef Context); + void GlbLoaded(TSharedPtr Response, bool bWasSuccessful, const TSharedRef& Context); +}; diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetIconLoader.h b/Source/RpmNextGen/Public/Api/Assets/AssetIconLoader.h new file mode 100644 index 0000000..25c2cef --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Assets/AssetIconLoader.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Models/Asset.h" + +struct FAssetLoadingContext; +struct FCachedAssetData; +class IHttpResponse; +class FHttpModule; +struct FAsset; + +class RPMNEXTGEN_API FAssetIconLoader : public TSharedFromThis +{ +public: + DECLARE_DELEGATE_TwoParams(FOnIconLoaded, const FAsset&, const TArray&); + + FOnIconLoaded OnIconLoaded; + + FAssetIconLoader(); + virtual ~FAssetIconLoader(); + + void LoadIcon(const FAsset& Asset, bool bStoreInCache); + +private: + FHttpModule* Http; + + UFUNCTION() + void IconLoaded(TSharedPtr Response, bool bWasSuccessful, const TSharedRef& Context); + + void LoadIcon(TSharedRef Context); +}; diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h b/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h deleted file mode 100644 index 75cf1c3..0000000 --- a/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "Api/Common/WebApi.h" -#include "HAL/PlatformFilemanager.h" -#include "Interfaces/IHttpRequest.h" - -struct FglTFRuntimeConfig; -class UglTFRuntimeAsset; - -DECLARE_DELEGATE_TwoParams(FOnAssetDataReceived, TArray, bool); -DECLARE_DELEGATE_TwoParams(FOnAssetSaved, FString, bool); -DECLARE_DELEGATE_TwoParams(FOnAssetDownloaded, UglTFRuntimeAsset*, bool); - - -class RPMNEXTGEN_API FAssetLoader : public FWebApi -{ -public: - FAssetLoader(); - FAssetLoader(FglTFRuntimeConfig* Config); - virtual ~FAssetLoader() override; - - void LoadGLBFromURL(const FString& URL); - - FOnAssetDataReceived OnRequestDataReceived; - FOnAssetDownloaded OnGLtfAssetLoaded; - FOnAssetSaved OnAssetSaved; - bool bSaveToDisk = false; - -protected: - FglTFRuntimeConfig* GltfConfig; - void virtual OnLoadComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - FString DownloadDirectory; -}; diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetLoaderContext.h b/Source/RpmNextGen/Public/Api/Assets/AssetLoaderContext.h new file mode 100644 index 0000000..81176f9 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Assets/AssetLoaderContext.h @@ -0,0 +1,16 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Models/Asset.h" + +struct FAssetLoadingContext +{ + FAsset Asset; + FString BaseModelId; + TArray Data; + bool bStoreInCache; + FAssetLoadingContext(const FAsset& InAsset, const FString& InBaseModelId, bool bInStoreInCache) + : Asset(InAsset), BaseModelId(InBaseModelId), bStoreInCache(bInStoreInCache) + { + } +}; diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h b/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h index edffd8a..a3301ce 100644 --- a/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h +++ b/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h @@ -1,11 +1,10 @@ -#pragma once +#pragma once #include "CoreMinimal.h" -#include "Api/Common/Models/ApiResponse.h" #include "Asset.generated.h" USTRUCT(BlueprintType) -struct RPMNEXTGEN_API FAsset : public FApiResponse +struct RPMNEXTGEN_API FAsset { GENERATED_BODY() @@ -29,4 +28,28 @@ struct RPMNEXTGEN_API FAsset : public FApiResponse UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "updatedAt")) FDateTime UpdatedAt; + + FAsset() + { + Id = ""; + Name = ""; + GlbUrl = ""; + IconUrl = ""; + Type = ""; + CreatedAt = FDateTime(); + UpdatedAt = FDateTime(); + } + +private: + UPROPERTY(meta = (JsonIgnore)) + FString organizationId = ""; + + UPROPERTY(meta = (JsonIgnore)) + FString cageMeshId = ""; + + UPROPERTY(meta = (JsonIgnore)) + bool active = false; + + UPROPERTY(meta = (JsonIgnore)) + FString _id = ""; }; \ No newline at end of file diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/AssetListRequest.h b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListRequest.h index 982517c..18427d5 100644 --- a/Source/RpmNextGen/Public/Api/Assets/Models/AssetListRequest.h +++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListRequest.h @@ -1,10 +1,11 @@ -#pragma once +#pragma once #include "CoreMinimal.h" +#include "Api/Common/Models/PaginationQueryParams.h" #include "AssetListRequest.generated.h" USTRUCT(BlueprintType) -struct RPMNEXTGEN_API FAssetListQueryParams +struct RPMNEXTGEN_API FAssetListQueryParams : public FPaginationQueryParams { GENERATED_BODY() @@ -15,48 +16,75 @@ struct RPMNEXTGEN_API FAssetListQueryParams FString Type; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "excludeTypes")) - FString ExcludeTypes; + TArray ExcludeTypes; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "characterModelAssetId")) + FString CharacterModelAssetId; }; USTRUCT(BlueprintType) -struct RPMNEXTGEN_API FAssetListRequest +struct RPMNEXTGEN_API FAssetListRequest { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") FAssetListQueryParams Params; - // Default constructor FAssetListRequest() { } - // Constructor that accepts FAssetListQueryParams FAssetListRequest(const FAssetListQueryParams& InParams) : Params(InParams) { } - FString BuildQueryString() const; + TMap BuildQueryMap() const; }; -inline FString FAssetListRequest::BuildQueryString() const +inline TMap FAssetListRequest::BuildQueryMap() const { - if (Params.ApplicationId.IsEmpty() && Params.Type.IsEmpty() && Params.ExcludeTypes.IsEmpty()) return FString(); - FString QueryString = TEXT("?"); + TMap QueryMap; if (!Params.ApplicationId.IsEmpty()) { - QueryString += TEXT("applicationId=") + Params.ApplicationId + TEXT("&"); + QueryMap.Add(TEXT("applicationId"), Params.ApplicationId); } if (!Params.Type.IsEmpty()) { - QueryString += TEXT("type=") + Params.Type + TEXT("&"); + QueryMap.Add(TEXT("type"), Params.Type); } if (!Params.ExcludeTypes.IsEmpty()) { - QueryString += TEXT("excludeTypes=") + Params.ExcludeTypes + TEXT("&"); + if (Params.ExcludeTypes.Num() > 0) + { + FString ExcludeTypesString; + for (int32 i = 0; i < Params.ExcludeTypes.Num(); i++) + { + ExcludeTypesString += Params.ExcludeTypes[i]; + // Add '&excludeTypes=' only if it's not the last element + if (i < Params.ExcludeTypes.Num() - 1) + { + ExcludeTypesString += TEXT("&excludeTypes="); + } + } + QueryMap.Add(TEXT("excludeTypes"), ExcludeTypesString); + } } - QueryString.RemoveFromEnd(TEXT("&")); - return QueryString; + if (!Params.CharacterModelAssetId.IsEmpty()) + { + QueryMap.Add(TEXT("characterModelAssetId"), Params.CharacterModelAssetId); + } + if( Params.Limit > 0 ) + { + QueryMap.Add(TEXT("limit"), FString::FromInt(Params.Limit)); + } + if( Params.Page > 0 ) + { + QueryMap.Add(TEXT("page"), FString::FromInt(Params.Page)); + } + if( !Params.Order.IsEmpty() ) + { + QueryMap.Add(TEXT("order"), Params.Order); + } + return QueryMap; } diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h index d31cd77..3b429d4 100644 --- a/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h +++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h @@ -2,16 +2,18 @@ #include "CoreMinimal.h" #include "Api/Assets/Models/Asset.h" +#include "Api/Common/Models/ApiResponse.h" +#include "Api/Common/Models/Pagination.h" #include "AssetListResponse.generated.h" USTRUCT(BlueprintType) -struct RPMNEXTGEN_API FAssetListResponse +struct RPMNEXTGEN_API FAssetListResponse : public FApiResponse { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data")) TArray Data; - bool bSuccess; - int64 Status; - FString Error; + + UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "pagination") ) + FPagination Pagination; }; diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListRequest.h b/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListRequest.h new file mode 100644 index 0000000..c91d397 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListRequest.h @@ -0,0 +1,60 @@ +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeListRequest.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FAssetTypeListQueryParams +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "applicationId")) + FString ApplicationId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "type")) + FString Type; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "excludeTypes")) + FString ExcludeTypes; +}; + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FAssetTypeListRequest +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FAssetTypeListQueryParams Params; + + + FAssetTypeListRequest() + { + } + + FAssetTypeListRequest(const FAssetTypeListQueryParams& InParams) + : Params(InParams) + { + } + + FString BuildQueryString() const; +}; + +inline FString FAssetTypeListRequest::BuildQueryString() const +{ + if (Params.ApplicationId.IsEmpty() && Params.Type.IsEmpty() && Params.ExcludeTypes.IsEmpty()) return FString(); + FString QueryString = TEXT("?"); + if (!Params.ApplicationId.IsEmpty()) + { + QueryString += TEXT("applicationId=") + Params.ApplicationId + TEXT("&"); + } + if (!Params.Type.IsEmpty()) + { + QueryString += TEXT("type=") + Params.Type + TEXT("&"); + } + if (!Params.ExcludeTypes.IsEmpty()) + { + QueryString += TEXT("excludeTypes=") + Params.ExcludeTypes + TEXT("&"); + } + QueryString.RemoveFromEnd(TEXT("&")); + return QueryString; +} \ No newline at end of file diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListResponse.h b/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListResponse.h new file mode 100644 index 0000000..932ae01 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetTypeListResponse.h @@ -0,0 +1,14 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Api/Common/Models/ApiResponse.h" +#include "AssetTypeListResponse.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FAssetTypeListResponse : public FApiResponse +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data")) + TArray Data; +}; diff --git a/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h b/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h index 273693d..183414a 100644 --- a/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h +++ b/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h @@ -8,7 +8,7 @@ class RPMNEXTGEN_API FApiKeyAuthStrategy : public IAuthenticationStrategy { public: FApiKeyAuthStrategy(); - virtual void AddAuthToRequest(TSharedPtr Request) override; - virtual void OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful) override; - virtual void TryRefresh(TSharedPtr Request) override; + virtual void AddAuthToRequest(TSharedPtr ApiRequest) override; + virtual void TryRefresh(TSharedPtr ApiRequest) override; + virtual void OnRefreshTokenResponse(TSharedPtr ApiRequest, const FRefreshTokenResponse& Response, bool bWasSuccessful) override; }; diff --git a/Source/RpmNextGen/Public/Api/Auth/AuthApi.h b/Source/RpmNextGen/Public/Api/Auth/AuthApi.h index 8093000..0399448 100644 --- a/Source/RpmNextGen/Public/Api/Auth/AuthApi.h +++ b/Source/RpmNextGen/Public/Api/Auth/AuthApi.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "CoreMinimal.h" #include "Api/Common/WebApi.h" @@ -6,15 +6,17 @@ struct FRefreshTokenResponse; struct FRefreshTokenRequest; -DECLARE_DELEGATE_TwoParams(FOnRefreshTokenResponse, const FRefreshTokenResponse&, bool); +DECLARE_DELEGATE_ThreeParams(FOnRefreshTokenResponse, TSharedPtr, const FRefreshTokenResponse&, bool); class RPMNEXTGEN_API FAuthApi : public FWebApi { public: + FOnRefreshTokenResponse OnRefreshTokenResponse; + FAuthApi(); void RefreshToken(const FRefreshTokenRequest& Request); - FOnRefreshTokenResponse OnRefreshTokenResponse; - virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) override; + + void OnProcessComplete(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful); private: FString ApiUrl; diff --git a/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h b/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h index 651827b..b31505d 100644 --- a/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h +++ b/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h @@ -1,11 +1,12 @@ #pragma once #include "CoreMinimal.h" -#include "Api/Auth/ApiRequest.h" #include "Models/RefreshTokenResponse.h" -DECLARE_DELEGATE_OneParam(FOnAuthComplete, bool); -DECLARE_DELEGATE_TwoParams(FOnTokenRefreshed, const FRefreshTokenResponseBody&, bool); +struct FApiRequest; + +DECLARE_DELEGATE_TwoParams(FOnAuthComplete, TSharedPtr, bool); +DECLARE_DELEGATE_ThreeParams(FOnTokenRefreshed, TSharedPtr, const FRefreshTokenResponseBody&, bool); class RPMNEXTGEN_API IAuthenticationStrategy { @@ -14,7 +15,7 @@ class RPMNEXTGEN_API IAuthenticationStrategy FOnTokenRefreshed OnTokenRefreshed; virtual ~IAuthenticationStrategy() = default; - virtual void AddAuthToRequest(TSharedPtr Request) = 0; - virtual void TryRefresh(TSharedPtr Request) = 0; - virtual void OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful) = 0; + virtual void AddAuthToRequest(TSharedPtr ApiRequest) = 0; + virtual void TryRefresh(TSharedPtr ApiRequest) = 0; + virtual void OnRefreshTokenResponse(TSharedPtr ApiRequest, const FRefreshTokenResponse& Response, bool bWasSuccessful) = 0; }; \ No newline at end of file diff --git a/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h b/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h index 2dd5d0c..6caf48a 100644 --- a/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h +++ b/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "CoreMinimal.h" #include "JsonObjectConverter.h" @@ -16,32 +16,34 @@ DECLARE_DELEGATE_TwoParams(FOnCharacterCreateResponse, FCharacterCreateResponse, DECLARE_DELEGATE_TwoParams(FOnCharacterUpdatResponse, FCharacterUpdateResponse, bool); DECLARE_DELEGATE_TwoParams(FOnCharacterFindResponse, FCharacterFindByIdResponse, bool); -class RPMNEXTGEN_API FCharacterApi : public TSharedFromThis, public FWebApiWithAuth +class RPMNEXTGEN_API FCharacterApi : public FWebApiWithAuth { public: + FOnRequestComplete OnApiResponse; + FOnCharacterCreateResponse OnCharacterCreateResponse; + FOnCharacterUpdatResponse OnCharacterUpdateResponse; + FOnCharacterFindResponse OnCharacterFindResponse; + FCharacterApi(); virtual ~FCharacterApi() override; - FOnWebApiResponse OnApiResponse; void CreateAsync(const FCharacterCreateRequest& Request); void UpdateAsync(const FCharacterUpdateRequest& Request); void FindByIdAsync(const FCharacterFindByIdRequest& Request); FString GeneratePreviewUrl(const FCharacterPreviewRequest& Request); - FOnCharacterCreateResponse OnCharacterCreateResponse; - FOnCharacterUpdatResponse OnCharacterUpdateResponse; - FOnCharacterFindResponse OnCharacterFindResponse; - protected: - virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) override; - template FString ConvertToJsonString(const T& Data); - FHttpModule* Http; + void HandleCharacterResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful); + void HandleCharacterCreateResponse(FHttpResponsePtr Response, bool bWasSuccessful); + void HandleUpdateResponse( FHttpResponsePtr Response, bool bWasSuccessful); + void HandleFindResponse(FHttpResponsePtr Response, bool bWasSuccessful); private: FString BaseUrl; + TMap AssetByType = TMap(); }; template diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h index 321ca68..dfa5f9f 100644 --- a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h +++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h @@ -12,4 +12,18 @@ struct RPMNEXTGEN_API FCharacterCreateResponse : public FApiResponse UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data")) FRpmCharacter Data; + + FCharacterCreateResponse() + { + } + + FCharacterCreateResponse(FRpmCharacter Data) + { + this->Data = Data; + } + + bool IsValid() const + { + return !Data.Id.IsEmpty() && !Data.GlbUrl.IsEmpty(); + } }; diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h b/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h index eec8bfd..8771bfc 100644 --- a/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h +++ b/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h @@ -7,7 +7,7 @@ USTRUCT(BlueprintType) struct RPMNEXTGEN_API FRpmCharacter { GENERATED_BODY() - + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "id")) FString Id; diff --git a/Source/RpmNextGen/Public/Api/Common/ApiRequestStrategy.h b/Source/RpmNextGen/Public/Api/Common/ApiRequestStrategy.h new file mode 100644 index 0000000..69a7460 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Common/ApiRequestStrategy.h @@ -0,0 +1,9 @@ +#pragma once + +UENUM(BlueprintType) +enum class EApiRequestStrategy : uint8 +{ + ApiOnly UMETA(DisplayName = "API only"), + FallbackToCache UMETA(DisplayName = "Fallback to Cache"), + CacheOnly UMETA(DisplayName = "Cache only") +}; \ No newline at end of file diff --git a/Source/RpmNextGen/Public/Api/Auth/ApiRequest.h b/Source/RpmNextGen/Public/Api/Common/Models/ApiRequest.h similarity index 92% rename from Source/RpmNextGen/Public/Api/Auth/ApiRequest.h rename to Source/RpmNextGen/Public/Api/Common/Models/ApiRequest.h index 45ba306..9295742 100644 --- a/Source/RpmNextGen/Public/Api/Auth/ApiRequest.h +++ b/Source/RpmNextGen/Public/Api/Common/Models/ApiRequest.h @@ -13,7 +13,6 @@ enum ERequestMethod { PATCH }; - USTRUCT(BlueprintType) struct RPMNEXTGEN_API FApiRequest { @@ -27,6 +26,8 @@ struct RPMNEXTGEN_API FApiRequest UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") TMap Headers; + TMap QueryParams; + FString Payload; FString GetVerb() const @@ -45,6 +46,11 @@ struct RPMNEXTGEN_API FApiRequest return TEXT("DELETE"); } } + + bool IsValid() const + { + return !Url.IsEmpty(); + } }; USTRUCT(BlueprintType) diff --git a/Source/RpmNextGen/Public/Api/Common/Models/Pagination.h b/Source/RpmNextGen/Public/Api/Common/Models/Pagination.h new file mode 100644 index 0000000..c2e18ff --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Common/Models/Pagination.h @@ -0,0 +1,37 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Pagination.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FPagination +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "totalDocs")) + int TotalDocs = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "limit")) + int Limit = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "totalPages")) + int TotalPages = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "page")) + int Page = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "pagingCounter")) + int PagingCounter = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "hasPrevPage")) + bool HasPrevPage = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "hasNextPage")) + bool HasNextPage = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "prevPage")) + int PrevPage = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "nextPage")) + int NextPage = 0; +}; diff --git a/Source/RpmNextGen/Public/Api/Common/Models/PaginationQueryParams.h b/Source/RpmNextGen/Public/Api/Common/Models/PaginationQueryParams.h new file mode 100644 index 0000000..1cf8c7b --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Common/Models/PaginationQueryParams.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" +#include "PaginationQueryParams.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FPaginationQueryParams +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "limit")) + int Limit = 10; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "page")) + int Page = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "order")) + FString Order; +}; diff --git a/Source/RpmNextGen/Public/Api/Common/WebApi.h b/Source/RpmNextGen/Public/Api/Common/WebApi.h index f611615..152c622 100644 --- a/Source/RpmNextGen/Public/Api/Common/WebApi.h +++ b/Source/RpmNextGen/Public/Api/Common/WebApi.h @@ -1,35 +1,34 @@ -#pragma once +#pragma once #include "CoreMinimal.h" #include "JsonObjectConverter.h" -#include "Api/Auth/ApiRequest.h" #include "Interfaces/IHttpRequest.h" #include "Misc/ScopeExit.h" +#include "Models/ApiRequest.h" class FHttpModule; -DECLARE_DELEGATE_TwoParams(FOnWebApiResponse, FString, bool); + class RPMNEXTGEN_API FWebApi { public: - FWebApi(); - virtual ~FWebApi(); + DECLARE_DELEGATE_ThreeParams(FOnRequestComplete, TSharedPtr, FHttpResponsePtr, bool); - FOnWebApiResponse OnApiResponse; + FOnRequestComplete OnRequestComplete; -protected: - void DispatchRaw( - const FApiRequest& Data - ); + FWebApi(); + virtual ~FWebApi(); - virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); + void DispatchRaw(TSharedPtr ApiRequest); +protected: + FHttpModule* Http; FString BuildQueryString(const TMap& QueryParams); template FString ConvertToJsonString(const T& Data); - - FHttpModule* Http; + + virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TSharedPtr ApiRequest); }; template diff --git a/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h b/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h index dd52dca..8add599 100644 --- a/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h +++ b/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h @@ -9,19 +9,18 @@ class RPMNEXTGEN_API FWebApiWithAuth : public FWebApi { public: FWebApiWithAuth(); - FWebApiWithAuth(IAuthenticationStrategy* InAuthenticationStrategy); + FWebApiWithAuth(const TSharedPtr& InAuthenticationStrategy); - void SetAuthenticationStrategy(IAuthenticationStrategy* InAuthenticationStrategy); + void SetAuthenticationStrategy(const TSharedPtr& InAuthenticationStrategy); - void OnAuthComplete(bool bWasSuccessful); - void OnAuthTokenRefreshed(const FRefreshTokenResponseBody& Response, bool bWasSuccessful); + void OnAuthComplete(TSharedPtr ApiRequest, bool bWasSuccessful); + void OnAuthTokenRefreshed(TSharedPtr ApiRequest, const FRefreshTokenResponseBody& Response, bool bWasSuccessful); - void DispatchRawWithAuth(FApiRequest& Data); + void DispatchRawWithAuth(TSharedPtr ApiRequest); + protected: - TSharedPtr ApiRequestData; - - virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) override; + virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TSharedPtr ApiRequest) override; private: - IAuthenticationStrategy* AuthenticationStrategy; + TSharedPtr AuthenticationStrategy; }; diff --git a/Source/RpmNextGen/Public/Api/Files/FileApi.h b/Source/RpmNextGen/Public/Api/Files/FileApi.h new file mode 100644 index 0000000..398f5d9 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Files/FileApi.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Interfaces/IHttpRequest.h" + +struct FAsset; +DECLARE_DELEGATE_TwoParams(FOnFileRequestComplete, const TArray*, const FString&); +DECLARE_DELEGATE_TwoParams(FOnAssetFileRequestComplete, const TArray*, const FAsset&); + +class RPMNEXTGEN_API FFileApi : public TSharedFromThis +{ +public: + FOnAssetFileRequestComplete OnAssetFileRequestComplete; + FOnFileRequestComplete OnFileRequestComplete; + + FFileApi(); + virtual ~FFileApi(); + virtual void LoadFileFromUrl(const FString& URL); + virtual void LoadAssetFileFromUrl(const FString& URL, FAsset Asset); + virtual void FileRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); + virtual void AssetFileRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FAsset Asset); + static bool LoadFileFromPath(const FString& Path, TArray& OutContent); +}; diff --git a/Source/RpmNextGen/Public/Api/Files/FileUtility.h b/Source/RpmNextGen/Public/Api/Files/FileUtility.h new file mode 100644 index 0000000..cfc35d0 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Files/FileUtility.h @@ -0,0 +1,41 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Misc/FileHelper.h" +#include "RpmNextGen.h" + +class RPMNEXTGEN_API FFileUtility +{ + +public: + static const FString RelativeCachePath; + + static bool SaveToFile(const TArray& Data, const FString& FilePath, const bool bSkipIfFileExists = true) + { + if (bSkipIfFileExists && FPaths::FileExists(FilePath)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("File already exists at: %s. Skipping"), *FilePath); + return true; + } + if (FFileHelper::SaveArrayToFile(Data, *FilePath)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully saved asset to: %s"), *FilePath); + return true; + } + + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to save asset to: %s"), *FilePath); + return false; + } + + static FString GetFullPersistentPath(const FString& RelativePath) + { + const FString PersistentPath = FPaths::ProjectPersistentDownloadDir() / RelativePath; + + return FPaths::ConvertRelativePathToFull(PersistentPath); + } + + static FString GetCachePath() + { + return GetFullPersistentPath(RelativeCachePath); + } +}; diff --git a/Source/RpmNextGen/Public/Api/Files/GlbLoader.h b/Source/RpmNextGen/Public/Api/Files/GlbLoader.h new file mode 100644 index 0000000..f850def --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Files/GlbLoader.h @@ -0,0 +1,35 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FileApi.h" +#include "HAL/PlatformFilemanager.h" + +class FFileUtility; +struct FglTFRuntimeConfig; +class UglTFRuntimeAsset; + +DECLARE_DELEGATE_TwoParams(FOnGlbDownloaded, UglTFRuntimeAsset*, const FString&); + +class RPMNEXTGEN_API FGlbLoader : public FFileApi +{ +public: + FOnGlbDownloaded OnGLtfAssetLoaded; + + FGlbLoader(); + FGlbLoader(FglTFRuntimeConfig* Config); + + virtual ~FGlbLoader() override; + + void SetConfig(FglTFRuntimeConfig* Config) + { + GltfConfig = Config; + } + +protected: + FglTFRuntimeConfig* GltfConfig; + FFileUtility* FileWriter; + FString DownloadDirectory; + + UFUNCTION() + virtual void HandleFileRequestComplete(const TArray* Data, const FString& String); +}; diff --git a/Source/RpmNextGen/Public/Api/Files/PakFileUtility.h b/Source/RpmNextGen/Public/Api/Files/PakFileUtility.h new file mode 100644 index 0000000..e273921 --- /dev/null +++ b/Source/RpmNextGen/Public/Api/Files/PakFileUtility.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" + +class RPMNEXTGEN_API FPakFileUtility +{ +public: + static const FString CachePakFilePath; + + static void CreatePakFile(); + static void ExtractPakFile(const FString& PakFilePath); + static void ExtractFilesFromPak(const FString& PakFilePath); + +private: + static void CreatePakFile(const FString& PakFilePath); + static void GeneratePakResponseFile(); +}; \ No newline at end of file diff --git a/Source/RpmNextGen/Public/Cache/AssetCacheManager.h b/Source/RpmNextGen/Public/Cache/AssetCacheManager.h new file mode 100644 index 0000000..6f38ddc --- /dev/null +++ b/Source/RpmNextGen/Public/Cache/AssetCacheManager.h @@ -0,0 +1,276 @@ +#pragma once +#include "RpmNextGen.h" +#include "CachedAssetData.h" +#include "Api/Assets/AssetLoaderContext.h" +#include "Api/Files/FileUtility.h" + +class FAssetCacheManager +{ +public: + static FAssetCacheManager& Get() + { + static FAssetCacheManager Instance; + return Instance; + } + + static void StoreAssetTypes(const TArray& TypeList) + { + const FString TypeListFilePath = FFileUtility::GetCachePath() / TEXT("TypeList.json"); + + TArray> JsonValues; + for (const FString& Type : TypeList) + { + JsonValues.Add(MakeShared(Type)); + } + + FString OutputString; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); + + FJsonSerializer::Serialize(JsonValues, Writer); + + FFileHelper::SaveStringToFile(OutputString, *TypeListFilePath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + } + + static TArray LoadAssetTypes() + { + const FString GlobalCachePath = FFileUtility::GetCachePath(); + const FString TypeListFilePath = GlobalCachePath / TEXT("TypeList.json"); + FString TypeListContent; + + if (FFileHelper::LoadFileToString(TypeListContent, *TypeListFilePath)) + { + TArray> JsonValues; + TSharedRef> Reader = TJsonReaderFactory<>::Create(TypeListContent); + + if (FJsonSerializer::Deserialize(Reader, JsonValues) && JsonValues.Num() > 0) + { + TArray TypeList; + for (const TSharedPtr& JsonValue : JsonValues) + { + TypeList.Add(JsonValue->AsString()); + } + return TypeList; + } + } + return TArray(); + } + + TArray GetAssetsOfType(const FString& AssetType) const + { + TArray Assets; + for (const auto& Entry : StoredAssets) + { + const FCachedAssetData& CachedAsset = Entry.Value; + if (CachedAsset.Type == AssetType) + { + Assets.Add(CachedAsset); + } + } + return Assets; + } + + TArray GetAssetsExcludingTypes(const TArray& AssetExcludeTypes) const + { + TArray Assets; + for (const auto& Entry : StoredAssets) + { + const FCachedAssetData& CachedAsset = Entry.Value; + for (auto ExcludeTypes : AssetExcludeTypes) + { + if (CachedAsset.Type != ExcludeTypes) + { + Assets.Add(CachedAsset); + } + } + } + return Assets; + } + + void StoreAndTrackIcon(const FAssetLoadingContext& Context, const bool bSaveManifest = true) + { + const FCachedAssetData& StoredAsset = FCachedAssetData(Context.Asset); + FFileUtility::SaveToFile(Context.Data, FFileUtility::GetFullPersistentPath(StoredAsset.RelativeIconFilePath)); + + StoreAndTrackAsset(StoredAsset, bSaveManifest); + } + + void StoreAndTrackGlb(const FAssetLoadingContext& Context, const bool bSaveManifest = true) + { + FCachedAssetData StoredAsset = FCachedAssetData(Context.Asset, Context.BaseModelId); + const FString& GlbPath = StoredAsset.GetGlbPathForBaseModelId(Context.BaseModelId); + FFileUtility::SaveToFile(Context.Data, GlbPath); + + StoreAndTrackAsset(StoredAsset, bSaveManifest); + } + + void UpdateExistingCachedAsset(const FCachedAssetData& StoredAsset, FCachedAssetData* ExistingStoredAsset) + { + if(!StoredAsset.RelativeGlbPathsByBaseModelId.IsEmpty()) + { + MergeTMaps(ExistingStoredAsset->RelativeGlbPathsByBaseModelId, StoredAsset.RelativeGlbPathsByBaseModelId); + } + if(ExistingStoredAsset->RelativeIconFilePath.IsEmpty() && !StoredAsset.RelativeIconFilePath.IsEmpty()) + { + ExistingStoredAsset->RelativeIconFilePath = StoredAsset.RelativeIconFilePath; + } + } + + void StoreAndTrackAsset(const FAsset& Asset, const FString& baseModelId = TEXT(""), const bool bSaveManifest = true) + { + FCachedAssetData NewCachedAsset = FCachedAssetData(Asset, baseModelId); + StoreAndTrackAsset(NewCachedAsset, bSaveManifest); + } + + void StoreAndTrackAsset(const FCachedAssetData& StoredAsset, const bool bSaveManifest = true) + { + FCachedAssetData* ExistingStoredAsset = StoredAssets.Find(StoredAsset.Id); + if(ExistingStoredAsset != nullptr) + { + UpdateExistingCachedAsset(StoredAsset, ExistingStoredAsset); + } + StoredAssets.Add(StoredAsset.Id, ExistingStoredAsset ? *ExistingStoredAsset : StoredAsset); + + if(bSaveManifest) + { + SaveManifest(); + } + } + + void LoadManifest() + { + const FString GlobalCachePath = FFileUtility::GetCachePath(); + const FString ManifestFilePath = GlobalCachePath / TEXT("AssetManifest.json"); + FString ManifestContent; + + if (FFileHelper::LoadFileToString(ManifestContent, *ManifestFilePath)) + { + TSharedPtr ManifestJson; + TSharedRef> Reader = TJsonReaderFactory<>::Create(ManifestContent); + + if (FJsonSerializer::Deserialize(Reader, ManifestJson) && ManifestJson.IsValid()) + { + const TSharedPtr TrackedAssetsJson = ManifestJson->GetObjectField(TEXT("TrackedAssets")); + for (const auto& Entry : TrackedAssetsJson->Values) + { + const FString& AssetId = Entry.Key; + const TSharedPtr AssetData = Entry.Value->AsObject(); + FCachedAssetData StoredAsset = FCachedAssetData::FromJson(AssetData); + if(StoredAsset.IsValid()) + { + StoredAssets.Add(AssetId, StoredAsset); + } + } + } + } + } + + void SaveManifest() + { + const FString GlobalCachePath = FFileUtility::GetCachePath(); + const FString ManifestFilePath = GlobalCachePath / TEXT("AssetManifest.json"); + + TSharedPtr ManifestJson = MakeShared(); + TSharedPtr TrackedAssetsJson = MakeShared(); + + for (const auto& Entry : StoredAssets) + { + TSharedPtr AssetJson = Entry.Value.ToJson(); + TrackedAssetsJson->SetObjectField(Entry.Key, AssetJson); + } + + ManifestJson->SetObjectField(TEXT("TrackedAssets"), TrackedAssetsJson); + + FString OutputString; + TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); + FJsonSerializer::Serialize(ManifestJson.ToSharedRef(), Writer); + + FFileHelper::SaveStringToFile(OutputString, *ManifestFilePath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + } + + void ClearAllCache() + { + const FString GlobalCachePath = FFileUtility::GetCachePath(); + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (PlatformFile.DirectoryExists(*GlobalCachePath)) + { + PlatformFile.DeleteDirectoryRecursively(*GlobalCachePath); + PlatformFile.CreateDirectory(*GlobalCachePath); + } + + StoredAssets.Empty(); + SaveManifest(); + } + + void RemoveAssetFromCache(const FString& AssetId) + { + if (!StoredAssets.Contains(AssetId)) + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("No Asset with ID %s found in the cache."), *AssetId); + return; + } + + FCachedAssetData CachedAsset = StoredAssets[AssetId]; + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + + for (const auto& GlbPath : CachedAsset.RelativeGlbPathsByBaseModelId) + { + const FString FullGlbPath = FFileUtility::GetFullPersistentPath(GlbPath.Value); + if (PlatformFile.FileExists(*FullGlbPath)) + { + PlatformFile.DeleteFile(*FullGlbPath); + } + } + const FString FullIconPath = FFileUtility::GetFullPersistentPath(CachedAsset.RelativeIconFilePath); + if (PlatformFile.FileExists(*FullIconPath)) + { + PlatformFile.DeleteFile(*FullIconPath); + } + + StoredAssets.Remove(AssetId); + SaveManifest(); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Asset %s of type %s removed from cache"), *AssetId, *CachedAsset.Type); + } + + const TMap& GetStoredAssets() const + { + return StoredAssets; + } + + bool IsAssetCached(const FString& AssetId) const + { + return StoredAssets.Contains(AssetId); + } + + bool GetCachedAsset(const FString& AssetId, FCachedAssetData& OutAsset) const + { + const FCachedAssetData* StoredAsset = StoredAssets.Find(AssetId); + if(StoredAsset != nullptr && StoredAsset->IsValid()) + { + OutAsset = *StoredAsset; + return true; + } + return false; + } + +private: + TMap StoredAssets; + + FAssetCacheManager() + { + LoadManifest(); + } + + template + void MergeTMaps(TMap& DestinationMap, const TMap& SourceMap) + { + for (const TPair& Elem : SourceMap) + { + // Add only if the key doesn't already exist in the destination map + if (!DestinationMap.Contains(Elem.Key)) + { + DestinationMap.Add(Elem.Key, Elem.Value); + } + } + } +}; diff --git a/Source/RpmNextGen/Public/Cache/CachedAssetData.h b/Source/RpmNextGen/Public/Cache/CachedAssetData.h new file mode 100644 index 0000000..733054b --- /dev/null +++ b/Source/RpmNextGen/Public/Cache/CachedAssetData.h @@ -0,0 +1,163 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Api/Assets/Models/Asset.h" +#include "Api/Files/FileUtility.h" +#include "CachedAssetData.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FCachedAssetData +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString Id; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString Name; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "glbUrl")) + FString GlbUrl; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "iconUrl")) + FString IconUrl; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + TMap RelativeGlbPathsByBaseModelId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString RelativeIconFilePath; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString Type; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "createdAt")) + FDateTime CreatedAt; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "updatedAt")) + FDateTime UpdatedAt; + + FCachedAssetData() + { + Id = FString(); + Name = FString(); + GlbUrl = FString(); + IconUrl = FString(); + RelativeGlbPathsByBaseModelId = TMap(); + RelativeIconFilePath = FString(); + Type = FString(); + CreatedAt = FDateTime(); + UpdatedAt = FDateTime(); + } + FCachedAssetData(const FAsset& InAsset) + { + Id = InAsset.Id; + Name = InAsset.Name; + GlbUrl = InAsset.GlbUrl; + IconUrl = InAsset.IconUrl; + RelativeGlbPathsByBaseModelId = TMap(); + RelativeIconFilePath = FString::Printf(TEXT("%s/Icons/%s.png"), *FFileUtility::RelativeCachePath, *Id); + Type = InAsset.Type; + CreatedAt = InAsset.CreatedAt; + UpdatedAt = InAsset.UpdatedAt; + } + + FCachedAssetData(const FAsset& InAsset, const FString& InBaseModelId) + { + Id = InAsset.Id; + Name = InAsset.Name; + GlbUrl = InAsset.GlbUrl; + IconUrl = InAsset.IconUrl; + RelativeGlbPathsByBaseModelId = TMap(); + if(InBaseModelId != FString()) + { + RelativeGlbPathsByBaseModelId.Add(InBaseModelId, FString::Printf(TEXT("%s/%s/%s.glb"), *FFileUtility::RelativeCachePath, *InBaseModelId, *Id)); + } + RelativeIconFilePath = FString::Printf(TEXT("%s/Icons/%s.png"), *FFileUtility::RelativeCachePath, *Id); + Type = InAsset.Type; + CreatedAt = InAsset.CreatedAt; + UpdatedAt = InAsset.UpdatedAt; + } + + bool IsValid () const + { + bool Valid = true; + if(RelativeGlbPathsByBaseModelId.Num() == 0) + { + Valid = false; + } + if (RelativeIconFilePath.IsEmpty()) + { + Valid = false; + } + return Valid; + } + + FAsset ToAsset() const + { + FAsset Asset; + Asset.Id = Id; + Asset.Name = Name; + Asset.GlbUrl = GlbUrl; + Asset.IconUrl = IconUrl; + Asset.Type = Type; + Asset.CreatedAt = CreatedAt; + Asset.UpdatedAt = UpdatedAt; + return Asset; + } + + TSharedPtr ToJson() const + { + TSharedPtr JsonObject = MakeShared(); + + JsonObject->SetStringField(TEXT("Id"), Id); + JsonObject->SetStringField(TEXT("Name"), Name); + JsonObject->SetStringField(TEXT("GlbUrl"), GlbUrl); + JsonObject->SetStringField(TEXT("IconUrl"), IconUrl); + JsonObject->SetStringField(TEXT("IconFilePath"), RelativeIconFilePath); + JsonObject->SetStringField(TEXT("Type"), Type); + JsonObject->SetStringField(TEXT("CreatedAt"), CreatedAt.ToString()); + JsonObject->SetStringField(TEXT("UpdatedAt"), UpdatedAt.ToString()); + + TSharedPtr GlbPathsObject = MakeShared(); + for (const auto& Entry : RelativeGlbPathsByBaseModelId) + { + GlbPathsObject->SetStringField(Entry.Key, Entry.Value); + } + JsonObject->SetObjectField(TEXT("GlbPathsByBaseModelId"), GlbPathsObject); + + return JsonObject; + } + + static FCachedAssetData FromJson(const TSharedPtr& JsonObject) + { + FCachedAssetData StoredAsset; + + StoredAsset.Id = JsonObject->GetStringField(TEXT("Id")); + StoredAsset.Name = JsonObject->GetStringField(TEXT("Name")); + StoredAsset.GlbUrl = JsonObject->GetStringField(TEXT("GlbUrl")); + StoredAsset.IconUrl = JsonObject->GetStringField(TEXT("IconUrl")); + StoredAsset.RelativeIconFilePath = JsonObject->GetStringField(TEXT("IconFilePath")); + StoredAsset.Type = JsonObject->GetStringField(TEXT("Type")); + FDateTime::Parse(JsonObject->GetStringField(TEXT("CreatedAt")), StoredAsset.CreatedAt); + FDateTime::Parse(JsonObject->GetStringField(TEXT("UpdatedAt")), StoredAsset.UpdatedAt); + + TSharedPtr GlbPathsObject = JsonObject->GetObjectField(TEXT("GlbPathsByBaseModelId")); + for (const auto& Entry : GlbPathsObject->Values) + { + StoredAsset.RelativeGlbPathsByBaseModelId.Add(Entry.Key, Entry.Value->AsString()); + } + + return StoredAsset; + } + + FString GetGlbPathForBaseModelId(FString BaseModelId) + { + if(RelativeGlbPathsByBaseModelId.Num() > 0 && !BaseModelId.IsEmpty()) + { + return FFileUtility::GetFullPersistentPath(RelativeGlbPathsByBaseModelId[BaseModelId]); + } + + return ""; + } +}; diff --git a/Source/RpmNextGen/Public/RpmActor.h b/Source/RpmNextGen/Public/RpmActor.h index 5bd9bb0..64abd6d 100644 --- a/Source/RpmNextGen/Public/RpmActor.h +++ b/Source/RpmNextGen/Public/RpmActor.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "glTFRuntimeAsset.h" +#include "RpmCharacterTypes.h" +#include "RpmLoaderComponent.h" #include "RpmActor.generated.h" UCLASS() @@ -13,71 +15,57 @@ class RPMNEXTGEN_API ARpmActor : public AActor GENERATED_BODY() public: - // Sets default values for this actor's properties ARpmActor(); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me") + FRpmAnimationConfig AnimationConfig; -protected: - // Called when the game starts or when spawned - virtual void BeginPlay() override; - - virtual void ProcessNode(USceneComponent* NodeParentComponent, const FName SocketName, FglTFRuntimeNode& Node); - - template - FName GetSafeNodeName(const FglTFRuntimeNode& Node) - { - return MakeUniqueObjectName(this, T::StaticClass(), *Node.Name); - } - - UPROPERTY() - TMap SocketMapping; - UPROPERTY() - TArray DiscoveredSkeletalMeshComponents; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me") + TMap AnimationConfigsByBaseModelId; -public: - // Called every frame - virtual void Tick(float DeltaTime) override; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") - UglTFRuntimeAsset* Asset; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|Glb Import Settings") FglTFRuntimeStaticMeshConfig StaticMeshConfig; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|Glb Import Settings") FglTFRuntimeSkeletalMeshConfig SkeletalMeshConfig; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") - FglTFRuntimeSkeletalAnimationConfig SkeletalAnimationConfig; - - UFUNCTION(BlueprintNativeEvent, Category = "Ready Player Me|glTFRuntime", meta = (DisplayName = "On StaticMeshComponent Created")) - void ReceiveOnStaticMeshComponentCreated(UStaticMeshComponent* StaticMeshComponent, const FglTFRuntimeNode& Node); - - UFUNCTION(BlueprintNativeEvent, Category = "Ready Player Me|glTFRuntime", meta = (DisplayName = "On SkeletalMeshComponent Created")) - void ReceiveOnSkeletalMeshComponentCreated(USkeletalMeshComponent* SkeletalMeshComponent, const FglTFRuntimeNode& Node); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") - int32 RootNodeIndex; + FRpmCharacterData CharacterData; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime") - bool bStaticMeshesAsSkeletalOnMorphTargets; - - DECLARE_MULTICAST_DELEGATE_TwoParams(FglTFRuntimeAssetActorNodeProcessed, const FglTFRuntimeNode&, USceneComponent*); - FglTFRuntimeAssetActorNodeProcessed OnNodeProcessed; + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadCharacter(const FRpmCharacterData& InCharacterData, UglTFRuntimeAsset* GltfAsset); - virtual void PostUnregisterAllComponents() override; + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadAsset(const FAsset& Asset, UglTFRuntimeAsset* GltfAsset ); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadGltfAssetWithSkeleton(UglTFRuntimeAsset* GltfAsset, const FAsset& Asset, const FRpmAnimationConfig& InAnimationCharacter); UFUNCTION(BlueprintCallable, Category = "Ready Player Me") - virtual void LoadGltfAsset(UglTFRuntimeAsset* GltfAsset); - void ClearLoadedComponents(); + void RemoveAllMeshes(); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + void RemoveMeshComponentsOfType(const FString& AssetType); + + virtual void Tick(float DeltaTime) override; + +protected: + TWeakObjectPtr MasterPoseComponent; + + virtual void BeginPlay() override; - virtual void SetupAsset(); + template + FName GetSafeNodeName(const FglTFRuntimeNode& Node) + { + return MakeUniqueObjectName(this, T::StaticClass(), *Node.Name); + } private: - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category="Ready Player Me|glTFRuntime") + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category="Ready Player Me") USceneComponent* AssetRoot; - void ProcessBoneNode(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node); - USceneComponent* CreateNewComponent(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node); - void SetupComponentTags(USceneComponent* Component, FglTFRuntimeNode& Node, const FName SocketName); - void ProcessChildNodes(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node); + TMap> LoadedMeshComponentsByAssetType; + + TArray LoadMeshComponents(UglTFRuntimeAsset* GltfAsset, const FString& AssetType); + USkeletalMeshComponent* CreateSkeletalMeshComponent(UglTFRuntimeAsset* GltfAsset, const FglTFRuntimeNode& Node); + UStaticMeshComponent* CreateStaticMeshComponent(UglTFRuntimeAsset* GltfAsset, const FglTFRuntimeNode& Node); + }; diff --git a/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h b/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h deleted file mode 100644 index 2c4b1f5..0000000 --- a/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h +++ /dev/null @@ -1,39 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Components/ActorComponent.h" -#include "RpmAssetLoaderComponent.generated.h" - -class UglTFRuntimeAsset; -class FAssetLoader; -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGltfAssetLoaded, UglTFRuntimeAsset*, Asset); - -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -class RPMNEXTGEN_API URpmAssetLoaderComponent : public UActorComponent -{ - GENERATED_BODY() - -public: - // Sets default values for this component's properties - URpmAssetLoaderComponent(); - - virtual void LoadCharacterFromUrl(FString Url); - - - - UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) - FOnGltfAssetLoaded OnGltfAssetLoaded; - -protected: - // Called when the game starts - virtual void BeginPlay() override; - - virtual void HandleGLtfAssetLoaded(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful); -public: - // Called every frame - virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; -private: - TSharedPtr AssetLoader; -}; diff --git a/Source/RpmNextGen/Public/RpmCharacterTypes.h b/Source/RpmNextGen/Public/RpmCharacterTypes.h new file mode 100644 index 0000000..fd4702f --- /dev/null +++ b/Source/RpmNextGen/Public/RpmCharacterTypes.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Api/Assets/Models/Asset.h" +#include "Components/ActorComponent.h" +#include "RpmCharacterTypes.generated.h" + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FRpmAnimationConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me" ) + USkeleton* Skeleton; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + TSubclassOf AnimationBlueprint; + + FRpmAnimationConfig() + { + Skeleton = nullptr; + AnimationBlueprint = nullptr; + } +}; + +USTRUCT(BlueprintType) +struct RPMNEXTGEN_API FRpmCharacterData +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString Id; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + FString BaseModelId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "assets")) + TMap Assets; + + FRpmCharacterData() + { + Id = ""; + BaseModelId = ""; + Assets = TMap(); + } +}; diff --git a/Source/RpmNextGen/Public/RpmFunctionLibrary.h b/Source/RpmNextGen/Public/RpmFunctionLibrary.h index e0ffbdc..36508e3 100644 --- a/Source/RpmNextGen/Public/RpmFunctionLibrary.h +++ b/Source/RpmNextGen/Public/RpmFunctionLibrary.h @@ -19,4 +19,7 @@ class RPMNEXTGEN_API URpmFunctionLibrary : public UBlueprintFunctionLibrary public: UFUNCTION(BlueprintCallable, Category = "ReadyPlayerMe", meta = (WorldContext = "WorldContextObject")) static void FetchFirstAssetId(UObject* WorldContextObject, const FString& AssetType, FOnAssetIdFetched OnAssetIdFetched); + + UFUNCTION(BlueprintCallable, Category = "ReadyPlayerMe/Cache") + static void ExtractCachePakFile(); }; diff --git a/Source/RpmNextGen/Public/RpmImageLoader.h b/Source/RpmNextGen/Public/RpmImageLoader.h deleted file mode 100644 index 43d4619..0000000 --- a/Source/RpmNextGen/Public/RpmImageLoader.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#pragma once - -#include "CoreMinimal.h" -#include "Interfaces/IHttpRequest.h" -#include "Engine/Texture2D.h" -#include "Components/Image.h" - -class RPMNEXTGEN_API FRpmImageLoader -{ -public: - FRpmImageLoader() = default; - void LoadUImageFromURL(UImage* Image, const FString& URL); - void LoadSImageFromURL(TSharedPtr ImageWidget, const FString& URL, TFunction OnImageUpdated); - -private: - void DownloadImage(const FString& URL, TFunction OnImageDownloaded); - void OnImageDownloadComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, TFunction OnImageDownloaded); - UTexture2D* CreateTextureFromImageData(const TArray& ImageData); -}; diff --git a/Source/RpmNextGen/Public/RpmLoaderComponent.h b/Source/RpmNextGen/Public/RpmLoaderComponent.h new file mode 100644 index 0000000..7a3fac2 --- /dev/null +++ b/Source/RpmNextGen/Public/RpmLoaderComponent.h @@ -0,0 +1,88 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "glTFRuntimeAsset.h" +#include "Api/Assets/Models/Asset.h" +#include "Api/Characters/Models/RpmCharacter.h" +#include "Components/ActorComponent.h" +#include "RpmCharacterTypes.h" +#include "RpmLoaderComponent.generated.h" + +class FFileApi; +class FGlbLoader; +struct FCharacterCreateResponse; +struct FCharacterUpdateResponse; +struct FCharacterFindByIdResponse; +class FCharacterApi; +struct FAsset; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCharacterCreated, FRpmCharacterData, CharacterData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCharacterUpdated, FRpmCharacterData, CharacterData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCharacterFound, FRpmCharacterData, CharacterData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCharacterAssetLoaded, const FRpmCharacterData&, CharacterData, UglTFRuntimeAsset*, GltfRuntimeAsset); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnNewAssetLoaded, const FAsset&, Asset, UglTFRuntimeAsset*, GltfRuntimeAsset ); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class RPMNEXTGEN_API URpmLoaderComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) + FOnCharacterAssetLoaded OnCharacterAssetLoaded; + UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) + FOnNewAssetLoaded OnNewAssetLoaded; + UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) + FOnCharacterCreated OnCharacterCreated; + UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) + FOnCharacterUpdated OnCharacterUpdated; + UPROPERTY(BlueprintAssignable, Category = "Ready Player Me" ) + FOnCharacterFound OnCharacterFound; + + URpmLoaderComponent(); + + void SetGltfConfig(const FglTFRuntimeConfig* Config); + + void HandleAssetLoaded(const TArray* Data, const FAsset& Asset); + void HandleCharacterAssetLoaded(const TArray* Array, const FString& FileName); + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + +protected: + FglTFRuntimeConfig GltfConfig; + FString AppId; + FRpmCharacter Character; + FRpmCharacterData CharacterData; + + virtual void BeginPlay() override; + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void CreateCharacter(const FString& BaseModelId); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadCharacterFromUrl(FString Url); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + void LoadGltfRuntimeAssetFromCache(const FAsset& Asset); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadCharacterAssetsFromCache(TMap AssetMap); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void LoadAssetPreview(FAsset AssetData, bool bUseCache); + + UFUNCTION() + virtual void HandleCharacterCreateResponse(FCharacterCreateResponse CharacterCreateResponse, bool bWasSuccessful); + UFUNCTION() + virtual void HandleCharacterUpdateResponse(FCharacterUpdateResponse CharacterUpdateResponse, bool bWasSuccessful); + UFUNCTION() + virtual void HandleCharacterFindResponse(FCharacterFindByIdResponse CharacterFindByIdResponse, bool bWasSuccessful); + +private: + TSharedPtr CharacterApi; + TSharedPtr FileApi; + + void LoadAssetsFromCacheWithNewStyle(); +}; diff --git a/Source/RpmNextGen/Public/RpmNextGen.h b/Source/RpmNextGen/Public/RpmNextGen.h index 48fa711..6be9f9e 100644 --- a/Source/RpmNextGen/Public/RpmNextGen.h +++ b/Source/RpmNextGen/Public/RpmNextGen.h @@ -7,7 +7,7 @@ RPMNEXTGEN_API DECLARE_LOG_CATEGORY_EXTERN(LogReadyPlayerMe, Log, All); -class FRpmNextGenModule : public IModuleInterface +class RPMNEXTGEN_API FRpmNextGenModule : public IModuleInterface { public: diff --git a/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h b/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h deleted file mode 100644 index e52e9ec..0000000 --- a/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h +++ /dev/null @@ -1,49 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "RpmAssetLoaderComponent.h" -#include "Api/Characters/Models/RpmCharacter.h" -#include "RpmPreviewLoaderComponent.generated.h" - -class FCharacterApi; -struct FCharacterCreateResponse; -struct FCharacterUpdateResponse; -struct FCharacterFindByIdResponse; -struct FAsset; - -/** - * - */ -UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) -class RPMNEXTGEN_API URpmPreviewLoaderComponent : public URpmAssetLoaderComponent -{ - GENERATED_BODY() - -public: - URpmPreviewLoaderComponent(); - - UFUNCTION(BlueprintCallable, Category = "Ready Player Me") - virtual void CreateCharacter(const FString& BaseModelId); - - UFUNCTION(BlueprintCallable, Category = "Ready Player Me") - virtual void LoadCharacter(FRpmCharacter CharacterData); - - UFUNCTION(BlueprintCallable, Category = "Ready Player Me") - virtual void LoadAssetPreview(FAsset AssetData); - - UFUNCTION() - virtual void HandleCharacterCreateResponse(FCharacterCreateResponse CharacterCreateResponse, bool bWasSuccessful); - UFUNCTION() - virtual void HandleCharacterUpdateResponse(FCharacterUpdateResponse CharacterUpdateResponse, bool bWasSuccessful); - UFUNCTION() - virtual void HandleCharacterFindResponse(FCharacterFindByIdResponse CharacterFindByIdResponse, bool bWasSuccessful); - -protected: - FString AppId; - FRpmCharacter Character; - TMap PreviewAssetMap; -private: - TSharedPtr CharacterApi; -}; diff --git a/Source/RpmNextGen/Public/RpmTextureLoader.h b/Source/RpmNextGen/Public/RpmTextureLoader.h new file mode 100644 index 0000000..9a7baa9 --- /dev/null +++ b/Source/RpmNextGen/Public/RpmTextureLoader.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Api/Assets/Models/Asset.h" +#include "Engine/Texture2D.h" + +class FAssetIconLoader; + +class RPMNEXTGEN_API FRpmTextureLoader : public TSharedFromThis +{ +public: + + FRpmTextureLoader(); + DECLARE_DELEGATE_OneParam(FOnTextureLoaded, UTexture2D*); + + void LoadIconFromAsset(const FAsset& Asset, bool bStoreInCache = false); + UFUNCTION() + void OnIconLoaded(const FAsset& Asset, const TArray& Array); + + FOnTextureLoaded OnTextureLoaded; +private: + TSharedPtr AssetIconLoader; +}; diff --git a/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h index 715f93e..6e98a3c 100644 --- a/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h +++ b/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h @@ -1,5 +1,4 @@ // Fill out your copyright notice in the Description page of Project Settings. - #pragma once #include "CoreMinimal.h" @@ -7,6 +6,7 @@ #include "Blueprint/UserWidget.h" #include "RpmAssetButtonWidget.generated.h" +class FRpmTextureLoader; class USizeBox; class UBorder; class UImage; @@ -23,7 +23,6 @@ class RPMNEXTGEN_API URpmAssetButtonWidget : public UUserWidget GENERATED_BODY() public: - UPROPERTY(meta = (BindWidget)) UButton* AssetButton; @@ -43,16 +42,19 @@ class RPMNEXTGEN_API URpmAssetButtonWidget : public UUserWidget virtual void SetSelected(const bool bInIsSelected); FAsset GetAssetData() const { return AssetData; } + protected: + UFUNCTION() + void OnTextureLoaded(UTexture2D* Texture2D); + virtual void NativeConstruct() override; private: + TSharedPtr TextureLoader; FLinearColor DefaultColor; - FAsset AssetData; - + bool bIsSelected; + UFUNCTION() virtual void HandleButtonClicked(); - - bool bIsSelected; }; diff --git a/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h index 5d9c3d2..29e026d 100644 --- a/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h +++ b/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h @@ -7,6 +7,7 @@ #include "Blueprint/UserWidget.h" #include "RpmAssetCardWidget.generated.h" +class FRpmTextureLoader; class UImage; class UTextBlock; @@ -16,14 +17,6 @@ class RPMNEXTGEN_API URpmAssetCardWidget : public UUserWidget GENERATED_BODY() public: - virtual void NativeConstruct() override; - - UFUNCTION(BlueprintCallable, Category = "Asset Card") - virtual void InitializeCard(const FAsset& Asset); - - UFUNCTION(BlueprintCallable, Category = "Asset Card") - void LoadImage(const FString& URL); - UPROPERTY(meta = (BindWidget)) UTextBlock* AssetCategoryText; @@ -36,6 +29,18 @@ class RPMNEXTGEN_API URpmAssetCardWidget : public UUserWidget UPROPERTY(meta = (BindWidget)) UTextBlock* AssetIdText; + UFUNCTION() + void OnTextureLoaded(UTexture2D* Texture2D); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + virtual void InitializeCard(const FAsset& Asset); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + void LoadImage(const FAsset& Asset); + + virtual void NativeConstruct() override; + private: FAsset AssetData; + TSharedPtr TextureLoader; }; diff --git a/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h index 0b50b3a..d5a45bb 100644 --- a/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h +++ b/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h @@ -12,6 +12,7 @@ struct FAsset; class URpmAssetButtonWidget; DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAssetSelected, const FAsset&, AssetData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPaginationUpdated, const FPagination&, Pagination); /** * @@ -21,13 +22,11 @@ class RPMNEXTGEN_API URpmAssetPanelWidget : public UUserWidget { GENERATED_BODY() public: - virtual void NativeConstruct() override; + UPROPERTY(meta = (BindWidget)) + UPanelWidget* ButtonContainer; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Panel" ) TSubclassOf AssetButtonBlueprint; - - UPROPERTY(meta = (BindWidget)) - UPanelWidget* ButtonContainer; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Panel") URpmAssetButtonWidget* SelectedAssetButton; @@ -38,9 +37,15 @@ class RPMNEXTGEN_API URpmAssetPanelWidget : public UUserWidget UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Button" ) FVector2D ImageSize; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Button" ) + int32 PaginationLimit = 50; + UPROPERTY(BlueprintAssignable, Category = "Events" ) FOnAssetSelected OnAssetSelected; + UPROPERTY(BlueprintAssignable, Category = "Events" ) + FPaginationUpdated OnPaginationUpdated; + UFUNCTION(BlueprintCallable, Category = "Asset Panel") void CreateButtonsFromAssets(TArray Assets); @@ -56,11 +61,25 @@ class RPMNEXTGEN_API URpmAssetPanelWidget : public UUserWidget UFUNCTION() void OnAssetListResponse(const FAssetListResponse& AssetListResponse, bool bWasSuccessful); + UFUNCTION(BlueprintCallable, Category = "Asset Panel") + void LoadAssetsFromCache(const FString& AssetType); + UFUNCTION(BlueprintCallable, Category = "Asset Panel") void LoadAssetsOfType(const FString& AssetType); + UFUNCTION(BlueprintCallable, Category = "Asset Panel") + virtual void LoadNextPage(); + UFUNCTION(BlueprintCallable, Category = "Asset Panel") + virtual void LoadPreviousPage(); + void CreateButton(const FAsset& AssetData); + + virtual void SynchronizeProperties() override; + virtual void NativeConstruct() override; + private: - TArray> AssetButtons; + FPagination Pagination; + FString CurrentAssetType; + TMap> AssetButtonMap; TSharedPtr AssetApi; }; diff --git a/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h b/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h index 3e22e0f..5d788ac 100644 --- a/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h +++ b/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h @@ -21,7 +21,6 @@ class RPMNEXTGEN_API URpmCategoryButtonWidget : public UUserWidget GENERATED_BODY() public: - virtual void NativeConstruct() override; UPROPERTY(meta = (BindWidget)) UImage* CategoryImage; @@ -39,7 +38,7 @@ class RPMNEXTGEN_API URpmCategoryButtonWidget : public UUserWidget UTexture2D* CategoryImageTexture; UFUNCTION(BlueprintCallable, Category = "Category Button") - virtual void InitializeButton(FString Category, UTexture2D* Image); + virtual void InitializeButton(FString Category, UTexture2D* Image, const FVector2D& InImageSize); UFUNCTION(BlueprintCallable, Category = "Category Button") virtual void SetSelected(bool bIsSelected); @@ -52,10 +51,11 @@ class RPMNEXTGEN_API URpmCategoryButtonWidget : public UUserWidget #endif virtual void SynchronizeProperties() override; + virtual void NativeConstruct() override; private: + FLinearColor DefaultColor; + UFUNCTION() virtual void HandleButtonClicked(); - - FLinearColor DefaultColor; }; diff --git a/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h b/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h index feae846..4bbe563 100644 --- a/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h +++ b/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h @@ -3,13 +3,19 @@ #pragma once #include "CoreMinimal.h" +#include "Api/Assets/Models/AssetTypeListResponse.h" +#include "Api/Common/Models/ApiRequest.h" #include "Blueprint/UserWidget.h" #include "RpmCategoryPanelWidget.generated.h" +class IHttpRequest; +class IHttpResponse; +class URpmAssetButtonWidget; +class FAssetApi; class URpmCategoryButtonWidget; DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCategorySelected, const FString&, CategoryName); - +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCategoriesLoaded, const TArray, CategoryNames); /** * */ @@ -19,20 +25,40 @@ class RPMNEXTGEN_API URpmCategoryPanelWidget : public UUserWidget GENERATED_BODY() public: - virtual void NativeConstruct() override; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Panel") + TSubclassOf CategoryButtonBlueprint; + + UPROPERTY(meta = (BindWidget)) + UPanelWidget* ButtonContainer; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Panel", meta = (ExposeOnSpawn = "true") ) + FVector2D ButtonSize; + + UPROPERTY() URpmCategoryButtonWidget* SelectedCategoryButton; UPROPERTY(BlueprintAssignable, Category = "Events") FOnCategorySelected OnCategorySelected; + + UPROPERTY(BlueprintAssignable, Category = "Events" ) + FOnCategoriesLoaded OnCategoriesLoaded; UFUNCTION(BlueprintCallable, Category = "Category Panel") virtual void UpdateSelectedButton(URpmCategoryButtonWidget* CategoryButton); + + UFUNCTION(BlueprintCallable, Category = "Category Panel") + void LoadAndCreateButtons(); UFUNCTION() virtual void OnCategoryButtonClicked(URpmCategoryButtonWidget* CategoryButton); - + + virtual void CreateButton(const FString& AssetType); + virtual void SynchronizeProperties() override; + virtual void NativeConstruct() override; + private: - void InitializeCategoryButtons(); + TArray> AssetButtons; + TSharedPtr AssetApi; + bool bIsInitialized = false; + void AssetTypesLoaded(const FAssetTypeListResponse& AssetTypeListResponse, bool bWasSuccessful); }; diff --git a/Source/RpmNextGen/Public/Samples/RpmCreatorWidget.h b/Source/RpmNextGen/Public/Samples/RpmCreatorWidget.h new file mode 100644 index 0000000..4040a1c --- /dev/null +++ b/Source/RpmNextGen/Public/Samples/RpmCreatorWidget.h @@ -0,0 +1,50 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "RpmAssetPanelWidget.h" +#include "Blueprint/UserWidget.h" +#include "RpmCreatorWidget.generated.h" + +class UWidgetSwitcher; + +/** + * + */ +UCLASS() +class RPMNEXTGEN_API URpmCreatorWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + void CreateAssetPanelsFromCategories(const TArray& CategoryArray); + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me") + void SwitchToPanel(const FString& Category); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me" ) + int32 PaginationLimit = 50; + + virtual void NativeConstruct() override; + +protected: + UPROPERTY(meta = (BindWidget)) + UWidgetSwitcher* AssetPanelSwitcher; + + UPROPERTY(BlueprintAssignable, Category = "Events" ) + FOnAssetSelected OnAssetSelected; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me") + TSubclassOf AssetPanelBlueprint; + TMap IndexMapByCategory; + + UFUNCTION() + void HandleAssetSelectedFromPanel(const FAsset& AssetData); + + virtual void SynchronizeProperties() override; + +private: + UUserWidget* CreateAssetPanel(const FString& Category); +}; diff --git a/Source/RpmNextGen/Public/Samples/RpmPaginatorWidget.h b/Source/RpmNextGen/Public/Samples/RpmPaginatorWidget.h new file mode 100644 index 0000000..55b2288 --- /dev/null +++ b/Source/RpmNextGen/Public/Samples/RpmPaginatorWidget.h @@ -0,0 +1,55 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Api/Common/Models/Pagination.h" +#include "Blueprint/UserWidget.h" +#include "RpmPaginatorWidget.generated.h" + +class UTextBlock; +class UButton; +struct FPagination; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNextButtonClicked); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPreviousButtonClicked); + +/** + * + */ +UCLASS() +class RPMNEXTGEN_API URpmPaginatorWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable, Category = "Events" ) + FOnNextButtonClicked OnNextButtonEvent; + + UPROPERTY( BlueprintAssignable, Category = "Events" ) + FOnPreviousButtonClicked OnPreviousButtonEvent; + + UFUNCTION(BlueprintCallable, Category = "Ready Player Me" ) + void UpdateState(const FPagination& Pagination); + + UFUNCTION() + void OnPrevButtonClicked(); + UFUNCTION() + void OnNextButtonClicked(); + +protected: + + virtual void NativeConstruct() override; + +private: + UPROPERTY(meta = (BindWidget)) + UButton* PreviousButton; + + UPROPERTY(meta = (BindWidget)) + UButton* NextButton; + + UPROPERTY(meta = (BindWidget)) + UTextBlock* PageText; + + FText GetPageCountText(const FPagination& Pagination); +}; diff --git a/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h b/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h index ecaeeaf..371fa99 100644 --- a/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h +++ b/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h @@ -18,7 +18,6 @@ class RPMNEXTGEN_API URpmDeveloperSettings : public UDeveloperSettings FString ApiBaseUrl; public: - URpmDeveloperSettings(); UPROPERTY(VisibleAnywhere, Config, Category = "Auth Settings", meta = (ReadOnly = "true", ToolTip = "Base URL for authentication requests.")) FString ApiBaseAuthUrl; @@ -31,9 +30,12 @@ class RPMNEXTGEN_API URpmDeveloperSettings : public UDeveloperSettings UPROPERTY(EditAnywhere, Config, Category = "Auth Settings", meta = (ToolTip = "Proxy URL for API requests. If empty, the base URL will be used.")) FString ApiProxyUrl; - + + URpmDeveloperSettings(); + void SetupDemoAccount(); void Reset(); + FString GetApiBaseUrl() const; bool IsValid() const @@ -46,7 +48,8 @@ class RPMNEXTGEN_API URpmDeveloperSettings : public UDeveloperSettings private: const FString DemoAppId = TEXT("665e05a50c62c921e5a6ab84"); const FString DemoProxyUrl = TEXT("https://api.readyplayer.me/demo"); + #if WITH_EDITOR virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; -#endif // WITH_EDITOR +#endif }; diff --git a/Source/RpmNextGen/Public/Utilities/RpmImageHelper.h b/Source/RpmNextGen/Public/Utilities/RpmImageHelper.h new file mode 100644 index 0000000..ad9a572 --- /dev/null +++ b/Source/RpmNextGen/Public/Utilities/RpmImageHelper.h @@ -0,0 +1,14 @@ +#pragma once + +class UImage; + +class RPMNEXTGEN_API FRpmImageHelper +{ +public: + static UTexture2D* CreateTextureFromData(const TArray& ImageData); + static UImage* CreateUImageFromData(const TArray& ImageData, const FVector2D& ImageSize); + static void LoadDataToUImage(const TArray& ImageData, UImage*& Image); + static void LoadTextureToUImage(UTexture2D* Texture, const FVector2D& ImageSize, UImage*& Image); + static void LoadDataToSImage(const TArray& ImageData, const FVector2D& ImageSize, TSharedPtr ImageWidget); + static void LoadTextureToSImage(UTexture2D* Texture, const FVector2D& ImageSize, TSharedPtr ImageWidget); +}; diff --git a/Source/RpmNextGen/RpmNextGen.Build.cs b/Source/RpmNextGen/RpmNextGen.Build.cs index 4ad0e8a..a0f48fe 100644 --- a/Source/RpmNextGen/RpmNextGen.Build.cs +++ b/Source/RpmNextGen/RpmNextGen.Build.cs @@ -30,6 +30,8 @@ public RpmNextGen(ReadOnlyTargetRules Target) : base(Target) "DeveloperSettings", "Slate", "SlateCore", + "PakFile", + "StreamingFile" } ); diff --git a/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp b/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp index bbb8e00..350a704 100644 --- a/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp +++ b/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp @@ -10,15 +10,15 @@ UAssetNameGenerator::UAssetNameGenerator() void UAssetNameGenerator::SetPath(FString NewPath) { - this->path = NewPath; + this->Path = NewPath; } FString UAssetNameGenerator::GenerateMaterialName(UMaterialInterface* Material, const int32 MaterialIndex, const FString& SlotName) const { - return FString::Printf(TEXT("%sMaterial_%d"), *path, MaterialIndex); + return FString::Printf(TEXT("%sMaterial_%d"), *Path, MaterialIndex); } FString UAssetNameGenerator::GenerateTextureName(UTexture* Texture, UMaterialInterface* Material, const FString& MaterialPath, const FString& ParamName) const { - return FString::Printf(TEXT("%s%s%s"), *path, *Material->GetName(), *ParamName); + return FString::Printf(TEXT("%s%s%s"), *Path, *Material->GetName(), *ParamName); } diff --git a/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp b/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp index d30fba9..51ec296 100644 --- a/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp +++ b/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp @@ -17,7 +17,7 @@ void FDevAuthTokenCache::Initialize() if (!AuthData.IsValid()) { - UE_LOG(LogTemp, Warning, TEXT("DevAuthTokenCache: Invalid AuthData %s"), *AuthData.ToJsonString()); + UE_LOG(LogReadyPlayerMe, Warning, TEXT("DevAuthTokenCache: Invalid AuthData %s"), *AuthData.ToJsonString()); ClearAuthData(); } diff --git a/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp b/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp index 37d4bc6..730255a 100644 --- a/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp +++ b/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp @@ -1,32 +1,39 @@ #include "Auth/DeveloperAuthApi.h" #include "Auth/Models/DeveloperLoginRequest.h" #include "Auth/Models/DeveloperLoginResponse.h" +#include "Interfaces/IHttpResponse.h" #include "Settings/RpmDeveloperSettings.h" FDeveloperAuthApi::FDeveloperAuthApi() { const URpmDeveloperSettings* RpmSettings = GetDefault(); ApiUrl = FString::Printf(TEXT("%s/login"), *RpmSettings->ApiBaseAuthUrl); - OnApiResponse.BindRaw(this, &FDeveloperAuthApi::HandleLoginResponse); + OnRequestComplete.BindRaw(this, &FDeveloperAuthApi::HandleLoginResponse); } -void FDeveloperAuthApi::HandleLoginResponse(FString JsonData, bool bIsSuccessful) const +void FDeveloperAuthApi::HandleLoginResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) const { - FDeveloperLoginResponse Response; - if (bIsSuccessful && !JsonData.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(JsonData, &Response, 0, 0)) + FDeveloperLoginResponse DevLoginResponse; + if(bWasSuccessful && Response.IsValid()) { - OnLoginResponse.ExecuteIfBound(Response, true); - return; + const FString Data = Response->GetContentAsString(); + if (!Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &DevLoginResponse, 0, 0)) + { + OnLoginResponse.ExecuteIfBound(DevLoginResponse, true); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to parse login response: %s"), *Data ); } - OnLoginResponse.ExecuteIfBound(Response, bIsSuccessful); + + OnLoginResponse.ExecuteIfBound(DevLoginResponse, bWasSuccessful); } void FDeveloperAuthApi::LoginWithEmail(FDeveloperLoginRequest Request) { - FApiRequest ApiRequest = FApiRequest(); - ApiRequest.Url = ApiUrl; - ApiRequest.Method = POST; - ApiRequest.Headers.Add(TEXT("Content-Type"), TEXT("application/json")); - ApiRequest.Payload = Request.ToJsonString(); + const TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = ApiUrl; + ApiRequest->Method = POST; + ApiRequest->Headers.Add(TEXT("Content-Type"), TEXT("application/json")); + ApiRequest->Payload = Request.ToJsonString(); DispatchRaw(ApiRequest); } diff --git a/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp b/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp index 365ea42..7e00f8c 100644 --- a/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp +++ b/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp @@ -1,45 +1,44 @@ #include "Auth/DeveloperTokenAuthStrategy.h" +#include "RpmNextGen.h" #include "Auth/DevAuthTokenCache.h" -#include "Api/Auth/ApiRequest.h" #include "Api/Auth/Models/RefreshTokenRequest.h" #include "Api/Auth/Models/RefreshTokenResponse.h" #include "Auth/Models/DeveloperAuth.h" DeveloperTokenAuthStrategy::DeveloperTokenAuthStrategy() { - AuthApi = FAuthApi(); - AuthApi.OnRefreshTokenResponse.BindRaw(this, &DeveloperTokenAuthStrategy::OnRefreshTokenResponse); + AuthApi = MakeShared(); + AuthApi->OnRefreshTokenResponse.BindRaw(this, &DeveloperTokenAuthStrategy::OnRefreshTokenResponse); } -void DeveloperTokenAuthStrategy::AddAuthToRequest(TSharedPtr Request) +void DeveloperTokenAuthStrategy::AddAuthToRequest(TSharedPtr ApiRequest) { const FString Key = TEXT("Authorization"); const FString Token = FDevAuthTokenCache::GetAuthData().Token; if(Token.IsEmpty()) { - UE_LOG(LogTemp, Error, TEXT("Token is empty")); - OnAuthComplete.ExecuteIfBound(false); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Token is empty")); + OnAuthComplete.ExecuteIfBound(ApiRequest, false); return; } - if (Request->Headers.Contains(Key)) + if (ApiRequest->Headers.Contains(Key)) { - Request->Headers.Remove(Key); + ApiRequest->Headers.Remove(Key); } - Request->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Token)); - - OnAuthComplete.ExecuteIfBound(true); + ApiRequest->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Token)); + OnAuthComplete.ExecuteIfBound(ApiRequest, true); } -void DeveloperTokenAuthStrategy::TryRefresh(TSharedPtr Request) +void DeveloperTokenAuthStrategy::TryRefresh(TSharedPtr ApiRequest) { + ApiRequestToRetry = ApiRequest; FRefreshTokenRequest RefreshRequest; RefreshRequest.Data.Token = FDevAuthTokenCache::GetAuthData().Token; RefreshRequest.Data.RefreshToken = FDevAuthTokenCache::GetAuthData().RefreshToken; - RefreshTokenAsync(RefreshRequest); } -void DeveloperTokenAuthStrategy::OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful) +void DeveloperTokenAuthStrategy::OnRefreshTokenResponse(TSharedPtr Request, const FRefreshTokenResponse& Response, bool bWasSuccessful) { if (bWasSuccessful && !Response.Data.Token.IsEmpty()) { @@ -47,15 +46,13 @@ void DeveloperTokenAuthStrategy::OnRefreshTokenResponse(const FRefreshTokenRespo DeveloperAuth.Token = Response.Data.Token; DeveloperAuth.RefreshToken = Response.Data.RefreshToken; FDevAuthTokenCache::SetAuthData(DeveloperAuth); - OnTokenRefreshed.ExecuteIfBound(Response.Data, true); + OnTokenRefreshed.ExecuteIfBound(ApiRequestToRetry, Response.Data, true); return; } - UE_LOG(LogTemp, Error, TEXT("Failed to refresh token")); - OnTokenRefreshed.ExecuteIfBound(Response.Data, false); + OnTokenRefreshed.ExecuteIfBound(ApiRequestToRetry, Response.Data, false); } - void DeveloperTokenAuthStrategy::RefreshTokenAsync(const FRefreshTokenRequest& Request) { - AuthApi.RefreshToken(Request); + AuthApi->RefreshToken(Request); } diff --git a/Source/RpmNextGenEditor/Private/Cache/CacheGenerator.cpp b/Source/RpmNextGenEditor/Private/Cache/CacheGenerator.cpp new file mode 100644 index 0000000..b682277 --- /dev/null +++ b/Source/RpmNextGenEditor/Private/Cache/CacheGenerator.cpp @@ -0,0 +1,365 @@ +#include "Cache/CacheGenerator.h" +#include "HttpModule.h" +#include "RpmNextGen.h" +#include "Api/Assets/AssetApi.h" +#include "Api/Assets/AssetGlbLoader.h" +#include "Api/Assets/AssetIconLoader.h" +#include "Api/Assets/Models/AssetListRequest.h" +#include "Api/Assets/Models/AssetTypeListRequest.h" +#include "Cache/AssetCacheManager.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Misc/Paths.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/FileHelper.h" +#include "Misc/ScopeExit.h" +#include "Settings/RpmDeveloperSettings.h" + +const FString FCacheGenerator::ZipFileName = TEXT("CacheAssets.pak"); + +FCacheGenerator::FCacheGenerator() : CurrentBaseModelIndex(0), MaxItemsPerCategory(10) +{ + Http = &FHttpModule::Get(); + AssetApi = MakeUnique(EApiRequestStrategy::ApiOnly); + AssetApi->OnListAssetsResponse.BindRaw(this, &FCacheGenerator::OnListAssetsResponse); + AssetApi->OnListAssetTypeResponse.BindRaw(this, &FCacheGenerator::OnListAssetTypesResponse); +} + +FCacheGenerator::~FCacheGenerator() +{ +} + +void FCacheGenerator::DownloadRemoteCacheFromUrl(const FString& Url) +{ + TSharedRef Request = Http->CreateRequest(); + Request->SetURL(Url); + Request->SetVerb(TEXT("GET")); + Request->OnProcessRequestComplete().BindRaw(this, &FCacheGenerator::OnDownloadRemoteCacheComplete); + Request->ProcessRequest(); +} + +void FCacheGenerator::GenerateLocalCache(int InItemsPerCategory) +{ + Reset(); + MaxItemsPerCategory = InItemsPerCategory; + FetchBaseModels(); +} + +void FCacheGenerator::LoadAndStoreAssets() +{ + TArray BaseModelAssets = TArray(); + int TotalRefittedAssets = 0; + + // Ensure AssetMapByBaseModelId contains valid data + if (AssetMapByBaseModelId.Num() == 0) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("No base models found in AssetMapByBaseModelId")); + return; + } + for ( auto BaseModel : AssetMapByBaseModelId) + { + for (auto Asset : BaseModel.Value) + { + if(Asset.Type == FAssetApi::BaseModelType) + { + BaseModelAssets.Add(Asset); + } + TotalRefittedAssets++; + } + } + + // Ensure there's at least one BaseModel + if (BaseModelAssets.Num() == 0) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("No base model assets found")); + OnCacheDataLoaded.ExecuteIfBound(false); + return; + } + + int AssetIconRequestCount = 0; + + // load and store base model assets + for ( auto Asset : BaseModelAssets) + { + LoadAndStoreAssetIcon(Asset.Id, &Asset); + AssetIconRequestCount++; + } + + // Ensure AssetMap contains the BaseModelId + if (AssetMapByBaseModelId.Contains(BaseModelAssets[0].Id)) + { + for (auto& Asset : AssetMapByBaseModelId[BaseModelAssets[0].Id]) + { + if (Asset.Type == FAssetApi::BaseModelType) continue; + LoadAndStoreAssetIcon(BaseModelAssets[0].Id, &Asset); + AssetIconRequestCount++; + } + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("BaseModelId not found in AssetMapByBaseModelId")); + return; + } + + RequiredAssetDownloadRequest = TotalRefittedAssets + AssetIconRequestCount; + const FString GlobalCachePath = FFileUtility::GetCachePath(); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Total assets to download: %d. Total refitted assets glbs to fetch: %d"), RequiredAssetDownloadRequest, TotalRefittedAssets - BaseModelAssets.Num()); + + for (auto Pair : AssetMapByBaseModelId) + { + const FString BaseModeFolder = GlobalCachePath / Pair.Key; + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + const FString DirectoryPath = FPaths::GetPath(BaseModeFolder); + if (!PlatformFile.DirectoryExists(*DirectoryPath)) + { + PlatformFile.CreateDirectoryTree(*DirectoryPath); + } + for (auto Asset : Pair.Value) + { + LoadAndStoreAssetGlb(Pair.Key, &Asset); + } + } +} + +void FCacheGenerator::LoadAndStoreAssetGlb(const FString& BaseModelId, const FAsset* Asset) +{ + if (!Asset) // Ensure asset is valid + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Invalid asset when loading GLB for BaseModelId: %s"), *BaseModelId); + return; + } + + TSharedPtr AssetLoader = MakeShared(); + AssetLoader->OnGlbLoaded.BindRaw(this, &FCacheGenerator::OnAssetGlbSaved); + AssetLoader->LoadGlb(*Asset, BaseModelId, true); +} + +void FCacheGenerator::LoadAndStoreAssetIcon(const FString& BaseModelId, const FAsset* Asset) +{ + if (!Asset) // Ensure asset is valid + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Invalid asset when loading Icon for BaseModelId: %s"), *BaseModelId); + return; + } + TSharedPtr AssetLoader = MakeShared(); + AssetLoader->OnIconLoaded.BindRaw( this, &FCacheGenerator::OnAssetIconSaved); + AssetLoader->LoadIcon(*Asset, true); +} + +void FCacheGenerator::Reset() +{ + AssetMapByBaseModelId.Empty(); + AssetListRequests.Empty(); + AssetTypes.Empty(); + CurrentBaseModelIndex = 0; + RefittedAssetRequestsCompleted = 0; + RequiredAssetDownloadRequest = 0; + NumberOfAssetsSaved = 0; + FAssetCacheManager::Get().ClearAllCache(); +} + +void FCacheGenerator::OnAssetGlbSaved(const FAsset& Asset, const TArray& Data) +{ + NumberOfAssetsSaved++; + if(NumberOfAssetsSaved >= RequiredAssetDownloadRequest) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("OnLocalCacheGenerated Total assets to download: %d. Asset download requests completed: %d"), RequiredAssetDownloadRequest, NumberOfAssetsSaved); + + OnLocalCacheGenerated.ExecuteIfBound(true); + AddFolderToNonAssetDirectory(); + } +} + +void FCacheGenerator::OnAssetIconSaved(const FAsset& Asset, const TArray& Data) +{ + NumberOfAssetsSaved++; + if(NumberOfAssetsSaved >= RequiredAssetDownloadRequest) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("OnLocalCacheGenerated Total assets to download: %d. Asset download requests completed: %d"), RequiredAssetDownloadRequest, NumberOfAssetsSaved); + + OnLocalCacheGenerated.ExecuteIfBound(true); + AddFolderToNonAssetDirectory(); + } +} + +void FCacheGenerator::AddFolderToNonAssetDirectory() const +{ + FString ConfigFilePath = FPaths::ProjectConfigDir() / TEXT("DefaultGame.ini"); + + // Section and key for "Additional Non-Asset Directories to Copy" + const FString SectionName = TEXT("/Script/UnrealEd.ProjectPackagingSettings"); + const FString KeyName = TEXT("+DirectoriesToAlwaysStageAsNonUFS"); + + // Folder to add to the non-asset directories + const FString FolderToAdd = FString::Printf(TEXT("(Path=\"%s\")"), *FFileUtility::RelativeCachePath); + + // Check if the folder is already added + FString CurrentValue; + if (GConfig->GetString(*SectionName, *KeyName, CurrentValue, ConfigFilePath)) + { + if (CurrentValue.Contains(FolderToAdd)) + { + // Folder already exists, no need to add it + UE_LOG(LogReadyPlayerMe, Log, TEXT("Folder already added to Additional Non-Asset Directories: %s"), *FolderToAdd); + return; + } + } + + // Add the folder to the config + GConfig->SetString(*SectionName, *KeyName, *FolderToAdd, ConfigFilePath); + + // Force update the config file + GConfig->Flush(false, ConfigFilePath); + + UE_LOG(LogReadyPlayerMe, Log, TEXT("Added folder to Additional Non-Asset Directories: %s"), *FolderToAdd); +} + +void FCacheGenerator::OnListAssetsResponse(const FAssetListResponse& AssetListResponse, bool bWasSuccessful) +{ + if(bWasSuccessful && AssetListResponse.IsSuccess && AssetListResponse.Data.Num() > 0) + { + if (AssetListResponse.Data[0].Type == FAssetApi::BaseModelType) + { + for ( auto BaseModel : AssetListResponse.Data) + { + TArray AssetList = TArray(); + AssetList.Add(BaseModel); + AssetMapByBaseModelId.Add(BaseModel.Id, AssetList); + } + UE_LOG(LogReadyPlayerMe, Log, TEXT("Fetched %d base models"), AssetListResponse.Data.Num()); + FetchAssetTypes(); + return; + } + UE_LOG(LogReadyPlayerMe, Log, TEXT("Fetched %d assets of type %s"), AssetListResponse.Data.Num(), *AssetListResponse.Data[0].Type); + + if(AssetListResponse.Data.Num() > 0) + { + FString BaseModelID = AssetListRequests[RefittedAssetRequestsCompleted].Params.CharacterModelAssetId; + if (!AssetMapByBaseModelId.Contains(BaseModelID)) + { + AssetMapByBaseModelId.Add(BaseModelID, AssetListResponse.Data); + } + else + { + AssetMapByBaseModelId[BaseModelID].Append(AssetListResponse.Data); + } + + } + RefittedAssetRequestsCompleted++; + FetchNextRefittedAsset(); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to fetch assets")); + OnCacheDataLoaded.ExecuteIfBound(false); +} + +void FCacheGenerator::StartFetchingRefittedAssets() +{ + RefittedAssetRequestsCompleted = 0; + + URpmDeveloperSettings *Settings = GetMutableDefault(); + AssetListRequests = TArray(); + for ( auto BaseModel : AssetMapByBaseModelId) + { + for(FString AssetType : AssetTypes) + { + if(AssetType == FAssetApi::BaseModelType) + { + continue; + } + FAssetListQueryParams QueryParams = FAssetListQueryParams(); + QueryParams.Type = AssetType; + QueryParams.ApplicationId = Settings->ApplicationId; + QueryParams.CharacterModelAssetId = BaseModel.Key; + QueryParams.Limit = MaxItemsPerCategory; + FAssetListRequest AssetListRequest = FAssetListRequest(QueryParams); + AssetListRequests.Add(AssetListRequest); + } + } + FetchNextRefittedAsset(); +} + +void FCacheGenerator::FetchNextRefittedAsset() +{ + if(RefittedAssetRequestsCompleted >= AssetListRequests.Num()) + { + OnCacheDataLoaded.ExecuteIfBound(true); + return; + } + AssetApi->ListAssetsAsync(AssetListRequests[RefittedAssetRequestsCompleted]); +} + +void FCacheGenerator::OnListAssetTypesResponse(const FAssetTypeListResponse& AssetListResponse, bool bWasSuccessful) +{ + if(bWasSuccessful && AssetListResponse.IsSuccess) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Fetched %d asset types"), AssetListResponse.Data.Num()); + AssetTypes.Append(AssetListResponse.Data); + FAssetCacheManager::Get().StoreAssetTypes(AssetTypes); + StartFetchingRefittedAssets(); + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to fetch asset types")); + OnCacheDataLoaded.ExecuteIfBound(false); +} + +void FCacheGenerator::OnDownloadRemoteCacheComplete(TSharedPtr Request, TSharedPtr Response, bool bWasSuccessful) +{ + if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode())) + { + // Get the response data + const TArray& Data = Response->GetContent(); + // Define the path to save the ZIP file + const FString SavePath = FFileUtility::GetCachePath()/ TEXT("/") / ZipFileName; + + // Ensure the directory exists + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + const FString DirectoryPath = FPaths::GetPath(SavePath); + if (!PlatformFile.DirectoryExists(*DirectoryPath)) + { + PlatformFile.CreateDirectoryTree(*DirectoryPath); + } + + // Save the data as a .zip file + if (FFileHelper::SaveArrayToFile(Data, *SavePath)) + { + UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully saved the remote cache to: %s"), *SavePath); + OnDownloadRemoteCacheDelegate.ExecuteIfBound(true); + } + else + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to save the remote cache to: %s"), *SavePath); + OnDownloadRemoteCacheDelegate.ExecuteIfBound(false); + } + return; + } + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to download the remote cache")); + OnDownloadRemoteCacheDelegate.ExecuteIfBound(false); +} + +void FCacheGenerator::ExtractCache() +{ + //FPakFileUtility::ExtractFilesFromPak( FRpmNextGenModule::GetGlobalAssetCachePath() / TEXT("/") / ZipFileName); +} + +void FCacheGenerator::FetchBaseModels() const +{ + const URpmDeveloperSettings* Settings = GetDefault(); + FAssetListRequest AssetListRequest = FAssetListRequest(); + FAssetListQueryParams QueryParams = FAssetListQueryParams(); + QueryParams.ApplicationId = Settings->ApplicationId; + QueryParams.Type = FAssetApi::BaseModelType; + AssetListRequest.Params = QueryParams; + AssetApi->ListAssetsAsync(AssetListRequest); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Fetching base models") ); +} + +void FCacheGenerator::FetchAssetTypes() const +{ + const URpmDeveloperSettings* Settings = GetDefault(); + FAssetTypeListRequest AssetListRequest; + FAssetTypeListQueryParams QueryParams = FAssetTypeListQueryParams(); + QueryParams.ApplicationId = Settings->ApplicationId; + AssetListRequest.Params = QueryParams; + AssetApi->ListAssetTypesAsync(AssetListRequest); +} diff --git a/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp b/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp index 570dbf3..a77703d 100644 --- a/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp +++ b/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp @@ -1,12 +1,13 @@ -#include "DeveloperAccounts/DeveloperAccountApi.h" +#include "DeveloperAccounts/DeveloperAccountApi.h" #include "JsonObjectConverter.h" #include "DeveloperAccounts/Models/ApplicationListRequest.h" #include "DeveloperAccounts/Models/ApplicationListResponse.h" #include "DeveloperAccounts/Models/OrganizationListRequest.h" #include "DeveloperAccounts/Models/OrganizationListResponse.h" +#include "Interfaces/IHttpResponse.h" #include "Settings/RpmDeveloperSettings.h" -FDeveloperAccountApi::FDeveloperAccountApi(IAuthenticationStrategy* InAuthenticationStrategy) : FWebApiWithAuth(InAuthenticationStrategy) +FDeveloperAccountApi::FDeveloperAccountApi(const TSharedPtr& InAuthenticationStrategy) : FWebApiWithAuth(InAuthenticationStrategy) { if (URpmDeveloperSettings* Settings = GetMutableDefault()) { @@ -21,9 +22,9 @@ void FDeveloperAccountApi::ListApplicationsAsync(const FApplicationListRequest& ApiBaseUrl = RpmSettings->GetApiBaseUrl(); const FString QueryString = BuildQueryString(Request.Params); const FString Url = FString::Printf(TEXT("%s/v1/applications%s"), *ApiBaseUrl, *QueryString); - FApiRequest ApiRequest; - ApiRequest.Url = Url; - OnApiResponse.BindRaw(this, &FDeveloperAccountApi::HandleAppListResponse); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = Url; + OnRequestComplete.BindRaw(this, &FDeveloperAccountApi::HandleAppListResponse); DispatchRawWithAuth(ApiRequest); } @@ -34,16 +35,17 @@ void FDeveloperAccountApi::ListOrganizationsAsync(const FOrganizationListRequest ApiBaseUrl = RpmSettings->GetApiBaseUrl(); const FString QueryString = BuildQueryString(Request.Params); const FString Url = FString::Printf(TEXT("%s/v1/organizations%s"), *ApiBaseUrl, *QueryString); - FApiRequest ApiRequest; - ApiRequest.Url = Url; - OnApiResponse.BindRaw(this, &FDeveloperAccountApi::HandleOrgListResponse); + TSharedPtr ApiRequest = MakeShared(); + ApiRequest->Url = Url; + OnRequestComplete.BindRaw(this, &FDeveloperAccountApi::HandleOrgListResponse); DispatchRawWithAuth(ApiRequest); } -void FDeveloperAccountApi::HandleAppListResponse(FString Data, bool bWasSuccessful) +void FDeveloperAccountApi::HandleAppListResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) { FApplicationListResponse ApplicationListResponse; + FString Data = Response->GetContentAsString(); if (bWasSuccessful && !Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &ApplicationListResponse, 0, 0)) { OnApplicationListResponse.ExecuteIfBound(ApplicationListResponse, true); @@ -52,14 +54,19 @@ void FDeveloperAccountApi::HandleAppListResponse(FString Data, bool bWasSuccessf OnApplicationListResponse.ExecuteIfBound(ApplicationListResponse, false); } -void FDeveloperAccountApi::HandleOrgListResponse(FString Data, bool bWasSuccessful) +void FDeveloperAccountApi::HandleOrgListResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) { FOrganizationListResponse OrganizationListResponse; - if (bWasSuccessful && !Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &OrganizationListResponse, 0, 0)) + if(Response.IsValid()) { - OnOrganizationResponse.ExecuteIfBound(OrganizationListResponse, true); - return; + FString Data = Response->GetContentAsString(); + if (bWasSuccessful && !Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &OrganizationListResponse, 0, 0)) + { + OnOrganizationResponse.ExecuteIfBound(OrganizationListResponse, true); + return; + } } + OnOrganizationResponse.ExecuteIfBound(OrganizationListResponse, false); } diff --git a/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp b/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp index 2bc520c..941c2e8 100644 --- a/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp +++ b/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp @@ -1,78 +1,91 @@ #include "EditorAssetLoader.h" #include "TransientObjectSaverLibrary.h" #include "AssetNameGenerator.h" +#include "glTFRuntimeFunctionLibrary.h" #include "RpmActor.h" #include "RpmNextGen.h" + FEditorAssetLoader::FEditorAssetLoader() { SkeletonToCopy = nullptr; + OnGlbLoaded.BindRaw(this, &FEditorAssetLoader::HandleGlbLoaded); + GltfConfig = new FglTFRuntimeConfig(); + GltfConfig->TransformBaseType = EglTFRuntimeTransformBaseType::YForward; } FEditorAssetLoader::~FEditorAssetLoader() { } -void FEditorAssetLoader::OnAssetLoadComplete(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful, - FString LoadedAssetId) +void FEditorAssetLoader::HandleGlbLoaded(const FAsset& Asset, const TArray& Data) { - if (bWasSuccessful) + UglTFRuntimeAsset* GltfAsset = nullptr; + if (!Data.IsEmpty()) { - gltfAsset->AddToRoot(); - SaveAsUAsset(gltfAsset, LoadedAssetId); - LoadAssetToWorldAsURpmActor(gltfAsset, LoadedAssetId); - gltfAsset->RemoveFromRoot(); + GltfAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromData(Data, *GltfConfig); + if (GltfAsset) + { + GltfAsset->AddToRoot(); + SaveAsUAsset(GltfAsset, Asset.Id); + LoadAssetToWorldAsURpmActor(GltfAsset, Asset.Id); + GltfAsset->RemoveFromRoot(); + } } + } USkeletalMesh* FEditorAssetLoader::SaveAsUAsset(UglTFRuntimeAsset* GltfAsset, const FString& LoadedAssetId) const { const FglTFRuntimeSkeletonConfig SkeletonConfig = FglTFRuntimeSkeletonConfig(); USkeleton* Skeleton = GltfAsset->LoadSkeleton(0, SkeletonConfig); + if (!IsValid(Skeleton)) + { + UE_LOG(LogTemp, Error, TEXT("Failed to load skeleton for %s"), *LoadedAssetId); + return nullptr; + } + FglTFRuntimeSkeletalMeshConfig MeshConfig = FglTFRuntimeSkeletalMeshConfig(); + MeshConfig.Skeleton = Skeleton; - FglTFRuntimeSkeletalMeshConfig meshConfig = FglTFRuntimeSkeletalMeshConfig(); - meshConfig.Skeleton = Skeleton; + USkeletalMesh* SkeletalMesh = GltfAsset->LoadSkeletalMeshRecursive(TEXT(""), {}, MeshConfig); - USkeletalMesh* skeletalMesh = GltfAsset->LoadSkeletalMeshRecursive(TEXT(""), {}, meshConfig); - skeletalMesh->SetSkeleton(Skeleton); - Skeleton->SetPreviewMesh(skeletalMesh); + if (!IsValid(SkeletalMesh)) + { + UE_LOG(LogTemp, Error, TEXT("Failed to load skeletal mesh for %s. 1"), *LoadedAssetId); + return nullptr; + } + + // Ensure proper UObject flags to avoid garbage collection + SkeletalMesh->SetFlags(RF_Public | RF_Standalone); + SkeletalMesh->SetSkeleton(Skeleton); + Skeleton->SetPreviewMesh(SkeletalMesh); - const FString CoreAssetPath = FString::Printf(TEXT("/Game/ReadyPlayerMe/%s/"), *LoadedAssetId); + const FString CoreAssetPath = FString::Printf(TEXT("/Game/ReadyPlayerMe/CharacterModels/%s/"), *LoadedAssetId); const FString SkeletonAssetPath = FString::Printf(TEXT("%s%s_Skeleton"), *CoreAssetPath, *LoadedAssetId); const FString SkeletalMeshAssetPath = FString::Printf(TEXT("%s%s_SkeletalMesh"), *CoreAssetPath, *LoadedAssetId); - + UE_LOG(LogTemp, Log, TEXT("Saving SkeletalMesh to path: %s"), *SkeletalMeshAssetPath); + UE_LOG(LogTemp, Log, TEXT("Saving Skeleton to path: %s"), *SkeletonAssetPath); const auto NameGenerator = NewObject(); NameGenerator->SetPath(CoreAssetPath); - UTransientObjectSaverLibrary::SaveTransientSkeletalMesh(skeletalMesh, SkeletalMeshAssetPath, SkeletonAssetPath, TEXT(""), NameGenerator->MaterialNameGeneratorDelegate, NameGenerator->TextureNameGeneratorDelegate); + UTransientObjectSaverLibrary::SaveTransientSkeletalMesh(SkeletalMesh, SkeletalMeshAssetPath, SkeletonAssetPath, TEXT(""), NameGenerator->MaterialNameGeneratorDelegate, NameGenerator->TextureNameGeneratorDelegate); UE_LOG(LogReadyPlayerMe, Log, TEXT("Character model saved: %s"), *LoadedAssetId); - return skeletalMesh; + return SkeletalMesh; } -void FEditorAssetLoader::LoadGLBFromURLWithId(const FString& URL, FString LoadedAssetId) +void FEditorAssetLoader::LoadBaseModelAsset(const FAsset& Asset) { - OnGLtfAssetLoaded.BindLambda( - [LoadedAssetId, this]( UglTFRuntimeAsset* gltfAsset, - bool bWasSuccessful) - { - if (!gltfAsset) - { - UE_LOG(LogReadyPlayerMe, Log, TEXT("No gltf asset")); - return; - } - OnAssetLoadComplete(gltfAsset, bWasSuccessful, LoadedAssetId); - }); - LoadGLBFromURL(URL); + LoadGlb(Asset, Asset.Id, false); } -void FEditorAssetLoader::LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* gltfAsset, FString AssetId) +void FEditorAssetLoader::LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* GltfAsset, FString AssetId) { - this->LoadAssetToWorld(AssetId, gltfAsset); + this->LoadAssetToWorld(AssetId, GltfAsset); } -void FEditorAssetLoader::LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gltfAsset) +void FEditorAssetLoader::LoadAssetToWorld(const FString& AssetId, UglTFRuntimeAsset* GltfAsset) { if (!GEditor) { @@ -87,9 +100,9 @@ void FEditorAssetLoader::LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gl return; } - if (gltfAsset) + if (GltfAsset) { - FTransform Transform = FTransform::Identity; + const FTransform Transform = FTransform::Identity; ARpmActor* NewActor = EditorWorld->SpawnActorDeferred(ARpmActor::StaticClass(), Transform); @@ -110,9 +123,9 @@ void FEditorAssetLoader::LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gl // Register the actor in the editor world and update the editor GEditor->SelectActor(NewActor, true, true); GEditor->EditorUpdateComponents(); - if (gltfAsset) + if (GltfAsset) { - NewActor->LoadGltfAsset(gltfAsset); + NewActor->LoadAsset(FAsset(), GltfAsset); } UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully loaded GLB asset into the editor world")); return; diff --git a/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp b/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp index d9e6dee..753a8cc 100644 --- a/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp +++ b/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp @@ -1,8 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "RpmNextGenEditor.h" - -#include "UI/CharacterLoaderWidget.h" +#include "UI/SCharacterLoaderWidget.h" #include "UI/Commands/LoaderWindowCommands.h" #include "UI/Commands/LoginWindowCommands.h" #include "UI/SRpmDeveloperLoginWidget.h" @@ -10,18 +9,24 @@ #include "Widgets/Layout/SBox.h" #include "Widgets/Text/STextBlock.h" #include "ToolMenus.h" +#include "UI/LoginWindowStyle.h" +#include "UI/SCacheGeneratorWidget.h" +#include "UI/Commands/CacheWindowCommands.h" static const FName DeveloperWindowName("LoginWindow"); -static const FName LoaderWinderName("LoaderWindow"); +static const FName LoaderWindowName("LoaderWindow"); +static const FName CacheWindowName("CacheGeneratorWindow"); #define LOCTEXT_NAMESPACE "RpmNextGenEditorModule" + void FRpmNextGenEditorModule::StartupModule() { FLoginWindowStyle::Initialize(); FLoginWindowStyle::ReloadTextures(); FLoginWindowCommands::Register(); - FLoaderWindowCommands::Register(); // Don't forget to register the other command set + FLoaderWindowCommands::Register(); + FCacheWindowCommands::Register(); PluginCommands = MakeShareable(new FUICommandList); @@ -30,6 +35,12 @@ void FRpmNextGenEditorModule::StartupModule() FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::PluginButtonClicked), FCanExecuteAction()); + + PluginCommands->MapAction( + FCacheWindowCommands::Get().OpenPluginWindow, + FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::OpenCacheEditorWindow), + FCanExecuteAction()); + // Don't show Loader window in the menu // PluginCommands->MapAction( // FLoaderWindowCommands::Get().OpenPluginWindow, @@ -42,6 +53,10 @@ void FRpmNextGenEditorModule::StartupModule() .SetDisplayName(LOCTEXT("DeveloperLoginWidget", "Ready Player Me")) .SetMenuType(ETabSpawnerMenuType::Hidden); + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(CacheWindowName, FOnSpawnTab::CreateRaw(this, &FRpmNextGenEditorModule::OnSpawnCacheWindow)) + .SetDisplayName(LOCTEXT("CacheGeneratorrWidget", "Cache Generator")) + .SetMenuType(ETabSpawnerMenuType::Hidden); + // Don't show Loader window in the menu // FGlobalTabmanager::Get()->RegisterNomadTabSpawner(NewWindowTabName, FOnSpawnTab::CreateRaw(this, &FRpmNextGenEditorModule::OnSpawnLoaderWindow)) // .SetDisplayName(LOCTEXT("CharacterLoaderWidget", "Avatar Loader")) @@ -80,6 +95,15 @@ void FRpmNextGenEditorModule::FillReadyPlayerMeMenu(UToolMenu* Menu) FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::PluginButtonClicked)) ); + + + Section.AddMenuEntry( + "OpenCacheGeneratorWindow", + LOCTEXT("OpenCacheGeneratorWindow", "Cache Generator"), + LOCTEXT("OpenGeneratorWindowToolTip", "Cache Generator Window."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::OpenCacheEditorWindow)) + ); // Don't show Loader window in the menu // Section.AddMenuEntry( @@ -99,12 +123,13 @@ void FRpmNextGenEditorModule::ShutdownModule() FLoginWindowStyle::Shutdown(); FLoginWindowCommands::Unregister(); - // Don't show Loader window in the menu - //FLoaderWindowCommands::Unregister(); + FLoaderWindowCommands::Unregister(); FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(DeveloperWindowName); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(CacheWindowName); + // Don't show Loader window in the menu - //FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(LoaderWinderName); + //FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(LoaderWindowName); } TSharedRef FRpmNextGenEditorModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) @@ -132,6 +157,15 @@ TSharedRef FRpmNextGenEditorModule::OnSpawnLoaderWindow(const FSpawnTa ]; } +TSharedRef FRpmNextGenEditorModule::OnSpawnCacheWindow(const FSpawnTabArgs& SpawnTabArgs) +{ + return SNew(SDockTab) + .TabRole(NomadTab) + [ + SNew(SCacheGeneratorWidget) + ]; +} + void FRpmNextGenEditorModule::PluginButtonClicked() { FGlobalTabmanager::Get()->TryInvokeTab(DeveloperWindowName); @@ -140,7 +174,12 @@ void FRpmNextGenEditorModule::PluginButtonClicked() void FRpmNextGenEditorModule::OpenLoaderWindow() { - FGlobalTabmanager::Get()->TryInvokeTab(LoaderWinderName); + FGlobalTabmanager::Get()->TryInvokeTab(LoaderWindowName); +} + +void FRpmNextGenEditorModule::OpenCacheEditorWindow() +{ + FGlobalTabmanager::Get()->TryInvokeTab(CacheWindowName); } #undef LOCTEXT_NAMESPACE diff --git a/Source/RpmNextGenEditor/Private/UI/Commands/CacheWindowCommands.cpp b/Source/RpmNextGenEditor/Private/UI/Commands/CacheWindowCommands.cpp new file mode 100644 index 0000000..26cc19e --- /dev/null +++ b/Source/RpmNextGenEditor/Private/UI/Commands/CacheWindowCommands.cpp @@ -0,0 +1,10 @@ +#include "UI/Commands/CacheWindowCommands.h" + +#define LOCTEXT_NAMESPACE "FRpmNextGenEditorModule" + +void FCacheWindowCommands::RegisterCommands() +{ + UI_COMMAND(OpenPluginWindow, "Cache Generator window", "Bring up RPM Cache Generator window", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/RpmNextGenEditor/Private/UI/SCacheGeneratorWidget.cpp b/Source/RpmNextGenEditor/Private/UI/SCacheGeneratorWidget.cpp new file mode 100644 index 0000000..5158ae9 --- /dev/null +++ b/Source/RpmNextGenEditor/Private/UI/SCacheGeneratorWidget.cpp @@ -0,0 +1,234 @@ +#include "UI/SCacheGeneratorWidget.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "EditorStyleSet.h" +#include "IPlatformFilePak.h" +#include "RpmNextGen.h" +#include "Api/Files/FileUtility.h" +#include "Api/Files/PakFileUtility.h" +#include "Cache/CacheGenerator.h" +#include "Misc/FileHelper.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Layout/SScrollBox.h" + +void SCacheGeneratorWidget::Construct(const FArguments& InArgs) +{ + if(!CacheGenerator) + { + CacheGenerator = MakeShared(); + CacheGenerator->OnCacheDataLoaded.BindRaw(this, &SCacheGeneratorWidget::OnFetchCacheDataComplete); + CacheGenerator->OnDownloadRemoteCacheDelegate.BindRaw(this, &SCacheGeneratorWidget::OnDownloadRemoteCacheComplete); + CacheGenerator->OnLocalCacheGenerated.BindRaw(this, &SCacheGeneratorWidget::OnGenerateLocalCacheCompleted); + } + ChildSlot + [ + SNew(SScrollBox) // Make the entire content scrollable + + SScrollBox::Slot() + .Padding(10) + [ + SNew(SVerticalBox) + + // Title/Label "Local Cache Generator" + + SVerticalBox::Slot() + .Padding(5) + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString("Local Cache Generator")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 16)) + ] + // Integer Slider with label "Items per category" + + SVerticalBox::Slot() + .Padding(5) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Max items per category:")) + ] + + SHorizontalBox::Slot() + .Padding(5, 0, 0, 0) + .FillWidth(1.0f) + [ + SNew(SNumericEntryBox) + .Value(this, &SCacheGeneratorWidget::GetItemsPerCategory) + .OnValueChanged(this, &SCacheGeneratorWidget::OnItemsPerCategoryChanged) + .AllowSpin(true) // Slider-like behavior + .MinValue(1) + .MaxValue(30) + .SliderExponent(1.0f) + ] + ] + + SVerticalBox::Slot() + .Padding(5) + .AutoHeight() + [ + SNew(SBox) + .HeightOverride(40) // Set button height + [ + SNew(SButton) + .Text(FText::FromString("Generate offline cache")) + .OnClicked(this, &SCacheGeneratorWidget::OnGenerateOfflineCacheClicked) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + ] + ] + + SVerticalBox::Slot() + .Padding(5) + .AutoHeight() + [ + SNew(SBox) + .HeightOverride(40) // Set button height + [ + SNew(SButton) + .Text(FText::FromString("Extract Cache to local folder")) + .OnClicked(this, &SCacheGeneratorWidget::OnExtractCacheClicked) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + ] + ] + + SVerticalBox::Slot() + .Padding(5) + .AutoHeight() + [ + SNew(SBox) + .HeightOverride(40) // Set button height + [ + SNew(SButton) + .Text(FText::FromString("Open Local Cache Folder")) + .OnClicked(this, &SCacheGeneratorWidget::OnOpenLocalCacheFolderClicked) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + ] + ] + // TODO implement remote cache download and unzip logic + // // Title/Label "Remote Cache Downloader" + // + SVerticalBox::Slot() + // .Padding(5) + // .AutoHeight() + // [ + // SNew(STextBlock) + // .Text(FText::FromString("Remote Cache Downloader")) + // .Font(FCoreStyle::GetDefaultFontStyle("Bold", 16)) + // ] + // + // // Editable text field with label "Cache Url" + // + SVerticalBox::Slot() + // .Padding(5) + // .AutoHeight() + // [ + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .AutoWidth() + // .VAlign(VAlign_Center) + // [ + // SNew(STextBlock) + // .Text(FText::FromString("Cache URL:")) + // ] + // + SHorizontalBox::Slot() + // .Padding(5, 0, 0, 0) + // .FillWidth(1.0f) + // [ + // SNew(SBox) + // .HeightOverride(30) // Set text box height + // [ + // SNew(SEditableTextBox) + // .Text(FText::FromString("http://")) + // .OnTextCommitted(this, &SCacheEditorWidget::OnCacheUrlTextCommitted) + // ] + // ] + // ] + // + // // Button "Download Remote Cache" + // + SVerticalBox::Slot() + // .Padding(5) + // .AutoHeight() + // [ + // SNew(SBox) + // .HeightOverride(40) // Set button height + // [ + // SNew(SButton) + // .Text(FText::FromString("Download Remote Cache")) + // .OnClicked(this, &SCacheEditorWidget::OnDownloadRemoteCacheClicked) + // .HAlign(HAlign_Center) + // .VAlign(VAlign_Center) + // ] + // ] + ] + ]; +} + + +FReply SCacheGeneratorWidget::OnGenerateOfflineCacheClicked() +{ + CacheGenerator->GenerateLocalCache(ItemsPerCategory); + return FReply::Handled(); +} + +FReply SCacheGeneratorWidget::OnExtractCacheClicked() +{ + FString PakFilePath = FPaths::ConvertRelativePathToFull(FPakFileUtility::CachePakFilePath); + FPakFileUtility::ExtractFilesFromPak(PakFilePath); + return FReply::Handled(); +} + +FReply SCacheGeneratorWidget::OnOpenLocalCacheFolderClicked() +{ + const FString CacheRoot = FFileUtility::GetFullPersistentPath(FFileUtility::RelativeCachePath); + // Check if the folder exists + if (FPaths::DirectoryExists(CacheRoot)) + { + // Open the folder in the file explorer + FPlatformProcess::LaunchFileInDefaultExternalApplication(*CacheRoot); + } + else + { + UE_LOG(LogReadyPlayerMe, Warning, TEXT("Folder does not exist: %s"), *CacheRoot); + } + + return FReply::Handled(); +} + +FReply SCacheGeneratorWidget::OnDownloadRemoteCacheClicked() +{ + // Handle downloading the remote cache + return FReply::Handled(); +} + +void SCacheGeneratorWidget::OnFetchCacheDataComplete(bool bWasSuccessful) +{ + if(!bWasSuccessful) + { + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to fetch cache data")); + return; + } + UE_LOG(LogReadyPlayerMe, Log, TEXT("Completed fetching assets")); + CacheGenerator->LoadAndStoreAssets(); +} + +void SCacheGeneratorWidget::OnDownloadRemoteCacheComplete(bool bWasSuccessful) +{ + +} + +void SCacheGeneratorWidget::OnGenerateLocalCacheCompleted(bool bWasSuccessful) +{ + UE_LOG(LogReadyPlayerMe, Log, TEXT("Completed generating cache")); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Local cache generated successfully")); + FPakFileUtility::CreatePakFile(); +} + + +void SCacheGeneratorWidget::OnItemsPerCategoryChanged(float NewValue) +{ + ItemsPerCategory = NewValue; +} + +void SCacheGeneratorWidget::OnCacheUrlChanged(const FText& NewText) +{ + CacheUrl = NewText.ToString(); +} diff --git a/Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp b/Source/RpmNextGenEditor/Private/UI/SCharacterLoaderWidget.cpp similarity index 92% rename from Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp rename to Source/RpmNextGenEditor/Private/UI/SCharacterLoaderWidget.cpp index f300345..737cf06 100644 --- a/Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp +++ b/Source/RpmNextGenEditor/Private/UI/SCharacterLoaderWidget.cpp @@ -1,12 +1,13 @@ -#include "UI/CharacterLoaderWidget.h" +#include "UI/SCharacterLoaderWidget.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "EditorStyleSet.h" #include "glTFRuntimeFunctionLibrary.h" -#include "Api/Assets/AssetLoader.h" +#include "Api/Files/GlbLoader.h" #include "PropertyCustomizationHelpers.h" +#include "RpmNextGen.h" #include "AssetRegistry/AssetData.h" // Configuration section and key names @@ -74,7 +75,7 @@ void SCharacterLoaderWidget::OnSkeletonSelected(const FAssetData& AssetData) SelectedSkeleton = Cast(AssetData.GetAsset()); if (SelectedSkeleton) { - UE_LOG(LogTemp, Log, TEXT("Selected Skeleton: %s"), *SelectedSkeleton->GetName()); + UE_LOG(LogReadyPlayerMe, Log, TEXT("Selected Skeleton: %s"), *SelectedSkeleton->GetName()); } } @@ -99,7 +100,7 @@ FReply SCharacterLoaderWidget::OnButtonClick() FString Path = PathText.ToString(); if (Path.IsEmpty()) { - UE_LOG(LogTemp, Error, TEXT("Path is empty")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Path is empty")); return FReply::Handled(); } LoadAsset(Path); diff --git a/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp b/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp index 7020835..1cf83b3 100644 --- a/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp +++ b/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp @@ -3,13 +3,14 @@ #include "UI/SRpmDeveloperLoginWidget.h" #include "Auth/DevAuthTokenCache.h" #include "EditorCache.h" +#include "RpmNextGen.h" #include "SlateOptMacros.h" #include "Api/Assets/Models/AssetListRequest.h" #include "Api/Assets/Models/AssetListResponse.h" #include "DeveloperAccounts/DeveloperAccountApi.h" #include "Auth/DeveloperTokenAuthStrategy.h" #include "Widgets/Input/SEditableTextBox.h" -#include "RpmImageLoader.h" +#include "RpmTextureLoader.h" #include "Auth/DeveloperAuthApi.h" #include "Auth/Models/DeveloperAuth.h" #include "Auth/Models/DeveloperLoginRequest.h" @@ -18,6 +19,7 @@ #include "DeveloperAccounts/Models/OrganizationListRequest.h" #include "DeveloperAccounts/Models/OrganizationListResponse.h" #include "Settings/RpmDeveloperSettings.h" +#include "Utilities/RpmImageHelper.h" #include "Widgets/Layout/SScrollBox.h" BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION @@ -25,10 +27,19 @@ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SRpmDeveloperLoginWidget::Construct(const FArguments& InArgs) { FDeveloperAuth AuthData = FDevAuthTokenCache::GetAuthData(); - FDevAuthTokenCache::SetAuthData(AuthData); - bIsLoggedIn = AuthData.IsValid(); - UserName = AuthData.Name; + bIsLoggedIn = false; + if(AuthData.IsValid()) + { + FDevAuthTokenCache::SetAuthData(AuthData); + bIsLoggedIn = AuthData.IsValid(); + UserName = AuthData.Name; + } + else + { + UserName = "User"; + FDevAuthTokenCache::ClearAuthData(); + } ChildSlot [ @@ -152,7 +163,7 @@ void SRpmDeveloperLoginWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Text(FText::FromString("Character Styles")) + .Text(FText::FromString("Character Models")) .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 16)) .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility) ] @@ -161,7 +172,7 @@ void SRpmDeveloperLoginWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Text(FText::FromString("Here you can import your character styles from Studio")) + .Text(FText::FromString("Here you can import your character models from Studio")) .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility) ] + SVerticalBox::Slot() @@ -187,31 +198,32 @@ void SRpmDeveloperLoginWidget::Initialize() { return; } + + ActiveLoaders = TArray>(); const FDeveloperAuth DevAuthData = FDevAuthTokenCache::GetAuthData(); if (!DeveloperAuthApi.IsValid()) { - DeveloperAuthApi = MakeUnique(); + DeveloperAuthApi = MakeShared(); DeveloperAuthApi->OnLoginResponse.BindRaw(this, &SRpmDeveloperLoginWidget::HandleLoginResponse); } if (!AssetApi.IsValid()) { - AssetApi = MakeUnique(); + AssetApi = MakeShared(); if (!DevAuthData.IsDemo) { - AssetApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy()); + AssetApi->SetAuthenticationStrategy(MakeShared()); } AssetApi->OnListAssetsResponse.BindRaw(this, &SRpmDeveloperLoginWidget::HandleBaseModelListResponse); } if (!DeveloperAccountApi.IsValid()) { - DeveloperAccountApi = MakeUnique(nullptr); + DeveloperAccountApi = MakeShared(nullptr); if (!DevAuthData.IsDemo) { - DeveloperAccountApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy()); + DeveloperAccountApi->SetAuthenticationStrategy(MakeShared()); } - DeveloperAccountApi->OnOrganizationResponse.BindRaw( this, &SRpmDeveloperLoginWidget::HandleOrganizationListResponse); DeveloperAccountApi->OnApplicationListResponse.BindRaw( @@ -270,13 +282,16 @@ void SRpmDeveloperLoginWidget::AddCharacterStyle(const FAsset& StyleAsset) .WidthOverride(100.0f) [ SNew(SButton) - .Text(FText::FromString("Load Style")) - .OnClicked_Lambda([this, StyleAsset]() -> FReply - { - OnLoadStyleClicked(StyleAsset.Id); - return FReply::Handled(); - }) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Text(FText::FromString("Import")) + .OnClicked_Lambda([this, StyleAsset]() -> FReply + { + OnLoadBaseModelClicked(StyleAsset); + return FReply::Handled(); + }) ] + ] ] + SHorizontalBox::Slot() @@ -293,18 +308,27 @@ void SRpmDeveloperLoginWidget::AddCharacterStyle(const FAsset& StyleAsset) ] ]; - FRpmImageLoader ImageLoader; - ImageLoader.LoadSImageFromURL(ImageWidget, StyleAsset.IconUrl, [this](UTexture2D* texture) + TSharedPtr ImageLoader = MakeShared(); + ActiveLoaders.Add(ImageLoader); + ImageLoader->OnTextureLoaded.BindRaw(this, &SRpmDeveloperLoginWidget::OnTextureLoaded, ImageWidget, ImageLoader); + ImageLoader->LoadIconFromAsset(StyleAsset); +} + +void SRpmDeveloperLoginWidget::OnTextureLoaded(UTexture2D* Texture2D, TSharedPtr SImage, TSharedPtr LoaderToRemove) +{ + if (Texture2D) { - texture->AddToRoot(); - CharacterStyleTextures.Add(texture); - }); + Texture2D->AddToRoot(); + CharacterStyleTextures.Add(Texture2D); + FRpmImageHelper::LoadTextureToSImage(Texture2D, FVector2D(100.0f, 100.0f), SImage); + } + ActiveLoaders.Remove(LoaderToRemove); } -void SRpmDeveloperLoginWidget::OnLoadStyleClicked(const FString& StyleId) +void SRpmDeveloperLoginWidget::OnLoadBaseModelClicked(const FAsset& StyleAsset) { - AssetLoader = FEditorAssetLoader(); - AssetLoader.LoadGLBFromURLWithId(CharacterStyleAssets[StyleId].GlbUrl, *StyleId); + AssetLoader = MakeShared(); + AssetLoader->LoadBaseModelAsset(StyleAsset); } EVisibility SRpmDeveloperLoginWidget::GetLoginViewVisibility() const @@ -331,8 +355,8 @@ FReply SRpmDeveloperLoginWidget::OnLoginClicked() FEditorCache::SetString(CacheKeyEmail, Email); Email = Email.TrimStartAndEnd(); Password = Password.TrimStartAndEnd(); - DeveloperAccountApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy()); - AssetApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy()); + DeveloperAccountApi->SetAuthenticationStrategy(MakeShared()); + AssetApi->SetAuthenticationStrategy(MakeShared()); FDeveloperLoginRequest LoginRequest = FDeveloperLoginRequest(Email, Password); DeveloperAuthApi->LoginWithEmail(LoginRequest); return FReply::Handled(); @@ -349,24 +373,23 @@ void SRpmDeveloperLoginWidget::HandleLoginResponse(const FDeveloperLoginResponse if (bWasSuccessful) { UserName = Response.Data.Name; - FDeveloperAuth AuthData = FDeveloperAuth(Response.Data, false); + const FDeveloperAuth AuthData = FDeveloperAuth(Response.Data, false); FDevAuthTokenCache::SetAuthData(AuthData); SetLoggedInState(true); GetOrgList(); return; } - UE_LOG(LogTemp, Error, TEXT("Login request failed")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Login request failed")); FDevAuthTokenCache::ClearAuthData(); } -void SRpmDeveloperLoginWidget::HandleOrganizationListResponse(const FOrganizationListResponse& Response, - bool bWasSuccessful) +void SRpmDeveloperLoginWidget::HandleOrganizationListResponse(const FOrganizationListResponse& Response, bool bWasSuccessful) { if (bWasSuccessful) { if (Response.Data.Num() == 0) { - UE_LOG(LogTemp, Error, TEXT("No organizations found")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("No organizations found")); return; } FApplicationListRequest Request; @@ -375,7 +398,7 @@ void SRpmDeveloperLoginWidget::HandleOrganizationListResponse(const FOrganizatio return; } - UE_LOG(LogTemp, Error, TEXT("Failed to list organizations")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to list organizations")); } @@ -405,7 +428,7 @@ void SRpmDeveloperLoginWidget::HandleApplicationListResponse(const FApplicationL } else { - UE_LOG(LogTemp, Error, TEXT("Failed to list applications")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to list applications")); } LoadBaseModelList(); } @@ -445,6 +468,7 @@ void SRpmDeveloperLoginWidget::OnComboBoxSelectionChanged(TSharedPtr Ne } } + FReply SRpmDeveloperLoginWidget::OnUseDemoAccountClicked() { URpmDeveloperSettings* RpmSettings = GetMutableDefault(); @@ -486,13 +510,13 @@ void SRpmDeveloperLoginWidget::LoadBaseModelList() const URpmDeveloperSettings* RpmSettings = GetDefault(); if (RpmSettings->ApplicationId.IsEmpty()) { - UE_LOG(LogTemp, Error, TEXT("Application ID is empty, unable to load base models.")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("Application ID is empty, unable to load base models.")); return; } FAssetListRequest Request = FAssetListRequest(); FAssetListQueryParams Params = FAssetListQueryParams(); Params.ApplicationId = RpmSettings->ApplicationId; - Params.Type = "baseModel"; + Params.Type = FAssetApi::BaseModelType; Request.Params = Params; AssetApi->ListAssetsAsync(Request); } diff --git a/Source/RpmNextGenEditor/Public/AssetNameGenerator.h b/Source/RpmNextGenEditor/Public/AssetNameGenerator.h index 346cd99..daf7f80 100644 --- a/Source/RpmNextGenEditor/Public/AssetNameGenerator.h +++ b/Source/RpmNextGenEditor/Public/AssetNameGenerator.h @@ -16,17 +16,18 @@ class RPMNEXTGENEDITOR_API UAssetNameGenerator : public UObject GENERATED_BODY() public: + FTransientObjectSaverMaterialNameGenerator MaterialNameGeneratorDelegate; + FTransientObjectSaverTextureNameGenerator TextureNameGeneratorDelegate; + UAssetNameGenerator(); - void SetPath(FString path); + UFUNCTION() FString GenerateMaterialName(UMaterialInterface* Material, int32 MaterialIndex, const FString& SlotName) const; UFUNCTION() - FString GenerateTextureName(UTexture* Texture, UMaterialInterface* Material, const FString& MaterialPath, - const FString& ParamName) const; - - FTransientObjectSaverMaterialNameGenerator MaterialNameGeneratorDelegate; - FTransientObjectSaverTextureNameGenerator TextureNameGeneratorDelegate; + FString GenerateTextureName(UTexture* Texture, UMaterialInterface* Material, const FString& MaterialPath, const FString& ParamName) const; + + void SetPath(FString Path); private: - FString path; + FString Path; }; diff --git a/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h b/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h index 316d9a6..b342d3c 100644 --- a/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h +++ b/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h @@ -11,12 +11,14 @@ class RPMNEXTGENEDITOR_API FDevAuthTokenCache static void SetAuthData(const FDeveloperAuth& DevAuthData); static void ClearAuthData(); private: + static FDeveloperAuth AuthData; + static constexpr const TCHAR* CacheKeyName = TEXT("Name"); static constexpr const TCHAR* CacheKeyToken = TEXT("Token"); static constexpr const TCHAR* CacheKeyRefreshToken = TEXT("RefreshToken"); static constexpr const TCHAR* CacheKeyIsDemo = TEXT("IsDemo"); - static FDeveloperAuth AuthData; + static bool bIsInitialized; static void Initialize(); }; diff --git a/Source/RpmNextGenEditor/Public/Auth/DeveloperAuthApi.h b/Source/RpmNextGenEditor/Public/Auth/DeveloperAuthApi.h index e8084b3..05c3cfc 100644 --- a/Source/RpmNextGenEditor/Public/Auth/DeveloperAuthApi.h +++ b/Source/RpmNextGenEditor/Public/Auth/DeveloperAuthApi.h @@ -11,11 +11,13 @@ DECLARE_DELEGATE_TwoParams(FOnDeveloperLoginResponse, const FDeveloperLoginRespo class RPMNEXTGENEDITOR_API FDeveloperAuthApi : public FWebApi { public: + FOnDeveloperLoginResponse OnLoginResponse; + FDeveloperAuthApi(); - void HandleLoginResponse(FString JsonData, bool bIsSuccessful) const; + void HandleLoginResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful) const; void LoginWithEmail(FDeveloperLoginRequest Request); - FOnDeveloperLoginResponse OnLoginResponse; + private: FString ApiUrl; }; diff --git a/Source/RpmNextGenEditor/Public/Auth/DeveloperTokenAuthStrategy.h b/Source/RpmNextGenEditor/Public/Auth/DeveloperTokenAuthStrategy.h index da3bf06..ee8d857 100644 --- a/Source/RpmNextGenEditor/Public/Auth/DeveloperTokenAuthStrategy.h +++ b/Source/RpmNextGenEditor/Public/Auth/DeveloperTokenAuthStrategy.h @@ -1,22 +1,22 @@ -#pragma once +#pragma once #include "CoreMinimal.h" -#include "Api/Auth/ApiRequest.h" #include "Api/Auth/AuthApi.h" #include "Api/Auth/IAuthenticationStrategy.h" struct FRefreshTokenResponse; - class RPMNEXTGENEDITOR_API DeveloperTokenAuthStrategy : public IAuthenticationStrategy { public: DeveloperTokenAuthStrategy(); - virtual void AddAuthToRequest(TSharedPtr Request) override; - virtual void OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful) override; - virtual void TryRefresh(TSharedPtr Request) override; + + virtual void AddAuthToRequest(TSharedPtr ApiRequest) override; + virtual void OnRefreshTokenResponse(TSharedPtr, const FRefreshTokenResponse& Response, bool bWasSuccessful) override; + virtual void TryRefresh(TSharedPtr ApiRequest) override; + private: - void RefreshTokenAsync(const FRefreshTokenRequest& Request); - FOnWebApiResponse OnWebApiResponse; - FAuthApi AuthApi; + TSharedPtr AuthApi; + TSharedPtr ApiRequestToRetry; + void RefreshTokenAsync(const FRefreshTokenRequest& RefreshRequest); }; diff --git a/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h index 6af03f5..f6f4c1b 100644 --- a/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h +++ b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "JsonObjectConverter.h" #include "Api/Common/Models/ApiResponse.h" +#include "RpmNextGen.h" #include "DeveloperLoginResponse.generated.h" USTRUCT(BlueprintType) @@ -40,9 +41,9 @@ struct RPMNEXTGENEDITOR_API FDeveloperLoginResponse : public FApiResponse { if (JsonObject.IsValid()) { - return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), StaticStruct(), &OutObject, 0, 0); + return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), StaticStruct(), &OutObject, 0, 0); } - UE_LOG(LogTemp, Warning, TEXT("JsonObject Invalid")); + UE_LOG(LogReadyPlayerMe, Error, TEXT("JsonObject Invalid")); return false; } }; diff --git a/Source/RpmNextGenEditor/Public/Cache/CacheGenerator.h b/Source/RpmNextGenEditor/Public/Cache/CacheGenerator.h new file mode 100644 index 0000000..b507d56 --- /dev/null +++ b/Source/RpmNextGenEditor/Public/Cache/CacheGenerator.h @@ -0,0 +1,70 @@ +#pragma once +#include "Api/Assets/Models/AssetListResponse.h" +#include "Api/Assets/Models/AssetListRequest.h" + +class FTaskManager; +struct FCachedAssetData; +class FAssetSaver; +struct FAssetTypeListResponse; +class FAssetApi; +struct FAsset; +class IHttpResponse; +class IHttpRequest; +class FHttpModule; + +DECLARE_DELEGATE_OneParam(FOnCacheDataLoaded, bool); +DECLARE_DELEGATE_OneParam(FOnLocalCacheGenerated, bool); +DECLARE_DELEGATE_OneParam(FOnDownloadRemoteCache, bool); + +class RPMNEXTGENEDITOR_API FCacheGenerator : public TSharedFromThis +{ +public: + FOnDownloadRemoteCache OnDownloadRemoteCacheDelegate; + FOnCacheDataLoaded OnCacheDataLoaded; + FOnLocalCacheGenerated OnLocalCacheGenerated; + + FCacheGenerator(); + virtual ~FCacheGenerator(); + + void DownloadRemoteCacheFromUrl(const FString& Url); + void GenerateLocalCache(int InItemsPerCategory); + void ExtractCache(); + void LoadAndStoreAssets(); + void LoadAndStoreAssetGlb(const FString& BaseModelId, const FAsset* Asset); + void LoadAndStoreAssetIcon(const FString& BaseModelId, const FAsset* Asset); + void Reset(); + +protected: + TUniquePtr AssetApi; + TArray AssetTypes; + TMap> AssetMapByBaseModelId; + TArray AssetListRequests; + int32 CurrentBaseModelIndex; + + UFUNCTION() + void OnAssetGlbSaved(const FAsset& Asset, const TArray& Data); + UFUNCTION() + void OnAssetIconSaved(const FAsset& Asset, const TArray& Data); + + void FetchBaseModels() const; + void FetchAssetTypes() const; + void AddFolderToNonAssetDirectory() const; + void FetchNextRefittedAsset(); + + virtual void OnDownloadRemoteCacheComplete(TSharedPtr Request, TSharedPtr Response, bool bWasSuccessful); + +private: + static const FString ZipFileName; + + FHttpModule* Http; + + int MaxItemsPerCategory; + int RefittedAssetRequestsCompleted = 0; + int RequiredAssetDownloadRequest = 0; + int NumberOfAssetsSaved = 0; + + void StartFetchingRefittedAssets(); + void OnListAssetsResponse(const FAssetListResponse& AssetListResponse, bool bWasSuccessful); + void OnListAssetTypesResponse(const FAssetTypeListResponse& AssetTypeListResponse, bool bWasSuccessful); + +}; diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h index 21565be..e55a9e7 100644 --- a/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h +++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "CoreMinimal.h" #include "Api/Common/WebApiWithAuth.h" @@ -14,16 +14,18 @@ DECLARE_DELEGATE_TwoParams(FOnOrganizationListResponse, const FOrganizationListR class RPMNEXTGENEDITOR_API FDeveloperAccountApi : public FWebApiWithAuth { public: - FDeveloperAccountApi(IAuthenticationStrategy* InAuthenticationStrategy); - void ListApplicationsAsync(const FApplicationListRequest& Request); - void ListOrganizationsAsync(const FOrganizationListRequest& Request); - FOnApplicationListResponse OnApplicationListResponse; FOnOrganizationListResponse OnOrganizationResponse; -private: - static FString BuildQueryString(const TMap& Params); - void HandleOrgListResponse(FString Data, bool bWasSuccessful); - void HandleAppListResponse(FString Data, bool bWasSuccessful); + + FDeveloperAccountApi(const TSharedPtr& InAuthenticationStrategy); + void ListApplicationsAsync(const FApplicationListRequest& Request); + void ListOrganizationsAsync(const FOrganizationListRequest& Request); +private: FString ApiBaseUrl; + + void HandleOrgListResponse(TSharedPtr ApiRequest, FHttpResponsePtr Response, bool bWasSuccessful); + void HandleAppListResponse(TSharedPtrApiRequest, FHttpResponsePtr Response, bool bWasSuccessful); + + static FString BuildQueryString(const TMap& Params); }; diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h index 12b49a8..4aec9d3 100644 --- a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h +++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h @@ -1,7 +1,7 @@ #pragma once #include "CoreMinimal.h" -#include "Api/Auth/ApiRequest.h" +#include "Api/Common/Models/ApiRequest.h" #include "ApplicationListRequest.generated.h" USTRUCT(BlueprintType) diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h index 0a83cd0..3fed75e 100644 --- a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h +++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h @@ -1,7 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "Api/Auth/ApiRequest.h" #include "OrganizationListRequest.generated.h" USTRUCT(BlueprintType) diff --git a/Source/RpmNextGenEditor/Public/EditorAssetLoader.h b/Source/RpmNextGenEditor/Public/EditorAssetLoader.h index 2dfa540..188cd1d 100644 --- a/Source/RpmNextGenEditor/Public/EditorAssetLoader.h +++ b/Source/RpmNextGenEditor/Public/EditorAssetLoader.h @@ -1,22 +1,29 @@ #pragma once #include "CoreMinimal.h" -#include "Api/Assets/AssetLoader.h" +#include "Api/Assets/AssetGlbLoader.h" #include "HAL/PlatformFilemanager.h" -class RPMNEXTGENEDITOR_API FEditorAssetLoader : public FAssetLoader +struct FglTFRuntimeConfig; +class UglTFRuntimeAsset; + +class RPMNEXTGENEDITOR_API FEditorAssetLoader : public FAssetGlbLoader { public: - void OnAssetLoadComplete(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful, - FString LoadedAssetId); + USkeleton* SkeletonToCopy; + FEditorAssetLoader(); virtual ~FEditorAssetLoader() override; - void LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* gltfAsset, FString AssetId = ""); - void LoadGLBFromURLWithId(const FString& URL, const FString AssetId); + void LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* GltfAsset, FString AssetId = ""); + void LoadBaseModelAsset(const FAsset& Asset); + USkeletalMesh* SaveAsUAsset(UglTFRuntimeAsset* GltfAsset, const FString& LoadedAssetId) const; - USkeleton* SkeletonToCopy; private: - void LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gltfAsset); + FglTFRuntimeConfig* GltfConfig; + + void LoadAssetToWorld(const FString& AssetId, UglTFRuntimeAsset* GltfAsset); + UFUNCTION() + void HandleGlbLoaded(const FAsset& Asset, const TArray& Data); }; diff --git a/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h b/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h index 96034cf..c6cd461 100644 --- a/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h +++ b/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h @@ -17,12 +17,13 @@ class RPMNEXTGENEDITOR_API FRpmNextGenEditorModule : public IModuleInterface void PluginButtonClicked(); private: - void RegisterMenus(); void FillReadyPlayerMeMenu(UToolMenu* Menu); void OpenLoaderWindow(); - TSharedRef OnSpawnLoaderWindow(const FSpawnTabArgs& SpawnTabArgs); - TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); - TSharedPtr PluginCommands; + void OpenCacheEditorWindow(); + TSharedRef OnSpawnLoaderWindow(const FSpawnTabArgs& SpawnTabArgs); + TSharedRef OnSpawnCacheWindow(const FSpawnTabArgs& SpawnTabArgs); + TSharedRef OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs); + TSharedPtr PluginCommands; }; diff --git a/Source/RpmNextGenEditor/Public/UI/Commands/CacheWindowCommands.h b/Source/RpmNextGenEditor/Public/UI/Commands/CacheWindowCommands.h new file mode 100644 index 0000000..6cebcc6 --- /dev/null +++ b/Source/RpmNextGenEditor/Public/UI/Commands/CacheWindowCommands.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" +#include "EditorStyleSet.h" +#include "Framework/Commands/Commands.h" + +class RPMNEXTGENEDITOR_API FCacheWindowCommands : public TCommands +{ +public: + FCacheWindowCommands() + : TCommands(TEXT("CacheGeneratorWindow"), NSLOCTEXT("Contexts", "CacheGeneratorWindow", "Cache Generator Window"), NAME_None, FEditorStyle::GetStyleSetName()) + { + } + + virtual void RegisterCommands() override; + +public: + TSharedPtr OpenPluginWindow; +}; \ No newline at end of file diff --git a/Source/RpmNextGenEditor/Public/UI/SCacheGeneratorWidget.h b/Source/RpmNextGenEditor/Public/UI/SCacheGeneratorWidget.h new file mode 100644 index 0000000..0d2663d --- /dev/null +++ b/Source/RpmNextGenEditor/Public/UI/SCacheGeneratorWidget.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/SCompoundWidget.h" + +class FCacheGenerator; + +class SCacheGeneratorWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SCacheGeneratorWidget) {} + SLATE_END_ARGS() + /** Constructs this widget with InArgs */ + void Construct(const FArguments& InArgs); + +private: + float ItemsPerCategory = 10.0f; + FString CacheUrl; + TSharedPtr CacheGenerator; + + // Callback functions for your buttons + FReply OnGenerateOfflineCacheClicked(); + FReply OnExtractCacheClicked(); + FReply OnOpenLocalCacheFolderClicked(); + FReply OnDownloadRemoteCacheClicked(); + + void OnFetchCacheDataComplete(bool bWasSuccessful); + void OnDownloadRemoteCacheComplete(bool bWasSuccessful); + void OnGenerateLocalCacheCompleted(bool bWasSuccessful); + + TOptional GetItemsPerCategory() const + { + return ItemsPerCategory; + } + + void OnItemsPerCategoryChanged(int32 NewValue) + { + ItemsPerCategory = NewValue; + } + + void OnCacheUrlTextCommitted(const FText& NewText, ETextCommit::Type CommitType) + { + CacheUrl = NewText.ToString(); + } + + void OnItemsPerCategoryChanged(float NewValue); + + + void OnCacheUrlChanged(const FText& NewText); +}; diff --git a/Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h b/Source/RpmNextGenEditor/Public/UI/SCharacterLoaderWidget.h similarity index 68% rename from Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h rename to Source/RpmNextGenEditor/Public/UI/SCharacterLoaderWidget.h index b4a61ce..dc3f2f0 100644 --- a/Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h +++ b/Source/RpmNextGenEditor/Public/UI/SCharacterLoaderWidget.h @@ -2,12 +2,11 @@ #include "CoreMinimal.h" #include "EditorAssetLoader.h" -#include "Api/Assets/AssetLoader.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" class FEditorAssetLoader; -class FAssetLoader; +class FGlbLoader; class SCharacterLoaderWidget : public SCompoundWidget { @@ -15,26 +14,22 @@ class SCharacterLoaderWidget : public SCompoundWidget SLATE_BEGIN_ARGS(SCharacterLoaderWidget) {} SLATE_END_ARGS() - void OnSkeletonSelected(const FAssetData& AssetData); /** Constructs this widget with InArgs */ void Construct(const FArguments& InArgs); -private: - /** Callback for when the button is clicked */ - FReply OnButtonClick(); + void OnSkeletonSelected(const FAssetData& AssetData); - /** Stores the text input by the user */ +private: + FEditorAssetLoader AssetLoader; + TSharedPtr PathTextBox; + USkeleton* SelectedSkeleton = nullptr; + FText PathText; - - /** Callback for when the text in the input field changes */ + + FReply OnButtonClick(); + void OnPathTextChanged(const FText& NewText); - - /** Function that gets called when the button is pressed */ void LoadAsset(const FString& Path); - FEditorAssetLoader AssetLoader; - TSharedPtr PathTextBox; - - // Store the selected skeleton - USkeleton* SelectedSkeleton; + FString GetCurrentSkeletonPath() const; }; diff --git a/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h b/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h index c61bfa9..6cf8dc9 100644 --- a/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h +++ b/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h @@ -12,7 +12,7 @@ #include "Widgets/SCompoundWidget.h" #include "Containers/Map.h" - +class FRpmTextureLoader; struct FDeveloperLoginResponse; class URpmDeveloperSettings; class UDeveloperAuthApi; @@ -45,11 +45,11 @@ class RPMNEXTGENEDITOR_API SRpmDeveloperLoginWidget : public SCompoundWidget EVisibility GetLoginViewVisibility() const; EVisibility GetLoggedInViewVisibility() const; - - FEditorAssetLoader AssetLoader; - TUniquePtr AssetApi; - TUniquePtr DeveloperAccountApi; - TUniquePtr DeveloperAuthApi; + TArray> ActiveLoaders; + TSharedPtr AssetLoader; + TSharedPtr AssetApi; + TSharedPtr DeveloperAccountApi; + TSharedPtr DeveloperAuthApi; static constexpr const TCHAR* CacheKeyEmail = TEXT("Email"); bool bIsLoggedIn = false; bool bIsInitialized = false; @@ -71,9 +71,11 @@ class RPMNEXTGENEDITOR_API SRpmDeveloperLoginWidget : public SCompoundWidget void HandleOrganizationListResponse(const FOrganizationListResponse& Response, bool bWasSuccessful); void HandleApplicationListResponse(const FApplicationListResponse& Response, bool bWasSuccessful); void HandleBaseModelListResponse(const FAssetListResponse& Response, bool bWasSuccessful); - void OnLoadStyleClicked(const FString& StyleId); + void OnLoadBaseModelClicked(const FAsset& Asset); void SetLoggedInState(const bool IsLoggedIn); void PopulateComboBoxItems(const TArray& Items, const FString ActiveItem); void OnComboBoxSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo); + UFUNCTION() + void OnTextureLoaded(UTexture2D* Texture2D, TSharedPtr SImage, TSharedPtr LoaderToRemove); void AddCharacterStyle(const FAsset& StyleAsset); };