From 22cac6b96b44ce1c71b2d4ed034d84ac429d02ff Mon Sep 17 00:00:00 2001 From: jamestagg Date: Tue, 20 Jan 2026 15:34:14 -0800 Subject: [PATCH] Add kitchen sink structure with catalogue, recipes, and presets Structure: - RalphaData/catalogue/ - Machine-readable asset index with JSON schema - RalphaData/recipes/ - Scene composition templates (living room, office, portrait) - RalphaData/presets/ - Lighting, post-processing, camera presets - RalphaData/scripts/ - Catalogue generation tooling - Content/Ralpha/Assets/ - Folder structure for all asset categories Key files: - catalogue.schema.json - Asset metadata schema with tags, bounds, licensing - recipe.schema.json - Scene composition schema with placement modes - generate_catalogue.py - Asset indexing script - LICENSE_POLICY.md - Asset provenance requirements Updated .gitattributes to keep RalphaData in regular git (not LFS) Co-Authored-By: Claude Opus 4.5 --- .gitattributes | 15 + LICENSE_POLICY.md | 110 + Plugins/RalphaPlugin | 1 + Plugins/RalphaPlugin/Config/FilterPlugin.ini | 8 - Plugins/RalphaPlugin/RalphaPlugin.uplugin | 29 - Plugins/RalphaPlugin/Resources/Icon20.svg | 6 - .../Source/RalphaCore/Private/RalphaCore.cpp | 29 - .../RalphaCore/Private/RalphaMCPServer.cpp | 965 -------- .../Private/RalphaParameterBridge.cpp | 2159 ----------------- .../Private/RalphaScreenCapture.cpp | 326 --- .../Source/RalphaCore/Public/RalphaCore.h | 20 - .../RalphaCore/Public/RalphaMCPServer.h | 98 - .../RalphaCore/Public/RalphaParameterBridge.h | 128 - .../RalphaCore/Public/RalphaScreenCapture.h | 58 - .../Source/RalphaCore/RalphaCore.Build.cs | 65 - .../RalphaEditor/Private/RalphaEditor.cpp | 82 - .../Private/RalphaEditorCommands.cpp | 12 - .../Private/RalphaEditorStyle.cpp | 59 - .../Source/RalphaEditor/Public/RalphaEditor.h | 22 - .../Public/RalphaEditorCommands.h | 25 - .../RalphaEditor/Public/RalphaEditorStyle.h | 20 - .../Source/RalphaEditor/RalphaEditor.Build.cs | 52 - README.md | 188 ++ Ralpha.uproject | 1 + RalphaData/catalogue/catalogue.json | 39 + RalphaData/catalogue/catalogue.schema.json | 122 + .../presets/lighting/natural_golden_hour.json | 40 + .../presets/lighting/studio_3point_soft.json | 55 + RalphaData/presets/post/pp_cinematic.json | 46 + RalphaData/recipes/living_room_modern.json | 68 + RalphaData/recipes/office_corporate.json | 77 + RalphaData/recipes/portrait_studio_white.json | 44 + RalphaData/recipes/recipe.schema.json | 137 ++ RalphaData/scripts/generate_catalogue.py | 186 ++ 34 files changed, 1129 insertions(+), 4163 deletions(-) create mode 100644 LICENSE_POLICY.md create mode 120000 Plugins/RalphaPlugin delete mode 100644 Plugins/RalphaPlugin/Config/FilterPlugin.ini delete mode 100644 Plugins/RalphaPlugin/RalphaPlugin.uplugin delete mode 100644 Plugins/RalphaPlugin/Resources/Icon20.svg delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaCore.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaScreenCapture.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaCore.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaMCPServer.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaScreenCapture.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaCore/RalphaCore.Build.cs delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditor.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorCommands.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorStyle.cpp delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditor.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorCommands.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorStyle.h delete mode 100644 Plugins/RalphaPlugin/Source/RalphaEditor/RalphaEditor.Build.cs create mode 100644 README.md create mode 100644 RalphaData/catalogue/catalogue.json create mode 100644 RalphaData/catalogue/catalogue.schema.json create mode 100644 RalphaData/presets/lighting/natural_golden_hour.json create mode 100644 RalphaData/presets/lighting/studio_3point_soft.json create mode 100644 RalphaData/presets/post/pp_cinematic.json create mode 100644 RalphaData/recipes/living_room_modern.json create mode 100644 RalphaData/recipes/office_corporate.json create mode 100644 RalphaData/recipes/portrait_studio_white.json create mode 100644 RalphaData/recipes/recipe.schema.json create mode 100644 RalphaData/scripts/generate_catalogue.py diff --git a/.gitattributes b/.gitattributes index d2b070bc..c53a97eb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -39,3 +39,18 @@ *.dll filter=lfs diff=lfs merge=lfs -text *.so filter=lfs diff=lfs merge=lfs -text *.dylib filter=lfs diff=lfs merge=lfs -text + +# UE bulk data +*.ubulk filter=lfs diff=lfs merge=lfs -text +*.uexp filter=lfs diff=lfs merge=lfs -text +*.upk filter=lfs diff=lfs merge=lfs -text + +# Keep RalphaData in regular git (not LFS) +# These are small JSON/text files for tooling +RalphaData/** !filter !diff !merge text +RalphaData/**/*.json text +RalphaData/**/*.py text +RalphaData/**/*.md text + +# Small thumbnails OK in regular git +RalphaData/thumbnails/*.png !filter !diff !merge -text diff --git a/LICENSE_POLICY.md b/LICENSE_POLICY.md new file mode 100644 index 00000000..4f8a463b --- /dev/null +++ b/LICENSE_POLICY.md @@ -0,0 +1,110 @@ +# Asset License Policy + +All assets in this repository must have clear licensing and provenance. + +## Allowed License Types + +### Tier 1: Unrestricted (Preferred) + +| License | Requirements | Folder | +|---------|--------------|--------| +| **CC0 / Public Domain** | None | `Content/Ralpha/Assets/` | +| **Unlicense** | None | `Content/Ralpha/Assets/` | +| **MIT** | Include license file | `Content/Ralpha/Assets/` | + +### Tier 2: Attribution Required + +| License | Requirements | Folder | +|---------|--------------|--------| +| **CC-BY** | Credit in catalogue.json | `Content/Ralpha/Assets/` | +| **CC-BY-SA** | Credit + share-alike | `Content/Ralpha/Assets/` | + +### Tier 3: Platform-Specific + +| License | Requirements | Folder | +|---------|--------------|--------| +| **Quixel Megascans** | Free with UE, UE projects only | `Content/Ralpha/Assets/` | +| **UE Marketplace** | Purchased, UE projects only | `Content/Ralpha/Assets/` | +| **Sketchfab Store** | Purchased, per-asset license | `Content/Ralpha/Assets/` | + +### Tier 4: Restricted + +| License | Requirements | Folder | +|---------|--------------|--------| +| **NC (Non-Commercial)** | Non-commercial use only | `Content/Ralpha/Restricted/` | +| **Custom/Unknown** | Case-by-case review | `Content/Ralpha/Restricted/` | + +## Prohibited + +- **ND (No Derivatives)** - Cannot modify, doesn't work for our use case +- **Copyrighted without license** - No unlicensed assets +- **AI-generated without rights** - Must have clear generation rights + +## Catalogue Requirements + +Every asset in `catalogue.json` must include: + +```json +{ + "license": { + "type": "CC0", // Required: license identifier + "source": "Poly Haven", // Required: where asset came from + "attribution": null, // Required for CC-BY: credit text + "url": "https://..." // Optional: source URL + } +} +``` + +## Recommended Sources + +### Free / CC0 + +| Source | License | Quality | Notes | +|--------|---------|---------|-------| +| [Poly Haven](https://polyhaven.com) | CC0 | Excellent | HDRIs, textures, models | +| [Sketchfab CC0](https://sketchfab.com/search?features=downloadable&licenses=cc0) | CC0 | Variable | Filter by license | +| [cgbookcase](https://cgbookcase.com) | CC0 | Good | Textures | +| [ambientCG](https://ambientcg.com) | CC0 | Good | PBR textures | +| [Kenney](https://kenney.nl) | CC0 | Stylized | Low-poly assets | + +### With UE License + +| Source | License | Quality | Notes | +|--------|---------|---------|-------| +| [Quixel Megascans](https://quixel.com/megascans) | Megascans | Excellent | Free with UE | +| [UE Marketplace](https://unrealengine.com/marketplace) | Marketplace | Variable | Many free monthly | + +### Paid + +| Source | License | Quality | Notes | +|--------|---------|---------|-------| +| [TurboSquid](https://turbosquid.com) | Per-asset | Variable | Check each license | +| [CGTrader](https://cgtrader.com) | Per-asset | Variable | Check each license | +| [Sketchfab Store](https://sketchfab.com/store) | Per-asset | Variable | Royalty-free options | + +## Adding New Assets + +1. **Verify license** before importing +2. **Document source** in commit message +3. **Add to catalogue** with full license info +4. **Place in correct folder** based on license tier + +## Audit Process + +Periodically run: + +```bash +# Find assets missing license info +python RalphaData/scripts/audit_licenses.py +``` + +## Questions? + +If unsure about an asset's license: +1. Don't add it +2. Ask in PR/issue +3. Find alternative with clear license + +--- + +*Last Updated: 2026-01-20* diff --git a/Plugins/RalphaPlugin b/Plugins/RalphaPlugin new file mode 120000 index 00000000..9b7d0116 --- /dev/null +++ b/Plugins/RalphaPlugin @@ -0,0 +1 @@ +../../ralpha/plugin \ No newline at end of file diff --git a/Plugins/RalphaPlugin/Config/FilterPlugin.ini b/Plugins/RalphaPlugin/Config/FilterPlugin.ini deleted file mode 100644 index ccebca2f..00000000 --- a/Plugins/RalphaPlugin/Config/FilterPlugin.ini +++ /dev/null @@ -1,8 +0,0 @@ -[FilterPlugin] -; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and -; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. -; -; Examples: -; /README.txt -; /Extras/... -; /Binaries/ThirdParty/*.dll diff --git a/Plugins/RalphaPlugin/RalphaPlugin.uplugin b/Plugins/RalphaPlugin/RalphaPlugin.uplugin deleted file mode 100644 index e43c477c..00000000 --- a/Plugins/RalphaPlugin/RalphaPlugin.uplugin +++ /dev/null @@ -1,29 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "Ralpha - AI Style Transfer", - "Description": "AI-driven style transfer system for Unreal Engine 5. Enables Claude to control rendering parameters via MCP.", - "Category": "Rendering", - "CreatedBy": "Ralpha Team", - "CreatedByURL": "", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "IsBetaVersion": true, - "IsExperimentalVersion": false, - "Installed": false, - "Modules": [ - { - "Name": "RalphaCore", - "Type": "Runtime", - "LoadingPhase": "Default" - }, - { - "Name": "RalphaEditor", - "Type": "Editor", - "LoadingPhase": "PostEngineInit" - } - ] -} diff --git a/Plugins/RalphaPlugin/Resources/Icon20.svg b/Plugins/RalphaPlugin/Resources/Icon20.svg deleted file mode 100644 index 820aba8f..00000000 --- a/Plugins/RalphaPlugin/Resources/Icon20.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaCore.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaCore.cpp deleted file mode 100644 index f484794e..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaCore.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaCore.h" - -#define LOCTEXT_NAMESPACE "FRalphaCoreModule" - -void FRalphaCoreModule::StartupModule() -{ - UE_LOG(LogTemp, Log, TEXT("Ralpha Core Module Started")); -} - -void FRalphaCoreModule::ShutdownModule() -{ - UE_LOG(LogTemp, Log, TEXT("Ralpha Core Module Shutdown")); -} - -FRalphaCoreModule& FRalphaCoreModule::Get() -{ - return FModuleManager::LoadModuleChecked("RalphaCore"); -} - -bool FRalphaCoreModule::IsAvailable() -{ - return FModuleManager::Get().IsModuleLoaded("RalphaCore"); -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FRalphaCoreModule, RalphaCore) diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp deleted file mode 100644 index 9e238ca1..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaMCPServer.cpp +++ /dev/null @@ -1,965 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaMCPServer.h" -#include "RalphaParameterBridge.h" -#include "RalphaScreenCapture.h" -#include "Dom/JsonObject.h" -#include "Dom/JsonValue.h" -#include "Serialization/JsonSerializer.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Misc/Base64.h" -#include "Engine/World.h" -#include "Engine/Engine.h" -#include "Kismet/GameplayStatics.h" - -DEFINE_LOG_CATEGORY(LogRalphaMCP); - -URalphaMCPServer* URalphaMCPServer::Instance = nullptr; - -URalphaMCPServer::URalphaMCPServer() - : ListenerSocket(nullptr) - , ServerPort(30010) - , bIsRunning(false) - , ParameterBridge(nullptr) - , ScreenCapture(nullptr) -{ -} - -URalphaMCPServer::~URalphaMCPServer() -{ - Stop(); -} - -URalphaMCPServer* URalphaMCPServer::Get() -{ - if (!Instance) - { - Instance = NewObject(); - Instance->AddToRoot(); // Prevent garbage collection - } - return Instance; -} - -bool URalphaMCPServer::Start(int32 Port) -{ - if (bIsRunning) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Server already running on port %d"), ServerPort); - return true; - } - - ServerPort = Port; - - // Create socket - ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); - if (!SocketSubsystem) - { - UE_LOG(LogRalphaMCP, Error, TEXT("Failed to get socket subsystem")); - return false; - } - - ListenerSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("RalphaMCPServer"), false); - if (!ListenerSocket) - { - UE_LOG(LogRalphaMCP, Error, TEXT("Failed to create socket")); - return false; - } - - // Configure socket - ListenerSocket->SetReuseAddr(true); - ListenerSocket->SetNonBlocking(true); - - // Bind to port - TSharedRef Addr = SocketSubsystem->CreateInternetAddr(); - Addr->SetAnyAddress(); - Addr->SetPort(ServerPort); - - if (!ListenerSocket->Bind(*Addr)) - { - UE_LOG(LogRalphaMCP, Error, TEXT("Failed to bind to port %d"), ServerPort); - SocketSubsystem->DestroySocket(ListenerSocket); - ListenerSocket = nullptr; - return false; - } - - // Start listening - if (!ListenerSocket->Listen(8)) - { - UE_LOG(LogRalphaMCP, Error, TEXT("Failed to listen on port %d"), ServerPort); - SocketSubsystem->DestroySocket(ListenerSocket); - ListenerSocket = nullptr; - return false; - } - - // Create parameter bridge and screen capture - ParameterBridge = NewObject(this); - ScreenCapture = NewObject(this); - - // Register tick - TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker( - FTickerDelegate::CreateUObject(this, &URalphaMCPServer::Tick), 0.0f); - - bIsRunning = true; - UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server started on port %d"), ServerPort); - - return true; -} - -void URalphaMCPServer::Stop() -{ - if (!bIsRunning) - { - return; - } - - // Unregister tick - FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); - - // Close all client connections - ClientConnections.Empty(); - - // Close listener socket - if (ListenerSocket) - { - ListenerSocket->Close(); - ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket); - ListenerSocket = nullptr; - } - - bIsRunning = false; - UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server stopped")); -} - -bool URalphaMCPServer::IsRunning() const -{ - return bIsRunning; -} - -bool URalphaMCPServer::Tick(float DeltaTime) -{ - if (!bIsRunning || !ListenerSocket) - { - return true; - } - - // Check for new connections - bool bHasPendingConnection = false; - if (ListenerSocket->HasPendingConnection(bHasPendingConnection) && bHasPendingConnection) - { - TSharedRef RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); - FSocket* ClientSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RalphaClient")); - - if (ClientSocket) - { - ClientSocket->SetNonBlocking(true); - - TSharedPtr NewConnection = MakeShared(ClientSocket, this); - ClientConnections.Add(NewConnection); - - UE_LOG(LogRalphaMCP, Log, TEXT("New client connected from %s"), *RemoteAddress->ToString(true)); - } - } - - // Process existing connections - for (int32 i = ClientConnections.Num() - 1; i >= 0; --i) - { - if (ClientConnections[i]->IsValid()) - { - ClientConnections[i]->Tick(); - } - else - { - ClientConnections.RemoveAt(i); - } - } - - return true; -} - -FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand) -{ - TSharedPtr JsonObject; - TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonCommand); - - if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) - { - return TEXT("{\"success\": false, \"error\": \"Invalid JSON\"}"); - } - - FString CommandType; - if (!JsonObject->TryGetStringField(TEXT("type"), CommandType)) - { - return TEXT("{\"success\": false, \"error\": \"Missing command type\"}"); - } - - TSharedPtr ResponseObject = MakeShared(); - - // Route commands - if (CommandType == TEXT("capture_screenshot")) - { - int32 Width = JsonObject->HasField(TEXT("width")) ? JsonObject->GetIntegerField(TEXT("width")) : 1920; - int32 Height = JsonObject->HasField(TEXT("height")) ? JsonObject->GetIntegerField(TEXT("height")) : 1080; - bool bUseCamera = JsonObject->HasField(TEXT("use_camera")) ? JsonObject->GetBoolField(TEXT("use_camera")) : true; - - FString Base64Image; - FString FilePath; - bool bSuccess = false; - - if (bUseCamera) - { - // Capture from CineCamera using SceneCapture2D - bSuccess = ScreenCapture->CaptureFromCamera(Width, Height, Base64Image, FilePath); - } - - // Fallback to viewport capture if camera capture failed or not requested - if (!bSuccess) - { - bSuccess = ScreenCapture->CaptureScreenshot(Width, Height, Base64Image, FilePath); - } - - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("base64"), Base64Image); - ResponseObject->SetStringField(TEXT("path"), FilePath); - } - } - else if (CommandType == TEXT("get_all_parameters")) - { - TSharedPtr ParamsObject = ParameterBridge->GetAllParameters(); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetObjectField(TEXT("data"), ParamsObject); - } - else if (CommandType == TEXT("set_post_process")) - { - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetPostProcessParameters(*Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("set_directional_light")) - { - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetDirectionalLightParameters(*Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("set_sky_light")) - { - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetSkyLightParameters(*Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("set_exponential_height_fog")) - { - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetFogParameters(*Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("set_camera")) - { - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetCameraParameters(*Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("search_assets")) - { - FString Query = JsonObject->GetStringField(TEXT("query")); - FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All"); - int32 MaxResults = JsonObject->HasField(TEXT("max_results")) ? JsonObject->GetIntegerField(TEXT("max_results")) : 20; - - TArray> Results = ParameterBridge->SearchAssets(Query, AssetType, MaxResults); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetArrayField(TEXT("results"), Results); - } - else if (CommandType == TEXT("spawn_actor")) - { - FString AssetPath = JsonObject->GetStringField(TEXT("asset_path")); - - const TSharedPtr* LocationObj; - FVector Location = FVector::ZeroVector; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->GetNumberField(TEXT("x")); - Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); - Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); - } - - FRotator Rotation = FRotator::ZeroRotator; - const TSharedPtr* RotationObj; - if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; - Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; - Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; - } - - float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnActor(AssetPath, Location, Rotation, Scale, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("delete_actor")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - bool bSuccess = ParameterBridge->DeleteActor(ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("spawn_ocean")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 100000.0f; - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnOcean(Location, Size, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("set_ocean_waves")) - { - float WaveAmplitude = JsonObject->HasField(TEXT("amplitude")) ? JsonObject->GetNumberField(TEXT("amplitude")) : 100.0f; - float WaveLength = JsonObject->HasField(TEXT("wavelength")) ? JsonObject->GetNumberField(TEXT("wavelength")) : 1000.0f; - float Steepness = JsonObject->HasField(TEXT("steepness")) ? JsonObject->GetNumberField(TEXT("steepness")) : 0.5f; - int32 NumWaves = JsonObject->HasField(TEXT("num_waves")) ? JsonObject->GetIntegerField(TEXT("num_waves")) : 4; - - bool bSuccess = ParameterBridge->SetOceanWaves(WaveAmplitude, WaveLength, Steepness, NumWaves); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("create_landscape")) - { - float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 10000.0f; - float HeightOffset = JsonObject->HasField(TEXT("height_offset")) ? JsonObject->GetNumberField(TEXT("height_offset")) : -500.0f; - - bool bSuccess = ParameterBridge->CreateLandscape(Size, HeightOffset); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("set_actor_material")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - FString MaterialPath = JsonObject->GetStringField(TEXT("material_path")); - int32 MaterialIndex = JsonObject->HasField(TEXT("material_index")) ? JsonObject->GetIntegerField(TEXT("material_index")) : 0; - bool bSuccess = ParameterBridge->SetActorMaterial(ActorId, MaterialPath, MaterialIndex); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("set_actor_simple_material")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - - // Parse color - supports hex string or RGB object - FLinearColor BaseColor = FLinearColor(0.1f, 0.2f, 0.4f); // Default dark blue - if (JsonObject->HasField(TEXT("color"))) - { - const TSharedPtr* ColorObj; - if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) - { - BaseColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 0.1f; - BaseColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.2f; - BaseColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.4f; - BaseColor.A = (*ColorObj)->HasField(TEXT("a")) ? (*ColorObj)->GetNumberField(TEXT("a")) : 1.0f; - } - } - - float Metallic = JsonObject->HasField(TEXT("metallic")) ? JsonObject->GetNumberField(TEXT("metallic")) : 0.0f; - float Roughness = JsonObject->HasField(TEXT("roughness")) ? JsonObject->GetNumberField(TEXT("roughness")) : 0.5f; - float Opacity = JsonObject->HasField(TEXT("opacity")) ? JsonObject->GetNumberField(TEXT("opacity")) : 1.0f; - - bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("set_actor_emissive_material")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - - // Parse emissive color - supports RGB object - FLinearColor EmissiveColor = FLinearColor(1.0f, 0.8f, 0.3f); // Default warm yellow - if (JsonObject->HasField(TEXT("color"))) - { - const TSharedPtr* ColorObj; - if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) - { - EmissiveColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; - EmissiveColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.8f; - EmissiveColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.3f; - } - } - - float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 10.0f; - - bool bSuccess = ParameterBridge->SetActorEmissiveMaterial(ActorId, EmissiveColor, Intensity); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("list_actors")) - { - FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT(""); - FString NameFilter = JsonObject->HasField(TEXT("name_filter")) ? JsonObject->GetStringField(TEXT("name_filter")) : TEXT(""); - bool bIncludeTransforms = JsonObject->HasField(TEXT("include_transforms")) ? JsonObject->GetBoolField(TEXT("include_transforms")) : true; - - TArray> Actors = ParameterBridge->ListActors(ClassFilter, NameFilter, bIncludeTransforms); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetArrayField(TEXT("actors"), Actors); - } - else if (CommandType == TEXT("set_render_quality")) - { - const TSharedPtr* Settings; - if (JsonObject->TryGetObjectField(TEXT("settings"), Settings)) - { - bool bSuccess = ParameterBridge->SetRenderQuality(*Settings); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing settings")); - } - } - else if (CommandType == TEXT("setup_scene")) - { - TSharedPtr SetupResult = ParameterBridge->SetupScene(); - ResponseObject = SetupResult; - } - else if (CommandType == TEXT("exec_command")) - { - FString Command = JsonObject->GetStringField(TEXT("command")); - if (!Command.IsEmpty()) - { - UWorld* World = GEngine->GetWorldContexts()[0].World(); - if (World) - { - GEngine->Exec(World, *Command); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetStringField(TEXT("executed"), Command); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("No world available")); - } - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing command")); - } - } - else if (CommandType == TEXT("new_level")) - { - FString Template = JsonObject->HasField(TEXT("template")) ? JsonObject->GetStringField(TEXT("template")) : TEXT("Basic"); -#if WITH_EDITOR - if (GEditor) - { - // Create new level from template - GEditor->Exec(nullptr, TEXT("MAP NEW")); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetStringField(TEXT("message"), TEXT("New level created")); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Editor not available")); - } -#else - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor")); -#endif - } - else if (CommandType == TEXT("spawn_point_light")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; - float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FLinearColor Color = FLinearColor::White; - if (JsonObject->HasField(TEXT("color"))) - { - const TSharedPtr* ColorObj; - if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) - { - Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; - Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; - Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; - } - } - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnPointLight(Location, Intensity, Color, AttenuationRadius, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("set_point_light")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetPointLightParameters(ActorId, *Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("spawn_spot_light")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - FRotator Rotation = FRotator::ZeroRotator; - const TSharedPtr* RotationObj; - if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; - Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; - Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; - } - - float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; - float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; - float InnerConeAngle = JsonObject->HasField(TEXT("inner_cone_angle")) ? JsonObject->GetNumberField(TEXT("inner_cone_angle")) : 20.0f; - float OuterConeAngle = JsonObject->HasField(TEXT("outer_cone_angle")) ? JsonObject->GetNumberField(TEXT("outer_cone_angle")) : 40.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FLinearColor Color = FLinearColor::White; - if (JsonObject->HasField(TEXT("color"))) - { - const TSharedPtr* ColorObj; - if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) - { - Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; - Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; - Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; - } - } - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnSpotLight(Location, Rotation, Intensity, Color, AttenuationRadius, InnerConeAngle, OuterConeAngle, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("set_spot_light")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetSpotLightParameters(ActorId, *Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("spawn_rect_light")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - FRotator Rotation = FRotator::ZeroRotator; - const TSharedPtr* RotationObj; - if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; - Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; - Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; - } - - float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; - float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; - float SourceWidth = JsonObject->HasField(TEXT("source_width")) ? JsonObject->GetNumberField(TEXT("source_width")) : 100.0f; - float SourceHeight = JsonObject->HasField(TEXT("source_height")) ? JsonObject->GetNumberField(TEXT("source_height")) : 100.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FLinearColor Color = FLinearColor::White; - if (JsonObject->HasField(TEXT("color"))) - { - const TSharedPtr* ColorObj; - if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) - { - Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; - Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; - Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; - } - } - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnRectLight(Location, Rotation, Intensity, Color, SourceWidth, SourceHeight, AttenuationRadius, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("set_rect_light")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - const TSharedPtr* Params; - if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) - { - bool bSuccess = ParameterBridge->SetRectLightParameters(ActorId, *Params); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); - } - } - else if (CommandType == TEXT("spawn_skeletal_mesh")) - { - FString SkeletalMeshPath = JsonObject->GetStringField(TEXT("skeletal_mesh_path")); - - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - FRotator Rotation = FRotator::ZeroRotator; - const TSharedPtr* RotationObj; - if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; - Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; - Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; - } - - float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnSkeletalMesh(SkeletalMeshPath, Location, Rotation, Scale, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("play_animation")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path")); - bool bLooping = JsonObject->HasField(TEXT("looping")) ? JsonObject->GetBoolField(TEXT("looping")) : true; - float PlayRate = JsonObject->HasField(TEXT("play_rate")) ? JsonObject->GetNumberField(TEXT("play_rate")) : 1.0f; - - bool bSuccess = ParameterBridge->PlayAnimation(ActorId, AnimationPath, bLooping, PlayRate); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("stop_animation")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - bool bSuccess = ParameterBridge->StopAnimation(ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("set_animation_pose")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path")); - float Time = JsonObject->HasField(TEXT("time")) ? JsonObject->GetNumberField(TEXT("time")) : 0.0f; - - bool bSuccess = ParameterBridge->SetAnimationPose(ActorId, AnimationPath, Time); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("spawn_sphere_reflection_capture")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - float InfluenceRadius = JsonObject->HasField(TEXT("influence_radius")) ? JsonObject->GetNumberField(TEXT("influence_radius")) : 3000.0f; - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnSphereReflectionCapture(Location, InfluenceRadius, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("spawn_box_reflection_capture")) - { - FVector Location = FVector::ZeroVector; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - } - - FVector BoxExtent = FVector(1000.0f, 1000.0f, 400.0f); - const TSharedPtr* ExtentObj; - if (JsonObject->TryGetObjectField(TEXT("box_extent"), ExtentObj)) - { - BoxExtent.X = (*ExtentObj)->HasField(TEXT("x")) ? (*ExtentObj)->GetNumberField(TEXT("x")) : 1000.0f; - BoxExtent.Y = (*ExtentObj)->HasField(TEXT("y")) ? (*ExtentObj)->GetNumberField(TEXT("y")) : 1000.0f; - BoxExtent.Z = (*ExtentObj)->HasField(TEXT("z")) ? (*ExtentObj)->GetNumberField(TEXT("z")) : 400.0f; - } - - FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); - - FString ActorId; - bool bSuccess = ParameterBridge->SpawnBoxReflectionCapture(Location, BoxExtent, ActorLabel, ActorId); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - if (bSuccess) - { - ResponseObject->SetStringField(TEXT("actor_id"), ActorId); - } - } - else if (CommandType == TEXT("update_reflection_captures")) - { - bool bSuccess = ParameterBridge->UpdateReflectionCaptures(); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("set_light_ies_profile")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - FString IESProfilePath = JsonObject->GetStringField(TEXT("ies_profile_path")); - - bool bSuccess = ParameterBridge->SetLightIESProfile(ActorId, IESProfilePath); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("list_assets")) - { - FString FolderPath = JsonObject->HasField(TEXT("folder_path")) ? JsonObject->GetStringField(TEXT("folder_path")) : TEXT("/Game"); - FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All"); - bool bRecursive = JsonObject->HasField(TEXT("recursive")) ? JsonObject->GetBoolField(TEXT("recursive")) : true; - - TArray> Results = ParameterBridge->ListAssets(FolderPath, AssetType, bRecursive); - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetArrayField(TEXT("assets"), Results); - } - else if (CommandType == TEXT("set_actor_transform")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - bool bRelative = JsonObject->HasField(TEXT("relative")) ? JsonObject->GetBoolField(TEXT("relative")) : false; - - const FVector* LocationPtr = nullptr; - FVector Location; - const TSharedPtr* LocationObj; - if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) - { - Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; - Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; - Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; - LocationPtr = &Location; - } - - const FRotator* RotationPtr = nullptr; - FRotator Rotation; - const TSharedPtr* RotationObj; - if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; - Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; - Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; - RotationPtr = &Rotation; - } - - const float* ScalePtr = nullptr; - float Scale; - if (JsonObject->HasField(TEXT("scale"))) - { - Scale = JsonObject->GetNumberField(TEXT("scale")); - ScalePtr = &Scale; - } - - bool bSuccess = ParameterBridge->SetActorTransform(ActorId, LocationPtr, RotationPtr, ScalePtr, bRelative); - ResponseObject->SetBoolField(TEXT("success"), bSuccess); - } - else if (CommandType == TEXT("get_actor_details")) - { - FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); - bool bIncludeComponents = JsonObject->HasField(TEXT("include_components")) ? JsonObject->GetBoolField(TEXT("include_components")) : true; - bool bIncludeMaterials = JsonObject->HasField(TEXT("include_materials")) ? JsonObject->GetBoolField(TEXT("include_materials")) : true; - - TSharedPtr Details = ParameterBridge->GetActorDetails(ActorId, bIncludeComponents, bIncludeMaterials); - if (Details.IsValid()) - { - ResponseObject->SetBoolField(TEXT("success"), true); - ResponseObject->SetObjectField(TEXT("actor"), Details); - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), TEXT("Actor not found")); - } - } - else - { - ResponseObject->SetBoolField(TEXT("success"), false); - ResponseObject->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command type: %s"), *CommandType)); - } - - // Serialize response - FString ResponseString; - TSharedRef> Writer = TJsonWriterFactory<>::Create(&ResponseString); - FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer); - - return ResponseString; -} - -// FRalphaClientConnection implementation - -FRalphaClientConnection::FRalphaClientConnection(FSocket* InSocket, URalphaMCPServer* InServer) - : Socket(InSocket) - , Server(InServer) -{ -} - -FRalphaClientConnection::~FRalphaClientConnection() -{ - if (Socket) - { - Socket->Close(); - ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket); - Socket = nullptr; - } -} - -bool FRalphaClientConnection::IsValid() const -{ - return Socket != nullptr && Socket->GetConnectionState() == SCS_Connected; -} - -void FRalphaClientConnection::Tick() -{ - if (!IsValid()) - { - return; - } - - // Receive data - uint32 PendingDataSize = 0; - while (Socket->HasPendingData(PendingDataSize) && PendingDataSize > 0) - { - TArray ReceivedData; - ReceivedData.SetNumUninitialized(FMath::Min(PendingDataSize, 65536u)); - - int32 BytesRead = 0; - if (Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), BytesRead)) - { - ReceiveBuffer += FString(UTF8_TO_TCHAR(reinterpret_cast(ReceivedData.GetData()))); - } - } - - // Process complete messages (newline-delimited JSON) - int32 NewlineIndex; - while (ReceiveBuffer.FindChar(TEXT('\n'), NewlineIndex)) - { - FString Message = ReceiveBuffer.Left(NewlineIndex); - ReceiveBuffer = ReceiveBuffer.Mid(NewlineIndex + 1); - - if (!Message.IsEmpty()) - { - FString Response = Server->ProcessCommand(Message); - SendResponse(Response); - } - } -} - -void FRalphaClientConnection::SendResponse(const FString& Response) -{ - if (!IsValid()) - { - return; - } - - FString ResponseWithNewline = Response + TEXT("\n"); - FTCHARToUTF8 Converter(*ResponseWithNewline); - int32 BytesSent = 0; - Socket->Send(reinterpret_cast(Converter.Get()), Converter.Length(), BytesSent); -} diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp deleted file mode 100644 index 76eba844..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaParameterBridge.cpp +++ /dev/null @@ -1,2159 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaParameterBridge.h" -#include "RalphaMCPServer.h" -#include "Engine/Engine.h" -#include "Engine/World.h" -#include "Engine/PostProcessVolume.h" -#include "Engine/DirectionalLight.h" -#include "Engine/SkyLight.h" -#include "Engine/ExponentialHeightFog.h" -#include "CineCameraActor.h" -#include "CineCameraComponent.h" -#include "Components/PostProcessComponent.h" -#include "Components/DirectionalLightComponent.h" -#include "Components/SkyLightComponent.h" -#include "Components/ExponentialHeightFogComponent.h" -#include "Kismet/GameplayStatics.h" -#include "EngineUtils.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "Engine/StaticMeshActor.h" -#include "Engine/StaticMesh.h" -#include "Components/SkyAtmosphereComponent.h" -#include "Components/VolumetricCloudComponent.h" -#include "Dom/JsonObject.h" -#include "Dom/JsonValue.h" -#include "Serialization/JsonSerializer.h" -#include "Materials/MaterialInstanceDynamic.h" -#include "WaterBodyOceanActor.h" -#include "WaterBodyActor.h" -#include "WaterBodyComponent.h" -#include "GerstnerWaterWaves.h" -#include "Landscape.h" -#include "LandscapeProxy.h" -#include "LandscapeInfo.h" -#include "Engine/PointLight.h" -#include "Engine/SpotLight.h" -#include "Engine/RectLight.h" -#include "Components/PointLightComponent.h" -#include "Components/SpotLightComponent.h" -#include "Components/RectLightComponent.h" -#include "Animation/SkeletalMeshActor.h" -#include "Components/SkeletalMeshComponent.h" -#include "Animation/AnimSequence.h" -#include "Animation/AnimSingleNodeInstance.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/SphereReflectionCapture.h" -#include "Engine/BoxReflectionCapture.h" -#include "Components/ReflectionCaptureComponent.h" -#include "Components/SphereReflectionCaptureComponent.h" -#include "Components/BoxReflectionCaptureComponent.h" -#include "Engine/TextureLightProfile.h" - -URalphaParameterBridge::URalphaParameterBridge() -{ -} - -UWorld* URalphaParameterBridge::GetCurrentWorld() -{ - if (GEngine && GEngine->GetWorldContexts().Num() > 0) - { - for (const FWorldContext& Context : GEngine->GetWorldContexts()) - { - if (Context.WorldType == EWorldType::Editor || Context.WorldType == EWorldType::PIE) - { - return Context.World(); - } - } - } - return nullptr; -} - -APostProcessVolume* URalphaParameterBridge::FindPostProcessVolume() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return nullptr; - - for (TActorIterator It(World); It; ++It) - { - if (It->bUnbound) // Prefer unbound (global) volumes - { - return *It; - } - } - - // Return any post-process volume if no unbound one found - for (TActorIterator It(World); It; ++It) - { - return *It; - } - - return nullptr; -} - -ADirectionalLight* URalphaParameterBridge::FindDirectionalLight() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return nullptr; - - for (TActorIterator It(World); It; ++It) - { - return *It; - } - return nullptr; -} - -ASkyLight* URalphaParameterBridge::FindSkyLight() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return nullptr; - - for (TActorIterator It(World); It; ++It) - { - return *It; - } - return nullptr; -} - -AExponentialHeightFog* URalphaParameterBridge::FindExponentialHeightFog() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return nullptr; - - for (TActorIterator It(World); It; ++It) - { - return *It; - } - return nullptr; -} - -ACineCameraActor* URalphaParameterBridge::FindCineCamera() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return nullptr; - - for (TActorIterator It(World); It; ++It) - { - return *It; - } - return nullptr; -} - -FLinearColor URalphaParameterBridge::ParseHexColor(const FString& HexColor) -{ - FString CleanHex = HexColor; - CleanHex.RemoveFromStart(TEXT("#")); - - FColor Color = FColor::FromHex(CleanHex); - return FLinearColor(Color); -} - -FString URalphaParameterBridge::ColorToHex(const FLinearColor& Color) -{ - FColor SRGBColor = Color.ToFColor(true); - return FString::Printf(TEXT("#%02X%02X%02X"), SRGBColor.R, SRGBColor.G, SRGBColor.B); -} - -TSharedPtr URalphaParameterBridge::GetAllParameters() -{ - TSharedPtr Result = MakeShared(); - - Result->SetObjectField(TEXT("post_process"), GetPostProcessParameters()); - Result->SetObjectField(TEXT("directional_light"), GetDirectionalLightParameters()); - Result->SetObjectField(TEXT("sky_light"), GetSkyLightParameters()); - Result->SetObjectField(TEXT("fog"), GetFogParameters()); - Result->SetObjectField(TEXT("camera"), GetCameraParameters()); - - return Result; -} - -// ============================================================================ -// Post-Process Volume -// ============================================================================ - -bool URalphaParameterBridge::SetPostProcessParameters(const TSharedPtr& Params) -{ - APostProcessVolume* PPV = FindPostProcessVolume(); - if (!PPV) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No PostProcessVolume found in scene")); - return false; - } - - FPostProcessSettings& Settings = PPV->Settings; - - // Exposure - if (Params->HasField(TEXT("exposure_compensation"))) - { - Settings.bOverride_AutoExposureBias = true; - Settings.AutoExposureBias = Params->GetNumberField(TEXT("exposure_compensation")); - } - - // Color Grading - if (Params->HasField(TEXT("color_saturation"))) - { - Settings.bOverride_ColorSaturation = true; - float Sat = Params->GetNumberField(TEXT("color_saturation")); - Settings.ColorSaturation = FVector4(Sat, Sat, Sat, 1.0f); - } - - if (Params->HasField(TEXT("color_contrast"))) - { - Settings.bOverride_ColorContrast = true; - float Contrast = Params->GetNumberField(TEXT("color_contrast")); - Settings.ColorContrast = FVector4(Contrast, Contrast, Contrast, 1.0f); - } - - // White Balance - if (Params->HasField(TEXT("white_balance_temp"))) - { - Settings.bOverride_WhiteTemp = true; - Settings.WhiteTemp = Params->GetNumberField(TEXT("white_balance_temp")); - } - - if (Params->HasField(TEXT("white_balance_tint"))) - { - Settings.bOverride_WhiteTint = true; - Settings.WhiteTint = Params->GetNumberField(TEXT("white_balance_tint")); - } - - // Bloom - if (Params->HasField(TEXT("bloom_intensity"))) - { - Settings.bOverride_BloomIntensity = true; - Settings.BloomIntensity = Params->GetNumberField(TEXT("bloom_intensity")); - } - - if (Params->HasField(TEXT("bloom_threshold"))) - { - Settings.bOverride_BloomThreshold = true; - Settings.BloomThreshold = Params->GetNumberField(TEXT("bloom_threshold")); - } - - // Vignette - if (Params->HasField(TEXT("vignette_intensity"))) - { - Settings.bOverride_VignetteIntensity = true; - Settings.VignetteIntensity = Params->GetNumberField(TEXT("vignette_intensity")); - } - - // Film Grain - if (Params->HasField(TEXT("film_grain_intensity"))) - { - Settings.bOverride_FilmGrainIntensity = true; - Settings.FilmGrainIntensity = Params->GetNumberField(TEXT("film_grain_intensity")); - } - - // Chromatic Aberration - if (Params->HasField(TEXT("chromatic_aberration_intensity"))) - { - Settings.bOverride_SceneFringeIntensity = true; - Settings.SceneFringeIntensity = Params->GetNumberField(TEXT("chromatic_aberration_intensity")); - } - - PPV->MarkPackageDirty(); - return true; -} - -TSharedPtr URalphaParameterBridge::GetPostProcessParameters() -{ - TSharedPtr Result = MakeShared(); - - APostProcessVolume* PPV = FindPostProcessVolume(); - if (!PPV) - { - return Result; - } - - const FPostProcessSettings& Settings = PPV->Settings; - - Result->SetNumberField(TEXT("exposure_compensation"), Settings.AutoExposureBias); - Result->SetNumberField(TEXT("color_saturation"), Settings.ColorSaturation.X); - Result->SetNumberField(TEXT("color_contrast"), Settings.ColorContrast.X); - Result->SetNumberField(TEXT("white_balance_temp"), Settings.WhiteTemp); - Result->SetNumberField(TEXT("white_balance_tint"), Settings.WhiteTint); - Result->SetNumberField(TEXT("bloom_intensity"), Settings.BloomIntensity); - Result->SetNumberField(TEXT("bloom_threshold"), Settings.BloomThreshold); - Result->SetNumberField(TEXT("vignette_intensity"), Settings.VignetteIntensity); - Result->SetNumberField(TEXT("film_grain_intensity"), Settings.FilmGrainIntensity); - Result->SetNumberField(TEXT("chromatic_aberration_intensity"), Settings.SceneFringeIntensity); - - return Result; -} - -// ============================================================================ -// Directional Light -// ============================================================================ - -bool URalphaParameterBridge::SetDirectionalLightParameters(const TSharedPtr& Params) -{ - ADirectionalLight* Light = FindDirectionalLight(); - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No DirectionalLight found in scene")); - return false; - } - - UDirectionalLightComponent* LightComp = Cast(Light->GetLightComponent()); - if (!LightComp) return false; - - if (Params->HasField(TEXT("intensity"))) - { - LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity"))); - } - - if (Params->HasField(TEXT("color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color"))); - LightComp->SetLightColor(Color); - } - - if (Params->HasField(TEXT("temperature"))) - { - LightComp->bUseTemperature = true; - LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature"))); - } - - if (Params->HasField(TEXT("pitch")) || Params->HasField(TEXT("yaw"))) - { - FRotator CurrentRot = Light->GetActorRotation(); - if (Params->HasField(TEXT("pitch"))) - { - CurrentRot.Pitch = Params->GetNumberField(TEXT("pitch")); - } - if (Params->HasField(TEXT("yaw"))) - { - CurrentRot.Yaw = Params->GetNumberField(TEXT("yaw")); - } - Light->SetActorRotation(CurrentRot); - } - - if (Params->HasField(TEXT("source_angle"))) - { - LightComp->LightSourceAngle = Params->GetNumberField(TEXT("source_angle")); - } - - if (Params->HasField(TEXT("source_soft_angle"))) - { - LightComp->LightSourceSoftAngle = Params->GetNumberField(TEXT("source_soft_angle")); - } - - LightComp->MarkRenderStateDirty(); - - // Refresh any BP_Sky_Sphere to update based on new sun position - RefreshSkySphere(); - - return true; -} - -void URalphaParameterBridge::RefreshSkySphere() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return; - - ADirectionalLight* DirectionalLight = FindDirectionalLight(); - - // Find BP_Sky_Sphere and refresh it - for (TActorIterator It(World); It; ++It) - { - FString ClassName = It->GetClass()->GetName(); - if (ClassName.Contains(TEXT("BP_Sky"))) - { - AActor* SkySphere = *It; - - // Ensure directional light is linked - if (DirectionalLight) - { - FProperty* DirLightProp = SkySphere->GetClass()->FindPropertyByName(TEXT("Directional light actor")); - if (!DirLightProp) - { - DirLightProp = SkySphere->GetClass()->FindPropertyByName(TEXT("DirectionalLightActor")); - } - if (DirLightProp) - { - FObjectProperty* ObjProp = CastField(DirLightProp); - if (ObjProp) - { - ObjProp->SetObjectPropertyValue_InContainer(SkySphere, DirectionalLight); - } - } - } - - // Call RefreshMaterial function - UFunction* RefreshFunc = SkySphere->FindFunction(TEXT("RefreshMaterial")); - if (RefreshFunc) - { - SkySphere->ProcessEvent(RefreshFunc, nullptr); - UE_LOG(LogRalphaMCP, Log, TEXT("Refreshed BP_Sky_Sphere material")); - } - break; - } - } -} - -TSharedPtr URalphaParameterBridge::GetDirectionalLightParameters() -{ - TSharedPtr Result = MakeShared(); - - ADirectionalLight* Light = FindDirectionalLight(); - if (!Light) return Result; - - UDirectionalLightComponent* LightComp = Cast(Light->GetLightComponent()); - if (!LightComp) return Result; - - Result->SetNumberField(TEXT("intensity"), LightComp->Intensity); - Result->SetStringField(TEXT("color"), ColorToHex(LightComp->GetLightColor())); - Result->SetNumberField(TEXT("temperature"), LightComp->Temperature); - Result->SetNumberField(TEXT("pitch"), Light->GetActorRotation().Pitch); - Result->SetNumberField(TEXT("yaw"), Light->GetActorRotation().Yaw); - Result->SetNumberField(TEXT("source_angle"), LightComp->LightSourceAngle); - Result->SetNumberField(TEXT("source_soft_angle"), LightComp->LightSourceSoftAngle); - - return Result; -} - -// ============================================================================ -// Sky Light -// ============================================================================ - -bool URalphaParameterBridge::SetSkyLightParameters(const TSharedPtr& Params) -{ - ASkyLight* Light = FindSkyLight(); - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No SkyLight found in scene")); - return false; - } - - USkyLightComponent* LightComp = Light->GetLightComponent(); - if (!LightComp) return false; - - if (Params->HasField(TEXT("intensity"))) - { - LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity"))); - } - - if (Params->HasField(TEXT("color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color"))); - LightComp->SetLightColor(Color); - } - - LightComp->MarkRenderStateDirty(); - return true; -} - -TSharedPtr URalphaParameterBridge::GetSkyLightParameters() -{ - TSharedPtr Result = MakeShared(); - - ASkyLight* Light = FindSkyLight(); - if (!Light) return Result; - - USkyLightComponent* LightComp = Light->GetLightComponent(); - if (!LightComp) return Result; - - Result->SetNumberField(TEXT("intensity"), LightComp->Intensity); - Result->SetStringField(TEXT("color"), ColorToHex(LightComp->GetLightColor())); - - return Result; -} - -// ============================================================================ -// Exponential Height Fog -// ============================================================================ - -bool URalphaParameterBridge::SetFogParameters(const TSharedPtr& Params) -{ - AExponentialHeightFog* Fog = FindExponentialHeightFog(); - if (!Fog) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No ExponentialHeightFog found in scene")); - return false; - } - - UExponentialHeightFogComponent* FogComp = Fog->GetComponent(); - if (!FogComp) return false; - - if (Params->HasField(TEXT("enabled"))) - { - FogComp->SetVisibility(Params->GetBoolField(TEXT("enabled"))); - } - - if (Params->HasField(TEXT("fog_density"))) - { - FogComp->SetFogDensity(Params->GetNumberField(TEXT("fog_density"))); - } - - if (Params->HasField(TEXT("fog_height_falloff"))) - { - FogComp->SetFogHeightFalloff(Params->GetNumberField(TEXT("fog_height_falloff"))); - } - - if (Params->HasField(TEXT("fog_inscattering_color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("fog_inscattering_color"))); - FogComp->SetFogInscatteringColor(Color); - } - - if (Params->HasField(TEXT("fog_max_opacity"))) - { - FogComp->SetFogMaxOpacity(Params->GetNumberField(TEXT("fog_max_opacity"))); - } - - if (Params->HasField(TEXT("start_distance"))) - { - FogComp->SetStartDistance(Params->GetNumberField(TEXT("start_distance"))); - } - - if (Params->HasField(TEXT("volumetric_fog"))) - { - FogComp->SetVolumetricFog(Params->GetBoolField(TEXT("volumetric_fog"))); - } - - if (Params->HasField(TEXT("volumetric_fog_scattering_distribution"))) - { - FogComp->VolumetricFogScatteringDistribution = Params->GetNumberField(TEXT("volumetric_fog_scattering_distribution")); - } - - FogComp->MarkRenderStateDirty(); - return true; -} - -TSharedPtr URalphaParameterBridge::GetFogParameters() -{ - TSharedPtr Result = MakeShared(); - - AExponentialHeightFog* Fog = FindExponentialHeightFog(); - if (!Fog) return Result; - - UExponentialHeightFogComponent* FogComp = Fog->GetComponent(); - if (!FogComp) return Result; - - Result->SetBoolField(TEXT("enabled"), FogComp->IsVisible()); - Result->SetNumberField(TEXT("fog_density"), FogComp->FogDensity); - Result->SetNumberField(TEXT("fog_height_falloff"), FogComp->FogHeightFalloff); - Result->SetNumberField(TEXT("fog_max_opacity"), FogComp->FogMaxOpacity); - Result->SetNumberField(TEXT("start_distance"), FogComp->StartDistance); - Result->SetBoolField(TEXT("volumetric_fog"), FogComp->bEnableVolumetricFog); - Result->SetNumberField(TEXT("volumetric_fog_scattering_distribution"), FogComp->VolumetricFogScatteringDistribution); - - return Result; -} - -// ============================================================================ -// Camera -// ============================================================================ - -bool URalphaParameterBridge::SetCameraParameters(const TSharedPtr& Params) -{ - ACineCameraActor* Camera = FindCineCamera(); - if (!Camera) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No CineCameraActor found in scene")); - return false; - } - - UCineCameraComponent* CameraComp = Camera->GetCineCameraComponent(); - if (!CameraComp) return false; - - // Camera location - const TSharedPtr* LocationObj; - if (Params->TryGetObjectField(TEXT("location"), LocationObj)) - { - FVector Location = Camera->GetActorLocation(); - if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x")); - if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); - if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); - Camera->SetActorLocation(Location); - } - - // Camera rotation - const TSharedPtr* RotationObj; - if (Params->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - FRotator Rotation = Camera->GetActorRotation(); - if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch")); - if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw")); - if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll")); - Camera->SetActorRotation(Rotation); - } - - if (Params->HasField(TEXT("focal_length"))) - { - CameraComp->SetCurrentFocalLength(Params->GetNumberField(TEXT("focal_length"))); - } - - if (Params->HasField(TEXT("aperture"))) - { - CameraComp->CurrentAperture = Params->GetNumberField(TEXT("aperture")); - } - - if (Params->HasField(TEXT("focus_distance"))) - { - CameraComp->FocusSettings.ManualFocusDistance = Params->GetNumberField(TEXT("focus_distance")); - } - - if (Params->HasField(TEXT("dof_enabled"))) - { - CameraComp->PostProcessSettings.bOverride_DepthOfFieldFstop = Params->GetBoolField(TEXT("dof_enabled")); - } - - if (Params->HasField(TEXT("motion_blur_amount"))) - { - CameraComp->PostProcessSettings.bOverride_MotionBlurAmount = true; - CameraComp->PostProcessSettings.MotionBlurAmount = Params->GetNumberField(TEXT("motion_blur_amount")); - } - - CameraComp->MarkRenderStateDirty(); - return true; -} - -TSharedPtr URalphaParameterBridge::GetCameraParameters() -{ - TSharedPtr Result = MakeShared(); - - ACineCameraActor* Camera = FindCineCamera(); - if (!Camera) return Result; - - UCineCameraComponent* CameraComp = Camera->GetCineCameraComponent(); - if (!CameraComp) return Result; - - Result->SetNumberField(TEXT("focal_length"), CameraComp->CurrentFocalLength); - Result->SetNumberField(TEXT("aperture"), CameraComp->CurrentAperture); - Result->SetNumberField(TEXT("focus_distance"), CameraComp->FocusSettings.ManualFocusDistance); - Result->SetNumberField(TEXT("motion_blur_amount"), CameraComp->PostProcessSettings.MotionBlurAmount); - - return Result; -} - -// ============================================================================ -// Render Quality -// ============================================================================ - -bool URalphaParameterBridge::SetRenderQuality(const TSharedPtr& Settings) -{ - // This would typically use console commands or project settings - // For now, we'll log what we would set - - if (Settings->HasField(TEXT("lumen_quality"))) - { - float Quality = Settings->GetNumberField(TEXT("lumen_quality")); - UE_LOG(LogRalphaMCP, Log, TEXT("Would set Lumen quality to: %f"), Quality); - // r.Lumen.ScreenTracing.MaxIterations, etc. - } - - if (Settings->HasField(TEXT("shadow_quality"))) - { - FString Quality = Settings->GetStringField(TEXT("shadow_quality")); - UE_LOG(LogRalphaMCP, Log, TEXT("Would set shadow quality to: %s"), *Quality); - // sg.ShadowQuality - } - - if (Settings->HasField(TEXT("resolution_scale"))) - { - float Scale = Settings->GetNumberField(TEXT("resolution_scale")); - UE_LOG(LogRalphaMCP, Log, TEXT("Would set resolution scale to: %f"), Scale); - // r.ScreenPercentage - } - - return true; -} - -// ============================================================================ -// Asset Search -// ============================================================================ - -TArray> URalphaParameterBridge::SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults) -{ - TArray> Results; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); - - FARFilter Filter; - Filter.bRecursivePaths = true; - Filter.PackagePaths.Add(TEXT("/Game")); - - if (AssetType == TEXT("StaticMesh")) - { - Filter.ClassPaths.Add(UStaticMesh::StaticClass()->GetClassPathName()); - } - // Add more type filters as needed - - TArray AssetList; - AssetRegistry.GetAssets(Filter, AssetList); - - int32 Count = 0; - for (const FAssetData& Asset : AssetList) - { - if (Count >= MaxResults) break; - - FString AssetName = Asset.AssetName.ToString(); - if (AssetName.Contains(Query, ESearchCase::IgnoreCase)) - { - TSharedPtr AssetObj = MakeShared(); - AssetObj->SetStringField(TEXT("name"), AssetName); - AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString()); - AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString()); - - Results.Add(MakeShared(AssetObj)); - Count++; - } - } - - return Results; -} - -TArray> URalphaParameterBridge::ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive) -{ - TArray> Results; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); - - FARFilter Filter; - Filter.bRecursivePaths = bRecursive; - Filter.PackagePaths.Add(*FolderPath); - - TArray AssetList; - AssetRegistry.GetAssets(Filter, AssetList); - - for (const FAssetData& Asset : AssetList) - { - TSharedPtr AssetObj = MakeShared(); - AssetObj->SetStringField(TEXT("name"), Asset.AssetName.ToString()); - AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString()); - AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString()); - - Results.Add(MakeShared(AssetObj)); - } - - return Results; -} - -// ============================================================================ -// Actor Management -// ============================================================================ - -bool URalphaParameterBridge::SpawnActor(const FString& AssetPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Load the asset - UObject* LoadedAsset = StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPath); - UStaticMesh* StaticMesh = Cast(LoadedAsset); - - if (!StaticMesh) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to load static mesh: %s"), *AssetPath); - return false; - } - - // Spawn the actor - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - AStaticMeshActor* NewActor = World->SpawnActor(Location, Rotation, SpawnParams); - if (!NewActor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn actor")); - return false; - } - - NewActor->GetStaticMeshComponent()->SetStaticMesh(StaticMesh); - NewActor->SetActorScale3D(FVector(Scale)); - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - NewActor->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = NewActor->GetName(); - return true; -} - -bool URalphaParameterBridge::DeleteActor(const FString& ActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - World->DestroyActor(*It); - return true; - } - } - - return false; -} - -bool URalphaParameterBridge::SpawnOcean(const FVector& Location, float Size, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - AWaterBodyOcean* Ocean = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); - if (!Ocean) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn WaterBodyOcean")); - return false; - } - -#if WITH_EDITOR - Ocean->SetActorLabel(TEXT("Ralpha_Ocean")); -#endif - - OutActorId = Ocean->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned WaterBodyOcean: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - - return true; -} - -bool URalphaParameterBridge::SetOceanWaves(float WaveAmplitude, float WaveLength, float Steepness, int32 NumWaves) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Find the ocean actor - AWaterBodyOcean* Ocean = nullptr; - for (TActorIterator It(World); It; ++It) - { - Ocean = *It; - break; - } - - if (!Ocean) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetOceanWaves: No WaterBodyOcean found in scene")); - return false; - } - - // Create a new GerstnerWaterWaves with a simple generator - UGerstnerWaterWaves* WaterWaves = NewObject(Ocean); - if (!WaterWaves) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetOceanWaves: Failed to create GerstnerWaterWaves")); - return false; - } - - // Create and configure the wave generator - UGerstnerWaterWaveGeneratorSimple* WaveGenerator = NewObject(WaterWaves); - if (WaveGenerator) - { - WaveGenerator->NumWaves = NumWaves; - WaveGenerator->MinWavelength = WaveLength * 0.5f; - WaveGenerator->MaxWavelength = WaveLength * 2.0f; - WaveGenerator->MinAmplitude = WaveAmplitude * 0.3f; - WaveGenerator->MaxAmplitude = WaveAmplitude; - WaveGenerator->SmallWaveSteepness = Steepness; - WaveGenerator->LargeWaveSteepness = Steepness * 0.5f; - WaveGenerator->WindAngleDeg = -90.0f; // Wind from west - WaveGenerator->DirectionAngularSpreadDeg = 90.0f; - - WaterWaves->GerstnerWaveGenerator = WaveGenerator; - } - - // Recompute waves based on the generator settings - WaterWaves->RecomputeWaves(false); - - // Assign to water body actor - Ocean->SetWaterWaves(WaterWaves); - - UE_LOG(LogRalphaMCP, Log, TEXT("SetOceanWaves: Configured %d waves (Amplitude=%f, WaveLength=%f, Steepness=%f)"), - NumWaves, WaveAmplitude, WaveLength, Steepness); - - return true; -} - -bool URalphaParameterBridge::CreateLandscape(float Size, float HeightOffset) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Check if landscape already exists - for (TActorIterator It(World); It; ++It) - { - UE_LOG(LogRalphaMCP, Log, TEXT("CreateLandscape: Landscape already exists")); - return true; - } - - // Create a minimal landscape - // Note: Creating landscapes programmatically is complex, using simpler approach - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - FVector Location(0.0f, 0.0f, HeightOffset); - ALandscape* Landscape = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); - - if (!Landscape) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("CreateLandscape: Failed to spawn landscape actor")); - return false; - } - -#if WITH_EDITOR - Landscape->SetActorLabel(TEXT("Ralpha_Landscape")); -#endif - - UE_LOG(LogRalphaMCP, Log, TEXT("CreateLandscape: Created landscape at Z=%f"), HeightOffset); - return true; -} - -bool URalphaParameterBridge::SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Find the actor - AActor* TargetActor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - TargetActor = *It; - break; - } - } - - if (!TargetActor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorMaterial: Actor not found: %s"), *ActorId); - return false; - } - - // Load the material - UMaterialInterface* Material = Cast(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialPath)); - if (!Material) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorMaterial: Failed to load material: %s"), *MaterialPath); - return false; - } - - // Try to find a mesh component to apply the material to - AStaticMeshActor* MeshActor = Cast(TargetActor); - if (MeshActor && MeshActor->GetStaticMeshComponent()) - { - MeshActor->GetStaticMeshComponent()->SetMaterial(MaterialIndex, Material); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorMaterial: Applied material %s to %s"), *MaterialPath, *ActorId); - return true; - } - - // Try generic primitive component - UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass(); - if (PrimComp) - { - PrimComp->SetMaterial(MaterialIndex, Material); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorMaterial: Applied material %s to %s (primitive)"), *MaterialPath, *ActorId); - return true; - } - - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorMaterial: No suitable component found on %s"), *ActorId); - return false; -} - -bool URalphaParameterBridge::SetActorSimpleMaterial(const FString& ActorId, const FLinearColor& BaseColor, float Metallic, float Roughness, float Opacity) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Find the actor - AActor* TargetActor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - TargetActor = *It; - break; - } - } - - if (!TargetActor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Actor not found: %s"), *ActorId); - return false; - } - - // Load the basic material from engine content - M_Basic_Wall is a simple material that supports our parameters - UMaterialInterface* BaseMaterial = Cast(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"))); - if (!BaseMaterial) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Failed to load base material")); - return false; - } - - // Create a dynamic material instance - UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, TargetActor); - if (!DynMaterial) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: Failed to create dynamic material")); - return false; - } - - // Set material parameters - BasicShapeMaterial uses specific parameter names - // Try common parameter names for base color - DynMaterial->SetVectorParameterValue(TEXT("Color"), BaseColor); - DynMaterial->SetVectorParameterValue(TEXT("BaseColor"), BaseColor); - DynMaterial->SetVectorParameterValue(TEXT("Base Color"), BaseColor); - - // Try common scalar parameters - DynMaterial->SetScalarParameterValue(TEXT("Metallic"), Metallic); - DynMaterial->SetScalarParameterValue(TEXT("Roughness"), Roughness); - DynMaterial->SetScalarParameterValue(TEXT("Opacity"), Opacity); - - // Apply to the actor's mesh component - AStaticMeshActor* MeshActor = Cast(TargetActor); - if (MeshActor && MeshActor->GetStaticMeshComponent()) - { - MeshActor->GetStaticMeshComponent()->SetMaterial(0, DynMaterial); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorSimpleMaterial: Applied dynamic material to %s (Color: R=%.2f G=%.2f B=%.2f, Metallic=%.2f, Roughness=%.2f)"), - *ActorId, BaseColor.R, BaseColor.G, BaseColor.B, Metallic, Roughness); - return true; - } - - // Try generic primitive component - UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass(); - if (PrimComp) - { - PrimComp->SetMaterial(0, DynMaterial); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorSimpleMaterial: Applied dynamic material to %s (primitive)"), *ActorId); - return true; - } - - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorSimpleMaterial: No suitable component found on %s"), *ActorId); - return false; -} - -bool URalphaParameterBridge::SetActorEmissiveMaterial(const FString& ActorId, const FLinearColor& EmissiveColor, float EmissiveIntensity) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Find the actor - AActor* TargetActor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - TargetActor = *It; - break; - } - } - - if (!TargetActor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Actor not found: %s"), *ActorId); - return false; - } - - // Create a new material from scratch that is unlit/emissive - // We need to use a material that supports emissive - let's try the basic shape material - // and set it to use emissive only - UMaterialInterface* BaseMaterial = Cast(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, TEXT("/Engine/EngineMaterials/DefaultMaterial.DefaultMaterial"))); - if (!BaseMaterial) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Failed to load default material")); - return false; - } - - // Create a dynamic material instance - UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, TargetActor); - if (!DynMaterial) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: Failed to create dynamic material")); - return false; - } - - // Calculate emissive color with intensity - FLinearColor ScaledEmissive = EmissiveColor * EmissiveIntensity; - - // Try to set emissive parameters with common names - DynMaterial->SetVectorParameterValue(TEXT("EmissiveColor"), ScaledEmissive); - DynMaterial->SetVectorParameterValue(TEXT("Emissive"), ScaledEmissive); - DynMaterial->SetVectorParameterValue(TEXT("Emissive Color"), ScaledEmissive); - - // Also try setting base color for materials that blend base with emissive - DynMaterial->SetVectorParameterValue(TEXT("BaseColor"), EmissiveColor); - DynMaterial->SetVectorParameterValue(TEXT("Color"), EmissiveColor); - - // Apply to the actor's mesh component - AStaticMeshActor* MeshActor = Cast(TargetActor); - if (MeshActor && MeshActor->GetStaticMeshComponent()) - { - MeshActor->GetStaticMeshComponent()->SetMaterial(0, DynMaterial); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorEmissiveMaterial: Applied emissive material to %s (Color: R=%.2f G=%.2f B=%.2f, Intensity=%.2f)"), - *ActorId, EmissiveColor.R, EmissiveColor.G, EmissiveColor.B, EmissiveIntensity); - return true; - } - - // Try generic primitive component - UPrimitiveComponent* PrimComp = TargetActor->FindComponentByClass(); - if (PrimComp) - { - PrimComp->SetMaterial(0, DynMaterial); - UE_LOG(LogRalphaMCP, Log, TEXT("SetActorEmissiveMaterial: Applied emissive material to %s (primitive)"), *ActorId); - return true; - } - - UE_LOG(LogRalphaMCP, Warning, TEXT("SetActorEmissiveMaterial: No suitable component found on %s"), *ActorId); - return false; -} - -TArray> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms) -{ - TArray> Results; - - UWorld* World = GetCurrentWorld(); - if (!World) return Results; - - for (TActorIterator It(World); It; ++It) - { - AActor* Actor = *It; - - // Apply filters - if (!ClassFilter.IsEmpty() && !Actor->GetClass()->GetName().Contains(ClassFilter)) - { - continue; - } - -#if WITH_EDITOR - if (!NameFilter.IsEmpty() && !Actor->GetActorLabel().Contains(NameFilter)) - { - continue; - } -#else - if (!NameFilter.IsEmpty() && !Actor->GetName().Contains(NameFilter)) - { - continue; - } -#endif - - TSharedPtr ActorObj = MakeShared(); - ActorObj->SetStringField(TEXT("actor_id"), Actor->GetName()); -#if WITH_EDITOR - ActorObj->SetStringField(TEXT("label"), Actor->GetActorLabel()); -#else - ActorObj->SetStringField(TEXT("label"), Actor->GetName()); -#endif - ActorObj->SetStringField(TEXT("class"), Actor->GetClass()->GetName()); - - if (bIncludeTransforms) - { - FVector Location = Actor->GetActorLocation(); - FRotator Rotation = Actor->GetActorRotation(); - FVector Scale = Actor->GetActorScale3D(); - - TSharedPtr TransformObj = MakeShared(); - - TSharedPtr LocObj = MakeShared(); - LocObj->SetNumberField(TEXT("x"), Location.X); - LocObj->SetNumberField(TEXT("y"), Location.Y); - LocObj->SetNumberField(TEXT("z"), Location.Z); - TransformObj->SetObjectField(TEXT("location"), LocObj); - - TSharedPtr RotObj = MakeShared(); - RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch); - RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw); - RotObj->SetNumberField(TEXT("roll"), Rotation.Roll); - TransformObj->SetObjectField(TEXT("rotation"), RotObj); - - TSharedPtr ScaleObj = MakeShared(); - ScaleObj->SetNumberField(TEXT("x"), Scale.X); - ScaleObj->SetNumberField(TEXT("y"), Scale.Y); - ScaleObj->SetNumberField(TEXT("z"), Scale.Z); - TransformObj->SetObjectField(TEXT("scale"), ScaleObj); - - ActorObj->SetObjectField(TEXT("transform"), TransformObj); - } - - Results.Add(MakeShared(ActorObj)); - } - - return Results; -} - -bool URalphaParameterBridge::SetActorTransform(const FString& ActorId, const FVector* Location, const FRotator* Rotation, const float* Scale, bool bRelative) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - AActor* Actor = *It; - - if (Location) - { - if (bRelative) - { - Actor->SetActorLocation(Actor->GetActorLocation() + *Location); - } - else - { - Actor->SetActorLocation(*Location); - } - } - - if (Rotation) - { - if (bRelative) - { - Actor->SetActorRotation(Actor->GetActorRotation() + *Rotation); - } - else - { - Actor->SetActorRotation(*Rotation); - } - } - - if (Scale) - { - Actor->SetActorScale3D(FVector(*Scale)); - } - - return true; - } - } - - return false; -} - -TSharedPtr URalphaParameterBridge::GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials) -{ - TSharedPtr Result = MakeShared(); - - UWorld* World = GetCurrentWorld(); - if (!World) return Result; - - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - AActor* Actor = *It; - - Result->SetStringField(TEXT("actor_id"), Actor->GetName()); -#if WITH_EDITOR - Result->SetStringField(TEXT("label"), Actor->GetActorLabel()); -#else - Result->SetStringField(TEXT("label"), Actor->GetName()); -#endif - Result->SetStringField(TEXT("class"), Actor->GetClass()->GetName()); - - // Transform - FVector Location = Actor->GetActorLocation(); - FRotator Rotation = Actor->GetActorRotation(); - FVector Scale = Actor->GetActorScale3D(); - - TSharedPtr TransformObj = MakeShared(); - TSharedPtr LocObj = MakeShared(); - LocObj->SetNumberField(TEXT("x"), Location.X); - LocObj->SetNumberField(TEXT("y"), Location.Y); - LocObj->SetNumberField(TEXT("z"), Location.Z); - TransformObj->SetObjectField(TEXT("location"), LocObj); - - TSharedPtr RotObj = MakeShared(); - RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch); - RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw); - RotObj->SetNumberField(TEXT("roll"), Rotation.Roll); - TransformObj->SetObjectField(TEXT("rotation"), RotObj); - - Result->SetObjectField(TEXT("transform"), TransformObj); - - if (bIncludeComponents) - { - TArray> ComponentsArray; - TArray Components; - Actor->GetComponents(Components); - - for (UActorComponent* Component : Components) - { - TSharedPtr CompObj = MakeShared(); - CompObj->SetStringField(TEXT("name"), Component->GetName()); - CompObj->SetStringField(TEXT("class"), Component->GetClass()->GetName()); - ComponentsArray.Add(MakeShared(CompObj)); - } - - Result->SetArrayField(TEXT("components"), ComponentsArray); - } - - return Result; - } - } - - return Result; -} - -// ============================================================================ -// Scene Setup -// ============================================================================ - -TSharedPtr URalphaParameterBridge::SetupScene() -{ - TSharedPtr Result = MakeShared(); - TArray CreatedActors; - TArray ExistingActors; - - UWorld* World = GetCurrentWorld(); - if (!World) - { - Result->SetBoolField(TEXT("success"), false); - Result->SetStringField(TEXT("error"), TEXT("No world available")); - return Result; - } - - // Check/Create PostProcessVolume - if (!FindPostProcessVolume()) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - APostProcessVolume* PPV = World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (PPV) - { - PPV->bUnbound = true; - PPV->Settings.bOverride_AutoExposureBias = true; - PPV->Settings.AutoExposureBias = 0.0f; -#if WITH_EDITOR - PPV->SetActorLabel(TEXT("Ralpha_PostProcess")); -#endif - CreatedActors.Add(TEXT("PostProcessVolume")); - } - } - else - { - ExistingActors.Add(TEXT("PostProcessVolume")); - } - - // Check/Create DirectionalLight - ADirectionalLight* Sun = FindDirectionalLight(); - if (!Sun) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - Sun = World->SpawnActor(FVector::ZeroVector, FRotator(-45.0f, 0.0f, 0.0f), SpawnParams); - if (Sun) - { -#if WITH_EDITOR - Sun->SetActorLabel(TEXT("Ralpha_Sun")); -#endif - CreatedActors.Add(TEXT("DirectionalLight")); - } - } - else - { - ExistingActors.Add(TEXT("DirectionalLight")); - } - - // Configure DirectionalLight as atmosphere sun for visible sun disc - if (Sun) - { - UDirectionalLightComponent* LightComp = Cast(Sun->GetLightComponent()); - if (LightComp) - { - LightComp->SetAtmosphereSunLight(true); - LightComp->SetAtmosphereSunLightIndex(0); - } - } - - // Check/Create SkyLight - if (!FindSkyLight()) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - ASkyLight* Light = World->SpawnActor(FVector(0.0f, 0.0f, 1000.0f), FRotator::ZeroRotator, SpawnParams); - if (Light) - { -#if WITH_EDITOR - Light->SetActorLabel(TEXT("Ralpha_SkyLight")); -#endif - CreatedActors.Add(TEXT("SkyLight")); - } - } - else - { - ExistingActors.Add(TEXT("SkyLight")); - } - - // Check/Create ExponentialHeightFog - if (!FindExponentialHeightFog()) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - AExponentialHeightFog* Fog = World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (Fog) - { -#if WITH_EDITOR - Fog->SetActorLabel(TEXT("Ralpha_Fog")); -#endif - CreatedActors.Add(TEXT("ExponentialHeightFog")); - } - } - else - { - ExistingActors.Add(TEXT("ExponentialHeightFog")); - } - - // Check/Create Sky Atmosphere (physically-based sky) - bool bHasSkyAtmosphere = false; - for (TActorIterator It(World); It; ++It) - { - bHasSkyAtmosphere = true; - break; - } - if (!bHasSkyAtmosphere) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - ASkyAtmosphere* SkyAtmo = World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (SkyAtmo) - { -#if WITH_EDITOR - SkyAtmo->SetActorLabel(TEXT("Ralpha_SkyAtmosphere")); -#endif - CreatedActors.Add(TEXT("SkyAtmosphere")); - } - } - else - { - ExistingActors.Add(TEXT("SkyAtmosphere")); - } - - // Check/Create Volumetric Cloud - bool bHasVolumetricCloud = false; - for (TActorIterator It(World); It; ++It) - { - bHasVolumetricCloud = true; - break; - } - if (!bHasVolumetricCloud) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - AVolumetricCloud* Cloud = World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (Cloud) - { -#if WITH_EDITOR - Cloud->SetActorLabel(TEXT("Ralpha_VolumetricCloud")); -#endif - CreatedActors.Add(TEXT("VolumetricCloud")); - } - } - else - { - ExistingActors.Add(TEXT("VolumetricCloud")); - } - - // Check/Create CineCameraActor - if (!FindCineCamera()) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - ACineCameraActor* Camera = World->SpawnActor(FVector(0.0f, -500.0f, 200.0f), FRotator(0.0f, 90.0f, 0.0f), SpawnParams); - if (Camera) - { -#if WITH_EDITOR - Camera->SetActorLabel(TEXT("Ralpha_Camera")); -#endif - CreatedActors.Add(TEXT("CineCameraActor")); - } - } - else - { - ExistingActors.Add(TEXT("CineCameraActor")); - } - - Result->SetBoolField(TEXT("success"), true); - - TArray> CreatedArray; - for (const FString& Actor : CreatedActors) - { - CreatedArray.Add(MakeShared(Actor)); - } - Result->SetArrayField(TEXT("created"), CreatedArray); - - TArray> ExistingArray; - for (const FString& Actor : ExistingActors) - { - ExistingArray.Add(MakeShared(Actor)); - } - Result->SetArrayField(TEXT("existing"), ExistingArray); - - return Result; -} - -// ============================================================================ -// Point Light -// ============================================================================ - -bool URalphaParameterBridge::SpawnPointLight(const FVector& Location, float Intensity, const FLinearColor& Color, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - APointLight* Light = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn PointLight")); - return false; - } - - UPointLightComponent* LightComp = Light->PointLightComponent; - if (LightComp) - { - LightComp->SetIntensity(Intensity); - LightComp->SetLightColor(Color); - LightComp->SetAttenuationRadius(AttenuationRadius); - } - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Light->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = Light->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned PointLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::SetPointLightParameters(const FString& ActorId, const TSharedPtr& Params) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - APointLight* Light = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - Light = *It; - break; - } - } - - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetPointLightParameters: PointLight not found: %s"), *ActorId); - return false; - } - - UPointLightComponent* LightComp = Light->PointLightComponent; - if (!LightComp) return false; - - if (Params->HasField(TEXT("intensity"))) - { - LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity"))); - } - - if (Params->HasField(TEXT("color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color"))); - LightComp->SetLightColor(Color); - } - - if (Params->HasField(TEXT("attenuation_radius"))) - { - LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius"))); - } - - if (Params->HasField(TEXT("source_radius"))) - { - LightComp->SourceRadius = Params->GetNumberField(TEXT("source_radius")); - } - - if (Params->HasField(TEXT("soft_source_radius"))) - { - LightComp->SoftSourceRadius = Params->GetNumberField(TEXT("soft_source_radius")); - } - - if (Params->HasField(TEXT("temperature"))) - { - LightComp->bUseTemperature = true; - LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature"))); - } - - // Location update - const TSharedPtr* LocationObj; - if (Params->TryGetObjectField(TEXT("location"), LocationObj)) - { - FVector Location = Light->GetActorLocation(); - if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x")); - if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); - if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); - Light->SetActorLocation(Location); - } - - LightComp->MarkRenderStateDirty(); - return true; -} - -// ============================================================================ -// Spot Light -// ============================================================================ - -bool URalphaParameterBridge::SpawnSpotLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float AttenuationRadius, float InnerConeAngle, float OuterConeAngle, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - ASpotLight* Light = World->SpawnActor(Location, Rotation, SpawnParams); - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SpotLight")); - return false; - } - - USpotLightComponent* LightComp = Cast(Light->GetLightComponent()); - if (LightComp) - { - LightComp->SetIntensity(Intensity); - LightComp->SetLightColor(Color); - LightComp->SetAttenuationRadius(AttenuationRadius); - LightComp->SetInnerConeAngle(InnerConeAngle); - LightComp->SetOuterConeAngle(OuterConeAngle); - } - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Light->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = Light->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SpotLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::SetSpotLightParameters(const FString& ActorId, const TSharedPtr& Params) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - ASpotLight* Light = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - Light = *It; - break; - } - } - - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetSpotLightParameters: SpotLight not found: %s"), *ActorId); - return false; - } - - USpotLightComponent* LightComp = Cast(Light->GetLightComponent()); - if (!LightComp) return false; - - if (Params->HasField(TEXT("intensity"))) - { - LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity"))); - } - - if (Params->HasField(TEXT("color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color"))); - LightComp->SetLightColor(Color); - } - - if (Params->HasField(TEXT("attenuation_radius"))) - { - LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius"))); - } - - if (Params->HasField(TEXT("inner_cone_angle"))) - { - LightComp->SetInnerConeAngle(Params->GetNumberField(TEXT("inner_cone_angle"))); - } - - if (Params->HasField(TEXT("outer_cone_angle"))) - { - LightComp->SetOuterConeAngle(Params->GetNumberField(TEXT("outer_cone_angle"))); - } - - if (Params->HasField(TEXT("source_radius"))) - { - LightComp->SourceRadius = Params->GetNumberField(TEXT("source_radius")); - } - - if (Params->HasField(TEXT("temperature"))) - { - LightComp->bUseTemperature = true; - LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature"))); - } - - // Location update - const TSharedPtr* LocationObj; - if (Params->TryGetObjectField(TEXT("location"), LocationObj)) - { - FVector Location = Light->GetActorLocation(); - if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x")); - if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); - if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); - Light->SetActorLocation(Location); - } - - // Rotation update - const TSharedPtr* RotationObj; - if (Params->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - FRotator Rotation = Light->GetActorRotation(); - if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch")); - if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw")); - if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll")); - Light->SetActorRotation(Rotation); - } - - LightComp->MarkRenderStateDirty(); - return true; -} - -// ============================================================================ -// Rect Light -// ============================================================================ - -bool URalphaParameterBridge::SpawnRectLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float SourceWidth, float SourceHeight, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - ARectLight* Light = World->SpawnActor(Location, Rotation, SpawnParams); - if (!Light) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn RectLight")); - return false; - } - - URectLightComponent* LightComp = Cast(Light->GetLightComponent()); - if (LightComp) - { - LightComp->SetIntensity(Intensity); - LightComp->SetLightColor(Color); - LightComp->SetSourceWidth(SourceWidth); - LightComp->SetSourceHeight(SourceHeight); - LightComp->SetAttenuationRadius(AttenuationRadius); - } - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Light->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = Light->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned RectLight: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::SetRectLightParameters(const FString& ActorId, const TSharedPtr& Params) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - ARectLight* RectLight = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - RectLight = *It; - break; - } - } - - if (!RectLight) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetRectLightParameters: RectLight not found: %s"), *ActorId); - return false; - } - - URectLightComponent* LightComp = Cast(RectLight->GetLightComponent()); - if (!LightComp) return false; - - if (Params->HasField(TEXT("intensity"))) - { - LightComp->SetIntensity(Params->GetNumberField(TEXT("intensity"))); - } - - if (Params->HasField(TEXT("color"))) - { - FLinearColor Color = ParseHexColor(Params->GetStringField(TEXT("color"))); - LightComp->SetLightColor(Color); - } - - if (Params->HasField(TEXT("source_width"))) - { - LightComp->SetSourceWidth(Params->GetNumberField(TEXT("source_width"))); - } - - if (Params->HasField(TEXT("source_height"))) - { - LightComp->SetSourceHeight(Params->GetNumberField(TEXT("source_height"))); - } - - if (Params->HasField(TEXT("attenuation_radius"))) - { - LightComp->SetAttenuationRadius(Params->GetNumberField(TEXT("attenuation_radius"))); - } - - if (Params->HasField(TEXT("barn_door_angle"))) - { - LightComp->BarnDoorAngle = Params->GetNumberField(TEXT("barn_door_angle")); - } - - if (Params->HasField(TEXT("barn_door_length"))) - { - LightComp->BarnDoorLength = Params->GetNumberField(TEXT("barn_door_length")); - } - - if (Params->HasField(TEXT("temperature"))) - { - LightComp->bUseTemperature = true; - LightComp->SetTemperature(Params->GetNumberField(TEXT("temperature"))); - } - - // Location update - const TSharedPtr* LocationObj; - if (Params->TryGetObjectField(TEXT("location"), LocationObj)) - { - FVector Location = RectLight->GetActorLocation(); - if ((*LocationObj)->HasField(TEXT("x"))) Location.X = (*LocationObj)->GetNumberField(TEXT("x")); - if ((*LocationObj)->HasField(TEXT("y"))) Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); - if ((*LocationObj)->HasField(TEXT("z"))) Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); - RectLight->SetActorLocation(Location); - } - - // Rotation update - const TSharedPtr* RotationObj; - if (Params->TryGetObjectField(TEXT("rotation"), RotationObj)) - { - FRotator Rotation = RectLight->GetActorRotation(); - if ((*RotationObj)->HasField(TEXT("pitch"))) Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch")); - if ((*RotationObj)->HasField(TEXT("yaw"))) Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw")); - if ((*RotationObj)->HasField(TEXT("roll"))) Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll")); - RectLight->SetActorRotation(Rotation); - } - - LightComp->MarkRenderStateDirty(); - return true; -} - -// ============================================================================ -// Skeletal Mesh -// ============================================================================ - -bool URalphaParameterBridge::SpawnSkeletalMesh(const FString& SkeletalMeshPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - // Load the skeletal mesh - USkeletalMesh* SkeletalMesh = Cast(StaticLoadObject(USkeletalMesh::StaticClass(), nullptr, *SkeletalMeshPath)); - if (!SkeletalMesh) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to load skeletal mesh: %s"), *SkeletalMeshPath); - return false; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - ASkeletalMeshActor* Actor = World->SpawnActor(Location, Rotation, SpawnParams); - if (!Actor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SkeletalMeshActor")); - return false; - } - - USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent(); - if (MeshComp) - { - MeshComp->SetSkeletalMesh(SkeletalMesh); - MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode); - } - - Actor->SetActorScale3D(FVector(Scale)); - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Actor->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = Actor->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SkeletalMeshActor: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::PlayAnimation(const FString& ActorId, const FString& AnimationPath, bool bLooping, float PlayRate) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - ASkeletalMeshActor* Actor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - Actor = *It; - break; - } - } - - if (!Actor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("PlayAnimation: SkeletalMeshActor not found: %s"), *ActorId); - return false; - } - - UAnimSequence* AnimSequence = Cast(StaticLoadObject(UAnimSequence::StaticClass(), nullptr, *AnimationPath)); - if (!AnimSequence) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("PlayAnimation: Failed to load animation: %s"), *AnimationPath); - return false; - } - - USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent(); - if (MeshComp) - { - MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode); - MeshComp->PlayAnimation(AnimSequence, bLooping); - MeshComp->SetPlayRate(PlayRate); - UE_LOG(LogRalphaMCP, Log, TEXT("PlayAnimation: Playing %s on %s (loop=%d, rate=%f)"), *AnimationPath, *ActorId, bLooping, PlayRate); - return true; - } - - return false; -} - -bool URalphaParameterBridge::StopAnimation(const FString& ActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - ASkeletalMeshActor* Actor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - Actor = *It; - break; - } - } - - if (!Actor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("StopAnimation: SkeletalMeshActor not found: %s"), *ActorId); - return false; - } - - USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent(); - if (MeshComp) - { - MeshComp->Stop(); - return true; - } - - return false; -} - -bool URalphaParameterBridge::SetAnimationPose(const FString& ActorId, const FString& AnimationPath, float Time) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - ASkeletalMeshActor* Actor = nullptr; - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - Actor = *It; - break; - } - } - - if (!Actor) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetAnimationPose: SkeletalMeshActor not found: %s"), *ActorId); - return false; - } - - UAnimSequence* AnimSequence = Cast(StaticLoadObject(UAnimSequence::StaticClass(), nullptr, *AnimationPath)); - if (!AnimSequence) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetAnimationPose: Failed to load animation: %s"), *AnimationPath); - return false; - } - - USkeletalMeshComponent* MeshComp = Actor->GetSkeletalMeshComponent(); - if (MeshComp) - { - MeshComp->SetAnimationMode(EAnimationMode::AnimationSingleNode); - MeshComp->SetAnimation(AnimSequence); - MeshComp->SetPosition(Time); - MeshComp->SetPlayRate(0.0f); - UE_LOG(LogRalphaMCP, Log, TEXT("SetAnimationPose: Set %s to pose at time %f"), *ActorId, Time); - return true; - } - - return false; -} - -// ============================================================================ -// Reflection Captures -// ============================================================================ - -bool URalphaParameterBridge::SpawnSphereReflectionCapture(const FVector& Location, float InfluenceRadius, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - ASphereReflectionCapture* Capture = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); - if (!Capture) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn SphereReflectionCapture")); - return false; - } - - USphereReflectionCaptureComponent* CaptureComp = Cast(Capture->GetCaptureComponent()); - if (CaptureComp) - { - CaptureComp->InfluenceRadius = InfluenceRadius; - } - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Capture->SetActorLabel(ActorLabel); - } -#endif - - OutActorId = Capture->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned SphereReflectionCapture: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::SpawnBoxReflectionCapture(const FVector& Location, const FVector& BoxExtent, const FString& ActorLabel, FString& OutActorId) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - ABoxReflectionCapture* Capture = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); - if (!Capture) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to spawn BoxReflectionCapture")); - return false; - } - - UBoxReflectionCaptureComponent* CaptureComp = Cast(Capture->GetCaptureComponent()); - if (CaptureComp) - { - CaptureComp->BoxTransitionDistance = BoxExtent.X; - } - -#if WITH_EDITOR - if (!ActorLabel.IsEmpty()) - { - Capture->SetActorLabel(ActorLabel); - } -#endif - - Capture->SetActorScale3D(BoxExtent / 100.0f); - - OutActorId = Capture->GetName(); - UE_LOG(LogRalphaMCP, Log, TEXT("Spawned BoxReflectionCapture: %s at (%f, %f, %f)"), *OutActorId, Location.X, Location.Y, Location.Z); - return true; -} - -bool URalphaParameterBridge::UpdateReflectionCaptures() -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - -#if WITH_EDITOR - for (TActorIterator It(World); It; ++It) - { - UReflectionCaptureComponent* CaptureComp = It->GetCaptureComponent(); - if (CaptureComp) - { - CaptureComp->MarkDirtyForRecapture(); - } - } - - GEditor->BuildReflectionCaptures(); - UE_LOG(LogRalphaMCP, Log, TEXT("UpdateReflectionCaptures: Triggered rebuild")); - return true; -#else - UE_LOG(LogRalphaMCP, Warning, TEXT("UpdateReflectionCaptures: Only available in editor")); - return false; -#endif -} - -// ============================================================================ -// IES Light Profiles -// ============================================================================ - -bool URalphaParameterBridge::SetLightIESProfile(const FString& ActorId, const FString& IESProfilePath) -{ - UWorld* World = GetCurrentWorld(); - if (!World) return false; - - UTextureLightProfile* IESProfile = nullptr; - if (!IESProfilePath.IsEmpty()) - { - IESProfile = Cast(StaticLoadObject(UTextureLightProfile::StaticClass(), nullptr, *IESProfilePath)); - if (!IESProfile) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("SetLightIESProfile: Failed to load IES profile: %s"), *IESProfilePath); - return false; - } - } - - for (TActorIterator It(World); It; ++It) - { - if (It->GetName() == ActorId) - { - ULightComponent* LightComp = It->GetLightComponent(); - if (LightComp) - { - LightComp->IESTexture = IESProfile; - LightComp->bUseIESBrightness = (IESProfile != nullptr); - LightComp->MarkRenderStateDirty(); - UE_LOG(LogRalphaMCP, Log, TEXT("SetLightIESProfile: Applied IES profile to %s"), *ActorId); - return true; - } - } - } - - UE_LOG(LogRalphaMCP, Warning, TEXT("SetLightIESProfile: Light not found: %s"), *ActorId); - return false; -} diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaScreenCapture.cpp b/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaScreenCapture.cpp deleted file mode 100644 index 2335f1be..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Private/RalphaScreenCapture.cpp +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaScreenCapture.h" -#include "RalphaMCPServer.h" -#include "Modules/ModuleManager.h" -#include "Engine/GameViewportClient.h" -#include "Engine/Engine.h" -#include "Slate/SceneViewport.h" -#include "ImageUtils.h" -#include "IImageWrapper.h" -#include "IImageWrapperModule.h" -#include "Misc/Base64.h" -#include "Misc/FileHelper.h" -#include "Misc/Paths.h" -#include "HAL/PlatformFileManager.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/SWindow.h" -#include "UnrealClient.h" -#include "HighResScreenshot.h" -#include "Components/SceneCaptureComponent2D.h" -#include "Engine/TextureRenderTarget2D.h" -#include "TextureResource.h" -#include "RenderingThread.h" -#include "CineCameraActor.h" -#include "CineCameraComponent.h" -#include "Kismet/GameplayStatics.h" -#include "EngineUtils.h" - -#if WITH_EDITOR -#include "Editor.h" -#include "LevelEditorViewport.h" -#include "SLevelViewport.h" -#include "LevelEditor.h" -#include "ILevelEditor.h" -#endif - -URalphaScreenCapture::URalphaScreenCapture() -{ -} - -bool URalphaScreenCapture::CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath) -{ - FViewport* Viewport = nullptr; - int32 ViewportWidth = 0; - int32 ViewportHeight = 0; - -#if WITH_EDITOR - // In editor, find the active level viewport with valid size - if (GEditor) - { - // Try to find a viewport with a valid size - for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients()) - { - if (LevelVC && LevelVC->Viewport) - { - FIntPoint Size = LevelVC->Viewport->GetSizeXY(); - if (Size.X > 0 && Size.Y > 0) - { - Viewport = LevelVC->Viewport; - ViewportWidth = Size.X; - ViewportHeight = Size.Y; - UE_LOG(LogRalphaMCP, Log, TEXT("Found editor viewport: %dx%d"), ViewportWidth, ViewportHeight); - break; - } - } - } - - // If still no viewport, try to get it from the level editor module - if (!Viewport) - { - FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); - TSharedPtr LevelEditor = LevelEditorModule.GetFirstLevelEditor(); - if (LevelEditor.IsValid()) - { - TSharedPtr ActiveViewport = LevelEditor->GetActiveViewportInterface(); - if (ActiveViewport.IsValid()) - { - FLevelEditorViewportClient& ViewportClient = ActiveViewport->GetLevelViewportClient(); - if (ViewportClient.Viewport) - { - FIntPoint Size = ViewportClient.Viewport->GetSizeXY(); - if (Size.X > 0 && Size.Y > 0) - { - Viewport = ViewportClient.Viewport; - ViewportWidth = Size.X; - ViewportHeight = Size.Y; - UE_LOG(LogRalphaMCP, Log, TEXT("Found active viewport: %dx%d"), ViewportWidth, ViewportHeight); - } - } - } - } - } - } -#endif - - // Fallback to game viewport - if (!Viewport) - { - UGameViewportClient* ViewportClient = GEngine ? GEngine->GameViewport : nullptr; - if (ViewportClient && ViewportClient->Viewport) - { - FIntPoint Size = ViewportClient->Viewport->GetSizeXY(); - if (Size.X > 0 && Size.Y > 0) - { - Viewport = ViewportClient->Viewport; - ViewportWidth = Size.X; - ViewportHeight = Size.Y; - UE_LOG(LogRalphaMCP, Log, TEXT("Using game viewport: %dx%d"), ViewportWidth, ViewportHeight); - } - } - } - - if (!Viewport || ViewportWidth <= 0 || ViewportHeight <= 0) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No valid viewport available for screenshot")); - return false; - } - - // Read pixels from the viewport - TArray Bitmap; - bool bSuccess = Viewport->ReadPixels(Bitmap); - - if (!bSuccess || Bitmap.Num() == 0) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to read pixels from viewport")); - return false; - } - - // Convert to base64 PNG - OutBase64 = PixelsToBase64PNG(Bitmap, ViewportWidth, ViewportHeight); - - // Save to disk as well - FString ScreenshotDir = GetScreenshotDirectory(); - FString Filename = FString::Printf(TEXT("Ralpha_%s.png"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"))); - OutFilePath = ScreenshotDir / Filename; - - // Save the PNG - IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), ViewportWidth, ViewportHeight, ERGBFormat::BGRA, 8)) - { - TArray64 PNGData = ImageWrapper->GetCompressed(0); - if (PNGData.Num() > 0) - { - FFileHelper::SaveArrayToFile(PNGData, *OutFilePath); - } - } - - UE_LOG(LogRalphaMCP, Log, TEXT("Screenshot captured: %dx%d, saved to %s"), ViewportWidth, ViewportHeight, *OutFilePath); - - return true; -} - -bool URalphaScreenCapture::CaptureHighResScreenshot(int32 Width, int32 Height, FString& OutBase64) -{ - // For high-res screenshots, we would use FHighResScreenshotConfig - // This is a simplified implementation - - FString FilePath; - return CaptureScreenshot(Width, Height, OutBase64, FilePath); -} - -FString URalphaScreenCapture::PixelsToBase64PNG(const TArray& Pixels, int32 Width, int32 Height) -{ - // Load the image wrapper module - IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - if (!ImageWrapper.IsValid()) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to create image wrapper")); - return FString(); - } - - // Set the raw pixel data - if (!ImageWrapper->SetRaw(Pixels.GetData(), Pixels.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8)) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to set raw pixel data")); - return FString(); - } - - // Compress to PNG - TArray64 PNGData = ImageWrapper->GetCompressed(0); - if (PNGData.Num() == 0) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to compress to PNG")); - return FString(); - } - - // Convert to base64 - TArray PNGData32; - PNGData32.Append(PNGData.GetData(), PNGData.Num()); - - return FBase64::Encode(PNGData32); -} - -bool URalphaScreenCapture::CaptureFromCamera(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath) -{ - // Get the world - UWorld* World = nullptr; - if (GEngine && GEngine->GetWorldContexts().Num() > 0) - { - World = GEngine->GetWorldContexts()[0].World(); - } - - if (!World) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No world available for camera capture")); - return false; - } - - // Find the CineCamera - ACineCameraActor* CineCamera = nullptr; - for (TActorIterator It(World); It; ++It) - { - CineCamera = *It; - break; - } - - if (!CineCamera) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("No CineCameraActor found for capture")); - return false; - } - - // Use reasonable defaults if width/height not specified - if (Width <= 0) Width = 1920; - if (Height <= 0) Height = 1080; - - // Create render target - UTextureRenderTarget2D* RenderTarget = NewObject(); - RenderTarget->InitAutoFormat(Width, Height); - RenderTarget->UpdateResourceImmediate(true); - - // Create scene capture component - USceneCaptureComponent2D* SceneCapture = NewObject(CineCamera); - SceneCapture->RegisterComponent(); - SceneCapture->SetWorldLocation(CineCamera->GetActorLocation()); - SceneCapture->SetWorldRotation(CineCamera->GetActorRotation()); - - // Copy camera settings - UCineCameraComponent* CameraComp = CineCamera->GetCineCameraComponent(); - if (CameraComp) - { - SceneCapture->FOVAngle = CameraComp->FieldOfView; - } - - // Configure capture settings - SceneCapture->TextureTarget = RenderTarget; - SceneCapture->CaptureSource = SCS_FinalToneCurveHDR; - SceneCapture->bCaptureEveryFrame = false; - SceneCapture->bCaptureOnMovement = false; - SceneCapture->bAlwaysPersistRenderingState = true; - - // Enable atmosphere and sky rendering in capture - SceneCapture->ShowFlags.SetAtmosphere(true); - SceneCapture->ShowFlags.SetFog(true); - SceneCapture->ShowFlags.SetVolumetricFog(true); - SceneCapture->ShowFlags.SetCloud(true); - SceneCapture->ShowFlags.SetLighting(true); - SceneCapture->ShowFlags.SetPostProcessing(true); - - // Capture the scene - SceneCapture->CaptureScene(); - - // Wait for rendering to complete - FlushRenderingCommands(); - - // Read pixels from render target - TArray Bitmap; - FTextureRenderTargetResource* RTResource = RenderTarget->GameThread_GetRenderTargetResource(); - if (RTResource) - { - FReadSurfaceDataFlags ReadFlags(RCM_UNorm); - RTResource->ReadPixels(Bitmap, ReadFlags); - } - - // Clean up - SceneCapture->UnregisterComponent(); - SceneCapture->DestroyComponent(); - - if (Bitmap.Num() == 0) - { - UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to read pixels from render target")); - return false; - } - - // Convert to base64 PNG - OutBase64 = PixelsToBase64PNG(Bitmap, Width, Height); - - // Save to disk - FString ScreenshotDir = GetScreenshotDirectory(); - FString Filename = FString::Printf(TEXT("Ralpha_%s.png"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"))); - OutFilePath = ScreenshotDir / Filename; - - IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8)) - { - TArray64 PNGData = ImageWrapper->GetCompressed(0); - if (PNGData.Num() > 0) - { - FFileHelper::SaveArrayToFile(PNGData, *OutFilePath); - } - } - - UE_LOG(LogRalphaMCP, Log, TEXT("Camera capture: %dx%d, saved to %s"), Width, Height, *OutFilePath); - - return true; -} - -FString URalphaScreenCapture::GetScreenshotDirectory() -{ - FString ScreenshotDir = FPaths::ProjectSavedDir() / TEXT("Screenshots") / TEXT("Ralpha"); - - // Create directory if it doesn't exist - IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - if (!PlatformFile.DirectoryExists(*ScreenshotDir)) - { - PlatformFile.CreateDirectoryTree(*ScreenshotDir); - } - - return ScreenshotDir; -} diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaCore.h b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaCore.h deleted file mode 100644 index 1c761116..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaCore.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -class RALPHACORE_API FRalphaCoreModule : public IModuleInterface -{ -public: - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - /** Get the module instance */ - static FRalphaCoreModule& Get(); - - /** Check if module is loaded */ - static bool IsAvailable(); -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaMCPServer.h b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaMCPServer.h deleted file mode 100644 index a0a76f2c..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaMCPServer.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Sockets.h" -#include "SocketSubsystem.h" -#include "Containers/Ticker.h" -#include "RalphaMCPServer.generated.h" - -class FRalphaClientConnection; -class URalphaParameterBridge; -class URalphaScreenCapture; - -DECLARE_LOG_CATEGORY_EXTERN(LogRalphaMCP, Log, All); - -/** - * TCP Server that receives JSON-RPC commands from the Python MCP server - * and executes them against UE5 systems. - */ -UCLASS(BlueprintType) -class RALPHACORE_API URalphaMCPServer : public UObject -{ - GENERATED_BODY() - -public: - URalphaMCPServer(); - virtual ~URalphaMCPServer(); - - /** Get the singleton instance */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - static URalphaMCPServer* Get(); - - /** Start the TCP server */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - bool Start(int32 Port = 30010); - - /** Stop the TCP server */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - void Stop(); - - /** Check if server is running */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - bool IsRunning() const; - - /** Get the port number */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - int32 GetPort() const { return ServerPort; } - - /** Process a JSON command and return response */ - FString ProcessCommand(const FString& JsonCommand); - -protected: - /** Tick function to accept new connections */ - bool Tick(float DeltaTime); - -private: - static URalphaMCPServer* Instance; - - FSocket* ListenerSocket; - int32 ServerPort; - bool bIsRunning; - - TArray> ClientConnections; - - UPROPERTY() - URalphaParameterBridge* ParameterBridge; - - UPROPERTY() - URalphaScreenCapture* ScreenCapture; - - FTSTicker::FDelegateHandle TickDelegateHandle; -}; - -/** - * Handles a single client connection - */ -class RALPHACORE_API FRalphaClientConnection : public TSharedFromThis -{ -public: - FRalphaClientConnection(FSocket* InSocket, URalphaMCPServer* InServer); - ~FRalphaClientConnection(); - - /** Process incoming data */ - void Tick(); - - /** Check if connection is valid */ - bool IsValid() const; - - /** Send response to client */ - void SendResponse(const FString& Response); - -private: - FSocket* Socket; - URalphaMCPServer* Server; - FString ReceiveBuffer; -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h deleted file mode 100644 index a6da1381..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaParameterBridge.h +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Dom/JsonObject.h" -#include "Dom/JsonValue.h" -#include "RalphaParameterBridge.generated.h" - -class APostProcessVolume; -class ADirectionalLight; -class ASkyLight; -class AExponentialHeightFog; -class ACineCameraActor; - -/** - * Bridge between MCP commands and UE5 rendering systems. - * Handles getting/setting parameters on post-process volumes, lights, fog, cameras, etc. - */ -UCLASS(BlueprintType) -class RALPHACORE_API URalphaParameterBridge : public UObject -{ - GENERATED_BODY() - -public: - URalphaParameterBridge(); - - // Get all current rendering parameters - TSharedPtr GetAllParameters(); - - // Post-Process Volume - bool SetPostProcessParameters(const TSharedPtr& Params); - TSharedPtr GetPostProcessParameters(); - - // Directional Light (Sun) - bool SetDirectionalLightParameters(const TSharedPtr& Params); - TSharedPtr GetDirectionalLightParameters(); - - // Sky Light - bool SetSkyLightParameters(const TSharedPtr& Params); - TSharedPtr GetSkyLightParameters(); - - // Exponential Height Fog - bool SetFogParameters(const TSharedPtr& Params); - TSharedPtr GetFogParameters(); - - // Camera - bool SetCameraParameters(const TSharedPtr& Params); - TSharedPtr GetCameraParameters(); - - // Render Quality - bool SetRenderQuality(const TSharedPtr& Settings); - - // Asset Search - TArray> SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults); - TArray> ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive); - - // Actor Management - bool SpawnActor(const FString& AssetPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId); - bool DeleteActor(const FString& ActorId); - TArray> ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms); - bool SetActorTransform(const FString& ActorId, const FVector* Location, const FRotator* Rotation, const float* Scale, bool bRelative); - TSharedPtr GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials); - bool SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex); - - // Create a simple material with specified properties and apply to actor - bool SetActorSimpleMaterial(const FString& ActorId, const FLinearColor& BaseColor, float Metallic, float Roughness, float Opacity); - - // Create an emissive (unlit) material for glowing objects like sun, lights, screens - bool SetActorEmissiveMaterial(const FString& ActorId, const FLinearColor& EmissiveColor, float EmissiveIntensity); - - // Scene Setup - creates required actors for rendering control - TSharedPtr SetupScene(); - - // Spawn WaterBodyOcean from Water plugin - bool SpawnOcean(const FVector& Location, float Size, FString& OutActorId); - - // Configure ocean wave parameters - bool SetOceanWaves(float WaveAmplitude, float WaveLength, float Steepness, int32 NumWaves); - - // Create a minimal landscape for water plugin support - bool CreateLandscape(float Size, float HeightOffset); - - // Point Light - bool SpawnPointLight(const FVector& Location, float Intensity, const FLinearColor& Color, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId); - bool SetPointLightParameters(const FString& ActorId, const TSharedPtr& Params); - - // Spot Light - bool SpawnSpotLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float AttenuationRadius, float InnerConeAngle, float OuterConeAngle, const FString& ActorLabel, FString& OutActorId); - bool SetSpotLightParameters(const FString& ActorId, const TSharedPtr& Params); - - // Rect Light (area lighting for interiors) - bool SpawnRectLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float SourceWidth, float SourceHeight, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId); - bool SetRectLightParameters(const FString& ActorId, const TSharedPtr& Params); - - // Skeletal Mesh (characters, animated objects) - bool SpawnSkeletalMesh(const FString& SkeletalMeshPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId); - bool PlayAnimation(const FString& ActorId, const FString& AnimationPath, bool bLooping, float PlayRate); - bool StopAnimation(const FString& ActorId); - bool SetAnimationPose(const FString& ActorId, const FString& AnimationPath, float Time); - - // Reflection Captures - bool SpawnSphereReflectionCapture(const FVector& Location, float InfluenceRadius, const FString& ActorLabel, FString& OutActorId); - bool SpawnBoxReflectionCapture(const FVector& Location, const FVector& BoxExtent, const FString& ActorLabel, FString& OutActorId); - bool UpdateReflectionCaptures(); - - // IES Light Profiles - bool SetLightIESProfile(const FString& ActorId, const FString& IESProfilePath); - -private: - // Find actors of specific types in the world - APostProcessVolume* FindPostProcessVolume(); - ADirectionalLight* FindDirectionalLight(); - ASkyLight* FindSkyLight(); - AExponentialHeightFog* FindExponentialHeightFog(); - ACineCameraActor* FindCineCamera(); - - // Get the current world - UWorld* GetCurrentWorld(); - - // Parse hex color string to FLinearColor - FLinearColor ParseHexColor(const FString& HexColor); - FString ColorToHex(const FLinearColor& Color); - - // Refresh BP_Sky_Sphere when sun position changes - void RefreshSkySphere(); -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaScreenCapture.h b/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaScreenCapture.h deleted file mode 100644 index 5d8f88f5..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/Public/RalphaScreenCapture.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "RalphaScreenCapture.generated.h" - -/** - * Handles viewport screenshot capture for the style convergence loop. - */ -UCLASS(BlueprintType) -class RALPHACORE_API URalphaScreenCapture : public UObject -{ - GENERATED_BODY() - -public: - URalphaScreenCapture(); - - /** - * Capture the current viewport as a PNG image. - * @param Width Desired width (may be clamped to viewport size) - * @param Height Desired height (may be clamped to viewport size) - * @param OutBase64 The captured image as base64-encoded PNG - * @param OutFilePath Path where the image was saved (if saved to disk) - * @return True if capture succeeded - */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - bool CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath); - - /** - * Capture a high-resolution screenshot. - * @param Width Desired width - * @param Height Desired height - * @param OutBase64 The captured image as base64-encoded PNG - * @return True if capture succeeded - */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - bool CaptureHighResScreenshot(int32 Width, int32 Height, FString& OutBase64); - - /** - * Capture from a specific camera using SceneCapture2D. - * @param Width Desired width - * @param Height Desired height - * @param OutBase64 The captured image as base64-encoded PNG - * @param OutFilePath Path where the image was saved - * @return True if capture succeeded - */ - UFUNCTION(BlueprintCallable, Category = "Ralpha") - bool CaptureFromCamera(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath); - -private: - /** Convert raw pixel data to base64 PNG */ - FString PixelsToBase64PNG(const TArray& Pixels, int32 Width, int32 Height); - - /** Get the screenshot save directory */ - FString GetScreenshotDirectory(); -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaCore/RalphaCore.Build.cs b/Plugins/RalphaPlugin/Source/RalphaCore/RalphaCore.Build.cs deleted file mode 100644 index 29980a9c..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaCore/RalphaCore.Build.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -using UnrealBuildTool; - -public class RalphaCore : ModuleRules -{ - public RalphaCore(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - } - ); - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "Sockets", - "Networking", - "Json", - "JsonUtilities", - "RenderCore", - "Renderer", - "RHI", - "ImageWrapper", - "AssetRegistry", - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "Slate", - "SlateCore", - "CinematicCamera", - "Water", - "Landscape", - } - ); - - if (Target.bBuildEditor) - { - PrivateDependencyModuleNames.AddRange(new string[] - { - "UnrealEd", - "LevelEditor", - }); - } - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - } - ); - } -} diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditor.cpp b/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditor.cpp deleted file mode 100644 index b676d419..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditor.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaEditor.h" -#include "RalphaEditorCommands.h" -#include "RalphaEditorStyle.h" -#include "ToolMenus.h" -#include "RalphaMCPServer.h" - -#define LOCTEXT_NAMESPACE "FRalphaEditorModule" - -void FRalphaEditorModule::StartupModule() -{ - FRalphaEditorStyle::Initialize(); - FRalphaEditorStyle::ReloadTextures(); - - FRalphaEditorCommands::Register(); - - PluginCommands = MakeShareable(new FUICommandList); - - PluginCommands->MapAction( - FRalphaEditorCommands::Get().StartServer, - FExecuteAction::CreateLambda([]() - { - URalphaMCPServer* Server = URalphaMCPServer::Get(); - if (Server) - { - if (!Server->IsRunning()) - { - Server->Start(); - } - else - { - Server->Stop(); - } - } - }), - FCanExecuteAction() - ); - - UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FRalphaEditorModule::RegisterMenus)); - - // Auto-start the MCP server - URalphaMCPServer* Server = URalphaMCPServer::Get(); - if (Server && !Server->IsRunning()) - { - Server->Start(); - UE_LOG(LogTemp, Log, TEXT("Ralpha MCP Server auto-started on port 30010")); - } - - UE_LOG(LogTemp, Log, TEXT("Ralpha Editor Module Started")); -} - -void FRalphaEditorModule::ShutdownModule() -{ - UToolMenus::UnRegisterStartupCallback(this); - UToolMenus::UnregisterOwner(this); - - FRalphaEditorStyle::Shutdown(); - FRalphaEditorCommands::Unregister(); - - UE_LOG(LogTemp, Log, TEXT("Ralpha Editor Module Shutdown")); -} - -void FRalphaEditorModule::RegisterMenus() -{ - FToolMenuOwnerScoped OwnerScoped(this); - - { - UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); - { - FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Ralpha"); - { - FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FRalphaEditorCommands::Get().StartServer)); - Entry.SetCommandList(PluginCommands); - } - } - } -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FRalphaEditorModule, RalphaEditor) diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorCommands.cpp b/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorCommands.cpp deleted file mode 100644 index f3f84e15..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorCommands.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaEditorCommands.h" - -#define LOCTEXT_NAMESPACE "FRalphaEditorModule" - -void FRalphaEditorCommands::RegisterCommands() -{ - UI_COMMAND(StartServer, "Ralpha", "Start/Stop the Ralpha MCP Server", EUserInterfaceActionType::Button, FInputChord()); -} - -#undef LOCTEXT_NAMESPACE diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorStyle.cpp b/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorStyle.cpp deleted file mode 100644 index 18acfe12..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Private/RalphaEditorStyle.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#include "RalphaEditorStyle.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 FRalphaEditorStyle::StyleInstance = nullptr; - -void FRalphaEditorStyle::Initialize() -{ - if (!StyleInstance.IsValid()) - { - StyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); - } -} - -void FRalphaEditorStyle::Shutdown() -{ - FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); - ensure(StyleInstance.IsUnique()); - StyleInstance.Reset(); -} - -FName FRalphaEditorStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("RalphaEditorStyle")); - return StyleSetName; -} - -const ISlateStyle& FRalphaEditorStyle::Get() -{ - return *StyleInstance; -} - -void FRalphaEditorStyle::ReloadTextures() -{ - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); - } -} - -TSharedRef FRalphaEditorStyle::Create() -{ - TSharedRef Style = MakeShareable(new FSlateStyleSet("RalphaEditorStyle")); - Style->SetContentRoot(IPluginManager::Get().FindPlugin("RalphaPlugin")->GetBaseDir() / TEXT("Resources")); - - Style->Set("Ralpha.StartServer", new IMAGE_BRUSH_SVG(TEXT("Icon20"), FVector2D(20.0f, 20.0f))); - - return Style; -} - -#undef RootToContentDir diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditor.h b/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditor.h deleted file mode 100644 index 242f0f31..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditor.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -class FToolBarBuilder; -class FMenuBuilder; - -class RALPHAEDITOR_API FRalphaEditorModule : public IModuleInterface -{ -public: - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -private: - void RegisterMenus(); - - TSharedPtr PluginCommands; -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorCommands.h b/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorCommands.h deleted file mode 100644 index 4337f940..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorCommands.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Framework/Commands/Commands.h" -#include "RalphaEditorStyle.h" - -class FRalphaEditorCommands : public TCommands -{ -public: - FRalphaEditorCommands() - : TCommands( - TEXT("Ralpha"), - NSLOCTEXT("Contexts", "Ralpha", "Ralpha Plugin"), - NAME_None, - FRalphaEditorStyle::GetStyleSetName()) - { - } - - virtual void RegisterCommands() override; - -public: - TSharedPtr StartServer; -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorStyle.h b/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorStyle.h deleted file mode 100644 index 79cff4e0..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/Public/RalphaEditorStyle.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Styling/SlateStyle.h" - -class FRalphaEditorStyle -{ -public: - static void Initialize(); - static void Shutdown(); - static void ReloadTextures(); - static const ISlateStyle& Get(); - static FName GetStyleSetName(); - -private: - static TSharedRef Create(); - static TSharedPtr StyleInstance; -}; diff --git a/Plugins/RalphaPlugin/Source/RalphaEditor/RalphaEditor.Build.cs b/Plugins/RalphaPlugin/Source/RalphaEditor/RalphaEditor.Build.cs deleted file mode 100644 index b9b0f7e1..00000000 --- a/Plugins/RalphaPlugin/Source/RalphaEditor/RalphaEditor.Build.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright Ralpha Team. All Rights Reserved. - -using UnrealBuildTool; - -public class RalphaEditor : ModuleRules -{ - public RalphaEditor(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - } - ); - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "Slate", - "SlateCore", - "RalphaCore", - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "UnrealEd", - "EditorStyle", - "ToolMenus", - "LevelEditor", - "AssetRegistry", - "ContentBrowser", - "Projects", - } - ); - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - } - ); - } -} diff --git a/README.md b/README.md new file mode 100644 index 00000000..dcb654ee --- /dev/null +++ b/README.md @@ -0,0 +1,188 @@ +# Ralpha UE5 - Kitchen Sink Asset Project + +Comprehensive UE5 asset library and scene templates for AI-controlled rendering with [Ralpha](https://github.com/jamestagg/ralpha). + +## Quick Start + +```bash +# Clone with LFS +git lfs install +git clone https://github.com/jamestagg/ralpha-ue5.git + +# Open in UE5.4+ +# File > Open Project > ralpha-ue5/Ralpha.uproject +``` + +## Architecture + +This repo contains **assets only**. The MCP plugin and tooling live in the main [ralpha](https://github.com/jamestagg/ralpha) repo. + +``` +ralpha-ue5/ <- You are here (assets, templates, presets) + │ + └── Plugins/RalphaPlugin/ <- Symlink to ralpha/plugin + │ + ├── MCP Server (port 30010) + └── 69 Command Handlers + +ralpha/ <- Main repo (tooling, API, frontend) + │ + ├── plugin/ <- UE5 plugin source + ├── ralpha-api/ <- Backend API + └── frontend/ <- Web interface +``` + +## Repository Structure + +``` +ralpha-ue5/ +├── RalphaData/ # NOT in LFS - text/JSON files +│ ├── catalogue/ +│ │ ├── catalogue.json # Machine-readable asset index +│ │ └── catalogue.schema.json # JSON schema +│ ├── thumbnails/ # Small preview images +│ ├── recipes/ # Scene composition templates +│ │ ├── recipe.schema.json +│ │ ├── living_room_modern.json +│ │ ├── portrait_studio_white.json +│ │ └── office_corporate.json +│ ├── presets/ +│ │ ├── lighting/ # Lighting configurations +│ │ ├── post/ # Post-processing profiles +│ │ ├── scene/ # Full scene configs +│ │ └── camera/ # Camera presets +│ └── scripts/ +│ └── generate_catalogue.py # Asset indexing tool +│ +├── Content/ # IN LFS - large binary files +│ └── Ralpha/ +│ ├── Maps/ # Template levels +│ │ ├── Template_Empty.umap +│ │ ├── Template_Studio.umap +│ │ ├── Template_Outdoor.umap +│ │ └── Template_Interior.umap +│ ├── Assets/ # Props, furniture, etc. +│ │ ├── Furniture/ +│ │ ├── Electronics/ +│ │ ├── Food/ +│ │ ├── Vehicles/ +│ │ ├── Nature/ +│ │ └── ... +│ ├── Materials/ +│ ├── HDRI/ +│ └── LUTs/ +│ +└── Plugins/ + └── RalphaPlugin/ # Symlink to ralpha/plugin +``` + +## Key Concepts + +### Template Maps vs Heavy Maps + +Instead of 50+ bespoke maps, we use **4 templates** + **JSON presets**: + +| Template | Use Case | +|----------|----------| +| `Template_Empty` | Bare minimum - sky + ground | +| `Template_Studio` | Portrait/product - cyclorama + lights | +| `Template_Outdoor` | Landscapes - terrain + atmosphere | +| `Template_Interior` | Rooms - walls + ceiling + basic lighting | + +Scene variation comes from presets, not map duplication. + +### Recipe System + +Recipes define composite scenes that can be spawned instantly: + +```json +{ + "recipe_id": "living_room_modern", + "components": [ + {"asset": "sofa_modern_3seat", "position": "center"}, + {"asset": "coffee_table_rect", "position": "in_front_of:sofa"}, + {"asset": "tv_65inch", "position": "wall:opposite_sofa"} + ], + "lighting_preset": "Interior_Evening" +} +``` + +MCP command: `{"type":"spawn_recipe","parameters":{"recipe":"living_room_modern"}}` + +### Asset Catalogue + +All assets are indexed in `catalogue.json` for programmatic discovery: + +```json +{ + "id": "sofa_modern_3seat", + "name": "Modern 3-Seat Sofa", + "category": "Furniture/Seating", + "unreal_path": "/Game/Ralpha/Assets/Furniture/Seating/Sofa_Modern_3Seat", + "tags": ["sofa", "couch", "seating", "modern", "living room"], + "bounds": {"x": 220, "y": 90, "z": 85}, + "placement_modes": ["floor"] +} +``` + +MCP command: `{"type":"search_catalogue","parameters":{"query":"modern sofa"}}` + +## MCP Commands + +These commands interface with this asset library: + +| Command | Description | +|---------|-------------| +| `load_template` | Load a base template map | +| `apply_scene_preset` | Apply lighting/post/camera preset | +| `spawn_recipe` | Spawn a full scene composition | +| `spawn_catalogue_item` | Spawn single asset from catalogue | +| `search_catalogue` | Search assets by tags/name | +| `get_version` | Get plugin + catalogue version | + +## Asset Standards + +All assets must meet these requirements: + +- **Scale**: 1 unit = 1 cm +- **Pivot**: Bottom center (for floor items) +- **Collision**: Present for interactive items +- **LODs**: At least 2 levels for complex meshes +- **Materials**: PBR with proper slots +- **Naming**: `Category_Type_Variant` (e.g., `Sofa_Modern_Grey`) + +## Regenerating Catalogue + +After adding assets, regenerate the catalogue: + +```bash +cd ralpha-ue5 +python RalphaData/scripts/generate_catalogue.py --scan-only +``` + +For full metadata extraction (requires UE Editor): +```bash +python RalphaData/scripts/generate_catalogue.py --project Ralpha.uproject --ue-path /path/to/UnrealEditor +``` + +## License Policy + +See [LICENSE_POLICY.md](LICENSE_POLICY.md) for asset provenance requirements. + +- **CC0/Public Domain**: Preferred, no restrictions +- **CC-BY**: Allowed, attribution in catalogue +- **Megascans**: Allowed (free with UE) +- **Marketplace**: Allowed for purchased assets +- **Restricted**: Separate folder, excluded by default + +## Related Repositories + +- [ralpha](https://github.com/jamestagg/ralpha) - Main tooling, plugin, API +- [ralpha-ue5-metahumans](https://github.com/jamestagg/ralpha-ue5-metahumans) - MetaHuman characters (optional) + +## Contributing + +1. Import asset following standards above +2. Run `generate_catalogue.py` +3. Add to appropriate recipe if applicable +4. Submit PR with asset source/license info diff --git a/Ralpha.uproject b/Ralpha.uproject index c2cce92d..fb23cf52 100644 --- a/Ralpha.uproject +++ b/Ralpha.uproject @@ -15,6 +15,7 @@ } ], "TargetPlatforms": [ + "Mac", "Windows" ] } diff --git a/RalphaData/catalogue/catalogue.json b/RalphaData/catalogue/catalogue.json new file mode 100644 index 00000000..73741c91 --- /dev/null +++ b/RalphaData/catalogue/catalogue.json @@ -0,0 +1,39 @@ +{ + "$schema": "./catalogue.schema.json", + "version": "1.0.0", + "generated_at": "2026-01-20T00:00:00Z", + "asset_count": 0, + "categories": [ + "Furniture/Seating", + "Furniture/Tables", + "Furniture/Storage", + "Furniture/Bedroom", + "Electronics/Computing", + "Electronics/Entertainment", + "Electronics/Appliances", + "Food/Packaged", + "Food/Fresh", + "Food/Prepared", + "Food/Beverages", + "Kitchenware", + "Office", + "Personal", + "Decor/Wall", + "Decor/Tabletop", + "Decor/Lighting", + "Vehicles/Cars", + "Vehicles/Motorcycles", + "Vehicles/Bicycles", + "Vehicles/Other", + "Nature/Trees", + "Nature/Plants", + "Nature/Rocks", + "Architecture/Structural", + "Architecture/Exterior", + "Urban", + "Sports", + "Tools", + "Animals" + ], + "assets": [] +} diff --git a/RalphaData/catalogue/catalogue.schema.json b/RalphaData/catalogue/catalogue.schema.json new file mode 100644 index 00000000..5addf128 --- /dev/null +++ b/RalphaData/catalogue/catalogue.schema.json @@ -0,0 +1,122 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ralpha Asset Catalogue", + "description": "Machine-readable index of all assets in ralpha-ue5", + "type": "object", + "required": ["version", "generated_at", "assets"], + "properties": { + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "asset_count": { + "type": "integer" + }, + "categories": { + "type": "array", + "items": { "type": "string" } + }, + "assets": { + "type": "array", + "items": { "$ref": "#/definitions/Asset" } + } + }, + "definitions": { + "Asset": { + "type": "object", + "required": ["id", "name", "category", "unreal_path"], + "properties": { + "id": { + "type": "string", + "description": "Stable unique identifier (snake_case)", + "pattern": "^[a-z][a-z0-9_]*$" + }, + "name": { + "type": "string", + "description": "Human-readable display name" + }, + "category": { + "type": "string", + "description": "Category path (e.g., Furniture/Seating)" + }, + "unreal_path": { + "type": "string", + "description": "Full Unreal asset path", + "pattern": "^/Game/" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Searchable tags/synonyms" + }, + "thumbnail": { + "type": "string", + "description": "Relative path to thumbnail image" + }, + "bounds": { + "type": "object", + "description": "Bounding box in cm", + "properties": { + "x": { "type": "number" }, + "y": { "type": "number" }, + "z": { "type": "number" } + } + }, + "pivot": { + "type": "string", + "enum": ["bottom_center", "center", "bottom_back", "custom"], + "default": "bottom_center" + }, + "placement_modes": { + "type": "array", + "items": { + "type": "string", + "enum": ["floor", "wall", "ceiling", "surface", "hanging", "floating"] + }, + "default": ["floor"] + }, + "collision": { + "type": "boolean", + "default": true + }, + "lods": { + "type": "integer", + "description": "Number of LOD levels" + }, + "triangle_count": { + "type": "integer", + "description": "LOD0 triangle count" + }, + "materials": { + "type": "array", + "items": { "type": "string" }, + "description": "Material slot names" + }, + "variants": { + "type": "array", + "items": { "type": "string" }, + "description": "IDs of variant assets (e.g., color options)" + }, + "license": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["CC0", "CC-BY", "CC-BY-SA", "Megascans", "Marketplace", "Custom", "Restricted"] + }, + "source": { "type": "string" }, + "attribution": { "type": "string" } + } + }, + "added_at": { + "type": "string", + "format": "date-time" + } + } + } + } +} diff --git a/RalphaData/presets/lighting/natural_golden_hour.json b/RalphaData/presets/lighting/natural_golden_hour.json new file mode 100644 index 00000000..3d3bae65 --- /dev/null +++ b/RalphaData/presets/lighting/natural_golden_hour.json @@ -0,0 +1,40 @@ +{ + "preset_id": "Natural_GoldenHour", + "name": "Golden Hour", + "description": "Warm afternoon/evening sun with long shadows", + "category": "natural", + "directional_light": { + "rotation": [-15, 45, 0], + "intensity": 8.0, + "color": "#FFB366", + "temperature": 4000, + "cast_shadows": true, + "shadow_cascades": 4, + "atmosphere_sun_light": true + }, + "sky_light": { + "intensity": 1.0, + "color": "#FFE4C4", + "real_time_capture": true + }, + "sky_atmosphere": { + "enabled": true, + "rayleigh_scattering": [0.175, 0.409, 1.0], + "mie_scattering_scale": 0.004, + "absorption_scale": 0.75 + }, + "volumetric_cloud": { + "enabled": true, + "layer_bottom_altitude": 5.0, + "layer_height": 10.0, + "cloud_density": 0.3 + }, + "exponential_height_fog": { + "enabled": true, + "fog_density": 0.02, + "fog_height_falloff": 0.2, + "fog_inscattering_color": "#FFCC99", + "volumetric_fog": true, + "volumetric_fog_scattering": 0.5 + } +} diff --git a/RalphaData/presets/lighting/studio_3point_soft.json b/RalphaData/presets/lighting/studio_3point_soft.json new file mode 100644 index 00000000..379ae30a --- /dev/null +++ b/RalphaData/presets/lighting/studio_3point_soft.json @@ -0,0 +1,55 @@ +{ + "preset_id": "Studio_3Point_Soft", + "name": "Studio 3-Point Soft", + "description": "Classic soft 3-point lighting for portraits and products", + "category": "studio", + "lights": [ + { + "name": "KeyLight", + "type": "rect", + "position": [-200, 150, 200], + "rotation": [0, -45, -30], + "intensity": 15.0, + "color": "#FFF5E6", + "temperature": 5500, + "source_width": 100, + "source_height": 150, + "barn_door_angle": 45, + "cast_shadows": true, + "soft_shadows": true + }, + { + "name": "FillLight", + "type": "rect", + "position": [150, 100, 180], + "rotation": [0, 45, -20], + "intensity": 5.0, + "color": "#E6F0FF", + "temperature": 6500, + "source_width": 120, + "source_height": 120, + "cast_shadows": false + }, + { + "name": "RimLight", + "type": "rect", + "position": [0, -200, 220], + "rotation": [0, 180, -30], + "intensity": 8.0, + "color": "#FFFFFF", + "temperature": 6000, + "source_width": 50, + "source_height": 150, + "cast_shadows": false + } + ], + "sky_light": { + "intensity": 0.5, + "color": "#D4E5F7", + "cubemap": null + }, + "environment": { + "ambient_intensity": 0.1, + "fog_enabled": false + } +} diff --git a/RalphaData/presets/post/pp_cinematic.json b/RalphaData/presets/post/pp_cinematic.json new file mode 100644 index 00000000..7c500b82 --- /dev/null +++ b/RalphaData/presets/post/pp_cinematic.json @@ -0,0 +1,46 @@ +{ + "preset_id": "PP_Cinematic", + "name": "Cinematic", + "description": "Film-like look with crushed blacks and teal/orange color grading", + "category": "stylized", + "bloom": { + "intensity": 0.5, + "threshold": -1.0 + }, + "exposure": { + "method": "manual", + "exposure_compensation": 0.0, + "min_brightness": 0.03, + "max_brightness": 2.0 + }, + "color_grading": { + "temperature": 6500, + "tint": 0, + "saturation": 1.05, + "contrast": 1.1, + "gamma": 1.0, + "gain": 1.0, + "offset": 0, + "shadows_saturation": 0.9, + "shadows_contrast": 1.1, + "shadows_tint": "#1A3D5C", + "highlights_saturation": 1.1, + "highlights_tint": "#FFE4C4", + "toe": 0.55, + "shoulder": 0.26, + "black_clip": 0.02, + "white_clip": 0.04 + }, + "vignette": { + "intensity": 0.3 + }, + "chromatic_aberration": { + "intensity": 0.1, + "start_offset": 0.5 + }, + "film_grain": { + "intensity": 0.15, + "response": 0.8 + }, + "lut": null +} diff --git a/RalphaData/recipes/living_room_modern.json b/RalphaData/recipes/living_room_modern.json new file mode 100644 index 00000000..2f8d3d7c --- /dev/null +++ b/RalphaData/recipes/living_room_modern.json @@ -0,0 +1,68 @@ +{ + "$schema": "./recipe.schema.json", + "recipe_id": "living_room_modern", + "name": "Modern Living Room", + "description": "Contemporary living room with sofa, coffee table, TV, and accent lighting", + "category": "interior", + "template_map": "Template_Interior", + "components": [ + { + "asset": "sofa_modern_3seat", + "position": "center", + "facing": "focal_point", + "placement": "floor" + }, + { + "asset": "coffee_table_rect_glass", + "position": "in_front_of:sofa_modern_3seat", + "offset": [0, 100, 0], + "placement": "floor" + }, + { + "asset": "tv_65inch_mounted", + "position": "wall:opposite_sofa", + "offset": [0, 0, 120], + "placement": "wall", + "facing": "sofa_modern_3seat" + }, + { + "asset": "tv_stand_modern", + "position": "below:tv_65inch_mounted", + "placement": "floor" + }, + { + "asset": "floor_lamp_arc", + "position": "beside:sofa_modern_3seat", + "offset": [-50, 0, 0], + "placement": "floor" + }, + { + "asset": "side_table_round", + "position": "beside:sofa_modern_3seat", + "offset": [50, 0, 0], + "placement": "floor" + }, + { + "asset": "plant_potted_fiddle", + "position": "corner:back_left", + "placement": "floor", + "optional": true + }, + { + "asset": "rug_modern_geometric", + "position": "under:coffee_table_rect_glass", + "placement": "floor", + "optional": true + }, + { + "asset": "book_stack_decorative", + "position": "on:coffee_table_rect_glass", + "placement": "surface", + "optional": true + } + ], + "lighting_preset": "Interior_Evening", + "post_preset": "PP_Warm", + "camera_preset": "Interior_Wide", + "tags": ["living room", "modern", "contemporary", "interior", "home"] +} diff --git a/RalphaData/recipes/office_corporate.json b/RalphaData/recipes/office_corporate.json new file mode 100644 index 00000000..d7c3e2d5 --- /dev/null +++ b/RalphaData/recipes/office_corporate.json @@ -0,0 +1,77 @@ +{ + "$schema": "./recipe.schema.json", + "recipe_id": "office_corporate", + "name": "Corporate Office", + "description": "Professional office space with desk, computer, and meeting area", + "category": "interior", + "template_map": "Template_Interior", + "components": [ + { + "asset": "desk_executive", + "position": "center_back", + "facing": "camera", + "placement": "floor" + }, + { + "asset": "chair_office_executive", + "position": "behind:desk_executive", + "facing": "camera", + "placement": "floor" + }, + { + "asset": "monitor_27inch", + "position": "on:desk_executive", + "offset": [0, -20, 0], + "placement": "surface" + }, + { + "asset": "keyboard_wireless", + "position": "on:desk_executive", + "offset": [0, 20, 0], + "placement": "surface" + }, + { + "asset": "mouse_wireless", + "position": "on:desk_executive", + "offset": [30, 20, 0], + "placement": "surface" + }, + { + "asset": "phone_desk", + "position": "on:desk_executive", + "offset": [-40, 10, 0], + "placement": "surface", + "optional": true + }, + { + "asset": "chair_guest", + "position": "in_front_of:desk_executive", + "offset": [-60, 150, 0], + "facing": "desk_executive", + "placement": "floor" + }, + { + "asset": "chair_guest", + "position": "in_front_of:desk_executive", + "offset": [60, 150, 0], + "facing": "desk_executive", + "placement": "floor" + }, + { + "asset": "bookshelf_modern", + "position": "wall:left", + "placement": "floor", + "optional": true + }, + { + "asset": "plant_potted_snake", + "position": "corner:back_right", + "placement": "floor", + "optional": true + } + ], + "lighting_preset": "Interior_Office", + "post_preset": "PP_Neutral", + "camera_preset": "Interior_Medium", + "tags": ["office", "corporate", "business", "professional", "desk", "workspace"] +} diff --git a/RalphaData/recipes/portrait_studio_white.json b/RalphaData/recipes/portrait_studio_white.json new file mode 100644 index 00000000..4e00d805 --- /dev/null +++ b/RalphaData/recipes/portrait_studio_white.json @@ -0,0 +1,44 @@ +{ + "$schema": "./recipe.schema.json", + "recipe_id": "portrait_studio_white", + "name": "White Portrait Studio", + "description": "Classic white cyclorama with 3-point lighting for portraits", + "category": "portrait", + "template_map": "Template_Studio", + "components": [ + { + "asset": "cyclorama_white", + "position": [0, 0, 0], + "scale": 1.0, + "placement": "floor" + }, + { + "asset": "light_key_softbox", + "position": [-200, 150, 200], + "facing": "center", + "placement": "floating" + }, + { + "asset": "light_fill_panel", + "position": [150, 100, 180], + "facing": "center", + "placement": "floating" + }, + { + "asset": "light_rim_strip", + "position": [0, -200, 220], + "facing": "center", + "placement": "floating" + }, + { + "asset": "apple_box_full", + "position": [0, 0, 0], + "placement": "floor", + "optional": true + } + ], + "lighting_preset": "Studio_3Point_Soft", + "post_preset": "PP_Portrait_Clean", + "camera_preset": "Portrait_Headshot", + "tags": ["portrait", "studio", "white", "cyclorama", "headshot", "professional"] +} diff --git a/RalphaData/recipes/recipe.schema.json b/RalphaData/recipes/recipe.schema.json new file mode 100644 index 00000000..9b0cbc14 --- /dev/null +++ b/RalphaData/recipes/recipe.schema.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ralpha Scene Recipe", + "description": "Composite scene definition for instant spawning", + "type": "object", + "required": ["recipe_id", "name", "components"], + "properties": { + "recipe_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "category": { + "type": "string", + "enum": ["interior", "exterior", "portrait", "product", "vehicle", "custom"] + }, + "template_map": { + "type": "string", + "description": "Base template map to use", + "enum": ["Template_Empty", "Template_Studio", "Template_Outdoor", "Template_Interior"] + }, + "components": { + "type": "array", + "items": { "$ref": "#/definitions/Component" } + }, + "lighting_preset": { + "type": "string", + "description": "Lighting preset ID to apply" + }, + "post_preset": { + "type": "string", + "description": "Post-processing preset ID to apply" + }, + "camera_preset": { + "type": "string", + "description": "Camera preset ID to apply" + }, + "sky_preset": { + "type": "string", + "description": "Sky/atmosphere preset to apply" + }, + "dependencies": { + "type": "array", + "items": { "type": "string" }, + "description": "Other recipe IDs that must be spawned first" + }, + "tags": { + "type": "array", + "items": { "type": "string" } + } + }, + "definitions": { + "Component": { + "type": "object", + "required": ["asset"], + "properties": { + "asset": { + "type": "string", + "description": "Asset ID from catalogue" + }, + "position": { + "oneOf": [ + { + "type": "array", + "items": { "type": "number" }, + "minItems": 3, + "maxItems": 3, + "description": "Absolute [x, y, z] position" + }, + { + "type": "string", + "description": "Relative position (e.g., 'center', 'in_front_of:Sofa', 'wall:north')" + } + ] + }, + "rotation": { + "type": "array", + "items": { "type": "number" }, + "minItems": 3, + "maxItems": 3, + "description": "[pitch, yaw, roll] in degrees" + }, + "scale": { + "oneOf": [ + { "type": "number" }, + { + "type": "array", + "items": { "type": "number" }, + "minItems": 3, + "maxItems": 3 + } + ], + "default": 1.0 + }, + "facing": { + "type": "string", + "description": "What the object should face", + "enum": ["focal_point", "camera", "center", "north", "south", "east", "west", "away"] + }, + "placement": { + "type": "string", + "enum": ["floor", "wall", "surface", "hanging", "floating"], + "default": "floor" + }, + "offset": { + "type": "array", + "items": { "type": "number" }, + "minItems": 3, + "maxItems": 3, + "description": "Offset from calculated position" + }, + "attach_to": { + "type": "string", + "description": "Asset ID to attach to" + }, + "socket": { + "type": "string", + "description": "Socket name for attachment" + }, + "optional": { + "type": "boolean", + "default": false, + "description": "Skip if asset not found" + }, + "variants": { + "type": "object", + "description": "Material/property overrides" + } + } + } + } +} diff --git a/RalphaData/scripts/generate_catalogue.py b/RalphaData/scripts/generate_catalogue.py new file mode 100644 index 00000000..d9c51bd2 --- /dev/null +++ b/RalphaData/scripts/generate_catalogue.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +Generate catalogue.json from Unreal Engine assets. + +This script can be run: +1. Standalone with Python (parses .uasset files for metadata) +2. As a wrapper for UE commandlet (for full asset inspection) + +Usage: + python generate_catalogue.py --project /path/to/Ralpha.uproject + python generate_catalogue.py --scan-only # Just scan folders +""" + +import argparse +import json +import os +import subprocess +import hashlib +from datetime import datetime +from pathlib import Path +from typing import Any + +# Asset categories based on folder structure +CATEGORY_MAP = { + "Furniture/Seating": ["sofa", "chair", "stool", "bench", "armchair"], + "Furniture/Tables": ["table", "desk", "counter"], + "Furniture/Storage": ["shelf", "cabinet", "drawer", "wardrobe"], + "Furniture/Bedroom": ["bed", "nightstand", "mattress"], + "Electronics/Computing": ["laptop", "monitor", "keyboard", "mouse", "computer"], + "Electronics/Entertainment": ["tv", "speaker", "console", "headphone"], + "Electronics/Appliances": ["refrigerator", "microwave", "oven", "washer"], + "Food/Packaged": ["can", "bottle", "box", "bag", "jar"], + "Food/Fresh": ["fruit", "vegetable", "bread", "meat"], + "Food/Prepared": ["pizza", "burger", "sandwich", "cake"], + "Food/Beverages": ["coffee", "tea", "juice", "wine", "beer"], + "Vehicles/Cars": ["sedan", "suv", "truck", "van", "sports"], + "Vehicles/Motorcycles": ["motorcycle", "scooter", "bike"], + "Nature/Trees": ["tree", "pine", "oak", "palm"], + "Nature/Plants": ["plant", "flower", "bush", "grass"], + "Nature/Rocks": ["rock", "boulder", "stone", "cliff"], +} + + +def scan_asset_folder(content_path: Path) -> list[dict[str, Any]]: + """Scan Content/Ralpha/Assets for .uasset files.""" + assets = [] + asset_root = content_path / "Ralpha" / "Assets" + + if not asset_root.exists(): + print(f"Warning: Asset folder not found: {asset_root}") + return assets + + for uasset_path in asset_root.rglob("*.uasset"): + # Skip generated files + if "Intermediate" in str(uasset_path) or "Saved" in str(uasset_path): + continue + + rel_path = uasset_path.relative_to(content_path) + category = str(rel_path.parent).replace("Ralpha/Assets/", "").replace("/", "/") + + # Generate stable ID from path + asset_name = uasset_path.stem + asset_id = asset_name.lower().replace(" ", "_").replace("-", "_") + + # Build Unreal path + unreal_path = f"/Game/{str(rel_path.with_suffix('')).replace(os.sep, '/')}" + + # Infer tags from name + tags = [word.lower() for word in asset_name.replace("_", " ").split()] + + asset = { + "id": asset_id, + "name": asset_name.replace("_", " ").title(), + "category": category, + "unreal_path": unreal_path, + "tags": tags, + "thumbnail": f"thumbnails/{asset_id}.png", + "pivot": "bottom_center", + "placement_modes": ["floor"], + "collision": True, + "license": { + "type": "Unknown", + "source": "TBD" + }, + "added_at": datetime.utcnow().isoformat() + "Z" + } + + assets.append(asset) + + return assets + + +def run_ue_commandlet(project_path: Path, ue_path: Path | None = None) -> list[dict[str, Any]]: + """Run UE commandlet to extract full asset metadata.""" + # This would invoke the UE editor in commandlet mode + # For now, just use scan method + print("Note: Full UE commandlet integration not implemented yet") + print("Using folder scan method instead") + + content_path = project_path.parent / "Content" + return scan_asset_folder(content_path) + + +def generate_thumbnails(assets: list[dict], project_path: Path) -> None: + """Generate thumbnail images for assets (placeholder).""" + thumbnail_dir = project_path.parent / "RalphaData" / "thumbnails" + thumbnail_dir.mkdir(parents=True, exist_ok=True) + + print(f"Thumbnail directory: {thumbnail_dir}") + print("Note: Actual thumbnail generation requires UE Editor Utility") + + # Create placeholder file + placeholder = thumbnail_dir / ".gitkeep" + placeholder.touch() + + +def compute_catalogue_hash(assets: list[dict]) -> str: + """Compute hash of catalogue for version tracking.""" + content = json.dumps(sorted([a["id"] for a in assets])) + return hashlib.sha256(content.encode()).hexdigest()[:12] + + +def main(): + parser = argparse.ArgumentParser(description="Generate Ralpha asset catalogue") + parser.add_argument("--project", type=Path, help="Path to .uproject file") + parser.add_argument("--scan-only", action="store_true", help="Only scan folders, no UE") + parser.add_argument("--output", type=Path, help="Output catalogue path") + parser.add_argument("--ue-path", type=Path, help="Path to UE Editor executable") + args = parser.parse_args() + + # Find project + if args.project: + project_path = args.project + else: + # Default to current directory + project_files = list(Path.cwd().glob("*.uproject")) + if project_files: + project_path = project_files[0] + else: + print("Error: No .uproject file found. Use --project to specify.") + return 1 + + print(f"Project: {project_path}") + + # Scan or run commandlet + content_path = project_path.parent / "Content" + if args.scan_only or not args.ue_path: + assets = scan_asset_folder(content_path) + else: + assets = run_ue_commandlet(project_path, args.ue_path) + + print(f"Found {len(assets)} assets") + + # Build catalogue + catalogue = { + "$schema": "./catalogue.schema.json", + "version": "1.0.0", + "generated_at": datetime.utcnow().isoformat() + "Z", + "asset_count": len(assets), + "catalogue_hash": compute_catalogue_hash(assets), + "categories": sorted(list(set(a["category"] for a in assets))), + "assets": sorted(assets, key=lambda a: (a["category"], a["id"])) + } + + # Output + if args.output: + output_path = args.output + else: + output_path = project_path.parent / "RalphaData" / "catalogue" / "catalogue.json" + + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w") as f: + json.dump(catalogue, f, indent=2) + + print(f"Catalogue written to: {output_path}") + print(f"Hash: {catalogue['catalogue_hash']}") + + # Generate thumbnails placeholder + generate_thumbnails(assets, project_path) + + return 0 + + +if __name__ == "__main__": + exit(main())