diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..6fe1934
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,19 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [0.1.0] 2024-09-03
+
+- Initial release of the Ready Player Me NextGen SDK for Unreal Engine.
+
+### Added
+
+- Custom RPM Developer window
+- BasicUI sample map
+- BasicLoader sample map
+- Character base models can be loaded as UAssets from Developer Window
+
+
+
+
diff --git a/Content/Samples/BasicLoader/BP_RpmActor.uasset b/Content/Samples/BasicLoader/BP_RpmActor.uasset
new file mode 100644
index 0000000..b37b7db
Binary files /dev/null and b/Content/Samples/BasicLoader/BP_RpmActor.uasset differ
diff --git a/Content/Samples/BasicLoader/BP_RpmActor_C.uasset b/Content/Samples/BasicLoader/BP_RpmActor_C.uasset
new file mode 100644
index 0000000..5de0f0e
Binary files /dev/null and b/Content/Samples/BasicLoader/BP_RpmActor_C.uasset differ
diff --git a/Content/Samples/BasicLoader/BasicLoaderSample.umap b/Content/Samples/BasicLoader/BasicLoaderSample.umap
new file mode 100644
index 0000000..f9ca51c
Binary files /dev/null and b/Content/Samples/BasicLoader/BasicLoaderSample.umap differ
diff --git a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset
new file mode 100644
index 0000000..0cc5e93
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI.uasset differ
diff --git a/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset
new file mode 100644
index 0000000..055f579
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/BP_LoaderDemoUI_C.uasset differ
diff --git a/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset b/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset
new file mode 100644
index 0000000..33daf78
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/BP_RpmPreviewActor.uasset differ
diff --git a/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset b/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset
new file mode 100644
index 0000000..64cf9ec
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/Default__BP_LoaderDemoUI_C.uasset differ
diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset
new file mode 100644
index 0000000..2e1f211
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderDemoUI.uasset differ
diff --git a/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset
new file mode 100644
index 0000000..a022f5a
Binary files /dev/null and b/Content/Samples/BasicLoader/Blueprints/WBP_LoaderUI.uasset differ
diff --git a/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset b/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset
new file mode 100644
index 0000000..6253a3c
Binary files /dev/null and b/Content/Samples/BasicLoader/Default__BP_RpmActor_C.uasset differ
diff --git a/Content/Samples/BasicLoader/LoaderSample.umap b/Content/Samples/BasicLoader/LoaderSample.umap
new file mode 100644
index 0000000..87bde8b
Binary files /dev/null and b/Content/Samples/BasicLoader/LoaderSample.umap differ
diff --git a/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap b/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap
new file mode 100644
index 0000000..bb88ae8
Binary files /dev/null and b/Content/Samples/BasicLoader/RpmBasicLoaderSample.umap differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset b/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset
new file mode 100644
index 0000000..2fe6816
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_AssetPanel.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset b/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset
new file mode 100644
index 0000000..9036355
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_BasicUI.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset b/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset
new file mode 100644
index 0000000..a0e8efa
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_BasicUISample.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset b/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset
new file mode 100644
index 0000000..faf6e70
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_CategoryButton.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetButton.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetButton.uasset
new file mode 100644
index 0000000..cd7dc7b
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetButton.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetCard.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetCard.uasset
new file mode 100644
index 0000000..e634b5f
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmAssetCard.uasset differ
diff --git a/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset
new file mode 100644
index 0000000..63c1b44
Binary files /dev/null and b/Content/Samples/BasicUI/Blueprints/WBP_RpmCategoryPanel.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset b/Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset
new file mode 100644
index 0000000..2536a4f
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-baseModel.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset b/Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset
new file mode 100644
index 0000000..cfde8a4
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-bottom.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-custom.uasset b/Content/Samples/BasicUI/Icons/T-rpm-custom.uasset
new file mode 100644
index 0000000..fd63df0
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-custom.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset b/Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset
new file mode 100644
index 0000000..bb1e8e8
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-facial-hair.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset b/Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset
new file mode 100644
index 0000000..19b0ae4
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-glasses.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-hair.uasset b/Content/Samples/BasicUI/Icons/T-rpm-hair.uasset
new file mode 100644
index 0000000..f2daba7
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-hair.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset b/Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset
new file mode 100644
index 0000000..5d7def3
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-shoes.uasset differ
diff --git a/Content/Samples/BasicUI/Icons/T-rpm-top.uasset b/Content/Samples/BasicUI/Icons/T-rpm-top.uasset
new file mode 100644
index 0000000..b6acb8e
Binary files /dev/null and b/Content/Samples/BasicUI/Icons/T-rpm-top.uasset differ
diff --git a/Content/Samples/BasicUI/RpmBasicUISample.umap b/Content/Samples/BasicUI/RpmBasicUISample.umap
new file mode 100644
index 0000000..f81c34e
Binary files /dev/null and b/Content/Samples/BasicUI/RpmBasicUISample.umap differ
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..3c76baa
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,4 @@
+The MIT License (MIT)
+=====================
+
+This software is provided under the MIT License.
diff --git a/README.md b/README.md
index 5d91149..3558607 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,16 @@
-# rpm-unreal-next-gen-sdk
\ No newline at end of file
+# Ready Player Me NextGen SDK for Unreal Engine
+
+This is the Unreal Engine SDK for Ready Player Me NextGen avatars. It allows you to easily integrate our avatars into your Unreal Engine project.
+
+### Requirements
+- Unreal Engine 5.0 or later
+- Visual Studio 2022
+- glTFRuntime plugin available here: https://github.com/rdeioris/glTFRuntime
+- TransientObjectSaver plugin available here: https://github.com/readyplayerme/TransientObjectSaver
+
+### Installation
+1. Download the latest release from the [Releases](https://github.com/readyplayerme/rpm-unreal-next-gen-sdk/releases) page.
+2. Extract the contents of the zip file into your project's Plugins directory. If the Plugins directory does not exist, create it.
+3. Open your project's .sln file in your IDE and build.
+4. Run your project and launch the Unreal Editor.
+5. In the Editor, go to Edit -> Plugins and enable the Ready Player Me NextGen SDK plugin.
\ No newline at end of file
diff --git a/Resources/Icon128.png b/Resources/Icon128.png
new file mode 100644
index 0000000..1231d4a
Binary files /dev/null and b/Resources/Icon128.png differ
diff --git a/Resources/PlaceholderButtonIcon.svg b/Resources/PlaceholderButtonIcon.svg
new file mode 100644
index 0000000..7302447
--- /dev/null
+++ b/Resources/PlaceholderButtonIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/RpmNextGen.uplugin b/RpmNextGen.uplugin
new file mode 100644
index 0000000..2091eb5
--- /dev/null
+++ b/RpmNextGen.uplugin
@@ -0,0 +1,51 @@
+{
+ "FileVersion": 3,
+ "Version": 1,
+ "VersionName": "0.1",
+ "FriendlyName": "RpmNextGen",
+ "Description": "Ready Player Me Next Gen SDK",
+ "Category": "Other",
+ "CreatedBy": "Ready Player Me",
+ "CreatedByURL": "https://readyplayer.me",
+ "DocsURL": "",
+ "MarketplaceURL": "",
+ "SupportURL": "",
+ "CanContainContent": true,
+ "IsBetaVersion": false,
+ "IsExperimentalVersion": false,
+ "Installed": false,
+ "Modules": [
+ {
+ "Name": "RpmNextGen",
+ "Type": "Runtime",
+ "LoadingPhase": "Default",
+ "WhitelistPlatforms": [
+ "Win64",
+ "Mac",
+ "Linux",
+ "Android",
+ "IOS"
+ ]
+ },
+ {
+ "Name": "RpmNextGenEditor",
+ "Type": "Editor",
+ "LoadingPhase": "Default",
+ "WhitelistPlatforms": [
+ "Win64",
+ "Mac",
+ "Linux"
+ ]
+ }
+ ],
+ "Plugins": [
+ {
+ "Name": "glTFRuntime",
+ "Enabled": true
+ },
+ {
+ "Name": "TransientObjectSaver",
+ "Enabled": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp
new file mode 100644
index 0000000..d22540b
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Assets/AssetApi.cpp
@@ -0,0 +1,52 @@
+#include "Api/Assets/AssetApi.h"
+#include "Settings/RpmDeveloperSettings.h"
+#include "Api/Assets/Models/AssetListRequest.h"
+#include "Api/Assets/Models/AssetListResponse.h"
+
+FAssetApi::FAssetApi()
+{
+ OnApiResponse.BindRaw(this, &FAssetApi::HandleListAssetResponse);
+}
+
+
+void FAssetApi::ListAssetsAsync(const FAssetListRequest& Request)
+{
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ ApiBaseUrl = RpmSettings->GetApiBaseUrl();
+ if(RpmSettings->ApplicationId.IsEmpty())
+ {
+ UE_LOG(LogTemp, 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;
+
+ DispatchRawWithAuth(ApiRequest);
+}
+
+void FAssetApi::HandleListAssetResponse(FString Response, bool bWasSuccessful)
+{
+ if(bWasSuccessful)
+ {
+ FAssetListResponse AssetListResponse = FAssetListResponse();
+ if(FJsonObjectConverter::JsonObjectStringToUStruct(Response, &AssetListResponse, 0, 0))
+ {
+ OnListAssetsResponse.ExecuteIfBound(AssetListResponse, true);
+ return;
+ }
+ UE_LOG(LogTemp, Error, TEXT("Failed to parse API response"));
+ }
+ else
+ {
+ UE_LOG(LogTemp, Error, TEXT("API Response was unsuccessful"));
+ }
+ OnListAssetsResponse.ExecuteIfBound(FAssetListResponse(), false);
+}
+
+void FAssetApi::HandleListAssetTypeResponse(FString Response, bool bWasSuccessful)
+{
+}
diff --git a/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp b/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp
new file mode 100644
index 0000000..88f8f09
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Assets/AssetLoader.cpp
@@ -0,0 +1,71 @@
+#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
new file mode 100644
index 0000000..897fbd2
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Auth/ApiKeyAuthStrategy.cpp
@@ -0,0 +1,32 @@
+#include "Api/Auth/ApiKeyAuthStrategy.h"
+#include "Settings/RpmDeveloperSettings.h"
+
+class URpmSettings;
+
+FApiKeyAuthStrategy::FApiKeyAuthStrategy()
+{
+}
+
+void FApiKeyAuthStrategy::AddAuthToRequest(TSharedPtr Request)
+{
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ if(RpmSettings->ApiKey.IsEmpty())
+ {
+ UE_LOG(LogTemp, Error, TEXT("API Key is empty"));
+ OnAuthComplete.ExecuteIfBound(false);
+ return;
+ }
+ Request->Headers.Add(TEXT("X-API-KEY"), RpmSettings->ApiKey);
+ OnAuthComplete.ExecuteIfBound(true);
+}
+
+void FApiKeyAuthStrategy::OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful)
+{
+}
+
+void FApiKeyAuthStrategy::TryRefresh(TSharedPtr Request)
+{
+}
+
+
+
diff --git a/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp b/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp
new file mode 100644
index 0000000..fa43fa2
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Auth/AuthApi.cpp
@@ -0,0 +1,34 @@
+#include "Api/Auth/AuthApi.h"
+#include "Interfaces/IHttpResponse.h"
+#include "Api/Auth/Models/RefreshTokenRequest.h"
+#include "Api/Auth/Models/RefreshTokenResponse.h"
+#include "Settings/RpmDeveloperSettings.h"
+
+FAuthApi::FAuthApi()
+{
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ ApiUrl = FString::Printf(TEXT("%s/refresh"), *RpmSettings->ApiBaseAuthUrl);
+}
+
+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();
+ DispatchRaw(ApiRequest);
+}
+
+void FAuthApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+{
+ FString data = Response->GetContentAsString();
+
+ FRefreshTokenResponse TokenResponse;
+ if (bWasSuccessful && !data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(data, &TokenResponse, 0, 0))
+ {
+ OnRefreshTokenResponse.ExecuteIfBound(TokenResponse, true);
+ return;
+ }
+ OnRefreshTokenResponse.ExecuteIfBound(FRefreshTokenResponse(), false);
+}
diff --git a/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp b/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp
new file mode 100644
index 0000000..8245e5d
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Characters/CharacterApi.cpp
@@ -0,0 +1,102 @@
+#include "Api/Characters/CharacterApi.h"
+#include "HttpModule.h"
+#include "Api/Auth/ApiKeyAuthStrategy.h"
+#include "Api/Characters/Models/CharacterFindByIdRequest.h"
+#include "Api/Characters/Models/CharacterPreviewRequest.h"
+#include "Api/Characters/Models/CharacterUpdateRequest.h"
+#include "Interfaces/IHttpResponse.h"
+#include "Settings/RpmDeveloperSettings.h"
+
+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());
+ }
+}
+
+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"));
+ 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"));
+ 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"));
+ DispatchRawWithAuth(ApiRequest);
+}
+
+FString FCharacterApi::GeneratePreviewUrl(const FCharacterPreviewRequest& Request)
+{
+ FString QueryString = BuildQueryString(Request.Params.Assets);
+ FString url = FString::Printf(TEXT("%s/%s/preview%s"), *BaseUrl, *Request.Id, *QueryString);
+ return url;
+}
+
+void FCharacterApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+{
+ FWebApiWithAuth::OnProcessResponse(Request, Response, bWasSuccessful);
+ bool bSuccess = bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode());
+ if (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;
+ }
+
+ const FString Verb = Request->GetVerb();
+ if (Verb == "POST")
+ {
+ FCharacterCreateResponse CharacterCreateResponse;
+ bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct(
+ Response->GetContentAsString(), &CharacterCreateResponse, 0, 0);
+ OnCharacterCreateResponse.ExecuteIfBound(CharacterCreateResponse, bSuccess);
+ }
+ else if (Verb == "PATCH")
+ {
+ FCharacterUpdateResponse CharacterUpdateResponse;
+ bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct(
+ Response->GetContentAsString(), &CharacterUpdateResponse, 0, 0);
+ OnCharacterUpdateResponse.ExecuteIfBound(CharacterUpdateResponse, bSuccess);
+ }
+ else if (Verb == "GET")
+ {
+ FCharacterFindByIdResponse CharacterFindByIdResponse;
+ bSuccess = bSuccess && FJsonObjectConverter::JsonObjectStringToUStruct(
+ Response->GetContentAsString(), &CharacterFindByIdResponse, 0, 0);
+ OnCharacterFindResponse.ExecuteIfBound(CharacterFindByIdResponse, bSuccess);
+ }
+ else
+ {
+ UE_LOG(LogTemp, Warning, TEXT("Unhandled verb"));
+ }
+}
+
diff --git a/Source/RpmNextGen/Private/Api/Common/WebApi.cpp b/Source/RpmNextGen/Private/Api/Common/WebApi.cpp
new file mode 100644
index 0000000..8d53855
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Common/WebApi.cpp
@@ -0,0 +1,58 @@
+#include "Api/Common/WebApi.h"
+#include "HttpModule.h"
+#include "GenericPlatform/GenericPlatformHttp.h"
+#include "Interfaces/IHttpResponse.h"
+
+FWebApi::FWebApi()
+{
+ Http = &FHttpModule::Get();
+}
+
+FWebApi::~FWebApi() {}
+
+void FWebApi::DispatchRaw(const FApiRequest& Data)
+{
+ TSharedRef Request = Http->CreateRequest();
+ Request->SetURL(Data.Url);
+ Request->SetVerb(Data.GetVerb());
+
+ for (const auto& Header : Data.Headers)
+ {
+ Request->SetHeader(Header.Key, Header.Value);
+ }
+
+ if (!Data.Payload.IsEmpty())
+ {
+ Request->SetContentAsString(Data.Payload);
+ }
+
+ Request->OnProcessRequestComplete().BindRaw(this, &FWebApi::OnProcessResponse);
+ Request->ProcessRequest();
+}
+
+void FWebApi::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+{
+ if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
+ {
+ OnApiResponse.ExecuteIfBound(Response->GetContentAsString(), 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);
+}
+
+FString FWebApi::BuildQueryString(const TMap& QueryParams)
+{
+ FString QueryString;
+ if (QueryParams.Num() > 0)
+ {
+ QueryString.Append(TEXT("?"));
+ for (const auto& Param : QueryParams)
+ {
+ QueryString.Append(FString::Printf(TEXT("%s=%s&"), *FGenericPlatformHttp::UrlEncode(Param.Key), *FGenericPlatformHttp::UrlEncode(Param.Value)));
+ }
+ QueryString.RemoveFromEnd(TEXT("&"));
+ }
+ return QueryString;
+}
diff --git a/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp b/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp
new file mode 100644
index 0000000..5877a38
--- /dev/null
+++ b/Source/RpmNextGen/Private/Api/Common/WebApiWithAuth.cpp
@@ -0,0 +1,75 @@
+#include "Api/Common/WebApiWithAuth.h"
+#include "Interfaces/IHttpResponse.h"
+
+FWebApiWithAuth::FWebApiWithAuth() : ApiRequestData(nullptr), AuthenticationStrategy(nullptr)
+{
+ FWebApi();
+}
+
+FWebApiWithAuth::FWebApiWithAuth(IAuthenticationStrategy* InAuthenticationStrategy) : AuthenticationStrategy(nullptr)
+{
+ SetAuthenticationStrategy(InAuthenticationStrategy);
+}
+
+void FWebApiWithAuth::SetAuthenticationStrategy(IAuthenticationStrategy* InAuthenticationStrategy)
+{
+ AuthenticationStrategy = InAuthenticationStrategy;
+
+ if (AuthenticationStrategy != nullptr)
+ {
+ AuthenticationStrategy->OnAuthComplete.BindRaw(this, &FWebApiWithAuth::OnAuthComplete);
+ AuthenticationStrategy->OnTokenRefreshed.BindRaw(this, &FWebApiWithAuth::OnAuthTokenRefreshed);
+ }
+}
+
+void FWebApiWithAuth::OnAuthComplete(bool bWasSuccessful)
+{
+ if(bWasSuccessful && ApiRequestData != nullptr)
+ {
+ DispatchRaw(*ApiRequestData);
+ return;
+ }
+ OnApiResponse.ExecuteIfBound(TEXT("Auth failed"), false);
+}
+
+void FWebApiWithAuth::OnAuthTokenRefreshed(const FRefreshTokenResponseBody& Response, bool bWasSuccessful)
+{
+ if(bWasSuccessful)
+ {
+ const FString Key = TEXT("Authorization");
+ if (ApiRequestData->Headers.Contains(Key))
+ {
+ ApiRequestData->Headers.Remove(Key);
+ }
+ ApiRequestData->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Response.Token));
+ DispatchRaw(*ApiRequestData);
+ return;
+ }
+
+ OnApiResponse.ExecuteIfBound(TEXT("Auth failed"), false);
+}
+
+void FWebApiWithAuth::DispatchRawWithAuth(FApiRequest& Data)
+{
+ this->ApiRequestData = MakeShared(Data);
+ if (AuthenticationStrategy == nullptr)
+ {
+ DispatchRaw(Data);
+ return;
+ }
+
+ AuthenticationStrategy->AddAuthToRequest(this->ApiRequestData);
+}
+
+void FWebApiWithAuth::OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+{
+ if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
+ {
+ OnApiResponse.ExecuteIfBound(Response->GetContentAsString(), true);
+ return;
+ }
+ if(EHttpResponseCodes::Denied == Response->GetResponseCode() && AuthenticationStrategy != nullptr)
+ {
+ AuthenticationStrategy->TryRefresh(ApiRequestData);
+ }
+}
diff --git a/Source/RpmNextGen/Private/RpmActor.cpp b/Source/RpmNextGen/Private/RpmActor.cpp
new file mode 100644
index 0000000..5a0aef0
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmActor.cpp
@@ -0,0 +1,278 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "RpmActor.h"
+#include "Components/InstancedStaticMeshComponent.h"
+#include "Components/SkeletalMeshComponent.h"
+#include "Animation/AnimSequence.h"
+#include "glTFRuntimeSkeletalMeshComponent.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;
+ AssetRoot = CreateDefaultSubobject(TEXT("AssetRoot"));
+ RootComponent = AssetRoot;
+ RootNodeIndex = INDEX_NONE;
+ bStaticMeshesAsSkeletalOnMorphTargets = true;
+}
+
+// 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()
+{
+ if (RootComponent)
+ {
+ TArray ChildComponents;
+ RootComponent->GetChildrenComponents(true, ChildComponents);
+
+ for (USceneComponent* ChildComponent : ChildComponents)
+ {
+ if (ChildComponent && ChildComponent != RootComponent)
+ {
+ ChildComponent->DestroyComponent();
+ }
+ }
+ }
+}
+
+void ARpmActor::SetupAsset()
+{
+ if (!Asset)
+ {
+ UE_LOG(LogGLTFRuntime, Warning, TEXT("No asset to setup"));
+ return;
+ }
+
+ double LoadingStartTime = FPlatformTime::Seconds();
+
+ if (RootNodeIndex > INDEX_NONE)
+ {
+ FglTFRuntimeNode Node;
+ if (!Asset->GetNode(RootNodeIndex, Node))
+ {
+ return;
+ }
+ AssetRoot = nullptr;
+ ProcessNode(nullptr, NAME_None, Node);
+ }
+ else
+ {
+ TArray Scenes = Asset->GetScenes();
+ for (FglTFRuntimeScene& Scene : Scenes)
+ {
+ USceneComponent* SceneComponent = NewObject(this, *FString::Printf(TEXT("Scene %d"), Scene.Index));
+ SceneComponent->SetupAttachment(RootComponent);
+ SceneComponent->RegisterComponent();
+ AddInstanceComponent(SceneComponent);
+ for (int32 NodeIndex : Scene.RootNodesIndices)
+ {
+ FglTFRuntimeNode Node;
+ if (!Asset->GetNode(NodeIndex, Node))
+ {
+ return;
+ }
+ ProcessNode(SceneComponent, NAME_None, Node);
+ }
+ }
+ }
+
+ for (TPair& Pair : SocketMapping)
+ {
+ for (USkeletalMeshComponent* SkeletalMeshComponent : DiscoveredSkeletalMeshComponents)
+ {
+ if (SkeletalMeshComponent->DoesSocketExist(Pair.Value))
+ {
+ Pair.Key->AttachToComponent(SkeletalMeshComponent, FAttachmentTransformRules::KeepRelativeTransform, Pair.Value);
+ Pair.Key->SetRelativeTransform(FTransform::Identity);
+ break;
+ }
+ }
+ }
+
+ UE_LOG(LogGLTFRuntime, Log, TEXT("Asset loaded in %f seconds"), FPlatformTime::Seconds() - LoadingStartTime);
+}
+
+
+void ARpmActor::ProcessNode(USceneComponent* NodeParentComponent, const FName SocketName, FglTFRuntimeNode& Node)
+{
+ if (Asset->NodeIsBone(Node.Index))
+ {
+ ProcessBoneNode(NodeParentComponent, Node);
+ return;
+ }
+
+ USceneComponent* NewComponent = CreateNewComponent(NodeParentComponent, Node);
+
+ if (!NewComponent)
+ {
+ return;
+ }
+
+ SetupComponentTags(NewComponent, Node, SocketName);
+ ProcessChildNodes(NewComponent, Node);
+}
+
+void ARpmActor::ProcessBoneNode(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node)
+{
+ for (int32 ChildIndex : Node.ChildrenIndices)
+ {
+ FglTFRuntimeNode Child;
+ if (!Asset->GetNode(ChildIndex, Child))
+ {
+ return;
+ }
+ ProcessNode(NodeParentComponent, *Child.Name, Child);
+ }
+}
+
+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)
+{
+ 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);
+ }
+}
+
+void ARpmActor::ProcessChildNodes(USceneComponent* NodeParentComponent, FglTFRuntimeNode& Node)
+{
+ for (int32 ChildIndex : Node.ChildrenIndices)
+ {
+ FglTFRuntimeNode Child;
+ if (!Asset->GetNode(ChildIndex, Child))
+ {
+ return;
+ }
+ ProcessNode(NodeParentComponent, NAME_None, Child);
+ }
+}
+
+// Called every frame
+void ARpmActor::Tick(float DeltaTime)
+{
+ Super::Tick(DeltaTime);
+}
+
+void ARpmActor::ReceiveOnStaticMeshComponentCreated_Implementation(UStaticMeshComponent* StaticMeshComponent, const FglTFRuntimeNode& Node)
+{
+
+}
+
+void ARpmActor::ReceiveOnSkeletalMeshComponentCreated_Implementation(USkeletalMeshComponent* SkeletalMeshComponent, const FglTFRuntimeNode& Node)
+{
+
+}
+
+void ARpmActor::PostUnregisterAllComponents()
+{
+ if (Asset)
+ {
+ Asset->ClearCache();
+ Asset = nullptr;
+ }
+ Super::PostUnregisterAllComponents();
+}
diff --git a/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp b/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp
new file mode 100644
index 0000000..3bc7f19
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmAssetLoaderComponent.cpp
@@ -0,0 +1,52 @@
+// 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
new file mode 100644
index 0000000..54e4c42
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmFunctionLibrary.cpp
@@ -0,0 +1,42 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "RpmFunctionLibrary.h"
+#include "Api/Assets/AssetApi.h"
+#include "Api/Assets/Models/AssetListRequest.h"
+#include "Api/Assets/Models/AssetListResponse.h"
+#include "Api/Auth/ApiKeyAuthStrategy.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);
+}
diff --git a/Source/RpmNextGen/Private/RpmImageLoader.cpp b/Source/RpmNextGen/Private/RpmImageLoader.cpp
new file mode 100644
index 0000000..9a32b8c
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmImageLoader.cpp
@@ -0,0 +1,119 @@
+#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/RpmNextGen.cpp b/Source/RpmNextGen/Private/RpmNextGen.cpp
new file mode 100644
index 0000000..818e442
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmNextGen.cpp
@@ -0,0 +1,22 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "RpmNextGen.h"
+
+DEFINE_LOG_CATEGORY(LogReadyPlayerMe);
+
+#define LOCTEXT_NAMESPACE "FRpmNextGenModule"
+
+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()
+{
+ // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
+ // we call this function before unloading the module.
+}
+
+#undef LOCTEXT_NAMESPACE
+
+IMPLEMENT_MODULE(FRpmNextGenModule, RpmNextGen)
\ No newline at end of file
diff --git a/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp b/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp
new file mode 100644
index 0000000..7ebf553
--- /dev/null
+++ b/Source/RpmNextGen/Private/RpmPreviewLoaderComponent.cpp
@@ -0,0 +1,78 @@
+// 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/Samples/RpmAssetButtonWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp
new file mode 100644
index 0000000..b168236
--- /dev/null
+++ b/Source/RpmNextGen/Private/Samples/RpmAssetButtonWidget.cpp
@@ -0,0 +1,47 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "Samples/RpmAssetButtonWidget.h"
+#include "RpmImageLoader.h"
+#include "Components/Button.h"
+#include "Components/Image.h"
+#include "Components/SizeBox.h"
+
+void URpmAssetButtonWidget::NativeConstruct()
+{
+ Super::NativeConstruct();
+ if (AssetButton)
+ {
+ AssetButton->OnClicked.AddDynamic(this, &URpmAssetButtonWidget::HandleButtonClicked);
+ DefaultColor = AssetButton->BackgroundColor;
+ }
+}
+
+void URpmAssetButtonWidget::InitializeButton(const FAsset& InAssetData, const FVector2D& InImageSize)
+{
+ AssetData = InAssetData;
+
+ if (AssetImage)
+ {
+ AssetImage->SetDesiredSizeOverride(InImageSize);
+
+ FRpmImageLoader ImageLoader;
+ ImageLoader.LoadUImageFromURL(AssetImage, AssetData.IconUrl);
+ }
+}
+
+void URpmAssetButtonWidget::HandleButtonClicked()
+{
+ SetSelected(!bIsSelected);
+ OnAssetButtonClicked.Broadcast(this);
+}
+
+void URpmAssetButtonWidget::SetSelected(const bool bInIsSelected)
+{
+ bIsSelected = bInIsSelected;
+
+ if (AssetButton)
+ {
+ const FLinearColor NewColor = bInIsSelected ? SelectedColor : DefaultColor;
+ AssetButton->SetBackgroundColor(NewColor);
+ }
+}
diff --git a/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp
new file mode 100644
index 0000000..10da885
--- /dev/null
+++ b/Source/RpmNextGen/Private/Samples/RpmAssetCardWidget.cpp
@@ -0,0 +1,32 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "Samples/RpmAssetCardWidget.h"
+#include "RpmImageLoader.h"
+#include "Api/Assets/Models/Asset.h"
+#include "Components/TextBlock.h"
+
+void URpmAssetCardWidget::NativeConstruct()
+{
+ Super::NativeConstruct();
+ this->SetVisibility(ESlateVisibility::Hidden);
+}
+
+void URpmAssetCardWidget::InitializeCard(const FAsset& Asset)
+{
+ this->SetVisibility(ESlateVisibility::Visible);
+ AssetData = Asset;
+
+ AssetCategoryText->SetText(FText::FromString(AssetData.Type));
+ AssetNameText->SetText(FText::FromString(AssetData.Name));
+ AssetNameText->SetVisibility( AssetData.Name.IsEmpty() ? ESlateVisibility::Hidden : ESlateVisibility::Visible );
+ AssetIdText->SetText(FText::FromString(AssetData.Id));
+
+ LoadImage(AssetData.IconUrl);
+}
+
+void URpmAssetCardWidget::LoadImage(const FString& URL)
+{
+ FRpmImageLoader ImageLoader;
+ ImageLoader.LoadUImageFromURL(AssetImage, URL);
+}
+
diff --git a/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp
new file mode 100644
index 0000000..a8dc7a4
--- /dev/null
+++ b/Source/RpmNextGen/Private/Samples/RpmAssetPanelWidget.cpp
@@ -0,0 +1,117 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Samples/RpmAssetPanelWidget.h"
+#include "Api/Assets/AssetApi.h"
+#include "Api/Assets/Models/AssetListRequest.h"
+#include "Api/Auth/ApiKeyAuthStrategy.h"
+#include "Components/PanelWidget.h"
+#include "Components/SizeBox.h"
+#include "Samples/RpmAssetButtonWidget.h"
+#include "Settings/RpmDeveloperSettings.h"
+
+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)
+ {
+ CreateButtonsFromAssets(AssetListResponse.Data);
+ return;
+ }
+ UE_LOG(LogTemp, Error, TEXT("Failed to fetch assets"));
+}
+
+void URpmAssetPanelWidget::CreateButtonsFromAssets(TArray Assets)
+{
+ for (auto Asset : Assets)
+ {
+ CreateButton(Asset);
+ }
+ UE_LOG(LogTemp, Warning, TEXT("No assets found") );
+}
+
+void URpmAssetPanelWidget::ClearAllButtons()
+{
+ if(!AssetButtons.IsEmpty())
+ {
+ AssetButtons.Empty();
+ }
+ SelectedAssetButton = nullptr;
+}
+
+void URpmAssetPanelWidget::UpdateSelectedButton(URpmAssetButtonWidget* AssetButton)
+{
+ if(SelectedAssetButton && SelectedAssetButton != AssetButton)
+ {
+ SelectedAssetButton->SetSelected(false);
+ }
+ SelectedAssetButton = AssetButton;
+}
+
+void URpmAssetPanelWidget::CreateButton(const FAsset& AssetData)
+{
+ if (AssetButtonBlueprint)
+ {
+ UWorld* World = GetWorld();
+
+ if (World)
+ {
+ if (URpmAssetButtonWidget* AssetButtonInstance = CreateWidget(World, AssetButtonBlueprint))
+ {
+ USizeBox* ButtonSizeBox = NewObject(this);
+ if (ButtonSizeBox && ButtonContainer)
+ {
+ ButtonSizeBox->SetWidthOverride(ButtonSize.X);
+ ButtonSizeBox->SetHeightOverride(ButtonSize.Y);
+ ButtonSizeBox->AddChild(AssetButtonInstance);
+ ButtonContainer->AddChild(ButtonSizeBox);
+ }
+
+ AssetButtonInstance->InitializeButton(AssetData, ImageSize);
+ AssetButtons.Add(AssetButtonBlueprint);
+ AssetButtonInstance->OnAssetButtonClicked.AddDynamic(this, &URpmAssetPanelWidget::OnAssetButtonClicked);
+ }
+ }
+ }
+ else
+ {
+ UE_LOG(LogTemp, Error, TEXT("AssetButtonBlueprint is not set!"));
+ }
+}
+
+void URpmAssetPanelWidget::OnAssetButtonClicked(const URpmAssetButtonWidget* AssetButton)
+{
+ UpdateSelectedButton(const_cast(AssetButton));
+ OnAssetSelected.Broadcast(AssetButton->GetAssetData());
+}
+
+void URpmAssetPanelWidget::LoadAssetsOfType(const FString& AssetType)
+{
+ if (!AssetApi.IsValid())
+ {
+ UE_LOG(LogTemp, 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);
+ AssetApi->ListAssetsAsync(AssetListRequest);
+}
diff --git a/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp
new file mode 100644
index 0000000..119bd8c
--- /dev/null
+++ b/Source/RpmNextGen/Private/Samples/RpmCategoryButtonWidget.cpp
@@ -0,0 +1,78 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "Samples/RpmCategoryButtonWidget.h"
+#include "Components/Button.h"
+#include "Components/Image.h"
+
+void URpmCategoryButtonWidget::NativeConstruct()
+{
+ Super::NativeConstruct();
+
+ if (CategoryButton)
+ {
+ CategoryButton->OnClicked.AddDynamic(this, &URpmCategoryButtonWidget::HandleButtonClicked);
+ DefaultColor = CategoryButton->BackgroundColor;
+ }
+ if (CategoryImageTexture)
+ {
+ CategoryImage->SetBrushFromTexture(CategoryImageTexture);
+ }
+}
+
+void URpmCategoryButtonWidget::InitializeButton(FString Category, UTexture2D* Image)
+{
+ AssetCategoryName = Category;
+
+ if (CategoryImage)
+ {
+ CategoryImageTexture = Image;
+ CategoryImage->SetBrushFromTexture(CategoryImageTexture);
+ }
+}
+
+void URpmCategoryButtonWidget::SetSelected(bool bIsSelected)
+{
+ if (CategoryButton)
+ {
+ const FLinearColor NewColor = bIsSelected ? SelectedColor : DefaultColor;
+ CategoryButton->SetBackgroundColor(NewColor);
+ }
+}
+
+void URpmCategoryButtonWidget::HandleButtonClicked()
+{
+ SetSelected(true);
+
+ OnCategoryClicked.Broadcast(this);
+}
+
+#if WITH_EDITOR
+// This function is called when a property is changed in the editor, it enables the CategoryImageTexture to be updated in the editor
+void URpmCategoryButtonWidget::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
+{
+ Super::PostEditChangeProperty(PropertyChangedEvent);
+
+ FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
+
+ // If the property that was changed is the CategoryImageTexture apply the new texture to the CategoryImage
+ if (PropertyName == GET_MEMBER_NAME_CHECKED(URpmCategoryButtonWidget, CategoryImageTexture))
+ {
+ if (CategoryImage && CategoryImageTexture)
+ {
+ CategoryImage->SetBrushFromTexture(CategoryImageTexture);
+ SynchronizeProperties();
+ }
+
+ }
+}
+#endif
+
+// This is also required to update the CategoryImageTexture in the editor
+void URpmCategoryButtonWidget::SynchronizeProperties()
+{
+ Super::SynchronizeProperties();
+ if (CategoryImage && CategoryImageTexture)
+ {
+ CategoryImage->SetBrushFromTexture(CategoryImageTexture);
+ }
+}
diff --git a/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp b/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp
new file mode 100644
index 0000000..b25abcd
--- /dev/null
+++ b/Source/RpmNextGen/Private/Samples/RpmCategoryPanelWidget.cpp
@@ -0,0 +1,42 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "Samples/RpmCategoryPanelWidget.h"
+#include "Blueprint/WidgetTree.h"
+#include "Samples/RpmCategoryButtonWidget.h"
+
+void URpmCategoryPanelWidget::NativeConstruct()
+{
+ Super::NativeConstruct();
+ InitializeCategoryButtons();
+}
+
+void URpmCategoryPanelWidget::InitializeCategoryButtons()
+{
+ TArray Widgets;
+ WidgetTree->GetAllWidgets(Widgets);
+
+ for (UWidget* Widget : Widgets)
+ {
+ if (URpmCategoryButtonWidget* CategoryButton = Cast(Widget))
+ {
+ CategoryButton->InitializeButton(CategoryButton->AssetCategoryName, CategoryButton->CategoryImageTexture);
+ CategoryButton->OnCategoryClicked.AddDynamic(this, &URpmCategoryPanelWidget::OnCategoryButtonClicked);
+ }
+ }
+}
+
+void URpmCategoryPanelWidget::UpdateSelectedButton(URpmCategoryButtonWidget* CategoryButton)
+{
+ if(SelectedCategoryButton && SelectedCategoryButton != CategoryButton)
+ {
+ SelectedCategoryButton->SetSelected(false);
+ }
+ SelectedCategoryButton = CategoryButton;
+}
+
+void URpmCategoryPanelWidget::OnCategoryButtonClicked(URpmCategoryButtonWidget* CategoryButton)
+{
+ UpdateSelectedButton(CategoryButton);
+ OnCategorySelected.Broadcast(CategoryButton->AssetCategoryName);
+}
+
diff --git a/Source/RpmNextGen/Private/Settings/RpmDeveloperSettings.cpp b/Source/RpmNextGen/Private/Settings/RpmDeveloperSettings.cpp
new file mode 100644
index 0000000..51fb4b9
--- /dev/null
+++ b/Source/RpmNextGen/Private/Settings/RpmDeveloperSettings.cpp
@@ -0,0 +1,60 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "Settings/RpmDeveloperSettings.h"
+
+URpmDeveloperSettings::URpmDeveloperSettings() : ApiBaseUrl(TEXT("https://api.readyplayer.me")), ApiBaseAuthUrl(TEXT("https://readyplayer.me/api/auth"))
+{
+ LoadConfig();
+}
+
+void URpmDeveloperSettings::PostInitProperties()
+{
+ Super::PostInitProperties();
+}
+
+void URpmDeveloperSettings::PreSave(const ITargetPlatform* TargetPlatform)
+{
+ Super::PreSave(TargetPlatform);
+
+ if(ApiKey.IsEmpty() && ApiProxyUrl.IsEmpty() && !ApplicationId.IsEmpty())
+ {
+ return;
+ }
+
+ // Ensure settings are saved before the build
+ SaveConfig(CPF_Config, *GetDefaultConfigFilename());
+}
+
+#if WITH_EDITOR
+void URpmDeveloperSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
+{
+ Super::PostEditChangeProperty(PropertyChangedEvent);
+ SaveConfig(CPF_Config, *GetDefaultConfigFilename());
+}
+#endif // WITH_EDITOR
+
+
+void URpmDeveloperSettings::SetupDemoAccount()
+{
+ ApplicationId = DemoAppId;
+ ApiProxyUrl = DemoProxyUrl;
+ SaveConfig(CPF_Config, *GetDefaultConfigFilename());
+}
+
+void URpmDeveloperSettings::Reset()
+{
+ if(ApplicationId == DemoAppId)
+ {
+ ApplicationId = TEXT("");
+ }
+ if(ApiProxyUrl == DemoProxyUrl)
+ {
+ ApiProxyUrl = TEXT("");
+ }
+ SaveConfig(CPF_Config, *GetDefaultConfigFilename());
+}
+
+FString URpmDeveloperSettings::GetApiBaseUrl() const
+{
+ return ApiProxyUrl.IsEmpty() ? ApiBaseUrl : ApiProxyUrl;
+}
diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetApi.h b/Source/RpmNextGen/Public/Api/Assets/AssetApi.h
new file mode 100644
index 0000000..a8d7548
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Assets/AssetApi.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "Api/Common/WebApiWithAuth.h"
+
+struct FAssetListRequest;
+struct FAssetListResponse;
+
+DECLARE_DELEGATE_TwoParams(FOnListAssetsResponse, const FAssetListResponse&, bool);
+DECLARE_DELEGATE_TwoParams(FOnListAssetTypeResponse, const FAssetListResponse&, bool);
+
+class RPMNEXTGEN_API FAssetApi : public FWebApiWithAuth
+{
+public:
+ FAssetApi();
+ void ListAssetsAsync(const FAssetListRequest& Request);
+ FOnListAssetsResponse OnListAssetsResponse;
+ FOnListAssetTypeResponse OnListAssetTypeResponse;
+private:
+ void HandleListAssetResponse(FString Response, bool bWasSuccessful);
+ void HandleListAssetTypeResponse(FString Response, bool bWasSuccessful);
+
+ FString ApiBaseUrl;
+};
diff --git a/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h b/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h
new file mode 100644
index 0000000..75cf1c3
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Assets/AssetLoader.h
@@ -0,0 +1,34 @@
+#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/Models/Asset.h b/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h
new file mode 100644
index 0000000..edffd8a
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Assets/Models/Asset.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "Asset.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FAsset : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "id"))
+ FString Id;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "name"))
+ 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", meta = (JsonName = "createdAt"))
+ 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;
+};
\ 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
new file mode 100644
index 0000000..982517c
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListRequest.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "AssetListRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FAssetListQueryParams
+{
+ 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 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;
+};
+
+inline FString FAssetListRequest::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;
+}
diff --git a/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h
new file mode 100644
index 0000000..d31cd77
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Assets/Models/AssetListResponse.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Assets/Models/Asset.h"
+#include "AssetListResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FAssetListResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ TArray Data;
+ bool bSuccess;
+ int64 Status;
+ FString Error;
+};
diff --git a/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h b/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h
new file mode 100644
index 0000000..273693d
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/ApiKeyAuthStrategy.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Auth/IAuthenticationStrategy.h"
+#include "Api/Common/WebApi.h"
+
+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;
+};
diff --git a/Source/RpmNextGen/Public/Api/Auth/ApiRequest.h b/Source/RpmNextGen/Public/Api/Auth/ApiRequest.h
new file mode 100644
index 0000000..45ba306
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/ApiRequest.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/ObjectMacros.h"
+#include "ApiRequest.generated.h"
+
+UENUM(BlueprintType)
+enum ERequestMethod {
+ GET,
+ POST,
+ PUT,
+ DELETE,
+ PATCH
+};
+
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FApiRequest
+{
+ GENERATED_BODY()
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Url;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ TEnumAsByte Method = GET;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ TMap Headers;
+
+ FString Payload;
+
+ FString GetVerb() const
+ {
+ switch (Method)
+ {
+ case GET: default:
+ return TEXT("GET");
+ case POST:
+ return TEXT("POST");
+ case PUT:
+ return TEXT("PUT");
+ case PATCH:
+ return TEXT("PATCH");
+ case DELETE:
+ return TEXT("DELETE");
+ }
+ }
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FApiRequestBody
+{
+ GENERATED_BODY()
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ FString Data;
+};
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Auth/AuthApi.h b/Source/RpmNextGen/Public/Api/Auth/AuthApi.h
new file mode 100644
index 0000000..8093000
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/AuthApi.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/WebApi.h"
+
+struct FRefreshTokenResponse;
+struct FRefreshTokenRequest;
+
+DECLARE_DELEGATE_TwoParams(FOnRefreshTokenResponse, const FRefreshTokenResponse&, bool);
+
+class RPMNEXTGEN_API FAuthApi : public FWebApi
+{
+public:
+ FAuthApi();
+ void RefreshToken(const FRefreshTokenRequest& Request);
+ FOnRefreshTokenResponse OnRefreshTokenResponse;
+ virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) override;
+
+private:
+ FString ApiUrl;
+};
diff --git a/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h b/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h
new file mode 100644
index 0000000..651827b
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/IAuthenticationStrategy.h
@@ -0,0 +1,20 @@
+#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);
+
+class RPMNEXTGEN_API IAuthenticationStrategy
+{
+public:
+ FOnAuthComplete OnAuthComplete;
+ 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;
+};
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenRequest.h b/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenRequest.h
new file mode 100644
index 0000000..a6c08ce
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenRequest.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "RefreshTokenRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FRefreshTokenRequestBody
+{
+ GENERATED_BODY()
+
+public:
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Token;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString RefreshToken;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FRefreshTokenRequestBody& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FRefreshTokenRequest
+{
+ GENERATED_BODY()
+
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FRefreshTokenRequestBody Data;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FRefreshTokenRequest& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+};
diff --git a/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenResponse.h b/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenResponse.h
new file mode 100644
index 0000000..8a2f7b7
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Auth/Models/RefreshTokenResponse.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "RefreshTokenResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FRefreshTokenResponseBody : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me|Auth|Response")
+ FString Token;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me|Auth|Response")
+ FString RefreshToken;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FRefreshTokenResponseBody& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FRefreshTokenResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FRefreshTokenResponseBody Data;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FRefreshTokenResponse& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+};
+
+
diff --git a/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h b/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h
new file mode 100644
index 0000000..2dd5d0c
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/CharacterApi.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "Api/Common/WebApi.h"
+#include "Api/Common/WebApiWithAuth.h"
+#include "Models/CharacterCreateRequest.h"
+#include "Models/CharacterCreateResponse.h"
+#include "Models/CharacterFindByIdRequest.h"
+#include "Models/CharacterFindByIdResponse.h"
+#include "Models/CharacterPreviewRequest.h"
+#include "Models/CharacterUpdateRequest.h"
+#include "Models/CharacterUpdateResponse.h"
+
+DECLARE_DELEGATE_TwoParams(FOnCharacterCreateResponse, FCharacterCreateResponse, bool);
+DECLARE_DELEGATE_TwoParams(FOnCharacterUpdatResponse, FCharacterUpdateResponse, bool);
+DECLARE_DELEGATE_TwoParams(FOnCharacterFindResponse, FCharacterFindByIdResponse, bool);
+
+class RPMNEXTGEN_API FCharacterApi : public TSharedFromThis, public FWebApiWithAuth
+{
+public:
+ 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;
+
+private:
+ FString BaseUrl;
+};
+
+template
+FString FCharacterApi::ConvertToJsonString(const T& Data)
+{
+ FString JsonString;
+ FJsonObjectConverter::UStructToJsonObjectString(Data, JsonString);
+ return JsonString;
+}
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateRequest.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateRequest.h
new file mode 100644
index 0000000..fefa7f9
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateRequest.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "CharacterCreateRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterCreateRequestBody
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "applicationId"))
+ FString ApplicationId;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "assets"))
+ TMap Assets;
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterCreateRequest
+{
+ GENERATED_BODY()
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FCharacterCreateRequestBody Data;
+};
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h
new file mode 100644
index 0000000..321ca68
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterCreateResponse.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "RpmCharacter.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "CharacterCreateResponse.generated.h"
+
+USTRUCT()
+struct RPMNEXTGEN_API FCharacterCreateResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ FRpmCharacter Data;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdRequest.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdRequest.h
new file mode 100644
index 0000000..24ee0be
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdRequest.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "CharacterFindByIdRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct FCharacterFindByIdRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Id;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdResponse.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdResponse.h
new file mode 100644
index 0000000..aa9ed26
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterFindByIdResponse.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "RpmCharacter.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "CharacterFindByIdResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterFindByIdResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ FRpmCharacter Data;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterPreviewRequest.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterPreviewRequest.h
new file mode 100644
index 0000000..71fda5a
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterPreviewRequest.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "CharacterPreviewRequest.generated.h"
+
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterPreviewQueryParams
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "assets"))
+ TMap Assets;
+};
+
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterPreviewRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Id;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FCharacterPreviewQueryParams Params;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateRequest.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateRequest.h
new file mode 100644
index 0000000..c73cfea
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateRequest.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "CharacterUpdateRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterUpdateRequestBody
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "assets"))
+ TMap Assets;
+};
+
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterUpdateRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "applicationId"))
+ FString Id;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FCharacterUpdateRequestBody Payload;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateResponse.h b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateResponse.h
new file mode 100644
index 0000000..5b01792
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/CharacterUpdateResponse.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "RpmCharacter.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "CharacterUpdateResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FCharacterUpdateResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ FRpmCharacter Data;
+};
diff --git a/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h b/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h
new file mode 100644
index 0000000..eec8bfd
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Characters/Models/RpmCharacter.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "RpmCharacter.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FRpmCharacter
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "id"))
+ FString Id;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "createdByApplicationId"))
+ FString CreatedByApplicationId;
+
+ 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", meta = (JsonName = "assets"))
+ TMap Assets;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "createdAt"))
+ FDateTime CreatedAt;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "updatedAt"))
+ FDateTime UpdatedAt;
+};
+
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Common/Models/ApiResponse.h b/Source/RpmNextGen/Public/Api/Common/Models/ApiResponse.h
new file mode 100644
index 0000000..8411e98
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Common/Models/ApiResponse.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "ApiResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGEN_API FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ bool IsSuccess = true;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ int64 Status = 200;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Error;
+};
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Common/WebApi.h b/Source/RpmNextGen/Public/Api/Common/WebApi.h
new file mode 100644
index 0000000..f611615
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Common/WebApi.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "Api/Auth/ApiRequest.h"
+#include "Interfaces/IHttpRequest.h"
+#include "Misc/ScopeExit.h"
+
+class FHttpModule;
+DECLARE_DELEGATE_TwoParams(FOnWebApiResponse, FString, bool);
+
+class RPMNEXTGEN_API FWebApi
+{
+public:
+ FWebApi();
+ virtual ~FWebApi();
+
+ FOnWebApiResponse OnApiResponse;
+
+protected:
+ void DispatchRaw(
+ const FApiRequest& Data
+ );
+
+ virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
+
+ FString BuildQueryString(const TMap& QueryParams);
+
+ template
+ FString ConvertToJsonString(const T& Data);
+
+ FHttpModule* Http;
+};
+
+template
+FString FWebApi::ConvertToJsonString(const T& Data)
+{
+ FString JsonString;
+ FJsonObjectConverter::UStructToJsonObjectString(Data, JsonString);
+ return JsonString;
+}
\ No newline at end of file
diff --git a/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h b/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h
new file mode 100644
index 0000000..dd52dca
--- /dev/null
+++ b/Source/RpmNextGen/Public/Api/Common/WebApiWithAuth.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "WebApi.h"
+#include "Api/Auth/IAuthenticationStrategy.h"
+
+
+class RPMNEXTGEN_API FWebApiWithAuth : public FWebApi
+{
+public:
+ FWebApiWithAuth();
+ FWebApiWithAuth(IAuthenticationStrategy* InAuthenticationStrategy);
+
+ void SetAuthenticationStrategy(IAuthenticationStrategy* InAuthenticationStrategy);
+
+ void OnAuthComplete(bool bWasSuccessful);
+ void OnAuthTokenRefreshed(const FRefreshTokenResponseBody& Response, bool bWasSuccessful);
+
+ void DispatchRawWithAuth(FApiRequest& Data);
+protected:
+ TSharedPtr ApiRequestData;
+
+ virtual void OnProcessResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) override;
+
+private:
+ IAuthenticationStrategy* AuthenticationStrategy;
+};
diff --git a/Source/RpmNextGen/Public/RpmActor.h b/Source/RpmNextGen/Public/RpmActor.h
new file mode 100644
index 0000000..5bd9bb0
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmActor.h
@@ -0,0 +1,83 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "GameFramework/Actor.h"
+#include "glTFRuntimeAsset.h"
+#include "RpmActor.generated.h"
+
+UCLASS()
+class RPMNEXTGEN_API ARpmActor : public AActor
+{
+ GENERATED_BODY()
+
+public:
+ // Sets default values for this actor's properties
+ ARpmActor();
+
+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;
+
+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")
+ FglTFRuntimeStaticMeshConfig StaticMeshConfig;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime")
+ 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;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (ExposeOnSpawn = true), Category = "Ready Player Me|glTFRuntime")
+ bool bStaticMeshesAsSkeletalOnMorphTargets;
+
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FglTFRuntimeAssetActorNodeProcessed, const FglTFRuntimeNode&, USceneComponent*);
+ FglTFRuntimeAssetActorNodeProcessed OnNodeProcessed;
+
+ virtual void PostUnregisterAllComponents() override;
+
+ UFUNCTION(BlueprintCallable, Category = "Ready Player Me")
+ virtual void LoadGltfAsset(UglTFRuntimeAsset* GltfAsset);
+ void ClearLoadedComponents();
+
+ virtual void SetupAsset();
+
+private:
+ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category="Ready Player Me|glTFRuntime")
+ 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);
+};
diff --git a/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h b/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h
new file mode 100644
index 0000000..2c4b1f5
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmAssetLoaderComponent.h
@@ -0,0 +1,39 @@
+// 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/RpmFunctionLibrary.h b/Source/RpmNextGen/Public/RpmFunctionLibrary.h
new file mode 100644
index 0000000..e0ffbdc
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmFunctionLibrary.h
@@ -0,0 +1,22 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Kismet/BlueprintFunctionLibrary.h"
+#include "RpmFunctionLibrary.generated.h"
+
+DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAssetIdFetched, FString, AssetId);
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGEN_API URpmFunctionLibrary : public UBlueprintFunctionLibrary
+{
+ GENERATED_BODY()
+
+public:
+ UFUNCTION(BlueprintCallable, Category = "ReadyPlayerMe", meta = (WorldContext = "WorldContextObject"))
+ static void FetchFirstAssetId(UObject* WorldContextObject, const FString& AssetType, FOnAssetIdFetched OnAssetIdFetched);
+};
diff --git a/Source/RpmNextGen/Public/RpmImageLoader.h b/Source/RpmNextGen/Public/RpmImageLoader.h
new file mode 100644
index 0000000..43d4619
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmImageLoader.h
@@ -0,0 +1,21 @@
+#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/RpmNextGen.h b/Source/RpmNextGen/Public/RpmNextGen.h
new file mode 100644
index 0000000..48fa711
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmNextGen.h
@@ -0,0 +1,17 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+
+RPMNEXTGEN_API DECLARE_LOG_CATEGORY_EXTERN(LogReadyPlayerMe, Log, All);
+
+class FRpmNextGenModule : public IModuleInterface
+{
+public:
+
+ /** IModuleInterface implementation */
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+};
diff --git a/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h b/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h
new file mode 100644
index 0000000..e52e9ec
--- /dev/null
+++ b/Source/RpmNextGen/Public/RpmPreviewLoaderComponent.h
@@ -0,0 +1,49 @@
+// 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/Samples/RpmAssetButtonWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h
new file mode 100644
index 0000000..715f93e
--- /dev/null
+++ b/Source/RpmNextGen/Public/Samples/RpmAssetButtonWidget.h
@@ -0,0 +1,58 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Assets/Models/Asset.h"
+#include "Blueprint/UserWidget.h"
+#include "RpmAssetButtonWidget.generated.h"
+
+class USizeBox;
+class UBorder;
+class UImage;
+class UButton;
+
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAssetButtonClicked, const URpmAssetButtonWidget*, AssetData);
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGEN_API URpmAssetButtonWidget : public UUserWidget
+{
+ GENERATED_BODY()
+
+public:
+
+ UPROPERTY(meta = (BindWidget))
+ UButton* AssetButton;
+
+ UPROPERTY(meta = (BindWidget))
+ UImage* AssetImage;
+
+ UPROPERTY(BlueprintAssignable, Category = "Events")
+ FOnAssetButtonClicked OnAssetButtonClicked;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Button" )
+ FLinearColor SelectedColor = FLinearColor::Yellow;
+
+ UFUNCTION(BlueprintCallable, Category = "Ready Player Me|Asset Button")
+ virtual void InitializeButton(const FAsset& InAssetData, const FVector2D& InImageSize);
+
+ UFUNCTION(BlueprintCallable, Category = "Ready Player Me|Asset Button")
+ virtual void SetSelected(const bool bInIsSelected);
+
+ FAsset GetAssetData() const { return AssetData; }
+protected:
+ virtual void NativeConstruct() override;
+
+private:
+ FLinearColor DefaultColor;
+
+ FAsset AssetData;
+
+ UFUNCTION()
+ virtual void HandleButtonClicked();
+
+ bool bIsSelected;
+};
diff --git a/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h
new file mode 100644
index 0000000..5d9c3d2
--- /dev/null
+++ b/Source/RpmNextGen/Public/Samples/RpmAssetCardWidget.h
@@ -0,0 +1,41 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Assets/Models/Asset.h"
+#include "Blueprint/UserWidget.h"
+#include "RpmAssetCardWidget.generated.h"
+
+class UImage;
+class UTextBlock;
+
+UCLASS()
+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;
+
+ UPROPERTY(meta = (BindWidget))
+ UImage* AssetImage;
+
+ UPROPERTY(meta = (BindWidget))
+ UTextBlock* AssetNameText;
+
+ UPROPERTY(meta = (BindWidget))
+ UTextBlock* AssetIdText;
+
+private:
+ FAsset AssetData;
+};
diff --git a/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h b/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h
new file mode 100644
index 0000000..0b50b3a
--- /dev/null
+++ b/Source/RpmNextGen/Public/Samples/RpmAssetPanelWidget.h
@@ -0,0 +1,66 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Assets/Models/AssetListResponse.h"
+#include "Blueprint/UserWidget.h"
+#include "RpmAssetPanelWidget.generated.h"
+
+class FAssetApi;
+struct FAsset;
+class URpmAssetButtonWidget;
+
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAssetSelected, const FAsset&, AssetData);
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGEN_API URpmAssetPanelWidget : public UUserWidget
+{
+ GENERATED_BODY()
+public:
+ virtual void NativeConstruct() override;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Panel" )
+ TSubclassOf AssetButtonBlueprint;
+
+ UPROPERTY(meta = (BindWidget))
+ UPanelWidget* ButtonContainer;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Panel")
+ URpmAssetButtonWidget* SelectedAssetButton;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Button" )
+ FVector2D ButtonSize;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Button" )
+ FVector2D ImageSize;
+
+ UPROPERTY(BlueprintAssignable, Category = "Events" )
+ FOnAssetSelected OnAssetSelected;
+
+ UFUNCTION(BlueprintCallable, Category = "Asset Panel")
+ void CreateButtonsFromAssets(TArray Assets);
+
+ UFUNCTION(BlueprintCallable, Category = "Asset Panel")
+ void ClearAllButtons();
+
+ UFUNCTION(BlueprintCallable, Category = "Asset Panel")
+ void UpdateSelectedButton(URpmAssetButtonWidget* AssetButton);
+
+ UFUNCTION()
+ void OnAssetButtonClicked(const URpmAssetButtonWidget* AssetButtonWidget);
+
+ UFUNCTION()
+ void OnAssetListResponse(const FAssetListResponse& AssetListResponse, bool bWasSuccessful);
+
+ UFUNCTION(BlueprintCallable, Category = "Asset Panel")
+ void LoadAssetsOfType(const FString& AssetType);
+
+ void CreateButton(const FAsset& AssetData);
+private:
+ TArray> AssetButtons;
+ TSharedPtr AssetApi;
+};
diff --git a/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h b/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h
new file mode 100644
index 0000000..3e22e0f
--- /dev/null
+++ b/Source/RpmNextGen/Public/Samples/RpmCategoryButtonWidget.h
@@ -0,0 +1,61 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Blueprint/UserWidget.h"
+#include "RpmCategoryButtonWidget.generated.h"
+
+class UImage;
+class UBorder;
+class UButton;
+
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCategoryClicked, URpmCategoryButtonWidget*, CategoryButton);
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGEN_API URpmCategoryButtonWidget : public UUserWidget
+{
+ GENERATED_BODY()
+
+public:
+ virtual void NativeConstruct() override;
+
+ UPROPERTY(meta = (BindWidget))
+ UImage* CategoryImage;
+
+ UPROPERTY(meta = (BindWidget))
+ UButton* CategoryButton;
+
+ UPROPERTY(BlueprintAssignable, Category = "Events")
+ FOnCategoryClicked OnCategoryClicked;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Button" )
+ FLinearColor SelectedColor = FLinearColor::Yellow;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Button")
+ UTexture2D* CategoryImageTexture;
+
+ UFUNCTION(BlueprintCallable, Category = "Category Button")
+ virtual void InitializeButton(FString Category, UTexture2D* Image);
+
+ UFUNCTION(BlueprintCallable, Category = "Category Button")
+ virtual void SetSelected(bool bIsSelected);
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Button" )
+ FString AssetCategoryName;
+
+#if WITH_EDITOR
+ virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
+#endif
+
+ virtual void SynchronizeProperties() override;
+
+private:
+ UFUNCTION()
+ virtual void HandleButtonClicked();
+
+ FLinearColor DefaultColor;
+};
diff --git a/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h b/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h
new file mode 100644
index 0000000..feae846
--- /dev/null
+++ b/Source/RpmNextGen/Public/Samples/RpmCategoryPanelWidget.h
@@ -0,0 +1,38 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Blueprint/UserWidget.h"
+#include "RpmCategoryPanelWidget.generated.h"
+
+class URpmCategoryButtonWidget;
+
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCategorySelected, const FString&, CategoryName);
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGEN_API URpmCategoryPanelWidget : public UUserWidget
+{
+ GENERATED_BODY()
+
+public:
+ virtual void NativeConstruct() override;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category Panel")
+ URpmCategoryButtonWidget* SelectedCategoryButton;
+
+ UPROPERTY(BlueprintAssignable, Category = "Events")
+ FOnCategorySelected OnCategorySelected;
+
+ UFUNCTION(BlueprintCallable, Category = "Category Panel")
+ virtual void UpdateSelectedButton(URpmCategoryButtonWidget* CategoryButton);
+
+ UFUNCTION()
+ virtual void OnCategoryButtonClicked(URpmCategoryButtonWidget* CategoryButton);
+
+private:
+ void InitializeCategoryButtons();
+};
diff --git a/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h b/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h
new file mode 100644
index 0000000..ecaeeaf
--- /dev/null
+++ b/Source/RpmNextGen/Public/Settings/RpmDeveloperSettings.h
@@ -0,0 +1,52 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Engine/DeveloperSettings.h"
+#include "RpmDeveloperSettings.generated.h"
+
+/**
+ *
+ */
+UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Ready Player Me"))
+class RPMNEXTGEN_API URpmDeveloperSettings : public UDeveloperSettings
+{
+ GENERATED_BODY()
+
+ UPROPERTY(VisibleAnywhere, Config, Category = "Auth Settings", meta = (ReadOnly = "true", ToolTip = "Base URL for requests."))
+ FString ApiBaseUrl;
+
+public:
+ URpmDeveloperSettings();
+
+ UPROPERTY(VisibleAnywhere, Config, Category = "Auth Settings", meta = (ReadOnly = "true", ToolTip = "Base URL for authentication requests."))
+ FString ApiBaseAuthUrl;
+
+ UPROPERTY(EditAnywhere, Config, Category = "Auth Settings", meta = (ToolTip = "Application ID used for authentication."))
+ FString ApplicationId;
+
+ UPROPERTY(EditAnywhere, Config, Category = "Auth Settings", meta = (ToolTip = "API key used for authentication."))
+ FString ApiKey;
+
+ UPROPERTY(EditAnywhere, Config, Category = "Auth Settings", meta = (ToolTip = "Proxy URL for API requests. If empty, the base URL will be used."))
+ FString ApiProxyUrl;
+
+ void SetupDemoAccount();
+ void Reset();
+ FString GetApiBaseUrl() const;
+
+ bool IsValid() const
+ {
+ return !ApplicationId.IsEmpty() && (!ApiKey.IsEmpty() || !ApiProxyUrl.IsEmpty());
+ }
+
+ virtual void PostInitProperties() override;
+ virtual void PreSave(const ITargetPlatform* TargetPlatform) override;
+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
+};
diff --git a/Source/RpmNextGen/RpmNextGen.Build.cs b/Source/RpmNextGen/RpmNextGen.Build.cs
new file mode 100644
index 0000000..4ad0e8a
--- /dev/null
+++ b/Source/RpmNextGen/RpmNextGen.Build.cs
@@ -0,0 +1,58 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class RpmNextGen : ModuleRules
+{
+ public RpmNextGen(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PublicIncludePaths.AddRange(
+ new string[] {
+ // ... add public include paths required here ...
+ }
+ );
+
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ // ... add other private include paths required here ...
+ }
+ );
+
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "glTFRuntime",
+ "DeveloperSettings",
+ "Slate",
+ "SlateCore",
+ }
+ );
+
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "CoreUObject",
+ "Engine",
+ "Json",
+ "JsonUtilities",
+ "HTTP",
+ "UMG",
+ "ImageWrapper",
+ }
+ );
+
+
+ DynamicallyLoadedModuleNames.AddRange(
+ new string[]
+ {
+ // ... add any modules that your module loads dynamically here ...
+ }
+ );
+ }
+}
diff --git a/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp b/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp
new file mode 100644
index 0000000..bbb8e00
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/AssetNameGenerator.cpp
@@ -0,0 +1,24 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "AssetNameGenerator.h"
+
+UAssetNameGenerator::UAssetNameGenerator()
+{
+ MaterialNameGeneratorDelegate.BindUFunction(this, "GenerateMaterialName");
+ TextureNameGeneratorDelegate.BindUFunction(this, "GenerateTextureName");
+}
+
+void UAssetNameGenerator::SetPath(FString NewPath)
+{
+ this->path = NewPath;
+}
+
+FString UAssetNameGenerator::GenerateMaterialName(UMaterialInterface* Material, const int32 MaterialIndex, const FString& SlotName) const
+{
+ 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);
+}
diff --git a/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp b/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp
new file mode 100644
index 0000000..d30fba9
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/Auth/DevAuthTokenCache.cpp
@@ -0,0 +1,50 @@
+#include "Auth/DevAuthTokenCache.h"
+#include "EditorCache.h"
+#include "Auth/Models/DeveloperAuth.h"
+#include "Misc/ConfigCacheIni.h"
+
+FDeveloperAuth FDevAuthTokenCache::AuthData = FDeveloperAuth();
+bool FDevAuthTokenCache::bIsInitialized = false;
+
+void FDevAuthTokenCache::Initialize()
+{
+ if (!bIsInitialized)
+ {
+ AuthData.Name = FEditorCache::GetString(CacheKeyName, FString());
+ AuthData.Token = FEditorCache::GetString(CacheKeyToken, FString());
+ AuthData.RefreshToken = FEditorCache::GetString(CacheKeyRefreshToken, FString());
+ AuthData.IsDemo = FEditorCache::GetBool(CacheKeyIsDemo, false);
+
+ if (!AuthData.IsValid())
+ {
+ UE_LOG(LogTemp, Warning, TEXT("DevAuthTokenCache: Invalid AuthData %s"), *AuthData.ToJsonString());
+ ClearAuthData();
+ }
+
+ bIsInitialized = true;
+ }
+}
+
+FDeveloperAuth FDevAuthTokenCache::GetAuthData()
+{
+ Initialize();
+ return AuthData;
+}
+
+void FDevAuthTokenCache::SetAuthData(const FDeveloperAuth& DevAuthData)
+{
+ AuthData = DevAuthData;
+ FEditorCache::SetString( CacheKeyName, AuthData.Name);
+ FEditorCache::SetString( CacheKeyToken, AuthData.Token);
+ FEditorCache::SetString( CacheKeyRefreshToken, AuthData.RefreshToken);
+ FEditorCache::SetBool( CacheKeyIsDemo, AuthData.IsDemo);
+}
+
+void FDevAuthTokenCache::ClearAuthData()
+{
+ AuthData = FDeveloperAuth();
+ FEditorCache::RemoveKey(CacheKeyName);
+ FEditorCache::RemoveKey(CacheKeyToken);
+ FEditorCache::RemoveKey(CacheKeyRefreshToken);
+ FEditorCache::RemoveKey(CacheKeyIsDemo);
+}
diff --git a/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp b/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp
new file mode 100644
index 0000000..37d4bc6
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/Auth/DeveloperAuthApi.cpp
@@ -0,0 +1,32 @@
+#include "Auth/DeveloperAuthApi.h"
+#include "Auth/Models/DeveloperLoginRequest.h"
+#include "Auth/Models/DeveloperLoginResponse.h"
+#include "Settings/RpmDeveloperSettings.h"
+
+FDeveloperAuthApi::FDeveloperAuthApi()
+{
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ ApiUrl = FString::Printf(TEXT("%s/login"), *RpmSettings->ApiBaseAuthUrl);
+ OnApiResponse.BindRaw(this, &FDeveloperAuthApi::HandleLoginResponse);
+}
+
+void FDeveloperAuthApi::HandleLoginResponse(FString JsonData, bool bIsSuccessful) const
+{
+ FDeveloperLoginResponse Response;
+ if (bIsSuccessful && !JsonData.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(JsonData, &Response, 0, 0))
+ {
+ OnLoginResponse.ExecuteIfBound(Response, true);
+ return;
+ }
+ OnLoginResponse.ExecuteIfBound(Response, bIsSuccessful);
+}
+
+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();
+ DispatchRaw(ApiRequest);
+}
diff --git a/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp b/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp
new file mode 100644
index 0000000..365ea42
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/Auth/DeveloperTokenAuthStrategy.cpp
@@ -0,0 +1,61 @@
+#include "Auth/DeveloperTokenAuthStrategy.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);
+}
+
+void DeveloperTokenAuthStrategy::AddAuthToRequest(TSharedPtr Request)
+{
+ 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);
+ return;
+ }
+ if (Request->Headers.Contains(Key))
+ {
+ Request->Headers.Remove(Key);
+ }
+ Request->Headers.Add(Key, FString::Printf(TEXT("Bearer %s"), *Token));
+
+ OnAuthComplete.ExecuteIfBound(true);
+}
+
+void DeveloperTokenAuthStrategy::TryRefresh(TSharedPtr Request)
+{
+ FRefreshTokenRequest RefreshRequest;
+ RefreshRequest.Data.Token = FDevAuthTokenCache::GetAuthData().Token;
+ RefreshRequest.Data.RefreshToken = FDevAuthTokenCache::GetAuthData().RefreshToken;
+
+ RefreshTokenAsync(RefreshRequest);
+}
+
+void DeveloperTokenAuthStrategy::OnRefreshTokenResponse(const FRefreshTokenResponse& Response, bool bWasSuccessful)
+{
+ if (bWasSuccessful && !Response.Data.Token.IsEmpty())
+ {
+ FDeveloperAuth DeveloperAuth = FDevAuthTokenCache::GetAuthData();
+ DeveloperAuth.Token = Response.Data.Token;
+ DeveloperAuth.RefreshToken = Response.Data.RefreshToken;
+ FDevAuthTokenCache::SetAuthData(DeveloperAuth);
+ OnTokenRefreshed.ExecuteIfBound(Response.Data, true);
+ return;
+ }
+ UE_LOG(LogTemp, Error, TEXT("Failed to refresh token"));
+ OnTokenRefreshed.ExecuteIfBound(Response.Data, false);
+}
+
+
+void DeveloperTokenAuthStrategy::RefreshTokenAsync(const FRefreshTokenRequest& Request)
+{
+ AuthApi.RefreshToken(Request);
+}
diff --git a/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp b/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp
new file mode 100644
index 0000000..570dbf3
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/DeveloperAccounts/DeveloperAccountApi.cpp
@@ -0,0 +1,77 @@
+#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 "Settings/RpmDeveloperSettings.h"
+
+FDeveloperAccountApi::FDeveloperAccountApi(IAuthenticationStrategy* InAuthenticationStrategy) : FWebApiWithAuth(InAuthenticationStrategy)
+{
+ if (URpmDeveloperSettings* Settings = GetMutableDefault())
+ {
+ ApiBaseUrl = Settings->GetApiBaseUrl();
+ }
+}
+
+void FDeveloperAccountApi::ListApplicationsAsync(const FApplicationListRequest& Request)
+{
+ // TODO find better way to get settings (or move to editor only code)
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ 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);
+ DispatchRawWithAuth(ApiRequest);
+}
+
+void FDeveloperAccountApi::ListOrganizationsAsync(const FOrganizationListRequest& Request)
+{
+ // TODO find better way to get settings (or move to editor only code)
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ 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);
+ DispatchRawWithAuth(ApiRequest);
+}
+
+
+void FDeveloperAccountApi::HandleAppListResponse(FString Data, bool bWasSuccessful)
+{
+ FApplicationListResponse ApplicationListResponse;
+ if (bWasSuccessful && !Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &ApplicationListResponse, 0, 0))
+ {
+ OnApplicationListResponse.ExecuteIfBound(ApplicationListResponse, true);
+ return;
+ }
+ OnApplicationListResponse.ExecuteIfBound(ApplicationListResponse, false);
+}
+
+void FDeveloperAccountApi::HandleOrgListResponse(FString Data, bool bWasSuccessful)
+{
+ FOrganizationListResponse OrganizationListResponse;
+ if (bWasSuccessful && !Data.IsEmpty() && FJsonObjectConverter::JsonObjectStringToUStruct(Data, &OrganizationListResponse, 0, 0))
+ {
+ OnOrganizationResponse.ExecuteIfBound(OrganizationListResponse, true);
+ return;
+ }
+ OnOrganizationResponse.ExecuteIfBound(OrganizationListResponse, false);
+}
+
+
+FString FDeveloperAccountApi::BuildQueryString(const TMap& Params)
+{
+ if (Params.Num() == 0) return FString();
+ FString QueryString = TEXT("?");
+ for (const auto& Param : Params)
+ {
+ QueryString += Param.Key + TEXT("=") + Param.Value + TEXT("&");
+ }
+ QueryString.RemoveFromEnd(TEXT("&"));
+ return QueryString;
+}
diff --git a/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp b/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp
new file mode 100644
index 0000000..2bc520c
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/EditorAssetLoader.cpp
@@ -0,0 +1,124 @@
+#include "EditorAssetLoader.h"
+#include "TransientObjectSaverLibrary.h"
+#include "AssetNameGenerator.h"
+#include "RpmActor.h"
+#include "RpmNextGen.h"
+
+FEditorAssetLoader::FEditorAssetLoader()
+{
+ SkeletonToCopy = nullptr;
+}
+
+FEditorAssetLoader::~FEditorAssetLoader()
+{
+}
+
+void FEditorAssetLoader::OnAssetLoadComplete(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful,
+ FString LoadedAssetId)
+{
+ if (bWasSuccessful)
+ {
+ gltfAsset->AddToRoot();
+ SaveAsUAsset(gltfAsset, LoadedAssetId);
+ LoadAssetToWorldAsURpmActor(gltfAsset, LoadedAssetId);
+ gltfAsset->RemoveFromRoot();
+ }
+}
+
+USkeletalMesh* FEditorAssetLoader::SaveAsUAsset(UglTFRuntimeAsset* GltfAsset, const FString& LoadedAssetId) const
+{
+ const FglTFRuntimeSkeletonConfig SkeletonConfig = FglTFRuntimeSkeletonConfig();
+ USkeleton* Skeleton = GltfAsset->LoadSkeleton(0, SkeletonConfig);
+
+ FglTFRuntimeSkeletalMeshConfig meshConfig = FglTFRuntimeSkeletalMeshConfig();
+ meshConfig.Skeleton = Skeleton;
+
+ USkeletalMesh* skeletalMesh = GltfAsset->LoadSkeletalMeshRecursive(TEXT(""), {}, meshConfig);
+ skeletalMesh->SetSkeleton(Skeleton);
+ Skeleton->SetPreviewMesh(skeletalMesh);
+
+ const FString CoreAssetPath = FString::Printf(TEXT("/Game/ReadyPlayerMe/%s/"), *LoadedAssetId);
+ const FString SkeletonAssetPath = FString::Printf(TEXT("%s%s_Skeleton"), *CoreAssetPath, *LoadedAssetId);
+ const FString SkeletalMeshAssetPath = FString::Printf(TEXT("%s%s_SkeletalMesh"), *CoreAssetPath, *LoadedAssetId);
+
+ const auto NameGenerator = NewObject();
+ NameGenerator->SetPath(CoreAssetPath);
+
+ UTransientObjectSaverLibrary::SaveTransientSkeletalMesh(skeletalMesh, SkeletalMeshAssetPath, SkeletonAssetPath, TEXT(""), NameGenerator->MaterialNameGeneratorDelegate, NameGenerator->TextureNameGeneratorDelegate);
+
+ UE_LOG(LogReadyPlayerMe, Log, TEXT("Character model saved: %s"), *LoadedAssetId);
+ return skeletalMesh;
+}
+
+void FEditorAssetLoader::LoadGLBFromURLWithId(const FString& URL, FString LoadedAssetId)
+{
+ 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);
+}
+
+void FEditorAssetLoader::LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* gltfAsset, FString AssetId)
+{
+ this->LoadAssetToWorld(AssetId, gltfAsset);
+}
+
+
+void FEditorAssetLoader::LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gltfAsset)
+{
+ if (!GEditor)
+ {
+ UE_LOG(LogReadyPlayerMe, Error, TEXT("GEditor is not available."));
+ return;
+ }
+
+ UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
+ if (!EditorWorld)
+ {
+ UE_LOG(LogReadyPlayerMe, Error, TEXT("No valid editor world found."));
+ return;
+ }
+
+ if (gltfAsset)
+ {
+ FTransform Transform = FTransform::Identity;
+
+
+ ARpmActor* NewActor = EditorWorld->SpawnActorDeferred(ARpmActor::StaticClass(), Transform);
+
+ if (NewActor)
+ {
+ NewActor->SetFlags(RF_Transient);
+ NewActor->Rename(*AssetId);
+ if (SkeletonToCopy)
+ {
+ NewActor->SkeletalMeshConfig.SkeletonConfig.CopyRotationsFrom = SkeletonToCopy;
+ }
+ NewActor->FinishSpawning(Transform);
+ NewActor->DispatchBeginPlay();
+ GEditor->SelectNone(true, true, true);
+ GEditor->SelectActor(NewActor, true, true, false, true);
+
+ // Register the actor in the editor world and update the editor
+ GEditor->SelectActor(NewActor, true, true);
+ GEditor->EditorUpdateComponents();
+ if (gltfAsset)
+ {
+ NewActor->LoadGltfAsset(gltfAsset);
+ }
+ UE_LOG(LogReadyPlayerMe, Log, TEXT("Successfully loaded GLB asset into the editor world"));
+ return;
+ }
+
+ UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to spawn ARpmActor in the editor world"));
+ }
+ UE_LOG(LogReadyPlayerMe, Error, TEXT("Failed to load GLB asset from file"));
+}
diff --git a/Source/RpmNextGenEditor/Private/EditorCache.cpp b/Source/RpmNextGenEditor/Private/EditorCache.cpp
new file mode 100644
index 0000000..e7f8f93
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/EditorCache.cpp
@@ -0,0 +1,74 @@
+#include "EditorCache.h"
+#include "Misc/ConfigCacheIni.h"
+
+#define RPM_CACHE_SECTION TEXT("ReadyPlayerMeCache")
+
+void FEditorCache::SetString( const FString& Key, const FString& Value)
+{
+ GConfig->SetString(RPM_CACHE_SECTION, *Key, *Value, GEditorPerProjectIni);
+ GConfig->Flush(false, GEditorPerProjectIni);
+}
+
+void FEditorCache::SetInt(const FString& Key, int32 Value)
+{
+ GConfig->SetInt(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni);
+ GConfig->Flush(false, GEditorPerProjectIni);
+}
+
+void FEditorCache::SetFloat(const FString& Key, float Value)
+{
+ GConfig->SetFloat(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni);
+ GConfig->Flush(false, GEditorPerProjectIni);
+}
+
+void FEditorCache::SetBool(const FString& Key, bool Value)
+{
+ GConfig->SetBool(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni);
+ GConfig->Flush(false, GEditorPerProjectIni);
+}
+
+FString FEditorCache::GetString(const FString& Key, const FString& DefaultValue)
+{
+ FString Value;
+ if (GConfig->GetString(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni))
+ {
+ return Value;
+ }
+ return DefaultValue;
+}
+
+int32 FEditorCache::GetInt(const FString& Key, int32 DefaultValue)
+{
+ int32 Value;
+ if (GConfig->GetInt(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni))
+ {
+ return Value;
+ }
+ return DefaultValue;
+}
+
+float FEditorCache::GetFloat(const FString& Key, float DefaultValue)
+{
+ float Value;
+ if (GConfig->GetFloat(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni))
+ {
+ return Value;
+ }
+ return DefaultValue;
+}
+
+bool FEditorCache::GetBool(const FString& Key, bool DefaultValue)
+{
+ bool Value;
+ if (GConfig->GetBool(RPM_CACHE_SECTION, *Key, Value, GEditorPerProjectIni))
+ {
+ return Value;
+ }
+ return DefaultValue;
+}
+
+void FEditorCache::RemoveKey(const FString& Key)
+{
+ GConfig->RemoveKey(RPM_CACHE_SECTION, *Key, GEditorPerProjectIni);
+ GConfig->Flush(false, GEditorPerProjectIni);
+}
\ No newline at end of file
diff --git a/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp b/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp
new file mode 100644
index 0000000..d9e6dee
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/RpmNextGenEditor.cpp
@@ -0,0 +1,148 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "RpmNextGenEditor.h"
+
+#include "UI/CharacterLoaderWidget.h"
+#include "UI/Commands/LoaderWindowCommands.h"
+#include "UI/Commands/LoginWindowCommands.h"
+#include "UI/SRpmDeveloperLoginWidget.h"
+#include "Widgets/Docking/SDockTab.h"
+#include "Widgets/Layout/SBox.h"
+#include "Widgets/Text/STextBlock.h"
+#include "ToolMenus.h"
+
+static const FName DeveloperWindowName("LoginWindow");
+static const FName LoaderWinderName("LoaderWindow");
+#define LOCTEXT_NAMESPACE "RpmNextGenEditorModule"
+
+void FRpmNextGenEditorModule::StartupModule()
+{
+ FLoginWindowStyle::Initialize();
+ FLoginWindowStyle::ReloadTextures();
+
+ FLoginWindowCommands::Register();
+ FLoaderWindowCommands::Register(); // Don't forget to register the other command set
+
+ PluginCommands = MakeShareable(new FUICommandList);
+
+ PluginCommands->MapAction(
+ FLoginWindowCommands::Get().OpenPluginWindow,
+ FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::PluginButtonClicked),
+ FCanExecuteAction());
+
+ // Don't show Loader window in the menu
+ // PluginCommands->MapAction(
+ // FLoaderWindowCommands::Get().OpenPluginWindow,
+ // FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::OpenLoaderWindow),
+ // FCanExecuteAction());
+
+ UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FRpmNextGenEditorModule::RegisterMenus));
+
+ FGlobalTabmanager::Get()->RegisterNomadTabSpawner(DeveloperWindowName, FOnSpawnTab::CreateRaw(this, &FRpmNextGenEditorModule::OnSpawnPluginTab))
+ .SetDisplayName(LOCTEXT("DeveloperLoginWidget", "Ready Player Me"))
+ .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"))
+ // .SetMenuType(ETabSpawnerMenuType::Hidden);
+}
+
+void FRpmNextGenEditorModule::RegisterMenus()
+{
+ FToolMenuOwnerScoped OwnerScoped(this);
+
+ // Create a new main menu entry called "ReadyPlayerMe"
+ UToolMenu* MainMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu");
+
+ // Add a new top-level menu "Ready Player Me"
+ FToolMenuSection& Section = MainMenu->AddSection("ReadyPlayerMeTopMenu", LOCTEXT("ReadyPlayerMeMenuSection", "Ready Player Me"));
+
+ // Add a sub-menu for "Ready Player Me"
+ FToolMenuEntry& SubMenuEntry = Section.AddSubMenu(
+ "ReadyPlayerMe",
+ LOCTEXT("ReadyPlayerMeMenu", "Ready Player Me"),
+ LOCTEXT("ReadyPlayerMeMenu_ToolTip", "Open Ready Player Me tools"),
+ FNewToolMenuDelegate::CreateRaw(this, &FRpmNextGenEditorModule::FillReadyPlayerMeMenu),
+ false, // Don't open on hover
+ FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.User") // Optional icon for the top-level menu
+ );
+}
+
+void FRpmNextGenEditorModule::FillReadyPlayerMeMenu(UToolMenu* Menu)
+{
+ FToolMenuSection& Section = Menu->AddSection("ReadyPlayerMeSubSection");
+
+ Section.AddMenuEntry(
+ "OpenLoginWindow",
+ LOCTEXT("OpenLoginWindow", "Developer Window"),
+ LOCTEXT("OpenLoginWindowToolTip", "Open the RPM Developer Window."),
+ FSlateIcon(),
+ FUIAction(FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::PluginButtonClicked))
+ );
+
+ // Don't show Loader window in the menu
+ // Section.AddMenuEntry(
+ // "OpenLoaderWindow",
+ // LOCTEXT("OpenLoaderWindow", "Glb Loader"),
+ // LOCTEXT("OpenLoaderWindowToolTip", "Avatar Loader Window."),
+ // FSlateIcon(),
+ // FUIAction(FExecuteAction::CreateRaw(this, &FRpmNextGenEditorModule::OpenLoaderWindow))
+ // );
+}
+
+void FRpmNextGenEditorModule::ShutdownModule()
+{
+ UToolMenus::UnRegisterStartupCallback(this);
+ UToolMenus::UnregisterOwner(this);
+
+ FLoginWindowStyle::Shutdown();
+
+ FLoginWindowCommands::Unregister();
+ // Don't show Loader window in the menu
+ //FLoaderWindowCommands::Unregister();
+
+ FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(DeveloperWindowName);
+ // Don't show Loader window in the menu
+ //FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(LoaderWinderName);
+}
+
+TSharedRef FRpmNextGenEditorModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
+{
+ FText WidgetText = FText::Format(
+ LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
+ FText::FromString(TEXT("FRpmNextGenEditorModule::OnSpawnPluginTab")),
+ FText::FromString(TEXT("RpmNextGenEditor.cpp"))
+ );
+
+ return SNew(SDockTab)
+ .TabRole(NomadTab)
+ [
+ SNew(SRpmDeveloperLoginWidget)
+ ];
+
+}
+
+TSharedRef FRpmNextGenEditorModule::OnSpawnLoaderWindow(const FSpawnTabArgs& SpawnTabArgs)
+{
+ return SNew(SDockTab)
+ .TabRole(NomadTab)
+ [
+ SNew(SCharacterLoaderWidget)
+ ];
+}
+
+void FRpmNextGenEditorModule::PluginButtonClicked()
+{
+ FGlobalTabmanager::Get()->TryInvokeTab(DeveloperWindowName);
+}
+
+
+void FRpmNextGenEditorModule::OpenLoaderWindow()
+{
+ FGlobalTabmanager::Get()->TryInvokeTab(LoaderWinderName);
+}
+
+#undef LOCTEXT_NAMESPACE
+
+IMPLEMENT_MODULE(FRpmNextGenEditorModule, RpmNextGenEditor)
\ No newline at end of file
diff --git a/Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp b/Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp
new file mode 100644
index 0000000..f300345
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/UI/CharacterLoaderWidget.cpp
@@ -0,0 +1,122 @@
+#include "UI/CharacterLoaderWidget.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 "PropertyCustomizationHelpers.h"
+#include "AssetRegistry/AssetData.h"
+
+// Configuration section and key names
+static const FString ConfigSection = TEXT("/Script/ReadyPlayerMe.CharacterLoaderSettings");
+static const FString PathKeyName = TEXT("LastFilePath");
+
+void SCharacterLoaderWidget::Construct(const FArguments& InArgs)
+{
+ FString LastSavedPath;
+ GConfig->GetString(*ConfigSection, *PathKeyName, LastSavedPath, GGameIni);
+ PathText = FText::FromString(LastSavedPath);
+ AssetLoader = FEditorAssetLoader();
+ ChildSlot
+ [
+ SNew(SVerticalBox)
+
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Enter Path:"))
+ ]
+
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SAssignNew(PathTextBox, SEditableTextBox)
+ .Text(FText::FromString(LastSavedPath)) // Set the loaded path as the initial text
+ .OnTextChanged(this, &SCharacterLoaderWidget::OnPathTextChanged)
+ ]
+
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SNew(SButton)
+ .Text(FText::FromString("Load Glb"))
+ .OnClicked(this, &SCharacterLoaderWidget::OnButtonClick)
+ ]
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Select Skeleton:"))
+ ]
+
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SNew(SObjectPropertyEntryBox)
+ .AllowedClass(USkeleton::StaticClass()) // Only allow USkeleton assets
+ .OnObjectChanged(this, &SCharacterLoaderWidget::OnSkeletonSelected) // Handle the asset selection
+ .ObjectPath(this, &SCharacterLoaderWidget::GetCurrentSkeletonPath) // Optionally provide a current value
+ ]
+ ];
+}
+
+
+void SCharacterLoaderWidget::OnSkeletonSelected(const FAssetData& AssetData)
+{
+ SelectedSkeleton = Cast(AssetData.GetAsset());
+ if (SelectedSkeleton)
+ {
+ UE_LOG(LogTemp, Log, TEXT("Selected Skeleton: %s"), *SelectedSkeleton->GetName());
+ }
+}
+
+
+FString SCharacterLoaderWidget::GetCurrentSkeletonPath() const
+{
+ return SelectedSkeleton ? SelectedSkeleton->GetPathName() : FString();
+}
+
+void SCharacterLoaderWidget::OnPathTextChanged(const FText& NewText)
+{
+ PathText = NewText;
+
+ // Save the path to the configuration file
+ GConfig->SetString(*ConfigSection, *PathKeyName, *PathText.ToString(), GGameIni);
+ GConfig->Flush(false, GGameIni); // Ensure the config is saved immediately
+}
+
+
+FReply SCharacterLoaderWidget::OnButtonClick()
+{
+ FString Path = PathText.ToString();
+ if (Path.IsEmpty())
+ {
+ UE_LOG(LogTemp, Error, TEXT("Path is empty"));
+ return FReply::Handled();
+ }
+ LoadAsset(Path);
+
+ return FReply::Handled();
+}
+
+void SCharacterLoaderWidget::LoadAsset(const FString& Path)
+{
+ FglTFRuntimeConfig Config = FglTFRuntimeConfig();
+ Config.SceneScale = 100.0f;
+ Config.TransformBaseType = EglTFRuntimeTransformBaseType::YForward;
+ UglTFRuntimeAsset* gltfAsset = UglTFRuntimeFunctionLibrary::glTFLoadAssetFromFilename(Path, true, Config);
+ if (SelectedSkeleton)
+ {
+ AssetLoader.SkeletonToCopy = SelectedSkeleton;
+ }
+ AssetLoader.LoadAssetToWorldAsURpmActor(gltfAsset);
+ AssetLoader.SaveAsUAsset(gltfAsset, TEXT("/Game/ReadyPlayerMe/TestSkeleton"));
+}
diff --git a/Source/RpmNextGenEditor/Private/UI/Commands/LoaderWindowCommands.cpp b/Source/RpmNextGenEditor/Private/UI/Commands/LoaderWindowCommands.cpp
new file mode 100644
index 0000000..e37aa1c
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/UI/Commands/LoaderWindowCommands.cpp
@@ -0,0 +1,9 @@
+#include "UI/Commands/LoaderWindowCommands.h"
+
+#define LOCTEXT_NAMESPACE "FRpmNextGenEditorModule"
+
+void FLoaderWindowCommands::RegisterCommands()
+{
+ UI_COMMAND(OpenPluginWindow, "Open Loader Window", "Open the custom loader window", EUserInterfaceActionType::Button, FInputGesture());
+}
+#undef LOCTEXT_NAMESPACE
\ No newline at end of file
diff --git a/Source/RpmNextGenEditor/Private/UI/Commands/LoginWindowCommands.cpp b/Source/RpmNextGenEditor/Private/UI/Commands/LoginWindowCommands.cpp
new file mode 100644
index 0000000..48d9937
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/UI/Commands/LoginWindowCommands.cpp
@@ -0,0 +1,10 @@
+#include "UI/Commands/LoginWindowCommands.h"
+
+#define LOCTEXT_NAMESPACE "FRpmNextGenEditorModule"
+
+void FLoginWindowCommands::RegisterCommands()
+{
+ UI_COMMAND(OpenPluginWindow, "Rpm Developer window", "Bring up RPM Developer window", EUserInterfaceActionType::Button, FInputChord());
+}
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Source/RpmNextGenEditor/Private/UI/LoginWindowStyle.cpp b/Source/RpmNextGenEditor/Private/UI/LoginWindowStyle.cpp
new file mode 100644
index 0000000..1d86263
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/UI/LoginWindowStyle.cpp
@@ -0,0 +1,58 @@
+#include "UI/LoginWindowStyle.h"
+#include "Styling/SlateStyleRegistry.h"
+#include "Framework/Application/SlateApplication.h"
+#include "Slate/SlateGameResources.h"
+#include "Interfaces/IPluginManager.h"
+#include "Styling/SlateStyleMacros.h"
+
+#define RootToContentDir Style->RootToContentDir
+
+TSharedPtr FLoginWindowStyle::StyleInstance = nullptr;
+
+void FLoginWindowStyle::Initialize()
+{
+ if (!StyleInstance.IsValid())
+ {
+ StyleInstance = Create();
+ FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
+ }
+}
+
+void FLoginWindowStyle::Shutdown()
+{
+ FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
+ ensure(StyleInstance.IsUnique());
+ StyleInstance.Reset();
+}
+
+FName FLoginWindowStyle::GetStyleSetName()
+{
+ static FName StyleSetName(TEXT("LoginWindowStyle"));
+ return StyleSetName;
+}
+
+const FVector2D Icon16x16(16.0f, 16.0f);
+const FVector2D Icon20x20(20.0f, 20.0f);
+
+TSharedRef< FSlateStyleSet > FLoginWindowStyle::Create()
+{
+ TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("LoginWindowStyle"));
+ Style->SetContentRoot(IPluginManager::Get().FindPlugin("RpmNextGen")->GetBaseDir() / TEXT("Resources"));
+
+ Style->Set("RpmNextGenEditor.OpenPluginWindow", new IMAGE_BRUSH_SVG(TEXT("PlaceholderButtonIcon"), Icon20x20));
+
+ return Style;
+}
+
+void FLoginWindowStyle::ReloadTextures()
+{
+ if (FSlateApplication::IsInitialized())
+ {
+ FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
+ }
+}
+
+const ISlateStyle& FLoginWindowStyle::Get()
+{
+ return *StyleInstance;
+}
diff --git a/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp b/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp
new file mode 100644
index 0000000..7020835
--- /dev/null
+++ b/Source/RpmNextGenEditor/Private/UI/SRpmDeveloperLoginWidget.cpp
@@ -0,0 +1,519 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "UI/SRpmDeveloperLoginWidget.h"
+#include "Auth/DevAuthTokenCache.h"
+#include "EditorCache.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 "Auth/DeveloperAuthApi.h"
+#include "Auth/Models/DeveloperAuth.h"
+#include "Auth/Models/DeveloperLoginRequest.h"
+#include "DeveloperAccounts/Models/ApplicationListRequest.h"
+#include "DeveloperAccounts/Models/ApplicationListResponse.h"
+#include "DeveloperAccounts/Models/OrganizationListRequest.h"
+#include "DeveloperAccounts/Models/OrganizationListResponse.h"
+#include "Settings/RpmDeveloperSettings.h"
+#include "Widgets/Layout/SScrollBox.h"
+
+BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
+
+void SRpmDeveloperLoginWidget::Construct(const FArguments& InArgs)
+{
+ FDeveloperAuth AuthData = FDevAuthTokenCache::GetAuthData();
+ FDevAuthTokenCache::SetAuthData(AuthData);
+
+ bIsLoggedIn = AuthData.IsValid();
+ UserName = AuthData.Name;
+
+ ChildSlot
+ [
+ SNew(SVerticalBox)
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Sign in with your Ready Player Me Studio account"))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Email:"))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SAssignNew(EmailTextBox, SEditableTextBox)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Password:"))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SAssignNew(PasswordTextBox, SEditableTextBox)
+ .IsPassword(true)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(SButton)
+ .Text(FText::FromString("Login"))
+ .OnClicked(this, &SRpmDeveloperLoginWidget::OnLoginClicked)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(SButton)
+ .Text(FText::FromString("Use Demo Account"))
+ .OnClicked(this, &SRpmDeveloperLoginWidget::OnUseDemoAccountClicked)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoginViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(SHorizontalBox)
+ + SHorizontalBox::Slot()
+ .FillWidth(1.0)
+ [
+ SNew(STextBlock)
+ .Text(this, &SRpmDeveloperLoginWidget::GetWelcomeText)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ + SHorizontalBox::Slot()
+ .AutoWidth()
+ .HAlign(HAlign_Right)
+ [
+ SNew(SButton)
+ .Text(FText::FromString("Logout"))
+ .OnClicked(this, &SRpmDeveloperLoginWidget::OnLogoutClicked)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Project Settings"))
+ .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 16))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Select the Ready Player Me application to link to project"))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(SComboBox>)
+ .OptionsSource(&ComboBoxItems)
+ .OnSelectionChanged(this, &SRpmDeveloperLoginWidget::OnComboBoxSelectionChanged)
+ .OnGenerateWidget_Lambda([](TSharedPtr Item)
+ {
+ return SNew(STextBlock).Text(FText::FromString(*Item));
+ })
+ [
+ SAssignNew(SelectedApplicationTextBlock, STextBlock).Text(
+ this, &SRpmDeveloperLoginWidget::GetSelectedComboBoxItemText)
+ ]
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Character Styles"))
+ .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 16))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .AutoHeight()
+ [
+ SNew(STextBlock)
+ .Text(FText::FromString("Here you can import your character styles from Studio"))
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ ]
+ + SVerticalBox::Slot()
+ .Padding(10)
+ .FillHeight(1.0f) // Allows the scroll box to take up remaining space
+ [
+ SNew(SScrollBox)
+ .Visibility(this, &SRpmDeveloperLoginWidget::GetLoggedInViewVisibility)
+ + SScrollBox::Slot()
+ [
+ SAssignNew(ContentBox, SVerticalBox)
+ ]
+ ]
+ ];
+
+ EmailTextBox->SetText(FText::FromString(FEditorCache::GetString(CacheKeyEmail)));
+ Initialize();
+}
+
+void SRpmDeveloperLoginWidget::Initialize()
+{
+ if (bIsInitialized)
+ {
+ return;
+ }
+ const FDeveloperAuth DevAuthData = FDevAuthTokenCache::GetAuthData();
+ if (!DeveloperAuthApi.IsValid())
+ {
+ DeveloperAuthApi = MakeUnique();
+
+ DeveloperAuthApi->OnLoginResponse.BindRaw(this, &SRpmDeveloperLoginWidget::HandleLoginResponse);
+ }
+
+ if (!AssetApi.IsValid())
+ {
+ AssetApi = MakeUnique();
+ if (!DevAuthData.IsDemo)
+ {
+ AssetApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy());
+ }
+ AssetApi->OnListAssetsResponse.BindRaw(this, &SRpmDeveloperLoginWidget::HandleBaseModelListResponse);
+ }
+ if (!DeveloperAccountApi.IsValid())
+ {
+ DeveloperAccountApi = MakeUnique(nullptr);
+ if (!DevAuthData.IsDemo)
+ {
+ DeveloperAccountApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy());
+ }
+
+ DeveloperAccountApi->OnOrganizationResponse.BindRaw(
+ this, &SRpmDeveloperLoginWidget::HandleOrganizationListResponse);
+ DeveloperAccountApi->OnApplicationListResponse.BindRaw(
+ this, &SRpmDeveloperLoginWidget::HandleApplicationListResponse);
+ }
+ bIsInitialized = true;
+ if (bIsLoggedIn)
+ {
+ GetOrgList();
+ return;
+ }
+ OnLogoutClicked();
+}
+
+SRpmDeveloperLoginWidget::~SRpmDeveloperLoginWidget()
+{
+ ClearLoadedCharacterModelImages();
+}
+
+void SRpmDeveloperLoginWidget::ClearLoadedCharacterModelImages()
+{
+ for (const auto Texture : CharacterStyleTextures)
+ {
+ Texture->RemoveFromRoot();
+ }
+ CharacterStyleTextures.Empty();
+}
+
+void SRpmDeveloperLoginWidget::AddCharacterStyle(const FAsset& StyleAsset)
+{
+ TSharedPtr ImageWidget;
+ const FVector2D ImageSize(100.0f, 100.0f);
+
+ ContentBox->AddSlot()
+ .AutoHeight()
+ .Padding(5)
+ [
+ SNew(SHorizontalBox)
+ + SHorizontalBox::Slot()
+ .AutoWidth()
+ .Padding(5)
+ [
+ SNew(SVerticalBox)
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .HAlign(HAlign_Left)
+ [
+ SAssignNew(ImageWidget, SImage).
+ DesiredSizeOverride(ImageSize)
+ ]
+ + SVerticalBox::Slot()
+ .AutoHeight()
+ .Padding(5, 5)
+ [
+ SNew(SBox)
+ .WidthOverride(100.0f)
+ [
+ SNew(SButton)
+ .Text(FText::FromString("Load Style"))
+ .OnClicked_Lambda([this, StyleAsset]() -> FReply
+ {
+ OnLoadStyleClicked(StyleAsset.Id);
+ return FReply::Handled();
+ })
+ ]
+ ]
+ ]
+ + SHorizontalBox::Slot()
+ .AutoWidth()
+ .VAlign(VAlign_Top)
+ .Padding(10, 10, 0, 0)
+ [
+ SNew(SEditableText)
+ .Text(FText::FromString(FString::Printf(TEXT("ID: %s"), *StyleAsset.Id)))
+ .IsReadOnly(true)
+ .IsCaretMovedWhenGainFocus(false)
+ .SelectAllTextWhenFocused(false)
+ .MinDesiredWidth(100.0f)
+ ]
+ ];
+
+ FRpmImageLoader ImageLoader;
+ ImageLoader.LoadSImageFromURL(ImageWidget, StyleAsset.IconUrl, [this](UTexture2D* texture)
+ {
+ texture->AddToRoot();
+ CharacterStyleTextures.Add(texture);
+ });
+}
+
+void SRpmDeveloperLoginWidget::OnLoadStyleClicked(const FString& StyleId)
+{
+ AssetLoader = FEditorAssetLoader();
+ AssetLoader.LoadGLBFromURLWithId(CharacterStyleAssets[StyleId].GlbUrl, *StyleId);
+}
+
+EVisibility SRpmDeveloperLoginWidget::GetLoginViewVisibility() const
+{
+ return bIsLoggedIn ? EVisibility::Collapsed : EVisibility::Visible;
+}
+
+EVisibility SRpmDeveloperLoginWidget::GetLoggedInViewVisibility() const
+{
+ return bIsLoggedIn ? EVisibility::Visible : EVisibility::Collapsed;
+}
+
+FText SRpmDeveloperLoginWidget::GetWelcomeText() const
+{
+ return FText::Format(FText::FromString("Welcome {0}"), FText::FromString(UserName));
+}
+
+FReply SRpmDeveloperLoginWidget::OnLoginClicked()
+{
+ URpmDeveloperSettings* RpmSettings = GetMutableDefault();
+ RpmSettings->Reset();
+ FString Email = EmailTextBox->GetText().ToString();
+ FString Password = PasswordTextBox->GetText().ToString();
+ FEditorCache::SetString(CacheKeyEmail, Email);
+ Email = Email.TrimStartAndEnd();
+ Password = Password.TrimStartAndEnd();
+ DeveloperAccountApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy());
+ AssetApi->SetAuthenticationStrategy(new DeveloperTokenAuthStrategy());
+ FDeveloperLoginRequest LoginRequest = FDeveloperLoginRequest(Email, Password);
+ DeveloperAuthApi->LoginWithEmail(LoginRequest);
+ return FReply::Handled();
+}
+
+void SRpmDeveloperLoginWidget::GetOrgList()
+{
+ FOrganizationListRequest OrgRequest;
+ DeveloperAccountApi->ListOrganizationsAsync(OrgRequest);
+}
+
+void SRpmDeveloperLoginWidget::HandleLoginResponse(const FDeveloperLoginResponse& Response, bool bWasSuccessful)
+{
+ if (bWasSuccessful)
+ {
+ UserName = Response.Data.Name;
+ FDeveloperAuth AuthData = FDeveloperAuth(Response.Data, false);
+ FDevAuthTokenCache::SetAuthData(AuthData);
+ SetLoggedInState(true);
+ GetOrgList();
+ return;
+ }
+ UE_LOG(LogTemp, Error, TEXT("Login request failed"));
+ FDevAuthTokenCache::ClearAuthData();
+}
+
+void SRpmDeveloperLoginWidget::HandleOrganizationListResponse(const FOrganizationListResponse& Response,
+ bool bWasSuccessful)
+{
+ if (bWasSuccessful)
+ {
+ if (Response.Data.Num() == 0)
+ {
+ UE_LOG(LogTemp, Error, TEXT("No organizations found"));
+ return;
+ }
+ FApplicationListRequest Request;
+ Request.Params.Add("organizationId", Response.Data[0].Id);
+ DeveloperAccountApi->ListApplicationsAsync(Request);
+ return;
+ }
+
+ UE_LOG(LogTemp, Error, TEXT("Failed to list organizations"));
+}
+
+
+void SRpmDeveloperLoginWidget::HandleApplicationListResponse(const FApplicationListResponse& Response, bool bWasSuccessful)
+{
+ if (bWasSuccessful)
+ {
+ const URpmDeveloperSettings* RpmSettings = GetDefault();
+ UserApplications = Response.Data;
+ FString Active;
+ TArray Items;
+ for (const FApplication& App : UserApplications)
+ {
+ Items.Add(App.Name);
+ if (App.Id == RpmSettings->ApplicationId)
+ {
+ Active = App.Name;
+ }
+ }
+ if (Active.IsEmpty() && Items.Num() > 0)
+ {
+ const auto NewActiveItem = MakeShared(Items[0]);
+ OnComboBoxSelectionChanged(NewActiveItem, ESelectInfo::Direct);
+ SelectedApplicationTextBlock->SetText(FText::FromString(*NewActiveItem));
+ }
+ PopulateComboBoxItems(Items, Active);
+ }
+ else
+ {
+ UE_LOG(LogTemp, Error, TEXT("Failed to list applications"));
+ }
+ LoadBaseModelList();
+}
+
+
+void SRpmDeveloperLoginWidget::PopulateComboBoxItems(const TArray& Items, const FString ActiveItem)
+{
+ ComboBoxItems.Empty();
+ for (const FString& Item : Items)
+ {
+ ComboBoxItems.Add(MakeShared(Item));
+ }
+ SelectedComboBoxItem = MakeShared(ActiveItem);
+}
+
+
+FText SRpmDeveloperLoginWidget::GetSelectedComboBoxItemText() const
+{
+ return SelectedComboBoxItem.IsValid() && !SelectedComboBoxItem->IsEmpty()
+ ? FText::FromString(*SelectedComboBoxItem)
+ : FText::FromString("Select an option");
+}
+
+
+void SRpmDeveloperLoginWidget::OnComboBoxSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo)
+{
+ SelectedComboBoxItem = NewValue;
+ FApplication* application = UserApplications.FindByPredicate([&](FApplication item)
+ {
+ return item.Name == *NewValue;
+ });
+ if (application)
+ {
+ URpmDeveloperSettings* RpmSettings = GetMutableDefault();
+ RpmSettings->ApplicationId = application->Id;
+ RpmSettings->SaveConfig();
+ }
+}
+
+FReply SRpmDeveloperLoginWidget::OnUseDemoAccountClicked()
+{
+ URpmDeveloperSettings* RpmSettings = GetMutableDefault();
+ RpmSettings->SetupDemoAccount();
+ FDeveloperAuth AuthData = FDeveloperAuth();
+ AuthData.Name = DemoUserName;
+ AuthData.IsDemo = true;
+ UserName = AuthData.Name;
+ FDevAuthTokenCache::SetAuthData(AuthData);
+ SetLoggedInState(true);
+
+ // Unset the authentication strategy for the APIs
+ DeveloperAccountApi->SetAuthenticationStrategy(nullptr);
+ AssetApi->SetAuthenticationStrategy(nullptr);
+ GetOrgList();
+ return FReply::Handled();
+}
+
+FReply SRpmDeveloperLoginWidget::OnLogoutClicked()
+{
+ URpmDeveloperSettings* RpmSettings = GetMutableDefault();
+ RpmSettings->Reset();
+
+ // Clear the content box to remove all child widgets
+ if (ContentBox.IsValid())
+ {
+ ContentBox->ClearChildren();
+ }
+ ComboBoxItems.Empty();
+
+ ClearLoadedCharacterModelImages();
+ FDevAuthTokenCache::ClearAuthData();
+ SetLoggedInState(false);
+ return FReply::Handled();
+}
+
+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."));
+ return;
+ }
+ FAssetListRequest Request = FAssetListRequest();
+ FAssetListQueryParams Params = FAssetListQueryParams();
+ Params.ApplicationId = RpmSettings->ApplicationId;
+ Params.Type = "baseModel";
+ Request.Params = Params;
+ AssetApi->ListAssetsAsync(Request);
+}
+
+void SRpmDeveloperLoginWidget::HandleBaseModelListResponse(const FAssetListResponse& Response, bool bWasSuccessful)
+{
+ CharacterStyleAssets.Empty();
+ for (FAsset Asset : Response.Data)
+ {
+ CharacterStyleAssets.Add(Asset.Id, Asset);
+ AddCharacterStyle(Asset);
+ }
+}
+
+
+void SRpmDeveloperLoginWidget::SetLoggedInState(const bool IsLoggedIn)
+{
+ this->bIsLoggedIn = IsLoggedIn;
+
+ // Force the UI to refresh
+ Invalidate(EInvalidateWidget::Layout);
+}
+
+END_SLATE_FUNCTION_BUILD_OPTIMIZATION
diff --git a/Source/RpmNextGenEditor/Public/AssetNameGenerator.h b/Source/RpmNextGenEditor/Public/AssetNameGenerator.h
new file mode 100644
index 0000000..346cd99
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/AssetNameGenerator.h
@@ -0,0 +1,32 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "TransientObjectSaverLibrary.h"
+#include "UObject/Object.h"
+#include "AssetNameGenerator.generated.h"
+
+/**
+ *
+ */
+UCLASS()
+class RPMNEXTGENEDITOR_API UAssetNameGenerator : public UObject
+{
+ GENERATED_BODY()
+
+public:
+ 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;
+
+private:
+ FString path;
+};
diff --git a/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h b/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h
new file mode 100644
index 0000000..316d9a6
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/DevAuthTokenCache.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "CoreMinimal.h"
+
+struct FDeveloperAuth;
+
+class RPMNEXTGENEDITOR_API FDevAuthTokenCache
+{
+public:
+ static FDeveloperAuth GetAuthData();
+ static void SetAuthData(const FDeveloperAuth& DevAuthData);
+ static void ClearAuthData();
+private:
+ 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
new file mode 100644
index 0000000..e8084b3
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/DeveloperAuthApi.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/WebApi.h"
+
+struct FDeveloperLoginResponse;
+struct FDeveloperLoginRequest;
+
+DECLARE_DELEGATE_TwoParams(FOnDeveloperLoginResponse, const FDeveloperLoginResponse&, bool);
+
+class RPMNEXTGENEDITOR_API FDeveloperAuthApi : public FWebApi
+{
+public:
+ FDeveloperAuthApi();
+
+ void HandleLoginResponse(FString JsonData, bool bIsSuccessful) 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
new file mode 100644
index 0000000..da3bf06
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/DeveloperTokenAuthStrategy.h
@@ -0,0 +1,22 @@
+#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;
+private:
+ void RefreshTokenAsync(const FRefreshTokenRequest& Request);
+ FOnWebApiResponse OnWebApiResponse;
+ FAuthApi AuthApi;
+};
diff --git a/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperAuth.h b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperAuth.h
new file mode 100644
index 0000000..aa7a092
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperAuth.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "DeveloperLoginResponse.h"
+#include "DeveloperAuth.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FDeveloperAuth
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "token"))
+ FString Token;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "refreshToken"))
+ FString RefreshToken;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "name"))
+ FString Name;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "isDemo"))
+ bool IsDemo;
+
+ FDeveloperAuth() = default;
+
+ FDeveloperAuth(FDeveloperLoginResponseBody Data, bool bIsDemo)
+ {
+ Token = Data.Token;
+ RefreshToken = Data.RefreshToken;
+ Name = Data.Name;
+ IsDemo = bIsDemo;
+ }
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString);
+ Writer->WriteObjectStart();
+ Writer->WriteValue(TEXT("token"), Token);
+ Writer->WriteValue(TEXT("refreshToken"), RefreshToken);
+ Writer->WriteValue(TEXT("name"), Name);
+ Writer->WriteValue(TEXT("isDemo"), IsDemo);
+ Writer->WriteObjectEnd();
+ Writer->Close();
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FDeveloperAuth& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+
+ bool IsValid() const
+ {
+ return IsDemo || !Token.IsEmpty();
+ }
+};
diff --git a/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginRequest.h b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginRequest.h
new file mode 100644
index 0000000..369453c
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginRequest.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "DeveloperLoginRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FDeveloperLoginRequestBody
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString LoginId;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me")
+ FString Password;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FDeveloperLoginRequestBody& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FDeveloperLoginRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Request")
+ FDeveloperLoginRequestBody Data;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonString(const FString& JsonString, FDeveloperLoginRequest& OutStruct)
+ {
+ return FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &OutStruct, 0, 0);
+ }
+
+ FDeveloperLoginRequest() = default;
+ explicit FDeveloperLoginRequest(const FDeveloperLoginRequestBody& DeveloperLoginRequestBody);
+ FDeveloperLoginRequest(const FString& Email, const FString& String)
+ {
+ Data.LoginId = Email;
+ Data.Password = String;
+ }
+};
diff --git a/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h
new file mode 100644
index 0000000..6af03f5
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/Auth/Models/DeveloperLoginResponse.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "JsonObjectConverter.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "DeveloperLoginResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FDeveloperLoginResponseBody : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "token"))
+ FString Token;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "refreshToken"))
+ FString RefreshToken;
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "name"))
+ FString Name;
+
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FDeveloperLoginResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ready Player Me", meta = (JsonName = "data"))
+ FDeveloperLoginResponseBody Data;
+
+ FString ToJsonString() const
+ {
+ FString OutputString;
+ FJsonObjectConverter::UStructToJsonObjectString(*this, OutputString);
+ return OutputString;
+ }
+
+ static bool FromJsonObject(const TSharedPtr& JsonObject, FDeveloperLoginResponse& OutObject)
+ {
+ if (JsonObject.IsValid())
+ {
+ return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), StaticStruct(), &OutObject, 0, 0);
+ }
+ UE_LOG(LogTemp, Warning, TEXT("JsonObject Invalid"));
+ return false;
+ }
+};
diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h
new file mode 100644
index 0000000..21565be
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/DeveloperAccountApi.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/WebApiWithAuth.h"
+
+struct FOrganizationListResponse;
+struct FOrganizationListRequest;
+struct FApplicationListResponse;
+struct FApplicationListRequest;
+
+DECLARE_DELEGATE_TwoParams(FOnApplicationListResponse, const FApplicationListResponse&, bool);
+DECLARE_DELEGATE_TwoParams(FOnOrganizationListResponse, const FOrganizationListResponse&, bool);
+
+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);
+
+ FString ApiBaseUrl;
+};
diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h
new file mode 100644
index 0000000..12b49a8
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListRequest.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Auth/ApiRequest.h"
+#include "ApplicationListRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FApplicationListRequest : public FApiRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "API")
+ TMap Params;
+};
diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListResponse.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListResponse.h
new file mode 100644
index 0000000..96cf4e2
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/ApplicationListResponse.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "ApplicationListResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FApplication
+{
+ GENERATED_BODY()
+ UPROPERTY(meta = (JsonName = "id"))
+ FString Id;
+ UPROPERTY(meta=(Jsonname = "name" ))
+ FString Name;
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FApplicationListResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "API", meta = (JsonName = "data"))
+ TArray Data;
+};
diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h
new file mode 100644
index 0000000..0a83cd0
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListRequest.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Auth/ApiRequest.h"
+#include "OrganizationListRequest.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FOrganizationListRequest : public FApiRequest
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "API")
+ TMap Params;
+};
diff --git a/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListResponse.h b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListResponse.h
new file mode 100644
index 0000000..8c3355e
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/DeveloperAccounts/Models/OrganizationListResponse.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Common/Models/ApiResponse.h"
+#include "Api/Common/WebApiWithAuth.h"
+#include "OrganizationListResponse.generated.h"
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FOrganization
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (JsonName = "id"))
+ FString Id;
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (JsonName = "name"))
+ FString Name;
+};
+
+USTRUCT(BlueprintType)
+struct RPMNEXTGENEDITOR_API FOrganizationListResponse : public FApiResponse
+{
+ GENERATED_BODY()
+
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "API", meta = ( JsonName = "data"))
+ TArray Data;
+};
diff --git a/Source/RpmNextGenEditor/Public/EditorAssetLoader.h b/Source/RpmNextGenEditor/Public/EditorAssetLoader.h
new file mode 100644
index 0000000..2dfa540
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/EditorAssetLoader.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Api/Assets/AssetLoader.h"
+#include "HAL/PlatformFilemanager.h"
+
+class RPMNEXTGENEDITOR_API FEditorAssetLoader : public FAssetLoader
+{
+public:
+ void OnAssetLoadComplete(UglTFRuntimeAsset* gltfAsset, bool bWasSuccessful,
+ FString LoadedAssetId);
+ FEditorAssetLoader();
+ virtual ~FEditorAssetLoader() override;
+
+ void LoadAssetToWorldAsURpmActor(UglTFRuntimeAsset* gltfAsset, FString AssetId = "");
+ void LoadGLBFromURLWithId(const FString& URL, const FString AssetId);
+ USkeletalMesh* SaveAsUAsset(UglTFRuntimeAsset* GltfAsset, const FString& LoadedAssetId) const;
+ USkeleton* SkeletonToCopy;
+
+private:
+ void LoadAssetToWorld(FString AssetId, UglTFRuntimeAsset* gltfAsset);
+};
diff --git a/Source/RpmNextGenEditor/Public/EditorCache.h b/Source/RpmNextGenEditor/Public/EditorCache.h
new file mode 100644
index 0000000..b1ae63e
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/EditorCache.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "CoreMinimal.h"
+
+class RPMNEXTGENEDITOR_API FEditorCache
+{
+public:
+ static void SetString(const FString& Key, const FString& Value);
+ static void SetInt(const FString& Key, int32 Value);
+ static void SetFloat(const FString& Key, float Value);
+ static void SetBool(const FString& Key, bool Value);
+
+ static FString GetString(const FString& Key, const FString& DefaultValue = TEXT(""));
+ static int32 GetInt(const FString& Key, int32 DefaultValue = 0);
+ static float GetFloat(const FString& Key, float DefaultValue = 0.0f);
+ static bool GetBool(const FString& Key, bool DefaultValue = false);
+
+ static void RemoveKey(const FString& Key);
+};
diff --git a/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h b/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h
new file mode 100644
index 0000000..96034cf
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/RpmNextGenEditor.h
@@ -0,0 +1,28 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+
+class RPMNEXTGENEDITOR_API FRpmNextGenEditorModule : public IModuleInterface
+{
+public:
+
+ /** IModuleInterface implementation */
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+
+ /** This function will be bound to Command (by default it will bring up plugin window) */
+ void PluginButtonClicked();
+
+private:
+
+ void RegisterMenus();
+ void FillReadyPlayerMeMenu(UToolMenu* Menu);
+ void OpenLoaderWindow();
+ TSharedRef OnSpawnLoaderWindow(const FSpawnTabArgs& SpawnTabArgs);
+ TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs);
+ TSharedPtr PluginCommands;
+
+};
diff --git a/Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h b/Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h
new file mode 100644
index 0000000..b4a61ce
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/UI/CharacterLoaderWidget.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EditorAssetLoader.h"
+#include "Api/Assets/AssetLoader.h"
+#include "Widgets/SCompoundWidget.h"
+#include "Widgets/DeclarativeSyntaxSupport.h"
+
+class FEditorAssetLoader;
+class FAssetLoader;
+
+class SCharacterLoaderWidget : public SCompoundWidget
+{
+public:
+ 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();
+
+ /** Stores the text input by the user */
+ FText PathText;
+
+ /** Callback for when the text in the input field changes */
+ 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/Commands/LoaderWindowCommands.h b/Source/RpmNextGenEditor/Public/UI/Commands/LoaderWindowCommands.h
new file mode 100644
index 0000000..df9a697
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/UI/Commands/LoaderWindowCommands.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EditorStyleSet.h"
+#include "Framework/Commands/Commands.h"
+
+class FLoaderWindowCommands : public TCommands
+{
+public:
+ FLoaderWindowCommands()
+ : TCommands(TEXT("LoaderWindow"), NSLOCTEXT("Contexts", "LoaderWindow", "Loader Window Plugin"), NAME_None, FEditorStyle::GetStyleSetName())
+ {
+ }
+
+ virtual void RegisterCommands() override;
+
+public:
+ TSharedPtr OpenPluginWindow;
+};
diff --git a/Source/RpmNextGenEditor/Public/UI/Commands/LoginWindowCommands.h b/Source/RpmNextGenEditor/Public/UI/Commands/LoginWindowCommands.h
new file mode 100644
index 0000000..86dc7de
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/UI/Commands/LoginWindowCommands.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Framework/Commands/Commands.h"
+#include "UI/LoginWindowStyle.h"
+
+class RPMNEXTGENEDITOR_API FLoginWindowCommands: public TCommands
+{
+public:
+
+ FLoginWindowCommands()
+ : TCommands(TEXT("RpmNextGenEditor"), NSLOCTEXT("Contexts", "RpmNextGenEditor", "RpmNextGen Plugin"), NAME_None, FLoginWindowStyle::GetStyleSetName())
+ {
+ }
+
+ // TCommands<> interface
+ virtual void RegisterCommands() override;
+
+ TSharedPtr< FUICommandInfo > OpenPluginWindow;
+};
diff --git a/Source/RpmNextGenEditor/Public/UI/LoginWindowStyle.h b/Source/RpmNextGenEditor/Public/UI/LoginWindowStyle.h
new file mode 100644
index 0000000..75812ef
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/UI/LoginWindowStyle.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Styling/SlateStyle.h"
+
+class RPMNEXTGENEDITOR_API FLoginWindowStyle
+{
+public:
+
+ static void Initialize();
+
+ static void Shutdown();
+
+ /** reloads textures used by slate renderer */
+ static void ReloadTextures();
+
+ /** @return The Slate style set for the Shooter game */
+ static const ISlateStyle& Get();
+
+ static FName GetStyleSetName();
+
+private:
+
+ static TSharedRef< class FSlateStyleSet > Create();
+
+ static TSharedPtr< class FSlateStyleSet > StyleInstance;
+
+};
diff --git a/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h b/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h
new file mode 100644
index 0000000..c61bfa9
--- /dev/null
+++ b/Source/RpmNextGenEditor/Public/UI/SRpmDeveloperLoginWidget.h
@@ -0,0 +1,79 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EditorAssetLoader.h"
+#include "Api/Assets/AssetApi.h"
+#include "Api/Assets/Models/AssetListResponse.h"
+#include "Auth/DeveloperAuthApi.h"
+#include "DeveloperAccounts/DeveloperAccountApi.h"
+#include "DeveloperAccounts/Models/ApplicationListResponse.h"
+#include "Widgets/SCompoundWidget.h"
+#include "Containers/Map.h"
+
+
+struct FDeveloperLoginResponse;
+class URpmDeveloperSettings;
+class UDeveloperAuthApi;
+class SEditableTextBox;
+
+/**
+ *
+ */
+class RPMNEXTGENEDITOR_API SRpmDeveloperLoginWidget : public SCompoundWidget
+{
+public:
+ SLATE_BEGIN_ARGS(SRpmDeveloperLoginWidget)
+ {
+ }
+
+ SLATE_END_ARGS()
+
+ void Construct(const FArguments& InArgs);
+ virtual ~SRpmDeveloperLoginWidget() override;
+
+private:
+ TSharedPtr ContentBox;
+ TSharedPtr EmailTextBox;
+ TSharedPtr PasswordTextBox;
+ TSharedPtr SelectedApplicationTextBlock;
+ TSharedPtr SelectedComboBoxItem;
+ TArray> ComboBoxItems;
+ TArray CharacterStyleTextures;
+ TMap CharacterStyleAssets;
+
+ EVisibility GetLoginViewVisibility() const;
+ EVisibility GetLoggedInViewVisibility() const;
+
+ FEditorAssetLoader AssetLoader;
+ TUniquePtr AssetApi;
+ TUniquePtr DeveloperAccountApi;
+ TUniquePtr DeveloperAuthApi;
+ static constexpr const TCHAR* CacheKeyEmail = TEXT("Email");
+ bool bIsLoggedIn = false;
+ bool bIsInitialized = false;
+ FString UserName;
+ TArray UserApplications;
+ FText GetWelcomeText() const;
+ const FString DemoUserName = TEXT("Guest user");
+ FText GetSelectedComboBoxItemText() const;
+
+ FReply OnLoginClicked();
+ FReply OnUseDemoAccountClicked();
+ FReply OnLogoutClicked();
+
+ void Initialize();
+ void GetOrgList();
+ void ClearLoadedCharacterModelImages();
+ void LoadBaseModelList();
+ void HandleLoginResponse(const FDeveloperLoginResponse& Response, bool bWasSuccessful);
+ 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 SetLoggedInState(const bool IsLoggedIn);
+ void PopulateComboBoxItems(const TArray& Items, const FString ActiveItem);
+ void OnComboBoxSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo);
+ void AddCharacterStyle(const FAsset& StyleAsset);
+};
diff --git a/Source/RpmNextGenEditor/RpmNextGenEditor.Build.cs b/Source/RpmNextGenEditor/RpmNextGenEditor.Build.cs
new file mode 100644
index 0000000..1fe1b7b
--- /dev/null
+++ b/Source/RpmNextGenEditor/RpmNextGenEditor.Build.cs
@@ -0,0 +1,65 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class RpmNextGenEditor : ModuleRules
+{
+ public RpmNextGenEditor(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PublicIncludePaths.AddRange(
+ new string[] {
+ }
+ );
+
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ }
+ );
+
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "CoreUObject",
+ "Engine",
+ "HTTP",
+ "JsonUtilities",
+ "RpmNextGen",
+ "EditorStyle",
+ "glTFRuntime",
+ "TransientObjectSaver",
+ "UnrealEd",
+ "PropertyEditor",
+ "Slate",
+ "SlateCore",
+ }
+ );
+
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Projects",
+ "InputCore",
+ "EditorFramework",
+ "UnrealEd",
+ "ToolMenus",
+ "CoreUObject",
+ "Json",
+ "UMG",
+ "ImageWrapper",
+ }
+ );
+
+
+ DynamicallyLoadedModuleNames.AddRange(
+ new string[]
+ {
+ }
+ );
+ }
+}