Add kitchen sink structure with catalogue, recipes, and presets

Structure:
- RalphaData/catalogue/ - Machine-readable asset index with JSON schema
- RalphaData/recipes/ - Scene composition templates (living room, office, portrait)
- RalphaData/presets/ - Lighting, post-processing, camera presets
- RalphaData/scripts/ - Catalogue generation tooling
- Content/Ralpha/Assets/ - Folder structure for all asset categories

Key files:
- catalogue.schema.json - Asset metadata schema with tags, bounds, licensing
- recipe.schema.json - Scene composition schema with placement modes
- generate_catalogue.py - Asset indexing script
- LICENSE_POLICY.md - Asset provenance requirements

Updated .gitattributes to keep RalphaData in regular git (not LFS)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamestagg 2026-01-20 15:34:14 -08:00
parent cd3dd56f26
commit 22cac6b96b
34 changed files with 1129 additions and 4163 deletions

15
.gitattributes vendored
View File

@ -39,3 +39,18 @@
*.dll filter=lfs diff=lfs merge=lfs -text *.dll filter=lfs diff=lfs merge=lfs -text
*.so filter=lfs diff=lfs merge=lfs -text *.so filter=lfs diff=lfs merge=lfs -text
*.dylib 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

110
LICENSE_POLICY.md Normal file
View File

@ -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*

1
Plugins/RalphaPlugin Symbolic link
View File

@ -0,0 +1 @@
../../ralpha/plugin

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 412 B

View File

@ -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<FRalphaCoreModule>("RalphaCore");
}
bool FRalphaCoreModule::IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("RalphaCore");
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRalphaCoreModule, RalphaCore)

View File

@ -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<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;
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<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("spawn_ocean"))
{
FVector Location = FVector::ZeroVector;
const TSharedPtr<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<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 if (CommandType == TEXT("setup_scene"))
{
TSharedPtr<FJsonObject> 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<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 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<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 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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<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->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<FJsonObject>* 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<FJsonObject>* 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<FJsonObject>* 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<TSharedPtr<FJsonValue>> 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<FJsonObject>* 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<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;
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<FJsonObject> 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<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);
}

View File

@ -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<FLevelEditorModule>("LevelEditor");
TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor();
if (LevelEditor.IsValid())
{
TSharedPtr<SLevelViewport> 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<FColor> 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<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);
}
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<ACineCameraActor> 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<UTextureRenderTarget2D>();
RenderTarget->InitAutoFormat(Width, Height);
RenderTarget->UpdateResourceImmediate(true);
// Create scene capture component
USceneCaptureComponent2D* SceneCapture = NewObject<USceneCaptureComponent2D>(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<FColor> 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<IImageWrapperModule>(FName("ImageWrapper"));
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8))
{
TArray64<uint8> 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;
}

View File

@ -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();
};

View File

@ -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<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;
};

View File

@ -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<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);
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<FJsonObject> 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<FJsonObject>& 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<FJsonObject>& 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<FJsonObject>& 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();
};

View File

@ -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<FColor>& Pixels, int32 Width, int32 Height);
/** Get the screenshot save directory */
FString GetScreenshotDirectory();
};

View File

@ -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[]
{
}
);
}
}

View File

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

View File

@ -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

View File

@ -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<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

View File

@ -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<class FUICommandList> PluginCommands;
};

View File

@ -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<FRalphaEditorCommands>
{
public:
FRalphaEditorCommands()
: TCommands<FRalphaEditorCommands>(
TEXT("Ralpha"),
NSLOCTEXT("Contexts", "Ralpha", "Ralpha Plugin"),
NAME_None,
FRalphaEditorStyle::GetStyleSetName())
{
}
virtual void RegisterCommands() override;
public:
TSharedPtr<FUICommandInfo> StartServer;
};

View File

@ -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<class FSlateStyleSet> Create();
static TSharedPtr<class FSlateStyleSet> StyleInstance;
};

View File

@ -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[]
{
}
);
}
}

188
README.md Normal file
View File

@ -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

View File

@ -15,6 +15,7 @@
} }
], ],
"TargetPlatforms": [ "TargetPlatforms": [
"Mac",
"Windows" "Windows"
] ]
} }

View File

@ -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": []
}

View File

@ -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"
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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"
}
}
}
}
}

View File

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