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())