Initial UE5 project with RalphaPlugin
- UE5.7 project configured with LFS for binary assets - RalphaPlugin for MCP-based AI rendering control - Ready for content creation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
8a4b9a3f06
|
|
@ -0,0 +1,41 @@
|
||||||
|
# UE5 LFS tracking
|
||||||
|
|
||||||
|
# Unreal binary assets
|
||||||
|
*.uasset filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.umap filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Textures
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.psd filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.exr filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.hdr filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Video
|
||||||
|
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mov filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.avi filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# 3D formats
|
||||||
|
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.obj filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gltf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.glb filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.abc filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.otf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Compiled/binary
|
||||||
|
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.so filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# UE5 Project .gitignore
|
||||||
|
|
||||||
|
# Build artifacts (rebuilt locally)
|
||||||
|
Binaries/
|
||||||
|
Intermediate/
|
||||||
|
DerivedDataCache/
|
||||||
|
Build/
|
||||||
|
|
||||||
|
# Saved data (user-specific, logs, autosaves)
|
||||||
|
Saved/
|
||||||
|
|
||||||
|
# Plugin binaries (rebuilt per machine)
|
||||||
|
Plugins/**/Binaries/
|
||||||
|
Plugins/**/Intermediate/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.sdf
|
||||||
|
*.VC.db
|
||||||
|
*.VC.opendb
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Cooked content (generated)
|
||||||
|
**/Cooked/
|
||||||
|
|
||||||
|
# Starter content (if using)
|
||||||
|
**/StarterContent/
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[/Script/UnrealEd.EditorPerformanceSettings]
|
||||||
|
bMonitorEditorPerformance=True
|
||||||
|
|
||||||
|
[/Script/UnrealEd.CrashReporterSettings]
|
||||||
|
bHideLogFilesOption=False
|
||||||
|
bHideRestartOption=False
|
||||||
|
|
||||||
|
[ContentBrowser]
|
||||||
|
ShowEngineContent=False
|
||||||
|
ShowPluginContent=True
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
|
EditorStartupMap=/Game/Maps/Main
|
||||||
|
GameDefaultMap=/Game/Maps/Main
|
||||||
|
|
||||||
|
[/Script/Engine.RendererSettings]
|
||||||
|
r.DefaultFeature.AutoExposure=True
|
||||||
|
r.DefaultFeature.MotionBlur=True
|
||||||
|
r.DefaultFeature.Bloom=True
|
||||||
|
r.DefaultFeature.AmbientOcclusion=True
|
||||||
|
r.DefaultFeature.AmbientOcclusionStaticFraction=True
|
||||||
|
r.Lumen.DiffuseIndirect.Allow=True
|
||||||
|
r.Lumen.Reflections.Allow=True
|
||||||
|
r.RayTracing=True
|
||||||
|
|
||||||
|
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
|
||||||
|
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||||
|
|
||||||
|
[/Script/Engine.Engine]
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/Ralpha")
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/Ralpha")
|
||||||
|
|
||||||
|
[URL]
|
||||||
|
GameName=Ralpha
|
||||||
|
|
||||||
|
[/Script/Engine.GarbageCollectionSettings]
|
||||||
|
gc.MaxObjectsNotConsideredByGC=1
|
||||||
|
|
||||||
|
[ConsoleVariables]
|
||||||
|
r.Lumen.ScreenProbeGather.FullResolutionJitterWidth=1
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[/Script/EngineSettings.GeneralProjectSettings]
|
||||||
|
ProjectID=Ralpha2026
|
||||||
|
ProjectName=Ralpha
|
||||||
|
CompanyName=Ralpha
|
||||||
|
CopyrightNotice=Copyright Ralpha Team
|
||||||
|
Description=AI-driven style transfer for infinite world generation
|
||||||
|
bStartInVR=False
|
||||||
|
bStartInAR=False
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
[/Script/Engine.InputSettings]
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Sensitivity=1.f,Exponent=1.f,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Sensitivity=1.f,Exponent=1.f,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Sensitivity=1.f,Exponent=1.f,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Sensitivity=1.f,Exponent=1.f,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
bCaptureMouseOnLaunch=True
|
||||||
|
DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown
|
||||||
|
bDefaultViewportMouseLock=False
|
||||||
|
DefaultViewportMouseLockMode=DoNotLock
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[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
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||||
|
<rect width="20" height="20" rx="3" fill="#1a1a2e"/>
|
||||||
|
<path d="M4 10 L8 6 L12 10 L16 4" stroke="#00d4ff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="10" cy="14" r="2" fill="#ff6b6b"/>
|
||||||
|
<path d="M6 16 L14 16" stroke="#4ecdc4" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 412 B |
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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<FRalphaCoreModule>("RalphaCore");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FRalphaCoreModule::IsAvailable()
|
||||||
|
{
|
||||||
|
return FModuleManager::Get().IsModuleLoaded("RalphaCore");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FRalphaCoreModule, RalphaCore)
|
||||||
|
|
@ -0,0 +1,448 @@
|
||||||
|
// 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<URalphaMCPServer>();
|
||||||
|
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<FInternetAddr> 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<URalphaParameterBridge>(this);
|
||||||
|
ScreenCapture = NewObject<URalphaScreenCapture>(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<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
|
||||||
|
FSocket* ClientSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RalphaClient"));
|
||||||
|
|
||||||
|
if (ClientSocket)
|
||||||
|
{
|
||||||
|
ClientSocket->SetNonBlocking(true);
|
||||||
|
|
||||||
|
TSharedPtr<FRalphaClientConnection> NewConnection = MakeShared<FRalphaClientConnection>(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<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> 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<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
FString Base64Image;
|
||||||
|
FString FilePath;
|
||||||
|
bool 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<FJsonObject> ParamsObject = ParameterBridge->GetAllParameters();
|
||||||
|
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||||
|
ResponseObject->SetObjectField(TEXT("data"), ParamsObject);
|
||||||
|
}
|
||||||
|
else if (CommandType == TEXT("set_post_process"))
|
||||||
|
{
|
||||||
|
const TSharedPtr<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<TSharedPtr<FJsonValue>> 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<FJsonObject>* 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<FJsonObject>* 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("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<TSharedPtr<FJsonValue>> 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<FJsonObject>* 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
|
||||||
|
{
|
||||||
|
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||||
|
ResponseObject->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command type: %s"), *CommandType));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize response
|
||||||
|
FString ResponseString;
|
||||||
|
TSharedRef<TJsonWriter<>> 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<uint8> 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<const char*>(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<const uint8*>(Converter.Get()), Converter.Length(), BytesSent);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,881 @@
|
||||||
|
// 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 "Dom/JsonObject.h"
|
||||||
|
#include "Dom/JsonValue.h"
|
||||||
|
#include "Serialization/JsonSerializer.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<APostProcessVolume> 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<APostProcessVolume> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
return *It;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ADirectionalLight* URalphaParameterBridge::FindDirectionalLight()
|
||||||
|
{
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return nullptr;
|
||||||
|
|
||||||
|
for (TActorIterator<ADirectionalLight> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
return *It;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASkyLight* URalphaParameterBridge::FindSkyLight()
|
||||||
|
{
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return nullptr;
|
||||||
|
|
||||||
|
for (TActorIterator<ASkyLight> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
return *It;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AExponentialHeightFog* URalphaParameterBridge::FindExponentialHeightFog()
|
||||||
|
{
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return nullptr;
|
||||||
|
|
||||||
|
for (TActorIterator<AExponentialHeightFog> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
return *It;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ACineCameraActor* URalphaParameterBridge::FindCineCamera()
|
||||||
|
{
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return nullptr;
|
||||||
|
|
||||||
|
for (TActorIterator<ACineCameraActor> 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<FJsonObject> URalphaParameterBridge::GetAllParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
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<FJsonObject>& 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<FJsonObject> URalphaParameterBridge::GetPostProcessParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
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<FJsonObject>& Params)
|
||||||
|
{
|
||||||
|
ADirectionalLight* Light = FindDirectionalLight();
|
||||||
|
if (!Light)
|
||||||
|
{
|
||||||
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No DirectionalLight found in scene"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(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();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> URalphaParameterBridge::GetDirectionalLightParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
ADirectionalLight* Light = FindDirectionalLight();
|
||||||
|
if (!Light) return Result;
|
||||||
|
|
||||||
|
UDirectionalLightComponent* LightComp = Cast<UDirectionalLightComponent>(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<FJsonObject>& 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<FJsonObject> URalphaParameterBridge::GetSkyLightParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
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<FJsonObject>& 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<FJsonObject> URalphaParameterBridge::GetFogParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
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<FJsonObject>& 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;
|
||||||
|
|
||||||
|
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<FJsonObject> URalphaParameterBridge::GetCameraParameters()
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
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<FJsonObject>& 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<TSharedPtr<FJsonValue>> URalphaParameterBridge::SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults)
|
||||||
|
{
|
||||||
|
TArray<TSharedPtr<FJsonValue>> Results;
|
||||||
|
|
||||||
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("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<FAssetData> 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<FJsonObject> AssetObj = MakeShared<FJsonObject>();
|
||||||
|
AssetObj->SetStringField(TEXT("name"), AssetName);
|
||||||
|
AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString());
|
||||||
|
AssetObj->SetStringField(TEXT("class"), Asset.AssetClassPath.GetAssetName().ToString());
|
||||||
|
|
||||||
|
Results.Add(MakeShared<FJsonValueObject>(AssetObj));
|
||||||
|
Count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive)
|
||||||
|
{
|
||||||
|
TArray<TSharedPtr<FJsonValue>> Results;
|
||||||
|
|
||||||
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||||
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
||||||
|
|
||||||
|
FARFilter Filter;
|
||||||
|
Filter.bRecursivePaths = bRecursive;
|
||||||
|
Filter.PackagePaths.Add(*FolderPath);
|
||||||
|
|
||||||
|
TArray<FAssetData> AssetList;
|
||||||
|
AssetRegistry.GetAssets(Filter, AssetList);
|
||||||
|
|
||||||
|
for (const FAssetData& Asset : AssetList)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
|
||||||
|
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<FJsonValueObject>(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<UStaticMesh>(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<AStaticMeshActor>(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<AActor> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
if (It->GetName() == ActorId)
|
||||||
|
{
|
||||||
|
World->DestroyActor(*It);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<TSharedPtr<FJsonValue>> URalphaParameterBridge::ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms)
|
||||||
|
{
|
||||||
|
TArray<TSharedPtr<FJsonValue>> Results;
|
||||||
|
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return Results;
|
||||||
|
|
||||||
|
for (TActorIterator<AActor> 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<FJsonObject> ActorObj = MakeShared<FJsonObject>();
|
||||||
|
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<FJsonObject> TransformObj = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
||||||
|
LocObj->SetNumberField(TEXT("x"), Location.X);
|
||||||
|
LocObj->SetNumberField(TEXT("y"), Location.Y);
|
||||||
|
LocObj->SetNumberField(TEXT("z"), Location.Z);
|
||||||
|
TransformObj->SetObjectField(TEXT("location"), LocObj);
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
|
||||||
|
RotObj->SetNumberField(TEXT("pitch"), Rotation.Pitch);
|
||||||
|
RotObj->SetNumberField(TEXT("yaw"), Rotation.Yaw);
|
||||||
|
RotObj->SetNumberField(TEXT("roll"), Rotation.Roll);
|
||||||
|
TransformObj->SetObjectField(TEXT("rotation"), RotObj);
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
|
||||||
|
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<FJsonValueObject>(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<AActor> 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<FJsonObject> URalphaParameterBridge::GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||||
|
|
||||||
|
UWorld* World = GetCurrentWorld();
|
||||||
|
if (!World) return Result;
|
||||||
|
|
||||||
|
for (TActorIterator<AActor> 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<FJsonObject> TransformObj = MakeShared<FJsonObject>();
|
||||||
|
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
||||||
|
LocObj->SetNumberField(TEXT("x"), Location.X);
|
||||||
|
LocObj->SetNumberField(TEXT("y"), Location.Y);
|
||||||
|
LocObj->SetNumberField(TEXT("z"), Location.Z);
|
||||||
|
TransformObj->SetObjectField(TEXT("location"), LocObj);
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
|
||||||
|
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<TSharedPtr<FJsonValue>> ComponentsArray;
|
||||||
|
TArray<UActorComponent*> Components;
|
||||||
|
Actor->GetComponents(Components);
|
||||||
|
|
||||||
|
for (UActorComponent* Component : Components)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> CompObj = MakeShared<FJsonObject>();
|
||||||
|
CompObj->SetStringField(TEXT("name"), Component->GetName());
|
||||||
|
CompObj->SetStringField(TEXT("class"), Component->GetClass()->GetName());
|
||||||
|
ComponentsArray.Add(MakeShared<FJsonValueObject>(CompObj));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result->SetArrayField(TEXT("components"), ComponentsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
#include "Editor.h"
|
||||||
|
#include "LevelEditorViewport.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
URalphaScreenCapture::URalphaScreenCapture()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URalphaScreenCapture::CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath)
|
||||||
|
{
|
||||||
|
// Get the game viewport
|
||||||
|
UGameViewportClient* ViewportClient = GEngine ? GEngine->GameViewport : nullptr;
|
||||||
|
|
||||||
|
// In editor, we might not have a game viewport, try to get the active viewport
|
||||||
|
FViewport* Viewport = nullptr;
|
||||||
|
|
||||||
|
if (ViewportClient && ViewportClient->Viewport)
|
||||||
|
{
|
||||||
|
Viewport = ViewportClient->Viewport;
|
||||||
|
}
|
||||||
|
#if WITH_EDITOR
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try to get the editor viewport
|
||||||
|
if (GEditor)
|
||||||
|
{
|
||||||
|
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
|
||||||
|
{
|
||||||
|
if (LevelVC && LevelVC->Viewport)
|
||||||
|
{
|
||||||
|
Viewport = LevelVC->Viewport;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!Viewport)
|
||||||
|
{
|
||||||
|
UE_LOG(LogRalphaMCP, Warning, TEXT("No viewport available for screenshot"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read pixels from the viewport
|
||||||
|
TArray<FColor> Bitmap;
|
||||||
|
int32 ViewportWidth = Viewport->GetSizeXY().X;
|
||||||
|
int32 ViewportHeight = Viewport->GetSizeXY().Y;
|
||||||
|
|
||||||
|
if (ViewportWidth <= 0 || ViewportHeight <= 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogRalphaMCP, Warning, TEXT("Invalid viewport size: %dx%d"), ViewportWidth, ViewportHeight);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the viewport pixels
|
||||||
|
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<IImageWrapperModule>(FName("ImageWrapper"));
|
||||||
|
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
||||||
|
|
||||||
|
if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), ViewportWidth, ViewportHeight, ERGBFormat::BGRA, 8))
|
||||||
|
{
|
||||||
|
TArray64<uint8> 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<FColor>& Pixels, int32 Width, int32 Height)
|
||||||
|
{
|
||||||
|
// Load the image wrapper module
|
||||||
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
||||||
|
TSharedPtr<IImageWrapper> 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<uint8> PNGData = ImageWrapper->GetCompressed(0);
|
||||||
|
if (PNGData.Num() == 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to compress to PNG"));
|
||||||
|
return FString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to base64
|
||||||
|
TArray<uint8> PNGData32;
|
||||||
|
PNGData32.Append(PNGData.GetData(), PNGData.Num());
|
||||||
|
|
||||||
|
return FBase64::Encode(PNGData32);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
// 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<TSharedPtr<FRalphaClientConnection>> ClientConnections;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
URalphaParameterBridge* ParameterBridge;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
URalphaScreenCapture* ScreenCapture;
|
||||||
|
|
||||||
|
FTSTicker::FDelegateHandle TickDelegateHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a single client connection
|
||||||
|
*/
|
||||||
|
class RALPHACORE_API FRalphaClientConnection : public TSharedFromThis<FRalphaClientConnection>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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<FJsonObject> GetAllParameters();
|
||||||
|
|
||||||
|
// Post-Process Volume
|
||||||
|
bool SetPostProcessParameters(const TSharedPtr<FJsonObject>& Params);
|
||||||
|
TSharedPtr<FJsonObject> GetPostProcessParameters();
|
||||||
|
|
||||||
|
// Directional Light (Sun)
|
||||||
|
bool SetDirectionalLightParameters(const TSharedPtr<FJsonObject>& Params);
|
||||||
|
TSharedPtr<FJsonObject> GetDirectionalLightParameters();
|
||||||
|
|
||||||
|
// Sky Light
|
||||||
|
bool SetSkyLightParameters(const TSharedPtr<FJsonObject>& Params);
|
||||||
|
TSharedPtr<FJsonObject> GetSkyLightParameters();
|
||||||
|
|
||||||
|
// Exponential Height Fog
|
||||||
|
bool SetFogParameters(const TSharedPtr<FJsonObject>& Params);
|
||||||
|
TSharedPtr<FJsonObject> GetFogParameters();
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
bool SetCameraParameters(const TSharedPtr<FJsonObject>& Params);
|
||||||
|
TSharedPtr<FJsonObject> GetCameraParameters();
|
||||||
|
|
||||||
|
// Render Quality
|
||||||
|
bool SetRenderQuality(const TSharedPtr<FJsonObject>& Settings);
|
||||||
|
|
||||||
|
// Asset Search
|
||||||
|
TArray<TSharedPtr<FJsonValue>> SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults);
|
||||||
|
TArray<TSharedPtr<FJsonValue>> 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<TSharedPtr<FJsonValue>> 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<FJsonObject> GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Convert raw pixel data to base64 PNG */
|
||||||
|
FString PixelsToBase64PNG(const TArray<FColor>& Pixels, int32 Width, int32 Height);
|
||||||
|
|
||||||
|
/** Get the screenshot save directory */
|
||||||
|
FString GetScreenshotDirectory();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Target.bBuildEditor)
|
||||||
|
{
|
||||||
|
PrivateDependencyModuleNames.Add("UnrealEd");
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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<FSlateStyleSet> 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<FSlateStyleSet> FRalphaEditorStyle::Create()
|
||||||
|
{
|
||||||
|
TSharedRef<FSlateStyleSet> 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
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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<class FUICommandList> PluginCommands;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright Ralpha Team. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Framework/Commands/Commands.h"
|
||||||
|
#include "RalphaEditorStyle.h"
|
||||||
|
|
||||||
|
class FRalphaEditorCommands : public TCommands<FRalphaEditorCommands>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FRalphaEditorCommands()
|
||||||
|
: TCommands<FRalphaEditorCommands>(
|
||||||
|
TEXT("Ralpha"),
|
||||||
|
NSLOCTEXT("Contexts", "Ralpha", "Ralpha Plugin"),
|
||||||
|
NAME_None,
|
||||||
|
FRalphaEditorStyle::GetStyleSetName())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void RegisterCommands() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TSharedPtr<FUICommandInfo> StartServer;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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<class FSlateStyleSet> Create();
|
||||||
|
static TSharedPtr<class FSlateStyleSet> StyleInstance;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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[]
|
||||||
|
{
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"EngineAssociation": "5.7",
|
||||||
|
"Category": "",
|
||||||
|
"Description": "AI-driven style transfer for infinite world generation",
|
||||||
|
"Modules": [],
|
||||||
|
"Plugins": [
|
||||||
|
{
|
||||||
|
"Name": "RalphaPlugin",
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TargetPlatforms": [
|
||||||
|
"Windows"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue